これは、なにをしたくて書いたもの?
こちらのエントリーを書いている時にmypyとtypes-PyMySQLでConnection
を扱う時にちょっとハマったので、単独のエントリーとして
メモしておきます。
PyMySQLでinsert文(またはreplace文)を高速に実行するにはexecutemanyを使う - CLOVER🍀
全然情報がなくて困ったので。
なんの話?
PyMySQLには型アノテーションの定義が含まれておらず、typeshedが提供するtypes-PyMySQLを使うことになります。
こちらのConnection
を扱ってmypyで型チェックをする時にハマりました。
環境
今回の環境はこちら。
$ uv --version uv 0.5.14 $ python3 --version Python 3.12.3
MySQLは172.17.0.2でアクセスできるものとします。
MySQL localhost:3306 ssl practice SQL > select version(); +-----------+ | version() | +-----------+ | 8.4.3 | +-----------+ 1 row in set (0.0007 sec)
準備
プロジェクトの作成。
$ uv init --vcs none types-pymysql-connection $ cd types-pymysql-connection
ライブラリーのインストール。
$ uv add PyMySQL[rsa] $ uv add --dev mypy types-PyMySQL
pyproject.toml
[project] name = "types-pymysql-connection" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [ "pymysql[rsa]>=1.1.1", ] [dependency-groups] dev = [ "mypy>=1.14.1", "types-pymysql>=1.1.0.20241103", ] [tool.mypy] strict = true disallow_any_unimported = true disallow_any_expr = true disallow_any_explicit = true warn_unreachable = true pretty = true
インストールされたライブラリーの一覧。
$ uv pip list Package Version ----------------- -------------- cffi 1.17.1 cryptography 44.0.0 mypy 1.14.1 mypy-extensions 1.0.0 pycparser 2.22 pymysql 1.1.1 types-pymysql 1.1.0.20241103 typing-extensions 4.12.2
困ったこと
困ったことは2つあります。
最初はcursorclass
の指定方法。こんなサンプルを書いてみます。
connect_mysql.py
import pymysql with pymysql.connect( host="172.17.0.2", port=3306, user="kazuhira", password="password", database="practice", cursorclass=pymysql.cursors.DictCursor ) as connection: print(f"auto commit mode = {connection.get_autocommit()}")
cursorclass
にはDictCursor
を指定しています。これはドキュメントのサンプルに書かれているものそのものです。
Examples — PyMySQL 0.7.2 documentation
もちろん実行できます。
$ uv run connect_mysql.py auto commit mode = False
ですが、mypyでチェックすると怒られます。
$ uv run mypy . connect_mysql.py:9: error: Expression type contains "Any" (has type "type[DictCursor]") [misc] cursorclass=pymysql.cursors.DictCursor ^~~~~~~~~~~~~~~~~~~~~~~~~~ Found 1 error in 1 file (checked 1 source file)
Any
扱いになってしまうようです。
ここで以下のようにtype[DictCursor]
型の変数を作成すると、パスするようになりました。
connect_mysql.py
import pymysql from pymysql.cursors import DictCursor DictCursorType: type[DictCursor] = DictCursor with pymysql.connect( host="172.17.0.2", port=3306, user="kazuhira", password="password", database="practice", #cursorclass=pymysql.cursors.DictCursor cursorclass=DictCursorType ) as connection: print(f"auto commit mode = {connection.get_autocommit()}")
この部分ですね。クラスオブジェクトの型を定義していることになります。
DictCursorType: type[DictCursor] = DictCursor
typing --- 型ヒントのサポート — Python 3.12.8 ドキュメント
これでmypyのチェックがパスするようになりました。
$ uv run mypy . Success: no issues found in 1 source file
実行できる点も変わりません。
$ uv run connect_mysql.py auto commit mode = False
2つ目はConnection
の型を明示的に宣言する場合です。以下のようなサンプルを作ってみます。
※type[DictCursor]
は含めたものにしています
connect_mysql2.py
import pymysql from pymysql.cursors import DictCursor from pymysql.connections import Connection def connect_mysql() -> Connection: DictCursorType: type[DictCursor] = DictCursor return pymysql.connect( host="172.17.0.2", port=3306, user="kazuhira", password="password", database="practice", cursorclass=DictCursorType ) with connect_mysql() as connection: print(f"auto commit mode = {connection.get_autocommit()}")
これも問題なく実行できます。
$ uv run connect_mysql2.py auto commit mode = False
ところがmypyでチェックすると、Connection
に型パラメーターがないと怒られます。
$ uv run mypy . connect_mysql2.py:5: error: Missing type parameters for generic type "Connection" [type-arg] def connect_mysql() -> Connection: ^ connect_mysql2.py:8: error: Expression type contains "Any" (has type "Connection[Any]") [misc] return pymysql.connect( ^ connect_mysql2.py:17: error: Expression type contains "Any" (has type "Connection[Any]") [misc] with connect_mysql() as connection: ^~~~~~~~~~~~~~~ connect_mysql2.py:18: error: Expression type contains "Any" (has type "Connection[Any]") [misc] print(f"auto commit mode = {connection.get_autocommit()}") ^~~~~~~~~~ Found 4 errors in 1 file (checked 2 source files)
Connection
を扱う部分がほぼNGになりますね。
もちろんPyMySQLのConnection
には型パラメーターなどありません。
Connection Object — PyMySQL 0.7.2 documentation
ここでtypeshedにあるPyMySQLのスタブファイルを見ると、Connection
が型パラメーターを取るようになっています。
class Connection(Generic[_C]):
typeshed/stubs/PyMySQL/pymysql/connections.pyi at main · python/typeshed · GitHub
_C
というのはどうやらCursor
を指しているようです。
_C = TypeVar("_C", bound=Cursor)
ではこうすればいいのかなと修正してみます。
def connect_mysql() -> Connection[DictCursor]:
mypyのチェックが通るようになりました。
$ uv run mypy . Success: no issues found in 2 source files
ですが今度は、実行できなくなってしまいました…。添字は付かないはずだ、と怒られています。
$ uv run connect_mysql2.py Traceback (most recent call last): File "/path/to/types-pymysql-connection/connect_mysql2.py", line 5, in <module> def connect_mysql() -> Connection[DictCursor]: ~~~~~~~~~~^^^^^^^^^^^^ TypeError: type 'Connection' is not subscriptable
そこで、戻り値の型を文字列にしてみます。
def connect_mysql() -> "Connection[DictCursor]":
こうすることで、mypyのチェックもパスしつつ
$ uv run mypy . Success: no issues found in 2 source files
実行もできるようになります。
$ uv run connect_mysql2.py auto commit mode = False
このあたりを通すのにけっこう苦労しました…。
おわりに
types-PyMySQLでConnection
を扱う時にハマった話を書いてみました。
あまり情報がなく、解決するのに苦労しましたね…。
戻り値の型を文字列で宣言する方法があることを忘れていたので、そこが盲点だったかもしれません。
Pythonで型ヒント(Type Hints)を試してみる(+Mypy) - CLOVER🍀
Pythonのバージョンによっては問題にならないかも?といった話もあるようですが…。