From 462540826b13196993702b519e2aad000dd599ca Mon Sep 17 00:00:00 2001 From: Nik Date: Mon, 15 Jun 2026 03:56:04 +0200 Subject: [PATCH 1/2] Add MariaDB/MySQL support, exclude dialect tests from CI autorun, add infra scripts for example project --- .github/workflows/ci.yml | 8 - .github/workflows/dialect-mariadb.yml | 52 +++++++ .github/workflows/dialect-mysql.yml | 52 +++++++ .github/workflows/test-dialects.yml | 43 ++++++ README.md | 21 ++- example_project/Makefile | 21 ++- example_project/docker-compose.yml | 18 +++ example_project/pyproject.toml | 7 + example_project/uv.lock | 137 +++++++++++++++++- pyproject.toml | 5 + .../dialects/__init__.py | 11 ++ .../dialects/mariadb.py | 58 ++++++++ .../dialects/postgresql.py | 2 + 13 files changed, 418 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/dialect-mariadb.yml create mode 100644 .github/workflows/dialect-mysql.yml create mode 100644 .github/workflows/test-dialects.yml create mode 100644 src/pytest_sqlalchemy_alembic/dialects/mariadb.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e2829d..46be953 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,14 +4,6 @@ on: pull_request: jobs: - dialect-sqlite: - uses: ./.github/workflows/dialect-sqlite.yml - secrets: inherit - - dialect-postgresql: - uses: ./.github/workflows/dialect-postgresql.yml - secrets: inherit - static-analysis: name: Static analysis runs-on: ubuntu-latest diff --git a/.github/workflows/dialect-mariadb.yml b/.github/workflows/dialect-mariadb.yml new file mode 100644 index 0000000..b75a7e7 --- /dev/null +++ b/.github/workflows/dialect-mariadb.yml @@ -0,0 +1,52 @@ +name: "MariaDB dialect tests" + +on: + workflow_call: + +jobs: + dialect-mariadb-tests: + name: Dialect tests (MariaDB) + runs-on: ubuntu-latest + strategy: + matrix: + python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + mariadb_version: ["10.6", "10.11", "11.8", "12.3"] + drivername: ["mariadb+mysqldb", "mariadb+pymysql", "mariadb+mariadbconnector", "mariadb+cymysql"] + + services: + mariadb: + image: mariadb:${{ matrix.mariadb_version }} + env: + MARIADB_DATABASE: example + MARIADB_ROOT_PASSWORD: password + ports: + - 3306:3306 + options: >- + --health-cmd "healthcheck.sh --connect --innodb_initialized" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 3 + + env: + UV_PYTHON: ${{ matrix.python_version }} + DATABASE_URL: ${{ matrix.drivername }}://root:password@localhost:3306/example + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python_version }} + + - name: Install uv + uses: astral-sh/setup-uv@v8.2.0 + with: + enable-cache: true + cache-suffix: mariadb-${{ matrix.python_version }} + + - name: Run example project tests + working-directory: example_project + run: uv run --extra mariadb pytest diff --git a/.github/workflows/dialect-mysql.yml b/.github/workflows/dialect-mysql.yml new file mode 100644 index 0000000..f73eb7e --- /dev/null +++ b/.github/workflows/dialect-mysql.yml @@ -0,0 +1,52 @@ +name: "MySQL dialect tests" + +on: + workflow_call: + +jobs: + dialect-mysql-tests: + name: Dialect tests (MySQL) + runs-on: ubuntu-latest + strategy: + matrix: + python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + mysql_version: ["8.0", "8.4", "9.0"] + drivername: ["mysql+mysqldb", "mysql+pymysql", "mysql+mysqlconnector", "mysql+cymysql"] + + services: + mysql: + image: mysql:${{ matrix.mysql_version }} + env: + MYSQL_DATABASE: example + MYSQL_ROOT_PASSWORD: password + ports: + - 3306:3306 + options: >- + --health-cmd "healthcheck.sh --connect --innodb_initialized" + --health-start-period 10s + --health-interval 10s + --health-timeout 5s + --health-retries 3 + + env: + UV_PYTHON: ${{ matrix.python_version }} + DATABASE_URL: ${{ matrix.drivername }}://root:password@localhost:3306/example + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python_version }} + + - name: Install uv + uses: astral-sh/setup-uv@v8.2.0 + with: + enable-cache: true + cache-suffix: mariadb-${{ matrix.python_version }} + + - name: Run example project tests + working-directory: example_project + run: uv run --extra mariadb pytest diff --git a/.github/workflows/test-dialects.yml b/.github/workflows/test-dialects.yml new file mode 100644 index 0000000..4def363 --- /dev/null +++ b/.github/workflows/test-dialects.yml @@ -0,0 +1,43 @@ +name: "Test all dialects" + +on: + workflow_call: + workflow_dispatch: + inputs: + sqlite: + type: boolean + description: "Run SQLite tests" + default: true + postgresql: + type: boolean + description: "Run PostgreSQL tests" + default: true + mariadb: + type: boolean + description: "Run MariaDB tests" + default: true + mysql: + type: boolean + description: "Run MySQL tests" + default: true + +jobs: + dialect-sqlite: + if: ${{ github.event.inputs.sqlite == 'true' }} + uses: ./.github/workflows/dialect-sqlite.yml + secrets: inherit + + dialect-postgresql: + if: ${{ github.event.inputs.postgresql == 'true' }} + uses: ./.github/workflows/dialect-postgresql.yml + secrets: inherit + + dialect-mariadb: + if: ${{ github.event.inputs.mariadb == 'true' }} + uses: ./.github/workflows/dialect-mariadb.yml + secrets: inherit + + dialect-mysql: + if: ${{ github.event.inputs.mysql == 'true' }} + uses: ./.github/workflows/dialect-mysql.yml + secrets: inherit diff --git a/README.md b/README.md index f1bcee9..67e746c 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,25 @@ It automatically provisions a dedicated test database per worker and runs Alembi | Driver | Status | | ------ | ------ | +| `sqlite+pysqlite` | ✅ Supported +| `sqlite+pysqlcipher` | ✅ Supported | `postgresql+psycopg` | ✅ Supported | `postgresql+psycopg2` | ✅ Supported -| `postgresql+pg8000` | ❌ -| `postgresql+psycopg2cffi` | ❌ +| `postgresql+pg8000` | ✅ Supported +| `postgresql+psycopg2cffi` | ✅ Supported | `postgresql+asyncpg` | ❌ -| `mysql+pymysql` | ❌ -| `mariadb+pymysql` | ❌ -| `sqlite` | ✅ Supported +| `mariadb+mysqldb` | ✅ Supported +| `mariadb+pymysql` | ✅ Supported +| `mariadb+mariadbconnector` | ✅ Supported +| `mariadb+asyncmy` | ❌ +| `mariadb+aiomysql` | ❌ +| `mariadb+cymysql` | ✅ Supported +| `mysql+mysqldb` | ✅ Supported +| `mysql+pymysql` | ✅ Supported +| `mysql+mysqlconnector` | ✅ Supported +| `mysql+asyncmy` | ❌ +| `mysql+aiomysql` | ❌ +| `mysql+cymysql` | ✅ Supported > [!NOTE] > If you need an implementation for your particular SQLAlchemy driver, please consider contributing to this project. diff --git a/example_project/Makefile b/example_project/Makefile index 0600bea..5277ace 100644 --- a/example_project/Makefile +++ b/example_project/Makefile @@ -1,7 +1,22 @@ SHELL := /bin/bash +.DEFAULT_GOAL := help -.PHONY: dev_infra +help: ## Show this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' -infra_postgres: - docker compose -p example_project -f docker-compose.yml up -d --build postgres +.PHONY: infra_postgres +infra_postgres: ## Start PostgreSQL container + @docker compose -p example_project -f docker-compose.yml up -d --build postgres + +.PHONY: infra_mariadb +infra_mariadb: ## Start MariaDB container + @docker compose -p example_project -f docker-compose.yml up -d --build mariadb + +.PHONY: infra_down +infra_down: ## Stop and remove all containers + @docker compose -p example_project -f docker-compose.yml down + +.PHONY: infra_down_volume_purge +infra_down_volume_purge: ## Stop and delete all volumes used by the project + @docker compose -p example_project -f docker-compose.yml down --volumes diff --git a/example_project/docker-compose.yml b/example_project/docker-compose.yml index e1e006e..2fadba1 100644 --- a/example_project/docker-compose.yml +++ b/example_project/docker-compose.yml @@ -16,5 +16,23 @@ services: timeout: 5s retries: 5 + mariadb: + image: mariadb:12.3 + volumes: + - mariadb_data:/var/lib/mysql + ports: + - 127.0.0.1:3306:3306 + environment: + - MARIADB_DATABASE=example + - MARIADB_ROOT_PASSWORD=example + restart: on-failure + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + start_period: 10s + interval: 10s + timeout: 5s + retries: 3 + volumes: postgres_data: + mariadb_data: diff --git a/example_project/pyproject.toml b/example_project/pyproject.toml index 541aa43..35287a0 100644 --- a/example_project/pyproject.toml +++ b/example_project/pyproject.toml @@ -22,6 +22,13 @@ postgres = [ sqlite = [ "sqlcipher3>=0.6.2", ] +mariadb = [ + "cymysql>=1.1.1", + "mariadb>=1.1.14", + "mysql-connector-python>=9.7.0", + "mysqlclient>=2.2.8", + "pymysql>=1.2.0", +] [dependency-groups] dev = [ diff --git a/example_project/uv.lock b/example_project/uv.lock index 53f6372..53ab1c7 100644 --- a/example_project/uv.lock +++ b/example_project/uv.lock @@ -180,6 +180,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "cymysql" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycryptodome" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/11/41f0df6fd5755001310b3a47aef17d051d5e9b568b1b6e7dd7ae114c7b7a/cymysql-1.1.1.tar.gz", hash = "sha256:c0ace2fa0df92aaed1050d18e039bbea2ed4b65faa5083f3df80e6dc07ca99e5", size = 50403, upload-time = "2025-07-11T08:51:49.672Z" } + [[package]] name = "decorator" version = "5.3.1" @@ -200,6 +209,13 @@ dependencies = [ ] [package.optional-dependencies] +mariadb = [ + { name = "cymysql" }, + { name = "mariadb" }, + { name = "mysql-connector-python" }, + { name = "mysqlclient" }, + { name = "pymysql" }, +] postgres = [ { name = "pg8000" }, { name = "psycopg" }, @@ -222,15 +238,20 @@ dev = [ [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.18.4" }, + { name = "cymysql", marker = "extra == 'mariadb'", specifier = ">=1.1.1" }, + { name = "mariadb", marker = "extra == 'mariadb'", specifier = ">=1.1.14" }, + { name = "mysql-connector-python", marker = "extra == 'mariadb'", specifier = ">=9.7.0" }, + { name = "mysqlclient", marker = "extra == 'mariadb'", specifier = ">=2.2.8" }, { name = "pg8000", marker = "extra == 'postgres'", specifier = ">=1.31.5" }, { name = "psycopg", marker = "extra == 'postgres'", specifier = ">=3.3.4" }, { name = "psycopg2", marker = "extra == 'postgres'", specifier = ">=2.9.12" }, { name = "psycopg2cffi", marker = "extra == 'postgres'", specifier = ">=2.9.0" }, { name = "pydantic-settings", specifier = ">=2.14.1" }, + { name = "pymysql", marker = "extra == 'mariadb'", specifier = ">=1.2.0" }, { name = "sqlalchemy", specifier = ">=2.0.41" }, { name = "sqlcipher3", marker = "extra == 'sqlite'", specifier = ">=0.6.2" }, ] -provides-extras = ["postgres", "sqlite"] +provides-extras = ["postgres", "sqlite", "mariadb"] [package.metadata.requires-dev] dev = [ @@ -513,6 +534,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, ] +[[package]] +name = "mariadb" +version = "1.1.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/ba/cedef19833be88e07bfff11964441cda8a998f1628dd3b2fa3e7751d36e0/mariadb-1.1.14.tar.gz", hash = "sha256:e6d702a53eccf20922e47f2f45cfb5c7a0c2c6c0a46e4ee2d8a80d0ff4a52f34", size = 111715, upload-time = "2025-10-07T06:45:48.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/bf/5e1a3a5be297c3a679e08f5359165491508fbfb64faf854dc1d626cea9c0/mariadb-1.1.14-cp310-cp310-win32.whl", hash = "sha256:4c7f33578da163a1b79929aae241f5f981d7b9d5a94d89e589aad7ec58e313ea", size = 185064, upload-time = "2025-10-07T06:45:24.858Z" }, + { url = "https://files.pythonhosted.org/packages/31/30/3a61991c13cb8257f5db64aca12bafaa3d811d407e1fae019139fd17c99b/mariadb-1.1.14-cp310-cp310-win_amd64.whl", hash = "sha256:3f6fdc4ded5e0500a6a29bf0c8bf1be94189dcef5a8d5e0e154a4b3456f86bcc", size = 202020, upload-time = "2025-10-07T06:45:27.785Z" }, + { url = "https://files.pythonhosted.org/packages/56/aa/a7b3c66b2792e8319ec9157d63851ff2e0b26496a05044e22b50a012a05e/mariadb-1.1.14-cp311-cp311-win32.whl", hash = "sha256:932a95016b7e9b8d78893aa5ee608e74199e3c6dd607dbe5e4da2010a4f67b88", size = 185061, upload-time = "2025-10-07T06:45:29.964Z" }, + { url = "https://files.pythonhosted.org/packages/54/04/ea2374867756b4082764484bc8b82e1798d94f171bcc914e08c60d640f8f/mariadb-1.1.14-cp311-cp311-win_amd64.whl", hash = "sha256:55ddbe5272c292cbcb2968d87681b5d2b327e65646a015e324b8eeb804d14531", size = 202016, upload-time = "2025-10-07T06:45:32.151Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/659a8d30513700b5921ec96bddc07f550016c045fcbeb199d8cd18476ecc/mariadb-1.1.14-cp312-cp312-win32.whl", hash = "sha256:98d552a8bb599eceaa88f65002ad00bd88aeed160592c273a7e5c1d79ab733dd", size = 185266, upload-time = "2025-10-07T06:45:34.164Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a9/8f210291bc5fc044e20497454f40d35b3bab326e2cab6fccdc38121cb2c1/mariadb-1.1.14-cp312-cp312-win_amd64.whl", hash = "sha256:685a1ad2a24fd0aae1c4416fe0ac794adc84ab9209c8d0c57078f770d39731db", size = 202112, upload-time = "2025-10-07T06:45:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/f3/42/51130048bcce038bb859978250515f1aad90e9c4d273630a704e0a8b1ae1/mariadb-1.1.14-cp313-cp313-win32.whl", hash = "sha256:3d2c795cde606f4e12c0d73282b062433f414cae035675b0d81f2d65c9b79ac5", size = 185221, upload-time = "2025-10-07T06:45:37.848Z" }, + { url = "https://files.pythonhosted.org/packages/af/23/e952a7e442913abd8079cd27b80b69474895c93b3727fad41c7642a80c62/mariadb-1.1.14-cp313-cp313-win_amd64.whl", hash = "sha256:7fd603c5cf23c47ef0d28fdc2b4b79919ee7f75d00ed070d3cd1054dcf816aeb", size = 202121, upload-time = "2025-10-07T06:45:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f2/7059f83543a4264b98777d7cc8aa203e1ca6a13a461f730d1c97f29628d4/mariadb-1.1.14-cp314-cp314-win32.whl", hash = "sha256:1a50b4612c0dd5b69690cebb34cef552a7f64dcadeb5aa91d70cd99bf01bc5b3", size = 190620, upload-time = "2025-10-07T06:45:41.349Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7c/7e094b0b396d742494f6346f2ffa9709e429970b0461aca50526f5f02f12/mariadb-1.1.14-cp314-cp314-win_amd64.whl", hash = "sha256:6659725837e48fa6af05e20128fb525029f706f1921d5dbf639a25b2f80b9f93", size = 206263, upload-time = "2025-10-07T06:45:43.227Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -678,6 +720,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "mysql-connector-python" +version = "9.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/c9/a9446dbebbcdf7d828d0a3be9049607eab6eeffb4e46ef1ee8ac304baede/mysql_connector_python-9.7.0.tar.gz", hash = "sha256:933887e71c871b6e9d8908459fe8303ebcf8feb5cc1e1c49caa6490e525cf78e", size = 12254829, upload-time = "2026-04-30T07:55:39.797Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/7b/bfbe1732bdc413fa29d4431e04f257bed32b0f3efe775ca2e70e9d347008/mysql_connector_python-9.7.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ee90c5f44f706f012be17f03f6ad158ff96e7f2dcc077896fe4537d3d28b3cf4", size = 20265583, upload-time = "2026-04-23T07:15:43.703Z" }, + { url = "https://files.pythonhosted.org/packages/43/40/cba971fdc54522742955f12d4b019e9f3325d9a5c734abf5f012fde7cfff/mysql_connector_python-9.7.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2f371ab69d65c61136c51ad7026017400166cef3c959cab7a9fb668c7acbfba", size = 19826949, upload-time = "2026-04-23T07:15:46.443Z" }, + { url = "https://files.pythonhosted.org/packages/83/5c/724577da77cd33d056ad48d1e29149f6c123371d651c0d824f6bfd2af28f/mysql_connector_python-9.7.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:9bdfc2d4c4444cd1cc79cc6487c047b28fe2b26d0327b27eb9f5737bb553cb5c", size = 21917561, upload-time = "2026-04-23T07:15:49.077Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/f0184970f6483a4e5ffcb99028f8402f3789b885872a5779edd3fa53da44/mysql_connector_python-9.7.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6546e0b60c275409a5add9e3308c3897fcf478d1338cd845b1664c1a8946f72f", size = 21687512, upload-time = "2026-04-23T07:15:51.614Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/0be8e060376e518897f4b3433e768ccd05bc8bb3d08c436cc2441b44ac0b/mysql_connector_python-9.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:c51be697bfdfdf63bb71c5ecc51f7c6faf4aaa3d14a0136fa16e97cc37df1185", size = 17678391, upload-time = "2026-04-23T07:15:54.626Z" }, + { url = "https://files.pythonhosted.org/packages/70/fa/babe981ec8c24eece7f47dc52c5e3fe3f126bc99cc80d637b49ac2fe50a4/mysql_connector_python-9.7.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:b5cb8a3ba42b539f79cd13e4c8376d28506f3180f7079c9b04ea7bfd0424fb03", size = 20265659, upload-time = "2026-04-23T07:15:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4b/c45b8b601b0270faf1d4384e4c7270af9abb8d95ea39425253217c3c236c/mysql_connector_python-9.7.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:5492d57a6a0e5127a928290737fbb91b66b46d31dac8de3e7604e550bf3b3a6e", size = 19826940, upload-time = "2026-04-23T07:16:00.156Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cf/9d93f8325697b4cde1e9ebb06cecf5eca61135503e111d1bf533255f03d6/mysql_connector_python-9.7.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:daf70f7da64a2e7c17e6dfd1fc98d2deb653fd955d0bd0b1fef246356e682b0f", size = 21923121, upload-time = "2026-04-30T07:54:04.273Z" }, + { url = "https://files.pythonhosted.org/packages/de/f5/2177f1e0371483204998389d8d999264c2d6567875b9f1a6b59df8f68e76/mysql_connector_python-9.7.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:b8d3d6f32b95ae1d0a2f29d1a2b4e628849bcf7e84a70dc56c85b4929361d86b", size = 21693369, upload-time = "2026-04-30T07:54:06.841Z" }, + { url = "https://files.pythonhosted.org/packages/75/68/1f7469669ba1b7d70bec4076766f7672190021090d6eb7e9a0ca6097f501/mysql_connector_python-9.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:079ce68d617250ef5cefffd5243056dc9f87c6034c804235fd6f45a0daf5a6ae", size = 17677742, upload-time = "2026-04-30T07:54:09.486Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2d/3fb86f8646c07c32b58e9f5bf0975472c62d07efd9e0918f2b5b64ef1f20/mysql_connector_python-9.7.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2eae45230b5cbd783d68bdfe8b05ad9b4ebd06799f8d302a6169d7f025572baf", size = 20270146, upload-time = "2026-04-30T07:54:12.214Z" }, + { url = "https://files.pythonhosted.org/packages/e8/87/f25195a824e8eb33f51113d85ff5bc3a3ec0430f6a537198d7d1a82fa1b4/mysql_connector_python-9.7.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:15106ed73d74487f86de6b1859ff7f362efca7c7f9c494497ccb7439d3139fe6", size = 19830228, upload-time = "2026-04-30T07:54:14.498Z" }, + { url = "https://files.pythonhosted.org/packages/2a/b0/f1ffcc88781a7099a8f32ec61a70d53f83432fd0c1f7be349d44810b65d5/mysql_connector_python-9.7.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:45491d4ce56722cb335e6d0bde2d4f4a98b7073421bd02a04ad5e6220d69e499", size = 21925372, upload-time = "2026-04-30T07:54:16.732Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6a/5488a05a7c56eac2ef127f5f74ccf604cc659d33616a3d48eb82ce35f1f5/mysql_connector_python-9.7.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d5924a76b530159c02f2fe8da4d3c6377ce1f5e195827e8ecbd36124673651d3", size = 21695866, upload-time = "2026-04-30T07:54:19.031Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f5/9b07250bc98c7abae0e538423fe8a2fda0f799c606dbe82014412299340c/mysql_connector_python-9.7.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e3842ecd62391a28c1dda5eb817f40418715e68482a8c146b3c478ac8bb7a23f", size = 20270283, upload-time = "2026-04-30T07:54:21.23Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/70a1549a52add00775e073fdd7b7b57c21a330ea6f01b26ea8ef0ac3dba8/mysql_connector_python-9.7.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:9da73e212bb08e4df286506ffbda943063bbce448375a7db29748bb367f4e0af", size = 19830388, upload-time = "2026-04-30T07:54:23.51Z" }, + { url = "https://files.pythonhosted.org/packages/81/f1/ef64f3c715f55d238ac56b0e0278408501ddc60aa62affa056eab167e28c/mysql_connector_python-9.7.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:39bf31cdbe920eae08801eb829584dc27f70dadbdb3a5c3b78402f54cea87ab0", size = 21925396, upload-time = "2026-04-30T07:54:25.657Z" }, + { url = "https://files.pythonhosted.org/packages/2d/17/e957dad49741680b7d0f69aef2b2144b22061c50581aaf0022ccf8de7e38/mysql_connector_python-9.7.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:236a4b8abfac8217517ce9e9edc735decf55282ada3890b3cd33770009f33d68", size = 21695834, upload-time = "2026-04-30T07:54:28.179Z" }, + { url = "https://files.pythonhosted.org/packages/08/b6/bc9018fb02748ef83acbabbb4ef8467162cf39fc207e4e575595ffe22fd0/mysql_connector_python-9.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:229ffce2333b88b59f8f034881e12dc85b309615338be9f843ed63923db99d52", size = 17678686, upload-time = "2026-04-30T07:54:30.317Z" }, + { url = "https://files.pythonhosted.org/packages/46/db/453c6916b4b1cad6c8e56631e67f81e0d01b52690de541894728c2122f70/mysql_connector_python-9.7.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e17ceb79c1c4c137a5994f8ca8c6a1ce8ebf83eda3ccc21dd67f2c1398680f0", size = 20270277, upload-time = "2026-04-30T07:54:32.628Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6d/3ff4dc01806511a3741764eafd5c28b73de18d3a3dfb391d4e4cfe51e1ca/mysql_connector_python-9.7.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:272c4e8263f6a514e4ae65e9553ed214fc9d4cf1f0f6bedca30def20c2ae1d52", size = 19830318, upload-time = "2026-04-30T07:54:34.952Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/b55c8f84a86071e5038567849c9cae4e3c96b211ce843c0f1808528bc202/mysql_connector_python-9.7.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:9549a2974353407427b574f005c92f03972e303182b4c3b4a272bf4ca10855ed", size = 21924702, upload-time = "2026-04-30T07:54:37.418Z" }, + { url = "https://files.pythonhosted.org/packages/61/3e/2ab7a525209826bde620f9029b2c8f340a16b4151384813827477d475569/mysql_connector_python-9.7.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ea4e05ab864ab8b0b5066d885d89ca51e5ba1320dba520c20a6f9388a8eca022", size = 21696426, upload-time = "2026-04-30T07:54:39.938Z" }, + { url = "https://files.pythonhosted.org/packages/af/94/e370c40a6863752ba6886f20b960284daeef78b2a0e23f38d141b4aa7e76/mysql_connector_python-9.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:5a5abbc152bc28cb2e64a04605ecd9941eff6b0dc5f9528cb84adb873e9a1e49", size = 18197576, upload-time = "2026-04-30T07:54:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/4ebb602d270108ea6e81ccdfe4aa0a511fedffdc3ef6c193ac9ff76402fe/mysql_connector_python-9.7.0-py2.py3-none-any.whl", hash = "sha256:af80b1e7179d5c2d983cf62470ad9b134a7e9ef05cf31108ae587f15873530cc", size = 480646, upload-time = "2026-04-30T07:54:43.82Z" }, +] + +[[package]] +name = "mysqlclient" +version = "2.2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/b0/9df076488cb2e536d40ce6dbd4273c1f20a386e31ffe6e7cb613902b3c2a/mysqlclient-2.2.8.tar.gz", hash = "sha256:8ed20c5615a915da451bb308c7d0306648a4fd9a2809ba95c992690006306199", size = 92287, upload-time = "2026-02-10T10:58:37.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/d0/22b896a2f8f797ee3517a0c1c1fe576cd38768cd25e2a7362a7277725ed4/mysqlclient-2.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:86db31bba7b3480fec2751350e9790e24f016f89af33a87bab7e79f7196474e8", size = 206873, upload-time = "2026-02-10T10:58:40.996Z" }, + { url = "https://files.pythonhosted.org/packages/44/f6/6253d116c024aeb11fb193ad14e2bae2bd3380690e885028abf798b1b0b4/mysqlclient-2.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:60c9ed339dc09e3d5380cc2a9f42e86754fee25a661ced77a02df77990664c92", size = 206875, upload-time = "2026-02-10T10:58:42.223Z" }, + { url = "https://files.pythonhosted.org/packages/db/1f/7d6b3fbfc8a317805fdb8bbfbc23af99f2090497090e82513daf491a29ce/mysqlclient-2.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:000c7ec3d11e7c411db832e4cfcd7f05db47464326381f5d5ae991b4bb572f93", size = 207171, upload-time = "2026-02-10T10:58:39.827Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d5/76e369b0fdccd2eb9ed7d890e4e3e23aa1344fea62f0180d7f1574285e54/mysqlclient-2.2.8-cp313-cp313-win_amd64.whl", hash = "sha256:a81f5e12f8d05439709cb02fba97f9f76d1a6c528164f2260d8798fec969e300", size = 207158, upload-time = "2026-02-10T10:58:38.663Z" }, + { url = "https://files.pythonhosted.org/packages/42/44/6ae21b4e8490eb5167d04fa7452c462ffd72a502b96bf531477ef044a380/mysqlclient-2.2.8-cp314-cp314-win_amd64.whl", hash = "sha256:9bed7c8d3b629bdc09e17fb628d5b3b0a5fd1f12b09432b464b9126c727bedc0", size = 211644, upload-time = "2026-02-10T10:58:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/037d55623be9f681236e04abe12e1290847c06bd48270c3f19ac33493cbf/mysqlclient-2.2.8-cp314-cp314t-win_amd64.whl", hash = "sha256:260cce0e81446c83bf0a389e0fae38d68547d9f8fc0833bc733014e10ce28a99", size = 213067, upload-time = "2026-02-10T10:58:43.389Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -842,6 +931,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + [[package]] name = "pydantic" version = "2.13.4" @@ -996,6 +1120,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] +[[package]] +name = "pymysql" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/bc/1c6a92f385940f727daeecf3bacaf186e03875dff57197801046c583bcf0/pymysql-1.2.0.tar.gz", hash = "sha256:6c7b17ca686988104d7426c27895b455cdeea3e9d3ceb1270f0c3704fead8c33", size = 49021, upload-time = "2026-05-19T08:26:22.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/bd/2534e130295c8cfd4f0a2e31623baab7502278f1e97bcfe61db75656a77f/pymysql-1.2.0-py3-none-any.whl", hash = "sha256:62169ce6d5510f08e140c5e7990ee884a9764024e4a9a27b2cc11f1099322ae0", size = 45716, upload-time = "2026-05-19T08:26:20.974Z" }, +] + [[package]] name = "pytest" version = "9.0.3" @@ -1027,9 +1160,11 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "alembic", specifier = ">=1.16" }, + { name = "mariadb", extras = ["binary"], marker = "extra == 'mariadb'", specifier = ">=2.0.0rc2" }, { name = "pytest", specifier = ">=8.4" }, { name = "sqlalchemy", specifier = ">=2.0" }, ] +provides-extras = ["mariadb"] [package.metadata.requires-dev] dev = [ diff --git a/pyproject.toml b/pyproject.toml index 11e5aeb..52bceb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,11 @@ dev = [ [project.entry-points.pytest11] sqlalchemy-alembic = "pytest_sqlalchemy_alembic.plugin" +[project.optional-dependencies] +mariadb = [ + "mariadb[binary]>=2.0.0rc2", +] + [build-system] build-backend = "uv_build" requires = ["uv_build>=0.11.18,<0.12"] diff --git a/src/pytest_sqlalchemy_alembic/dialects/__init__.py b/src/pytest_sqlalchemy_alembic/dialects/__init__.py index 1a23cdb..1a9f1c5 100644 --- a/src/pytest_sqlalchemy_alembic/dialects/__init__.py +++ b/src/pytest_sqlalchemy_alembic/dialects/__init__.py @@ -1,4 +1,5 @@ from .base import BaseDatabaseManager +from .mariadb import MariaDBManager from .postgresql import PostgresqlManager from .sqlite import SqliteManager @@ -14,4 +15,14 @@ 'sqlite': SqliteManager, 'sqlite+pysqlite': SqliteManager, 'sqlite+pysqlcipher': SqliteManager, + 'mariadb': MariaDBManager, + 'mariadb+mysqldb': MariaDBManager, + 'mariadb+pymysql': MariaDBManager, + 'mariadb+mariadbconnector': MariaDBManager, + 'mariadb+cymysql': MariaDBManager, + 'mysql': MariaDBManager, + 'mysql+mysqldb': MariaDBManager, + 'mysql+pymysql': MariaDBManager, + 'mysql+mysqlconnector': MariaDBManager, + 'mysql+cymysql': MariaDBManager, } diff --git a/src/pytest_sqlalchemy_alembic/dialects/mariadb.py b/src/pytest_sqlalchemy_alembic/dialects/mariadb.py new file mode 100644 index 0000000..4c7f4b5 --- /dev/null +++ b/src/pytest_sqlalchemy_alembic/dialects/mariadb.py @@ -0,0 +1,58 @@ +from contextlib import suppress +from typing import Any + +import sqlalchemy as sa +from sqlalchemy import Engine, create_engine +from sqlalchemy.exc import OperationalError +from typing_extensions import Self + +from .base import BaseDatabaseManager + + +class MariaDBManager(BaseDatabaseManager): + def __enter__(self) -> Self: + self.connection = self.engine.connect() + return self + + def __exit__(self, exc_type, exc_value, traceback) -> None: + self.connection.close() + + def create_test_engine(self, worker_id: str, engine_kwargs: dict[str, Any]) -> Engine: + url = self.engine.url + test_db_url = url.set(database=f'{url.database}_test_{worker_id}') + self.test_engine = create_engine(test_db_url, **engine_kwargs) + return self.test_engine + + def drop_test_database(self) -> None: + self.check() + connection_ids = self.connection.execute( + sa.text(""" + SELECT id FROM information_schema.processlist + WHERE db = :db_name AND id != CONNECTION_ID() + """), + {'db_name': self.test_engine.url.database}, + ) + + for cid in [row[0] for row in connection_ids.fetchall()]: + with suppress(OperationalError): # connection may already be gone + self.connection.execute(sa.text(f'KILL CONNECTION {cid}')) + + self.connection.execute(sa.text(f'DROP DATABASE IF EXISTS `{self.test_engine.url.database}`')) + + def create_test_database(self) -> None: + self.check() + self.connection.execute(sa.text(f'CREATE DATABASE {self.test_engine.url.database};')) + + def test_database_exists(self) -> bool: + self.check() + return ( + self.connection.scalar( + sa.text('SELECT 1 FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = :dbname'), + parameters={'dbname': self.test_engine.url.database}, + ) + is not None + ) + + def check(self): + super().check() + assert getattr(self, 'connection', None) is not None, 'Connection has not been created yet. Use calls inside context manager' diff --git a/src/pytest_sqlalchemy_alembic/dialects/postgresql.py b/src/pytest_sqlalchemy_alembic/dialects/postgresql.py index 7eb9d7c..22c02f3 100644 --- a/src/pytest_sqlalchemy_alembic/dialects/postgresql.py +++ b/src/pytest_sqlalchemy_alembic/dialects/postgresql.py @@ -27,11 +27,13 @@ def drop_test_database(self) -> None: self.connection.execute(sa.text(f'DROP DATABASE IF EXISTS {self.test_engine.url.database} WITH (FORCE);')) def create_test_database(self) -> None: + self.check() # if connection to postgres is made with passwordless access using the 'trust' authentication method `url.username` may be empty owner = self.test_engine.url.username or os.environ.get('USER') self.connection.execute(sa.text(f'CREATE DATABASE {self.test_engine.url.database} OWNER {owner};')) def test_database_exists(self) -> bool: + self.check() return ( self.connection.scalar( sa.text('SELECT 1 FROM pg_catalog.pg_database WHERE datname = :dbname'), From 2272bd34470565f6b31283263bb443fe02d98efc Mon Sep 17 00:00:00 2001 From: Nik Date: Mon, 15 Jun 2026 04:10:54 +0200 Subject: [PATCH 2/2] Improve test-dialects workflow --- .github/workflows/test-dialects.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-dialects.yml b/.github/workflows/test-dialects.yml index 4def363..6b0fa92 100644 --- a/.github/workflows/test-dialects.yml +++ b/.github/workflows/test-dialects.yml @@ -1,7 +1,6 @@ name: "Test all dialects" on: - workflow_call: workflow_dispatch: inputs: sqlite: @@ -23,21 +22,21 @@ on: jobs: dialect-sqlite: - if: ${{ github.event.inputs.sqlite == 'true' }} + if: ${{ inputs.sqlite == 'true' }} uses: ./.github/workflows/dialect-sqlite.yml secrets: inherit dialect-postgresql: - if: ${{ github.event.inputs.postgresql == 'true' }} + if: ${{ inputs.postgresql == 'true' }} uses: ./.github/workflows/dialect-postgresql.yml secrets: inherit dialect-mariadb: - if: ${{ github.event.inputs.mariadb == 'true' }} + if: ${{ inputs.mariadb == 'true' }} uses: ./.github/workflows/dialect-mariadb.yml secrets: inherit dialect-mysql: - if: ${{ github.event.inputs.mysql == 'true' }} + if: ${{ inputs.mysql == 'true' }} uses: ./.github/workflows/dialect-mysql.yml secrets: inherit