これは、なにをしたくて書いたもの?
こちらのエントリーを書いている時に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のバージョンによっては問題にならないかも?といった話もあるようですが…。