From 92165edbe63315afd2c03de9a44443abebfeecb2 Mon Sep 17 00:00:00 2001 From: Abdulaziz Axmadaliyev Date: Thu, 26 Feb 2026 16:35:47 +0500 Subject: [PATCH] Initial commit --- .env | 8 + .env.example | 8 + .idea/.gitignore | 10 + .idea/dataSources.xml | 12 + .idea/inspectionProfiles/Project_Default.xml | 35 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/material_theme_project_new.xml | 12 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/payme_shopify_service.iml | 10 + Dockerfile | 18 + README.md | 0 alembic.ini | 37 + alembic/env.py | 49 + alembic/script.py.mako | 24 + alembic/versions/0001_create_transactions.py | 31 + app/__init__.py | 0 app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 171 bytes app/__pycache__/main.cpython-312.pyc | Bin 0 -> 800 bytes app/api/__init__.py | 0 app/api/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 175 bytes app/api/__pycache__/payme.cpython-312.pyc | Bin 0 -> 1820 bytes app/api/__pycache__/shopify.cpython-312.pyc | Bin 0 -> 1801 bytes app/api/health.py | 0 app/api/payme.py | 45 + app/api/shopify.py | 78 + app/core/__init__.py | 0 app/core/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 176 bytes app/core/__pycache__/config.cpython-312.pyc | Bin 0 -> 858 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 410 bytes app/core/__pycache__/security.cpython-312.pyc | Bin 0 -> 1176 bytes app/core/config.py | 18 + app/core/constants.py | 10 + app/core/security.py | 18 + app/db/__init__.py | 0 app/db/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 174 bytes app/db/__pycache__/base.cpython-312.pyc | Bin 0 -> 400 bytes app/db/__pycache__/session.cpython-312.pyc | Bin 0 -> 761 bytes app/db/base.py | 5 + app/db/session.py | 13 + app/main.py | 13 + app/models/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 178 bytes .../__pycache__/transaction.cpython-312.pyc | Bin 0 -> 1162 bytes app/models/transaction.py | 16 + app/schemas/__init__.py | 0 app/schemas/payme.py | 0 app/schemas/transaction.py | 0 app/services/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 180 bytes .../__pycache__/payme_service.cpython-312.pyc | Bin 0 -> 3139 bytes .../shopify_service.cpython-312.pyc | Bin 0 -> 2279 bytes .../transaction_service.cpython-312.pyc | Bin 0 -> 1522 bytes app/services/payme_checkout.py | 15 + app/services/payme_service.py | 110 + app/services/shopify_service.py | 63 + app/services/transaction_service.py | 29 + app/utils/__init__.py | 0 app/utils/get_token.py | 30 + app/utils/helpers.py | 0 app/utils/logger.py | 0 docker-compose.yml | 29 + requirements.txt | 9 + testing.py | 31 + venv/bin/Activate.ps1 | 247 + venv/bin/activate | 70 + venv/bin/activate.csh | 27 + venv/bin/activate.fish | 69 + venv/bin/alembic | 10 + venv/bin/dotenv | 10 + venv/bin/fastapi | 10 + venv/bin/httpx | 10 + venv/bin/mako-render | 10 + venv/bin/normalizer | 10 + venv/bin/pip | 10 + venv/bin/pip3 | 10 + venv/bin/pip3.12 | 10 + venv/bin/python | 1 + venv/bin/python3 | 1 + venv/bin/python3.12 | 1 + venv/bin/uvicorn | 10 + .../site/python3.12/greenlet/greenlet.h | 164 + .../typing_extensions.cpython-312.pyc | Bin 0 -> 163777 bytes .../alembic-1.18.4.dist-info/INSTALLER | 1 + .../alembic-1.18.4.dist-info/METADATA | 139 + .../alembic-1.18.4.dist-info/RECORD | 179 + .../alembic-1.18.4.dist-info/REQUESTED | 0 .../alembic-1.18.4.dist-info/WHEEL | 5 + .../alembic-1.18.4.dist-info/entry_points.txt | 2 + .../alembic-1.18.4.dist-info/licenses/LICENSE | 19 + .../alembic-1.18.4.dist-info/top_level.txt | 1 + .../site-packages/alembic/__init__.py | 6 + .../site-packages/alembic/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 342 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 335 bytes .../__pycache__/command.cpython-312.pyc | Bin 0 -> 30104 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 39133 bytes .../__pycache__/context.cpython-312.pyc | Bin 0 -> 400 bytes .../__pycache__/environment.cpython-312.pyc | Bin 0 -> 253 bytes .../__pycache__/migration.cpython-312.pyc | Bin 0 -> 249 bytes .../alembic/__pycache__/op.cpython-312.pyc | Bin 0 -> 382 bytes .../alembic/autogenerate/__init__.py | 10 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 648 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 23020 bytes .../__pycache__/render.cpython-312.pyc | Bin 0 -> 47392 bytes .../__pycache__/rewriter.cpython-312.pyc | Bin 0 -> 9557 bytes .../site-packages/alembic/autogenerate/api.py | 667 ++ .../alembic/autogenerate/compare/__init__.py | 62 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2457 bytes .../__pycache__/comments.cpython-312.pyc | Bin 0 -> 3796 bytes .../__pycache__/constraints.cpython-312.pyc | Bin 0 -> 27085 bytes .../__pycache__/schema.cpython-312.pyc | Bin 0 -> 2395 bytes .../server_defaults.cpython-312.pyc | Bin 0 -> 10561 bytes .../__pycache__/tables.cpython-312.pyc | Bin 0 -> 11663 bytes .../compare/__pycache__/types.cpython-312.pyc | Bin 0 -> 4397 bytes .../compare/__pycache__/util.cpython-312.pyc | Bin 0 -> 10980 bytes .../alembic/autogenerate/compare/comments.py | 106 + .../autogenerate/compare/constraints.py | 812 ++ .../alembic/autogenerate/compare/schema.py | 62 + .../autogenerate/compare/server_defaults.py | 344 + .../alembic/autogenerate/compare/tables.py | 316 + .../alembic/autogenerate/compare/types.py | 147 + .../alembic/autogenerate/compare/util.py | 314 + .../alembic/autogenerate/render.py | 1172 +++ .../alembic/autogenerate/rewriter.py | 240 + .../site-packages/alembic/command.py | 848 ++ .../site-packages/alembic/config.py | 1051 ++ .../site-packages/alembic/context.py | 5 + .../site-packages/alembic/context.pyi | 876 ++ .../site-packages/alembic/ddl/__init__.py | 6 + .../ddl/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 410 bytes .../ddl/__pycache__/_autogen.cpython-312.pyc | Bin 0 -> 15204 bytes .../ddl/__pycache__/base.cpython-312.pyc | Bin 0 -> 17535 bytes .../ddl/__pycache__/impl.cpython-312.pyc | Bin 0 -> 35955 bytes .../ddl/__pycache__/mssql.cpython-312.pyc | Bin 0 -> 19434 bytes .../ddl/__pycache__/mysql.cpython-312.pyc | Bin 0 -> 17054 bytes .../ddl/__pycache__/oracle.cpython-312.pyc | Bin 0 -> 8541 bytes .../__pycache__/postgresql.cpython-312.pyc | Bin 0 -> 33745 bytes .../ddl/__pycache__/sqlite.cpython-312.pyc | Bin 0 -> 8054 bytes .../site-packages/alembic/ddl/_autogen.py | 329 + .../site-packages/alembic/ddl/base.py | 406 + .../site-packages/alembic/ddl/impl.py | 921 ++ .../site-packages/alembic/ddl/mssql.py | 523 + .../site-packages/alembic/ddl/mysql.py | 526 + .../site-packages/alembic/ddl/oracle.py | 202 + .../site-packages/alembic/ddl/postgresql.py | 864 ++ .../site-packages/alembic/ddl/sqlite.py | 237 + .../site-packages/alembic/environment.py | 1 + .../site-packages/alembic/migration.py | 1 + .../python3.12/site-packages/alembic/op.py | 5 + .../python3.12/site-packages/alembic/op.pyi | 1429 +++ .../alembic/operations/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 510 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 81783 bytes .../__pycache__/batch.cpython-312.pyc | Bin 0 -> 31485 bytes .../__pycache__/ops.cpython-312.pyc | Bin 0 -> 113740 bytes .../__pycache__/schemaobj.cpython-312.pyc | Bin 0 -> 11888 bytes .../__pycache__/toimpl.cpython-312.pyc | Bin 0 -> 12048 bytes .../site-packages/alembic/operations/base.py | 2001 ++++ .../site-packages/alembic/operations/batch.py | 720 ++ .../site-packages/alembic/operations/ops.py | 2918 ++++++ .../alembic/operations/schemaobj.py | 290 + .../alembic/operations/toimpl.py | 261 + .../python3.12/site-packages/alembic/py.typed | 0 .../site-packages/alembic/runtime/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/environment.cpython-312.pyc | Bin 0 -> 44704 bytes .../__pycache__/migration.cpython-312.pyc | Bin 0 -> 57839 bytes .../__pycache__/plugins.cpython-312.pyc | Bin 0 -> 7857 bytes .../alembic/runtime/environment.py | 1074 ++ .../alembic/runtime/migration.py | 1346 +++ .../site-packages/alembic/runtime/plugins.py | 179 + .../site-packages/alembic/script/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 330 bytes .../script/__pycache__/base.cpython-312.pyc | Bin 0 -> 42668 bytes .../__pycache__/revision.cpython-312.pyc | Bin 0 -> 62510 bytes .../__pycache__/write_hooks.cpython-312.pyc | Bin 0 -> 7256 bytes .../site-packages/alembic/script/base.py | 1052 ++ .../site-packages/alembic/script/revision.py | 1728 ++++ .../alembic/script/write_hooks.py | 181 + .../alembic/templates/async/README | 1 + .../async/__pycache__/env.cpython-312.pyc | Bin 0 -> 3399 bytes .../alembic/templates/async/alembic.ini.mako | 149 + .../alembic/templates/async/env.py | 89 + .../alembic/templates/async/script.py.mako | 28 + .../alembic/templates/generic/README | 1 + .../generic/__pycache__/env.cpython-312.pyc | Bin 0 -> 2605 bytes .../templates/generic/alembic.ini.mako | 149 + .../alembic/templates/generic/env.py | 78 + .../alembic/templates/generic/script.py.mako | 28 + .../alembic/templates/multidb/README | 12 + .../multidb/__pycache__/env.cpython-312.pyc | Bin 0 -> 4859 bytes .../templates/multidb/alembic.ini.mako | 157 + .../alembic/templates/multidb/env.py | 140 + .../alembic/templates/multidb/script.py.mako | 51 + .../alembic/templates/pyproject/README | 1 + .../pyproject/__pycache__/env.cpython-312.pyc | Bin 0 -> 2607 bytes .../templates/pyproject/alembic.ini.mako | 44 + .../alembic/templates/pyproject/env.py | 78 + .../templates/pyproject/pyproject.toml.mako | 84 + .../templates/pyproject/script.py.mako | 28 + .../alembic/templates/pyproject_async/README | 1 + .../__pycache__/env.cpython-312.pyc | Bin 0 -> 3409 bytes .../pyproject_async/alembic.ini.mako | 44 + .../alembic/templates/pyproject_async/env.py | 89 + .../pyproject_async/pyproject.toml.mako | 84 + .../templates/pyproject_async/script.py.mako | 28 + .../site-packages/alembic/testing/__init__.py | 32 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1444 bytes .../__pycache__/assertions.cpython-312.pyc | Bin 0 -> 7516 bytes .../testing/__pycache__/env.cpython-312.pyc | Bin 0 -> 16858 bytes .../__pycache__/fixtures.cpython-312.pyc | Bin 0 -> 19075 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 10438 bytes .../__pycache__/schemacompare.cpython-312.pyc | Bin 0 -> 9147 bytes .../testing/__pycache__/util.cpython-312.pyc | Bin 0 -> 5134 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 1112 bytes .../alembic/testing/assertions.py | 180 + .../site-packages/alembic/testing/env.py | 581 ++ .../site-packages/alembic/testing/fixtures.py | 404 + .../alembic/testing/plugin/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 224 bytes .../__pycache__/bootstrap.cpython-312.pyc | Bin 0 -> 285 bytes .../alembic/testing/plugin/bootstrap.py | 4 + .../alembic/testing/requirements.py | 189 + .../alembic/testing/schemacompare.py | 169 + .../alembic/testing/suite/__init__.py | 7 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 455 bytes .../_autogen_fixtures.cpython-312.pyc | Bin 0 -> 17450 bytes .../test_autogen_comments.cpython-312.pyc | Bin 0 -> 6715 bytes .../test_autogen_computed.cpython-312.pyc | Bin 0 -> 8513 bytes .../test_autogen_diffs.cpython-312.pyc | Bin 0 -> 12396 bytes .../test_autogen_fks.cpython-312.pyc | Bin 0 -> 34763 bytes .../test_autogen_identity.cpython-312.pyc | Bin 0 -> 9702 bytes .../test_environment.cpython-312.pyc | Bin 0 -> 18792 bytes .../suite/__pycache__/test_op.cpython-312.pyc | Bin 0 -> 2904 bytes .../testing/suite/_autogen_fixtures.py | 479 + .../testing/suite/test_autogen_comments.py | 242 + .../testing/suite/test_autogen_computed.py | 157 + .../testing/suite/test_autogen_diffs.py | 273 + .../alembic/testing/suite/test_autogen_fks.py | 1191 +++ .../testing/suite/test_autogen_identity.py | 226 + .../alembic/testing/suite/test_environment.py | 364 + .../alembic/testing/suite/test_op.py | 42 + .../site-packages/alembic/testing/util.py | 126 + .../site-packages/alembic/testing/warnings.py | 31 + .../site-packages/alembic/util/__init__.py | 33 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1491 bytes .../util/__pycache__/compat.cpython-312.pyc | Bin 0 -> 5256 bytes .../util/__pycache__/editor.cpython-312.pyc | Bin 0 -> 3264 bytes .../util/__pycache__/exc.cpython-312.pyc | Bin 0 -> 1821 bytes .../__pycache__/langhelpers.cpython-312.pyc | Bin 0 -> 16985 bytes .../__pycache__/messaging.cpython-312.pyc | Bin 0 -> 5180 bytes .../util/__pycache__/pyfiles.cpython-312.pyc | Bin 0 -> 6429 bytes .../__pycache__/sqla_compat.cpython-312.pyc | Bin 0 -> 20863 bytes .../site-packages/alembic/util/compat.py | 130 + .../site-packages/alembic/util/editor.py | 81 + .../site-packages/alembic/util/exc.py | 43 + .../site-packages/alembic/util/langhelpers.py | 445 + .../site-packages/alembic/util/messaging.py | 122 + .../site-packages/alembic/util/pyfiles.py | 153 + .../site-packages/alembic/util/sqla_compat.py | 510 + .../annotated_doc-0.0.4.dist-info/INSTALLER | 1 + .../annotated_doc-0.0.4.dist-info/METADATA | 145 + .../annotated_doc-0.0.4.dist-info/RECORD | 11 + .../annotated_doc-0.0.4.dist-info/WHEEL | 4 + .../entry_points.txt | 4 + .../licenses/LICENSE | 21 + .../site-packages/annotated_doc/__init__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 279 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 1927 bytes .../site-packages/annotated_doc/main.py | 36 + .../site-packages/annotated_doc/py.typed | 0 .../annotated_types-0.7.0.dist-info/INSTALLER | 1 + .../annotated_types-0.7.0.dist-info/METADATA | 295 + .../annotated_types-0.7.0.dist-info/RECORD | 10 + .../annotated_types-0.7.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../site-packages/annotated_types/__init__.py | 432 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 18648 bytes .../__pycache__/test_cases.cpython-312.pyc | Bin 0 -> 13267 bytes .../site-packages/annotated_types/py.typed | 0 .../annotated_types/test_cases.py | 151 + .../anyio-4.12.1.dist-info/INSTALLER | 1 + .../anyio-4.12.1.dist-info/METADATA | 96 + .../anyio-4.12.1.dist-info/RECORD | 92 + .../anyio-4.12.1.dist-info/WHEEL | 5 + .../anyio-4.12.1.dist-info/entry_points.txt | 2 + .../anyio-4.12.1.dist-info/licenses/LICENSE | 20 + .../anyio-4.12.1.dist-info/top_level.txt | 1 + .../site-packages/anyio/__init__.py | 111 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4656 bytes .../__pycache__/from_thread.cpython-312.pyc | Bin 0 -> 25899 bytes .../__pycache__/functools.cpython-312.pyc | Bin 0 -> 15703 bytes .../__pycache__/lowlevel.cpython-312.pyc | Bin 0 -> 8022 bytes .../__pycache__/pytest_plugin.cpython-312.pyc | Bin 0 -> 14353 bytes .../to_interpreter.cpython-312.pyc | Bin 0 -> 10379 bytes .../__pycache__/to_process.cpython-312.pyc | Bin 0 -> 12051 bytes .../__pycache__/to_thread.cpython-312.pyc | Bin 0 -> 3225 bytes .../site-packages/anyio/_backends/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/_asyncio.cpython-312.pyc | Bin 0 -> 138692 bytes .../__pycache__/_trio.cpython-312.pyc | Bin 0 -> 71762 bytes .../site-packages/anyio/_backends/_asyncio.py | 2980 ++++++ .../site-packages/anyio/_backends/_trio.py | 1346 +++ .../site-packages/anyio/_core/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 213 bytes .../_asyncio_selector_thread.cpython-312.pyc | Bin 0 -> 8444 bytes .../_contextmanagers.cpython-312.pyc | Bin 0 -> 9019 bytes .../__pycache__/_eventloop.cpython-312.pyc | Bin 0 -> 8212 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 7455 bytes .../_core/__pycache__/_fileio.cpython-312.pyc | Bin 0 -> 43401 bytes .../__pycache__/_resources.cpython-312.pyc | Bin 0 -> 966 bytes .../__pycache__/_signals.cpython-312.pyc | Bin 0 -> 1409 bytes .../__pycache__/_sockets.cpython-312.pyc | Bin 0 -> 40665 bytes .../__pycache__/_streams.cpython-312.pyc | Bin 0 -> 2362 bytes .../__pycache__/_subprocesses.cpython-312.pyc | Bin 0 -> 9673 bytes .../_synchronization.cpython-312.pyc | Bin 0 -> 32991 bytes .../_core/__pycache__/_tasks.cpython-312.pyc | Bin 0 -> 7717 bytes .../__pycache__/_tempfile.cpython-312.pyc | Bin 0 -> 28165 bytes .../__pycache__/_testing.cpython-312.pyc | Bin 0 -> 3815 bytes .../__pycache__/_typedattr.cpython-312.pyc | Bin 0 -> 3863 bytes .../anyio/_core/_asyncio_selector_thread.py | 167 + .../anyio/_core/_contextmanagers.py | 200 + .../site-packages/anyio/_core/_eventloop.py | 234 + .../site-packages/anyio/_core/_exceptions.py | 156 + .../site-packages/anyio/_core/_fileio.py | 797 ++ .../site-packages/anyio/_core/_resources.py | 18 + .../site-packages/anyio/_core/_signals.py | 29 + .../site-packages/anyio/_core/_sockets.py | 1003 ++ .../site-packages/anyio/_core/_streams.py | 52 + .../anyio/_core/_subprocesses.py | 202 + .../anyio/_core/_synchronization.py | 753 ++ .../site-packages/anyio/_core/_tasks.py | 173 + .../site-packages/anyio/_core/_tempfile.py | 616 ++ .../site-packages/anyio/_core/_testing.py | 82 + .../site-packages/anyio/_core/_typedattr.py | 81 + .../site-packages/anyio/abc/__init__.py | 58 + .../abc/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2393 bytes .../__pycache__/_eventloop.cpython-312.pyc | Bin 0 -> 16669 bytes .../__pycache__/_resources.cpython-312.pyc | Bin 0 -> 1660 bytes .../abc/__pycache__/_sockets.cpython-312.pyc | Bin 0 -> 18476 bytes .../abc/__pycache__/_streams.cpython-312.pyc | Bin 0 -> 9914 bytes .../__pycache__/_subprocesses.cpython-312.pyc | Bin 0 -> 3268 bytes .../abc/__pycache__/_tasks.cpython-312.pyc | Bin 0 -> 5167 bytes .../abc/__pycache__/_testing.cpython-312.pyc | Bin 0 -> 2863 bytes .../site-packages/anyio/abc/_eventloop.py | 414 + .../site-packages/anyio/abc/_resources.py | 33 + .../site-packages/anyio/abc/_sockets.py | 405 + .../site-packages/anyio/abc/_streams.py | 239 + .../site-packages/anyio/abc/_subprocesses.py | 79 + .../site-packages/anyio/abc/_tasks.py | 117 + .../site-packages/anyio/abc/_testing.py | 65 + .../site-packages/anyio/from_thread.py | 578 ++ .../site-packages/anyio/functools.py | 375 + .../site-packages/anyio/lowlevel.py | 196 + .../python3.12/site-packages/anyio/py.typed | 0 .../site-packages/anyio/pytest_plugin.py | 302 + .../site-packages/anyio/streams/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 215 bytes .../__pycache__/buffered.cpython-312.pyc | Bin 0 -> 9094 bytes .../streams/__pycache__/file.cpython-312.pyc | Bin 0 -> 7595 bytes .../__pycache__/memory.cpython-312.pyc | Bin 0 -> 15046 bytes .../__pycache__/stapled.cpython-312.pyc | Bin 0 -> 7615 bytes .../streams/__pycache__/text.cpython-312.pyc | Bin 0 -> 9361 bytes .../streams/__pycache__/tls.cpython-312.pyc | Bin 0 -> 20260 bytes .../site-packages/anyio/streams/buffered.py | 188 + .../site-packages/anyio/streams/file.py | 154 + .../site-packages/anyio/streams/memory.py | 325 + .../site-packages/anyio/streams/stapled.py | 147 + .../site-packages/anyio/streams/text.py | 176 + .../site-packages/anyio/streams/tls.py | 424 + .../site-packages/anyio/to_interpreter.py | 246 + .../site-packages/anyio/to_process.py | 266 + .../site-packages/anyio/to_thread.py | 78 + .../certifi-2026.1.4.dist-info/INSTALLER | 1 + .../certifi-2026.1.4.dist-info/METADATA | 78 + .../certifi-2026.1.4.dist-info/RECORD | 14 + .../certifi-2026.1.4.dist-info/WHEEL | 5 + .../licenses/LICENSE | 20 + .../certifi-2026.1.4.dist-info/top_level.txt | 1 + .../site-packages/certifi/__init__.py | 4 + .../site-packages/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 336 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 651 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 2083 bytes .../site-packages/certifi/cacert.pem | 4468 +++++++++ .../python3.12/site-packages/certifi/core.py | 83 + .../python3.12/site-packages/certifi/py.typed | 0 .../INSTALLER | 1 + .../METADATA | 764 ++ .../charset_normalizer-3.4.4.dist-info/RECORD | 35 + .../charset_normalizer-3.4.4.dist-info/WHEEL | 7 + .../entry_points.txt | 2 + .../licenses/LICENSE | 21 + .../top_level.txt | 1 + .../charset_normalizer/__init__.py | 48 + .../charset_normalizer/__main__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1802 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 377 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 18213 bytes .../__pycache__/cd.cpython-312.pyc | Bin 0 -> 13318 bytes .../__pycache__/constant.cpython-312.pyc | Bin 0 -> 40832 bytes .../__pycache__/legacy.cpython-312.pyc | Bin 0 -> 3032 bytes .../__pycache__/md.cpython-312.pyc | Bin 0 -> 24369 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 17149 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 13778 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 404 bytes .../site-packages/charset_normalizer/api.py | 669 ++ .../site-packages/charset_normalizer/cd.py | 395 + .../charset_normalizer/cli/__init__.py | 8 + .../charset_normalizer/cli/__main__.py | 381 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 365 bytes .../cli/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 14426 bytes .../charset_normalizer/constant.py | 2015 ++++ .../charset_normalizer/legacy.py | 80 + .../md.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 15912 bytes .../site-packages/charset_normalizer/md.py | 635 ++ .../md__mypyc.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 290584 bytes .../charset_normalizer/models.py | 360 + .../site-packages/charset_normalizer/py.typed | 0 .../site-packages/charset_normalizer/utils.py | 414 + .../charset_normalizer/version.py | 8 + .../click-8.3.1.dist-info/INSTALLER | 1 + .../click-8.3.1.dist-info/METADATA | 84 + .../click-8.3.1.dist-info/RECORD | 40 + .../site-packages/click-8.3.1.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 28 + .../site-packages/click/__init__.py | 123 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4093 bytes .../click/__pycache__/_compat.cpython-312.pyc | Bin 0 -> 24215 bytes .../__pycache__/_termui_impl.cpython-312.pyc | Bin 0 -> 31637 bytes .../__pycache__/_textwrap.cpython-312.pyc | Bin 0 -> 2446 bytes .../click/__pycache__/_utils.cpython-312.pyc | Bin 0 -> 1221 bytes .../__pycache__/_winconsole.cpython-312.pyc | Bin 0 -> 11791 bytes .../click/__pycache__/core.cpython-312.pyc | Bin 0 -> 134687 bytes .../__pycache__/decorators.cpython-312.pyc | Bin 0 -> 22158 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 14797 bytes .../__pycache__/formatting.cpython-312.pyc | Bin 0 -> 13693 bytes .../click/__pycache__/globals.cpython-312.pyc | Bin 0 -> 2986 bytes .../click/__pycache__/parser.cpython-312.pyc | Bin 0 -> 20463 bytes .../shell_completion.cpython-312.pyc | Bin 0 -> 23358 bytes .../click/__pycache__/termui.cpython-312.pyc | Bin 0 -> 34672 bytes .../click/__pycache__/testing.cpython-312.pyc | Bin 0 -> 27430 bytes .../click/__pycache__/types.cpython-312.pyc | Bin 0 -> 50059 bytes .../click/__pycache__/utils.cpython-312.pyc | Bin 0 -> 24900 bytes .../python3.12/site-packages/click/_compat.py | 622 ++ .../site-packages/click/_termui_impl.py | 852 ++ .../site-packages/click/_textwrap.py | 51 + .../python3.12/site-packages/click/_utils.py | 36 + .../site-packages/click/_winconsole.py | 296 + .../python3.12/site-packages/click/core.py | 3415 +++++++ .../site-packages/click/decorators.py | 551 + .../site-packages/click/exceptions.py | 308 + .../site-packages/click/formatting.py | 301 + .../python3.12/site-packages/click/globals.py | 67 + .../python3.12/site-packages/click/parser.py | 532 + .../python3.12/site-packages/click/py.typed | 0 .../site-packages/click/shell_completion.py | 667 ++ .../python3.12/site-packages/click/termui.py | 883 ++ .../python3.12/site-packages/click/testing.py | 577 ++ .../python3.12/site-packages/click/types.py | 1209 +++ .../python3.12/site-packages/click/utils.py | 627 ++ .../site-packages/dotenv/__init__.py | 51 + .../site-packages/dotenv/__main__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1713 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 381 bytes .../dotenv/__pycache__/cli.cpython-312.pyc | Bin 0 -> 10334 bytes .../__pycache__/ipython.cpython-312.pyc | Bin 0 -> 1997 bytes .../dotenv/__pycache__/main.cpython-312.pyc | Bin 0 -> 18232 bytes .../dotenv/__pycache__/parser.cpython-312.pyc | Bin 0 -> 10007 bytes .../__pycache__/variables.cpython-312.pyc | Bin 0 -> 5054 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 233 bytes .../python3.12/site-packages/dotenv/cli.py | 218 + .../site-packages/dotenv/ipython.py | 50 + .../python3.12/site-packages/dotenv/main.py | 435 + .../python3.12/site-packages/dotenv/parser.py | 182 + .../python3.12/site-packages/dotenv/py.typed | 1 + .../site-packages/dotenv/variables.py | 86 + .../site-packages/dotenv/version.py | 1 + .../fastapi-0.131.0.dist-info/INSTALLER | 1 + .../fastapi-0.131.0.dist-info/METADATA | 637 ++ .../fastapi-0.131.0.dist-info/RECORD | 103 + .../fastapi-0.131.0.dist-info/REQUESTED | 0 .../fastapi-0.131.0.dist-info/WHEEL | 4 + .../entry_points.txt | 5 + .../licenses/LICENSE | 21 + .../site-packages/fastapi/__init__.py | 25 + .../site-packages/fastapi/__main__.py | 3 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1124 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 271 bytes .../__pycache__/applications.cpython-312.pyc | Bin 0 -> 86593 bytes .../__pycache__/background.cpython-312.pyc | Bin 0 -> 2452 bytes .../fastapi/__pycache__/cli.cpython-312.pyc | Bin 0 -> 685 bytes .../__pycache__/concurrency.cpython-312.pyc | Bin 0 -> 1746 bytes .../datastructures.cpython-312.pyc | Bin 0 -> 7241 bytes .../__pycache__/encoders.cpython-312.pyc | Bin 0 -> 11089 bytes .../exception_handlers.cpython-312.pyc | Bin 0 -> 2051 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 10472 bytes .../__pycache__/logger.cpython-312.pyc | Bin 0 -> 311 bytes .../param_functions.cpython-312.pyc | Bin 0 -> 40960 bytes .../__pycache__/params.cpython-312.pyc | Bin 0 -> 23568 bytes .../__pycache__/requests.cpython-312.pyc | Bin 0 -> 300 bytes .../__pycache__/responses.cpython-312.pyc | Bin 0 -> 4280 bytes .../__pycache__/routing.cpython-312.pyc | Bin 0 -> 91910 bytes .../__pycache__/staticfiles.cpython-312.pyc | Bin 0 -> 272 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 274 bytes .../__pycache__/testclient.cpython-312.pyc | Bin 0 -> 269 bytes .../fastapi/__pycache__/types.cpython-312.pyc | Bin 0 -> 814 bytes .../fastapi/__pycache__/utils.cpython-312.pyc | Bin 0 -> 5433 bytes .../__pycache__/websockets.cpython-312.pyc | Bin 0 -> 349 bytes .../site-packages/fastapi/_compat/__init__.py | 40 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1653 bytes .../__pycache__/shared.cpython-312.pyc | Bin 0 -> 9429 bytes .../_compat/__pycache__/v2.cpython-312.pyc | Bin 0 -> 19330 bytes .../site-packages/fastapi/_compat/shared.py | 214 + .../site-packages/fastapi/_compat/v2.py | 480 + .../site-packages/fastapi/applications.py | 4666 +++++++++ .../site-packages/fastapi/background.py | 61 + .../python3.12/site-packages/fastapi/cli.py | 13 + .../site-packages/fastapi/concurrency.py | 41 + .../site-packages/fastapi/datastructures.py | 181 + .../fastapi/dependencies/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 222 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 9175 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 40346 bytes .../fastapi/dependencies/models.py | 193 + .../fastapi/dependencies/utils.py | 1024 ++ .../site-packages/fastapi/encoders.py | 347 + .../fastapi/exception_handlers.py | 34 + .../site-packages/fastapi/exceptions.py | 256 + .../site-packages/fastapi/logger.py | 3 + .../fastapi/middleware/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 278 bytes .../asyncexitstack.cpython-312.pyc | Bin 0 -> 1530 bytes .../__pycache__/cors.cpython-312.pyc | Bin 0 -> 283 bytes .../__pycache__/gzip.cpython-312.pyc | Bin 0 -> 283 bytes .../__pycache__/httpsredirect.cpython-312.pyc | Bin 0 -> 312 bytes .../__pycache__/trustedhost.cpython-312.pyc | Bin 0 -> 306 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 285 bytes .../fastapi/middleware/asyncexitstack.py | 18 + .../site-packages/fastapi/middleware/cors.py | 1 + .../site-packages/fastapi/middleware/gzip.py | 1 + .../fastapi/middleware/httpsredirect.py | 3 + .../fastapi/middleware/trustedhost.py | 3 + .../site-packages/fastapi/middleware/wsgi.py | 3 + .../site-packages/fastapi/openapi/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/constants.cpython-312.pyc | Bin 0 -> 387 bytes .../openapi/__pycache__/docs.cpython-312.pyc | Bin 0 -> 12425 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 22381 bytes .../openapi/__pycache__/utils.cpython-312.pyc | Bin 0 -> 21483 bytes .../fastapi/openapi/constants.py | 3 + .../site-packages/fastapi/openapi/docs.py | 375 + .../site-packages/fastapi/openapi/models.py | 435 + .../site-packages/fastapi/openapi/utils.py | 571 ++ .../site-packages/fastapi/param_functions.py | 2461 +++++ .../site-packages/fastapi/params.py | 755 ++ .../python3.12/site-packages/fastapi/py.typed | 0 .../site-packages/fastapi/requests.py | 2 + .../site-packages/fastapi/responses.py | 84 + .../site-packages/fastapi/routing.py | 4643 +++++++++ .../fastapi/security/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 874 bytes .../__pycache__/api_key.cpython-312.pyc | Bin 0 -> 10440 bytes .../security/__pycache__/base.cpython-312.pyc | Bin 0 -> 536 bytes .../security/__pycache__/http.cpython-312.pyc | Bin 0 -> 14522 bytes .../__pycache__/oauth2.cpython-312.pyc | Bin 0 -> 20574 bytes .../open_id_connect_url.cpython-312.pyc | Bin 0 -> 3766 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 617 bytes .../site-packages/fastapi/security/api_key.py | 318 + .../site-packages/fastapi/security/base.py | 6 + .../site-packages/fastapi/security/http.py | 417 + .../site-packages/fastapi/security/oauth2.py | 693 ++ .../fastapi/security/open_id_connect_url.py | 94 + .../site-packages/fastapi/security/utils.py | 7 + .../site-packages/fastapi/staticfiles.py | 1 + .../site-packages/fastapi/templating.py | 1 + .../site-packages/fastapi/testclient.py | 1 + .../python3.12/site-packages/fastapi/types.py | 12 + .../python3.12/site-packages/fastapi/utils.py | 136 + .../site-packages/fastapi/websockets.py | 3 + .../greenlet-3.3.2.dist-info/INSTALLER | 1 + .../greenlet-3.3.2.dist-info/METADATA | 98 + .../greenlet-3.3.2.dist-info/RECORD | 123 + .../greenlet-3.3.2.dist-info/WHEEL | 6 + .../greenlet-3.3.2.dist-info/licenses/LICENSE | 30 + .../licenses/LICENSE.PSF | 47 + .../greenlet-3.3.2.dist-info/top_level.txt | 1 + .../site-packages/greenlet/CObjects.cpp | 157 + .../site-packages/greenlet/PyGreenlet.cpp | 795 ++ .../site-packages/greenlet/PyGreenlet.hpp | 35 + .../greenlet/PyGreenletUnswitchable.cpp | 147 + .../site-packages/greenlet/PyModule.cpp | 292 + .../greenlet/TBrokenGreenlet.cpp | 45 + .../greenlet/TExceptionState.cpp | 62 + .../site-packages/greenlet/TGreenlet.cpp | 725 ++ .../site-packages/greenlet/TGreenlet.hpp | 837 ++ .../greenlet/TGreenletGlobals.cpp | 94 + .../site-packages/greenlet/TMainGreenlet.cpp | 160 + .../site-packages/greenlet/TPythonState.cpp | 439 + .../site-packages/greenlet/TStackState.cpp | 265 + .../site-packages/greenlet/TThreadState.hpp | 543 + .../greenlet/TThreadStateCreator.hpp | 102 + .../greenlet/TThreadStateDestroy.cpp | 223 + .../site-packages/greenlet/TUserGreenlet.cpp | 662 ++ .../site-packages/greenlet/__init__.py | 71 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1096 bytes .../_greenlet.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 1448744 bytes .../site-packages/greenlet/greenlet.cpp | 323 + .../site-packages/greenlet/greenlet.h | 164 + .../greenlet/greenlet_allocator.hpp | 76 + .../greenlet/greenlet_compiler_compat.hpp | 98 + .../greenlet/greenlet_cpython_compat.hpp | 156 + .../greenlet/greenlet_exceptions.hpp | 171 + .../greenlet/greenlet_internal.hpp | 107 + .../greenlet/greenlet_msvc_compat.hpp | 100 + .../site-packages/greenlet/greenlet_refs.hpp | 1118 +++ .../greenlet/greenlet_slp_switch.hpp | 103 + .../greenlet/greenlet_thread_support.hpp | 31 + .../greenlet/platform/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 219 bytes .../platform/setup_switch_x64_masm.cmd | 2 + .../greenlet/platform/switch_aarch64_gcc.h | 124 + .../greenlet/platform/switch_alpha_unix.h | 30 + .../greenlet/platform/switch_amd64_unix.h | 87 + .../greenlet/platform/switch_arm32_gcc.h | 79 + .../greenlet/platform/switch_arm32_ios.h | 67 + .../greenlet/platform/switch_arm64_masm.asm | 53 + .../greenlet/platform/switch_arm64_masm.obj | Bin 0 -> 746 bytes .../greenlet/platform/switch_arm64_msvc.h | 17 + .../greenlet/platform/switch_csky_gcc.h | 48 + .../platform/switch_loongarch64_linux.h | 31 + .../greenlet/platform/switch_m68k_gcc.h | 38 + .../greenlet/platform/switch_mips_unix.h | 65 + .../greenlet/platform/switch_ppc64_aix.h | 103 + .../greenlet/platform/switch_ppc64_linux.h | 105 + .../greenlet/platform/switch_ppc_aix.h | 87 + .../greenlet/platform/switch_ppc_linux.h | 84 + .../greenlet/platform/switch_ppc_macosx.h | 82 + .../greenlet/platform/switch_ppc_unix.h | 82 + .../greenlet/platform/switch_riscv_unix.h | 41 + .../greenlet/platform/switch_s390_unix.h | 87 + .../greenlet/platform/switch_sh_gcc.h | 36 + .../greenlet/platform/switch_sparc_sun_gcc.h | 92 + .../greenlet/platform/switch_x32_unix.h | 63 + .../greenlet/platform/switch_x64_masm.asm | 111 + .../greenlet/platform/switch_x64_masm.obj | Bin 0 -> 1078 bytes .../greenlet/platform/switch_x64_msvc.h | 60 + .../greenlet/platform/switch_x86_msvc.h | 326 + .../greenlet/platform/switch_x86_unix.h | 105 + .../greenlet/slp_platformselect.h | 77 + .../site-packages/greenlet/tests/__init__.py | 248 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 9272 bytes ...fail_clearing_run_switches.cpython-312.pyc | Bin 0 -> 2104 bytes .../fail_cpp_exception.cpython-312.pyc | Bin 0 -> 1639 bytes ...nitialstub_already_started.cpython-312.pyc | Bin 0 -> 3507 bytes .../fail_slp_switch.cpython-312.pyc | Bin 0 -> 1332 bytes ...ail_switch_three_greenlets.cpython-312.pyc | Bin 0 -> 1748 bytes ...il_switch_three_greenlets2.cpython-312.pyc | Bin 0 -> 2602 bytes .../fail_switch_two_greenlets.cpython-312.pyc | Bin 0 -> 1719 bytes .../__pycache__/leakcheck.cpython-312.pyc | Bin 0 -> 11726 bytes .../test_contextvars.cpython-312.pyc | Bin 0 -> 15503 bytes .../__pycache__/test_cpp.cpython-312.pyc | Bin 0 -> 4133 bytes .../test_extension_interface.cpython-312.pyc | Bin 0 -> 7491 bytes .../tests/__pycache__/test_gc.cpython-312.pyc | Bin 0 -> 4942 bytes .../test_generator.cpython-312.pyc | Bin 0 -> 3107 bytes .../test_generator_nested.cpython-312.pyc | Bin 0 -> 7845 bytes .../__pycache__/test_greenlet.cpython-312.pyc | Bin 0 -> 76350 bytes .../test_greenlet_trash.cpython-312.pyc | Bin 0 -> 6804 bytes .../test_interpreter_shutdown.cpython-312.pyc | Bin 0 -> 13084 bytes .../__pycache__/test_leaks.cpython-312.pyc | Bin 0 -> 20238 bytes .../test_stack_saved.cpython-312.pyc | Bin 0 -> 1371 bytes .../__pycache__/test_throw.cpython-312.pyc | Bin 0 -> 7407 bytes .../__pycache__/test_tracing.cpython-312.pyc | Bin 0 -> 13903 bytes .../__pycache__/test_version.cpython-312.pyc | Bin 0 -> 2590 bytes .../__pycache__/test_weakref.cpython-312.pyc | Bin 0 -> 2756 bytes .../greenlet/tests/_test_extension.c | 258 + ..._extension.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 17256 bytes .../greenlet/tests/_test_extension_cpp.cpp | 229 + ...ension_cpp.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 58384 bytes .../tests/fail_clearing_run_switches.py | 47 + .../greenlet/tests/fail_cpp_exception.py | 33 + .../tests/fail_initialstub_already_started.py | 78 + .../greenlet/tests/fail_slp_switch.py | 29 + .../tests/fail_switch_three_greenlets.py | 44 + .../tests/fail_switch_three_greenlets2.py | 55 + .../tests/fail_switch_two_greenlets.py | 41 + .../site-packages/greenlet/tests/leakcheck.py | 336 + .../greenlet/tests/test_contextvars.py | 312 + .../site-packages/greenlet/tests/test_cpp.py | 73 + .../tests/test_extension_interface.py | 115 + .../site-packages/greenlet/tests/test_gc.py | 86 + .../greenlet/tests/test_generator.py | 59 + .../greenlet/tests/test_generator_nested.py | 168 + .../greenlet/tests/test_greenlet.py | 1365 +++ .../greenlet/tests/test_greenlet_trash.py | 187 + .../tests/test_interpreter_shutdown.py | 320 + .../greenlet/tests/test_leaks.py | 474 + .../greenlet/tests/test_stack_saved.py | 19 + .../greenlet/tests/test_throw.py | 128 + .../greenlet/tests/test_tracing.py | 299 + .../greenlet/tests/test_version.py | 41 + .../greenlet/tests/test_weakref.py | 35 + .../h11-0.16.0.dist-info/INSTALLER | 1 + .../h11-0.16.0.dist-info/METADATA | 202 + .../site-packages/h11-0.16.0.dist-info/RECORD | 29 + .../site-packages/h11-0.16.0.dist-info/WHEEL | 5 + .../h11-0.16.0.dist-info/licenses/LICENSE.txt | 22 + .../h11-0.16.0.dist-info/top_level.txt | 1 + .../python3.12/site-packages/h11/__init__.py | 62 + .../h11/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1096 bytes .../h11/__pycache__/_abnf.cpython-312.pyc | Bin 0 -> 1802 bytes .../__pycache__/_connection.cpython-312.pyc | Bin 0 -> 23152 bytes .../h11/__pycache__/_events.cpython-312.pyc | Bin 0 -> 13247 bytes .../h11/__pycache__/_headers.cpython-312.pyc | Bin 0 -> 8023 bytes .../h11/__pycache__/_readers.cpython-312.pyc | Bin 0 -> 9679 bytes .../_receivebuffer.cpython-312.pyc | Bin 0 -> 4725 bytes .../h11/__pycache__/_state.cpython-312.pyc | Bin 0 -> 8489 bytes .../h11/__pycache__/_util.cpython-312.pyc | Bin 0 -> 4740 bytes .../h11/__pycache__/_version.cpython-312.pyc | Bin 0 -> 234 bytes .../h11/__pycache__/_writers.cpython-312.pyc | Bin 0 -> 6316 bytes .../lib/python3.12/site-packages/h11/_abnf.py | 132 + .../site-packages/h11/_connection.py | 659 ++ .../python3.12/site-packages/h11/_events.py | 369 + .../python3.12/site-packages/h11/_headers.py | 282 + .../python3.12/site-packages/h11/_readers.py | 250 + .../site-packages/h11/_receivebuffer.py | 153 + .../python3.12/site-packages/h11/_state.py | 365 + .../lib/python3.12/site-packages/h11/_util.py | 135 + .../python3.12/site-packages/h11/_version.py | 16 + .../python3.12/site-packages/h11/_writers.py | 145 + .../lib/python3.12/site-packages/h11/py.typed | 1 + .../httpcore-1.0.9.dist-info/INSTALLER | 1 + .../httpcore-1.0.9.dist-info/METADATA | 625 ++ .../httpcore-1.0.9.dist-info/RECORD | 68 + .../httpcore-1.0.9.dist-info/WHEEL | 4 + .../licenses/LICENSE.md | 27 + .../site-packages/httpcore/__init__.py | 141 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3192 bytes .../httpcore/__pycache__/_api.cpython-312.pyc | Bin 0 -> 3796 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 3211 bytes .../__pycache__/_models.cpython-312.pyc | Bin 0 -> 23142 bytes .../httpcore/__pycache__/_ssl.cpython-312.pyc | Bin 0 -> 631 bytes .../_synchronization.cpython-312.pyc | Bin 0 -> 14206 bytes .../__pycache__/_trace.cpython-312.pyc | Bin 0 -> 5623 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 1311 bytes .../python3.12/site-packages/httpcore/_api.py | 94 + .../site-packages/httpcore/_async/__init__.py | 39 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1644 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 11821 bytes .../connection_pool.cpython-312.pyc | Bin 0 -> 19701 bytes .../_async/__pycache__/http11.cpython-312.pyc | Bin 0 -> 20263 bytes .../_async/__pycache__/http2.cpython-312.pyc | Bin 0 -> 31466 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 18021 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 5775 bytes .../__pycache__/socks_proxy.cpython-312.pyc | Bin 0 -> 16918 bytes .../httpcore/_async/connection.py | 222 + .../httpcore/_async/connection_pool.py | 420 + .../site-packages/httpcore/_async/http11.py | 379 + .../site-packages/httpcore/_async/http2.py | 592 ++ .../httpcore/_async/http_proxy.py | 367 + .../httpcore/_async/interfaces.py | 137 + .../httpcore/_async/socks_proxy.py | 341 + .../httpcore/_backends/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 220 bytes .../__pycache__/anyio.cpython-312.pyc | Bin 0 -> 8650 bytes .../__pycache__/auto.cpython-312.pyc | Bin 0 -> 2712 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 4914 bytes .../__pycache__/mock.cpython-312.pyc | Bin 0 -> 7178 bytes .../__pycache__/sync.cpython-312.pyc | Bin 0 -> 11451 bytes .../__pycache__/trio.cpython-312.pyc | Bin 0 -> 8987 bytes .../site-packages/httpcore/_backends/anyio.py | 146 + .../site-packages/httpcore/_backends/auto.py | 52 + .../site-packages/httpcore/_backends/base.py | 101 + .../site-packages/httpcore/_backends/mock.py | 143 + .../site-packages/httpcore/_backends/sync.py | 241 + .../site-packages/httpcore/_backends/trio.py | 159 + .../site-packages/httpcore/_exceptions.py | 81 + .../site-packages/httpcore/_models.py | 516 + .../python3.12/site-packages/httpcore/_ssl.py | 9 + .../site-packages/httpcore/_sync/__init__.py | 39 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1598 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 10441 bytes .../connection_pool.cpython-312.pyc | Bin 0 -> 18780 bytes .../_sync/__pycache__/http11.cpython-312.pyc | Bin 0 -> 17807 bytes .../_sync/__pycache__/http2.cpython-312.pyc | Bin 0 -> 27471 bytes .../__pycache__/http_proxy.cpython-312.pyc | Bin 0 -> 17204 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 5322 bytes .../__pycache__/socks_proxy.cpython-312.pyc | Bin 0 -> 15669 bytes .../httpcore/_sync/connection.py | 222 + .../httpcore/_sync/connection_pool.py | 420 + .../site-packages/httpcore/_sync/http11.py | 379 + .../site-packages/httpcore/_sync/http2.py | 592 ++ .../httpcore/_sync/http_proxy.py | 367 + .../httpcore/_sync/interfaces.py | 137 + .../httpcore/_sync/socks_proxy.py | 341 + .../httpcore/_synchronization.py | 318 + .../site-packages/httpcore/_trace.py | 107 + .../site-packages/httpcore/_utils.py | 37 + .../site-packages/httpcore/py.typed | 0 .../httpx-0.28.1.dist-info/INSTALLER | 1 + .../httpx-0.28.1.dist-info/METADATA | 203 + .../httpx-0.28.1.dist-info/RECORD | 55 + .../httpx-0.28.1.dist-info/REQUESTED | 0 .../httpx-0.28.1.dist-info/WHEEL | 4 + .../httpx-0.28.1.dist-info/entry_points.txt | 2 + .../licenses/LICENSE.md | 12 + .../site-packages/httpx/__init__.py | 105 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2146 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 336 bytes .../httpx/__pycache__/_api.cpython-312.pyc | Bin 0 -> 10371 bytes .../httpx/__pycache__/_auth.cpython-312.pyc | Bin 0 -> 15623 bytes .../httpx/__pycache__/_client.cpython-312.pyc | Bin 0 -> 64208 bytes .../httpx/__pycache__/_config.cpython-312.pyc | Bin 0 -> 11004 bytes .../__pycache__/_content.cpython-312.pyc | Bin 0 -> 10429 bytes .../__pycache__/_decoders.cpython-312.pyc | Bin 0 -> 16781 bytes .../__pycache__/_exceptions.cpython-312.pyc | Bin 0 -> 12106 bytes .../httpx/__pycache__/_main.cpython-312.pyc | Bin 0 -> 20644 bytes .../httpx/__pycache__/_models.cpython-312.pyc | Bin 0 -> 58618 bytes .../__pycache__/_multipart.cpython-312.pyc | Bin 0 -> 13629 bytes .../__pycache__/_status_codes.cpython-312.pyc | Bin 0 -> 7216 bytes .../httpx/__pycache__/_types.cpython-312.pyc | Bin 0 -> 3848 bytes .../__pycache__/_urlparse.cpython-312.pyc | Bin 0 -> 17509 bytes .../httpx/__pycache__/_urls.cpython-312.pyc | Bin 0 -> 27952 bytes .../httpx/__pycache__/_utils.cpython-312.pyc | Bin 0 -> 9385 bytes .../site-packages/httpx/__version__.py | 3 + .../python3.12/site-packages/httpx/_api.py | 438 + .../python3.12/site-packages/httpx/_auth.py | 348 + .../python3.12/site-packages/httpx/_client.py | 2019 ++++ .../python3.12/site-packages/httpx/_config.py | 248 + .../site-packages/httpx/_content.py | 240 + .../site-packages/httpx/_decoders.py | 393 + .../site-packages/httpx/_exceptions.py | 379 + .../python3.12/site-packages/httpx/_main.py | 506 + .../python3.12/site-packages/httpx/_models.py | 1277 +++ .../site-packages/httpx/_multipart.py | 300 + .../site-packages/httpx/_status_codes.py | 162 + .../httpx/_transports/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 465 bytes .../__pycache__/asgi.cpython-312.pyc | Bin 0 -> 7602 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3895 bytes .../__pycache__/default.cpython-312.pyc | Bin 0 -> 17148 bytes .../__pycache__/mock.cpython-312.pyc | Bin 0 -> 1963 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 6837 bytes .../site-packages/httpx/_transports/asgi.py | 187 + .../site-packages/httpx/_transports/base.py | 86 + .../httpx/_transports/default.py | 406 + .../site-packages/httpx/_transports/mock.py | 43 + .../site-packages/httpx/_transports/wsgi.py | 149 + .../python3.12/site-packages/httpx/_types.py | 114 + .../site-packages/httpx/_urlparse.py | 527 + .../python3.12/site-packages/httpx/_urls.py | 641 ++ .../python3.12/site-packages/httpx/_utils.py | 242 + .../python3.12/site-packages/httpx/py.typed | 0 .../idna-3.11.dist-info/INSTALLER | 1 + .../idna-3.11.dist-info/METADATA | 209 + .../site-packages/idna-3.11.dist-info/RECORD | 22 + .../site-packages/idna-3.11.dist-info/WHEEL | 4 + .../idna-3.11.dist-info/licenses/LICENSE.md | 31 + .../python3.12/site-packages/idna/__init__.py | 45 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 904 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 5004 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 908 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 16216 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 100933 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2656 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 235 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 161863 bytes .../python3.12/site-packages/idna/codec.py | 122 + .../python3.12/site-packages/idna/compat.py | 15 + .../lib/python3.12/site-packages/idna/core.py | 437 + .../python3.12/site-packages/idna/idnadata.py | 4309 ++++++++ .../site-packages/idna/intranges.py | 57 + .../site-packages/idna/package_data.py | 1 + .../python3.12/site-packages/idna/py.typed | 0 .../site-packages/idna/uts46data.py | 8841 +++++++++++++++++ .../mako-1.3.10.dist-info/INSTALLER | 1 + .../mako-1.3.10.dist-info/METADATA | 88 + .../mako-1.3.10.dist-info/RECORD | 74 + .../site-packages/mako-1.3.10.dist-info/WHEEL | 5 + .../mako-1.3.10.dist-info/entry_points.txt | 18 + .../mako-1.3.10.dist-info/licenses/LICENSE | 19 + .../mako-1.3.10.dist-info/top_level.txt | 1 + .../python3.12/site-packages/mako/__init__.py | 8 + .../mako/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 235 bytes .../__pycache__/_ast_util.cpython-312.pyc | Bin 0 -> 36445 bytes .../mako/__pycache__/ast.cpython-312.pyc | Bin 0 -> 7519 bytes .../mako/__pycache__/cache.cpython-312.pyc | Bin 0 -> 8518 bytes .../mako/__pycache__/cmd.cpython-312.pyc | Bin 0 -> 3769 bytes .../mako/__pycache__/codegen.cpython-312.pyc | Bin 0 -> 59093 bytes .../mako/__pycache__/compat.cpython-312.pyc | Bin 0 -> 3095 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 14806 bytes .../mako/__pycache__/filters.cpython-312.pyc | Bin 0 -> 6727 bytes .../mako/__pycache__/lexer.cpython-312.pyc | Bin 0 -> 20366 bytes .../mako/__pycache__/lookup.cpython-312.pyc | Bin 0 -> 13735 bytes .../__pycache__/parsetree.cpython-312.pyc | Bin 0 -> 29993 bytes .../mako/__pycache__/pygen.cpython-312.pyc | Bin 0 -> 11075 bytes .../mako/__pycache__/pyparser.cpython-312.pyc | Bin 0 -> 12309 bytes .../mako/__pycache__/runtime.cpython-312.pyc | Bin 0 -> 39134 bytes .../mako/__pycache__/template.cpython-312.pyc | Bin 0 -> 26820 bytes .../mako/__pycache__/util.cpython-312.pyc | Bin 0 -> 20400 bytes .../site-packages/mako/_ast_util.py | 713 ++ venv/lib/python3.12/site-packages/mako/ast.py | 202 + .../python3.12/site-packages/mako/cache.py | 239 + venv/lib/python3.12/site-packages/mako/cmd.py | 99 + .../python3.12/site-packages/mako/codegen.py | 1319 +++ .../python3.12/site-packages/mako/compat.py | 70 + .../site-packages/mako/exceptions.py | 417 + .../site-packages/mako/ext/__init__.py | 0 .../ext/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 210 bytes .../__pycache__/autohandler.cpython-312.pyc | Bin 0 -> 2383 bytes .../__pycache__/babelplugin.cpython-312.pyc | Bin 0 -> 2696 bytes .../__pycache__/beaker_cache.cpython-312.pyc | Bin 0 -> 3945 bytes .../ext/__pycache__/extract.cpython-312.pyc | Bin 0 -> 5173 bytes .../__pycache__/linguaplugin.cpython-312.pyc | Bin 0 -> 2659 bytes .../__pycache__/preprocessors.cpython-312.pyc | Bin 0 -> 705 bytes .../__pycache__/pygmentplugin.cpython-312.pyc | Bin 0 -> 5940 bytes .../__pycache__/turbogears.cpython-312.pyc | Bin 0 -> 2482 bytes .../site-packages/mako/ext/autohandler.py | 70 + .../site-packages/mako/ext/babelplugin.py | 57 + .../site-packages/mako/ext/beaker_cache.py | 82 + .../site-packages/mako/ext/extract.py | 129 + .../site-packages/mako/ext/linguaplugin.py | 57 + .../site-packages/mako/ext/preprocessors.py | 20 + .../site-packages/mako/ext/pygmentplugin.py | 150 + .../site-packages/mako/ext/turbogears.py | 61 + .../python3.12/site-packages/mako/filters.py | 163 + .../python3.12/site-packages/mako/lexer.py | 481 + .../python3.12/site-packages/mako/lookup.py | 361 + .../site-packages/mako/parsetree.py | 656 ++ .../python3.12/site-packages/mako/pygen.py | 309 + .../python3.12/site-packages/mako/pyparser.py | 235 + .../python3.12/site-packages/mako/runtime.py | 968 ++ .../python3.12/site-packages/mako/template.py | 711 ++ .../site-packages/mako/testing/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 214 bytes .../__pycache__/_config.cpython-312.pyc | Bin 0 -> 5843 bytes .../__pycache__/assertions.cpython-312.pyc | Bin 0 -> 5923 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 828 bytes .../__pycache__/exclusions.cpython-312.pyc | Bin 0 -> 2431 bytes .../__pycache__/fixtures.cpython-312.pyc | Bin 0 -> 4825 bytes .../__pycache__/helpers.cpython-312.pyc | Bin 0 -> 3536 bytes .../site-packages/mako/testing/_config.py | 128 + .../site-packages/mako/testing/assertions.py | 166 + .../site-packages/mako/testing/config.py | 17 + .../site-packages/mako/testing/exclusions.py | 80 + .../site-packages/mako/testing/fixtures.py | 119 + .../site-packages/mako/testing/helpers.py | 71 + .../lib/python3.12/site-packages/mako/util.py | 388 + .../markupsafe-3.0.3.dist-info/INSTALLER | 1 + .../markupsafe-3.0.3.dist-info/METADATA | 74 + .../markupsafe-3.0.3.dist-info/RECORD | 14 + .../markupsafe-3.0.3.dist-info/WHEEL | 7 + .../licenses/LICENSE.txt | 28 + .../markupsafe-3.0.3.dist-info/top_level.txt | 1 + .../site-packages/markupsafe/__init__.py | 396 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 20987 bytes .../__pycache__/_native.cpython-312.pyc | Bin 0 -> 639 bytes .../site-packages/markupsafe/_native.py | 8 + .../site-packages/markupsafe/_speedups.c | 200 + .../_speedups.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 44072 bytes .../site-packages/markupsafe/_speedups.pyi | 1 + .../site-packages/markupsafe/py.typed | 0 .../pip-24.3.1.dist-info/AUTHORS.txt | 799 ++ .../pip-24.3.1.dist-info/INSTALLER | 1 + .../pip-24.3.1.dist-info/LICENSE.txt | 20 + .../pip-24.3.1.dist-info/METADATA | 90 + .../site-packages/pip-24.3.1.dist-info/RECORD | 854 ++ .../pip-24.3.1.dist-info/REQUESTED | 0 .../site-packages/pip-24.3.1.dist-info/WHEEL | 5 + .../pip-24.3.1.dist-info/direct_url.json | 1 + .../pip-24.3.1.dist-info/entry_points.txt | 3 + .../pip-24.3.1.dist-info/top_level.txt | 1 + .../python3.12/site-packages/pip/__init__.py | 13 + .../python3.12/site-packages/pip/__main__.py | 24 + .../site-packages/pip/__pip-runner__.py | 50 + .../pip/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 716 bytes .../pip/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 870 bytes .../__pip-runner__.cpython-312.pyc | Bin 0 -> 2233 bytes .../site-packages/pip/_internal/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 816 bytes .../__pycache__/build_env.cpython-312.pyc | Bin 0 -> 14572 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 12694 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 17695 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 36902 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 699 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 5158 bytes .../self_outdated_check.cpython-312.pyc | Bin 0 -> 10251 bytes .../__pycache__/wheel_builder.cpython-312.pyc | Bin 0 -> 13667 bytes .../site-packages/pip/_internal/build_env.py | 319 + .../site-packages/pip/_internal/cache.py | 290 + .../pip/_internal/cli/__init__.py | 4 + .../cli/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 307 bytes .../autocompletion.cpython-312.pyc | Bin 0 -> 8661 bytes .../__pycache__/base_command.cpython-312.pyc | Bin 0 -> 10234 bytes .../__pycache__/cmdoptions.cpython-312.pyc | Bin 0 -> 30427 bytes .../command_context.cpython-312.pyc | Bin 0 -> 1810 bytes .../__pycache__/index_command.cpython-312.pyc | Bin 0 -> 7164 bytes .../cli/__pycache__/main.cpython-312.pyc | Bin 0 -> 2329 bytes .../__pycache__/main_parser.cpython-312.pyc | Bin 0 -> 4934 bytes .../cli/__pycache__/parser.cpython-312.pyc | Bin 0 -> 15122 bytes .../__pycache__/progress_bars.cpython-312.pyc | Bin 0 -> 3894 bytes .../__pycache__/req_command.cpython-312.pyc | Bin 0 -> 12304 bytes .../cli/__pycache__/spinners.cpython-312.pyc | Bin 0 -> 7869 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 404 bytes .../pip/_internal/cli/autocompletion.py | 176 + .../pip/_internal/cli/base_command.py | 231 + .../pip/_internal/cli/cmdoptions.py | 1075 ++ .../pip/_internal/cli/command_context.py | 27 + .../pip/_internal/cli/index_command.py | 170 + .../site-packages/pip/_internal/cli/main.py | 80 + .../pip/_internal/cli/main_parser.py | 134 + .../site-packages/pip/_internal/cli/parser.py | 294 + .../pip/_internal/cli/progress_bars.py | 94 + .../pip/_internal/cli/req_command.py | 329 + .../pip/_internal/cli/spinners.py | 159 + .../pip/_internal/cli/status_codes.py | 6 + .../pip/_internal/commands/__init__.py | 132 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4031 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 9740 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 2645 bytes .../__pycache__/completion.cpython-312.pyc | Bin 0 -> 5221 bytes .../__pycache__/configuration.cpython-312.pyc | Bin 0 -> 13241 bytes .../__pycache__/debug.cpython-312.pyc | Bin 0 -> 10143 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 7537 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 4419 bytes .../commands/__pycache__/hash.cpython-312.pyc | Bin 0 -> 3012 bytes .../commands/__pycache__/help.cpython-312.pyc | Bin 0 -> 1702 bytes .../__pycache__/index.cpython-312.pyc | Bin 0 -> 6705 bytes .../__pycache__/inspect.cpython-312.pyc | Bin 0 -> 4005 bytes .../__pycache__/install.cpython-312.pyc | Bin 0 -> 29192 bytes .../commands/__pycache__/list.cpython-312.pyc | Bin 0 -> 15797 bytes .../__pycache__/search.cpython-312.pyc | Bin 0 -> 7574 bytes .../commands/__pycache__/show.cpython-312.pyc | Bin 0 -> 10521 bytes .../__pycache__/uninstall.cpython-312.pyc | Bin 0 -> 4762 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 8904 bytes .../pip/_internal/commands/cache.py | 225 + .../pip/_internal/commands/check.py | 67 + .../pip/_internal/commands/completion.py | 130 + .../pip/_internal/commands/configuration.py | 280 + .../pip/_internal/commands/debug.py | 201 + .../pip/_internal/commands/download.py | 146 + .../pip/_internal/commands/freeze.py | 109 + .../pip/_internal/commands/hash.py | 59 + .../pip/_internal/commands/help.py | 41 + .../pip/_internal/commands/index.py | 139 + .../pip/_internal/commands/inspect.py | 92 + .../pip/_internal/commands/install.py | 783 ++ .../pip/_internal/commands/list.py | 375 + .../pip/_internal/commands/search.py | 172 + .../pip/_internal/commands/show.py | 217 + .../pip/_internal/commands/uninstall.py | 114 + .../pip/_internal/commands/wheel.py | 182 + .../pip/_internal/configuration.py | 383 + .../pip/_internal/distributions/__init__.py | 21 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 970 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 2922 bytes .../__pycache__/installed.cpython-312.pyc | Bin 0 -> 1729 bytes .../__pycache__/sdist.cpython-312.pyc | Bin 0 -> 8511 bytes .../__pycache__/wheel.cpython-312.pyc | Bin 0 -> 2310 bytes .../pip/_internal/distributions/base.py | 53 + .../pip/_internal/distributions/installed.py | 29 + .../pip/_internal/distributions/sdist.py | 158 + .../pip/_internal/distributions/wheel.py | 42 + .../site-packages/pip/_internal/exceptions.py | 809 ++ .../pip/_internal/index/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 261 bytes .../__pycache__/collector.cpython-312.pyc | Bin 0 -> 21654 bytes .../package_finder.cpython-312.pyc | Bin 0 -> 40711 bytes .../index/__pycache__/sources.cpython-312.pyc | Bin 0 -> 12569 bytes .../pip/_internal/index/collector.py | 494 + .../pip/_internal/index/package_finder.py | 1020 ++ .../pip/_internal/index/sources.py | 284 + .../pip/_internal/locations/__init__.py | 456 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 16480 bytes .../__pycache__/_distutils.cpython-312.pyc | Bin 0 -> 6824 bytes .../__pycache__/_sysconfig.cpython-312.pyc | Bin 0 -> 8062 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 3810 bytes .../pip/_internal/locations/_distutils.py | 172 + .../pip/_internal/locations/_sysconfig.py | 214 + .../pip/_internal/locations/base.py | 81 + .../site-packages/pip/_internal/main.py | 12 + .../pip/_internal/metadata/__init__.py | 128 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5911 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 2959 bytes .../metadata/__pycache__/base.cpython-312.pyc | Bin 0 -> 35253 bytes .../__pycache__/pkg_resources.cpython-312.pyc | Bin 0 -> 16134 bytes .../pip/_internal/metadata/_json.py | 84 + .../pip/_internal/metadata/base.py | 688 ++ .../_internal/metadata/importlib/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 387 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 4520 bytes .../__pycache__/_dists.cpython-312.pyc | Bin 0 -> 12602 bytes .../__pycache__/_envs.cpython-312.pyc | Bin 0 -> 11172 bytes .../_internal/metadata/importlib/_compat.py | 85 + .../_internal/metadata/importlib/_dists.py | 221 + .../pip/_internal/metadata/importlib/_envs.py | 189 + .../pip/_internal/metadata/pkg_resources.py | 301 + .../pip/_internal/models/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 295 bytes .../__pycache__/candidate.cpython-312.pyc | Bin 0 -> 1633 bytes .../__pycache__/direct_url.cpython-312.pyc | Bin 0 -> 10868 bytes .../format_control.cpython-312.pyc | Bin 0 -> 4256 bytes .../models/__pycache__/index.cpython-312.pyc | Bin 0 -> 1723 bytes .../installation_report.cpython-312.pyc | Bin 0 -> 2301 bytes .../models/__pycache__/link.cpython-312.pyc | Bin 0 -> 26633 bytes .../models/__pycache__/scheme.cpython-312.pyc | Bin 0 -> 1052 bytes .../__pycache__/search_scope.cpython-312.pyc | Bin 0 -> 5025 bytes .../selection_prefs.cpython-312.pyc | Bin 0 -> 1880 bytes .../__pycache__/target_python.cpython-312.pyc | Bin 0 -> 4983 bytes .../models/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 6601 bytes .../pip/_internal/models/candidate.py | 25 + .../pip/_internal/models/direct_url.py | 224 + .../pip/_internal/models/format_control.py | 78 + .../pip/_internal/models/index.py | 28 + .../_internal/models/installation_report.py | 56 + .../pip/_internal/models/link.py | 590 ++ .../pip/_internal/models/scheme.py | 25 + .../pip/_internal/models/search_scope.py | 127 + .../pip/_internal/models/selection_prefs.py | 53 + .../pip/_internal/models/target_python.py | 121 + .../pip/_internal/models/wheel.py | 118 + .../pip/_internal/network/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 283 bytes .../network/__pycache__/auth.cpython-312.pyc | Bin 0 -> 22137 bytes .../network/__pycache__/cache.cpython-312.pyc | Bin 0 -> 6547 bytes .../__pycache__/download.cpython-312.pyc | Bin 0 -> 8537 bytes .../__pycache__/lazy_wheel.cpython-312.pyc | Bin 0 -> 11692 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 18911 bytes .../network/__pycache__/utils.cpython-312.pyc | Bin 0 -> 2289 bytes .../__pycache__/xmlrpc.cpython-312.pyc | Bin 0 -> 2978 bytes .../pip/_internal/network/auth.py | 566 ++ .../pip/_internal/network/cache.py | 106 + .../pip/_internal/network/download.py | 187 + .../pip/_internal/network/lazy_wheel.py | 210 + .../pip/_internal/network/session.py | 522 + .../pip/_internal/network/utils.py | 98 + .../pip/_internal/network/xmlrpc.py | 62 + .../pip/_internal/operations/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 226 bytes .../__pycache__/check.cpython-312.pyc | Bin 0 -> 7182 bytes .../__pycache__/freeze.cpython-312.pyc | Bin 0 -> 10210 bytes .../__pycache__/prepare.cpython-312.pyc | Bin 0 -> 25899 bytes .../_internal/operations/build/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 232 bytes .../__pycache__/build_tracker.cpython-312.pyc | Bin 0 -> 7748 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 1909 bytes .../metadata_editable.cpython-312.pyc | Bin 0 -> 1943 bytes .../metadata_legacy.cpython-312.pyc | Bin 0 -> 3056 bytes .../build/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 1713 bytes .../wheel_editable.cpython-312.pyc | Bin 0 -> 2054 bytes .../__pycache__/wheel_legacy.cpython-312.pyc | Bin 0 -> 3886 bytes .../operations/build/build_tracker.py | 138 + .../_internal/operations/build/metadata.py | 39 + .../operations/build/metadata_editable.py | 41 + .../operations/build/metadata_legacy.py | 74 + .../pip/_internal/operations/build/wheel.py | 37 + .../operations/build/wheel_editable.py | 46 + .../operations/build/wheel_legacy.py | 102 + .../pip/_internal/operations/check.py | 181 + .../pip/_internal/operations/freeze.py | 258 + .../_internal/operations/install/__init__.py | 2 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 295 bytes .../editable_legacy.cpython-312.pyc | Bin 0 -> 1848 bytes .../install/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 34275 bytes .../operations/install/editable_legacy.py | 47 + .../pip/_internal/operations/install/wheel.py | 741 ++ .../pip/_internal/operations/prepare.py | 732 ++ .../site-packages/pip/_internal/pyproject.py | 185 + .../pip/_internal/req/__init__.py | 90 + .../req/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3505 bytes .../__pycache__/constructors.cpython-312.pyc | Bin 0 -> 21305 bytes .../req/__pycache__/req_file.cpython-312.pyc | Bin 0 -> 22201 bytes .../__pycache__/req_install.cpython-312.pyc | Bin 0 -> 38568 bytes .../req/__pycache__/req_set.cpython-312.pyc | Bin 0 -> 5504 bytes .../__pycache__/req_uninstall.cpython-312.pyc | Bin 0 -> 32279 bytes .../pip/_internal/req/constructors.py | 560 ++ .../pip/_internal/req/req_file.py | 574 ++ .../pip/_internal/req/req_install.py | 934 ++ .../pip/_internal/req/req_set.py | 82 + .../pip/_internal/req/req_uninstall.py | 633 ++ .../pip/_internal/resolution/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 226 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 1214 bytes .../pip/_internal/resolution/base.py | 20 + .../_internal/resolution/legacy/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 233 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 22646 bytes .../_internal/resolution/legacy/resolver.py | 597 ++ .../resolution/resolvelib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 237 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 8177 bytes .../__pycache__/candidates.cpython-312.pyc | Bin 0 -> 29458 bytes .../__pycache__/factory.cpython-312.pyc | Bin 0 -> 32603 bytes .../found_candidates.cpython-312.pyc | Bin 0 -> 6836 bytes .../__pycache__/provider.cpython-312.pyc | Bin 0 -> 10552 bytes .../__pycache__/reporter.cpython-312.pyc | Bin 0 -> 5074 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 15379 bytes .../__pycache__/resolver.cpython-312.pyc | Bin 0 -> 12380 bytes .../_internal/resolution/resolvelib/base.py | 139 + .../resolution/resolvelib/candidates.py | 574 ++ .../resolution/resolvelib/factory.py | 823 ++ .../resolution/resolvelib/found_candidates.py | 174 + .../resolution/resolvelib/provider.py | 258 + .../resolution/resolvelib/reporter.py | 81 + .../resolution/resolvelib/requirements.py | 245 + .../resolution/resolvelib/resolver.py | 317 + .../pip/_internal/self_outdated_check.py | 244 + .../pip/_internal/utils/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../__pycache__/_jaraco_text.cpython-312.pyc | Bin 0 -> 4561 bytes .../utils/__pycache__/_log.cpython-312.pyc | Bin 0 -> 1892 bytes .../utils/__pycache__/appdirs.cpython-312.pyc | Bin 0 -> 2436 bytes .../utils/__pycache__/compat.cpython-312.pyc | Bin 0 -> 2933 bytes .../compatibility_tags.cpython-312.pyc | Bin 0 -> 6370 bytes .../__pycache__/datetime.cpython-312.pyc | Bin 0 -> 710 bytes .../__pycache__/deprecation.cpython-312.pyc | Bin 0 -> 4216 bytes .../direct_url_helpers.cpython-312.pyc | Bin 0 -> 3562 bytes .../__pycache__/egg_link.cpython-312.pyc | Bin 0 -> 3252 bytes .../__pycache__/encoding.cpython-312.pyc | Bin 0 -> 2184 bytes .../__pycache__/entrypoints.cpython-312.pyc | Bin 0 -> 4019 bytes .../__pycache__/filesystem.cpython-312.pyc | Bin 0 -> 7378 bytes .../__pycache__/filetypes.cpython-312.pyc | Bin 0 -> 1190 bytes .../utils/__pycache__/glibc.cpython-312.pyc | Bin 0 -> 2445 bytes .../utils/__pycache__/hashes.cpython-312.pyc | Bin 0 -> 7675 bytes .../utils/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13583 bytes .../utils/__pycache__/misc.cpython-312.pyc | Bin 0 -> 33485 bytes .../__pycache__/packaging.cpython-312.pyc | Bin 0 -> 2609 bytes .../utils/__pycache__/retry.cpython-312.pyc | Bin 0 -> 2134 bytes .../setuptools_build.cpython-312.pyc | Bin 0 -> 4576 bytes .../__pycache__/subprocess.cpython-312.pyc | Bin 0 -> 8690 bytes .../__pycache__/temp_dir.cpython-312.pyc | Bin 0 -> 12088 bytes .../__pycache__/unpacking.cpython-312.pyc | Bin 0 -> 13557 bytes .../utils/__pycache__/urls.cpython-312.pyc | Bin 0 -> 2103 bytes .../__pycache__/virtualenv.cpython-312.pyc | Bin 0 -> 4506 bytes .../utils/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 5925 bytes .../pip/_internal/utils/_jaraco_text.py | 109 + .../site-packages/pip/_internal/utils/_log.py | 38 + .../pip/_internal/utils/appdirs.py | 52 + .../pip/_internal/utils/compat.py | 79 + .../pip/_internal/utils/compatibility_tags.py | 188 + .../pip/_internal/utils/datetime.py | 11 + .../pip/_internal/utils/deprecation.py | 124 + .../pip/_internal/utils/direct_url_helpers.py | 87 + .../pip/_internal/utils/egg_link.py | 80 + .../pip/_internal/utils/encoding.py | 36 + .../pip/_internal/utils/entrypoints.py | 84 + .../pip/_internal/utils/filesystem.py | 149 + .../pip/_internal/utils/filetypes.py | 27 + .../pip/_internal/utils/glibc.py | 101 + .../pip/_internal/utils/hashes.py | 147 + .../pip/_internal/utils/logging.py | 347 + .../site-packages/pip/_internal/utils/misc.py | 772 ++ .../pip/_internal/utils/packaging.py | 57 + .../pip/_internal/utils/retry.py | 42 + .../pip/_internal/utils/setuptools_build.py | 146 + .../pip/_internal/utils/subprocess.py | 245 + .../pip/_internal/utils/temp_dir.py | 296 + .../pip/_internal/utils/unpacking.py | 337 + .../site-packages/pip/_internal/utils/urls.py | 55 + .../pip/_internal/utils/virtualenv.py | 104 + .../pip/_internal/utils/wheel.py | 134 + .../pip/_internal/vcs/__init__.py | 15 + .../vcs/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 560 bytes .../vcs/__pycache__/bazaar.cpython-312.pyc | Bin 0 -> 5090 bytes .../vcs/__pycache__/git.cpython-312.pyc | Bin 0 -> 19056 bytes .../vcs/__pycache__/mercurial.cpython-312.pyc | Bin 0 -> 7641 bytes .../__pycache__/subversion.cpython-312.pyc | Bin 0 -> 12554 bytes .../versioncontrol.cpython-312.pyc | Bin 0 -> 29037 bytes .../site-packages/pip/_internal/vcs/bazaar.py | 112 + .../site-packages/pip/_internal/vcs/git.py | 527 + .../pip/_internal/vcs/mercurial.py | 163 + .../pip/_internal/vcs/subversion.py | 324 + .../pip/_internal/vcs/versioncontrol.py | 688 ++ .../pip/_internal/wheel_builder.py | 354 + .../site-packages/pip/_vendor/__init__.py | 116 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4579 bytes .../typing_extensions.cpython-312.pyc | Bin 0 -> 139575 bytes .../pip/_vendor/cachecontrol/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 932 bytes .../__pycache__/_cmd.cpython-312.pyc | Bin 0 -> 2676 bytes .../__pycache__/adapter.cpython-312.pyc | Bin 0 -> 6494 bytes .../__pycache__/cache.cpython-312.pyc | Bin 0 -> 3839 bytes .../__pycache__/controller.cpython-312.pyc | Bin 0 -> 16254 bytes .../__pycache__/filewrapper.cpython-312.pyc | Bin 0 -> 4377 bytes .../__pycache__/heuristics.cpython-312.pyc | Bin 0 -> 6724 bytes .../__pycache__/serialize.cpython-312.pyc | Bin 0 -> 5295 bytes .../__pycache__/wrapper.cpython-312.pyc | Bin 0 -> 1704 bytes .../pip/_vendor/cachecontrol/_cmd.py | 70 + .../pip/_vendor/cachecontrol/adapter.py | 161 + .../pip/_vendor/cachecontrol/cache.py | 74 + .../_vendor/cachecontrol/caches/__init__.py | 8 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 465 bytes .../__pycache__/file_cache.cpython-312.pyc | Bin 0 -> 7818 bytes .../__pycache__/redis_cache.cpython-312.pyc | Bin 0 -> 2768 bytes .../_vendor/cachecontrol/caches/file_cache.py | 182 + .../cachecontrol/caches/redis_cache.py | 48 + .../pip/_vendor/cachecontrol/controller.py | 499 + .../pip/_vendor/cachecontrol/filewrapper.py | 119 + .../pip/_vendor/cachecontrol/heuristics.py | 154 + .../pip/_vendor/cachecontrol/py.typed | 0 .../pip/_vendor/cachecontrol/serialize.py | 146 + .../pip/_vendor/cachecontrol/wrapper.py | 43 + .../pip/_vendor/certifi/__init__.py | 4 + .../pip/_vendor/certifi/__main__.py | 12 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 348 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 675 bytes .../certifi/__pycache__/core.cpython-312.pyc | Bin 0 -> 3250 bytes .../pip/_vendor/certifi/cacert.pem | 4929 +++++++++ .../site-packages/pip/_vendor/certifi/core.py | 114 + .../pip/_vendor/certifi/py.typed | 0 .../pip/_vendor/distlib/__init__.py | 33 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1299 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 45630 bytes .../__pycache__/database.cpython-312.pyc | Bin 0 -> 65955 bytes .../distlib/__pycache__/index.cpython-312.pyc | Bin 0 -> 24396 bytes .../__pycache__/locators.cpython-312.pyc | Bin 0 -> 60119 bytes .../__pycache__/manifest.cpython-312.pyc | Bin 0 -> 15155 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 7688 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 41727 bytes .../__pycache__/resources.cpython-312.pyc | Bin 0 -> 17355 bytes .../__pycache__/scripts.cpython-312.pyc | Bin 0 -> 19806 bytes .../distlib/__pycache__/util.cpython-312.pyc | Bin 0 -> 88304 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 30395 bytes .../distlib/__pycache__/wheel.cpython-312.pyc | Bin 0 -> 52985 bytes .../pip/_vendor/distlib/compat.py | 1137 +++ .../pip/_vendor/distlib/database.py | 1329 +++ .../pip/_vendor/distlib/index.py | 508 + .../pip/_vendor/distlib/locators.py | 1295 +++ .../pip/_vendor/distlib/manifest.py | 384 + .../pip/_vendor/distlib/markers.py | 162 + .../pip/_vendor/distlib/metadata.py | 1031 ++ .../pip/_vendor/distlib/resources.py | 358 + .../pip/_vendor/distlib/scripts.py | 447 + .../site-packages/pip/_vendor/distlib/t32.exe | Bin 0 -> 97792 bytes .../pip/_vendor/distlib/t64-arm.exe | Bin 0 -> 182784 bytes .../site-packages/pip/_vendor/distlib/t64.exe | Bin 0 -> 108032 bytes .../site-packages/pip/_vendor/distlib/util.py | 1984 ++++ .../pip/_vendor/distlib/version.py | 750 ++ .../site-packages/pip/_vendor/distlib/w32.exe | Bin 0 -> 91648 bytes .../pip/_vendor/distlib/w64-arm.exe | Bin 0 -> 168448 bytes .../site-packages/pip/_vendor/distlib/w64.exe | Bin 0 -> 101888 bytes .../pip/_vendor/distlib/wheel.py | 1100 ++ .../pip/_vendor/distro/__init__.py | 54 + .../pip/_vendor/distro/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 990 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 322 bytes .../distro/__pycache__/distro.cpython-312.pyc | Bin 0 -> 53875 bytes .../pip/_vendor/distro/distro.py | 1403 +++ .../site-packages/pip/_vendor/distro/py.typed | 0 .../pip/_vendor/idna/__init__.py | 44 + .../idna/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 911 bytes .../idna/__pycache__/codec.cpython-312.pyc | Bin 0 -> 5016 bytes .../idna/__pycache__/compat.cpython-312.pyc | Bin 0 -> 917 bytes .../idna/__pycache__/core.cpython-312.pyc | Bin 0 -> 15878 bytes .../idna/__pycache__/idnadata.cpython-312.pyc | Bin 0 -> 99506 bytes .../__pycache__/intranges.cpython-312.pyc | Bin 0 -> 2668 bytes .../__pycache__/package_data.cpython-312.pyc | Bin 0 -> 246 bytes .../__pycache__/uts46data.cpython-312.pyc | Bin 0 -> 158878 bytes .../site-packages/pip/_vendor/idna/codec.py | 118 + .../site-packages/pip/_vendor/idna/compat.py | 13 + .../site-packages/pip/_vendor/idna/core.py | 395 + .../pip/_vendor/idna/idnadata.py | 4245 ++++++++ .../pip/_vendor/idna/intranges.py | 54 + .../pip/_vendor/idna/package_data.py | 2 + .../site-packages/pip/_vendor/idna/py.typed | 0 .../pip/_vendor/idna/uts46data.py | 8598 ++++++++++++++++ .../pip/_vendor/msgpack/__init__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1771 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2055 bytes .../msgpack/__pycache__/ext.cpython-312.pyc | Bin 0 -> 8200 bytes .../__pycache__/fallback.cpython-312.pyc | Bin 0 -> 42104 bytes .../pip/_vendor/msgpack/exceptions.py | 48 + .../site-packages/pip/_vendor/msgpack/ext.py | 168 + .../pip/_vendor/msgpack/fallback.py | 951 ++ .../pip/_vendor/packaging/__init__.py | 15 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 588 bytes .../__pycache__/_elffile.cpython-312.pyc | Bin 0 -> 5004 bytes .../__pycache__/_manylinux.cpython-312.pyc | Bin 0 -> 9760 bytes .../__pycache__/_musllinux.cpython-312.pyc | Bin 0 -> 4599 bytes .../__pycache__/_parser.cpython-312.pyc | Bin 0 -> 14030 bytes .../__pycache__/_structures.cpython-312.pyc | Bin 0 -> 3271 bytes .../__pycache__/_tokenizer.cpython-312.pyc | Bin 0 -> 7941 bytes .../__pycache__/markers.cpython-312.pyc | Bin 0 -> 11043 bytes .../__pycache__/metadata.cpython-312.pyc | Bin 0 -> 25022 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 4440 bytes .../__pycache__/specifiers.cpython-312.pyc | Bin 0 -> 38794 bytes .../__pycache__/tags.cpython-312.pyc | Bin 0 -> 23373 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 7369 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 19532 bytes .../pip/_vendor/packaging/_elffile.py | 110 + .../pip/_vendor/packaging/_manylinux.py | 262 + .../pip/_vendor/packaging/_musllinux.py | 85 + .../pip/_vendor/packaging/_parser.py | 354 + .../pip/_vendor/packaging/_structures.py | 61 + .../pip/_vendor/packaging/_tokenizer.py | 194 + .../pip/_vendor/packaging/markers.py | 325 + .../pip/_vendor/packaging/metadata.py | 804 ++ .../pip/_vendor/packaging/py.typed | 0 .../pip/_vendor/packaging/requirements.py | 91 + .../pip/_vendor/packaging/specifiers.py | 1009 ++ .../pip/_vendor/packaging/tags.py | 627 ++ .../pip/_vendor/packaging/utils.py | 174 + .../pip/_vendor/packaging/version.py | 563 ++ .../pip/_vendor/pkg_resources/__init__.py | 3676 +++++++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 161555 bytes .../pip/_vendor/platformdirs/__init__.py | 627 ++ .../pip/_vendor/platformdirs/__main__.py | 55 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 19859 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 1998 bytes .../__pycache__/android.cpython-312.pyc | Bin 0 -> 10737 bytes .../__pycache__/api.cpython-312.pyc | Bin 0 -> 12961 bytes .../__pycache__/macos.cpython-312.pyc | Bin 0 -> 8037 bytes .../__pycache__/unix.cpython-312.pyc | Bin 0 -> 15062 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 627 bytes .../__pycache__/windows.cpython-312.pyc | Bin 0 -> 13703 bytes .../pip/_vendor/platformdirs/android.py | 249 + .../pip/_vendor/platformdirs/api.py | 292 + .../pip/_vendor/platformdirs/macos.py | 130 + .../pip/_vendor/platformdirs/py.typed | 0 .../pip/_vendor/platformdirs/unix.py | 275 + .../pip/_vendor/platformdirs/version.py | 16 + .../pip/_vendor/platformdirs/windows.py | 272 + .../pip/_vendor/pygments/__init__.py | 82 + .../pip/_vendor/pygments/__main__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3519 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 765 bytes .../__pycache__/cmdline.cpython-312.pyc | Bin 0 -> 26699 bytes .../__pycache__/console.cpython-312.pyc | Bin 0 -> 2664 bytes .../__pycache__/filter.cpython-312.pyc | Bin 0 -> 3257 bytes .../__pycache__/formatter.cpython-312.pyc | Bin 0 -> 4751 bytes .../__pycache__/lexer.cpython-312.pyc | Bin 0 -> 38496 bytes .../__pycache__/modeline.cpython-312.pyc | Bin 0 -> 1600 bytes .../__pycache__/plugin.cpython-312.pyc | Bin 0 -> 2659 bytes .../__pycache__/regexopt.cpython-312.pyc | Bin 0 -> 4112 bytes .../__pycache__/scanner.cpython-312.pyc | Bin 0 -> 4787 bytes .../__pycache__/sphinxext.cpython-312.pyc | Bin 0 -> 12172 bytes .../__pycache__/style.cpython-312.pyc | Bin 0 -> 6748 bytes .../__pycache__/token.cpython-312.pyc | Bin 0 -> 8225 bytes .../__pycache__/unistring.cpython-312.pyc | Bin 0 -> 33042 bytes .../pygments/__pycache__/util.cpython-312.pyc | Bin 0 -> 14114 bytes .../pip/_vendor/pygments/cmdline.py | 668 ++ .../pip/_vendor/pygments/console.py | 70 + .../pip/_vendor/pygments/filter.py | 70 + .../pip/_vendor/pygments/filters/__init__.py | 940 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 38010 bytes .../pip/_vendor/pygments/formatter.py | 129 + .../_vendor/pygments/formatters/__init__.py | 157 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6984 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 4246 bytes .../__pycache__/bbcode.cpython-312.pyc | Bin 0 -> 4263 bytes .../__pycache__/groff.cpython-312.pyc | Bin 0 -> 7363 bytes .../__pycache__/html.cpython-312.pyc | Bin 0 -> 41128 bytes .../__pycache__/img.cpython-312.pyc | Bin 0 -> 28684 bytes .../__pycache__/irc.cpython-312.pyc | Bin 0 -> 6096 bytes .../__pycache__/latex.cpython-312.pyc | Bin 0 -> 20202 bytes .../__pycache__/other.cpython-312.pyc | Bin 0 -> 6930 bytes .../__pycache__/pangomarkup.cpython-312.pyc | Bin 0 -> 2999 bytes .../__pycache__/rtf.cpython-312.pyc | Bin 0 -> 13855 bytes .../__pycache__/svg.cpython-312.pyc | Bin 0 -> 9182 bytes .../__pycache__/terminal.cpython-312.pyc | Bin 0 -> 5860 bytes .../__pycache__/terminal256.cpython-312.pyc | Bin 0 -> 15188 bytes .../_vendor/pygments/formatters/_mapping.py | 23 + .../pip/_vendor/pygments/formatters/bbcode.py | 108 + .../pip/_vendor/pygments/formatters/groff.py | 170 + .../pip/_vendor/pygments/formatters/html.py | 987 ++ .../pip/_vendor/pygments/formatters/img.py | 685 ++ .../pip/_vendor/pygments/formatters/irc.py | 154 + .../pip/_vendor/pygments/formatters/latex.py | 518 + .../pip/_vendor/pygments/formatters/other.py | 160 + .../pygments/formatters/pangomarkup.py | 83 + .../pip/_vendor/pygments/formatters/rtf.py | 349 + .../pip/_vendor/pygments/formatters/svg.py | 185 + .../_vendor/pygments/formatters/terminal.py | 127 + .../pygments/formatters/terminal256.py | 338 + .../pip/_vendor/pygments/lexer.py | 963 ++ .../pip/_vendor/pygments/lexers/__init__.py | 362 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14766 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 68294 bytes .../lexers/__pycache__/python.cpython-312.pyc | Bin 0 -> 43000 bytes .../pip/_vendor/pygments/lexers/_mapping.py | 589 ++ .../pip/_vendor/pygments/lexers/python.py | 1198 +++ .../pip/_vendor/pygments/modeline.py | 43 + .../pip/_vendor/pygments/plugin.py | 72 + .../pip/_vendor/pygments/regexopt.py | 91 + .../pip/_vendor/pygments/scanner.py | 104 + .../pip/_vendor/pygments/sphinxext.py | 247 + .../pip/_vendor/pygments/style.py | 203 + .../pip/_vendor/pygments/styles/__init__.py | 61 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2702 bytes .../__pycache__/_mapping.cpython-312.pyc | Bin 0 -> 3679 bytes .../pip/_vendor/pygments/styles/_mapping.py | 54 + .../pip/_vendor/pygments/token.py | 214 + .../pip/_vendor/pygments/unistring.py | 153 + .../pip/_vendor/pygments/util.py | 324 + .../pip/_vendor/pyproject_hooks/__init__.py | 23 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 644 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 405 bytes .../__pycache__/_impl.cpython-312.pyc | Bin 0 -> 14756 bytes .../pip/_vendor/pyproject_hooks/_compat.py | 8 + .../pip/_vendor/pyproject_hooks/_impl.py | 330 + .../pyproject_hooks/_in_process/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1111 bytes .../__pycache__/_in_process.cpython-312.pyc | Bin 0 -> 14428 bytes .../_in_process/_in_process.py | 353 + .../pip/_vendor/requests/__init__.py | 179 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5284 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 615 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2055 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 28468 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7235 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13954 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 953 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 1708 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25307 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7629 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4259 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1083 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35509 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1318 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27913 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 6062 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5648 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36473 bytes .../pip/_vendor/requests/__version__.py | 14 + .../pip/_vendor/requests/_internal_utils.py | 50 + .../pip/_vendor/requests/adapters.py | 719 ++ .../site-packages/pip/_vendor/requests/api.py | 157 + .../pip/_vendor/requests/auth.py | 314 + .../pip/_vendor/requests/certs.py | 24 + .../pip/_vendor/requests/compat.py | 78 + .../pip/_vendor/requests/cookies.py | 561 ++ .../pip/_vendor/requests/exceptions.py | 151 + .../pip/_vendor/requests/help.py | 127 + .../pip/_vendor/requests/hooks.py | 33 + .../pip/_vendor/requests/models.py | 1037 ++ .../pip/_vendor/requests/packages.py | 25 + .../pip/_vendor/requests/sessions.py | 831 ++ .../pip/_vendor/requests/status_codes.py | 128 + .../pip/_vendor/requests/structures.py | 99 + .../pip/_vendor/requests/utils.py | 1096 ++ .../pip/_vendor/resolvelib/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 665 bytes .../__pycache__/providers.cpython-312.pyc | Bin 0 -> 6882 bytes .../__pycache__/reporters.cpython-312.pyc | Bin 0 -> 2685 bytes .../__pycache__/resolvers.cpython-312.pyc | Bin 0 -> 25928 bytes .../__pycache__/structs.cpython-312.pyc | Bin 0 -> 10537 bytes .../pip/_vendor/resolvelib/compat/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 231 bytes .../collections_abc.cpython-312.pyc | Bin 0 -> 451 bytes .../resolvelib/compat/collections_abc.py | 6 + .../pip/_vendor/resolvelib/providers.py | 133 + .../pip/_vendor/resolvelib/py.typed | 0 .../pip/_vendor/resolvelib/reporters.py | 43 + .../pip/_vendor/resolvelib/resolvers.py | 547 + .../pip/_vendor/resolvelib/structs.py | 170 + .../pip/_vendor/rich/__init__.py | 177 + .../pip/_vendor/rich/__main__.py | 273 + .../rich/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7046 bytes .../rich/__pycache__/__main__.cpython-312.pyc | Bin 0 -> 10335 bytes .../__pycache__/_cell_widths.cpython-312.pyc | Bin 0 -> 7903 bytes .../__pycache__/_emoji_codes.cpython-312.pyc | Bin 0 -> 206007 bytes .../_emoji_replace.cpython-312.pyc | Bin 0 -> 1760 bytes .../_export_format.cpython-312.pyc | Bin 0 -> 2380 bytes .../__pycache__/_extension.cpython-312.pyc | Bin 0 -> 568 bytes .../rich/__pycache__/_fileno.cpython-312.pyc | Bin 0 -> 886 bytes .../rich/__pycache__/_inspect.cpython-312.pyc | Bin 0 -> 12108 bytes .../__pycache__/_log_render.cpython-312.pyc | Bin 0 -> 4178 bytes .../rich/__pycache__/_loop.cpython-312.pyc | Bin 0 -> 1916 bytes .../__pycache__/_null_file.cpython-312.pyc | Bin 0 -> 3651 bytes .../__pycache__/_palettes.cpython-312.pyc | Bin 0 -> 5191 bytes .../rich/__pycache__/_pick.cpython-312.pyc | Bin 0 -> 757 bytes .../rich/__pycache__/_ratio.cpython-312.pyc | Bin 0 -> 6608 bytes .../__pycache__/_spinners.cpython-312.pyc | Bin 0 -> 13210 bytes .../rich/__pycache__/_stack.cpython-312.pyc | Bin 0 -> 996 bytes .../rich/__pycache__/_timer.cpython-312.pyc | Bin 0 -> 896 bytes .../_win32_console.cpython-312.pyc | Bin 0 -> 29007 bytes .../rich/__pycache__/_windows.cpython-312.pyc | Bin 0 -> 2521 bytes .../_windows_renderer.cpython-312.pyc | Bin 0 -> 3604 bytes .../rich/__pycache__/_wrap.cpython-312.pyc | Bin 0 -> 3367 bytes .../rich/__pycache__/abc.cpython-312.pyc | Bin 0 -> 1639 bytes .../rich/__pycache__/align.cpython-312.pyc | Bin 0 -> 12353 bytes .../rich/__pycache__/ansi.cpython-312.pyc | Bin 0 -> 9137 bytes .../rich/__pycache__/bar.cpython-312.pyc | Bin 0 -> 4303 bytes .../rich/__pycache__/box.cpython-312.pyc | Bin 0 -> 11889 bytes .../rich/__pycache__/cells.cpython-312.pyc | Bin 0 -> 5850 bytes .../rich/__pycache__/color.cpython-312.pyc | Bin 0 -> 26601 bytes .../__pycache__/color_triplet.cpython-312.pyc | Bin 0 -> 1732 bytes .../rich/__pycache__/columns.cpython-312.pyc | Bin 0 -> 8618 bytes .../rich/__pycache__/console.cpython-312.pyc | Bin 0 -> 113725 bytes .../__pycache__/constrain.cpython-312.pyc | Bin 0 -> 2289 bytes .../__pycache__/containers.cpython-312.pyc | Bin 0 -> 9262 bytes .../rich/__pycache__/control.cpython-312.pyc | Bin 0 -> 10960 bytes .../default_styles.cpython-312.pyc | Bin 0 -> 10404 bytes .../rich/__pycache__/diagnose.cpython-312.pyc | Bin 0 -> 1518 bytes .../rich/__pycache__/emoji.cpython-312.pyc | Bin 0 -> 4240 bytes .../rich/__pycache__/errors.cpython-312.pyc | Bin 0 -> 1876 bytes .../__pycache__/file_proxy.cpython-312.pyc | Bin 0 -> 3608 bytes .../rich/__pycache__/filesize.cpython-312.pyc | Bin 0 -> 3113 bytes .../__pycache__/highlighter.cpython-312.pyc | Bin 0 -> 9930 bytes .../rich/__pycache__/json.cpython-312.pyc | Bin 0 -> 6066 bytes .../rich/__pycache__/jupyter.cpython-312.pyc | Bin 0 -> 5240 bytes .../rich/__pycache__/layout.cpython-312.pyc | Bin 0 -> 20251 bytes .../rich/__pycache__/live.cpython-312.pyc | Bin 0 -> 19174 bytes .../__pycache__/live_render.cpython-312.pyc | Bin 0 -> 4925 bytes .../rich/__pycache__/logging.cpython-312.pyc | Bin 0 -> 13585 bytes .../rich/__pycache__/markup.cpython-312.pyc | Bin 0 -> 9621 bytes .../rich/__pycache__/measure.cpython-312.pyc | Bin 0 -> 6407 bytes .../rich/__pycache__/padding.cpython-312.pyc | Bin 0 -> 7165 bytes .../rich/__pycache__/pager.cpython-312.pyc | Bin 0 -> 1851 bytes .../rich/__pycache__/palette.cpython-312.pyc | Bin 0 -> 5345 bytes .../rich/__pycache__/panel.cpython-312.pyc | Bin 0 -> 12219 bytes .../rich/__pycache__/pretty.cpython-312.pyc | Bin 0 -> 40196 bytes .../rich/__pycache__/progress.cpython-312.pyc | Bin 0 -> 75150 bytes .../__pycache__/progress_bar.cpython-312.pyc | Bin 0 -> 10420 bytes .../rich/__pycache__/prompt.cpython-312.pyc | Bin 0 -> 14818 bytes .../rich/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 1823 bytes .../rich/__pycache__/region.cpython-312.pyc | Bin 0 -> 598 bytes .../rich/__pycache__/repr.cpython-312.pyc | Bin 0 -> 6655 bytes .../rich/__pycache__/rule.cpython-312.pyc | Bin 0 -> 6599 bytes .../rich/__pycache__/scope.cpython-312.pyc | Bin 0 -> 3861 bytes .../rich/__pycache__/screen.cpython-312.pyc | Bin 0 -> 2515 bytes .../rich/__pycache__/segment.cpython-312.pyc | Bin 0 -> 28192 bytes .../rich/__pycache__/spinner.cpython-312.pyc | Bin 0 -> 6095 bytes .../rich/__pycache__/status.cpython-312.pyc | Bin 0 -> 6099 bytes .../rich/__pycache__/style.cpython-312.pyc | Bin 0 -> 33545 bytes .../rich/__pycache__/styled.cpython-312.pyc | Bin 0 -> 2170 bytes .../rich/__pycache__/syntax.cpython-312.pyc | Bin 0 -> 40006 bytes .../rich/__pycache__/table.cpython-312.pyc | Bin 0 -> 43615 bytes .../terminal_theme.cpython-312.pyc | Bin 0 -> 3379 bytes .../rich/__pycache__/text.cpython-312.pyc | Bin 0 -> 60924 bytes .../rich/__pycache__/theme.cpython-312.pyc | Bin 0 -> 6371 bytes .../rich/__pycache__/themes.cpython-312.pyc | Bin 0 -> 345 bytes .../__pycache__/traceback.cpython-312.pyc | Bin 0 -> 31579 bytes .../rich/__pycache__/tree.cpython-312.pyc | Bin 0 -> 11470 bytes .../pip/_vendor/rich/_cell_widths.py | 454 + .../pip/_vendor/rich/_emoji_codes.py | 3610 +++++++ .../pip/_vendor/rich/_emoji_replace.py | 32 + .../pip/_vendor/rich/_export_format.py | 76 + .../pip/_vendor/rich/_extension.py | 10 + .../site-packages/pip/_vendor/rich/_fileno.py | 24 + .../pip/_vendor/rich/_inspect.py | 270 + .../pip/_vendor/rich/_log_render.py | 94 + .../site-packages/pip/_vendor/rich/_loop.py | 43 + .../pip/_vendor/rich/_null_file.py | 69 + .../pip/_vendor/rich/_palettes.py | 309 + .../site-packages/pip/_vendor/rich/_pick.py | 17 + .../site-packages/pip/_vendor/rich/_ratio.py | 159 + .../pip/_vendor/rich/_spinners.py | 482 + .../site-packages/pip/_vendor/rich/_stack.py | 16 + .../site-packages/pip/_vendor/rich/_timer.py | 19 + .../pip/_vendor/rich/_win32_console.py | 662 ++ .../pip/_vendor/rich/_windows.py | 71 + .../pip/_vendor/rich/_windows_renderer.py | 56 + .../site-packages/pip/_vendor/rich/_wrap.py | 93 + .../site-packages/pip/_vendor/rich/abc.py | 33 + .../site-packages/pip/_vendor/rich/align.py | 311 + .../site-packages/pip/_vendor/rich/ansi.py | 240 + .../site-packages/pip/_vendor/rich/bar.py | 93 + .../site-packages/pip/_vendor/rich/box.py | 480 + .../site-packages/pip/_vendor/rich/cells.py | 167 + .../site-packages/pip/_vendor/rich/color.py | 621 ++ .../pip/_vendor/rich/color_triplet.py | 38 + .../site-packages/pip/_vendor/rich/columns.py | 187 + .../site-packages/pip/_vendor/rich/console.py | 2633 +++++ .../pip/_vendor/rich/constrain.py | 37 + .../pip/_vendor/rich/containers.py | 167 + .../site-packages/pip/_vendor/rich/control.py | 225 + .../pip/_vendor/rich/default_styles.py | 190 + .../pip/_vendor/rich/diagnose.py | 37 + .../site-packages/pip/_vendor/rich/emoji.py | 96 + .../site-packages/pip/_vendor/rich/errors.py | 34 + .../pip/_vendor/rich/file_proxy.py | 57 + .../pip/_vendor/rich/filesize.py | 89 + .../pip/_vendor/rich/highlighter.py | 232 + .../site-packages/pip/_vendor/rich/json.py | 139 + .../site-packages/pip/_vendor/rich/jupyter.py | 101 + .../site-packages/pip/_vendor/rich/layout.py | 442 + .../site-packages/pip/_vendor/rich/live.py | 375 + .../pip/_vendor/rich/live_render.py | 112 + .../site-packages/pip/_vendor/rich/logging.py | 289 + .../site-packages/pip/_vendor/rich/markup.py | 251 + .../site-packages/pip/_vendor/rich/measure.py | 151 + .../site-packages/pip/_vendor/rich/padding.py | 141 + .../site-packages/pip/_vendor/rich/pager.py | 34 + .../site-packages/pip/_vendor/rich/palette.py | 100 + .../site-packages/pip/_vendor/rich/panel.py | 312 + .../site-packages/pip/_vendor/rich/pretty.py | 995 ++ .../pip/_vendor/rich/progress.py | 1699 ++++ .../pip/_vendor/rich/progress_bar.py | 223 + .../site-packages/pip/_vendor/rich/prompt.py | 375 + .../pip/_vendor/rich/protocol.py | 42 + .../site-packages/pip/_vendor/rich/py.typed | 0 .../site-packages/pip/_vendor/rich/region.py | 10 + .../site-packages/pip/_vendor/rich/repr.py | 149 + .../site-packages/pip/_vendor/rich/rule.py | 130 + .../site-packages/pip/_vendor/rich/scope.py | 86 + .../site-packages/pip/_vendor/rich/screen.py | 54 + .../site-packages/pip/_vendor/rich/segment.py | 738 ++ .../site-packages/pip/_vendor/rich/spinner.py | 137 + .../site-packages/pip/_vendor/rich/status.py | 131 + .../site-packages/pip/_vendor/rich/style.py | 796 ++ .../site-packages/pip/_vendor/rich/styled.py | 42 + .../site-packages/pip/_vendor/rich/syntax.py | 958 ++ .../site-packages/pip/_vendor/rich/table.py | 1000 ++ .../pip/_vendor/rich/terminal_theme.py | 153 + .../site-packages/pip/_vendor/rich/text.py | 1357 +++ .../site-packages/pip/_vendor/rich/theme.py | 115 + .../site-packages/pip/_vendor/rich/themes.py | 5 + .../pip/_vendor/rich/traceback.py | 753 ++ .../site-packages/pip/_vendor/rich/tree.py | 249 + .../pip/_vendor/tomli/__init__.py | 11 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 415 bytes .../tomli/__pycache__/_parser.cpython-312.pyc | Bin 0 -> 26958 bytes .../tomli/__pycache__/_re.cpython-312.pyc | Bin 0 -> 3939 bytes .../tomli/__pycache__/_types.cpython-312.pyc | Bin 0 -> 397 bytes .../pip/_vendor/tomli/_parser.py | 691 ++ .../site-packages/pip/_vendor/tomli/_re.py | 107 + .../site-packages/pip/_vendor/tomli/_types.py | 10 + .../site-packages/pip/_vendor/tomli/py.typed | 1 + .../pip/_vendor/truststore/__init__.py | 36 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1375 bytes .../__pycache__/_api.cpython-312.pyc | Bin 0 -> 16805 bytes .../__pycache__/_macos.cpython-312.pyc | Bin 0 -> 19023 bytes .../__pycache__/_openssl.cpython-312.pyc | Bin 0 -> 2246 bytes .../_ssl_constants.cpython-312.pyc | Bin 0 -> 1130 bytes .../__pycache__/_windows.cpython-312.pyc | Bin 0 -> 15806 bytes .../pip/_vendor/truststore/_api.py | 316 + .../pip/_vendor/truststore/_macos.py | 571 ++ .../pip/_vendor/truststore/_openssl.py | 66 + .../pip/_vendor/truststore/_ssl_constants.py | 31 + .../pip/_vendor/truststore/_windows.py | 567 ++ .../pip/_vendor/truststore/py.typed | 0 .../pip/_vendor/typing_extensions.py | 3641 +++++++ .../pip/_vendor/urllib3/__init__.py | 102 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3436 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 16519 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 249 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 20438 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 36574 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 13524 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 10444 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 4049 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 20503 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 7325 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 33999 bytes .../pip/_vendor/urllib3/_collections.py | 355 + .../pip/_vendor/urllib3/_version.py | 2 + .../pip/_vendor/urllib3/connection.py | 572 ++ .../pip/_vendor/urllib3/connectionpool.py | 1140 +++ .../pip/_vendor/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 229 bytes .../_appengine_environ.cpython-312.pyc | Bin 0 -> 1879 bytes .../__pycache__/appengine.cpython-312.pyc | Bin 0 -> 11595 bytes .../__pycache__/ntlmpool.cpython-312.pyc | Bin 0 -> 5750 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 24481 bytes .../securetransport.cpython-312.pyc | Bin 0 -> 35584 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 7542 bytes .../urllib3/contrib/_appengine_environ.py | 36 + .../contrib/_securetransport/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 246 bytes .../__pycache__/bindings.cpython-312.pyc | Bin 0 -> 17458 bytes .../__pycache__/low_level.cpython-312.pyc | Bin 0 -> 14832 bytes .../contrib/_securetransport/bindings.py | 519 + .../contrib/_securetransport/low_level.py | 397 + .../pip/_vendor/urllib3/contrib/appengine.py | 314 + .../pip/_vendor/urllib3/contrib/ntlmpool.py | 130 + .../pip/_vendor/urllib3/contrib/pyopenssl.py | 518 + .../urllib3/contrib/securetransport.py | 920 ++ .../pip/_vendor/urllib3/contrib/socks.py | 216 + .../pip/_vendor/urllib3/exceptions.py | 323 + .../pip/_vendor/urllib3/fields.py | 274 + .../pip/_vendor/urllib3/filepost.py | 98 + .../pip/_vendor/urllib3/packages/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 230 bytes .../packages/__pycache__/six.cpython-312.pyc | Bin 0 -> 41350 bytes .../urllib3/packages/backports/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 240 bytes .../__pycache__/makefile.cpython-312.pyc | Bin 0 -> 1856 bytes .../weakref_finalize.cpython-312.pyc | Bin 0 -> 7362 bytes .../urllib3/packages/backports/makefile.py | 51 + .../packages/backports/weakref_finalize.py | 155 + .../pip/_vendor/urllib3/packages/six.py | 1076 ++ .../pip/_vendor/urllib3/poolmanager.py | 540 + .../pip/_vendor/urllib3/request.py | 191 + .../pip/_vendor/urllib3/response.py | 879 ++ .../pip/_vendor/urllib3/util/__init__.py | 49 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1177 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4787 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1583 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 1383 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 4214 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 3020 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 21749 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 15408 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5102 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 10803 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11170 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 15826 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 4434 bytes .../pip/_vendor/urllib3/util/connection.py | 149 + .../pip/_vendor/urllib3/util/proxy.py | 57 + .../pip/_vendor/urllib3/util/queue.py | 22 + .../pip/_vendor/urllib3/util/request.py | 137 + .../pip/_vendor/urllib3/util/response.py | 107 + .../pip/_vendor/urllib3/util/retry.py | 622 ++ .../pip/_vendor/urllib3/util/ssl_.py | 504 + .../urllib3/util/ssl_match_hostname.py | 159 + .../pip/_vendor/urllib3/util/ssltransport.py | 221 + .../pip/_vendor/urllib3/util/timeout.py | 271 + .../pip/_vendor/urllib3/util/url.py | 435 + .../pip/_vendor/urllib3/util/wait.py | 152 + .../site-packages/pip/_vendor/vendor.txt | 18 + .../lib/python3.12/site-packages/pip/py.typed | 4 + .../site-packages/psycopg2/__init__.py | 126 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3778 bytes .../__pycache__/_ipaddress.cpython-312.pyc | Bin 0 -> 2654 bytes .../__pycache__/_json.cpython-312.pyc | Bin 0 -> 7503 bytes .../__pycache__/_range.cpython-312.pyc | Bin 0 -> 21074 bytes .../__pycache__/errorcodes.cpython-312.pyc | Bin 0 -> 14598 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 562 bytes .../__pycache__/extensions.cpython-312.pyc | Bin 0 -> 7488 bytes .../__pycache__/extras.cpython-312.pyc | Bin 0 -> 60572 bytes .../psycopg2/__pycache__/pool.cpython-312.pyc | Bin 0 -> 7858 bytes .../psycopg2/__pycache__/sql.cpython-312.pyc | Bin 0 -> 18962 bytes .../psycopg2/__pycache__/tz.cpython-312.pyc | Bin 0 -> 6224 bytes .../site-packages/psycopg2/_ipaddress.py | 90 + .../site-packages/psycopg2/_json.py | 199 + .../_psycopg.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 339185 bytes .../site-packages/psycopg2/_range.py | 554 ++ .../site-packages/psycopg2/errorcodes.py | 455 + .../site-packages/psycopg2/errors.py | 38 + .../site-packages/psycopg2/extensions.py | 213 + .../site-packages/psycopg2/extras.py | 1340 +++ .../python3.12/site-packages/psycopg2/pool.py | 187 + .../python3.12/site-packages/psycopg2/sql.py | 455 + .../python3.12/site-packages/psycopg2/tz.py | 158 + .../INSTALLER | 1 + .../psycopg2_binary-2.9.11.dist-info/METADATA | 131 + .../psycopg2_binary-2.9.11.dist-info/RECORD | 44 + .../REQUESTED | 0 .../psycopg2_binary-2.9.11.dist-info/WHEEL | 6 + .../licenses/LICENSE | 49 + .../top_level.txt | 1 + .../libcom_err-2abe824b.so.2.1 | Bin 0 -> 17497 bytes .../libcrypto-81d66ed9.so.3 | Bin 0 -> 6489873 bytes .../libgssapi_krb5-497db0c6.so.2.2 | Bin 0 -> 345209 bytes .../libk5crypto-b1f99d5c.so.3.1 | Bin 0 -> 219953 bytes .../libkeyutils-dfe70bd6.so.1.5 | Bin 0 -> 17913 bytes .../libkrb5-fcafa220.so.3.3 | Bin 0 -> 1018953 bytes .../libkrb5support-d0bcff84.so.0.1 | Bin 0 -> 76873 bytes .../liblber-568fe118.so.2.0.200 | Bin 0 -> 60977 bytes .../libldap-1accf1ee.so.2.0.200 | Bin 0 -> 451425 bytes .../libpcre-9513aab5.so.1.2.0 | Bin 0 -> 406817 bytes .../libpq-9b38f5e3.so.5.17 | Bin 0 -> 387497 bytes .../libsasl2-883649fd.so.3.0.0 | Bin 0 -> 119217 bytes .../libselinux-0922c95c.so.1 | Bin 0 -> 178337 bytes .../psycopg2_binary.libs/libssl-81ffa89e.so.3 | Bin 0 -> 1139041 bytes .../pydantic-2.12.5.dist-info/INSTALLER | 1 + .../pydantic-2.12.5.dist-info/METADATA | 1029 ++ .../pydantic-2.12.5.dist-info/RECORD | 218 + .../pydantic-2.12.5.dist-info/REQUESTED | 0 .../pydantic-2.12.5.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../site-packages/pydantic/__init__.py | 456 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 14455 bytes .../__pycache__/_migration.cpython-312.pyc | Bin 0 -> 11048 bytes .../alias_generators.cpython-312.pyc | Bin 0 -> 3315 bytes .../__pycache__/aliases.cpython-312.pyc | Bin 0 -> 6597 bytes .../annotated_handlers.cpython-312.pyc | Bin 0 -> 5521 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 395 bytes .../__pycache__/color.cpython-312.pyc | Bin 0 -> 30178 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 7527 bytes .../__pycache__/dataclasses.cpython-312.pyc | Bin 0 -> 17368 bytes .../datetime_parse.cpython-312.pyc | Bin 0 -> 395 bytes .../__pycache__/decorator.cpython-312.pyc | Bin 0 -> 385 bytes .../__pycache__/env_settings.cpython-312.pyc | Bin 0 -> 391 bytes .../error_wrappers.cpython-312.pyc | Bin 0 -> 395 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 7656 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 72969 bytes .../functional_serializers.cpython-312.pyc | Bin 0 -> 17958 bytes .../functional_validators.cpython-312.pyc | Bin 0 -> 34767 bytes .../__pycache__/generics.cpython-312.pyc | Bin 0 -> 383 bytes .../pydantic/__pycache__/json.cpython-312.pyc | Bin 0 -> 375 bytes .../__pycache__/json_schema.cpython-312.pyc | Bin 0 -> 120184 bytes .../pydantic/__pycache__/main.cpython-312.pyc | Bin 0 -> 77363 bytes .../pydantic/__pycache__/mypy.cpython-312.pyc | Bin 0 -> 61824 bytes .../__pycache__/networks.cpython-312.pyc | Bin 0 -> 50246 bytes .../__pycache__/parse.cpython-312.pyc | Bin 0 -> 377 bytes .../__pycache__/root_model.cpython-312.pyc | Bin 0 -> 7838 bytes .../__pycache__/schema.cpython-312.pyc | Bin 0 -> 379 bytes .../__pycache__/tools.cpython-312.pyc | Bin 0 -> 377 bytes .../__pycache__/type_adapter.cpython-312.pyc | Bin 0 -> 35579 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 96814 bytes .../__pycache__/typing.cpython-312.pyc | Bin 0 -> 375 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 377 bytes .../validate_call_decorator.cpython-312.pyc | Bin 0 -> 5473 bytes .../__pycache__/validators.cpython-312.pyc | Bin 0 -> 387 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 4825 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 7239 bytes .../pydantic/_internal/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 220 bytes .../__pycache__/_config.cpython-312.pyc | Bin 0 -> 15687 bytes .../_core_metadata.cpython-312.pyc | Bin 0 -> 4640 bytes .../__pycache__/_core_utils.cpython-312.pyc | Bin 0 -> 7750 bytes .../__pycache__/_dataclasses.cpython-312.pyc | Bin 0 -> 13452 bytes .../__pycache__/_decorators.cpython-312.pyc | Bin 0 -> 36794 bytes .../_decorators_v1.cpython-312.pyc | Bin 0 -> 8621 bytes .../_discriminated_union.cpython-312.pyc | Bin 0 -> 20886 bytes .../_docs_extraction.cpython-312.pyc | Bin 0 -> 5333 bytes .../__pycache__/_fields.cpython-312.pyc | Bin 0 -> 24392 bytes .../__pycache__/_forward_ref.cpython-312.pyc | Bin 0 -> 1329 bytes .../_generate_schema.cpython-312.pyc | Bin 0 -> 130722 bytes .../__pycache__/_generics.cpython-312.pyc | Bin 0 -> 24380 bytes .../__pycache__/_git.cpython-312.pyc | Bin 0 -> 1535 bytes .../__pycache__/_import_utils.cpython-312.pyc | Bin 0 -> 848 bytes .../_internal_dataclass.cpython-312.pyc | Bin 0 -> 369 bytes .../_known_annotated_metadata.cpython-312.pyc | Bin 0 -> 14272 bytes .../__pycache__/_mock_val_ser.cpython-312.pyc | Bin 0 -> 11130 bytes .../_model_construction.cpython-312.pyc | Bin 0 -> 35144 bytes .../_namespace_utils.cpython-312.pyc | Bin 0 -> 12331 bytes .../__pycache__/_repr.cpython-312.pyc | Bin 0 -> 7878 bytes .../_schema_gather.cpython-312.pyc | Bin 0 -> 7720 bytes .../_schema_generation_shared.cpython-312.pyc | Bin 0 -> 6246 bytes .../__pycache__/_serializers.cpython-312.pyc | Bin 0 -> 2141 bytes .../__pycache__/_signature.cpython-312.pyc | Bin 0 -> 6789 bytes .../__pycache__/_typing_extra.cpython-312.pyc | Bin 0 -> 28272 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 19818 bytes .../_validate_call.cpython-312.pyc | Bin 0 -> 7007 bytes .../__pycache__/_validators.cpython-312.pyc | Bin 0 -> 22977 bytes .../pydantic/_internal/_config.py | 383 + .../pydantic/_internal/_core_metadata.py | 97 + .../pydantic/_internal/_core_utils.py | 174 + .../pydantic/_internal/_dataclasses.py | 315 + .../pydantic/_internal/_decorators.py | 858 ++ .../pydantic/_internal/_decorators_v1.py | 174 + .../_internal/_discriminated_union.py | 479 + .../pydantic/_internal/_docs_extraction.py | 113 + .../pydantic/_internal/_fields.py | 635 ++ .../pydantic/_internal/_forward_ref.py | 23 + .../pydantic/_internal/_generate_schema.py | 2867 ++++++ .../pydantic/_internal/_generics.py | 543 + .../site-packages/pydantic/_internal/_git.py | 27 + .../pydantic/_internal/_import_utils.py | 20 + .../pydantic/_internal/_internal_dataclass.py | 7 + .../_internal/_known_annotated_metadata.py | 401 + .../pydantic/_internal/_mock_val_ser.py | 228 + .../pydantic/_internal/_model_construction.py | 848 ++ .../pydantic/_internal/_namespace_utils.py | 293 + .../site-packages/pydantic/_internal/_repr.py | 124 + .../pydantic/_internal/_schema_gather.py | 209 + .../_internal/_schema_generation_shared.py | 125 + .../pydantic/_internal/_serializers.py | 53 + .../pydantic/_internal/_signature.py | 188 + .../pydantic/_internal/_typing_extra.py | 760 ++ .../pydantic/_internal/_utils.py | 446 + .../pydantic/_internal/_validate_call.py | 140 + .../pydantic/_internal/_validators.py | 533 + .../site-packages/pydantic/_migration.py | 316 + .../pydantic/alias_generators.py | 62 + .../site-packages/pydantic/aliases.py | 135 + .../pydantic/annotated_handlers.py | 122 + .../pydantic/class_validators.py | 5 + .../site-packages/pydantic/color.py | 604 ++ .../site-packages/pydantic/config.py | 1288 +++ .../site-packages/pydantic/dataclasses.py | 413 + .../site-packages/pydantic/datetime_parse.py | 5 + .../site-packages/pydantic/decorator.py | 5 + .../pydantic/deprecated/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 11757 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 4098 bytes .../copy_internals.cpython-312.pyc | Bin 0 -> 8671 bytes .../__pycache__/decorator.cpython-312.pyc | Bin 0 -> 14059 bytes .../__pycache__/json.cpython-312.pyc | Bin 0 -> 6217 bytes .../__pycache__/parse.cpython-312.pyc | Bin 0 -> 3427 bytes .../__pycache__/tools.cpython-312.pyc | Bin 0 -> 3563 bytes .../pydantic/deprecated/class_validators.py | 256 + .../pydantic/deprecated/config.py | 72 + .../pydantic/deprecated/copy_internals.py | 224 + .../pydantic/deprecated/decorator.py | 284 + .../site-packages/pydantic/deprecated/json.py | 141 + .../pydantic/deprecated/parse.py | 80 + .../pydantic/deprecated/tools.py | 103 + .../site-packages/pydantic/env_settings.py | 5 + .../site-packages/pydantic/error_wrappers.py | 5 + .../site-packages/pydantic/errors.py | 189 + .../pydantic/experimental/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 335 bytes .../arguments_schema.cpython-312.pyc | Bin 0 -> 2427 bytes .../missing_sentinel.cpython-312.pyc | Bin 0 -> 376 bytes .../__pycache__/pipeline.cpython-312.pyc | Bin 0 -> 34488 bytes .../pydantic/experimental/arguments_schema.py | 44 + .../pydantic/experimental/missing_sentinel.py | 5 + .../pydantic/experimental/pipeline.py | 654 ++ .../site-packages/pydantic/fields.py | 1834 ++++ .../pydantic/functional_serializers.py | 451 + .../pydantic/functional_validators.py | 893 ++ .../site-packages/pydantic/generics.py | 5 + .../python3.12/site-packages/pydantic/json.py | 5 + .../site-packages/pydantic/json_schema.py | 2854 ++++++ .../python3.12/site-packages/pydantic/main.py | 1819 ++++ .../python3.12/site-packages/pydantic/mypy.py | 1374 +++ .../site-packages/pydantic/networks.py | 1331 +++ .../site-packages/pydantic/parse.py | 5 + .../site-packages/pydantic/plugin/__init__.py | 193 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 8963 bytes .../__pycache__/_loader.cpython-312.pyc | Bin 0 -> 2463 bytes .../_schema_validator.cpython-312.pyc | Bin 0 -> 6963 bytes .../site-packages/pydantic/plugin/_loader.py | 58 + .../pydantic/plugin/_schema_validator.py | 140 + .../site-packages/pydantic/py.typed | 0 .../site-packages/pydantic/root_model.py | 155 + .../site-packages/pydantic/schema.py | 5 + .../site-packages/pydantic/tools.py | 5 + .../site-packages/pydantic/type_adapter.py | 795 ++ .../site-packages/pydantic/types.py | 3295 ++++++ .../site-packages/pydantic/typing.py | 5 + .../site-packages/pydantic/utils.py | 5 + .../site-packages/pydantic/v1/__init__.py | 142 + .../v1/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2542 bytes .../_hypothesis_plugin.cpython-312.pyc | Bin 0 -> 20566 bytes .../annotated_types.cpython-312.pyc | Bin 0 -> 3897 bytes .../class_validators.cpython-312.pyc | Bin 0 -> 19696 bytes .../v1/__pycache__/color.cpython-312.pyc | Bin 0 -> 25851 bytes .../v1/__pycache__/config.cpython-312.pyc | Bin 0 -> 8424 bytes .../__pycache__/dataclasses.cpython-312.pyc | Bin 0 -> 22794 bytes .../datetime_parse.cpython-312.pyc | Bin 0 -> 10370 bytes .../v1/__pycache__/decorator.cpython-312.pyc | Bin 0 -> 13953 bytes .../__pycache__/env_settings.cpython-312.pyc | Bin 0 -> 17760 bytes .../error_wrappers.cpython-312.pyc | Bin 0 -> 8953 bytes .../v1/__pycache__/errors.cpython-312.pyc | Bin 0 -> 29622 bytes .../v1/__pycache__/fields.cpython-312.pyc | Bin 0 -> 57451 bytes .../v1/__pycache__/generics.cpython-312.pyc | Bin 0 -> 17017 bytes .../v1/__pycache__/json.cpython-312.pyc | Bin 0 -> 5254 bytes .../v1/__pycache__/main.cpython-312.pyc | Bin 0 -> 48294 bytes .../v1/__pycache__/mypy.cpython-312.pyc | Bin 0 -> 46457 bytes .../v1/__pycache__/networks.cpython-312.pyc | Bin 0 -> 29583 bytes .../v1/__pycache__/parse.cpython-312.pyc | Bin 0 -> 2771 bytes .../v1/__pycache__/schema.cpython-312.pyc | Bin 0 -> 48523 bytes .../v1/__pycache__/tools.cpython-312.pyc | Bin 0 -> 3904 bytes .../v1/__pycache__/types.cpython-312.pyc | Bin 0 -> 48526 bytes .../v1/__pycache__/typing.cpython-312.pyc | Bin 0 -> 22682 bytes .../v1/__pycache__/utils.cpython-312.pyc | Bin 0 -> 35308 bytes .../v1/__pycache__/validators.cpython-312.pyc | Bin 0 -> 30922 bytes .../v1/__pycache__/version.cpython-312.pyc | Bin 0 -> 1983 bytes .../pydantic/v1/_hypothesis_plugin.py | 391 + .../pydantic/v1/annotated_types.py | 72 + .../pydantic/v1/class_validators.py | 361 + .../site-packages/pydantic/v1/color.py | 494 + .../site-packages/pydantic/v1/config.py | 191 + .../site-packages/pydantic/v1/dataclasses.py | 500 + .../pydantic/v1/datetime_parse.py | 248 + .../site-packages/pydantic/v1/decorator.py | 264 + .../site-packages/pydantic/v1/env_settings.py | 350 + .../pydantic/v1/error_wrappers.py | 161 + .../site-packages/pydantic/v1/errors.py | 646 ++ .../site-packages/pydantic/v1/fields.py | 1253 +++ .../site-packages/pydantic/v1/generics.py | 400 + .../site-packages/pydantic/v1/json.py | 112 + .../site-packages/pydantic/v1/main.py | 1113 +++ .../site-packages/pydantic/v1/mypy.py | 949 ++ .../site-packages/pydantic/v1/networks.py | 747 ++ .../site-packages/pydantic/v1/parse.py | 66 + .../site-packages/pydantic/v1/py.typed | 0 .../site-packages/pydantic/v1/schema.py | 1163 +++ .../site-packages/pydantic/v1/tools.py | 92 + .../site-packages/pydantic/v1/types.py | 1205 +++ .../site-packages/pydantic/v1/typing.py | 614 ++ .../site-packages/pydantic/v1/utils.py | 806 ++ .../site-packages/pydantic/v1/validators.py | 768 ++ .../site-packages/pydantic/v1/version.py | 38 + .../pydantic/validate_call_decorator.py | 116 + .../site-packages/pydantic/validators.py | 5 + .../site-packages/pydantic/version.py | 113 + .../site-packages/pydantic/warnings.py | 122 + .../pydantic_core-2.41.5.dist-info/INSTALLER | 1 + .../pydantic_core-2.41.5.dist-info/METADATA | 180 + .../pydantic_core-2.41.5.dist-info/RECORD | 12 + .../pydantic_core-2.41.5.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../site-packages/pydantic_core/__init__.py | 171 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3176 bytes .../__pycache__/core_schema.cpython-312.pyc | Bin 0 -> 155163 bytes ...antic_core.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 4883472 bytes .../pydantic_core/_pydantic_core.pyi | 1046 ++ .../pydantic_core/core_schema.py | 4435 +++++++++ .../site-packages/pydantic_core/py.typed | 0 .../INSTALLER | 1 + .../METADATA | 63 + .../pydantic_settings-2.13.1.dist-info/RECORD | 51 + .../REQUESTED | 0 .../pydantic_settings-2.13.1.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../pydantic_settings/__init__.py | 69 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1486 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 473 bytes .../__pycache__/main.cpython-312.pyc | Bin 0 -> 37774 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 1893 bytes .../__pycache__/version.cpython-312.pyc | Bin 0 -> 241 bytes .../pydantic_settings/exceptions.py | 4 + .../site-packages/pydantic_settings/main.py | 901 ++ .../site-packages/pydantic_settings/py.typed | 0 .../pydantic_settings/sources/__init__.py | 84 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2053 bytes .../sources/__pycache__/base.cpython-312.pyc | Bin 0 -> 25728 bytes .../sources/__pycache__/types.cpython-312.pyc | Bin 0 -> 3356 bytes .../sources/__pycache__/utils.cpython-312.pyc | Bin 0 -> 12423 bytes .../pydantic_settings/sources/base.py | 579 ++ .../sources/providers/__init__.py | 45 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1260 bytes .../providers/__pycache__/aws.cpython-312.pyc | Bin 0 -> 3530 bytes .../__pycache__/azure.cpython-312.pyc | Bin 0 -> 8193 bytes .../providers/__pycache__/cli.cpython-312.pyc | Bin 0 -> 78166 bytes .../__pycache__/dotenv.cpython-312.pyc | Bin 0 -> 7070 bytes .../providers/__pycache__/env.cpython-312.pyc | Bin 0 -> 12980 bytes .../providers/__pycache__/gcp.cpython-312.pyc | Bin 0 -> 11635 bytes .../__pycache__/json.cpython-312.pyc | Bin 0 -> 2586 bytes .../nested_secrets.cpython-312.pyc | Bin 0 -> 7967 bytes .../__pycache__/pyproject.cpython-312.pyc | Bin 0 -> 3073 bytes .../__pycache__/secrets.cpython-312.pyc | Bin 0 -> 5925 bytes .../__pycache__/toml.cpython-312.pyc | Bin 0 -> 3151 bytes .../__pycache__/yaml.cpython-312.pyc | Bin 0 -> 5470 bytes .../sources/providers/aws.py | 86 + .../sources/providers/azure.py | 159 + .../sources/providers/cli.py | 1522 +++ .../sources/providers/dotenv.py | 170 + .../sources/providers/env.py | 310 + .../sources/providers/gcp.py | 241 + .../sources/providers/json.py | 48 + .../sources/providers/nested_secrets.py | 166 + .../sources/providers/pyproject.py | 62 + .../sources/providers/secrets.py | 132 + .../sources/providers/toml.py | 67 + .../sources/providers/yaml.py | 130 + .../pydantic_settings/sources/types.py | 99 + .../pydantic_settings/sources/utils.py | 283 + .../site-packages/pydantic_settings/utils.py | 43 + .../pydantic_settings/version.py | 1 + .../python_dotenv-1.2.1.dist-info/INSTALLER | 1 + .../python_dotenv-1.2.1.dist-info/METADATA | 749 ++ .../python_dotenv-1.2.1.dist-info/RECORD | 26 + .../python_dotenv-1.2.1.dist-info/REQUESTED | 0 .../python_dotenv-1.2.1.dist-info/WHEEL | 5 + .../entry_points.txt | 2 + .../licenses/LICENSE | 27 + .../top_level.txt | 1 + .../requests-2.32.5.dist-info/INSTALLER | 1 + .../requests-2.32.5.dist-info/METADATA | 133 + .../requests-2.32.5.dist-info/RECORD | 43 + .../requests-2.32.5.dist-info/REQUESTED | 0 .../requests-2.32.5.dist-info/WHEEL | 5 + .../licenses/LICENSE | 175 + .../requests-2.32.5.dist-info/top_level.txt | 1 + .../site-packages/requests/__init__.py | 184 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5439 bytes .../__pycache__/__version__.cpython-312.pyc | Bin 0 -> 603 bytes .../_internal_utils.cpython-312.pyc | Bin 0 -> 2043 bytes .../__pycache__/adapters.cpython-312.pyc | Bin 0 -> 27830 bytes .../requests/__pycache__/api.cpython-312.pyc | Bin 0 -> 7223 bytes .../requests/__pycache__/auth.cpython-312.pyc | Bin 0 -> 13942 bytes .../__pycache__/certs.cpython-312.pyc | Bin 0 -> 685 bytes .../__pycache__/compat.cpython-312.pyc | Bin 0 -> 2399 bytes .../__pycache__/cookies.cpython-312.pyc | Bin 0 -> 25295 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 7604 bytes .../requests/__pycache__/help.cpython-312.pyc | Bin 0 -> 4346 bytes .../__pycache__/hooks.cpython-312.pyc | Bin 0 -> 1071 bytes .../__pycache__/models.cpython-312.pyc | Bin 0 -> 35520 bytes .../__pycache__/packages.cpython-312.pyc | Bin 0 -> 1158 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 27909 bytes .../__pycache__/status_codes.cpython-312.pyc | Bin 0 -> 6050 bytes .../__pycache__/structures.cpython-312.pyc | Bin 0 -> 5636 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 36197 bytes .../site-packages/requests/__version__.py | 14 + .../site-packages/requests/_internal_utils.py | 50 + .../site-packages/requests/adapters.py | 696 ++ .../python3.12/site-packages/requests/api.py | 157 + .../python3.12/site-packages/requests/auth.py | 314 + .../site-packages/requests/certs.py | 17 + .../site-packages/requests/compat.py | 106 + .../site-packages/requests/cookies.py | 561 ++ .../site-packages/requests/exceptions.py | 151 + .../python3.12/site-packages/requests/help.py | 134 + .../site-packages/requests/hooks.py | 33 + .../site-packages/requests/models.py | 1039 ++ .../site-packages/requests/packages.py | 23 + .../site-packages/requests/sessions.py | 831 ++ .../site-packages/requests/status_codes.py | 128 + .../site-packages/requests/structures.py | 99 + .../site-packages/requests/utils.py | 1086 ++ .../sqlalchemy-2.0.46.dist-info/INSTALLER | 1 + .../sqlalchemy-2.0.46.dist-info/METADATA | 243 + .../sqlalchemy-2.0.46.dist-info/RECORD | 532 + .../sqlalchemy-2.0.46.dist-info/REQUESTED | 0 .../sqlalchemy-2.0.46.dist-info/WHEEL | 7 + .../licenses/LICENSE | 19 + .../sqlalchemy-2.0.46.dist-info/top_level.txt | 1 + .../site-packages/sqlalchemy/__init__.py | 283 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 9448 bytes .../__pycache__/events.cpython-312.pyc | Bin 0 -> 585 bytes .../__pycache__/exc.cpython-312.pyc | Bin 0 -> 31256 bytes .../__pycache__/inspection.cpython-312.pyc | Bin 0 -> 6688 bytes .../__pycache__/log.cpython-312.pyc | Bin 0 -> 11646 bytes .../__pycache__/schema.cpython-312.pyc | Bin 0 -> 2400 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 2310 bytes .../sqlalchemy/connectors/__init__.py | 18 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 624 bytes .../__pycache__/aioodbc.cpython-312.pyc | Bin 0 -> 7871 bytes .../__pycache__/asyncio.cpython-312.pyc | Bin 0 -> 21687 bytes .../__pycache__/pyodbc.cpython-312.pyc | Bin 0 -> 9656 bytes .../sqlalchemy/connectors/aioodbc.py | 184 + .../sqlalchemy/connectors/asyncio.py | 429 + .../sqlalchemy/connectors/pyodbc.py | 250 + .../sqlalchemy/cyextension/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 224 bytes ...ollections.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 2017984 bytes .../sqlalchemy/cyextension/collections.pyx | 409 + ...utabledict.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 819064 bytes .../sqlalchemy/cyextension/immutabledict.pxd | 8 + .../sqlalchemy/cyextension/immutabledict.pyx | 133 + ...processors.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 630736 bytes .../sqlalchemy/cyextension/processors.pyx | 68 + ...esultproxy.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 611072 bytes .../sqlalchemy/cyextension/resultproxy.pyx | 102 + .../util.cpython-312-x86_64-linux-gnu.so | Bin 0 -> 958336 bytes .../sqlalchemy/cyextension/util.pyx | 90 + .../sqlalchemy/dialects/__init__.py | 62 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1940 bytes .../__pycache__/_typing.cpython-312.pyc | Bin 0 -> 1114 bytes .../sqlalchemy/dialects/_typing.py | 30 + .../sqlalchemy/dialects/mssql/__init__.py | 88 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1692 bytes .../mssql/__pycache__/aioodbc.cpython-312.pyc | Bin 0 -> 2433 bytes .../mssql/__pycache__/base.cpython-312.pyc | Bin 0 -> 153670 bytes .../information_schema.cpython-312.pyc | Bin 0 -> 8968 bytes .../mssql/__pycache__/json.cpython-312.pyc | Bin 0 -> 5249 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 8285 bytes .../mssql/__pycache__/pymssql.cpython-312.pyc | Bin 0 -> 5981 bytes .../mssql/__pycache__/pyodbc.cpython-312.pyc | Bin 0 -> 30645 bytes .../sqlalchemy/dialects/mssql/aioodbc.py | 63 + .../sqlalchemy/dialects/mssql/base.py | 4093 ++++++++ .../dialects/mssql/information_schema.py | 285 + .../sqlalchemy/dialects/mssql/json.py | 129 + .../sqlalchemy/dialects/mssql/provision.py | 185 + .../sqlalchemy/dialects/mssql/pymssql.py | 126 + .../sqlalchemy/dialects/mssql/pyodbc.py | 760 ++ .../sqlalchemy/dialects/mysql/__init__.py | 104 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2021 bytes .../__pycache__/aiomysql.cpython-312.pyc | Bin 0 -> 11774 bytes .../mysql/__pycache__/asyncmy.cpython-312.pyc | Bin 0 -> 11251 bytes .../mysql/__pycache__/base.cpython-312.pyc | Bin 0 -> 155096 bytes .../mysql/__pycache__/cymysql.cpython-312.pyc | Bin 0 -> 4128 bytes .../mysql/__pycache__/dml.cpython-312.pyc | Bin 0 -> 8379 bytes .../__pycache__/enumerated.cpython-312.pyc | Bin 0 -> 11557 bytes .../__pycache__/expression.cpython-312.pyc | Bin 0 -> 5255 bytes .../mysql/__pycache__/json.cpython-312.pyc | Bin 0 -> 3969 bytes .../mysql/__pycache__/mariadb.cpython-312.pyc | Bin 0 -> 2689 bytes .../mariadbconnector.cpython-312.pyc | Bin 0 -> 13815 bytes .../mysqlconnector.cpython-312.pyc | Bin 0 -> 14233 bytes .../mysql/__pycache__/mysqldb.cpython-312.pyc | Bin 0 -> 12406 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 6433 bytes .../mysql/__pycache__/pymysql.cpython-312.pyc | Bin 0 -> 6245 bytes .../mysql/__pycache__/pyodbc.cpython-312.pyc | Bin 0 -> 6131 bytes .../__pycache__/reflection.cpython-312.pyc | Bin 0 -> 25928 bytes .../reserved_words.cpython-312.pyc | Bin 0 -> 4421 bytes .../mysql/__pycache__/types.cpython-312.pyc | Bin 0 -> 33090 bytes .../sqlalchemy/dialects/mysql/aiomysql.py | 250 + .../sqlalchemy/dialects/mysql/asyncmy.py | 231 + .../sqlalchemy/dialects/mysql/base.py | 3943 ++++++++ .../sqlalchemy/dialects/mysql/cymysql.py | 106 + .../sqlalchemy/dialects/mysql/dml.py | 225 + .../sqlalchemy/dialects/mysql/enumerated.py | 282 + .../sqlalchemy/dialects/mysql/expression.py | 146 + .../sqlalchemy/dialects/mysql/json.py | 91 + .../sqlalchemy/dialects/mysql/mariadb.py | 72 + .../dialects/mysql/mariadbconnector.py | 322 + .../dialects/mysql/mysqlconnector.py | 302 + .../sqlalchemy/dialects/mysql/mysqldb.py | 314 + .../sqlalchemy/dialects/mysql/provision.py | 147 + .../sqlalchemy/dialects/mysql/pymysql.py | 158 + .../sqlalchemy/dialects/mysql/pyodbc.py | 157 + .../sqlalchemy/dialects/mysql/reflection.py | 727 ++ .../dialects/mysql/reserved_words.py | 570 ++ .../sqlalchemy/dialects/mysql/types.py | 835 ++ .../sqlalchemy/dialects/oracle/__init__.py | 81 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1693 bytes .../oracle/__pycache__/base.cpython-312.pyc | Bin 0 -> 153981 bytes .../__pycache__/cx_oracle.cpython-312.pyc | Bin 0 -> 61035 bytes .../__pycache__/dictionary.cpython-312.pyc | Bin 0 -> 24628 bytes .../__pycache__/oracledb.cpython-312.pyc | Bin 0 -> 41109 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 13322 bytes .../oracle/__pycache__/types.cpython-312.pyc | Bin 0 -> 13242 bytes .../oracle/__pycache__/vector.cpython-312.pyc | Bin 0 -> 12256 bytes .../sqlalchemy/dialects/oracle/base.py | 3802 +++++++ .../sqlalchemy/dialects/oracle/cx_oracle.py | 1555 +++ .../sqlalchemy/dialects/oracle/dictionary.py | 507 + .../sqlalchemy/dialects/oracle/oracledb.py | 941 ++ .../sqlalchemy/dialects/oracle/provision.py | 297 + .../sqlalchemy/dialects/oracle/types.py | 316 + .../sqlalchemy/dialects/oracle/vector.py | 365 + .../dialects/postgresql/__init__.py | 167 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3462 bytes .../_psycopg_common.cpython-312.pyc | Bin 0 -> 7954 bytes .../__pycache__/array.cpython-312.pyc | Bin 0 -> 19582 bytes .../__pycache__/asyncpg.cpython-312.pyc | Bin 0 -> 58856 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 216482 bytes .../__pycache__/dml.cpython-312.pyc | Bin 0 -> 12616 bytes .../__pycache__/ext.cpython-312.pyc | Bin 0 -> 20649 bytes .../__pycache__/hstore.cpython-312.pyc | Bin 0 -> 15398 bytes .../__pycache__/json.cpython-312.pyc | Bin 0 -> 17168 bytes .../__pycache__/named_types.cpython-312.pyc | Bin 0 -> 23099 bytes .../__pycache__/operators.cpython-312.pyc | Bin 0 -> 2185 bytes .../__pycache__/pg8000.cpython-312.pyc | Bin 0 -> 30547 bytes .../__pycache__/pg_catalog.cpython-312.pyc | Bin 0 -> 11898 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 7808 bytes .../__pycache__/psycopg.cpython-312.pyc | Bin 0 -> 40819 bytes .../__pycache__/psycopg2.cpython-312.pyc | Bin 0 -> 36016 bytes .../__pycache__/psycopg2cffi.cpython-312.pyc | Bin 0 -> 2192 bytes .../__pycache__/ranges.cpython-312.pyc | Bin 0 -> 34796 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 11543 bytes .../dialects/postgresql/_psycopg_common.py | 189 + .../sqlalchemy/dialects/postgresql/array.py | 519 + .../sqlalchemy/dialects/postgresql/asyncpg.py | 1284 +++ .../sqlalchemy/dialects/postgresql/base.py | 5333 ++++++++++ .../sqlalchemy/dialects/postgresql/dml.py | 339 + .../sqlalchemy/dialects/postgresql/ext.py | 540 + .../sqlalchemy/dialects/postgresql/hstore.py | 406 + .../sqlalchemy/dialects/postgresql/json.py | 404 + .../dialects/postgresql/named_types.py | 524 + .../dialects/postgresql/operators.py | 129 + .../sqlalchemy/dialects/postgresql/pg8000.py | 669 ++ .../dialects/postgresql/pg_catalog.py | 326 + .../dialects/postgresql/provision.py | 175 + .../sqlalchemy/dialects/postgresql/psycopg.py | 862 ++ .../dialects/postgresql/psycopg2.py | 892 ++ .../dialects/postgresql/psycopg2cffi.py | 61 + .../sqlalchemy/dialects/postgresql/ranges.py | 1031 ++ .../sqlalchemy/dialects/postgresql/types.py | 313 + .../sqlalchemy/dialects/sqlite/__init__.py | 57 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1088 bytes .../__pycache__/aiosqlite.cpython-312.pyc | Bin 0 -> 22031 bytes .../sqlite/__pycache__/base.cpython-312.pyc | Bin 0 -> 109163 bytes .../sqlite/__pycache__/dml.cpython-312.pyc | Bin 0 -> 10071 bytes .../sqlite/__pycache__/json.cpython-312.pyc | Bin 0 -> 3800 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 8780 bytes .../__pycache__/pysqlcipher.cpython-312.pyc | Bin 0 -> 6247 bytes .../__pycache__/pysqlite.cpython-312.pyc | Bin 0 -> 31251 bytes .../sqlalchemy/dialects/sqlite/aiosqlite.py | 482 + .../sqlalchemy/dialects/sqlite/base.py | 3045 ++++++ .../sqlalchemy/dialects/sqlite/dml.py | 263 + .../sqlalchemy/dialects/sqlite/json.py | 92 + .../sqlalchemy/dialects/sqlite/provision.py | 223 + .../sqlalchemy/dialects/sqlite/pysqlcipher.py | 157 + .../sqlalchemy/dialects/sqlite/pysqlite.py | 756 ++ .../dialects/type_migration_guidelines.txt | 145 + .../sqlalchemy/engine/__init__.py | 62 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2303 bytes .../_py_processors.cpython-312.pyc | Bin 0 -> 4526 bytes .../__pycache__/_py_row.cpython-312.pyc | Bin 0 -> 5795 bytes .../__pycache__/_py_util.cpython-312.pyc | Bin 0 -> 2234 bytes .../engine/__pycache__/base.cpython-312.pyc | Bin 0 -> 130363 bytes .../characteristics.cpython-312.pyc | Bin 0 -> 6872 bytes .../engine/__pycache__/create.cpython-312.pyc | Bin 0 -> 35029 bytes .../engine/__pycache__/cursor.cpython-312.pyc | Bin 0 -> 83050 bytes .../__pycache__/default.cpython-312.pyc | Bin 0 -> 89189 bytes .../engine/__pycache__/events.cpython-312.pyc | Bin 0 -> 39955 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 102590 bytes .../engine/__pycache__/mock.cpython-312.pyc | Bin 0 -> 5709 bytes .../__pycache__/processors.cpython-312.pyc | Bin 0 -> 1317 bytes .../__pycache__/reflection.cpython-312.pyc | Bin 0 -> 80832 bytes .../engine/__pycache__/result.cpython-312.pyc | Bin 0 -> 91772 bytes .../engine/__pycache__/row.cpython-312.pyc | Bin 0 -> 17446 bytes .../__pycache__/strategies.cpython-312.pyc | Bin 0 -> 581 bytes .../engine/__pycache__/url.cpython-312.pyc | Bin 0 -> 34438 bytes .../engine/__pycache__/util.cpython-312.pyc | Bin 0 -> 6671 bytes .../sqlalchemy/engine/_py_processors.py | 136 + .../sqlalchemy/engine/_py_row.py | 128 + .../sqlalchemy/engine/_py_util.py | 74 + .../site-packages/sqlalchemy/engine/base.py | 3382 +++++++ .../sqlalchemy/engine/characteristics.py | 155 + .../site-packages/sqlalchemy/engine/create.py | 893 ++ .../site-packages/sqlalchemy/engine/cursor.py | 2298 +++++ .../sqlalchemy/engine/default.py | 2394 +++++ .../site-packages/sqlalchemy/engine/events.py | 965 ++ .../sqlalchemy/engine/interfaces.py | 3471 +++++++ .../site-packages/sqlalchemy/engine/mock.py | 134 + .../sqlalchemy/engine/processors.py | 61 + .../sqlalchemy/engine/reflection.py | 2102 ++++ .../site-packages/sqlalchemy/engine/result.py | 2399 +++++ .../site-packages/sqlalchemy/engine/row.py | 400 + .../sqlalchemy/engine/strategies.py | 16 + .../site-packages/sqlalchemy/engine/url.py | 924 ++ .../site-packages/sqlalchemy/engine/util.py | 167 + .../sqlalchemy/event/__init__.py | 26 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 912 bytes .../event/__pycache__/api.cpython-312.pyc | Bin 0 -> 9104 bytes .../event/__pycache__/attr.cpython-312.pyc | Bin 0 -> 31129 bytes .../event/__pycache__/base.cpython-312.pyc | Bin 0 -> 20025 bytes .../event/__pycache__/legacy.cpython-312.pyc | Bin 0 -> 9725 bytes .../__pycache__/registry.cpython-312.pyc | Bin 0 -> 12645 bytes .../site-packages/sqlalchemy/event/api.py | 220 + .../site-packages/sqlalchemy/event/attr.py | 676 ++ .../site-packages/sqlalchemy/event/base.py | 472 + .../site-packages/sqlalchemy/event/legacy.py | 258 + .../sqlalchemy/event/registry.py | 390 + .../site-packages/sqlalchemy/events.py | 17 + .../site-packages/sqlalchemy/exc.py | 832 ++ .../site-packages/sqlalchemy/ext/__init__.py | 11 + .../ext/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 379 bytes .../associationproxy.cpython-312.pyc | Bin 0 -> 87355 bytes .../ext/__pycache__/automap.cpython-312.pyc | Bin 0 -> 57435 bytes .../ext/__pycache__/baked.cpython-312.pyc | Bin 0 -> 23427 bytes .../ext/__pycache__/compiler.cpython-312.pyc | Bin 0 -> 21277 bytes .../horizontal_shard.cpython-312.pyc | Bin 0 -> 17695 bytes .../ext/__pycache__/hybrid.cpython-312.pyc | Bin 0 -> 60013 bytes .../ext/__pycache__/indexable.cpython-312.pyc | Bin 0 -> 13109 bytes .../instrumentation.cpython-312.pyc | Bin 0 -> 19914 bytes .../ext/__pycache__/mutable.cpython-312.pyc | Bin 0 -> 45902 bytes .../__pycache__/orderinglist.cpython-312.pyc | Bin 0 -> 18123 bytes .../__pycache__/serializer.cpython-312.pyc | Bin 0 -> 8074 bytes .../sqlalchemy/ext/associationproxy.py | 2027 ++++ .../sqlalchemy/ext/asyncio/__init__.py | 25 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1002 bytes .../asyncio/__pycache__/base.cpython-312.pyc | Bin 0 -> 11380 bytes .../__pycache__/engine.cpython-312.pyc | Bin 0 -> 57587 bytes .../asyncio/__pycache__/exc.cpython-312.pyc | Bin 0 -> 1062 bytes .../__pycache__/result.cpython-312.pyc | Bin 0 -> 37701 bytes .../__pycache__/scoping.cpython-312.pyc | Bin 0 -> 56522 bytes .../__pycache__/session.cpython-312.pyc | Bin 0 -> 71632 bytes .../sqlalchemy/ext/asyncio/base.py | 281 + .../sqlalchemy/ext/asyncio/engine.py | 1471 +++ .../sqlalchemy/ext/asyncio/exc.py | 21 + .../sqlalchemy/ext/asyncio/result.py | 965 ++ .../sqlalchemy/ext/asyncio/scoping.py | 1599 +++ .../sqlalchemy/ext/asyncio/session.py | 1947 ++++ .../site-packages/sqlalchemy/ext/automap.py | 1701 ++++ .../site-packages/sqlalchemy/ext/baked.py | 570 ++ .../site-packages/sqlalchemy/ext/compiler.py | 600 ++ .../sqlalchemy/ext/declarative/__init__.py | 65 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2038 bytes .../__pycache__/extensions.cpython-312.pyc | Bin 0 -> 21306 bytes .../sqlalchemy/ext/declarative/extensions.py | 564 ++ .../sqlalchemy/ext/horizontal_shard.py | 478 + .../site-packages/sqlalchemy/ext/hybrid.py | 1535 +++ .../site-packages/sqlalchemy/ext/indexable.py | 364 + .../sqlalchemy/ext/instrumentation.py | 450 + .../site-packages/sqlalchemy/ext/mutable.py | 1085 ++ .../sqlalchemy/ext/mypy/__init__.py | 6 + .../mypy/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes .../mypy/__pycache__/apply.cpython-312.pyc | Bin 0 -> 10568 bytes .../__pycache__/decl_class.cpython-312.pyc | Bin 0 -> 15872 bytes .../mypy/__pycache__/infer.cpython-312.pyc | Bin 0 -> 15661 bytes .../mypy/__pycache__/names.cpython-312.pyc | Bin 0 -> 11074 bytes .../mypy/__pycache__/plugin.cpython-312.pyc | Bin 0 -> 12545 bytes .../ext/mypy/__pycache__/util.cpython-312.pyc | Bin 0 -> 14031 bytes .../sqlalchemy/ext/mypy/apply.py | 324 + .../sqlalchemy/ext/mypy/decl_class.py | 515 + .../sqlalchemy/ext/mypy/infer.py | 590 ++ .../sqlalchemy/ext/mypy/names.py | 335 + .../sqlalchemy/ext/mypy/plugin.py | 303 + .../site-packages/sqlalchemy/ext/mypy/util.py | 357 + .../sqlalchemy/ext/orderinglist.py | 439 + .../sqlalchemy/ext/serializer.py | 185 + .../sqlalchemy/future/__init__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 489 bytes .../future/__pycache__/engine.cpython-312.pyc | Bin 0 -> 422 bytes .../site-packages/sqlalchemy/future/engine.py | 15 + .../site-packages/sqlalchemy/inspection.py | 174 + .../site-packages/sqlalchemy/log.py | 288 + .../site-packages/sqlalchemy/orm/__init__.py | 171 + .../orm/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 6413 bytes .../_orm_constructors.cpython-312.pyc | Bin 0 -> 107057 bytes .../orm/__pycache__/_typing.cpython-312.pyc | Bin 0 -> 6829 bytes .../__pycache__/attributes.cpython-312.pyc | Bin 0 -> 100626 bytes .../orm/__pycache__/base.cpython-312.pyc | Bin 0 -> 30363 bytes .../bulk_persistence.cpython-312.pyc | Bin 0 -> 64743 bytes .../__pycache__/clsregistry.cpython-312.pyc | Bin 0 -> 23794 bytes .../__pycache__/collections.cpython-312.pyc | Bin 0 -> 61970 bytes .../orm/__pycache__/context.cpython-312.pyc | Bin 0 -> 103254 bytes .../orm/__pycache__/decl_api.cpython-312.pyc | Bin 0 -> 69731 bytes .../orm/__pycache__/decl_base.cpython-312.pyc | Bin 0 -> 69014 bytes .../__pycache__/dependency.cpython-312.pyc | Bin 0 -> 43366 bytes .../descriptor_props.cpython-312.pyc | Bin 0 -> 49664 bytes .../orm/__pycache__/dynamic.cpython-312.pyc | Bin 0 -> 12966 bytes .../orm/__pycache__/evaluator.cpython-312.pyc | Bin 0 -> 16786 bytes .../orm/__pycache__/events.cpython-312.pyc | Bin 0 -> 136544 bytes .../orm/__pycache__/exc.cpython-312.pyc | Bin 0 -> 10244 bytes .../orm/__pycache__/identity.cpython-312.pyc | Bin 0 -> 12652 bytes .../instrumentation.cpython-312.pyc | Bin 0 -> 31244 bytes .../__pycache__/interfaces.cpython-312.pyc | Bin 0 -> 54621 bytes .../orm/__pycache__/loading.cpython-312.pyc | Bin 0 -> 47148 bytes .../mapped_collection.cpython-312.pyc | Bin 0 -> 21911 bytes .../orm/__pycache__/mapper.cpython-312.pyc | Bin 0 -> 169092 bytes .../__pycache__/path_registry.cpython-312.pyc | Bin 0 -> 31515 bytes .../__pycache__/persistence.cpython-312.pyc | Bin 0 -> 48507 bytes .../__pycache__/properties.cpython-312.pyc | Bin 0 -> 34677 bytes .../orm/__pycache__/query.cpython-312.pyc | Bin 0 -> 128846 bytes .../__pycache__/relationships.cpython-312.pyc | Bin 0 -> 130424 bytes .../orm/__pycache__/scoping.cpython-312.pyc | Bin 0 -> 83133 bytes .../orm/__pycache__/session.cpython-312.pyc | Bin 0 -> 202990 bytes .../orm/__pycache__/state.cpython-312.pyc | Bin 0 -> 45906 bytes .../__pycache__/state_changes.cpython-312.pyc | Bin 0 -> 7032 bytes .../__pycache__/strategies.cpython-312.pyc | Bin 0 -> 105147 bytes .../strategy_options.cpython-312.pyc | Bin 0 -> 87571 bytes .../orm/__pycache__/sync.cpython-312.pyc | Bin 0 -> 6634 bytes .../__pycache__/unitofwork.cpython-312.pyc | Bin 0 -> 34123 bytes .../orm/__pycache__/util.cpython-312.pyc | Bin 0 -> 85010 bytes .../orm/__pycache__/writeonly.cpython-312.pyc | Bin 0 -> 28833 bytes .../sqlalchemy/orm/_orm_constructors.py | 2661 +++++ .../site-packages/sqlalchemy/orm/_typing.py | 179 + .../sqlalchemy/orm/attributes.py | 2845 ++++++ .../site-packages/sqlalchemy/orm/base.py | 971 ++ .../sqlalchemy/orm/bulk_persistence.py | 2135 ++++ .../sqlalchemy/orm/clsregistry.py | 571 ++ .../sqlalchemy/orm/collections.py | 1627 +++ .../site-packages/sqlalchemy/orm/context.py | 3334 +++++++ .../site-packages/sqlalchemy/orm/decl_api.py | 2004 ++++ .../site-packages/sqlalchemy/orm/decl_base.py | 2192 ++++ .../sqlalchemy/orm/dependency.py | 1302 +++ .../sqlalchemy/orm/descriptor_props.py | 1092 ++ .../site-packages/sqlalchemy/orm/dynamic.py | 300 + .../site-packages/sqlalchemy/orm/evaluator.py | 379 + .../site-packages/sqlalchemy/orm/events.py | 3252 ++++++ .../site-packages/sqlalchemy/orm/exc.py | 237 + .../site-packages/sqlalchemy/orm/identity.py | 302 + .../sqlalchemy/orm/instrumentation.py | 754 ++ .../sqlalchemy/orm/interfaces.py | 1496 +++ .../site-packages/sqlalchemy/orm/loading.py | 1686 ++++ .../sqlalchemy/orm/mapped_collection.py | 557 ++ .../site-packages/sqlalchemy/orm/mapper.py | 4444 +++++++++ .../sqlalchemy/orm/path_registry.py | 809 ++ .../sqlalchemy/orm/persistence.py | 1788 ++++ .../sqlalchemy/orm/properties.py | 935 ++ .../site-packages/sqlalchemy/orm/query.py | 3459 +++++++ .../sqlalchemy/orm/relationships.py | 3508 +++++++ .../site-packages/sqlalchemy/orm/scoping.py | 2148 ++++ .../site-packages/sqlalchemy/orm/session.py | 5280 ++++++++++ .../site-packages/sqlalchemy/orm/state.py | 1168 +++ .../sqlalchemy/orm/state_changes.py | 196 + .../sqlalchemy/orm/strategies.py | 3470 +++++++ .../sqlalchemy/orm/strategy_options.py | 2568 +++++ .../site-packages/sqlalchemy/orm/sync.py | 164 + .../sqlalchemy/orm/unitofwork.py | 796 ++ .../site-packages/sqlalchemy/orm/util.py | 2403 +++++ .../site-packages/sqlalchemy/orm/writeonly.py | 674 ++ .../site-packages/sqlalchemy/pool/__init__.py | 44 + .../pool/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1540 bytes .../pool/__pycache__/base.cpython-312.pyc | Bin 0 -> 56444 bytes .../pool/__pycache__/events.cpython-312.pyc | Bin 0 -> 14353 bytes .../pool/__pycache__/impl.cpython-312.pyc | Bin 0 -> 26344 bytes .../site-packages/sqlalchemy/pool/base.py | 1516 +++ .../site-packages/sqlalchemy/pool/events.py | 372 + .../site-packages/sqlalchemy/pool/impl.py | 588 ++ .../site-packages/sqlalchemy/py.typed | 0 .../site-packages/sqlalchemy/schema.py | 69 + .../site-packages/sqlalchemy/sql/__init__.py | 145 + .../sql/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 4713 bytes .../_dml_constructors.cpython-312.pyc | Bin 0 -> 4021 bytes .../_elements_constructors.cpython-312.pyc | Bin 0 -> 66041 bytes .../__pycache__/_orm_types.cpython-312.pyc | Bin 0 -> 653 bytes .../sql/__pycache__/_py_util.cpython-312.pyc | Bin 0 -> 2992 bytes .../_selectable_constructors.cpython-312.pyc | Bin 0 -> 25176 bytes .../sql/__pycache__/_typing.cpython-312.pyc | Bin 0 -> 15229 bytes .../__pycache__/annotation.cpython-312.pyc | Bin 0 -> 21419 bytes .../sql/__pycache__/base.cpython-312.pyc | Bin 0 -> 100167 bytes .../sql/__pycache__/cache_key.cpython-312.pyc | Bin 0 -> 35622 bytes .../sql/__pycache__/coercions.cpython-312.pyc | Bin 0 -> 49105 bytes .../sql/__pycache__/compiler.cpython-312.pyc | Bin 0 -> 280156 bytes .../sql/__pycache__/crud.cpython-312.pyc | Bin 0 -> 48104 bytes .../sql/__pycache__/ddl.cpython-312.pyc | Bin 0 -> 58712 bytes .../default_comparator.cpython-312.pyc | Bin 0 -> 19506 bytes .../sql/__pycache__/dml.cpython-312.pyc | Bin 0 -> 74308 bytes .../sql/__pycache__/elements.cpython-312.pyc | Bin 0 -> 214203 bytes .../sql/__pycache__/events.cpython-312.pyc | Bin 0 -> 19248 bytes .../__pycache__/expression.cpython-312.pyc | Bin 0 -> 5173 bytes .../sql/__pycache__/functions.cpython-312.pyc | Bin 0 -> 79674 bytes .../sql/__pycache__/lambdas.cpython-312.pyc | Bin 0 -> 54810 bytes .../sql/__pycache__/naming.cpython-312.pyc | Bin 0 -> 8497 bytes .../sql/__pycache__/operators.cpython-312.pyc | Bin 0 -> 89896 bytes .../sql/__pycache__/roles.cpython-312.pyc | Bin 0 -> 12272 bytes .../sql/__pycache__/schema.cpython-312.pyc | Bin 0 -> 246060 bytes .../__pycache__/selectable.cpython-312.pyc | Bin 0 -> 264544 bytes .../sql/__pycache__/sqltypes.cpython-312.pyc | Bin 0 -> 155298 bytes .../__pycache__/traversals.cpython-312.pyc | Bin 0 -> 42443 bytes .../sql/__pycache__/type_api.cpython-312.pyc | Bin 0 -> 87845 bytes .../sql/__pycache__/util.cpython-312.pyc | Bin 0 -> 54463 bytes .../sql/__pycache__/visitors.cpython-312.pyc | Bin 0 -> 36091 bytes .../sqlalchemy/sql/_dml_constructors.py | 132 + .../sqlalchemy/sql/_elements_constructors.py | 1872 ++++ .../sqlalchemy/sql/_orm_types.py | 20 + .../site-packages/sqlalchemy/sql/_py_util.py | 75 + .../sql/_selectable_constructors.py | 763 ++ .../site-packages/sqlalchemy/sql/_typing.py | 482 + .../sqlalchemy/sql/annotation.py | 587 ++ .../site-packages/sqlalchemy/sql/base.py | 2219 +++++ .../site-packages/sqlalchemy/sql/cache_key.py | 1057 ++ .../site-packages/sqlalchemy/sql/coercions.py | 1404 +++ .../site-packages/sqlalchemy/sql/compiler.py | 8035 +++++++++++++++ .../site-packages/sqlalchemy/sql/crud.py | 1744 ++++ .../site-packages/sqlalchemy/sql/ddl.py | 1444 +++ .../sqlalchemy/sql/default_comparator.py | 551 + .../site-packages/sqlalchemy/sql/dml.py | 1850 ++++ .../site-packages/sqlalchemy/sql/elements.py | 5589 +++++++++++ .../site-packages/sqlalchemy/sql/events.py | 458 + .../sqlalchemy/sql/expression.py | 159 + .../site-packages/sqlalchemy/sql/functions.py | 2158 ++++ .../site-packages/sqlalchemy/sql/lambdas.py | 1442 +++ .../site-packages/sqlalchemy/sql/naming.py | 209 + .../site-packages/sqlalchemy/sql/operators.py | 2623 +++++ .../site-packages/sqlalchemy/sql/roles.py | 323 + .../site-packages/sqlalchemy/sql/schema.py | 6222 ++++++++++++ .../sqlalchemy/sql/selectable.py | 7265 ++++++++++++++ .../site-packages/sqlalchemy/sql/sqltypes.py | 3930 ++++++++ .../sqlalchemy/sql/traversals.py | 1024 ++ .../site-packages/sqlalchemy/sql/type_api.py | 2368 +++++ .../site-packages/sqlalchemy/sql/util.py | 1485 +++ .../site-packages/sqlalchemy/sql/visitors.py | 1164 +++ .../sqlalchemy/testing/__init__.py | 96 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 3357 bytes .../__pycache__/assertions.cpython-312.pyc | Bin 0 -> 42186 bytes .../__pycache__/assertsql.cpython-312.pyc | Bin 0 -> 20326 bytes .../__pycache__/asyncio.cpython-312.pyc | Bin 0 -> 4177 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 18207 bytes .../__pycache__/engines.cpython-312.pyc | Bin 0 -> 21273 bytes .../__pycache__/entities.cpython-312.pyc | Bin 0 -> 5016 bytes .../__pycache__/exclusions.cpython-312.pyc | Bin 0 -> 23488 bytes .../__pycache__/pickleable.cpython-312.pyc | Bin 0 -> 6716 bytes .../__pycache__/profiling.cpython-312.pyc | Bin 0 -> 13249 bytes .../__pycache__/provision.cpython-312.pyc | Bin 0 -> 25178 bytes .../__pycache__/requirements.cpython-312.pyc | Bin 0 -> 92387 bytes .../__pycache__/schema.cpython-312.pyc | Bin 0 -> 8395 bytes .../testing/__pycache__/util.cpython-312.pyc | Bin 0 -> 21859 bytes .../__pycache__/warnings.cpython-312.pyc | Bin 0 -> 2036 bytes .../sqlalchemy/testing/assertions.py | 994 ++ .../sqlalchemy/testing/assertsql.py | 520 + .../sqlalchemy/testing/asyncio.py | 135 + .../sqlalchemy/testing/config.py | 434 + .../sqlalchemy/testing/engines.py | 478 + .../sqlalchemy/testing/entities.py | 117 + .../sqlalchemy/testing/exclusions.py | 476 + .../sqlalchemy/testing/fixtures/__init__.py | 28 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 938 bytes .../fixtures/__pycache__/base.cpython-312.pyc | Bin 0 -> 13575 bytes .../fixtures/__pycache__/mypy.cpython-312.pyc | Bin 0 -> 13755 bytes .../fixtures/__pycache__/orm.cpython-312.pyc | Bin 0 -> 11477 bytes .../fixtures/__pycache__/sql.cpython-312.pyc | Bin 0 -> 21524 bytes .../sqlalchemy/testing/fixtures/base.py | 366 + .../sqlalchemy/testing/fixtures/mypy.py | 332 + .../sqlalchemy/testing/fixtures/orm.py | 227 + .../sqlalchemy/testing/fixtures/sql.py | 482 + .../sqlalchemy/testing/pickleable.py | 155 + .../sqlalchemy/testing/plugin/__init__.py | 6 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 227 bytes .../__pycache__/bootstrap.cpython-312.pyc | Bin 0 -> 2178 bytes .../__pycache__/plugin_base.cpython-312.pyc | Bin 0 -> 29362 bytes .../__pycache__/pytestplugin.cpython-312.pyc | Bin 0 -> 33715 bytes .../sqlalchemy/testing/plugin/bootstrap.py | 51 + .../sqlalchemy/testing/plugin/plugin_base.py | 828 ++ .../sqlalchemy/testing/plugin/pytestplugin.py | 892 ++ .../sqlalchemy/testing/profiling.py | 329 + .../sqlalchemy/testing/provision.py | 597 ++ .../sqlalchemy/testing/requirements.py | 1940 ++++ .../sqlalchemy/testing/schema.py | 198 + .../sqlalchemy/testing/suite/__init__.py | 19 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 594 bytes .../__pycache__/test_cte.cpython-312.pyc | Bin 0 -> 11109 bytes .../__pycache__/test_ddl.cpython-312.pyc | Bin 0 -> 18781 bytes .../test_deprecations.cpython-312.pyc | Bin 0 -> 9065 bytes .../__pycache__/test_dialect.cpython-312.pyc | Bin 0 -> 36428 bytes .../__pycache__/test_insert.cpython-312.pyc | Bin 0 -> 25274 bytes .../test_reflection.cpython-312.pyc | Bin 0 -> 155709 bytes .../__pycache__/test_results.cpython-312.pyc | Bin 0 -> 25611 bytes .../__pycache__/test_rowcount.cpython-312.pyc | Bin 0 -> 10378 bytes .../__pycache__/test_select.cpython-312.pyc | Bin 0 -> 115241 bytes .../__pycache__/test_sequence.cpython-312.pyc | Bin 0 -> 15028 bytes .../__pycache__/test_types.cpython-312.pyc | Bin 0 -> 99187 bytes .../test_unicode_ddl.cpython-312.pyc | Bin 0 -> 7648 bytes .../test_update_delete.cpython-312.pyc | Bin 0 -> 7427 bytes .../sqlalchemy/testing/suite/test_cte.py | 237 + .../sqlalchemy/testing/suite/test_ddl.py | 389 + .../testing/suite/test_deprecations.py | 153 + .../sqlalchemy/testing/suite/test_dialect.py | 776 ++ .../sqlalchemy/testing/suite/test_insert.py | 630 ++ .../testing/suite/test_reflection.py | 3557 +++++++ .../sqlalchemy/testing/suite/test_results.py | 504 + .../sqlalchemy/testing/suite/test_rowcount.py | 258 + .../sqlalchemy/testing/suite/test_select.py | 2010 ++++ .../sqlalchemy/testing/suite/test_sequence.py | 317 + .../sqlalchemy/testing/suite/test_types.py | 2147 ++++ .../testing/suite/test_unicode_ddl.py | 189 + .../testing/suite/test_update_delete.py | 139 + .../site-packages/sqlalchemy/testing/util.py | 535 + .../sqlalchemy/testing/warnings.py | 52 + .../site-packages/sqlalchemy/types.py | 74 + .../site-packages/sqlalchemy/util/__init__.py | 162 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 5766 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 31812 bytes .../_concurrency_py3k.cpython-312.pyc | Bin 0 -> 10867 bytes .../util/__pycache__/_has_cy.cpython-312.pyc | Bin 0 -> 1118 bytes .../_py_collections.cpython-312.pyc | Bin 0 -> 29232 bytes .../util/__pycache__/compat.cpython-312.pyc | Bin 0 -> 12983 bytes .../__pycache__/concurrency.cpython-312.pyc | Bin 0 -> 4122 bytes .../__pycache__/deprecations.cpython-312.pyc | Bin 0 -> 13688 bytes .../__pycache__/langhelpers.cpython-312.pyc | Bin 0 -> 86811 bytes .../__pycache__/preloaded.cpython-312.pyc | Bin 0 -> 5919 bytes .../util/__pycache__/queue.cpython-312.pyc | Bin 0 -> 14651 bytes .../__pycache__/tool_support.cpython-312.pyc | Bin 0 -> 8753 bytes .../__pycache__/topological.cpython-312.pyc | Bin 0 -> 3963 bytes .../util/__pycache__/typing.cpython-312.pyc | Bin 0 -> 26126 bytes .../sqlalchemy/util/_collections.py | 717 ++ .../sqlalchemy/util/_concurrency_py3k.py | 288 + .../site-packages/sqlalchemy/util/_has_cy.py | 40 + .../sqlalchemy/util/_py_collections.py | 541 + .../site-packages/sqlalchemy/util/compat.py | 316 + .../sqlalchemy/util/concurrency.py | 110 + .../sqlalchemy/util/deprecations.py | 401 + .../sqlalchemy/util/langhelpers.py | 2306 +++++ .../sqlalchemy/util/preloaded.py | 150 + .../site-packages/sqlalchemy/util/queue.py | 322 + .../sqlalchemy/util/tool_support.py | 201 + .../sqlalchemy/util/topological.py | 120 + .../site-packages/sqlalchemy/util/typing.py | 734 ++ .../starlette-0.52.1.dist-info/INSTALLER | 1 + .../starlette-0.52.1.dist-info/METADATA | 177 + .../starlette-0.52.1.dist-info/RECORD | 74 + .../starlette-0.52.1.dist-info/WHEEL | 4 + .../licenses/LICENSE.md | 27 + .../site-packages/starlette/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 238 bytes .../_exception_handler.cpython-312.pyc | Bin 0 -> 3102 bytes .../__pycache__/_utils.cpython-312.pyc | Bin 0 -> 5144 bytes .../__pycache__/applications.cpython-312.pyc | Bin 0 -> 12918 bytes .../authentication.cpython-312.pyc | Bin 0 -> 7750 bytes .../__pycache__/background.cpython-312.pyc | Bin 0 -> 2564 bytes .../__pycache__/concurrency.cpython-312.pyc | Bin 0 -> 3234 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 7547 bytes .../__pycache__/convertors.cpython-312.pyc | Bin 0 -> 4790 bytes .../datastructures.cpython-312.pyc | Bin 0 -> 40322 bytes .../__pycache__/endpoints.cpython-312.pyc | Bin 0 -> 7974 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 2422 bytes .../__pycache__/formparsers.cpython-312.pyc | Bin 0 -> 14324 bytes .../__pycache__/requests.cpython-312.pyc | Bin 0 -> 16741 bytes .../__pycache__/responses.cpython-312.pyc | Bin 0 -> 30374 bytes .../__pycache__/routing.cpython-312.pyc | Bin 0 -> 44051 bytes .../__pycache__/schemas.cpython-312.pyc | Bin 0 -> 7106 bytes .../__pycache__/staticfiles.cpython-312.pyc | Bin 0 -> 11563 bytes .../__pycache__/status.cpython-312.pyc | Bin 0 -> 5208 bytes .../__pycache__/templating.cpython-312.pyc | Bin 0 -> 10015 bytes .../__pycache__/testclient.cpython-312.pyc | Bin 0 -> 33242 bytes .../__pycache__/types.cpython-312.pyc | Bin 0 -> 1343 bytes .../__pycache__/websockets.cpython-312.pyc | Bin 0 -> 11847 bytes .../starlette/_exception_handler.py | 65 + .../site-packages/starlette/_utils.py | 105 + .../site-packages/starlette/applications.py | 244 + .../site-packages/starlette/authentication.py | 142 + .../site-packages/starlette/background.py | 36 + .../site-packages/starlette/concurrency.py | 57 + .../site-packages/starlette/config.py | 140 + .../site-packages/starlette/convertors.py | 89 + .../site-packages/starlette/datastructures.py | 706 ++ .../site-packages/starlette/endpoints.py | 123 + .../site-packages/starlette/exceptions.py | 33 + .../site-packages/starlette/formparsers.py | 276 + .../starlette/middleware/__init__.py | 37 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 2555 bytes .../authentication.cpython-312.pyc | Bin 0 -> 2922 bytes .../__pycache__/base.cpython-312.pyc | Bin 0 -> 11912 bytes .../__pycache__/cors.cpython-312.pyc | Bin 0 -> 7858 bytes .../__pycache__/errors.cpython-312.pyc | Bin 0 -> 9963 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 4133 bytes .../__pycache__/gzip.cpython-312.pyc | Bin 0 -> 8838 bytes .../__pycache__/httpsredirect.cpython-312.pyc | Bin 0 -> 1797 bytes .../__pycache__/sessions.cpython-312.pyc | Bin 0 -> 4690 bytes .../__pycache__/trustedhost.cpython-312.pyc | Bin 0 -> 3175 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 8664 bytes .../starlette/middleware/authentication.py | 52 + .../starlette/middleware/base.py | 244 + .../starlette/middleware/cors.py | 181 + .../starlette/middleware/errors.py | 259 + .../starlette/middleware/exceptions.py | 73 + .../starlette/middleware/gzip.py | 145 + .../starlette/middleware/httpsredirect.py | 19 + .../starlette/middleware/sessions.py | 85 + .../starlette/middleware/trustedhost.py | 60 + .../starlette/middleware/wsgi.py | 154 + .../site-packages/starlette/py.typed | 0 .../site-packages/starlette/requests.py | 332 + .../site-packages/starlette/responses.py | 566 ++ .../site-packages/starlette/routing.py | 876 ++ .../site-packages/starlette/schemas.py | 148 + .../site-packages/starlette/staticfiles.py | 217 + .../site-packages/starlette/status.py | 209 + .../site-packages/starlette/templating.py | 217 + .../site-packages/starlette/testclient.py | 739 ++ .../site-packages/starlette/types.py | 26 + .../site-packages/starlette/websockets.py | 196 + .../INSTALLER | 1 + .../METADATA | 72 + .../typing_extensions-4.15.0.dist-info/RECORD | 7 + .../typing_extensions-4.15.0.dist-info/WHEEL | 4 + .../licenses/LICENSE | 279 + .../site-packages/typing_extensions.py | 4317 ++++++++ .../INSTALLER | 1 + .../METADATA | 49 + .../typing_inspection-0.4.2.dist-info/RECORD | 13 + .../typing_inspection-0.4.2.dist-info/WHEEL | 4 + .../licenses/LICENSE | 21 + .../typing_inspection/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 219 bytes .../__pycache__/introspection.cpython-312.pyc | Bin 0 -> 17823 bytes .../typing_objects.cpython-312.pyc | Bin 0 -> 17400 bytes .../typing_inspection/introspection.py | 587 ++ .../site-packages/typing_inspection/py.typed | 0 .../typing_inspection/typing_objects.py | 607 ++ .../typing_inspection/typing_objects.pyi | 417 + .../urllib3-2.6.3.dist-info/INSTALLER | 1 + .../urllib3-2.6.3.dist-info/METADATA | 164 + .../urllib3-2.6.3.dist-info/RECORD | 79 + .../urllib3-2.6.3.dist-info/WHEEL | 4 + .../licenses/LICENSE.txt | 21 + .../site-packages/urllib3/__init__.py | 211 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 7334 bytes .../_base_connection.cpython-312.pyc | Bin 0 -> 6872 bytes .../__pycache__/_collections.cpython-312.pyc | Bin 0 -> 23023 bytes .../_request_methods.cpython-312.pyc | Bin 0 -> 10626 bytes .../__pycache__/_version.cpython-312.pyc | Bin 0 -> 817 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 38944 bytes .../connectionpool.cpython-312.pyc | Bin 0 -> 39759 bytes .../__pycache__/exceptions.cpython-312.pyc | Bin 0 -> 16635 bytes .../__pycache__/fields.cpython-312.pyc | Bin 0 -> 12046 bytes .../__pycache__/filepost.cpython-312.pyc | Bin 0 -> 3513 bytes .../__pycache__/poolmanager.cpython-312.pyc | Bin 0 -> 24362 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 57514 bytes .../site-packages/urllib3/_base_connection.py | 165 + .../site-packages/urllib3/_collections.py | 487 + .../site-packages/urllib3/_request_methods.py | 278 + .../site-packages/urllib3/_version.py | 34 + .../site-packages/urllib3/connection.py | 1099 ++ .../site-packages/urllib3/connectionpool.py | 1178 +++ .../site-packages/urllib3/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 217 bytes .../__pycache__/pyopenssl.cpython-312.pyc | Bin 0 -> 28240 bytes .../contrib/__pycache__/socks.cpython-312.pyc | Bin 0 -> 8195 bytes .../urllib3/contrib/emscripten/__init__.py | 17 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1014 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 10411 bytes .../__pycache__/fetch.cpython-312.pyc | Bin 0 -> 28662 bytes .../__pycache__/request.cpython-312.pyc | Bin 0 -> 1445 bytes .../__pycache__/response.cpython-312.pyc | Bin 0 -> 12239 bytes .../urllib3/contrib/emscripten/connection.py | 260 + .../emscripten/emscripten_fetch_worker.js | 110 + .../urllib3/contrib/emscripten/fetch.py | 726 ++ .../urllib3/contrib/emscripten/request.py | 22 + .../urllib3/contrib/emscripten/response.py | 277 + .../urllib3/contrib/pyopenssl.py | 564 ++ .../site-packages/urllib3/contrib/socks.py | 228 + .../site-packages/urllib3/exceptions.py | 335 + .../site-packages/urllib3/fields.py | 341 + .../site-packages/urllib3/filepost.py | 89 + .../site-packages/urllib3/http2/__init__.py | 53 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1770 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 17077 bytes .../http2/__pycache__/probe.cpython-312.pyc | Bin 0 -> 3721 bytes .../site-packages/urllib3/http2/connection.py | 356 + .../site-packages/urllib3/http2/probe.py | 87 + .../site-packages/urllib3/poolmanager.py | 651 ++ .../python3.12/site-packages/urllib3/py.typed | 2 + .../site-packages/urllib3/response.py | 1480 +++ .../site-packages/urllib3/util/__init__.py | 42 + .../util/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 1030 bytes .../__pycache__/connection.cpython-312.pyc | Bin 0 -> 4720 bytes .../util/__pycache__/proxy.cpython-312.pyc | Bin 0 -> 1242 bytes .../util/__pycache__/request.cpython-312.pyc | Bin 0 -> 8366 bytes .../util/__pycache__/response.cpython-312.pyc | Bin 0 -> 2898 bytes .../util/__pycache__/retry.cpython-312.pyc | Bin 0 -> 20858 bytes .../util/__pycache__/ssl_.cpython-312.pyc | Bin 0 -> 17344 bytes .../ssl_match_hostname.cpython-312.pyc | Bin 0 -> 5582 bytes .../__pycache__/ssltransport.cpython-312.pyc | Bin 0 -> 13349 bytes .../util/__pycache__/timeout.cpython-312.pyc | Bin 0 -> 11714 bytes .../util/__pycache__/url.cpython-312.pyc | Bin 0 -> 16251 bytes .../util/__pycache__/util.cpython-312.pyc | Bin 0 -> 2019 bytes .../util/__pycache__/wait.cpython-312.pyc | Bin 0 -> 3465 bytes .../site-packages/urllib3/util/connection.py | 137 + .../site-packages/urllib3/util/proxy.py | 43 + .../site-packages/urllib3/util/request.py | 263 + .../site-packages/urllib3/util/response.py | 101 + .../site-packages/urllib3/util/retry.py | 549 + .../site-packages/urllib3/util/ssl_.py | 527 + .../urllib3/util/ssl_match_hostname.py | 159 + .../urllib3/util/ssltransport.py | 271 + .../site-packages/urllib3/util/timeout.py | 275 + .../site-packages/urllib3/util/url.py | 469 + .../site-packages/urllib3/util/util.py | 42 + .../site-packages/urllib3/util/wait.py | 124 + .../uvicorn-0.41.0.dist-info/INSTALLER | 1 + .../uvicorn-0.41.0.dist-info/METADATA | 191 + .../uvicorn-0.41.0.dist-info/RECORD | 89 + .../uvicorn-0.41.0.dist-info/REQUESTED | 0 .../uvicorn-0.41.0.dist-info/WHEEL | 4 + .../uvicorn-0.41.0.dist-info/entry_points.txt | 2 + .../licenses/LICENSE.md | 27 + .../site-packages/uvicorn/__init__.py | 5 + .../site-packages/uvicorn/__main__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 407 bytes .../__pycache__/__main__.cpython-312.pyc | Bin 0 -> 327 bytes .../__pycache__/_compat.cpython-312.pyc | Bin 0 -> 4043 bytes .../__pycache__/_subprocess.cpython-312.pyc | Bin 0 -> 2970 bytes .../__pycache__/_types.cpython-312.pyc | Bin 0 -> 11433 bytes .../__pycache__/config.cpython-312.pyc | Bin 0 -> 26069 bytes .../__pycache__/importer.cpython-312.pyc | Bin 0 -> 1805 bytes .../__pycache__/logging.cpython-312.pyc | Bin 0 -> 7831 bytes .../uvicorn/__pycache__/main.cpython-312.pyc | Bin 0 -> 21398 bytes .../__pycache__/server.cpython-312.pyc | Bin 0 -> 17317 bytes .../__pycache__/workers.cpython-312.pyc | Bin 0 -> 6593 bytes .../site-packages/uvicorn/_compat.py | 91 + .../site-packages/uvicorn/_subprocess.py | 84 + .../site-packages/uvicorn/_types.py | 274 + .../site-packages/uvicorn/config.py | 551 + .../site-packages/uvicorn/importer.py | 34 + .../uvicorn/lifespan/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 218 bytes .../lifespan/__pycache__/off.cpython-312.pyc | Bin 0 -> 1007 bytes .../lifespan/__pycache__/on.cpython-312.pyc | Bin 0 -> 7967 bytes .../site-packages/uvicorn/lifespan/off.py | 17 + .../site-packages/uvicorn/lifespan/on.py | 137 + .../site-packages/uvicorn/logging.py | 117 + .../site-packages/uvicorn/loops/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 215 bytes .../loops/__pycache__/asyncio.cpython-312.pyc | Bin 0 -> 747 bytes .../loops/__pycache__/auto.cpython-312.pyc | Bin 0 -> 847 bytes .../loops/__pycache__/uvloop.cpython-312.pyc | Bin 0 -> 604 bytes .../site-packages/uvicorn/loops/asyncio.py | 11 + .../site-packages/uvicorn/loops/auto.py | 17 + .../site-packages/uvicorn/loops/uvloop.py | 10 + .../python3.12/site-packages/uvicorn/main.py | 630 ++ .../uvicorn/middleware/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 220 bytes .../__pycache__/asgi2.cpython-312.pyc | Bin 0 -> 1030 bytes .../message_logger.cpython-312.pyc | Bin 0 -> 4426 bytes .../__pycache__/proxy_headers.cpython-312.pyc | Bin 0 -> 5841 bytes .../__pycache__/wsgi.cpython-312.pyc | Bin 0 -> 9970 bytes .../site-packages/uvicorn/middleware/asgi2.py | 15 + .../uvicorn/middleware/message_logger.py | 87 + .../uvicorn/middleware/proxy_headers.py | 142 + .../site-packages/uvicorn/middleware/wsgi.py | 199 + .../uvicorn/protocols/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 219 bytes .../__pycache__/utils.cpython-312.pyc | Bin 0 -> 3171 bytes .../uvicorn/protocols/http/__init__.py | 0 .../http/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 224 bytes .../http/__pycache__/auto.cpython-312.pyc | Bin 0 -> 635 bytes .../__pycache__/flow_control.cpython-312.pyc | Bin 0 -> 3065 bytes .../http/__pycache__/h11_impl.cpython-312.pyc | Bin 0 -> 27201 bytes .../httptools_impl.cpython-312.pyc | Bin 0 -> 29220 bytes .../uvicorn/protocols/http/auto.py | 15 + .../uvicorn/protocols/http/flow_control.py | 54 + .../uvicorn/protocols/http/h11_impl.py | 550 + .../uvicorn/protocols/http/httptools_impl.py | 577 ++ .../site-packages/uvicorn/protocols/utils.py | 62 + .../uvicorn/protocols/websockets/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 230 bytes .../__pycache__/auto.cpython-312.pyc | Bin 0 -> 839 bytes .../websockets_impl.cpython-312.pyc | Bin 0 -> 20318 bytes .../websockets_sansio_impl.cpython-312.pyc | Bin 0 -> 24404 bytes .../__pycache__/wsproto_impl.cpython-312.pyc | Bin 0 -> 20900 bytes .../uvicorn/protocols/websockets/auto.py | 21 + .../protocols/websockets/websockets_impl.py | 384 + .../websockets/websockets_sansio_impl.py | 415 + .../protocols/websockets/wsproto_impl.py | 375 + .../python3.12/site-packages/uvicorn/py.typed | 1 + .../site-packages/uvicorn/server.py | 346 + .../uvicorn/supervisors/__init__.py | 16 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 784 bytes .../__pycache__/basereload.cpython-312.pyc | Bin 0 -> 7071 bytes .../__pycache__/multiprocess.cpython-312.pyc | Bin 0 -> 13513 bytes .../__pycache__/statreload.cpython-312.pyc | Bin 0 -> 2865 bytes .../watchfilesreload.cpython-312.pyc | Bin 0 -> 4309 bytes .../uvicorn/supervisors/basereload.py | 125 + .../uvicorn/supervisors/multiprocess.py | 223 + .../uvicorn/supervisors/statreload.py | 52 + .../uvicorn/supervisors/watchfilesreload.py | 86 + .../site-packages/uvicorn/workers.py | 111 + venv/lib64 | 1 + venv/pyvenv.cfg | 5 + 2984 files changed, 629155 insertions(+) create mode 100644 .env create mode 100644 .env.example create mode 100644 .idea/.gitignore create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/payme_shopify_service.iml create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 alembic.ini create mode 100644 alembic/env.py create mode 100644 alembic/script.py.mako create mode 100644 alembic/versions/0001_create_transactions.py create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/main.cpython-312.pyc create mode 100644 app/api/__init__.py create mode 100644 app/api/__pycache__/__init__.cpython-312.pyc create mode 100644 app/api/__pycache__/payme.cpython-312.pyc create mode 100644 app/api/__pycache__/shopify.cpython-312.pyc create mode 100644 app/api/health.py create mode 100644 app/api/payme.py create mode 100644 app/api/shopify.py create mode 100644 app/core/__init__.py create mode 100644 app/core/__pycache__/__init__.cpython-312.pyc create mode 100644 app/core/__pycache__/config.cpython-312.pyc create mode 100644 app/core/__pycache__/constants.cpython-312.pyc create mode 100644 app/core/__pycache__/security.cpython-312.pyc create mode 100644 app/core/config.py create mode 100644 app/core/constants.py create mode 100644 app/core/security.py create mode 100644 app/db/__init__.py create mode 100644 app/db/__pycache__/__init__.cpython-312.pyc create mode 100644 app/db/__pycache__/base.cpython-312.pyc create mode 100644 app/db/__pycache__/session.cpython-312.pyc create mode 100644 app/db/base.py create mode 100644 app/db/session.py create mode 100644 app/main.py create mode 100644 app/models/__init__.py create mode 100644 app/models/__pycache__/__init__.cpython-312.pyc create mode 100644 app/models/__pycache__/transaction.cpython-312.pyc create mode 100644 app/models/transaction.py create mode 100644 app/schemas/__init__.py create mode 100644 app/schemas/payme.py create mode 100644 app/schemas/transaction.py create mode 100644 app/services/__init__.py create mode 100644 app/services/__pycache__/__init__.cpython-312.pyc create mode 100644 app/services/__pycache__/payme_service.cpython-312.pyc create mode 100644 app/services/__pycache__/shopify_service.cpython-312.pyc create mode 100644 app/services/__pycache__/transaction_service.cpython-312.pyc create mode 100644 app/services/payme_checkout.py create mode 100644 app/services/payme_service.py create mode 100644 app/services/shopify_service.py create mode 100644 app/services/transaction_service.py create mode 100644 app/utils/__init__.py create mode 100644 app/utils/get_token.py create mode 100644 app/utils/helpers.py create mode 100644 app/utils/logger.py create mode 100644 docker-compose.yml create mode 100644 requirements.txt create mode 100644 testing.py create mode 100644 venv/bin/Activate.ps1 create mode 100644 venv/bin/activate create mode 100644 venv/bin/activate.csh create mode 100644 venv/bin/activate.fish create mode 100755 venv/bin/alembic create mode 100755 venv/bin/dotenv create mode 100755 venv/bin/fastapi create mode 100755 venv/bin/httpx create mode 100755 venv/bin/mako-render create mode 100755 venv/bin/normalizer create mode 100755 venv/bin/pip create mode 100755 venv/bin/pip3 create mode 100755 venv/bin/pip3.12 create mode 120000 venv/bin/python create mode 120000 venv/bin/python3 create mode 120000 venv/bin/python3.12 create mode 100755 venv/bin/uvicorn create mode 100644 venv/include/site/python3.12/greenlet/greenlet.h create mode 100644 venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/alembic/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/__main__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/context.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/__pycache__/op.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/api.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/render.py create mode 100644 venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py create mode 100644 venv/lib/python3.12/site-packages/alembic/command.py create mode 100644 venv/lib/python3.12/site-packages/alembic/config.py create mode 100644 venv/lib/python3.12/site-packages/alembic/context.py create mode 100644 venv/lib/python3.12/site-packages/alembic/context.pyi create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/_autogen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mssql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/mysql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/base.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/impl.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/mssql.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/mysql.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/oracle.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py create mode 100644 venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py create mode 100644 venv/lib/python3.12/site-packages/alembic/environment.py create mode 100644 venv/lib/python3.12/site-packages/alembic/migration.py create mode 100644 venv/lib/python3.12/site-packages/alembic/op.py create mode 100644 venv/lib/python3.12/site-packages/alembic/op.pyi create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/base.py create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/batch.py create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/ops.py create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py create mode 100644 venv/lib/python3.12/site-packages/alembic/operations/toimpl.py create mode 100644 venv/lib/python3.12/site-packages/alembic/py.typed create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/plugins.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/environment.py create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/migration.py create mode 100644 venv/lib/python3.12/site-packages/alembic/runtime/plugins.py create mode 100644 venv/lib/python3.12/site-packages/alembic/script/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/script/base.py create mode 100644 venv/lib/python3.12/site-packages/alembic/script/revision.py create mode 100644 venv/lib/python3.12/site-packages/alembic/script/write_hooks.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/async/README create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/async/alembic.ini.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/async/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/generic/README create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/generic/alembic.ini.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/generic/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/multidb/README create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/README create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/alembic.ini.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/schemacompare.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/assertions.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/env.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/fixtures.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/plugin/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/plugin/bootstrap.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/requirements.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/schemacompare.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/util.py create mode 100644 venv/lib/python3.12/site-packages/alembic/testing/warnings.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/editor.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/langhelpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/__pycache__/sqla_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/alembic/util/compat.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/editor.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/exc.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/langhelpers.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/messaging.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/pyfiles.py create mode 100644 venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/annotated_doc/__init__.py create mode 100644 venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/annotated_doc/main.py create mode 100644 venv/lib/python3.12/site-packages/annotated_doc/py.typed create mode 100644 venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/annotated_types/__init__.py create mode 100644 venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/annotated_types/py.typed create mode 100644 venv/lib/python3.12/site-packages/annotated_types/test_cases.py create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/anyio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/functools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/to_process.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/__init__.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/_trio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_backends/_trio.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__init__.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_eventloop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_subprocesses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_synchronization.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_fileio.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_resources.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_signals.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_sockets.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_streams.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_tasks.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_testing.py create mode 100644 venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__init__.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_eventloop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_sockets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_eventloop.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_resources.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_sockets.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_streams.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_tasks.py create mode 100644 venv/lib/python3.12/site-packages/anyio/abc/_testing.py create mode 100644 venv/lib/python3.12/site-packages/anyio/from_thread.py create mode 100644 venv/lib/python3.12/site-packages/anyio/functools.py create mode 100644 venv/lib/python3.12/site-packages/anyio/lowlevel.py create mode 100644 venv/lib/python3.12/site-packages/anyio/py.typed create mode 100644 venv/lib/python3.12/site-packages/anyio/pytest_plugin.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__init__.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/text.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/__pycache__/tls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/buffered.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/file.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/memory.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/stapled.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/text.py create mode 100644 venv/lib/python3.12/site-packages/anyio/streams/tls.py create mode 100644 venv/lib/python3.12/site-packages/anyio/to_interpreter.py create mode 100644 venv/lib/python3.12/site-packages/anyio/to_process.py create mode 100644 venv/lib/python3.12/site-packages/anyio/to_thread.py create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/certifi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/certifi/__main__.py create mode 100644 venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/certifi/cacert.pem create mode 100644 venv/lib/python3.12/site-packages/certifi/core.py create mode 100644 venv/lib/python3.12/site-packages/certifi/py.typed create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__init__.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__main__.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/constant.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/md.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/api.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/cd.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/cli/__main__.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/constant.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/legacy.py create mode 100755 venv/lib/python3.12/site-packages/charset_normalizer/md.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/md.py create mode 100755 venv/lib/python3.12/site-packages/charset_normalizer/md__mypyc.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/models.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/py.typed create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/utils.py create mode 100644 venv/lib/python3.12/site-packages/charset_normalizer/version.py create mode 100644 venv/lib/python3.12/site-packages/click-8.3.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/click-8.3.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/click-8.3.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/click-8.3.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/click-8.3.1.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/click/__init__.py create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_textwrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/_winconsole.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/decorators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/formatting.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/globals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/shell_completion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/termui.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/testing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/click/_compat.py create mode 100644 venv/lib/python3.12/site-packages/click/_termui_impl.py create mode 100644 venv/lib/python3.12/site-packages/click/_textwrap.py create mode 100644 venv/lib/python3.12/site-packages/click/_utils.py create mode 100644 venv/lib/python3.12/site-packages/click/_winconsole.py create mode 100644 venv/lib/python3.12/site-packages/click/core.py create mode 100644 venv/lib/python3.12/site-packages/click/decorators.py create mode 100644 venv/lib/python3.12/site-packages/click/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/click/formatting.py create mode 100644 venv/lib/python3.12/site-packages/click/globals.py create mode 100644 venv/lib/python3.12/site-packages/click/parser.py create mode 100644 venv/lib/python3.12/site-packages/click/py.typed create mode 100644 venv/lib/python3.12/site-packages/click/shell_completion.py create mode 100644 venv/lib/python3.12/site-packages/click/termui.py create mode 100644 venv/lib/python3.12/site-packages/click/testing.py create mode 100644 venv/lib/python3.12/site-packages/click/types.py create mode 100644 venv/lib/python3.12/site-packages/click/utils.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/__init__.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/__main__.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/cli.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/ipython.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/variables.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/dotenv/cli.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/ipython.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/main.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/parser.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/py.typed create mode 100644 venv/lib/python3.12/site-packages/dotenv/variables.py create mode 100644 venv/lib/python3.12/site-packages/dotenv/version.py create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/fastapi-0.131.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/fastapi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/__main__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/applications.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/background.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/cli.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/concurrency.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/datastructures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/encoders.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/exception_handlers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/logger.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/param_functions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/params.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/requests.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/responses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/routing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/staticfiles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/templating.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/testclient.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/__pycache__/websockets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/shared.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/__pycache__/v2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/shared.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/_compat/v2.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/applications.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/background.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/cli.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/concurrency.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/datastructures.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/models.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/encoders.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/exception_handlers.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/logger.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/asyncexitstack.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/cors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/gzip.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/httpsredirect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/trustedhost.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/asyncexitstack.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/cors.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/gzip.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/httpsredirect.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/trustedhost.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/middleware/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/constants.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/docs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/constants.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/docs.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/models.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/openapi/utils.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/param_functions.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/params.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/py.typed create mode 100644 venv/lib/python3.12/site-packages/fastapi/requests.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/responses.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/routing.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__init__.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/api_key.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/http.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/oauth2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/open_id_connect_url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/api_key.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/base.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/http.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/oauth2.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/open_id_connect_url.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/security/utils.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/staticfiles.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/templating.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/testclient.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/types.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/utils.py create mode 100644 venv/lib/python3.12/site-packages/fastapi/websockets.py create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/licenses/LICENSE.PSF create mode 100644 venv/lib/python3.12/site-packages/greenlet-3.3.2.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/greenlet/CObjects.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenlet.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyGreenletUnswitchable.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/PyModule.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TBrokenGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TExceptionState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenlet.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TGreenletGlobals.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TMainGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TPythonState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TStackState.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadState.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadStateCreator.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TThreadStateDestroy.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/TUserGreenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc create mode 100755 venv/lib/python3.12/site-packages/greenlet/_greenlet.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet.cpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_allocator.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_compiler_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_cpython_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_exceptions.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_internal.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_msvc_compat.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_refs.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_slp_switch.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/greenlet_thread_support.hpp create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/setup_switch_x64_masm.cmd create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_aarch64_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_alpha_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_amd64_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm32_ios.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.asm create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_masm.obj create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_arm64_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_csky_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_loongarch64_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_m68k_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_mips_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_aix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc64_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_aix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_linux.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_macosx.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_ppc_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_riscv_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_s390_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_sh_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_sparc_sun_gcc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x32_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.asm create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_masm.obj create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x64_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_msvc.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/platform/switch_x86_unix.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/slp_platformselect.h create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/leakcheck.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_cpp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_gc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_interpreter_shutdown.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_leaks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_throw.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_tracing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/__pycache__/test_weakref.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension.c create mode 100755 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpp create mode 100755 venv/lib/python3.12/site-packages/greenlet/tests/_test_extension_cpp.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_clearing_run_switches.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_cpp_exception.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_initialstub_already_started.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_slp_switch.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_three_greenlets2.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/fail_switch_two_greenlets.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/leakcheck.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_contextvars.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_cpp.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_extension_interface.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_gc.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_generator.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_generator_nested.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_greenlet_trash.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_interpreter_shutdown.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_leaks.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_stack_saved.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_throw.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_tracing.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_version.py create mode 100644 venv/lib/python3.12/site-packages/greenlet/tests/test_weakref.py create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/h11-0.16.0.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/h11/__init__.py create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_abnf.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_headers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_readers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_receivebuffer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_state.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/__pycache__/_writers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/h11/_abnf.py create mode 100644 venv/lib/python3.12/site-packages/h11/_connection.py create mode 100644 venv/lib/python3.12/site-packages/h11/_events.py create mode 100644 venv/lib/python3.12/site-packages/h11/_headers.py create mode 100644 venv/lib/python3.12/site-packages/h11/_readers.py create mode 100644 venv/lib/python3.12/site-packages/h11/_receivebuffer.py create mode 100644 venv/lib/python3.12/site-packages/h11/_state.py create mode 100644 venv/lib/python3.12/site-packages/h11/_util.py create mode 100644 venv/lib/python3.12/site-packages/h11/_version.py create mode 100644 venv/lib/python3.12/site-packages/h11/_writers.py create mode 100644 venv/lib/python3.12/site-packages/h11/py.typed create mode 100644 venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/httpcore-1.0.9.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/httpcore/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_ssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_synchronization.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_trace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_api.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/connection_pool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/http11.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/http2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/http_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/interfaces.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/__pycache__/socks_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/connection.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/connection_pool.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/http11.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/http2.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/http_proxy.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/interfaces.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_async/socks_proxy.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/anyio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/auto.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/mock.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/sync.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/__pycache__/trio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/anyio.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/auto.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/base.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/mock.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/sync.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_backends/trio.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_exceptions.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_models.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_ssl.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/connection_pool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http11.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/http_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/interfaces.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/__pycache__/socks_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/connection.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/connection_pool.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/http11.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/http2.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/http_proxy.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/interfaces.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_sync/socks_proxy.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_synchronization.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_trace.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/_utils.py create mode 100644 venv/lib/python3.12/site-packages/httpcore/py.typed create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/httpx/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_client.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_content.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_decoders.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_multipart.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_urlparse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_urls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/__version__.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_api.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_auth.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_client.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_config.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_content.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_decoders.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_exceptions.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_main.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_models.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_multipart.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_status_codes.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__init__.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/asgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/default.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/mock.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/asgi.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/base.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/default.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/mock.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_transports/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_types.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_urlparse.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_urls.py create mode 100644 venv/lib/python3.12/site-packages/httpx/_utils.py create mode 100644 venv/lib/python3.12/site-packages/httpx/py.typed create mode 100644 venv/lib/python3.12/site-packages/idna-3.11.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/idna-3.11.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/idna-3.11.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/idna-3.11.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/idna-3.11.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/idna/__init__.py create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/codec.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/idna/codec.py create mode 100644 venv/lib/python3.12/site-packages/idna/compat.py create mode 100644 venv/lib/python3.12/site-packages/idna/core.py create mode 100644 venv/lib/python3.12/site-packages/idna/idnadata.py create mode 100644 venv/lib/python3.12/site-packages/idna/intranges.py create mode 100644 venv/lib/python3.12/site-packages/idna/package_data.py create mode 100644 venv/lib/python3.12/site-packages/idna/py.typed create mode 100644 venv/lib/python3.12/site-packages/idna/uts46data.py create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/mako-1.3.10.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/mako/__init__.py create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/_ast_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/ast.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/cmd.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/codegen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/filters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/lookup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/parsetree.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/pygen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/pyparser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/runtime.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/template.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/_ast_util.py create mode 100644 venv/lib/python3.12/site-packages/mako/ast.py create mode 100644 venv/lib/python3.12/site-packages/mako/cache.py create mode 100644 venv/lib/python3.12/site-packages/mako/cmd.py create mode 100644 venv/lib/python3.12/site-packages/mako/codegen.py create mode 100644 venv/lib/python3.12/site-packages/mako/compat.py create mode 100644 venv/lib/python3.12/site-packages/mako/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__init__.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/autohandler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/babelplugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/beaker_cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/extract.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/linguaplugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/preprocessors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/pygmentplugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/__pycache__/turbogears.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/ext/autohandler.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/babelplugin.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/beaker_cache.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/extract.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/linguaplugin.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/preprocessors.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/pygmentplugin.py create mode 100644 venv/lib/python3.12/site-packages/mako/ext/turbogears.py create mode 100644 venv/lib/python3.12/site-packages/mako/filters.py create mode 100644 venv/lib/python3.12/site-packages/mako/lexer.py create mode 100644 venv/lib/python3.12/site-packages/mako/lookup.py create mode 100644 venv/lib/python3.12/site-packages/mako/parsetree.py create mode 100644 venv/lib/python3.12/site-packages/mako/pygen.py create mode 100644 venv/lib/python3.12/site-packages/mako/pyparser.py create mode 100644 venv/lib/python3.12/site-packages/mako/runtime.py create mode 100644 venv/lib/python3.12/site-packages/mako/template.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/_config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/assertions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/exclusions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/fixtures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/__pycache__/helpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/mako/testing/_config.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/assertions.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/config.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/exclusions.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/fixtures.py create mode 100644 venv/lib/python3.12/site-packages/mako/testing/helpers.py create mode 100644 venv/lib/python3.12/site-packages/mako/util.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/markupsafe-3.0.3.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__init__.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/markupsafe/__pycache__/_native.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_native.py create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_speedups.c create mode 100755 venv/lib/python3.12/site-packages/markupsafe/_speedups.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/markupsafe/_speedups.pyi create mode 100644 venv/lib/python3.12/site-packages/markupsafe/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/AUTHORS.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/direct_url.json create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/pip-24.3.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/pip/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__pip-runner__.py create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/__pycache__/__pip-runner__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/build_env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/pyproject.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/self_outdated_check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/__pycache__/wheel_builder.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/build_env.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/autocompletion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/base_command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/cmdoptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/command_context.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/index_command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/main_parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/progress_bars.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/req_command.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/spinners.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/autocompletion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/base_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/cmdoptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/command_context.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/index_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/main_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/progress_bars.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/req_command.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/cli/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/completion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/configuration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/debug.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/download.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/hash.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/help.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/inspect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/install.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/list.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/search.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/show.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/uninstall.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/completion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/debug.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/hash.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/list.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/search.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/show.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/commands/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/configuration.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/installed.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/sdist.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/installed.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/sdist.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/distributions/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/collector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/package_finder.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/__pycache__/sources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/collector.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/package_finder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/index/sources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_distutils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/_sysconfig.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_distutils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/_sysconfig.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/locations/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/main.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/_json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/__pycache__/pkg_resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/_json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_dists.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/__pycache__/_envs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_dists.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/importlib/_envs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/metadata/pkg_resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/candidate.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/direct_url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/format_control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/installation_report.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/link.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/scheme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/search_scope.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/selection_prefs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/target_python.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/candidate.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/direct_url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/format_control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/installation_report.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/scheme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/search_scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/selection_prefs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/target_python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/models/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/download.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/lazy_wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/session.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/__pycache__/xmlrpc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/download.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/lazy_wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/session.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/network/xmlrpc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/check.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/freeze.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/__pycache__/prepare.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/build_tracker.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_editable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/metadata_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_editable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/__pycache__/wheel_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/build_tracker.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/metadata_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_editable.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/build/wheel_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/freeze.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/editable_legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/editable_legacy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/install/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/operations/prepare.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/pyproject.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_file.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_install.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_set.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/__pycache__/req_uninstall.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/constructors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_install.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_set.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/req/req_uninstall.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/legacy/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/candidates.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/factory.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/found_candidates.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/provider.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/reporter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/__pycache__/resolver.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/base.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/factory.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/provider.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/reporter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/resolution/resolvelib/resolver.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/self_outdated_check.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_jaraco_text.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/_log.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/appdirs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/compatibility_tags.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/datetime.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/deprecation.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/direct_url_helpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/egg_link.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/encoding.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/entrypoints.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filesystem.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/filetypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/glibc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/hashes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/misc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/packaging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/retry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/setuptools_build.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/subprocess.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/temp_dir.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/unpacking.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/urls.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/virtualenv.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_jaraco_text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/_log.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/appdirs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/compatibility_tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/datetime.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/deprecation.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/direct_url_helpers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/egg_link.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/encoding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/entrypoints.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filesystem.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/filetypes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/glibc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/hashes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/misc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/packaging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/setuptools_build.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/subprocess.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/temp_dir.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/unpacking.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/urls.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/virtualenv.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/utils/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/bazaar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/git.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/mercurial.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/subversion.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/__pycache__/versioncontrol.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/bazaar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/git.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/mercurial.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/subversion.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/vcs/versioncontrol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_internal/wheel_builder.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/__pycache__/typing_extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/_cmd.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/adapter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/controller.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/filewrapper.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/heuristics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/serialize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/__pycache__/wrapper.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/_cmd.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/adapter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/file_cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/__pycache__/redis_cache.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/controller.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/filewrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/heuristics.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/serialize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/cachecontrol/wrapper.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/certifi/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/database.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/index.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/locators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/manifest.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/markers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/resources.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/scripts.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/__pycache__/wheel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/database.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/index.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/locators.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/manifest.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/resources.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/scripts.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/t32.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/t64-arm.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/t64.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/w32.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/w64-arm.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/w64.exe create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distlib/wheel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/__pycache__/distro.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/distro.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/distro/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/codec.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/core.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/idnadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/intranges.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/package_data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/__pycache__/uts46data.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/codec.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/core.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/idnadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/intranges.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/package_data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/idna/uts46data.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/ext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/__pycache__/fallback.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/ext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/msgpack/fallback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_elffile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_manylinux.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_musllinux.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/_tokenizer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/markers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/specifiers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/tags.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_elffile.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_manylinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_musllinux.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/_tokenizer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/markers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/metadata.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/requirements.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/specifiers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/tags.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/packaging/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pkg_resources/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/android.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/macos.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/unix.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/__pycache__/windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/android.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/unix.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/platformdirs/windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/cmdline.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/filter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/formatter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/lexer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/modeline.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/plugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/regexopt.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/scanner.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/sphinxext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/style.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/token.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/unistring.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/cmdline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/filters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/bbcode.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/groff.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/html.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/img.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/irc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/latex.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/other.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/pangomarkup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/rtf.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/svg.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/__pycache__/terminal256.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/bbcode.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/groff.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/html.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/img.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/irc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/latex.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/other.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/rtf.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/svg.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/formatters/terminal256.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/__pycache__/python.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/lexers/python.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/modeline.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/plugin.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/regexopt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/scanner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/sphinxext.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/__pycache__/_mapping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/styles/_mapping.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/token.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/unistring.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pygments/util.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/__pycache__/_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_impl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/__pycache__/_in_process.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/certs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/help.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/packages.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/__version__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/_internal_utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/adapters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/auth.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/certs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/compat.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/cookies.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/help.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/hooks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/models.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/packages.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/sessions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/structures.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/requests/utils.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/providers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/reporters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/resolvers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/__pycache__/structs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/__pycache__/collections_abc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/providers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/reporters.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/resolvers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/resolvelib/structs.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__main__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_cell_widths.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_emoji_replace.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_export_format.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_extension.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_fileno.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_inspect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_log_render.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_loop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_null_file.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_palettes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_pick.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_ratio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_spinners.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_stack.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_timer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_win32_console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_windows_renderer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/_wrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/abc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/align.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/ansi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/bar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/box.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/cells.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/color_triplet.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/columns.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/console.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/constrain.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/containers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/default_styles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/diagnose.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/emoji.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/file_proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/filesize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/highlighter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/jupyter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/layout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/live_render.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/markup.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/measure.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/padding.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/palette.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/panel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/pretty.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/progress_bar.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/prompt.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/protocol.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/region.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/repr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/rule.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/scope.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/screen.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/segment.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/spinner.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/status.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/style.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/styled.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/syntax.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/table.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/terminal_theme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/text.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/theme.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/themes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/traceback.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/__pycache__/tree.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_cell_widths.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_codes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_emoji_replace.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_export_format.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_extension.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_fileno.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_inspect.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_log_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_loop.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_null_file.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_palettes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_pick.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_ratio.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_spinners.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_stack.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_timer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_win32_console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_windows_renderer.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/_wrap.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/abc.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/align.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/ansi.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/box.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/cells.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/color_triplet.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/columns.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/console.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/constrain.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/containers.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/control.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/default_styles.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/diagnose.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/emoji.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/errors.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/file_proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/filesize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/highlighter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/json.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/jupyter.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/layout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/live_render.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/logging.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/markup.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/measure.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/padding.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/palette.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/panel.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/pretty.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/progress_bar.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/prompt.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/protocol.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/region.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/repr.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/rule.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/scope.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/screen.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/segment.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/spinner.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/status.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/style.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/styled.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/syntax.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/table.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/terminal_theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/text.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/theme.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/themes.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/traceback.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/rich/tree.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_parser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_re.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/__pycache__/_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_parser.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_re.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/_types.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/tomli/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_macos.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_openssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_ssl_constants.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/__pycache__/_windows.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_api.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_macos.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_openssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_ssl_constants.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/_windows.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/truststore/py.typed create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/typing_extensions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_collections.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/_version.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/connectionpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/_appengine_environ.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/appengine.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/ntlmpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/securetransport.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/bindings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__pycache__/low_level.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/appengine.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/securetransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/fields.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/filepost.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/__pycache__/six.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/makefile.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/__pycache__/weakref_finalize.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/packages/six.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/poolmanager.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/queue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/connection.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/proxy.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/queue.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/request.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/response.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/retry.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/ssltransport.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/timeout.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/url.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/urllib3/util/wait.py create mode 100644 venv/lib/python3.12/site-packages/pip/_vendor/vendor.txt create mode 100644 venv/lib/python3.12/site-packages/pip/py.typed create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__init__.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/_ipaddress.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/_json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/_range.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/errorcodes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/extras.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/pool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/sql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/__pycache__/tz.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/psycopg2/_ipaddress.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/_json.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/_psycopg.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/psycopg2/_range.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/errorcodes.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/errors.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/extensions.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/extras.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/pool.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/sql.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2/tz.py create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/psycopg2_binary-2.9.11.dist-info/top_level.txt create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libcom_err-2abe824b.so.2.1 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libcrypto-81d66ed9.so.3 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libgssapi_krb5-497db0c6.so.2.2 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libk5crypto-b1f99d5c.so.3.1 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libkeyutils-dfe70bd6.so.1.5 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libkrb5-fcafa220.so.3.3 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libkrb5support-d0bcff84.so.0.1 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/liblber-568fe118.so.2.0.200 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libldap-1accf1ee.so.2.0.200 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libpcre-9513aab5.so.1.2.0 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libpq-9b38f5e3.so.5.17 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libsasl2-883649fd.so.3.0.0 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libselinux-0922c95c.so.1 create mode 100755 venv/lib/python3.12/site-packages/psycopg2_binary.libs/libssl-81ffa89e.so.3 create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pydantic-2.12.5.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/pydantic/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/_migration.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/alias_generators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/aliases.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/annotated_handlers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/class_validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/color.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/dataclasses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/datetime_parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/decorator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/env_settings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/error_wrappers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_serializers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/functional_validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/generics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/json_schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/mypy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/networks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/root_model.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/tools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/type_adapter.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/validate_call_decorator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/__pycache__/warnings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_core_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_dataclasses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_decorators_v1.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_discriminated_union.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_docs_extraction.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_forward_ref.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generate_schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_generics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_git.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_import_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_internal_dataclass.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_known_annotated_metadata.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_mock_val_ser.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_model_construction.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_namespace_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_repr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_gather.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_schema_generation_shared.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_serializers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_signature.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_typing_extra.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validate_call.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/__pycache__/_validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_config.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_core_metadata.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_core_utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_dataclasses.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_decorators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_decorators_v1.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_discriminated_union.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_docs_extraction.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_fields.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_forward_ref.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_generics.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_git.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_import_utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_internal_dataclass.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_known_annotated_metadata.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_mock_val_ser.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_model_construction.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_namespace_utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_repr.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_schema_gather.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_schema_generation_shared.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_serializers.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_signature.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_typing_extra.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_validate_call.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_internal/_validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/_migration.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/alias_generators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/aliases.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/annotated_handlers.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/class_validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/color.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/config.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/dataclasses.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/datetime_parse.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/decorator.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/class_validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/copy_internals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/decorator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/__pycache__/tools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/class_validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/config.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/copy_internals.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/decorator.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/json.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/parse.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/deprecated/tools.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/env_settings.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/error_wrappers.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/errors.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/arguments_schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/missing_sentinel.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/__pycache__/pipeline.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/arguments_schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/missing_sentinel.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/experimental/pipeline.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/fields.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/functional_serializers.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/functional_validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/generics.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/json.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/json_schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/main.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/mypy.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/networks.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/parse.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_loader.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/__pycache__/_schema_validator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/_loader.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/plugin/_schema_validator.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/py.typed create mode 100644 venv/lib/python3.12/site-packages/pydantic/root_model.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/tools.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/type_adapter.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/types.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/typing.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/_hypothesis_plugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/annotated_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/class_validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/color.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/dataclasses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/datetime_parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/decorator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/env_settings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/error_wrappers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/generics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/mypy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/networks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/parse.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/tools.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/validators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/_hypothesis_plugin.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/annotated_types.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/class_validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/color.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/config.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/dataclasses.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/datetime_parse.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/decorator.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/env_settings.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/error_wrappers.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/errors.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/fields.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/generics.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/json.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/main.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/mypy.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/networks.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/parse.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/py.typed create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/tools.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/types.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/typing.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/v1/version.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/validate_call_decorator.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/validators.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/version.py create mode 100644 venv/lib/python3.12/site-packages/pydantic/warnings.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pydantic_core-2.41.5.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/__pycache__/core_schema.cpython-312.pyc create mode 100755 venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/_pydantic_core.pyi create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/core_schema.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_core/py.typed create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings-2.13.1.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/__pycache__/version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/main.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/py.typed create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/base.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__init__.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/aws.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/azure.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/cli.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/dotenv.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/env.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/gcp.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/nested_secrets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/pyproject.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/secrets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/toml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/__pycache__/yaml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/aws.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/azure.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/cli.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/dotenv.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/env.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/gcp.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/json.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/nested_secrets.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/pyproject.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/secrets.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/toml.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/providers/yaml.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/types.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/sources/utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/utils.py create mode 100644 venv/lib/python3.12/site-packages/pydantic_settings/version.py create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/python_dotenv-1.2.1.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/requests-2.32.5.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/requests/__init__.py create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/__version__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/_internal_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/adapters.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/auth.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/certs.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/cookies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/help.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/hooks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/models.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/packages.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/status_codes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/structures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/requests/__version__.py create mode 100644 venv/lib/python3.12/site-packages/requests/_internal_utils.py create mode 100644 venv/lib/python3.12/site-packages/requests/adapters.py create mode 100644 venv/lib/python3.12/site-packages/requests/api.py create mode 100644 venv/lib/python3.12/site-packages/requests/auth.py create mode 100644 venv/lib/python3.12/site-packages/requests/certs.py create mode 100644 venv/lib/python3.12/site-packages/requests/compat.py create mode 100644 venv/lib/python3.12/site-packages/requests/cookies.py create mode 100644 venv/lib/python3.12/site-packages/requests/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/requests/help.py create mode 100644 venv/lib/python3.12/site-packages/requests/hooks.py create mode 100644 venv/lib/python3.12/site-packages/requests/models.py create mode 100644 venv/lib/python3.12/site-packages/requests/packages.py create mode 100644 venv/lib/python3.12/site-packages/requests/sessions.py create mode 100644 venv/lib/python3.12/site-packages/requests/status_codes.py create mode 100644 venv/lib/python3.12/site-packages/requests/structures.py create mode 100644 venv/lib/python3.12/site-packages/requests/utils.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy-2.0.46.dist-info/top_level.txt create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/inspection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/log.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/aioodbc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/aioodbc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/asyncio.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/connectors/pyodbc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc create mode 100755 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/collections.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/collections.pyx create mode 100755 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/immutabledict.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/immutabledict.pxd create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/immutabledict.pyx create mode 100755 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/processors.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/processors.pyx create mode 100755 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/resultproxy.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/resultproxy.pyx create mode 100755 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/util.cpython-312-x86_64-linux-gnu.so create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/cyextension/util.pyx create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/_typing.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/aioodbc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/information_schema.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/json.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/pymssql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mssql/pyodbc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/aiomysql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/asyncmy.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/cymysql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/dml.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/enumerated.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/expression.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/json.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/mariadb.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/mariadbconnector.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/mysqlconnector.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/mysqldb.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/pymysql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/pyodbc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/reflection.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/reserved_words.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/mysql/types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/__pycache__/vector.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/cx_oracle.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/dictionary.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/oracledb.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/oracle/vector.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/_psycopg_common.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/array.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/asyncpg.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/dml.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/ext.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/hstore.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/json.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/named_types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/operators.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/pg8000.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/pg_catalog.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/psycopg.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/psycopg2.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/psycopg2cffi.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/ranges.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/postgresql/types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/json.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/aiosqlite.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/dml.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/json.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/pysqlcipher.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/sqlite/pysqlite.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/dialects/type_migration_guidelines.txt create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_row.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/_py_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/create.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/default.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/mock.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/processors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/result.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/row.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/_py_processors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/_py_row.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/_py_util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/characteristics.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/create.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/cursor.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/default.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/events.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/interfaces.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/mock.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/processors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/reflection.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/result.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/row.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/strategies.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/url.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/engine/util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/attr.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/legacy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/__pycache__/registry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/api.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/attr.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/legacy.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/event/registry.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/events.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/exc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/associationproxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/automap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/baked.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/horizontal_shard.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/hybrid.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/indexable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/instrumentation.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/mutable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/orderinglist.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/__pycache__/serializer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/associationproxy.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/engine.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/result.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/__pycache__/session.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/engine.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/exc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/result.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/scoping.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/asyncio/session.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/automap.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/baked.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/compiler.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/declarative/extensions.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/horizontal_shard.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/hybrid.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/indexable.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/instrumentation.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mutable.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/apply.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/infer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/names.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/plugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/apply.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/decl_class.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/infer.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/names.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/plugin.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/mypy/util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/orderinglist.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/ext/serializer.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/future/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/future/__pycache__/engine.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/future/engine.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/inspection.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/log.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/context.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/exc.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/identity.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/loading.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/properties.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/query.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/session.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/sync.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/_orm_constructors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/_typing.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/attributes.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/bulk_persistence.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/clsregistry.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/collections.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/context.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/decl_api.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/dependency.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/descriptor_props.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/dynamic.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/evaluator.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/events.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/exc.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/identity.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/instrumentation.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/interfaces.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/loading.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/mapped_collection.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/path_registry.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/persistence.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/properties.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/query.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/relationships.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/scoping.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/session.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/state.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/state_changes.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/strategies.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/strategy_options.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/sync.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/unitofwork.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/orm/writeonly.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/__pycache__/impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/events.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/pool/impl.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/py.typed create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/schema.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_py_util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/crud.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/dml.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/elements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/events.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/expression.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/functions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/naming.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/operators.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/roles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_dml_constructors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_elements_constructors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_orm_types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_py_util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_selectable_constructors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/_typing.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/annotation.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/cache_key.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/coercions.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/compiler.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/crud.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/ddl.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/default_comparator.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/dml.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/elements.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/events.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/expression.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/functions.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/lambdas.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/naming.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/operators.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/roles.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/schema.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/sqltypes.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/traversals.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/type_api.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/sql/visitors.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/assertions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/assertsql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/engines.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/entities.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/exclusions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/pickleable.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/profiling.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/provision.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/requirements.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/schema.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/__pycache__/warnings.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/assertions.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/assertsql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/asyncio.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/config.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/engines.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/entities.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/exclusions.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__pycache__/orm.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/__pycache__/sql.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/mypy.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/orm.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/fixtures/sql.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/pickleable.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/bootstrap.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/plugin_base.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/plugin/pytestplugin.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/profiling.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/provision.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/requirements.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/schema.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_cte.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_insert.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_results.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_select.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_cte.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_ddl.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_deprecations.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_dialect.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_insert.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_reflection.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_results.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_rowcount.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_select.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_sequence.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_unicode_ddl.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/suite/test_update_delete.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/util.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/testing/warnings.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/types.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/_py_collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/queue.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/tool_support.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/topological.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/__pycache__/typing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/_collections.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/_concurrency_py3k.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/_has_cy.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/_py_collections.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/compat.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/concurrency.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/deprecations.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/langhelpers.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/preloaded.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/queue.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/tool_support.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/topological.py create mode 100644 venv/lib/python3.12/site-packages/sqlalchemy/util/typing.py create mode 100644 venv/lib/python3.12/site-packages/starlette-0.52.1.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/starlette-0.52.1.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/starlette-0.52.1.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/starlette-0.52.1.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/starlette-0.52.1.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/starlette/__init__.py create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/_exception_handler.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/_utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/applications.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/authentication.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/background.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/concurrency.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/convertors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/datastructures.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/endpoints.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/formparsers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/requests.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/responses.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/routing.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/schemas.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/staticfiles.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/status.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/templating.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/testclient.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/__pycache__/websockets.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/_exception_handler.py create mode 100644 venv/lib/python3.12/site-packages/starlette/_utils.py create mode 100644 venv/lib/python3.12/site-packages/starlette/applications.py create mode 100644 venv/lib/python3.12/site-packages/starlette/authentication.py create mode 100644 venv/lib/python3.12/site-packages/starlette/background.py create mode 100644 venv/lib/python3.12/site-packages/starlette/concurrency.py create mode 100644 venv/lib/python3.12/site-packages/starlette/config.py create mode 100644 venv/lib/python3.12/site-packages/starlette/convertors.py create mode 100644 venv/lib/python3.12/site-packages/starlette/datastructures.py create mode 100644 venv/lib/python3.12/site-packages/starlette/endpoints.py create mode 100644 venv/lib/python3.12/site-packages/starlette/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/starlette/formparsers.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__init__.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/authentication.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/base.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/cors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/errors.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/gzip.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/httpsredirect.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/sessions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/trustedhost.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/authentication.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/base.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/cors.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/errors.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/gzip.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/httpsredirect.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/sessions.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/trustedhost.py create mode 100644 venv/lib/python3.12/site-packages/starlette/middleware/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/starlette/py.typed create mode 100644 venv/lib/python3.12/site-packages/starlette/requests.py create mode 100644 venv/lib/python3.12/site-packages/starlette/responses.py create mode 100644 venv/lib/python3.12/site-packages/starlette/routing.py create mode 100644 venv/lib/python3.12/site-packages/starlette/schemas.py create mode 100644 venv/lib/python3.12/site-packages/starlette/staticfiles.py create mode 100644 venv/lib/python3.12/site-packages/starlette/status.py create mode 100644 venv/lib/python3.12/site-packages/starlette/templating.py create mode 100644 venv/lib/python3.12/site-packages/starlette/testclient.py create mode 100644 venv/lib/python3.12/site-packages/starlette/types.py create mode 100644 venv/lib/python3.12/site-packages/starlette/websockets.py create mode 100644 venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/typing_extensions-4.15.0.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/typing_extensions.py create mode 100644 venv/lib/python3.12/site-packages/typing_inspection-0.4.2.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/typing_inspection-0.4.2.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/typing_inspection-0.4.2.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/typing_inspection-0.4.2.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/typing_inspection-0.4.2.dist-info/licenses/LICENSE create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/__init__.py create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/__pycache__/introspection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/__pycache__/typing_objects.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/introspection.py create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/py.typed create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/typing_objects.py create mode 100644 venv/lib/python3.12/site-packages/typing_inspection/typing_objects.pyi create mode 100644 venv/lib/python3.12/site-packages/urllib3-2.6.3.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/urllib3-2.6.3.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/urllib3-2.6.3.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/urllib3-2.6.3.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/urllib3-2.6.3.dist-info/licenses/LICENSE.txt create mode 100644 venv/lib/python3.12/site-packages/urllib3/__init__.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_base_connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_collections.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_request_methods.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/_version.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/connectionpool.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/exceptions.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/fields.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/filepost.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/poolmanager.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/_base_connection.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/_collections.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/_request_methods.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/_version.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/connection.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/connectionpool.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/__init__.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/pyopenssl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/__pycache__/socks.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__init__.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/fetch.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/connection.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/fetch.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/request.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/emscripten/response.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/pyopenssl.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/contrib/socks.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/exceptions.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/fields.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/filepost.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__init__.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/__pycache__/probe.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/connection.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/http2/probe.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/poolmanager.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/py.typed create mode 100644 venv/lib/python3.12/site-packages/urllib3/response.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__init__.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/connection.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/proxy.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/request.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/response.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/retry.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/ssltransport.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/timeout.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/url.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/util.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/__pycache__/wait.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/connection.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/proxy.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/request.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/response.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/retry.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/ssl_.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/ssl_match_hostname.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/ssltransport.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/timeout.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/url.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/util.py create mode 100644 venv/lib/python3.12/site-packages/urllib3/util/wait.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/INSTALLER create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/METADATA create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/RECORD create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/REQUESTED create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/WHEEL create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/entry_points.txt create mode 100644 venv/lib/python3.12/site-packages/uvicorn-0.41.0.dist-info/licenses/LICENSE.md create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__main__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/__main__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/_compat.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/_subprocess.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/_types.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/config.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/importer.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/logging.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/main.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/server.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/__pycache__/workers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/_compat.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/_subprocess.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/_types.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/config.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/importer.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/off.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/__pycache__/on.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/off.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/lifespan/on.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/logging.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/asyncio.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/auto.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/__pycache__/uvloop.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/asyncio.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/auto.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/loops/uvloop.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/main.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/asgi2.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/message_logger.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/proxy_headers.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/__pycache__/wsgi.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/asgi2.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/message_logger.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/middleware/wsgi.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/__pycache__/utils.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/auto.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/flow_control.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/h11_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/__pycache__/httptools_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/auto.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/flow_control.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/utils.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/auto.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/websockets_sansio_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/__pycache__/wsproto_impl.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/auto.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/websockets_impl.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/websockets_sansio_impl.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/protocols/websockets/wsproto_impl.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/py.typed create mode 100644 venv/lib/python3.12/site-packages/uvicorn/server.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__init__.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/__init__.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/basereload.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/multiprocess.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/statreload.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/__pycache__/watchfilesreload.cpython-312.pyc create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/basereload.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/multiprocess.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/statreload.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/supervisors/watchfilesreload.py create mode 100644 venv/lib/python3.12/site-packages/uvicorn/workers.py create mode 120000 venv/lib64 create mode 100644 venv/pyvenv.cfg diff --git a/.env b/.env new file mode 100644 index 0000000..e4e57c1 --- /dev/null +++ b/.env @@ -0,0 +1,8 @@ +PAYME_MERCHANT_ID=699e8a5fbbf6bc7219737538 +PAYME_SECRET_KEY=fX5shuxVvr@RgmZMU3U61no#XQkT&S&Tob@o + +SHOPIFY_ACCESS_TOKEN=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 +SHOPIFY_STORE_URL=cx0du9-sq.myshopify.com +SHOPIFY_WEBHOOK_SECRET=2d8e668f7ad3c78bf04cc7676e9c2d4bdd80ce45a88cc4f91110e41ff4cfc844 + +DATABASE_URL=postgresql://payme_user:payme_pass@db:5432/payme_db \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4e57c1 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +PAYME_MERCHANT_ID=699e8a5fbbf6bc7219737538 +PAYME_SECRET_KEY=fX5shuxVvr@RgmZMU3U61no#XQkT&S&Tob@o + +SHOPIFY_ACCESS_TOKEN=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 +SHOPIFY_STORE_URL=cx0du9-sq.myshopify.com +SHOPIFY_WEBHOOK_SECRET=2d8e668f7ad3c78bf04cc7676e9c2d4bdd80ce45a88cc4f91110e41ff4cfc844 + +DATABASE_URL=postgresql://payme_user:payme_pass@db:5432/payme_db \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..9469db5 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/payme_db + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..f7b32cc --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..240edcc --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..55bfcce --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..41f36bf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/payme_shopify_service.iml b/.idea/payme_shopify_service.iml new file mode 100644 index 0000000..68b73b6 --- /dev/null +++ b/.idea/payme_shopify_service.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..596536a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD ["sh", "-c", "\ + echo 'Waiting for database...' && \ + while ! python -c \"import psycopg2; psycopg2.connect('$DATABASE_URL')\" 2>/dev/null; do \ + sleep 1; \ + done && \ + echo 'Database ready!' && \ + alembic upgrade head && \ + uvicorn app.main:app --host 0.0.0.0 --port 8000 \ +"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..b721049 --- /dev/null +++ b/alembic.ini @@ -0,0 +1,37 @@ +[alembic] +script_location = alembic +prepend_sys_path = . + +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..3d18a71 --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,49 @@ +import os +from logging.config import fileConfig +from sqlalchemy import engine_from_config, pool +from alembic import context + +from app.db.base import Base +from app.models.transaction import Transaction # noqa: makes Alembic detect the model + +config = context.config + +# Use DATABASE_URL from environment (set by docker-compose via .env) +database_url = os.environ.get("DATABASE_URL") +if database_url: + config.set_main_option("sqlalchemy.url", database_url) + +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +target_metadata = Base.metadata + + +def run_migrations_offline() -> None: + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() \ No newline at end of file diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..004f92d --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} \ No newline at end of file diff --git a/alembic/versions/0001_create_transactions.py b/alembic/versions/0001_create_transactions.py new file mode 100644 index 0000000..76391fe --- /dev/null +++ b/alembic/versions/0001_create_transactions.py @@ -0,0 +1,31 @@ +"""create transactions table + +Revision ID: 0001 +Revises: +Create Date: 2026-02-25 +""" +from typing import Sequence, Union +from alembic import op +import sqlalchemy as sa + +revision: str = "0001" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "transactions", + sa.Column("id", sa.Integer(), primary_key=True, index=True), + sa.Column("order_id", sa.String(), nullable=False), + sa.Column("payme_transaction_id", sa.String(), unique=True, nullable=True), + sa.Column("amount", sa.BigInteger(), nullable=False), + sa.Column("state", sa.Integer(), default=0), + sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()), + sa.Column("updated_at", sa.DateTime(timezone=True), onupdate=sa.func.now()), + ) + + +def downgrade() -> None: + op.drop_table("transactions") \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78a3fc41708fb5e6198e4713be7ef7fd3a76ea67 GIT binary patch literal 171 zcmX@j%ge<81kvhqGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!^3u=9&rQ`&OiC%u zNvz7O(sxNM&MwI>(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDk#v8 qkI&4@EQycTE2#X%VUwE=G$z%qh!towBM=vZ7$2D#85xV1fh+)G-YfLQlhkdxP+5xTp?ESL8W-J@WkHYyFJ)nGy_DEYyHoRvnN+tut*oF& zJ?+ImK&k(UmkI@!Kw-s`7g2ia$(bZg*TW9X%=f-|`QG)h~6l9P0ub2oxY_noD%bq&(bN3vYIUhiUx{ zBay3qDNU7(LvYQP+Enbh`tD~Y+zN-rm=q+2&X#Nsfi(xB&!TvCt&l9@|8Ppz(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDkuQr tO#S%y%)HE!_;|g7%3mBdx%oh&QtgUZfyOfeaWRPTk(rT^v4|PS0st+4Epq?> literal 0 HcmV?d00001 diff --git a/app/api/__pycache__/payme.cpython-312.pyc b/app/api/__pycache__/payme.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e638c87080beac2380c0f7a883e6b35a118d29b9 GIT binary patch literal 1820 zcma)7PfT1z7@v86_wDi)mIBMamKB=T?STa}5o4igmY8Ujkk%;K%jU5&%P#EOx1IMW zS%hS@Mr}0Frb+QY4Dq6dXt;Y3j3+M?4&V!oBwjS}u*gBerGE2vmlB1flgu~2@0;&8 zGw+*kzW0l+`w_I|SC+CrO9=hWm0DA(;NVLDi-;hCbI8OAYzhg%6cZxmHX$dOazgg7 zm{UwOp?X-#`AjXLnf`>|3?u?(U7`*n0a3hCT{fXped>H|v!^4GS8zoGZ;`4sEscpn z1)@?pEfL=b^6@(LMJ?+8lT)Iit6d*^?WQ&3Q0A&P>FpV6JFYrH3p7vcD0bCb)V8x$ z9-uNwog|rvN^V;!LsQeq0%d87nMsEkdD}=i;O4fc7&RPv(w58^d6I+mJ5C`SPqwFw ze2V5yBhD$Yf>AW7tFr0=1u~Hmo<;!4fbT*-_zEI%17V2%JesOT<%A!q#o#X7#JeIw z4~0eXLs>=(!n`mA`hOe2wP-GgXLtx%v{F6Gf^>$Dc)W@{FA{0`d4yP#!%ywCd8s!4 zzjG=1cYq;#jAlnYW?zyrp8m$( zvaLKTq*w?d;L4OSi$z7&3R=aaFlx`_oT%bT+|?D~Q7|8$=5c1cI4~Z)sLJX%)lNTnU!h-EC3#*7IvlQZVBbFmR>Pdip2HcDqBW6YYO zDaWonh@_pd3fXiqX;XG5n*vgy06klo?JX2t-FrC6SvrxitZ5d3eS&<$zd-zf4oj%4 zpvJbv&ikDYE^G(7N+WxE=(F*Ad2g-f#Wl;)4sN*Y{L? zBXng`y|NqbTW#424{nDB?XVo7$z_aQ|xeYI-X?v>h6%+AnWvmv_Shs{^Zd zw!*{Pq2aUr%Rbb4ey6c>=fcH}{`jNjk$qKa@RyOK`O87n)V33B-DzwEW1RvLQ0Q)%fxu|EOV4UaR0EdmKLa{ib+5eu(3;w0J!LaIG81RcWoKjpM60u1RY{9Iv9M@24iwbSe2CgB1Nv+kkfDj@OAW<)km*sZgcDcJfXNOYo zNUp7^AvI|mWAQ^j_^}2}8b9@W{pJ@=np`tr#Ml`9Vo;j2=_hA*Z#+^b+4p`k@6CH} z-oE#K42K&4U0lNft1Jj9GB-)d|pTic`+q&flNwC zzK)!deXgVw%7vVo52ZpJ-~r^OzdI!koJtPr6yBXw5Dq z9b`h(l|j(J&?E{9?q+I!eeVjTSLwGG=K&Z69DRKW*!wknH2_#_S7`=5q4~PauL;*A z39N8bpWc0O+0u(AfM%7wd4(I++f5ABx!qjH46*1-9FDOw zJI+v@uh%NUZmpj&#B8uP*lkQw=S*is*!O}%bvZ*Np~PR}T%Ohwzh29p8^%YN+!Rdl zTZu`oaQGD_v0r7V&P(;YiY3vMz2H|QiWDEoxRdFYq>{|6{_jsJrdm?W&>emXya7J{ zi+~y?>lQ7Q<;Y0kqLH)A_C+|8b)5MGPc#=B zc)?9)91{}RgxIihgttthVFw$zbP-t@NFqgJDGv)cU8_-gQ4TH=$rmM>i8$k!OGL>+ zgBG((ggJu)_aSC!Ttq0qMI_VLlK9l{_{7N2hw1kR&kc`{kEW*v z&rc3c6S=0ZWDPf)vu3En;4QLg&3TxYHq(B*qG@1*sD7iSEfa;ktBrv{1l^)j zaG~qsr&dqh%x-E&$^$!*=9>en%jMxm(fG|38d!Psi3|>% zyr#k^LH^j5MNzl~sZ1;5#f0ujidJT0YHW1=&a|4pNcD{qc z2XAr%iumAk1I0fm9Hogc^172Kvj(luqJ_LkfkcmZm4Uorf)0Ab8!#k7bKRYB5Tv9q zYt02kDWhPFi$%x9ezLy-dsH2u str: + """Generate a Payme checkout URL""" + # Amount must be in tiyin (1 UZS = 100 tiyin) + amount_tiyin = int(amount_uzs * 100) + + # Payme requires account params to be base64-encoded + import json + account = json.dumps({"order": str(order_id)}) + account_encoded = base64.b64encode(account.encode()).decode() + + merchant_id = settings.PAYME_MERCHANT_ID + + # Payme checkout URL format + params = f"m={merchant_id};ac.order={order_id};a={amount_tiyin}" + params_encoded = base64.b64encode(params.encode()).decode() + + return f"https://checkout.paycom.uz/{params_encoded}" + + +@router.post("/shopify/order-created") +async def order_created(request: Request): + raw_body = await request.body() + + # Verify Shopify webhook signature + hmac_header = request.headers.get("X-Shopify-Hmac-Sha256") + computed_hmac = base64.b64encode( + hmac.new( + settings.SHOPIFY_WEBHOOK_SECRET.encode(), + raw_body, + hashlib.sha256 + ).digest() + ).decode() + + if not hmac.compare_digest(computed_hmac, hmac_header or ""): + raise HTTPException(status_code=401, detail="Invalid webhook") + + data = json.loads(raw_body) + + order_id = str(data["id"]) + total_price = float(data["total_price"]) + customer_email = data.get("email", "") + + # Generate Payme payment link + payment_link = create_payme_payment_link(order_id, total_price) + + # Add the link as an order note in Shopify so customer can see it + await add_payment_link_to_order(order_id, payment_link) + + return {"status": "payment_link_created", "link": payment_link} + + +async def add_payment_link_to_order(order_id: str, payment_link: str): + """Add payment link as a note on the Shopify order""" + import httpx + url = f"https://{settings.SHOPIFY_STORE_URL}/admin/api/2024-01/orders/{order_id}.json" + headers = { + "X-Shopify-Access-Token": settings.SHOPIFY_ACCESS_TOKEN, + "Content-Type": "application/json", + } + payload = { + "order": { + "id": order_id, + "note": f"Payme payment link: {payment_link}" + } + } + async with httpx.AsyncClient() as client: + await client.put(url, json=payload, headers=headers) \ No newline at end of file diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/__pycache__/__init__.cpython-312.pyc b/app/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a49b96b67ade51cd224179cda2044181f9e7dc79 GIT binary patch literal 176 zcmX@j%ge<81kvhqGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!^4HJE&rQ`&OiC%u zNvz7O(sxNM&MwI>(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDk#uT v&M!*UkB`sH%PfhH*DI*}#bJ}14>T*)u80+AJ|hqpgBTx~85tRin1L(+Wd1GV literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/config.cpython-312.pyc b/app/core/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fe57cb80355a1ccd7f88f88691287b7f64b4e8c GIT binary patch literal 858 zcmY*XOK;Oa5Z<+&)P#gIZBmq|QMsB!%mFx{3Mmgms*s1`R46`lW#Ub1NNi)fMTxi| z6^X|W=!HLk@>4kG60L+(i4!*k6tX?Q*r)=%9lN*$iE(Qf@09R8zVntyY_@gC|{7ESD8kMV)3% zX^_;)t9F`g1s%4Zk)4(6xl*b&n>7>$A@NGFQ!EwLKy|Hd+U#J>H>vqww*hJ6IEh~* zSD6;dw$(R}$3g*HXAMapB0`ov#0ZfXLPOg)9WWk8==G^SSe}Sfi~8mOe+6^)%%U!z z*h4JqFQ{A}n`60xy;r{N$PN7FPRq4l;-2rxjy|G+y%XCp`y=FG_ss0kOO7M=Y!_2t znG~E6iH1qJ2^#wr{k#nd=U}#-{V2NIl)D)U4JqofQ#Z+g6-s!|cS#Z<-Lh<7_f6aK z5DLWZ783<`(~Pm{3xG|CcaOq*FuR?b3c1-%b}D2qbNQ)|UwU@;ro!IkR%R+>{t7UX z`vWM1!B`yDwv;n6bjvq;w61UtT{gJE6GUA5u4l0j_rnf9c5_tBewvr3@Qa5Keg}82 XqBmjg!`mxB&F@_hPk(;&E^NR*Je$!? literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/constants.cpython-312.pyc b/app/core/__pycache__/constants.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..908872f7a2a13ccf92021d889e9d9d9873aa1026 GIT binary patch literal 410 zcmX@j%ge<81YO#5GS33(#~=<2FhUuh6@ZNC3@Hpz3@MCJj44b}OexG!%qc8UEGeu} ztSM|!Y?bVq94|r2UxGBfWC9WYfgtmF8<2PkA|3zREWuvEJu0iXq-z$n%Q5+9fu85tk&@HTMX5D;wOy&)vrz<)zX gq=Ekf8-s-01qP8Daw-=Xq;H7JUStp{;sfdc03Wh`*Z=?k literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/security.cpython-312.pyc b/app/core/__pycache__/security.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..849fed8d04b42197b68e8643f0f8cb5eb0adb88a GIT binary patch literal 1176 zcmZuw-D?v|6rb5mcGK3ZZSJK~KT}X7a_#mZRuCza8b2saV{f1~TnO9Dw3(P>*O^Ic zN&-Rr0~Ez72=-B_5B>qZ`RGeygvjW{ich{3>QkTYOwy%Q?t#qjoH@VqHD~ia9PR~D z-kp0+W;6i4E5jFQcVs6nVGSUFU=d^#YDHMmGFnN`=up{uF;EI-f>IA)ZBawP`+-V; zy9R2YaKP!S1;zMH5Zu_yQ-?Lk!aV2`kcRZvfsMWK?n!UK^nLq?f_^+QhU9f!<2}B& z(0Mf9>S9gv`GxSW9%XIpUDfrP{=d~sKl&fpS3OV<)&kt}^A!4hZ69x61c3cf3m#xa z_PxHQSoGlRA=Gnl_9}?=CPjFn!t*XAtBzUNT1wbAL9BwQE^4#Q({(X%iXN7%2RkgBbTfz-tObRs*Qn4P>nkxXZAToL`t9I@PpJt?Mo8^!m-Fb+&Qsi}h#QI@z>NK3!>9XWQ0D(;8`6qm64@M~7dWe|G+* z-a7hMGjOCa^KuwHZon>)^sI zxFb6QF_R8R5ayyIm+uj({$}099hS3>s9EJvUL;GMJ#_|q1?UMyJ0_3a(B+aV9}WS8;9Uc_;lqbP}qt74++s3fdBvi literal 0 HcmV?d00001 diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..d8763a1 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,18 @@ +from pydantic_settings import BaseSettings + + +class Settings(BaseSettings): + PAYME_MERCHANT_ID: str + PAYME_SECRET_KEY: str + + SHOPIFY_ACCESS_TOKEN: str + SHOPIFY_STORE_URL: str + SHOPIFY_WEBHOOK_SECRET: str + + DATABASE_URL: str + + class Config: + env_file = ".env" + + +settings = Settings() \ No newline at end of file diff --git a/app/core/constants.py b/app/core/constants.py new file mode 100644 index 0000000..b8a23a1 --- /dev/null +++ b/app/core/constants.py @@ -0,0 +1,10 @@ +# Payme states +STATE_NEW = 0 +STATE_CREATED = 1 +STATE_COMPLETED = 2 +STATE_CANCELLED = -1 + +# Payme error codes +ERROR_INVALID_AMOUNT = -31001 +ERROR_TRANSACTION_NOT_FOUND = -31003 +ERROR_CANNOT_PERFORM = -31008 \ No newline at end of file diff --git a/app/core/security.py b/app/core/security.py new file mode 100644 index 0000000..487cc32 --- /dev/null +++ b/app/core/security.py @@ -0,0 +1,18 @@ +import base64 +from fastapi import Request, HTTPException +from app.core.config import settings + + +def verify_payme_auth(request: Request): + auth_header = request.headers.get("Authorization") + + if not auth_header: + raise HTTPException(status_code=401, detail="Missing auth") + + encoded = auth_header.split(" ")[1] + decoded = base64.b64decode(encoded).decode() + + merchant_id, secret = decoded.split(":") + + if merchant_id != settings.PAYME_MERCHANT_ID or secret != settings.PAYME_SECRET_KEY: + raise HTTPException(status_code=403, detail="Invalid Payme credentials") \ No newline at end of file diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/db/__pycache__/__init__.cpython-312.pyc b/app/db/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27ea66591473a300b031f373d4d1c43e4d228766 GIT binary patch literal 174 zcmX@j%ge<81kvhqGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!^3~7C&rQ`&OiC%u zNvz7O(sxNM&MwI>(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDk#uT tNz#vx&&KECBU^EdBrh literal 0 HcmV?d00001 diff --git a/app/db/__pycache__/base.cpython-312.pyc b/app/db/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb40d69b2538c131d6c1e6c9affb1dad0f10a4cf GIT binary patch literal 400 zcmXv~u};G<5WP!M)RqPoK7a`khWZB(NT?EGsSslp%Z-g1NJwg@Md{puKj^{_RQv}E zKY$`JF|i>PmQI|La>Lzw_Iu~^z4?9@C|-}B;~T1kevs5uLB9WNKb2d=szQ}SmRF8+(I=fR* zVOTCoYG=k-9^aPSC_RfKGUYjwBQ~N}`+2GDr|aYqFqSRg-D_*Rruy8Dwr3`ZNTRVy v%YLR)Z8y+3I~LX2T|%4oX*2kl_KDhWn*&PUE+T%w$udcJUzOAVNy*rLlG^Iii z5B}f}9_&quo+Nq{JbL#e3hH7(@uIgvE~WI~o9u=jeK6nq-kUdX-g`42v)MGDY`=VA zUnT&*g)tU2Cc`F@EwI2M4!ER3h~SbVxpGAwaoJH^wW1P`fD)>+1g{wfqtjVYm&?@yBwnx#C;goaR_grI*GPE^LgTVG0 ze5oLZX`>VPrti9T5T*pLJ00E(m748YW^_q}3aVLL!vJ9!2zD04wE+yG7`P27*o5cO z7WTRct+>i~bEL+R1bUe6e=%KHmv2Cqyp_A8KvoJ;m|2aQDf_13gsSQIT=XbeH7=@| z*)Qu&-=(@yvpSBkVQ=V*l&=MTTVJ8wnJdh1Q8VCr+vvHp%A0=MuJ@{(vRk%^huUr3 zs_F5(=G(n6(V#)qs<9mA)^PCx!XMb{!;jSD!&Ci*T}f6azU7WS(0*#^C)$1OQF`y5 z6p;gd0;BI7DU!#^EId0_B(IL2m*2@n>?9qg`Ax@g%qDevObjZ_9XjTH<}xwyFpo9O zn?9omUfpgm@w>vzV(D6Gp|pCbdi`oSOhix3#FrdZ`Wl8vfn^c)wF|C~v!EB*6q;g4 z{2aorObFS5(>rhm@mFf<>H62{xlhw`pHpZ1iSPN7+nx97hovw1<$h+UsAPgo;;DaO CfVPeR literal 0 HcmV?d00001 diff --git a/app/db/base.py b/app/db/base.py new file mode 100644 index 0000000..1c2dcc4 --- /dev/null +++ b/app/db/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + pass \ No newline at end of file diff --git a/app/db/session.py b/app/db/session.py new file mode 100644 index 0000000..5399ce7 --- /dev/null +++ b/app/db/session.py @@ -0,0 +1,13 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.core.config import settings + +engine = create_engine(settings.DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..d707b6f --- /dev/null +++ b/app/main.py @@ -0,0 +1,13 @@ +from fastapi import FastAPI +from app.api.shopify import router as shopify_router +from app.api.payme import router as payme_router +from app.db.base import Base +from app.db.session import engine + +Base.metadata.create_all(bind=engine) +app = FastAPI() +app.include_router(shopify_router) +app.include_router(payme_router) +@app.get("/") +def root(): + return {"message": "Service running"} \ No newline at end of file diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/__pycache__/__init__.cpython-312.pyc b/app/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5ee1ae93f3c7a20e62c15ebb322e2f08ddb14f4 GIT binary patch literal 178 zcmX@j%ge<81kvhqGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!3e?ZY&rQ`&OiC%u zNvz7O(sxNM&MwI>(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDk#v; x%}+_qDb|mV&&`+jZc-Rl;=q%#ExgF>7o|$14Okf-}aDY5#6%9046TOS<6~aY`1I4 zSys$)rYdHi2NtOyyaO>?CQ$tpyOIHE4#xMZvZ9y07jTA{t=Y#Buqt;IoTETD}mk8!Yy%5h|* z$)lAZbe1X>>)a{ROJQix){q++XX+}16`Wx;Fu@9pHmTSKUkAslj}EoIno=*l8fXVconf@dWw{sV2^SeW>oKc-?X9sFG>vvk&$?EK3|HbF( z?nGYsf!kH=3+S;%LI9<95!TV6q4 z<#gPgQFlh!nH}A1FRI?D`gwg49ki}a_%+DS}2#!x54Rf>+)Tj{EA<7-r9e?T-! g#ZQ6bxNo5N859q|=mEI&OV+tLt~T)-uy-fGU-#=VC;$Ke literal 0 HcmV?d00001 diff --git a/app/models/transaction.py b/app/models/transaction.py new file mode 100644 index 0000000..78aaf4b --- /dev/null +++ b/app/models/transaction.py @@ -0,0 +1,16 @@ +from sqlalchemy import Column, Integer, String, BigInteger, DateTime +from sqlalchemy.sql import func +from app.db.base import Base + + +class Transaction(Base): + __tablename__ = "transactions" + + id = Column(Integer, primary_key=True, index=True) + order_id = Column(String, nullable=False) + payme_transaction_id = Column(String, unique=True) + amount = Column(BigInteger, nullable=False) + state = Column(Integer, default=0) + + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) \ No newline at end of file diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/payme.py b/app/schemas/payme.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schemas/transaction.py b/app/schemas/transaction.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/__pycache__/__init__.cpython-312.pyc b/app/services/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e69a348c7858e957860bbc91e14373d9f66a3ba8 GIT binary patch literal 180 zcmX@j%ge<81kvhqGC}lX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!3f9la&rQ`&OiC%u zNvz7O(sxNM&MwI>(DzF%R|qJ|&q_@$Db_DYtjtY~FV4s>$V{t@FHS8g%S;ApDk#v0 uvWxZO<1_OzOXB183Mzkb*yQE|%}cc_Vg=g42*kx8#z$sGM#ds$APWFTR4<|c literal 0 HcmV?d00001 diff --git a/app/services/__pycache__/payme_service.cpython-312.pyc b/app/services/__pycache__/payme_service.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dded6f4b8b3abe1e50d28e9e82ac1ba0c7b39525 GIT binary patch literal 3139 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 literal 0 HcmV?d00001 diff --git a/app/services/__pycache__/transaction_service.cpython-312.pyc b/app/services/__pycache__/transaction_service.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fcebcedbcaf380abe6387d332074daf099eb9a4 GIT binary patch literal 1522 zcmah|-D?zA6u)=Ac9Pj$Yt(FF8dxe~Mq&5CLIp*Ts;ve>>4rXp;xd`JYbMU@toP0; zt1Bc_Fh1#nkUsRmzA0(J$NmXIUm6;48RAnPO5X<7C!aj$?nl-b=z%%s+;h%7U%xwl z=5pf%=+`s9_|qyO|KMRjW?$(pgR)6H;?aPFbcs^bl|TvAB~|Kbp#83ZrZMX2Os{F4 z@labbX;i z??_K4inpAY4W|{ds_(f zZ_N=hyg&A7#8%%_Aus8Itta~JCgUxR9VSN24`RlXqrD;Gy#ZmMM6dZ=#J0k*F3$iH z*m1G={ziEv3R&4%_L_ln&%al`%*3rYYLpk)-QssVT4io5%DtpwC2IJ!R#h;5$9LgU zqfzebqC8r$Z!9%h$;?d_S4Vxzt?FRU{1Ehj9f`BR)=0<7JvsOI+_U+=t>PQ&)V6hM z*F5sZoZB|%UVOW4e%V!(tnsg5t+StQrn;i%yKs#pX2dvJaU%o5{1U&X zplp(Oh+dnvmA1Nv$!~P4fA4_?C?9}dTN|K5@g3kA_t5|$=|2F$k1}W#Vk--_#t%al zo<%kREYU^m#LU)vz~(B8mlm&7zyAITh|7uUcrd3Mg`poO8O~~)i4};Iz{*6}Rxgn? zvUh>=6p?ryi%htjL6uQx_;GMx8{!hMHL^P~+c`YlnLXJ#bZj?2^KMK(Vyw-lS)yki zU0uJraehZDbTs49we@Qc=TW)7etqNqjyBV=CjZhV`Dc)YPa``D%ta6egt`Rf2TA_em1ywK zAq+2xlfdAqPR*{(?`X&VZ~g^)JjsjOffKkZENqn`9&)UhOv2Ao3L}pNqBH_i5Az9h zx)Eo<>QKKPi$oPMmp@FR`t?|1C7XIldW%L>-u`8O#6@k(@2q}g#=`1z}pfOz3b@kd39*C)DYOpj}{@Vt-`5U=h_@%rpL IJjv=G0Dg~2p8x;= literal 0 HcmV?d00001 diff --git a/app/services/payme_checkout.py b/app/services/payme_checkout.py new file mode 100644 index 0000000..82a76fd --- /dev/null +++ b/app/services/payme_checkout.py @@ -0,0 +1,15 @@ +import base64 +import json +from app.core.config import settings + + +def generate_payme_checkout_url(order_id: int, amount: str): + data = { + "m": settings.PAYME_MERCHANT_ID, + "ac.order": str(order_id), + "a": int(float(amount) * 100), + } + + encoded = base64.b64encode(json.dumps(data).encode()).decode() + + return f"https://checkout.paycom.uz/{encoded}" \ No newline at end of file diff --git a/app/services/payme_service.py b/app/services/payme_service.py new file mode 100644 index 0000000..94edb9d --- /dev/null +++ b/app/services/payme_service.py @@ -0,0 +1,110 @@ +import time +from sqlalchemy.orm import Session + +from app.services.transaction_service import ( + get_transaction_by_payme_id, + create_transaction, + update_transaction_state, +) +from app.services.shopify_service import mark_order_paid, get_shopify_order +from app.core.constants import ( + STATE_CREATED, + STATE_COMPLETED, + STATE_CANCELLED, + ERROR_TRANSACTION_NOT_FOUND, +) + + +async def check_perform_transaction(params: dict) -> dict: + order_id = params["account"]["order"] + payme_amount = params["amount"] # tiyin + + order = await get_shopify_order(order_id) + + if not order: + return {"allow": False} + + # Draft orders use "total_price", completed orders too — same field name + shopify_amount = int(float(order["total_price"]) * 100) + + if shopify_amount != payme_amount: + return {"allow": False} + + # Draft: status field is "status" not "financial_status" + if order.get("_is_draft"): + already_paid = order.get("status") == "completed" + else: + already_paid = order.get("financial_status") == "paid" + + if already_paid: + return {"allow": False} + + return {"allow": True} + + +async def create_transaction_handler(db: Session, params: dict) -> dict: + payme_id = params["id"] + order_id = params["account"]["order"] + amount = params["amount"] + + existing = get_transaction_by_payme_id(db, payme_id) + if existing: + return { + "create_time": int(time.time() * 1000), + "transaction": payme_id, + "state": existing.state, + } + + create_transaction(db, order_id, payme_id, amount) + + return { + "create_time": int(time.time() * 1000), + "transaction": payme_id, + "state": STATE_CREATED, + } + + +async def perform_transaction_handler(db: Session, params: dict) -> dict | None: + payme_id = params["id"] + + transaction = get_transaction_by_payme_id(db, payme_id) + if not transaction: + return None + + if transaction.state == STATE_COMPLETED: + return { + "perform_time": int(time.time() * 1000), + "transaction": payme_id, + "state": STATE_COMPLETED, + } + + update_transaction_state(db, transaction, STATE_COMPLETED) + + # Fetch order to know if it's a draft + order = await get_shopify_order(transaction.order_id) + is_draft = order.get("_is_draft", False) if order else False + + amount_uzs = str(transaction.amount / 100) + await mark_order_paid(transaction.order_id, amount_uzs, is_draft=is_draft) + + return { + "perform_time": int(time.time() * 1000), + "transaction": payme_id, + "state": STATE_COMPLETED, + } + + +async def cancel_transaction_handler(db: Session, params: dict) -> dict | None: + payme_id = params["id"] + + transaction = get_transaction_by_payme_id(db, payme_id) + if not transaction: + return None + + update_transaction_state(db, transaction, STATE_CANCELLED) + + return { + "cancel_time": int(time.time() * 1000), + "transaction": payme_id, + "state": STATE_CANCELLED, + } \ No newline at end of file diff --git a/app/services/shopify_service.py b/app/services/shopify_service.py new file mode 100644 index 0000000..01d5efb --- /dev/null +++ b/app/services/shopify_service.py @@ -0,0 +1,63 @@ +import httpx +from app.core.config import settings + +SHOPIFY_API = f"https://{settings.SHOPIFY_STORE_URL}/admin/api/2024-01" +HEADERS = { + "X-Shopify-Access-Token": settings.SHOPIFY_ACCESS_TOKEN, + "Content-Type": "application/json", +} + + +async def get_shopify_order(order_id: str) -> dict | None: + """ + Tries regular orders first, falls back to draft orders. + Returns the order dict or None. + """ + async with httpx.AsyncClient() as client: + # 1. Try regular order + r = await client.get(f"{SHOPIFY_API}/orders/{order_id}.json", headers=HEADERS) + if r.status_code == 200: + order = r.json().get("order") + if order: + order["_is_draft"] = False + return order + + # 2. Try draft order + r = await client.get(f"{SHOPIFY_API}/draft_orders/{order_id}.json", headers=HEADERS) + if r.status_code == 200: + order = r.json().get("draft_order") + if order: + order["_is_draft"] = True + return order + + return None + + +async def mark_order_paid(order_id: str, amount: str, is_draft: bool = False) -> dict: + """ + For draft orders → complete the draft (converts it to a real order, marks paid). + For real orders → post a capture transaction. + """ + async with httpx.AsyncClient() as client: + if is_draft: + # Complete the draft order — this marks it as paid in Shopify + r = await client.post( + f"{SHOPIFY_API}/draft_orders/{order_id}/complete.json", + headers=HEADERS, + params={"payment_pending": False}, # False = mark as paid immediately + ) + return r.json() + else: + # Regular order — post a capture transaction + r = await client.post( + f"{SHOPIFY_API}/orders/{order_id}/transactions.json", + headers=HEADERS, + json={ + "transaction": { + "kind": "capture", + "status": "success", + "amount": amount, + } + }, + ) + return r.json() \ No newline at end of file diff --git a/app/services/transaction_service.py b/app/services/transaction_service.py new file mode 100644 index 0000000..0a74741 --- /dev/null +++ b/app/services/transaction_service.py @@ -0,0 +1,29 @@ +from sqlalchemy.orm import Session +from app.models.transaction import Transaction +from app.core.constants import * + + +def get_transaction_by_payme_id(db: Session, payme_id: str): + return db.query(Transaction).filter( + Transaction.payme_transaction_id == payme_id + ).first() + + +def create_transaction(db: Session, order_id: str, payme_id: str, amount: int): + transaction = Transaction( + order_id=order_id, + payme_transaction_id=payme_id, + amount=amount, + state=STATE_CREATED, + ) + db.add(transaction) + db.commit() + db.refresh(transaction) + return transaction + + +def update_transaction_state(db: Session, transaction: Transaction, state: int): + transaction.state = state + db.commit() + db.refresh(transaction) + return transaction \ No newline at end of file diff --git a/app/utils/__init__.py b/app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/get_token.py b/app/utils/get_token.py new file mode 100644 index 0000000..9b55d0e --- /dev/null +++ b/app/utils/get_token.py @@ -0,0 +1,30 @@ +import webbrowser +import httpx +from urllib.parse import urlparse, parse_qs + +CLIENT_ID = "5811038f7c170b60b93fca3727545e6f" +CLIENT_SECRET = input("Paste your Client Secret: ").strip() +STORE = "cx0du9-sq.myshopify.com" + +auth_url = ( + f"https://{STORE}/admin/oauth/authorize" + f"?client_id={CLIENT_ID}" + f"&scope=read_orders,write_orders,read_draft_orders,write_draft_orders,read_customers" + f"&redirect_uri=https://example.com" + f"&state=abc123" +) + +print("\nOpening browser — click Allow...") +webbrowser.open(auth_url) + +redirect_url = input("\nPaste the full redirect URL: ").strip() +code = parse_qs(urlparse(redirect_url).query)["code"][0] + +response = httpx.post( + f"https://{STORE}/admin/oauth/access_token", + json={"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET, "code": code}, +) + +data = response.json() +print("\n✅ Update your .env with this:") +print(f"SHOPIFY_ACCESS_TOKEN={data['access_token']}") \ No newline at end of file diff --git a/app/utils/helpers.py b/app/utils/helpers.py new file mode 100644 index 0000000..e69de29 diff --git a/app/utils/logger.py b/app/utils/logger.py new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b65040b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: "3.9" + +services: + db: + image: postgres:15 + container_name: payme_db + restart: always + environment: + POSTGRES_USER: payme_user + POSTGRES_PASSWORD: payme_pass + POSTGRES_DB: payme_db + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + + app: + build: . + container_name: payme_app + restart: always + ports: + - "8000:8000" + env_file: + - .env + depends_on: + - db + +volumes: + postgres_data: \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b98f2cd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +fastapi +uvicorn[standard] +sqlalchemy +alembic +psycopg2-binary +httpx +pydantic-settings +python-dotenv +requests \ No newline at end of file diff --git a/testing.py b/testing.py new file mode 100644 index 0000000..6221d52 --- /dev/null +++ b/testing.py @@ -0,0 +1,31 @@ +import requests + +SHOP = "cx0du9-sq.myshopify.com" +TOKEN = "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" # sizda bor token +VERSION = "2026-01" + + +def mark_order_paid(order_id, amount): + url = f"https://{SHOP}/admin/api/{VERSION}/orders/{order_id}/transactions.json" + + payload = { + "transaction": { + "kind": "sale", + "source": "external", + "amount": amount + } + } + + r = requests.post( + url, + headers={ + "X-Shopify-Access-Token": TOKEN, + "Content-Type": "application/json" + }, + json=payload + ) + + print(r.status_code, r.text) + + +mark_order_paid("1254611222844", 600000) \ No newline at end of file diff --git a/venv/bin/Activate.ps1 b/venv/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/venv/bin/activate b/venv/bin/activate new file mode 100644 index 0000000..fc8e116 --- /dev/null +++ b/venv/bin/activate @@ -0,0 +1,70 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "${OSTYPE:-}" = "cygwin" ] || [ "${OSTYPE:-}" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath '/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv') +else + # use the path as-is + export VIRTUAL_ENV='/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv' +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/"bin":$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1='(venv) '"${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT='(venv) ' + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/venv/bin/activate.csh b/venv/bin/activate.csh new file mode 100644 index 0000000..d705315 --- /dev/null +++ b/venv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV '/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv' + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/"bin":$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = '(venv) '"$prompt" + setenv VIRTUAL_ENV_PROMPT '(venv) ' +endif + +alias pydoc python -m pydoc + +rehash diff --git a/venv/bin/activate.fish b/venv/bin/activate.fish new file mode 100644 index 0000000..7f0968d --- /dev/null +++ b/venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV '/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv' + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/"bin $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT '(venv) ' +end diff --git a/venv/bin/alembic b/venv/bin/alembic new file mode 100755 index 0000000..9d089ff --- /dev/null +++ b/venv/bin/alembic @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from alembic.config import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/dotenv b/venv/bin/dotenv new file mode 100755 index 0000000..f04bad0 --- /dev/null +++ b/venv/bin/dotenv @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from dotenv.__main__ import cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli()) diff --git a/venv/bin/fastapi b/venv/bin/fastapi new file mode 100755 index 0000000..d5a1a4b --- /dev/null +++ b/venv/bin/fastapi @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from fastapi.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/httpx b/venv/bin/httpx new file mode 100755 index 0000000..e31f036 --- /dev/null +++ b/venv/bin/httpx @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from httpx import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/mako-render b/venv/bin/mako-render new file mode 100755 index 0000000..1a023cc --- /dev/null +++ b/venv/bin/mako-render @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from mako.cmd import cmdline +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cmdline()) diff --git a/venv/bin/normalizer b/venv/bin/normalizer new file mode 100755 index 0000000..88dc42c --- /dev/null +++ b/venv/bin/normalizer @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/venv/bin/pip b/venv/bin/pip new file mode 100755 index 0000000..e796855 --- /dev/null +++ b/venv/bin/pip @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3 b/venv/bin/pip3 new file mode 100755 index 0000000..e796855 --- /dev/null +++ b/venv/bin/pip3 @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/pip3.12 b/venv/bin/pip3.12 new file mode 100755 index 0000000..e796855 --- /dev/null +++ b/venv/bin/pip3.12 @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/bin/python b/venv/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/python3 b/venv/bin/python3 new file mode 120000 index 0000000..ae65fda --- /dev/null +++ b/venv/bin/python3 @@ -0,0 +1 @@ +/usr/bin/python3 \ No newline at end of file diff --git a/venv/bin/python3.12 b/venv/bin/python3.12 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/venv/bin/python3.12 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/venv/bin/uvicorn b/venv/bin/uvicorn new file mode 100755 index 0000000..8f13463 --- /dev/null +++ b/venv/bin/uvicorn @@ -0,0 +1,10 @@ +#!/bin/sh +'''exec' "/home/abdulaziz/Desktop/New Projects/payme_shopify_service/venv/bin/python3" "$0" "$@" +' ''' +# -*- coding: utf-8 -*- +import re +import sys +from uvicorn.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/venv/include/site/python3.12/greenlet/greenlet.h b/venv/include/site/python3.12/greenlet/greenlet.h new file mode 100644 index 0000000..d02a16e --- /dev/null +++ b/venv/include/site/python3.12/greenlet/greenlet.h @@ -0,0 +1,164 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */ + +/* Greenlet object interface */ + +#ifndef Py_GREENLETOBJECT_H +#define Py_GREENLETOBJECT_H + + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* This is deprecated and undocumented. It does not change. */ +#define GREENLET_VERSION "1.0.0" + +#ifndef GREENLET_MODULE +#define implementation_ptr_t void* +#endif + +typedef struct _greenlet { + PyObject_HEAD + PyObject* weakreflist; + PyObject* dict; + implementation_ptr_t pimpl; +} PyGreenlet; + +#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type)) + + +/* C API functions */ + +/* Total number of symbols that are exported */ +#define PyGreenlet_API_pointers 12 + +#define PyGreenlet_Type_NUM 0 +#define PyExc_GreenletError_NUM 1 +#define PyExc_GreenletExit_NUM 2 + +#define PyGreenlet_New_NUM 3 +#define PyGreenlet_GetCurrent_NUM 4 +#define PyGreenlet_Throw_NUM 5 +#define PyGreenlet_Switch_NUM 6 +#define PyGreenlet_SetParent_NUM 7 + +#define PyGreenlet_MAIN_NUM 8 +#define PyGreenlet_STARTED_NUM 9 +#define PyGreenlet_ACTIVE_NUM 10 +#define PyGreenlet_GET_PARENT_NUM 11 + +#ifndef GREENLET_MODULE +/* This section is used by modules that uses the greenlet C API */ +static void** _PyGreenlet_API = NULL; + +# define PyGreenlet_Type \ + (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM]) + +# define PyExc_GreenletError \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM]) + +# define PyExc_GreenletExit \ + ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM]) + +/* + * PyGreenlet_New(PyObject *args) + * + * greenlet.greenlet(run, parent=None) + */ +# define PyGreenlet_New \ + (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \ + _PyGreenlet_API[PyGreenlet_New_NUM]) + +/* + * PyGreenlet_GetCurrent(void) + * + * greenlet.getcurrent() + */ +# define PyGreenlet_GetCurrent \ + (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM]) + +/* + * PyGreenlet_Throw( + * PyGreenlet *greenlet, + * PyObject *typ, + * PyObject *val, + * PyObject *tb) + * + * g.throw(...) + */ +# define PyGreenlet_Throw \ + (*(PyObject * (*)(PyGreenlet * self, \ + PyObject * typ, \ + PyObject * val, \ + PyObject * tb)) \ + _PyGreenlet_API[PyGreenlet_Throw_NUM]) + +/* + * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args) + * + * g.switch(*args, **kwargs) + */ +# define PyGreenlet_Switch \ + (*(PyObject * \ + (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \ + _PyGreenlet_API[PyGreenlet_Switch_NUM]) + +/* + * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent) + * + * g.parent = new_parent + */ +# define PyGreenlet_SetParent \ + (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \ + _PyGreenlet_API[PyGreenlet_SetParent_NUM]) + +/* + * PyGreenlet_GetParent(PyObject* greenlet) + * + * return greenlet.parent; + * + * This could return NULL even if there is no exception active. + * If it does not return NULL, you are responsible for decrementing the + * reference count. + */ +# define PyGreenlet_GetParent \ + (*(PyGreenlet* (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM]) + +/* + * deprecated, undocumented alias. + */ +# define PyGreenlet_GET_PARENT PyGreenlet_GetParent + +# define PyGreenlet_MAIN \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_MAIN_NUM]) + +# define PyGreenlet_STARTED \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_STARTED_NUM]) + +# define PyGreenlet_ACTIVE \ + (*(int (*)(PyGreenlet*)) \ + _PyGreenlet_API[PyGreenlet_ACTIVE_NUM]) + + + + +/* Macro that imports greenlet and initializes C API */ +/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we + keep the older definition to be sure older code that might have a copy of + the header still works. */ +# define PyGreenlet_Import() \ + { \ + _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \ + } + +#endif /* GREENLET_MODULE */ + +#ifdef __cplusplus +} +#endif +#endif /* !Py_GREENLETOBJECT_H */ diff --git a/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc b/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f867aec8f95b74182b1ebe14ba206ebc8b835d2 GIT binary patch literal 163777 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

N%Ettuz3g%W#Y`BvPWz1vaRWoIe1#LTJ#!Bq#w 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 +License-Expression: MIT +Project-URL: Homepage, https://alembic.sqlalchemy.org +Project-URL: Documentation, https://alembic.sqlalchemy.org/en/latest/ +Project-URL: Changelog, https://alembic.sqlalchemy.org/en/latest/changelog.html +Project-URL: Source, https://github.com/sqlalchemy/alembic/ +Project-URL: Issue Tracker, https://github.com/sqlalchemy/alembic/issues/ +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Environment :: Console +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Database :: Front-Ends +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: SQLAlchemy>=1.4.23 +Requires-Dist: Mako +Requires-Dist: typing-extensions>=4.12 +Requires-Dist: tomli; python_version < "3.11" +Provides-Extra: tz +Requires-Dist: tzdata; extra == "tz" +Dynamic: license-file + +Alembic is a database migrations tool written by the author +of `SQLAlchemy `_. A migrations tool +offers the following functionality: + +* Can emit ALTER statements to a database in order to change + the structure of tables and other constructs +* Provides a system whereby "migration scripts" may be constructed; + each script indicates a particular series of steps that can "upgrade" a + target database to a new version, and optionally a series of steps that can + "downgrade" similarly, doing the same steps in reverse. +* Allows the scripts to execute in some sequential manner. + +The goals of Alembic are: + +* Very open ended and transparent configuration and operation. A new + Alembic environment is generated from a set of templates which is selected + among a set of options when setup first occurs. The templates then deposit a + series of scripts that define fully how database connectivity is established + and how migration scripts are invoked; the migration scripts themselves are + generated from a template within that series of scripts. The scripts can + then be further customized to define exactly how databases will be + interacted with and what structure new migration files should take. +* Full support for transactional DDL. The default scripts ensure that all + migrations occur within a transaction - for those databases which support + this (Postgresql, Microsoft SQL Server), migrations can be tested with no + need to manually undo changes upon failure. +* Minimalist script construction. Basic operations like renaming + tables/columns, adding/removing columns, changing column attributes can be + performed through one line commands like alter_column(), rename_table(), + add_constraint(). There is no need to recreate full SQLAlchemy Table + structures for simple operations like these - the functions themselves + generate minimalist schema structures behind the scenes to achieve the given + DDL sequence. +* "auto generation" of migrations. While real world migrations are far more + complex than what can be automatically determined, Alembic can still + eliminate the initial grunt work in generating new migration directives + from an altered schema. The ``--autogenerate`` feature will inspect the + current status of a database using SQLAlchemy's schema inspection + capabilities, compare it to the current state of the database model as + specified in Python, and generate a series of "candidate" migrations, + rendering them into a new migration script as Python directives. The + developer then edits the new file, adding additional directives and data + migrations as needed, to produce a finished migration. Table and column + level changes can be detected, with constraints and indexes to follow as + well. +* Full support for migrations generated as SQL scripts. Those of us who + work in corporate environments know that direct access to DDL commands on a + production database is a rare privilege, and DBAs want textual SQL scripts. + Alembic's usage model and commands are oriented towards being able to run a + series of migrations into a textual output file as easily as it runs them + directly to a database. Care must be taken in this mode to not invoke other + operations that rely upon in-memory SELECTs of rows - Alembic tries to + provide helper constructs like bulk_insert() to help with data-oriented + operations that are compatible with script-based DDL. +* Non-linear, dependency-graph versioning. Scripts are given UUID + identifiers similarly to a DVCS, and the linkage of one script to the next + is achieved via human-editable markers within the scripts themselves. + The structure of a set of migration files is considered as a + directed-acyclic graph, meaning any migration file can be dependent + on any other arbitrary set of migration files, or none at + all. Through this open-ended system, migration files can be organized + into branches, multiple roots, and mergepoints, without restriction. + Commands are provided to produce new branches, roots, and merges of + branches automatically. +* Provide a library of ALTER constructs that can be used by any SQLAlchemy + application. The DDL constructs build upon SQLAlchemy's own DDLElement base + and can be used standalone by any application or script. +* At long last, bring SQLite and its inability to ALTER things into the fold, + but in such a way that SQLite's very special workflow needs are accommodated + in an explicit way that makes the most of a bad situation, through the + concept of a "batch" migration, where multiple changes to a table can + be batched together to form a series of instructions for a single, subsequent + "move-and-copy" workflow. You can even use "move-and-copy" workflow for + other databases, if you want to recreate a table in the background + on a busy system. + +Documentation and status of Alembic is at https://alembic.sqlalchemy.org/ + +The SQLAlchemy Project +====================== + +Alembic is part of the `SQLAlchemy Project `_ and +adheres to the same standards and conventions as the core project. + +Development / Bug reporting / Pull requests +___________________________________________ + +Please refer to the +`SQLAlchemy Community Guide `_ for +guidelines on coding and participating in this project. + +Code of Conduct +_______________ + +Above all, SQLAlchemy places great emphasis on polite, thoughtful, and +constructive communication between users and developers. +Please see our current Code of Conduct at +`Code of Conduct `_. + +License +======= + +Alembic is distributed under the `MIT license +`_. diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/RECORD b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/RECORD new file mode 100644 index 0000000..a3a7a97 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/RECORD @@ -0,0 +1,179 @@ +../../../bin/alembic,sha256=MG45ZJz53mc5fAtG6DLoV11myuejaCGpb7KPIUfRLXk,307 +alembic-1.18.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +alembic-1.18.4.dist-info/METADATA,sha256=sPH3Zq5eEaNtbnI1os9Rvk7eBbFJSMPq13poNNaxvfs,7217 +alembic-1.18.4.dist-info/RECORD,, +alembic-1.18.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic-1.18.4.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91 +alembic-1.18.4.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48 +alembic-1.18.4.dist-info/licenses/LICENSE,sha256=bmjZSgOg4-Mn3fPobR6-3BTuzjkiAiYY_CRqNilv0Mw,1059 +alembic-1.18.4.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8 +alembic/__init__.py,sha256=6ppwNUS6dfdFIm5uwZaaZ9lDZ7pIwkTNyQcbjY47V3I,93 +alembic/__main__.py,sha256=373m7-TBh72JqrSMYviGrxCHZo-cnweM8AGF8A22PmY,78 +alembic/__pycache__/__init__.cpython-312.pyc,, +alembic/__pycache__/__main__.cpython-312.pyc,, +alembic/__pycache__/command.cpython-312.pyc,, +alembic/__pycache__/config.cpython-312.pyc,, +alembic/__pycache__/context.cpython-312.pyc,, +alembic/__pycache__/environment.cpython-312.pyc,, +alembic/__pycache__/migration.cpython-312.pyc,, +alembic/__pycache__/op.cpython-312.pyc,, +alembic/autogenerate/__init__.py,sha256=ntmUTXhjLm4_zmqIwyVaECdpPDn6_u1yM9vYk6-553E,543 +alembic/autogenerate/__pycache__/__init__.cpython-312.pyc,, +alembic/autogenerate/__pycache__/api.cpython-312.pyc,, +alembic/autogenerate/__pycache__/render.cpython-312.pyc,, +alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc,, +alembic/autogenerate/api.py,sha256=8tVNDSHlqsBgj1IVLdqvZr_jlvz9kp3O5EKIL9biaZg,22781 +alembic/autogenerate/compare/__init__.py,sha256=kCvA0ZK0rTahNv9wlgyIB5DH2lFEhTRO4PFmoqcL9JE,1809 +alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/constraints.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc,, +alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc,, +alembic/autogenerate/compare/comments.py,sha256=agSrWsZhJ47i-E-EqiP3id2CXTTbP0muOKk1-9in9lg,3234 +alembic/autogenerate/compare/constraints.py,sha256=7sLSvUK9M2CbMRRQy5pveIXbjDLRDnfPx0Dvi_KXOf8,27906 +alembic/autogenerate/compare/schema.py,sha256=plQ7JJ1zJGlnajweSV8lAD9tDYPks5G40sliocTuXJA,1695 +alembic/autogenerate/compare/server_defaults.py,sha256=D--5EvEfyX0fSVkK6iLtRoer5sYK6xeNC2TIdu7klUk,10792 +alembic/autogenerate/compare/tables.py,sha256=47pAgVhbmXGLrm3dMK6hrNABxOAe_cGSQmPtCBwORVc,10611 +alembic/autogenerate/compare/types.py,sha256=75bOduz-dOiyLI065XD5sEP_JF9GPLkDAQ_y5B8lXF0,4005 +alembic/autogenerate/compare/util.py,sha256=K_GArJ2xQXZi6ftb8gkgZuIdVqvyep3E2ZXq8F3-jIU,9521 +alembic/autogenerate/render.py,sha256=ceQL8nk8m2kBtQq5gtxtDLR9iR0Sck8xG_61Oez-Sqs,37270 +alembic/autogenerate/rewriter.py,sha256=NIASSS-KaNKPmbm1k4pE45aawwjSh1Acf6eZrOwnUGM,7814 +alembic/command.py,sha256=7RzAwwXR31sOl0oVItyZl9B0j3TeR5dRyx9634lVsLM,25297 +alembic/config.py,sha256=VoCZV2cFZoF0Xa1OxHqsA-MKzuwBRaJSC7hxZ3-uWN4,34983 +alembic/context.py,sha256=hK1AJOQXJ29Bhn276GYcosxeG7pC5aZRT5E8c4bMJ4Q,195 +alembic/context.pyi,sha256=b_naI_W8dyiZRsL_n299a-LbqLZxKTAgDIXubRLVKlY,32531 +alembic/ddl/__init__.py,sha256=Df8fy4Vn_abP8B7q3x8gyFwEwnLw6hs2Ljt_bV3EZWE,152 +alembic/ddl/__pycache__/__init__.cpython-312.pyc,, +alembic/ddl/__pycache__/_autogen.cpython-312.pyc,, +alembic/ddl/__pycache__/base.cpython-312.pyc,, +alembic/ddl/__pycache__/impl.cpython-312.pyc,, +alembic/ddl/__pycache__/mssql.cpython-312.pyc,, +alembic/ddl/__pycache__/mysql.cpython-312.pyc,, +alembic/ddl/__pycache__/oracle.cpython-312.pyc,, +alembic/ddl/__pycache__/postgresql.cpython-312.pyc,, +alembic/ddl/__pycache__/sqlite.cpython-312.pyc,, +alembic/ddl/_autogen.py,sha256=Blv2RrHNyF4cE6znCQXNXG5T9aO-YmiwD4Fz-qfoaWA,9275 +alembic/ddl/base.py,sha256=dNhLIZnFMP7Cr8rE_e2Zb5skGgCMBOdca1PajXqZYhs,11977 +alembic/ddl/impl.py,sha256=IU3yHFVI3v0QHEwNL_LSN1PRpPF0n09NFFqRZkW86wE,31376 +alembic/ddl/mssql.py,sha256=dee0acwnxmTZXuYPqqlYaDiSbKS46zVH0WRULjX5Blg,17398 +alembic/ddl/mysql.py,sha256=2fvzGcdg4qqCJogGnzvQN636vUi9mF6IoQWLGevvF_A,18456 +alembic/ddl/oracle.py,sha256=669YlkcZihlXFbnXhH2krdrvDry8q5pcUGfoqkg_R6Y,6243 +alembic/ddl/postgresql.py,sha256=04M4OpZOCJJ3ipuHoVwlR1gI1sgRwOguRRVx_mFg8Uc,30417 +alembic/ddl/sqlite.py,sha256=TmzU3YaR3aw_0spSrA6kcUY8fyDfwsu4GkH5deYPEK8,8017 +alembic/environment.py,sha256=MM5lPayGT04H3aeng1H7GQ8HEAs3VGX5yy6mDLCPLT4,43 +alembic/migration.py,sha256=MV6Fju6rZtn2fTREKzXrCZM6aIBGII4OMZFix0X-GLs,41 +alembic/op.py,sha256=flHtcsVqOD-ZgZKK2pv-CJ5Cwh-KJ7puMUNXzishxLw,167 +alembic/op.pyi,sha256=ABBlNk4Eg7DR17knSKIjmvHQBNAmKh3aHQNHU8Oyw08,53347 +alembic/operations/__init__.py,sha256=e0KQSZAgLpTWvyvreB7DWg7RJV_MWSOPVDgCqsd2FzY,318 +alembic/operations/__pycache__/__init__.cpython-312.pyc,, +alembic/operations/__pycache__/base.cpython-312.pyc,, +alembic/operations/__pycache__/batch.cpython-312.pyc,, +alembic/operations/__pycache__/ops.cpython-312.pyc,, +alembic/operations/__pycache__/schemaobj.cpython-312.pyc,, +alembic/operations/__pycache__/toimpl.cpython-312.pyc,, +alembic/operations/base.py,sha256=ubpv1HDol0g0nuLi0b8-uN7-HEVRZ6mq8arvK9EGo0g,78432 +alembic/operations/batch.py,sha256=hYOpzG2FK_8hk-rHNuLuFAA3-VXRSOnsTrpz2YlA61Q,26947 +alembic/operations/ops.py,sha256=ofbHkReZkZX2n9lXDaIPlrKe2U1mwgQpZNhEbuC4QrM,99325 +alembic/operations/schemaobj.py,sha256=Wp-bBe4a8lXPTvIHJttBY0ejtpVR5Jvtb2kI-U2PztQ,9468 +alembic/operations/toimpl.py,sha256=f8rH3jdob9XvEJr6CoWEkX6X1zgNB5qxdcEQugyhBvU,8466 +alembic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/runtime/__pycache__/__init__.cpython-312.pyc,, +alembic/runtime/__pycache__/environment.cpython-312.pyc,, +alembic/runtime/__pycache__/migration.cpython-312.pyc,, +alembic/runtime/__pycache__/plugins.cpython-312.pyc,, +alembic/runtime/environment.py,sha256=1cR1v18sIKvOPZMlc4fHGU4J8r6Dec9h4o3WXkMmFKQ,42400 +alembic/runtime/migration.py,sha256=mR2Ee1h9Yy6OMFeDL4LOYorLYby2l2f899WGK_boECw,48427 +alembic/runtime/plugins.py,sha256=pWCDhMX8MvR8scXhiGSRNYNW7-ckEbOW2qK58xRFy1Q,5707 +alembic/script/__init__.py,sha256=lSj06O391Iy5avWAiq8SPs6N8RBgxkSPjP8wpXcNDGg,100 +alembic/script/__pycache__/__init__.cpython-312.pyc,, +alembic/script/__pycache__/base.cpython-312.pyc,, +alembic/script/__pycache__/revision.cpython-312.pyc,, +alembic/script/__pycache__/write_hooks.cpython-312.pyc,, +alembic/script/base.py,sha256=OInSjbfcnUSjVCc5vVYY33UJ1Uo5xE5Huicp8P9VM1I,36698 +alembic/script/revision.py,sha256=SEePZPTMIyfjF73QAD0VIax9jc1dALkiLQZwTzwiyPw,62312 +alembic/script/write_hooks.py,sha256=KWH12250h_JcdBkGsLVo9JKYKpNcJxBUjwZ9r_r88Bc,5369 +alembic/templates/async/README,sha256=ISVtAOvqvKk_5ThM5ioJE-lMkvf9IbknFUFVU_vPma4,58 +alembic/templates/async/__pycache__/env.cpython-312.pyc,, +alembic/templates/async/alembic.ini.mako,sha256=esbuCnpkyjntJC7k9NnYcCAzhrRQ8NVC4pWineiRk_w,5010 +alembic/templates/async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389 +alembic/templates/async/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704 +alembic/templates/generic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38 +alembic/templates/generic/__pycache__/env.cpython-312.pyc,, +alembic/templates/generic/alembic.ini.mako,sha256=2i2vPsGQSmE9XMiLz8tSBF_UIA8PJl0-fAvbRVmiK_w,5010 +alembic/templates/generic/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103 +alembic/templates/generic/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704 +alembic/templates/multidb/README,sha256=dWLDhnBgphA4Nzb7sNlMfCS3_06YqVbHhz-9O5JNqyI,606 +alembic/templates/multidb/__pycache__/env.cpython-312.pyc,, +alembic/templates/multidb/alembic.ini.mako,sha256=asVt3aJVwjuuw9bopfMofVvonO31coXBbV5DeMRN6cM,5336 +alembic/templates/multidb/env.py,sha256=6zNjnW8mXGUk7erTsAvrfhvqoczJ-gagjVq1Ypg2YIQ,4230 +alembic/templates/multidb/script.py.mako,sha256=ZbCXMkI5Wj2dwNKcxuVGkKZ7Iav93BNx_bM4zbGi3c8,1235 +alembic/templates/pyproject/README,sha256=dMhIiFoeM7EdeaOXBs3mVQ6zXACMyGXDb_UBB6sGRA0,60 +alembic/templates/pyproject/__pycache__/env.cpython-312.pyc,, +alembic/templates/pyproject/alembic.ini.mako,sha256=bQnEoydnLOUgg9vNbTOys4r5MaW8lmwYFXSrlfdEEkw,782 +alembic/templates/pyproject/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103 +alembic/templates/pyproject/pyproject.toml.mako,sha256=W6x_K-xLfEvyM8D4B3Fg0l20P1h6SPK33188pqRFroQ,3000 +alembic/templates/pyproject/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704 +alembic/templates/pyproject_async/README,sha256=2Q5XcEouiqQ-TJssO9805LROkVUd0F6d74rTnuLrifA,45 +alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc,, +alembic/templates/pyproject_async/alembic.ini.mako,sha256=bQnEoydnLOUgg9vNbTOys4r5MaW8lmwYFXSrlfdEEkw,782 +alembic/templates/pyproject_async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389 +alembic/templates/pyproject_async/pyproject.toml.mako,sha256=W6x_K-xLfEvyM8D4B3Fg0l20P1h6SPK33188pqRFroQ,3000 +alembic/templates/pyproject_async/script.py.mako,sha256=04kgeBtNMa4cCnG8CfQcKt6P6rnloIfj8wy0u_DBydM,704 +alembic/testing/__init__.py,sha256=PTMhi_2PZ1T_3atQS2CIr0V4YRZzx_doKI-DxKdQS44,1297 +alembic/testing/__pycache__/__init__.cpython-312.pyc,, +alembic/testing/__pycache__/assertions.cpython-312.pyc,, +alembic/testing/__pycache__/env.cpython-312.pyc,, +alembic/testing/__pycache__/fixtures.cpython-312.pyc,, +alembic/testing/__pycache__/requirements.cpython-312.pyc,, +alembic/testing/__pycache__/schemacompare.cpython-312.pyc,, +alembic/testing/__pycache__/util.cpython-312.pyc,, +alembic/testing/__pycache__/warnings.cpython-312.pyc,, +alembic/testing/assertions.py,sha256=VKXMEVWjuPAsYnNxP3WnUpXaFN3ytNFf9LI72OEJ074,5344 +alembic/testing/env.py,sha256=oQN56xXHtHfK8RD-8pH8yZ-uWcjpuNL1Mt5HNrzZyc0,12151 +alembic/testing/fixtures.py,sha256=meqm10rd1ynppW6tw1wcpDJJLyQezZ7FwKyqcrwIOok,11931 +alembic/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc,, +alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc,, +alembic/testing/plugin/bootstrap.py,sha256=9C6wtjGrIVztZ928w27hsQE0KcjDLIUtUN3dvZKsMVk,50 +alembic/testing/requirements.py,sha256=OZSHd8I3zOb7288cZxUTebqxx8j0T6I8MekH15TyPvY,4566 +alembic/testing/schemacompare.py,sha256=N5UqSNCOJetIKC4vKhpYzQEpj08XkdgIoqBmEPQ3tlc,4838 +alembic/testing/suite/__init__.py,sha256=MvE7-hwbaVN1q3NM-ztGxORU9dnIelUCINKqNxewn7Y,288 +alembic/testing/suite/__pycache__/__init__.cpython-312.pyc,, +alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_autogen_identity.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc,, +alembic/testing/suite/__pycache__/test_op.cpython-312.pyc,, +alembic/testing/suite/_autogen_fixtures.py,sha256=3nNTd8iDeVeSgpPIj8KAraNbU-PkJtxDb4X_TVsZ528,14200 +alembic/testing/suite/test_autogen_comments.py,sha256=aEGqKUDw4kHjnDk298aoGcQvXJWmZXcIX_2FxH4cJK8,6283 +alembic/testing/suite/test_autogen_computed.py,sha256=puJ0hBtLzNz8LiPGqDPS8vse6dUS9VCBpUdw-cOksZo,4554 +alembic/testing/suite/test_autogen_diffs.py,sha256=T4SR1n_kmcOKYhR4W1-dA0e5sddJ69DSVL2HW96kAkE,8394 +alembic/testing/suite/test_autogen_fks.py,sha256=wHKjD4Egf7IZlH0HYw-c8uti0jhJpOm5K42QMXf5tIw,32930 +alembic/testing/suite/test_autogen_identity.py,sha256=kcuqngG7qXAKPJDX4U8sRzPKHEJECHuZ0DtuaS6tVkk,5824 +alembic/testing/suite/test_environment.py,sha256=OwD-kpESdLoc4byBrGrXbZHvqtPbzhFCG4W9hJOJXPQ,11877 +alembic/testing/suite/test_op.py,sha256=2XQCdm_NmnPxHGuGj7hmxMzIhKxXNotUsKdACXzE1mM,1343 +alembic/testing/util.py,sha256=CQrcQDA8fs_7ME85z5ydb-Bt70soIIID-qNY1vbR2dg,3350 +alembic/testing/warnings.py,sha256=cDDWzvxNZE6x9dME2ACTXSv01G81JcIbE1GIE_s1kvg,831 +alembic/util/__init__.py,sha256=xNpZtajyTF4eVEbLj0Pcm2FbNkIZD_pCvKGKSPucTEs,1777 +alembic/util/__pycache__/__init__.cpython-312.pyc,, +alembic/util/__pycache__/compat.cpython-312.pyc,, +alembic/util/__pycache__/editor.cpython-312.pyc,, +alembic/util/__pycache__/exc.cpython-312.pyc,, +alembic/util/__pycache__/langhelpers.cpython-312.pyc,, +alembic/util/__pycache__/messaging.cpython-312.pyc,, +alembic/util/__pycache__/pyfiles.cpython-312.pyc,, +alembic/util/__pycache__/sqla_compat.cpython-312.pyc,, +alembic/util/compat.py,sha256=NytmcsMtK8WEEVwWc-ZWYlSOi55BtRlmJXjxnF3nsh8,3810 +alembic/util/editor.py,sha256=JIz6_BdgV8_oKtnheR6DZoB7qnrHrlRgWjx09AsTsUw,2546 +alembic/util/exc.py,sha256=SublpLmAeAW8JeEml-1YyhIjkSORTkZbvHVVJeoPymg,993 +alembic/util/langhelpers.py,sha256=GBbR01xNi1kmz8W37h0NzXl3hBC1SY7k7Bj-h5jVgps,13164 +alembic/util/messaging.py,sha256=3bEBoDy4EAXETXAvArlYjeMITXDTgPTu6ZoE3ytnzSw,3294 +alembic/util/pyfiles.py,sha256=QUZYc5kE3Z7nV64PblcRffzA7VfVaiFB2x3vtcG0_AE,4707 +alembic/util/sqla_compat.py,sha256=llgJVtOsO1c3euS9_peORZkM9QeSvQWa-1LNHqrzEM4,15246 diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/REQUESTED b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/WHEEL b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/WHEEL new file mode 100644 index 0000000..1ef5583 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/entry_points.txt new file mode 100644 index 0000000..5945268 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +alembic = alembic.config:main diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..b03e235 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright 2009-2026 Michael Bayer. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/top_level.txt new file mode 100644 index 0000000..b5bd98d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic-1.18.4.dist-info/top_level.txt @@ -0,0 +1 @@ +alembic diff --git a/venv/lib/python3.12/site-packages/alembic/__init__.py b/venv/lib/python3.12/site-packages/alembic/__init__.py new file mode 100644 index 0000000..059b6b1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/__init__.py @@ -0,0 +1,6 @@ +from . import context +from . import op +from .runtime import plugins + + +__version__ = "1.18.4" diff --git a/venv/lib/python3.12/site-packages/alembic/__main__.py b/venv/lib/python3.12/site-packages/alembic/__main__.py new file mode 100644 index 0000000..af1b8e8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/__main__.py @@ -0,0 +1,4 @@ +from .config import main + +if __name__ == "__main__": + main(prog="alembic") diff --git a/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53836586b55d6098d0980caeaba3aaef083a1884 GIT binary patch literal 342 zcmXw#y-ve05XbE#O%bJnrD9=VLy;g>fq?-rF;~oykd-D!YZ`yZb_40gQ?M|w@+`bT zrA|z2mAZA}g5ZY#{qA(9`}pklcM012$(y)G|J4Tn7W~2H6ycSWq@)#*G^4cT06EBl zUk@5^VBrkoolIa i7~I%BT%f(o4N%r0rF2CGD{`dbtx~s6*rL7R?w;=By}P%5zlWSXA3uqai$5N0jck%t?2<>6p$wNu;#-Un1X)m= zAY^(00-TZ8aDo53F={$ZXW0Gqc!=9ja#6JrV?Hw0?(h<1B2r({M&+X+X}hVa#v(VK z(6?4x0%I>|X z&?!_?T8f0~rk<$kB0f8(R@88+d0OxsEZxYVTY<4|*3qR@jts8Mo)h2?v4_qr3C8#f a4Zg|leD+TEKf`D)KElD0pzy%>KmGyNIbMta literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/__pycache__/command.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b7f82be603433a6b5e3a8c392be2aadf88aca86f GIT binary patch literal 30104 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

Wdo4W{n?22(~c5+YnyaFXA8zH2O4 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 Hw*AyUyf1I3 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bcf12f01237b3549737730fc0045670ec020f837 GIT binary patch literal 253 zcmX@j%ge<81Pj#XWNHKH#~=<2FhLog`GAb+3@HpLj5!QznHU)=nKYSSG6DrP8E-La z`2k7cqSCyQ%-mGH)V#9HqWrwv)Vz|F44**;{3_PZ$j?pHPfSWF%}K1vtkQQ$EzT~< zFVOc(EmsIA%FjwoE-BV8NUY3FjW5o~FUU-*j4w_tD$7hx)h`2@r=OFVq+d{3l98Wh ztY>JXUz}Nzs#}nloSm4STCAU#lbV~9nXHdwv0g#tFAkgBe4u@)c10XOH-Ow)3}Sp> PW@Kc%%b--m2IK$$mz+t6 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/__pycache__/migration.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..690b14c822c77a78e106a46ec30c718b29af5eb0 GIT binary patch literal 249 zcmX@j%ge<81Pj#XWNHHG#~=<2FhLog`GAb+3@HpLj5!QznHU)=nKYSSG6DrP8E-La z`2k76qSCyQ%-mGH+|2Z%#FEVXyp;@}LE3*6>SyHVrs^jqrIh9*R%KS{yQCIpm*f}d z`=ypE1Qg|Gr6!jY>lY+e=BCCMXXF=Trd7rlrxulECa3C`rRJ6C=VT`77gUyHnG-<<|bt(>%*&b9?Tj{;y=8l7b*YCuW!|$0`R%t;dM~>V=8`?xdLe2?+ z0w9TtOXEMfxi?x!$BRooD$IujvO>fAAx=X>wNckuzS5B@V$$Yiskp;un9O3PG3}}ZDT~|&8;T)4MB7W1rY*dx zo){G)Pc79fOE9!urNu=Sy10ShNC(&-P$?p+v$U)FyUpz#YDJ1$n(-d%pruS=5(!R~ yZt866@6QiAFl#P)_L!}@HueZ1Ur-EH$9sVz-G%nM)^8n^o52n+&zX zcE5Ahc~p&Cqd{hJK=siTY*mep7p8RZ#i4X84(Cg7B3sLH1>PXt|4KSzeR|aSnM!mA zM|%ara&iSXRynYj^P^oPD`g|RjP3g{@L}jfjA0jdcd&_Mk#7olt8UpxsIQ%=*d^RN`(({$$Xv}T*=P{;+Ei-Uh#KrxWx%#j zZdsugY~PWy(^s!qjTOJFWOgm_&P*1d7b;_N!dmjCB`3ehiJjrT|9e8br2qf` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/api.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b02e116cecf09abdcb57cf58e1a3b48460d98225 GIT binary patch literal 23020 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 Oo{CYk;V(=&g#QmQSZxac literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/script/__pycache__/revision.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8dbb8334ff25f89ce90631223e64e2896f11bf54 GIT binary patch literal 62510 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 hfG;T|#tQoNcp>}*;IA}#^q5e~{{cD!nYsV~ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/script/__pycache__/write_hooks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dc63997e08d7710669dc25afc6de9dea36c733c GIT binary patch literal 7256 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 W&pp`GwaV3R@J@>-@Ej?&-TwpOV6@%< literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/script/base.py b/venv/lib/python3.12/site-packages/alembic/script/base.py new file mode 100644 index 0000000..f841708 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/script/base.py @@ -0,0 +1,1052 @@ +from __future__ import annotations + +from contextlib import contextmanager +import datetime +import os +from pathlib import Path +import re +import shutil +import sys +from types import ModuleType +from typing import Any +from typing import cast +from typing import Iterator +from typing import List +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from . import revision +from . import write_hooks +from .. import util +from ..runtime import migration +from ..util import compat +from ..util import not_none +from ..util.pyfiles import _preserving_path_as_str + +if TYPE_CHECKING: + from .revision import _GetRevArg + from .revision import _RevIdType + from .revision import Revision + from ..config import Config + from ..config import MessagingOptions + from ..config import PostWriteHookConfig + from ..runtime.migration import RevisionStep + from ..runtime.migration import StampStep + +try: + from zoneinfo import ZoneInfo + from zoneinfo import ZoneInfoNotFoundError +except ImportError: + ZoneInfo = None # type: ignore[assignment, misc] + +_sourceless_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)(c|o)?$") +_only_source_rev_file = re.compile(r"(?!\.\#|__init__)(.*\.py)$") +_legacy_rev = re.compile(r"([a-f0-9]+)\.py$") +_slug_re = re.compile(r"\w+") +_default_file_template = "%(rev)s_%(slug)s" + + +class ScriptDirectory: + """Provides operations upon an Alembic script directory. + + This object is useful to get information as to current revisions, + most notably being able to get at the "head" revision, for schemes + that want to test if the current revision in the database is the most + recent:: + + from alembic.script import ScriptDirectory + from alembic.config import Config + config = Config() + config.set_main_option("script_location", "myapp:migrations") + script = ScriptDirectory.from_config(config) + + head_revision = script.get_current_head() + + + + """ + + def __init__( + self, + dir: Union[str, os.PathLike[str]], # noqa: A002 + file_template: str = _default_file_template, + truncate_slug_length: Optional[int] = 40, + version_locations: Optional[ + Sequence[Union[str, os.PathLike[str]]] + ] = None, + sourceless: bool = False, + output_encoding: str = "utf-8", + timezone: Optional[str] = None, + hooks: list[PostWriteHookConfig] = [], + recursive_version_locations: bool = False, + messaging_opts: MessagingOptions = cast( + "MessagingOptions", util.EMPTY_DICT + ), + ) -> None: + self.dir = _preserving_path_as_str(dir) + self.version_locations = [ + _preserving_path_as_str(p) for p in version_locations or () + ] + self.file_template = file_template + self.truncate_slug_length = truncate_slug_length or 40 + self.sourceless = sourceless + self.output_encoding = output_encoding + self.revision_map = revision.RevisionMap(self._load_revisions) + self.timezone = timezone + self.hooks = hooks + self.recursive_version_locations = recursive_version_locations + self.messaging_opts = messaging_opts + + if not os.access(dir, os.F_OK): + raise util.CommandError( + f"Path doesn't exist: {dir}. Please use " + "the 'init' command to create a new " + "scripts folder." + ) + + @property + def versions(self) -> str: + """return a single version location based on the sole path passed + within version_locations. + + If multiple version locations are configured, an error is raised. + + + """ + return str(self._singular_version_location) + + @util.memoized_property + def _singular_version_location(self) -> Path: + loc = self._version_locations + if len(loc) > 1: + raise util.CommandError("Multiple version_locations present") + else: + return loc[0] + + @util.memoized_property + def _version_locations(self) -> Sequence[Path]: + if self.version_locations: + return [ + util.coerce_resource_to_filename(location).absolute() + for location in self.version_locations + ] + else: + return [Path(self.dir, "versions").absolute()] + + def _load_revisions(self) -> Iterator[Script]: + paths = [vers for vers in self._version_locations if vers.exists()] + + dupes = set() + for vers in paths: + for file_path in Script._list_py_dir(self, vers): + real_path = file_path.resolve() + if real_path in dupes: + util.warn( + f"File {real_path} loaded twice! ignoring. " + "Please ensure version_locations is unique." + ) + continue + dupes.add(real_path) + + script = Script._from_path(self, real_path) + if script is None: + continue + yield script + + @classmethod + def from_config(cls, config: Config) -> ScriptDirectory: + """Produce a new :class:`.ScriptDirectory` given a :class:`.Config` + instance. + + The :class:`.Config` need only have the ``script_location`` key + present. + + """ + script_location = config.get_alembic_option("script_location") + if script_location is None: + raise util.CommandError( + "No 'script_location' key found in configuration." + ) + truncate_slug_length: Optional[int] + tsl = config.get_alembic_option("truncate_slug_length") + if tsl is not None: + truncate_slug_length = int(tsl) + else: + truncate_slug_length = None + + prepend_sys_path = config.get_prepend_sys_paths_list() + if prepend_sys_path: + sys.path[:0] = prepend_sys_path + + rvl = config.get_alembic_boolean_option("recursive_version_locations") + return ScriptDirectory( + util.coerce_resource_to_filename(script_location), + file_template=config.get_alembic_option( + "file_template", _default_file_template + ), + truncate_slug_length=truncate_slug_length, + sourceless=config.get_alembic_boolean_option("sourceless"), + output_encoding=config.get_alembic_option( + "output_encoding", "utf-8" + ), + version_locations=config.get_version_locations_list(), + timezone=config.get_alembic_option("timezone"), + hooks=config.get_hooks_list(), + recursive_version_locations=rvl, + messaging_opts=config.messaging_opts, + ) + + @contextmanager + def _catch_revision_errors( + self, + ancestor: Optional[str] = None, + multiple_heads: Optional[str] = None, + start: Optional[str] = None, + end: Optional[str] = None, + resolution: Optional[str] = None, + ) -> Iterator[None]: + try: + yield + except revision.RangeNotAncestorError as rna: + if start is None: + start = cast(Any, rna.lower) + if end is None: + end = cast(Any, rna.upper) + if not ancestor: + ancestor = ( + "Requested range %(start)s:%(end)s does not refer to " + "ancestor/descendant revisions along the same branch" + ) + ancestor = ancestor % {"start": start, "end": end} + raise util.CommandError(ancestor) from rna + except revision.MultipleHeads as mh: + if not multiple_heads: + multiple_heads = ( + "Multiple head revisions are present for given " + "argument '%(head_arg)s'; please " + "specify a specific target revision, " + "'@%(head_arg)s' to " + "narrow to a specific head, or 'heads' for all heads" + ) + multiple_heads = multiple_heads % { + "head_arg": end or mh.argument, + "heads": util.format_as_comma(mh.heads), + } + raise util.CommandError(multiple_heads) from mh + except revision.ResolutionError as re: + if resolution is None: + resolution = "Can't locate revision identified by '%s'" % ( + re.argument + ) + raise util.CommandError(resolution) from re + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + + def walk_revisions( + self, base: str = "base", head: str = "heads" + ) -> Iterator[Script]: + """Iterate through all revisions. + + :param base: the base revision, or "base" to start from the + empty revision. + + :param head: the head revision; defaults to "heads" to indicate + all head revisions. May also be "head" to indicate a single + head revision. + + """ + with self._catch_revision_errors(start=base, end=head): + for rev in self.revision_map.iterate_revisions( + head, base, inclusive=True, assert_relative_length=False + ): + yield cast(Script, rev) + + def get_revisions(self, id_: _GetRevArg) -> Tuple[Script, ...]: + """Return the :class:`.Script` instance with the given rev identifier, + symbolic name, or sequence of identifiers. + + """ + with self._catch_revision_errors(): + return cast( + Tuple[Script, ...], + self.revision_map.get_revisions(id_), + ) + + def get_all_current(self, id_: Tuple[str, ...]) -> Set[Script]: + with self._catch_revision_errors(): + return cast(Set[Script], self.revision_map._get_all_current(id_)) + + def get_revision(self, id_: str) -> Script: + """Return the :class:`.Script` instance with the given rev id. + + .. seealso:: + + :meth:`.ScriptDirectory.get_revisions` + + """ + + with self._catch_revision_errors(): + return cast(Script, self.revision_map.get_revision(id_)) + + def as_revision_number( + self, id_: Optional[str] + ) -> Optional[Union[str, Tuple[str, ...]]]: + """Convert a symbolic revision, i.e. 'head' or 'base', into + an actual revision number.""" + + with self._catch_revision_errors(): + rev, branch_name = self.revision_map._resolve_revision_number(id_) + + if not rev: + # convert () to None + return None + elif id_ == "heads": + return rev + else: + return rev[0] + + def iterate_revisions( + self, + upper: Union[str, Tuple[str, ...], None], + lower: Union[str, Tuple[str, ...], None], + **kw: Any, + ) -> Iterator[Script]: + """Iterate through script revisions, starting at the given + upper revision identifier and ending at the lower. + + The traversal uses strictly the `down_revision` + marker inside each migration script, so + it is a requirement that upper >= lower, + else you'll get nothing back. + + The iterator yields :class:`.Script` objects. + + .. seealso:: + + :meth:`.RevisionMap.iterate_revisions` + + """ + return cast( + Iterator[Script], + self.revision_map.iterate_revisions(upper, lower, **kw), + ) + + def get_current_head(self) -> Optional[str]: + """Return the current head revision. + + If the script directory has multiple heads + due to branching, an error is raised; + :meth:`.ScriptDirectory.get_heads` should be + preferred. + + :return: a string revision number. + + .. seealso:: + + :meth:`.ScriptDirectory.get_heads` + + """ + with self._catch_revision_errors( + multiple_heads=( + "The script directory has multiple heads (due to branching)." + "Please use get_heads(), or merge the branches using " + "alembic merge." + ) + ): + return self.revision_map.get_current_head() + + def get_heads(self) -> List[str]: + """Return all "versioned head" revisions as strings. + + This is normally a list of length one, + unless branches are present. The + :meth:`.ScriptDirectory.get_current_head()` method + can be used normally when a script directory + has only one head. + + :return: a tuple of string revision numbers. + """ + return list(self.revision_map.heads) + + def get_base(self) -> Optional[str]: + """Return the "base" revision as a string. + + This is the revision number of the script that + has a ``down_revision`` of None. + + If the script directory has multiple bases, an error is raised; + :meth:`.ScriptDirectory.get_bases` should be + preferred. + + """ + bases = self.get_bases() + if len(bases) > 1: + raise util.CommandError( + "The script directory has multiple bases. " + "Please use get_bases()." + ) + elif bases: + return bases[0] + else: + return None + + def get_bases(self) -> List[str]: + """return all "base" revisions as strings. + + This is the revision number of all scripts that + have a ``down_revision`` of None. + + """ + return list(self.revision_map.bases) + + def _upgrade_revs( + self, destination: str, current_rev: str + ) -> List[RevisionStep]: + with self._catch_revision_errors( + ancestor="Destination %(end)s is not a valid upgrade " + "target from current head(s)", + end=destination, + ): + revs = self.iterate_revisions( + destination, current_rev, implicit_base=True + ) + return [ + migration.MigrationStep.upgrade_from_script( + self.revision_map, script + ) + for script in reversed(list(revs)) + ] + + def _downgrade_revs( + self, destination: str, current_rev: Optional[str] + ) -> List[RevisionStep]: + with self._catch_revision_errors( + ancestor="Destination %(end)s is not a valid downgrade " + "target from current head(s)", + end=destination, + ): + revs = self.iterate_revisions( + current_rev, destination, select_for_downgrade=True + ) + return [ + migration.MigrationStep.downgrade_from_script( + self.revision_map, script + ) + for script in revs + ] + + def _stamp_revs( + self, revision: _RevIdType, heads: _RevIdType + ) -> List[StampStep]: + with self._catch_revision_errors( + multiple_heads="Multiple heads are present; please specify a " + "single target revision" + ): + heads_revs = self.get_revisions(heads) + + steps = [] + + if not revision: + revision = "base" + + filtered_heads: List[Script] = [] + for rev in util.to_tuple(revision): + if rev: + filtered_heads.extend( + self.revision_map.filter_for_lineage( + cast(Sequence[Script], heads_revs), + rev, + include_dependencies=True, + ) + ) + filtered_heads = util.unique_list(filtered_heads) + + dests = self.get_revisions(revision) or [None] + + for dest in dests: + if dest is None: + # dest is 'base'. Return a "delete branch" migration + # for all applicable heads. + steps.extend( + [ + migration.StampStep( + head.revision, + None, + False, + True, + self.revision_map, + ) + for head in filtered_heads + ] + ) + continue + elif dest in filtered_heads: + # the dest is already in the version table, do nothing. + continue + + # figure out if the dest is a descendant or an + # ancestor of the selected nodes + descendants = set( + self.revision_map._get_descendant_nodes([dest]) + ) + ancestors = set(self.revision_map._get_ancestor_nodes([dest])) + + if descendants.intersection(filtered_heads): + # heads are above the target, so this is a downgrade. + # we can treat them as a "merge", single step. + assert not ancestors.intersection(filtered_heads) + todo_heads = [head.revision for head in filtered_heads] + step = migration.StampStep( + todo_heads, + dest.revision, + False, + False, + self.revision_map, + ) + steps.append(step) + continue + elif ancestors.intersection(filtered_heads): + # heads are below the target, so this is an upgrade. + # we can treat them as a "merge", single step. + todo_heads = [head.revision for head in filtered_heads] + step = migration.StampStep( + todo_heads, + dest.revision, + True, + False, + self.revision_map, + ) + steps.append(step) + continue + else: + # destination is in a branch not represented, + # treat it as new branch + step = migration.StampStep( + (), dest.revision, True, True, self.revision_map + ) + steps.append(step) + continue + + return steps + + def run_env(self) -> None: + """Run the script environment. + + This basically runs the ``env.py`` script present + in the migration environment. It is called exclusively + by the command functions in :mod:`alembic.command`. + + + """ + util.load_python_file(self.dir, "env.py") + + @property + def env_py_location(self) -> str: + return str(Path(self.dir, "env.py")) + + def _append_template(self, src: Path, dest: Path, **kw: Any) -> None: + with util.status( + f"Appending to existing {dest.absolute()}", + **self.messaging_opts, + ): + util.template_to_file( + src, + dest, + self.output_encoding, + append_with_newlines=True, + **kw, + ) + + def _generate_template(self, src: Path, dest: Path, **kw: Any) -> None: + with util.status( + f"Generating {dest.absolute()}", **self.messaging_opts + ): + util.template_to_file(src, dest, self.output_encoding, **kw) + + def _copy_file(self, src: Path, dest: Path) -> None: + with util.status( + f"Generating {dest.absolute()}", **self.messaging_opts + ): + shutil.copy(src, dest) + + def _ensure_directory(self, path: Path) -> None: + path = path.absolute() + if not path.exists(): + with util.status( + f"Creating directory {path}", **self.messaging_opts + ): + os.makedirs(path) + + def _generate_create_date(self) -> datetime.datetime: + if self.timezone is not None: + if ZoneInfo is None: + raise util.CommandError( + "Python >= 3.9 is required for timezone support or " + "the 'backports.zoneinfo' package must be installed." + ) + # First, assume correct capitalization + try: + tzinfo = ZoneInfo(self.timezone) + except ZoneInfoNotFoundError: + tzinfo = None + if tzinfo is None: + try: + tzinfo = ZoneInfo(self.timezone.upper()) + except ZoneInfoNotFoundError: + raise util.CommandError( + "Can't locate timezone: %s" % self.timezone + ) from None + + create_date = datetime.datetime.now( + tz=datetime.timezone.utc + ).astimezone(tzinfo) + else: + create_date = datetime.datetime.now() + return create_date + + def generate_revision( + self, + revid: str, + message: Optional[str], + head: Optional[_RevIdType] = None, + splice: Optional[bool] = False, + branch_labels: Optional[_RevIdType] = None, + version_path: Union[str, os.PathLike[str], None] = None, + file_template: Optional[str] = None, + depends_on: Optional[_RevIdType] = None, + **kw: Any, + ) -> Optional[Script]: + """Generate a new revision file. + + This runs the ``script.py.mako`` template, given + template arguments, and creates a new file. + + :param revid: String revision id. Typically this + comes from ``alembic.util.rev_id()``. + :param message: the revision message, the one passed + by the -m argument to the ``revision`` command. + :param head: the head revision to generate against. Defaults + to the current "head" if no branches are present, else raises + an exception. + :param splice: if True, allow the "head" version to not be an + actual head; otherwise, the selected head must be a head + (e.g. endpoint) revision. + + """ + if head is None: + head = "head" + + try: + Script.verify_rev_id(revid) + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + + with self._catch_revision_errors( + multiple_heads=( + "Multiple heads are present; please specify the head " + "revision on which the new revision should be based, " + "or perform a merge." + ) + ): + heads = cast( + Tuple[Optional["Revision"], ...], + self.revision_map.get_revisions(head), + ) + for h in heads: + assert h != "base" # type: ignore[comparison-overlap] + + if len(set(heads)) != len(heads): + raise util.CommandError("Duplicate head revisions specified") + + create_date = self._generate_create_date() + + if version_path is None: + if len(self._version_locations) > 1: + for head_ in heads: + if head_ is not None: + assert isinstance(head_, Script) + version_path = head_._script_path.parent + break + else: + raise util.CommandError( + "Multiple version locations present, " + "please specify --version-path" + ) + else: + version_path = self._singular_version_location + else: + version_path = Path(version_path) + + assert isinstance(version_path, Path) + norm_path = version_path.absolute() + for vers_path in self._version_locations: + if vers_path.absolute() == norm_path: + break + else: + raise util.CommandError( + f"Path {version_path} is not represented in current " + "version locations" + ) + + if self.version_locations: + self._ensure_directory(version_path) + + path = self._rev_path(version_path, revid, message, create_date) + self._ensure_directory(path.parent) + + if not splice: + for head_ in heads: + if head_ is not None and not head_.is_head: + raise util.CommandError( + "Revision %s is not a head revision; please specify " + "--splice to create a new branch from this revision" + % head_.revision + ) + + resolved_depends_on: Optional[List[str]] + if depends_on: + with self._catch_revision_errors(): + resolved_depends_on = [ + ( + dep + if dep in rev.branch_labels # maintain branch labels + else rev.revision + ) # resolve partial revision identifiers + for rev, dep in [ + (not_none(self.revision_map.get_revision(dep)), dep) + for dep in util.to_list(depends_on) + ] + ] + else: + resolved_depends_on = None + + self._generate_template( + Path(self.dir, "script.py.mako"), + path, + up_revision=str(revid), + down_revision=revision.tuple_rev_as_scalar( + tuple(h.revision if h is not None else None for h in heads) + ), + branch_labels=util.to_tuple(branch_labels), + depends_on=revision.tuple_rev_as_scalar(resolved_depends_on), + create_date=create_date, + comma=util.format_as_comma, + message=message if message is not None else ("empty message"), + **kw, + ) + + post_write_hooks = self.hooks + if post_write_hooks: + write_hooks._run_hooks(path, post_write_hooks) + + try: + script = Script._from_path(self, path) + except revision.RevisionError as err: + raise util.CommandError(err.args[0]) from err + if script is None: + return None + if branch_labels and not script.branch_labels: + raise util.CommandError( + "Version %s specified branch_labels %s, however the " + "migration file %s does not have them; have you upgraded " + "your script.py.mako to include the " + "'branch_labels' section?" + % (script.revision, branch_labels, script.path) + ) + self.revision_map.add_revision(script) + return script + + def _rev_path( + self, + path: Union[str, os.PathLike[str]], + rev_id: str, + message: Optional[str], + create_date: datetime.datetime, + ) -> Path: + epoch = int(create_date.timestamp()) + slug = "_".join(_slug_re.findall(message or "")).lower() + if len(slug) > self.truncate_slug_length: + slug = slug[: self.truncate_slug_length].rsplit("_", 1)[0] + "_" + filename = "%s.py" % ( + self.file_template + % { + "rev": rev_id, + "slug": slug, + "epoch": epoch, + "year": create_date.year, + "month": create_date.month, + "day": create_date.day, + "hour": create_date.hour, + "minute": create_date.minute, + "second": create_date.second, + } + ) + return Path(path) / filename + + +class Script(revision.Revision): + """Represent a single revision file in a ``versions/`` directory. + + The :class:`.Script` instance is returned by methods + such as :meth:`.ScriptDirectory.iterate_revisions`. + + """ + + def __init__( + self, + module: ModuleType, + rev_id: str, + path: Union[str, os.PathLike[str]], + ): + self.module = module + self.path = _preserving_path_as_str(path) + super().__init__( + rev_id, + module.down_revision, + branch_labels=util.to_tuple( + getattr(module, "branch_labels", None), default=() + ), + dependencies=util.to_tuple( + getattr(module, "depends_on", None), default=() + ), + ) + + module: ModuleType + """The Python module representing the actual script itself.""" + + path: str + """Filesystem path of the script.""" + + @property + def _script_path(self) -> Path: + return Path(self.path) + + _db_current_indicator: Optional[bool] = None + """Utility variable which when set will cause string output to indicate + this is a "current" version in some database""" + + @property + def doc(self) -> str: + """Return the docstring given in the script.""" + + return re.split("\n\n", self.longdoc)[0] + + @property + def longdoc(self) -> str: + """Return the docstring given in the script.""" + + doc = self.module.__doc__ + if doc: + if hasattr(self.module, "_alembic_source_encoding"): + doc = doc.decode( # type: ignore[attr-defined] + self.module._alembic_source_encoding + ) + return doc.strip() + else: + return "" + + @property + def log_entry(self) -> str: + entry = "Rev: %s%s%s%s%s\n" % ( + self.revision, + " (head)" if self.is_head else "", + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + " (current)" if self._db_current_indicator else "", + ) + if self.is_merge_point: + entry += "Merges: %s\n" % (self._format_down_revision(),) + else: + entry += "Parent: %s\n" % (self._format_down_revision(),) + + if self.dependencies: + entry += "Also depends on: %s\n" % ( + util.format_as_comma(self.dependencies) + ) + + if self.is_branch_point: + entry += "Branches into: %s\n" % ( + util.format_as_comma(self.nextrev) + ) + + if self.branch_labels: + entry += "Branch names: %s\n" % ( + util.format_as_comma(self.branch_labels), + ) + + entry += "Path: %s\n" % (self.path,) + + entry += "\n%s\n" % ( + "\n".join(" %s" % para for para in self.longdoc.splitlines()) + ) + return entry + + def __str__(self) -> str: + return "%s -> %s%s%s%s, %s" % ( + self._format_down_revision(), + self.revision, + " (head)" if self.is_head else "", + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + self.doc, + ) + + def _head_only( + self, + include_branches: bool = False, + include_doc: bool = False, + include_parents: bool = False, + tree_indicators: bool = True, + head_indicators: bool = True, + ) -> str: + text = self.revision + if include_parents: + if self.dependencies: + text = "%s (%s) -> %s" % ( + self._format_down_revision(), + util.format_as_comma(self.dependencies), + text, + ) + else: + text = "%s -> %s" % (self._format_down_revision(), text) + assert text is not None + if include_branches and self.branch_labels: + text += " (%s)" % util.format_as_comma(self.branch_labels) + if head_indicators or tree_indicators: + text += "%s%s%s" % ( + " (head)" if self._is_real_head else "", + ( + " (effective head)" + if self.is_head and not self._is_real_head + else "" + ), + " (current)" if self._db_current_indicator else "", + ) + if tree_indicators: + text += "%s%s" % ( + " (branchpoint)" if self.is_branch_point else "", + " (mergepoint)" if self.is_merge_point else "", + ) + if include_doc: + text += ", %s" % self.doc + return text + + def cmd_format( + self, + verbose: bool, + include_branches: bool = False, + include_doc: bool = False, + include_parents: bool = False, + tree_indicators: bool = True, + ) -> str: + if verbose: + return self.log_entry + else: + return self._head_only( + include_branches, include_doc, include_parents, tree_indicators + ) + + def _format_down_revision(self) -> str: + if not self.down_revision: + return "" + else: + return util.format_as_comma(self._versioned_down_revisions) + + @classmethod + def _list_py_dir( + cls, scriptdir: ScriptDirectory, path: Path + ) -> List[Path]: + paths = [] + for root, dirs, files in compat.path_walk(path, top_down=True): + if root.name.endswith("__pycache__"): + # a special case - we may include these files + # if a `sourceless` option is specified + continue + + for filename in sorted(files): + paths.append(root / filename) + + if scriptdir.sourceless: + # look for __pycache__ + py_cache_path = root / "__pycache__" + if py_cache_path.exists(): + # add all files from __pycache__ whose filename is not + # already in the names we got from the version directory. + # add as relative paths including __pycache__ token + names = { + Path(filename).name.split(".")[0] for filename in files + } + paths.extend( + py_cache_path / pyc + for pyc in py_cache_path.iterdir() + if pyc.name.split(".")[0] not in names + ) + + if not scriptdir.recursive_version_locations: + break + + # the real script order is defined by revision, + # but it may be undefined if there are many files with a same + # `down_revision`, for a better user experience (ex. debugging), + # we use a deterministic order + dirs.sort() + + return paths + + @classmethod + def _from_path( + cls, scriptdir: ScriptDirectory, path: Union[str, os.PathLike[str]] + ) -> Optional[Script]: + + path = Path(path) + dir_, filename = path.parent, path.name + + if scriptdir.sourceless: + py_match = _sourceless_rev_file.match(filename) + else: + py_match = _only_source_rev_file.match(filename) + + if not py_match: + return None + + py_filename = py_match.group(1) + + if scriptdir.sourceless: + is_c = py_match.group(2) == "c" + is_o = py_match.group(2) == "o" + else: + is_c = is_o = False + + if is_o or is_c: + py_exists = (dir_ / py_filename).exists() + pyc_exists = (dir_ / (py_filename + "c")).exists() + + # prefer .py over .pyc because we'd like to get the + # source encoding; prefer .pyc over .pyo because we'd like to + # have the docstrings which a -OO file would not have + if py_exists or is_o and pyc_exists: + return None + + module = util.load_python_file(dir_, filename) + + if not hasattr(module, "revision"): + # attempt to get the revision id from the script name, + # this for legacy only + m = _legacy_rev.match(filename) + if not m: + raise util.CommandError( + "Could not determine revision id from " + f"filename {filename}. " + "Be sure the 'revision' variable is " + "declared inside the script (please see 'Upgrading " + "from Alembic 0.1 to 0.2' in the documentation)." + ) + else: + revision = m.group(1) + else: + revision = module.revision + return Script(module, revision, dir_ / filename) diff --git a/venv/lib/python3.12/site-packages/alembic/script/revision.py b/venv/lib/python3.12/site-packages/alembic/script/revision.py new file mode 100644 index 0000000..5825da3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/script/revision.py @@ -0,0 +1,1728 @@ +from __future__ import annotations + +import collections +import re +from typing import Any +from typing import Callable +from typing import cast +from typing import Collection +from typing import Deque +from typing import Dict +from typing import FrozenSet +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import overload +from typing import Protocol +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import util as sqlautil + +from .. import util +from ..util import not_none + +if TYPE_CHECKING: + from typing import Literal + +_RevIdType = Union[str, List[str], Tuple[str, ...]] +_GetRevArg = Union[ + str, + Iterable[Optional[str]], + Iterable[str], +] +_RevisionIdentifierType = Union[str, Tuple[str, ...], None] +_RevisionOrStr = Union["Revision", str] +_RevisionOrBase = Union["Revision", "Literal['base']"] +_InterimRevisionMapType = Dict[str, "Revision"] +_RevisionMapType = Dict[Union[None, str, Tuple[()]], Optional["Revision"]] +_T = TypeVar("_T") +_TR = TypeVar("_TR", bound=Optional[_RevisionOrStr]) + +_relative_destination = re.compile(r"(?:(.+?)@)?(\w+)?((?:\+|-)\d+)") +_revision_illegal_chars = ["@", "-", "+", ":"] + + +class _CollectRevisionsProtocol(Protocol): + def __call__( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: ... + + +class RevisionError(Exception): + pass + + +class RangeNotAncestorError(RevisionError): + def __init__( + self, lower: _RevisionIdentifierType, upper: _RevisionIdentifierType + ) -> None: + self.lower = lower + self.upper = upper + super().__init__( + "Revision %s is not an ancestor of revision %s" + % (lower or "base", upper or "base") + ) + + +class MultipleHeads(RevisionError): + def __init__(self, heads: Sequence[str], argument: Optional[str]) -> None: + self.heads = heads + self.argument = argument + super().__init__( + "Multiple heads are present for given argument '%s'; " + "%s" % (argument, ", ".join(heads)) + ) + + +class ResolutionError(RevisionError): + def __init__(self, message: str, argument: str) -> None: + super().__init__(message) + self.argument = argument + + +class CycleDetected(RevisionError): + kind = "Cycle" + + def __init__(self, revisions: Sequence[str]) -> None: + self.revisions = revisions + super().__init__( + "%s is detected in revisions (%s)" + % (self.kind, ", ".join(revisions)) + ) + + +class DependencyCycleDetected(CycleDetected): + kind = "Dependency cycle" + + def __init__(self, revisions: Sequence[str]) -> None: + super().__init__(revisions) + + +class LoopDetected(CycleDetected): + kind = "Self-loop" + + def __init__(self, revision: str) -> None: + super().__init__([revision]) + + +class DependencyLoopDetected(DependencyCycleDetected, LoopDetected): + kind = "Dependency self-loop" + + def __init__(self, revision: Sequence[str]) -> None: + super().__init__(revision) + + +class RevisionMap: + """Maintains a map of :class:`.Revision` objects. + + :class:`.RevisionMap` is used by :class:`.ScriptDirectory` to maintain + and traverse the collection of :class:`.Script` objects, which are + themselves instances of :class:`.Revision`. + + """ + + def __init__(self, generator: Callable[[], Iterable[Revision]]) -> None: + """Construct a new :class:`.RevisionMap`. + + :param generator: a zero-arg callable that will generate an iterable + of :class:`.Revision` instances to be used. These are typically + :class:`.Script` subclasses within regular Alembic use. + + """ + self._generator = generator + + @util.memoized_property + def heads(self) -> Tuple[str, ...]: + """All "head" revisions as strings. + + This is normally a tuple of length one, + unless unmerged branches are present. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self.heads + + @util.memoized_property + def bases(self) -> Tuple[str, ...]: + """All "base" revisions as strings. + + These are revisions that have a ``down_revision`` of None, + or empty tuple. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self.bases + + @util.memoized_property + def _real_heads(self) -> Tuple[str, ...]: + """All "real" head revisions as strings. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self._real_heads + + @util.memoized_property + def _real_bases(self) -> Tuple[str, ...]: + """All "real" base revisions as strings. + + :return: a tuple of string revision numbers. + + """ + self._revision_map + return self._real_bases + + @util.memoized_property + def _revision_map(self) -> _RevisionMapType: + """memoized attribute, initializes the revision map from the + initial collection. + + """ + # Ordering required for some tests to pass (but not required in + # general) + map_: _InterimRevisionMapType = sqlautil.OrderedDict() + + heads: Set[Revision] = sqlautil.OrderedSet() + _real_heads: Set[Revision] = sqlautil.OrderedSet() + bases: Tuple[Revision, ...] = () + _real_bases: Tuple[Revision, ...] = () + + has_branch_labels = set() + all_revisions = set() + + for revision in self._generator(): + all_revisions.add(revision) + + if revision.revision in map_: + util.warn( + "Revision %s is present more than once" % revision.revision + ) + map_[revision.revision] = revision + if revision.branch_labels: + has_branch_labels.add(revision) + + heads.add(revision) + _real_heads.add(revision) + if revision.is_base: + bases += (revision,) + if revision._is_real_base: + _real_bases += (revision,) + + # add the branch_labels to the map_. We'll need these + # to resolve the dependencies. + rev_map = map_.copy() + self._map_branch_labels( + has_branch_labels, cast(_RevisionMapType, map_) + ) + + # resolve dependency names from branch labels and symbolic + # names + self._add_depends_on(all_revisions, cast(_RevisionMapType, map_)) + + for rev in map_.values(): + for downrev in rev._all_down_revisions: + if downrev not in map_: + util.warn( + "Revision %s referenced from %s is not present" + % (downrev, rev) + ) + down_revision = map_[downrev] + down_revision.add_nextrev(rev) + if downrev in rev._versioned_down_revisions: + heads.discard(down_revision) + _real_heads.discard(down_revision) + + # once the map has downrevisions populated, the dependencies + # can be further refined to include only those which are not + # already ancestors + self._normalize_depends_on(all_revisions, cast(_RevisionMapType, map_)) + self._detect_cycles(rev_map, heads, bases, _real_heads, _real_bases) + + revision_map: _RevisionMapType = dict(map_.items()) + revision_map[None] = revision_map[()] = None + self.heads = tuple(rev.revision for rev in heads) + self._real_heads = tuple(rev.revision for rev in _real_heads) + self.bases = tuple(rev.revision for rev in bases) + self._real_bases = tuple(rev.revision for rev in _real_bases) + + self._add_branches(has_branch_labels, revision_map) + return revision_map + + def _detect_cycles( + self, + rev_map: _InterimRevisionMapType, + heads: Set[Revision], + bases: Tuple[Revision, ...], + _real_heads: Set[Revision], + _real_bases: Tuple[Revision, ...], + ) -> None: + if not rev_map: + return + if not heads or not bases: + raise CycleDetected(list(rev_map)) + total_space = { + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._versioned_down_revisions, + heads, + map_=cast(_RevisionMapType, rev_map), + ) + }.intersection( + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r.nextrev, + bases, + map_=cast(_RevisionMapType, rev_map), + ) + ) + deleted_revs = set(rev_map.keys()) - total_space + if deleted_revs: + raise CycleDetected(sorted(deleted_revs)) + + if not _real_heads or not _real_bases: + raise DependencyCycleDetected(list(rev_map)) + total_space = { + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._all_down_revisions, + _real_heads, + map_=cast(_RevisionMapType, rev_map), + ) + }.intersection( + rev.revision + for rev in self._iterate_related_revisions( + lambda r: r._all_nextrev, + _real_bases, + map_=cast(_RevisionMapType, rev_map), + ) + ) + deleted_revs = set(rev_map.keys()) - total_space + if deleted_revs: + raise DependencyCycleDetected(sorted(deleted_revs)) + + def _map_branch_labels( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + for revision in revisions: + if revision.branch_labels: + assert revision._orig_branch_labels is not None + for branch_label in revision._orig_branch_labels: + if branch_label in map_: + map_rev = map_[branch_label] + assert map_rev is not None + raise RevisionError( + "Branch name '%s' in revision %s already " + "used by revision %s" + % ( + branch_label, + revision.revision, + map_rev.revision, + ) + ) + map_[branch_label] = revision + + def _add_branches( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + for revision in revisions: + if revision.branch_labels: + revision.branch_labels.update(revision.branch_labels) + for node in self._get_descendant_nodes( + [revision], map_, include_dependencies=False + ): + node.branch_labels.update(revision.branch_labels) + + parent = node + while ( + parent + and not parent._is_real_branch_point + and not parent.is_merge_point + ): + parent.branch_labels.update(revision.branch_labels) + if parent.down_revision: + parent = map_[parent.down_revision] + else: + break + + def _add_depends_on( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + """Resolve the 'dependencies' for each revision in a collection + in terms of actual revision ids, as opposed to branch labels or other + symbolic names. + + The collection is then assigned to the _resolved_dependencies + attribute on each revision object. + + """ + + for revision in revisions: + if revision.dependencies: + deps = [ + map_[dep] for dep in util.to_tuple(revision.dependencies) + ] + revision._resolved_dependencies = tuple( + [d.revision for d in deps if d is not None] + ) + else: + revision._resolved_dependencies = () + + def _normalize_depends_on( + self, revisions: Collection[Revision], map_: _RevisionMapType + ) -> None: + """Create a collection of "dependencies" that omits dependencies + that are already ancestor nodes for each revision in a given + collection. + + This builds upon the _resolved_dependencies collection created in the + _add_depends_on() method, looking in the fully populated revision map + for ancestors, and omitting them as the _resolved_dependencies + collection as it is copied to a new collection. The new collection is + then assigned to the _normalized_resolved_dependencies attribute on + each revision object. + + The collection is then used to determine the immediate "down revision" + identifiers for this revision. + + """ + + for revision in revisions: + if revision._resolved_dependencies: + normalized_resolved = set(revision._resolved_dependencies) + for rev in self._get_ancestor_nodes( + [revision], + include_dependencies=False, + map_=map_, + ): + if rev is revision: + continue + elif rev._resolved_dependencies: + normalized_resolved.difference_update( + rev._resolved_dependencies + ) + + revision._normalized_resolved_dependencies = tuple( + normalized_resolved + ) + else: + revision._normalized_resolved_dependencies = () + + def add_revision(self, revision: Revision, _replace: bool = False) -> None: + """add a single revision to an existing map. + + This method is for single-revision use cases, it's not + appropriate for fully populating an entire revision map. + + """ + map_ = self._revision_map + if not _replace and revision.revision in map_: + util.warn( + "Revision %s is present more than once" % revision.revision + ) + elif _replace and revision.revision not in map_: + raise Exception("revision %s not in map" % revision.revision) + + map_[revision.revision] = revision + + revisions = [revision] + self._add_branches(revisions, map_) + self._map_branch_labels(revisions, map_) + self._add_depends_on(revisions, map_) + + if revision.is_base: + self.bases += (revision.revision,) + if revision._is_real_base: + self._real_bases += (revision.revision,) + + for downrev in revision._all_down_revisions: + if downrev not in map_: + util.warn( + "Revision %s referenced from %s is not present" + % (downrev, revision) + ) + not_none(map_[downrev]).add_nextrev(revision) + + self._normalize_depends_on(revisions, map_) + + if revision._is_real_head: + self._real_heads = tuple( + head + for head in self._real_heads + if head + not in set(revision._all_down_revisions).union( + [revision.revision] + ) + ) + (revision.revision,) + if revision.is_head: + self.heads = tuple( + head + for head in self.heads + if head + not in set(revision._versioned_down_revisions).union( + [revision.revision] + ) + ) + (revision.revision,) + + def get_current_head( + self, branch_label: Optional[str] = None + ) -> Optional[str]: + """Return the current head revision. + + If the script directory has multiple heads + due to branching, an error is raised; + :meth:`.ScriptDirectory.get_heads` should be + preferred. + + :param branch_label: optional branch name which will limit the + heads considered to those which include that branch_label. + + :return: a string revision number. + + .. seealso:: + + :meth:`.ScriptDirectory.get_heads` + + """ + current_heads: Sequence[str] = self.heads + if branch_label: + current_heads = self.filter_for_lineage( + current_heads, branch_label + ) + if len(current_heads) > 1: + raise MultipleHeads( + current_heads, + "%s@head" % branch_label if branch_label else "head", + ) + + if current_heads: + return current_heads[0] + else: + return None + + def _get_base_revisions(self, identifier: str) -> Tuple[str, ...]: + return self.filter_for_lineage(self.bases, identifier) + + def get_revisions( + self, id_: Optional[_GetRevArg] + ) -> Tuple[Optional[_RevisionOrBase], ...]: + """Return the :class:`.Revision` instances with the given rev id + or identifiers. + + May be given a single identifier, a sequence of identifiers, or the + special symbols "head" or "base". The result is a tuple of one + or more identifiers, or an empty tuple in the case of "base". + + In the cases where 'head', 'heads' is requested and the + revision map is empty, returns an empty tuple. + + Supports partial identifiers, where the given identifier + is matched against all identifiers that start with the given + characters; if there is exactly one match, that determines the + full revision. + + """ + + if isinstance(id_, (list, tuple, set, frozenset)): + return sum([self.get_revisions(id_elem) for id_elem in id_], ()) + else: + resolved_id, branch_label = self._resolve_revision_number(id_) + if len(resolved_id) == 1: + try: + rint = int(resolved_id[0]) + if rint < 0: + # branch@-n -> walk down from heads + select_heads = self.get_revisions("heads") + if branch_label is not None: + select_heads = tuple( + head + for head in select_heads + if branch_label + in is_revision(head).branch_labels + ) + return tuple( + self._walk(head, steps=rint) + for head in select_heads + ) + except ValueError: + # couldn't resolve as integer + pass + return tuple( + self._revision_for_ident(rev_id, branch_label) + for rev_id in resolved_id + ) + + def get_revision(self, id_: Optional[str]) -> Optional[Revision]: + """Return the :class:`.Revision` instance with the given rev id. + + If a symbolic name such as "head" or "base" is given, resolves + the identifier into the current head or base revision. If the symbolic + name refers to multiples, :class:`.MultipleHeads` is raised. + + Supports partial identifiers, where the given identifier + is matched against all identifiers that start with the given + characters; if there is exactly one match, that determines the + full revision. + + """ + + resolved_id, branch_label = self._resolve_revision_number(id_) + if len(resolved_id) > 1: + raise MultipleHeads(resolved_id, id_) + + resolved: Union[str, Tuple[()]] = resolved_id[0] if resolved_id else () + return self._revision_for_ident(resolved, branch_label) + + def _resolve_branch(self, branch_label: str) -> Optional[Revision]: + try: + branch_rev = self._revision_map[branch_label] + except KeyError: + try: + nonbranch_rev = self._revision_for_ident(branch_label) + except ResolutionError as re: + raise ResolutionError( + "No such branch: '%s'" % branch_label, branch_label + ) from re + + else: + return nonbranch_rev + else: + return branch_rev + + def _revision_for_ident( + self, + resolved_id: Union[str, Tuple[()], None], + check_branch: Optional[str] = None, + ) -> Optional[Revision]: + branch_rev: Optional[Revision] + if check_branch: + branch_rev = self._resolve_branch(check_branch) + else: + branch_rev = None + + revision: Union[Optional[Revision], Literal[False]] + try: + revision = self._revision_map[resolved_id] + except KeyError: + # break out to avoid misleading py3k stack traces + revision = False + revs: Sequence[str] + if revision is False: + assert resolved_id + # do a partial lookup + revs = [ + x + for x in self._revision_map + if x and len(x) > 3 and x.startswith(resolved_id) + ] + + if branch_rev: + revs = self.filter_for_lineage(revs, check_branch) + if not revs: + raise ResolutionError( + "No such revision or branch '%s'%s" + % ( + resolved_id, + ( + "; please ensure at least four characters are " + "present for partial revision identifier matches" + if len(resolved_id) < 4 + else "" + ), + ), + resolved_id, + ) + elif len(revs) > 1: + raise ResolutionError( + "Multiple revisions start " + "with '%s': %s..." + % (resolved_id, ", ".join("'%s'" % r for r in revs[0:3])), + resolved_id, + ) + else: + revision = self._revision_map[revs[0]] + + if check_branch and revision is not None: + assert branch_rev is not None + assert resolved_id + if not self._shares_lineage( + revision.revision, branch_rev.revision + ): + raise ResolutionError( + "Revision %s is not a member of branch '%s'" + % (revision.revision, check_branch), + resolved_id, + ) + return revision + + def _filter_into_branch_heads( + self, targets: Iterable[Optional[_RevisionOrBase]] + ) -> Set[Optional[_RevisionOrBase]]: + targets = set(targets) + + for rev in list(targets): + assert rev + if targets.intersection( + self._get_descendant_nodes([rev], include_dependencies=False) + ).difference([rev]): + targets.discard(rev) + return targets + + def filter_for_lineage( + self, + targets: Iterable[_TR], + check_against: Optional[str], + include_dependencies: bool = False, + ) -> Tuple[_TR, ...]: + id_, branch_label = self._resolve_revision_number(check_against) + + shares = [] + if branch_label: + shares.append(branch_label) + if id_: + shares.extend(id_) + + return tuple( + tg + for tg in targets + if self._shares_lineage( + tg, shares, include_dependencies=include_dependencies + ) + ) + + def _shares_lineage( + self, + target: Optional[_RevisionOrStr], + test_against_revs: Sequence[_RevisionOrStr], + include_dependencies: bool = False, + ) -> bool: + if not test_against_revs: + return True + if not isinstance(target, Revision): + resolved_target = not_none(self._revision_for_ident(target)) + else: + resolved_target = target + + resolved_test_against_revs = [ + ( + self._revision_for_ident(test_against_rev) + if not isinstance(test_against_rev, Revision) + else test_against_rev + ) + for test_against_rev in util.to_tuple( + test_against_revs, default=() + ) + ] + + return bool( + set( + self._get_descendant_nodes( + [resolved_target], + include_dependencies=include_dependencies, + ) + ) + .union( + self._get_ancestor_nodes( + [resolved_target], + include_dependencies=include_dependencies, + ) + ) + .intersection(resolved_test_against_revs) + ) + + def _resolve_revision_number( + self, id_: Optional[_GetRevArg] + ) -> Tuple[Tuple[str, ...], Optional[str]]: + branch_label: Optional[str] + if isinstance(id_, str) and "@" in id_: + branch_label, id_ = id_.split("@", 1) + + elif id_ is not None and ( + (isinstance(id_, tuple) and id_ and not isinstance(id_[0], str)) + or not isinstance(id_, (str, tuple)) + ): + raise RevisionError( + "revision identifier %r is not a string; ensure database " + "driver settings are correct" % (id_,) + ) + + else: + branch_label = None + + # ensure map is loaded + self._revision_map + if id_ == "heads": + if branch_label: + return ( + self.filter_for_lineage(self.heads, branch_label), + branch_label, + ) + else: + return self._real_heads, branch_label + elif id_ == "head": + current_head = self.get_current_head(branch_label) + if current_head: + return (current_head,), branch_label + else: + return (), branch_label + elif id_ == "base" or id_ is None: + return (), branch_label + else: + return util.to_tuple(id_, default=None), branch_label + + def iterate_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + implicit_base: bool = False, + inclusive: bool = False, + assert_relative_length: bool = True, + select_for_downgrade: bool = False, + ) -> Iterator[Revision]: + """Iterate through script revisions, starting at the given + upper revision identifier and ending at the lower. + + The traversal uses strictly the `down_revision` + marker inside each migration script, so + it is a requirement that upper >= lower, + else you'll get nothing back. + + The iterator yields :class:`.Revision` objects. + + """ + fn: _CollectRevisionsProtocol + if select_for_downgrade: + fn = self._collect_downgrade_revisions + else: + fn = self._collect_upgrade_revisions + + revisions, heads = fn( + upper, + lower, + inclusive=inclusive, + implicit_base=implicit_base, + assert_relative_length=assert_relative_length, + ) + + for node in self._topological_sort(revisions, heads): + yield not_none(self.get_revision(node)) + + def _get_descendant_nodes( + self, + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType] = None, + check: bool = False, + omit_immediate_dependencies: bool = False, + include_dependencies: bool = True, + ) -> Iterator[Any]: + if omit_immediate_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + if rev not in targets: + return rev._all_nextrev + else: + return rev.nextrev + + elif include_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + return rev._all_nextrev + + else: + + def fn(rev: Revision) -> Iterable[str]: + return rev.nextrev + + return self._iterate_related_revisions( + fn, targets, map_=map_, check=check + ) + + def _get_ancestor_nodes( + self, + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType] = None, + check: bool = False, + include_dependencies: bool = True, + ) -> Iterator[Revision]: + if include_dependencies: + + def fn(rev: Revision) -> Iterable[str]: + return rev._normalized_down_revisions + + else: + + def fn(rev: Revision) -> Iterable[str]: + return rev._versioned_down_revisions + + return self._iterate_related_revisions( + fn, targets, map_=map_, check=check + ) + + def _iterate_related_revisions( + self, + fn: Callable[[Revision], Iterable[str]], + targets: Collection[Optional[_RevisionOrBase]], + map_: Optional[_RevisionMapType], + check: bool = False, + ) -> Iterator[Revision]: + if map_ is None: + map_ = self._revision_map + + seen = set() + todo: Deque[Revision] = collections.deque() + for target_for in targets: + target = is_revision(target_for) + todo.append(target) + if check: + per_target = set() + + while todo: + rev = todo.pop() + if check: + per_target.add(rev) + + if rev in seen: + continue + seen.add(rev) + # Check for map errors before collecting. + for rev_id in fn(rev): + next_rev = map_[rev_id] + assert next_rev is not None + if next_rev.revision != rev_id: + raise RevisionError( + "Dependency resolution failed; broken map" + ) + todo.append(next_rev) + yield rev + if check: + overlaps = per_target.intersection(targets).difference( + [target] + ) + if overlaps: + raise RevisionError( + "Requested revision %s overlaps with " + "other requested revisions %s" + % ( + target.revision, + ", ".join(r.revision for r in overlaps), + ) + ) + + def _topological_sort( + self, + revisions: Collection[Revision], + heads: Any, + ) -> List[str]: + """Yield revision ids of a collection of Revision objects in + topological sorted order (i.e. revisions always come after their + down_revisions and dependencies). Uses the order of keys in + _revision_map to sort. + + """ + + id_to_rev = self._revision_map + + def get_ancestors(rev_id: str) -> Set[str]: + return { + r.revision + for r in self._get_ancestor_nodes([id_to_rev[rev_id]]) + } + + todo = {d.revision for d in revisions} + + # Use revision map (ordered dict) key order to pre-sort. + inserted_order = list(self._revision_map) + + current_heads = list( + sorted( + {d.revision for d in heads if d.revision in todo}, + key=inserted_order.index, + ) + ) + ancestors_by_idx = [get_ancestors(rev_id) for rev_id in current_heads] + + output = [] + + current_candidate_idx = 0 + while current_heads: + candidate = current_heads[current_candidate_idx] + + for check_head_index, ancestors in enumerate(ancestors_by_idx): + # scan all the heads. see if we can continue walking + # down the current branch indicated by current_candidate_idx. + if ( + check_head_index != current_candidate_idx + and candidate in ancestors + ): + current_candidate_idx = check_head_index + # nope, another head is dependent on us, they have + # to be traversed first + break + else: + # yup, we can emit + if candidate in todo: + output.append(candidate) + todo.remove(candidate) + + # now update the heads with our ancestors. + + candidate_rev = id_to_rev[candidate] + assert candidate_rev is not None + + heads_to_add = [ + r + for r in candidate_rev._normalized_down_revisions + if r in todo and r not in current_heads + ] + + if not heads_to_add: + # no ancestors, so remove this head from the list + del current_heads[current_candidate_idx] + del ancestors_by_idx[current_candidate_idx] + current_candidate_idx = max(current_candidate_idx - 1, 0) + else: + if ( + not candidate_rev._normalized_resolved_dependencies + and len(candidate_rev._versioned_down_revisions) == 1 + ): + current_heads[current_candidate_idx] = heads_to_add[0] + + # for plain movement down a revision line without + # any mergepoints, branchpoints, or deps, we + # can update the ancestors collection directly + # by popping out the candidate we just emitted + ancestors_by_idx[current_candidate_idx].discard( + candidate + ) + + else: + # otherwise recalculate it again, things get + # complicated otherwise. This can possibly be + # improved to not run the whole ancestor thing + # each time but it was getting complicated + current_heads[current_candidate_idx] = heads_to_add[0] + current_heads.extend(heads_to_add[1:]) + ancestors_by_idx[current_candidate_idx] = ( + get_ancestors(heads_to_add[0]) + ) + ancestors_by_idx.extend( + get_ancestors(head) for head in heads_to_add[1:] + ) + + assert not todo + return output + + def _walk( + self, + start: Optional[Union[str, Revision]], + steps: int, + branch_label: Optional[str] = None, + no_overwalk: bool = True, + ) -> Optional[_RevisionOrBase]: + """ + Walk the requested number of :steps up (steps > 0) or down (steps < 0) + the revision tree. + + :branch_label is used to select branches only when walking up. + + If the walk goes past the boundaries of the tree and :no_overwalk is + True, None is returned, otherwise the walk terminates early. + + A RevisionError is raised if there is no unambiguous revision to + walk to. + """ + initial: Optional[_RevisionOrBase] + if isinstance(start, str): + initial = self.get_revision(start) + else: + initial = start + + children: Sequence[Optional[_RevisionOrBase]] + for _ in range(abs(steps)): + if steps > 0: + assert initial != "base" # type: ignore[comparison-overlap] + # Walk up + walk_up = [ + is_revision(rev) + for rev in self.get_revisions( + self.bases if initial is None else initial.nextrev + ) + ] + if branch_label: + children = self.filter_for_lineage(walk_up, branch_label) + else: + children = walk_up + else: + # Walk down + if initial == "base": # type: ignore[comparison-overlap] + children = () + else: + children = self.get_revisions( + self.heads + if initial is None + else initial.down_revision + ) + if not children: + children = ("base",) + if not children: + # This will return an invalid result if no_overwalk, otherwise + # further steps will stay where we are. + ret = None if no_overwalk else initial + return ret + elif len(children) > 1: + raise RevisionError("Ambiguous walk") + initial = children[0] + + return initial + + def _parse_downgrade_target( + self, + current_revisions: _RevisionIdentifierType, + target: _RevisionIdentifierType, + assert_relative_length: bool, + ) -> Tuple[Optional[str], Optional[_RevisionOrBase]]: + """ + Parse downgrade command syntax :target to retrieve the target revision + and branch label (if any) given the :current_revisions stamp of the + database. + + Returns a tuple (branch_label, target_revision) where branch_label + is a string from the command specifying the branch to consider (or + None if no branch given), and target_revision is a Revision object + which the command refers to. target_revisions is None if the command + refers to 'base'. The target may be specified in absolute form, or + relative to :current_revisions. + """ + if target is None: + return None, None + assert isinstance( + target, str + ), "Expected downgrade target in string form" + match = _relative_destination.match(target) + if match: + branch_label, symbol, relative = match.groups() + rel_int = int(relative) + if rel_int >= 0: + if symbol is None: + # Downgrading to current + n is not valid. + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(rel_int)) + ) + # Find target revision relative to given symbol. + rev = self._walk( + symbol, + rel_int, + branch_label, + no_overwalk=assert_relative_length, + ) + if rev is None: + raise RevisionError("Walked too far") + return branch_label, rev + else: + relative_revision = symbol is None + if relative_revision: + # Find target revision relative to current state. + if branch_label: + cr_tuple = util.to_tuple(current_revisions) + symbol_list: Sequence[str] + symbol_list = self.filter_for_lineage( + cr_tuple, branch_label + ) + if not symbol_list: + # check the case where there are multiple branches + # but there is currently a single heads, since all + # other branch heads are dependent of the current + # single heads. + all_current = cast( + Set[Revision], self._get_all_current(cr_tuple) + ) + sl_all_current = self.filter_for_lineage( + all_current, branch_label + ) + symbol_list = [ + r.revision if r else r # type: ignore[misc] + for r in sl_all_current + ] + + assert len(symbol_list) == 1 + symbol = symbol_list[0] + else: + current_revisions = util.to_tuple(current_revisions) + if not current_revisions: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" + % (relative, abs(rel_int)) + ) + # Have to check uniques here for duplicate rows test. + if len(set(current_revisions)) > 1: + util.warn( + "downgrade -1 from multiple heads is " + "ambiguous; " + "this usage will be disallowed in a future " + "release." + ) + symbol = current_revisions[0] + # Restrict iteration to just the selected branch when + # ambiguous branches are involved. + branch_label = symbol + # Walk down the tree to find downgrade target. + rev = self._walk( + start=( + self.get_revision(symbol) + if branch_label is None + else self.get_revision( + "%s@%s" % (branch_label, symbol) + ) + ), + steps=rel_int, + no_overwalk=assert_relative_length, + ) + if rev is None: + if relative_revision: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(rel_int)) + ) + else: + raise RevisionError("Walked too far") + return branch_label, rev + + # No relative destination given, revision specified is absolute. + branch_label, _, symbol = target.rpartition("@") + if not branch_label: + branch_label = None + return branch_label, self.get_revision(symbol) + + def _parse_upgrade_target( + self, + current_revisions: _RevisionIdentifierType, + target: _RevisionIdentifierType, + assert_relative_length: bool, + ) -> Tuple[Optional[_RevisionOrBase], ...]: + """ + Parse upgrade command syntax :target to retrieve the target revision + and given the :current_revisions stamp of the database. + + Returns a tuple of Revision objects which should be iterated/upgraded + to. The target may be specified in absolute form, or relative to + :current_revisions. + """ + if isinstance(target, str): + match = _relative_destination.match(target) + else: + match = None + + if not match: + # No relative destination, target is absolute. + return self.get_revisions(target) + + current_revisions_tup: Union[str, Tuple[Optional[str], ...], None] + current_revisions_tup = util.to_tuple(current_revisions) + + branch_label, symbol, relative_str = match.groups() + relative = int(relative_str) + if relative > 0: + if symbol is None: + if not current_revisions_tup: + current_revisions_tup = (None,) + # Try to filter to a single target (avoid ambiguous branches). + start_revs = current_revisions_tup + if branch_label: + start_revs = self.filter_for_lineage( + self.get_revisions(current_revisions_tup), # type: ignore[arg-type] # noqa: E501 + branch_label, + ) + if not start_revs: + # The requested branch is not a head, so we need to + # backtrack to find a branchpoint. + active_on_branch = self.filter_for_lineage( + self._get_ancestor_nodes( + self.get_revisions(current_revisions_tup) + ), + branch_label, + ) + # Find the tips of this set of revisions (revisions + # without children within the set). + start_revs = tuple( + {rev.revision for rev in active_on_branch} + - { + down + for rev in active_on_branch + for down in rev._normalized_down_revisions + } + ) + if not start_revs: + # We must need to go right back to base to find + # a starting point for this branch. + start_revs = (None,) + if len(start_revs) > 1: + raise RevisionError( + "Ambiguous upgrade from multiple current revisions" + ) + # Walk up from unique target revision. + rev = self._walk( + start=start_revs[0], + steps=relative, + branch_label=branch_label, + no_overwalk=assert_relative_length, + ) + if rev is None: + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative_str, abs(relative)) + ) + return (rev,) + else: + # Walk is relative to a given revision, not the current state. + return ( + self._walk( + start=self.get_revision(symbol), + steps=relative, + branch_label=branch_label, + no_overwalk=assert_relative_length, + ), + ) + else: + if symbol is None: + # Upgrading to current - n is not valid. + raise RevisionError( + "Relative revision %s didn't " + "produce %d migrations" % (relative, abs(relative)) + ) + return ( + self._walk( + start=( + self.get_revision(symbol) + if branch_label is None + else self.get_revision( + "%s@%s" % (branch_label, symbol) + ) + ), + steps=relative, + no_overwalk=assert_relative_length, + ), + ) + + def _collect_downgrade_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: + """ + Compute the set of current revisions specified by :upper, and the + downgrade target specified by :target. Return all dependents of target + which are currently active. + + :inclusive=True includes the target revision in the set + """ + + branch_label, target_revision = self._parse_downgrade_target( + current_revisions=upper, + target=lower, + assert_relative_length=assert_relative_length, + ) + if target_revision == "base": + target_revision = None + assert target_revision is None or isinstance(target_revision, Revision) + + roots: List[Revision] + # Find candidates to drop. + if target_revision is None: + # Downgrading back to base: find all tree roots. + roots = [ + rev + for rev in self._revision_map.values() + if rev is not None and rev.down_revision is None + ] + elif inclusive: + # inclusive implies target revision should also be dropped + roots = [target_revision] + else: + # Downgrading to fixed target: find all direct children. + roots = [ + is_revision(rev) + for rev in self.get_revisions(target_revision.nextrev) + ] + + if branch_label and len(roots) > 1: + # Need to filter roots. + ancestors = { + rev.revision + for rev in self._get_ancestor_nodes( + [self._resolve_branch(branch_label)], + include_dependencies=False, + ) + } + # Intersection gives the root revisions we are trying to + # rollback with the downgrade. + roots = [ + is_revision(rev) + for rev in self.get_revisions( + {rev.revision for rev in roots}.intersection(ancestors) + ) + ] + + # Ensure we didn't throw everything away when filtering branches. + if len(roots) == 0: + raise RevisionError( + "Not a valid downgrade target from current heads" + ) + + heads = self.get_revisions(upper) + + # Aim is to drop :branch_revision; to do so we also need to drop its + # descendents and anything dependent on it. + downgrade_revisions = set( + self._get_descendant_nodes( + roots, + include_dependencies=True, + omit_immediate_dependencies=False, + ) + ) + active_revisions = set( + self._get_ancestor_nodes(heads, include_dependencies=True) + ) + + # Emit revisions to drop in reverse topological sorted order. + downgrade_revisions.intersection_update(active_revisions) + + if implicit_base: + # Wind other branches back to base. + downgrade_revisions.update( + active_revisions.difference(self._get_ancestor_nodes(roots)) + ) + + if ( + target_revision is not None + and not downgrade_revisions + and target_revision not in heads + ): + # Empty intersection: target revs are not present. + + raise RangeNotAncestorError("Nothing to drop", upper) + + return downgrade_revisions, heads + + def _collect_upgrade_revisions( + self, + upper: _RevisionIdentifierType, + lower: _RevisionIdentifierType, + inclusive: bool, + implicit_base: bool, + assert_relative_length: bool, + ) -> Tuple[Set[Revision], Tuple[Revision, ...]]: + """ + Compute the set of required revisions specified by :upper, and the + current set of active revisions specified by :lower. Find the + difference between the two to compute the required upgrades. + + :inclusive=True includes the current/lower revisions in the set + + :implicit_base=False only returns revisions which are downstream + of the current/lower revisions. Dependencies from branches with + different bases will not be included. + """ + targets: Collection[Revision] = [ + is_revision(rev) + for rev in self._parse_upgrade_target( + current_revisions=lower, + target=upper, + assert_relative_length=assert_relative_length, + ) + ] + + # assert type(targets) is tuple, "targets should be a tuple" + + # Handled named bases (e.g. branch@... -> heads should only produce + # targets on the given branch) + if isinstance(lower, str) and "@" in lower: + branch, _, _ = lower.partition("@") + branch_rev = self.get_revision(branch) + if branch_rev is not None and branch_rev.revision == branch: + # A revision was used as a label; get its branch instead + assert len(branch_rev.branch_labels) == 1 + branch = next(iter(branch_rev.branch_labels)) + targets = { + need for need in targets if branch in need.branch_labels + } + + required_node_set = set( + self._get_ancestor_nodes( + targets, check=True, include_dependencies=True + ) + ).union(targets) + + current_revisions = self.get_revisions(lower) + if not implicit_base and any( + rev not in required_node_set + for rev in current_revisions + if rev is not None + ): + raise RangeNotAncestorError(lower, upper) + assert ( + type(current_revisions) is tuple + ), "current_revisions should be a tuple" + + # Special case where lower = a relative value (get_revisions can't + # find it) + if current_revisions and current_revisions[0] is None: + _, rev = self._parse_downgrade_target( + current_revisions=upper, + target=lower, + assert_relative_length=assert_relative_length, + ) + assert rev + if rev == "base": + current_revisions = tuple() + lower = None + else: + current_revisions = (rev,) + lower = rev.revision + + current_node_set = set( + self._get_ancestor_nodes( + current_revisions, check=True, include_dependencies=True + ) + ).union(current_revisions) + + needs = required_node_set.difference(current_node_set) + + # Include the lower revision (=current_revisions?) in the iteration + if inclusive: + needs.update(is_revision(rev) for rev in self.get_revisions(lower)) + # By default, base is implicit as we want all dependencies returned. + # Base is also implicit if lower = base + # implicit_base=False -> only return direct downstreams of + # current_revisions + if current_revisions and not implicit_base: + lower_descendents = self._get_descendant_nodes( + [is_revision(rev) for rev in current_revisions], + check=True, + include_dependencies=False, + ) + needs.intersection_update(lower_descendents) + + return needs, tuple(targets) + + def _get_all_current( + self, id_: Tuple[str, ...] + ) -> Set[Optional[_RevisionOrBase]]: + top_revs: Set[Optional[_RevisionOrBase]] + top_revs = set(self.get_revisions(id_)) + top_revs.update( + self._get_ancestor_nodes(list(top_revs), include_dependencies=True) + ) + return self._filter_into_branch_heads(top_revs) + + +class Revision: + """Base class for revisioned objects. + + The :class:`.Revision` class is the base of the more public-facing + :class:`.Script` object, which represents a migration script. + The mechanics of revision management and traversal are encapsulated + within :class:`.Revision`, while :class:`.Script` applies this logic + to Python files in a version directory. + + """ + + nextrev: FrozenSet[str] = frozenset() + """following revisions, based on down_revision only.""" + + _all_nextrev: FrozenSet[str] = frozenset() + + revision: str = None # type: ignore[assignment] + """The string revision number.""" + + down_revision: Optional[_RevIdType] = None + """The ``down_revision`` identifier(s) within the migration script. + + Note that the total set of "down" revisions is + down_revision + dependencies. + + """ + + dependencies: Optional[_RevIdType] = None + """Additional revisions which this revision is dependent on. + + From a migration standpoint, these dependencies are added to the + down_revision to form the full iteration. However, the separation + of down_revision from "dependencies" is to assist in navigating + a history that contains many branches, typically a multi-root scenario. + + """ + + branch_labels: Set[str] = None # type: ignore[assignment] + """Optional string/tuple of symbolic names to apply to this + revision's branch""" + + _resolved_dependencies: Tuple[str, ...] + _normalized_resolved_dependencies: Tuple[str, ...] + + @classmethod + def verify_rev_id(cls, revision: str) -> None: + illegal_chars = set(revision).intersection(_revision_illegal_chars) + if illegal_chars: + raise RevisionError( + "Character(s) '%s' not allowed in revision identifier '%s'" + % (", ".join(sorted(illegal_chars)), revision) + ) + + def __init__( + self, + revision: str, + down_revision: Optional[Union[str, Tuple[str, ...]]], + dependencies: Optional[Union[str, Tuple[str, ...]]] = None, + branch_labels: Optional[Union[str, Tuple[str, ...]]] = None, + ) -> None: + if down_revision and revision in util.to_tuple(down_revision): + raise LoopDetected(revision) + elif dependencies is not None and revision in util.to_tuple( + dependencies + ): + raise DependencyLoopDetected(revision) + + self.verify_rev_id(revision) + self.revision = revision + self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision)) + self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies)) + self._orig_branch_labels = util.to_tuple(branch_labels, default=()) + self.branch_labels = set(self._orig_branch_labels) + + def __repr__(self) -> str: + args = [repr(self.revision), repr(self.down_revision)] + if self.dependencies: + args.append("dependencies=%r" % (self.dependencies,)) + if self.branch_labels: + args.append("branch_labels=%r" % (self.branch_labels,)) + return "%s(%s)" % (self.__class__.__name__, ", ".join(args)) + + def add_nextrev(self, revision: Revision) -> None: + self._all_nextrev = self._all_nextrev.union([revision.revision]) + if self.revision in revision._versioned_down_revisions: + self.nextrev = self.nextrev.union([revision.revision]) + + @property + def _all_down_revisions(self) -> Tuple[str, ...]: + return util.dedupe_tuple( + util.to_tuple(self.down_revision, default=()) + + self._resolved_dependencies + ) + + @property + def _normalized_down_revisions(self) -> Tuple[str, ...]: + """return immediate down revisions for a rev, omitting dependencies + that are still dependencies of ancestors. + + """ + return util.dedupe_tuple( + util.to_tuple(self.down_revision, default=()) + + self._normalized_resolved_dependencies + ) + + @property + def _versioned_down_revisions(self) -> Tuple[str, ...]: + return util.to_tuple(self.down_revision, default=()) + + @property + def is_head(self) -> bool: + """Return True if this :class:`.Revision` is a 'head' revision. + + This is determined based on whether any other :class:`.Script` + within the :class:`.ScriptDirectory` refers to this + :class:`.Script`. Multiple heads can be present. + + """ + return not bool(self.nextrev) + + @property + def _is_real_head(self) -> bool: + return not bool(self._all_nextrev) + + @property + def is_base(self) -> bool: + """Return True if this :class:`.Revision` is a 'base' revision.""" + + return self.down_revision is None + + @property + def _is_real_base(self) -> bool: + """Return True if this :class:`.Revision` is a "real" base revision, + e.g. that it has no dependencies either.""" + + # we use self.dependencies here because this is called up + # in initialization where _real_dependencies isn't set up + # yet + return self.down_revision is None and self.dependencies is None + + @property + def is_branch_point(self) -> bool: + """Return True if this :class:`.Script` is a branch point. + + A branchpoint is defined as a :class:`.Script` which is referred + to by more than one succeeding :class:`.Script`, that is more + than one :class:`.Script` has a `down_revision` identifier pointing + here. + + """ + return len(self.nextrev) > 1 + + @property + def _is_real_branch_point(self) -> bool: + """Return True if this :class:`.Script` is a 'real' branch point, + taking into account dependencies as well. + + """ + return len(self._all_nextrev) > 1 + + @property + def is_merge_point(self) -> bool: + """Return True if this :class:`.Script` is a merge point.""" + + return len(self._versioned_down_revisions) > 1 + + +@overload +def tuple_rev_as_scalar(rev: None) -> None: ... + + +@overload +def tuple_rev_as_scalar( + rev: Union[Tuple[_T, ...], List[_T]], +) -> Union[_T, Tuple[_T, ...], List[_T]]: ... + + +def tuple_rev_as_scalar( + rev: Optional[Sequence[_T]], +) -> Union[_T, Sequence[_T], None]: + if not rev: + return None + elif len(rev) == 1: + return rev[0] + else: + return rev + + +def is_revision(rev: Any) -> Revision: + assert isinstance(rev, Revision) + return rev diff --git a/venv/lib/python3.12/site-packages/alembic/script/write_hooks.py b/venv/lib/python3.12/site-packages/alembic/script/write_hooks.py new file mode 100644 index 0000000..3dd49d9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/script/write_hooks.py @@ -0,0 +1,181 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import importlib.util +import os +import shlex +import subprocess +import sys +from typing import Any +from typing import Callable +from typing import TYPE_CHECKING + +from .. import util +from ..util import compat +from ..util.pyfiles import _preserving_path_as_str + +if TYPE_CHECKING: + from ..config import PostWriteHookConfig + +REVISION_SCRIPT_TOKEN = "REVISION_SCRIPT_FILENAME" + +_registry: dict = {} + + +def register(name: str) -> Callable: + """A function decorator that will register that function as a write hook. + + See the documentation linked below for an example. + + .. seealso:: + + :ref:`post_write_hooks_custom` + + + """ + + def decorate(fn): + _registry[name] = fn + return fn + + return decorate + + +def _invoke( + name: str, + revision_path: str | os.PathLike[str], + options: PostWriteHookConfig, +) -> Any: + """Invokes the formatter registered for the given name. + + :param name: The name of a formatter in the registry + :param revision: string path to the revision file + :param options: A dict containing kwargs passed to the + specified formatter. + :raises: :class:`alembic.util.CommandError` + """ + revision_path = _preserving_path_as_str(revision_path) + try: + hook = _registry[name] + except KeyError as ke: + raise util.CommandError( + f"No formatter with name '{name}' registered" + ) from ke + else: + return hook(revision_path, options) + + +def _run_hooks( + path: str | os.PathLike[str], hooks: list[PostWriteHookConfig] +) -> None: + """Invoke hooks for a generated revision.""" + + for hook in hooks: + name = hook["_hook_name"] + try: + type_ = hook["type"] + except KeyError as ke: + raise util.CommandError( + f"Key '{name}.type' (or 'type' in toml) is required " + f"for post write hook {name!r}" + ) from ke + else: + with util.status( + f"Running post write hook {name!r}", newline=True + ): + _invoke(type_, path, hook) + + +def _parse_cmdline_options(cmdline_options_str: str, path: str) -> list[str]: + """Parse options from a string into a list. + + Also substitutes the revision script token with the actual filename of + the revision script. + + If the revision script token doesn't occur in the options string, it is + automatically prepended. + """ + if REVISION_SCRIPT_TOKEN not in cmdline_options_str: + cmdline_options_str = REVISION_SCRIPT_TOKEN + " " + cmdline_options_str + cmdline_options_list = shlex.split( + cmdline_options_str, posix=compat.is_posix + ) + cmdline_options_list = [ + option.replace(REVISION_SCRIPT_TOKEN, path) + for option in cmdline_options_list + ] + return cmdline_options_list + + +def _get_required_option(options: dict, name: str) -> str: + try: + return options[name] + except KeyError as ke: + raise util.CommandError( + f"Key {options['_hook_name']}.{name} is required for post " + f"write hook {options['_hook_name']!r}" + ) from ke + + +def _run_hook( + path: str, options: dict, ignore_output: bool, command: list[str] +) -> None: + cwd: str | None = options.get("cwd", None) + cmdline_options_str = options.get("options", "") + cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path) + + kw: dict[str, Any] = {} + if ignore_output: + kw["stdout"] = kw["stderr"] = subprocess.DEVNULL + + subprocess.run([*command, *cmdline_options_list], cwd=cwd, **kw) + + +@register("console_scripts") +def console_scripts( + path: str, + options: dict, + ignore_output: bool = False, + verify_version: tuple[int, ...] | None = None, +) -> None: + entrypoint_name = _get_required_option(options, "entrypoint") + for entry in compat.importlib_metadata_get("console_scripts"): + if entry.name == entrypoint_name: + impl: Any = entry + break + else: + raise util.CommandError( + f"Could not find entrypoint console_scripts.{entrypoint_name}" + ) + + if verify_version: + pyscript = ( + f"import {impl.module}; " + f"assert tuple(int(x) for x in {impl.module}.__version__.split('.')) >= {verify_version}, " # noqa: E501 + f"'need exactly version {verify_version} of {impl.name}'; " + f"{impl.module}.{impl.attr}()" + ) + else: + pyscript = f"import {impl.module}; {impl.module}.{impl.attr}()" + + command = [sys.executable, "-c", pyscript] + _run_hook(path, options, ignore_output, command) + + +@register("exec") +def exec_(path: str, options: dict, ignore_output: bool = False) -> None: + executable = _get_required_option(options, "executable") + _run_hook(path, options, ignore_output, command=[executable]) + + +@register("module") +def module(path: str, options: dict, ignore_output: bool = False) -> None: + module_name = _get_required_option(options, "module") + + if importlib.util.find_spec(module_name) is None: + raise util.CommandError(f"Could not find module {module_name}") + + command = [sys.executable, "-m", module_name] + _run_hook(path, options, ignore_output, command) diff --git a/venv/lib/python3.12/site-packages/alembic/templates/async/README b/venv/lib/python3.12/site-packages/alembic/templates/async/README new file mode 100644 index 0000000..e0d0858 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/async/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/templates/async/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..95776385ecb0ba6603861d4a28c06419c08b48c1 GIT binary patch literal 3399 zcmb7GO>Eo96&{L|L{YY6$Fbws*<|dxcC<~bP2$uow%x+Yw#7EB5d^!3^+lk`u}p;` z)gfhD-lXu>MSp+=ie5ICJ>;;57EXa2dhEr%_7cGkDS}q)cqOrv5Omy5R@5b!3Ni^hRne9-i7>4^5~=UDOKFxak9PYJ_3@b)VX@Q4 zc3@9fK)P`0R5-J@-f4HZ3KE&9 zCy}}Y{lVUTF7q#xUCc>=wqn`rmg}roCFm(t*R_!g9ETac z8uiSWDVvHVJ&y0RT`l#_7J(7ThHYesn_%{yb^1<9VK0&d_ayM!iG?y?(!^KDHR4I@ z5I!G*9^`DuACh$`m(;%pUcFm$XvHdV9Zl#_%b_#w%8Cv4o1qogWV4z^p}fCpc{H?T zjWdt>s|>qnP`YmUtF&D6e5%tA?*357>yAks7dQ_6uJFbcnBvw-t6dHSjk;m5s;@8G zOw3wkoKfAQ>&&)iDJ|5gch9O;EvF+8J4{9=Xq zx~cnm&~IBl-1MP81Q^k7q?4UImXxVEod z-@5f&NjH_@hBAEEmwi4ub2u_~I5PR@`%j0j{3jKUCthZVHrPx}Hd2%Osi}VxDL(OB z%{JAG4fW!IO23qW@g;J_(6NUG*-Z^E%*eYl@rAdNfd*!61B=lHKM)}Vvm7y@^#`Z` zDLP~2cFqC2Qy~8%SSOFAZdUfpIt4YNY!-Rn)!#vqj{4rbN8l&j73$=F@}9KwKDL;P z@pGU=#7izN!ib-V7<>q+vrs{tpja3ud>F`J&~%F@UB=G?e;&oY1JxGk_~p^qAB)eF zAN?&g@PD`=4LY6-FJ$E1Onl*7620Fk8138ew|)XPxeZ9xYkj|C{c;D9J2Oeo=qdKL zI(9RqE?$>ULFC7=9rQhv2jo5}W4^ssBHzuCZON2B0_jg<59Oc7 z6JqsKvF1F9pSUwVxfaF_NYmfTaCa!1#(NGRB0#ujFh}Q>3!ojsFG4FKG(c|z{sK4g zO3*#eH7qfP_}fmf@CNC*QX*7M_n>Mn79^^itymjD3NL@d24#ymjiX078Zk&CKziY8 zAUG4^V$pJ}h{rJ*19h=x+jl@ifKLM4LZD(yBQA+(4w9zjRb7w4lIgYxULavMV4zVN zro&mB7rr^V@XDt^;vM+C&!PG!c?I~@M~1%kT+KAqv4%SK+1H!nbB*!2C(HZlHxIKH zo}U?cc=b0|UnZ5I#MbSjJ~A-g%uY74ll$2z#HgD`Sp8Nz%#1cO6OGKo!Q_qo%v@8Q zd#cVMlHClY5+ps+)ZS=lZ|rNAn#!eya_J}uqpuFtxmQOh_euzg3UTk|@lk@r>9My0 z8~y3RP~qFs?yyv#iQN$y>GS6c)AH_hsW6$?y`dmIp9cP3Oe#z%d-6G?$E3pB%HDVa z=}8G>_NFAHrxS&5B=@dJg?!)M)g;n$D)3KaG`a)xsfE_+&F0gvwEF^z;yTI~d^qM} zoAmA}PZ_7@C9{~5gQW15gPVtsBx|I1Y9hv*E*) zBZ37Zz5ES-w!Lu??r@CoUJbEZ1ph!g-tn1S#0Q6fcX5-7s1}D6Cke8a7u~c)eB<%U zu%I}Cu)?wWE#TH{_71-e1BgoAHdGKolJsYic|pGVcQXEhTz)~`2I5GCtM=Cye|2#y z0i>ce<*|l5_Eet!LdpDct(l!}WTy|58Hnqlb6eTgz_2uN^p+w`JbLqpK=b5m3!7FZ jE78`JEL{dcWqfNsRC6F-_(B/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.12/site-packages/alembic/templates/async/env.py b/venv/lib/python3.12/site-packages/alembic/templates/async/env.py new file mode 100644 index 0000000..9f2d519 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/async/env.py @@ -0,0 +1,89 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + connectable = async_engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako b/venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/async/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.12/site-packages/alembic/templates/generic/README b/venv/lib/python3.12/site-packages/alembic/templates/generic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/generic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/templates/generic/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03fea979056322b8e12dc583da3d3b1e6882d49a GIT binary patch literal 2605 zcmZuz&1)M+6ra_uq}A#xwj(F8T{CtgD+-Aer%6LgX^7JgLW>C@J;WEW+8s-)tajDe zk!_h6TJLg^_`a>%tfhaUP5IM-ex>>y`x4yB~fn~NO`KK0G6RVCQ|!do%CN z`;GocCKUwZS?;NELq_N?&ICj361&?#JV83r1rybUIYHob$PCrRInkp!*elR)+66ba|+B+nB%Z6Qac;? z>CbCjlFDGA1e}0cVc_G3`F!jjZkrceW!^B!ZQGhRD!>gAt72HBG*9h%NeiS>!?sP( zMnS^J5}VcfF%gOa!xOjrH+-HT*3;F3boL|&5*i$B1+j3Ts6_&*KR-eY%Ht0`q>H*_ z2+zZ7k}nYsul5QW*!pvD*z4$sW#Xt6=K@Peb%g)cLcR^8E%H8^Mvkx;63_$i!zD}N z1GFgQqv{t>p?ggW*NqBQnPFQFHY}X8=jTmGU=G)9ofH%W!?<5H9PB5vNr{75m2j44 z5-%F8ifc`WVHJOK?`zLqwRCLRAhGc0UO6to61!Qc_9YlRs+vX`Of8$lTU8~LVAa8k z#54;S&$O`f&}cLatKtRpJz*E!OX^GaJQfu zCXdiP$!*m%VgGip{Q_AcT9c6rdF+KJb%S})DhBuHFzSW3xy*~`Xw+yhY&Th>$+}LL zQ_i=hYHC$dZxx!2NRcz@!9m15PdlcHMIo2I&KEEhi`&O>H5inC-9-=%g9w&D~UYO794 zPStK0^R1FY=z^hYaH)Xsp zf?%IZJ=KB3+iE?Qxs?;wbKzT8qC9SI^zdOBk_PzDg*Jp(x6=@+^^l;q>ag|%bTClR zr@GL`iP{NB@I168`d(MiBUDTFBLq}{0N2rqz|w*AB*=awu)#q8Tnu&q2-9E1bAN2~ z{U5O$K4u1NU=#g9SGq+Q-MYLx30beEd+)=R2{DF1&6~&ki!Q^I+x0{zbIu5$)-1ZmvK)0pPPxw@ zJo5Cz&gixF=(XR<8}dh6>9Os>;U|}WzPuNeh9aw9?Ie(r>BPp`v9XQV_zsGMv(GMi zop!D*pW8Y!@%znpVkoBU$tacSB&XWRsg2|XXu4Q-J2BKrOtuq~n~C$#kFo5299DZS z_7z}vxY0phc;K9e?X9P;%uI{xH#0NY@Qc$j&@Zy#nL_Nv#VDtz!!sX8c{wOyO=`{N zlWxkiD}afWf}df^d&yP$)$=Rj#uw`g=n2DtiZ{PFl|1V~Bg zh$C%r/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.12/site-packages/alembic/templates/generic/env.py b/venv/lib/python3.12/site-packages/alembic/templates/generic/env.py new file mode 100644 index 0000000..36112a3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/generic/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako b/venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/generic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.12/site-packages/alembic/templates/multidb/README b/venv/lib/python3.12/site-packages/alembic/templates/multidb/README new file mode 100644 index 0000000..f046ec9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/multidb/README @@ -0,0 +1,12 @@ +Rudimentary multi-database configuration. + +Multi-DB isn't vastly different from generic. The primary difference is that it +will run the migrations N times (depending on how many databases you have +configured), providing one engine name and associated context for each run. + +That engine name will then allow the migration to restrict what runs within it to +just the appropriate migrations for that engine. You can see this behavior within +the mako template. + +In the provided configuration, you'll need to have `databases` provided in +alembic's config, and an `sqlalchemy.url` provided for each engine name. diff --git a/venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/templates/multidb/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea8d41ea3964b6639e96d68cca89f4a397e92b25 GIT binary patch literal 4859 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako b/venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako new file mode 100644 index 0000000..7684646 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/multidb/alembic.ini.mako @@ -0,0 +1,157 @@ +# a multi-database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = ${script_location} + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# for multiple database configuration, new named sections are added +# which each include a distinct ``sqlalchemy.url`` entry. A custom value +# ``databases`` is added which indicates a listing of the per-database sections. +# The ``databases`` entry as well as the URLs present in the ``[engine1]`` +# and ``[engine2]`` sections continue to be consumed by the user-maintained env.py +# script only. + +databases = engine1, engine2 + +[engine1] +sqlalchemy.url = driver://user:pass@localhost/dbname + +[engine2] +sqlalchemy.url = driver://user:pass@localhost/dbname2 + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py b/venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py new file mode 100644 index 0000000..e937b64 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/multidb/env.py @@ -0,0 +1,140 @@ +import logging +from logging.config import fileConfig +import re + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +USE_TWOPHASE = False + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) +logger = logging.getLogger("alembic.env") + +# gather section names referring to different +# databases. These are named "engine1", "engine2" +# in the sample .ini file. +db_names = config.get_main_option("databases", "") + +# add your model's MetaData objects here +# for 'autogenerate' support. These must be set +# up to hold just those tables targeting a +# particular database. table.tometadata() may be +# helpful here in case a "copy" of +# a MetaData is needed. +# from myapp import mymodel +# target_metadata = { +# 'engine1':mymodel.metadata1, +# 'engine2':mymodel.metadata2 +# } +target_metadata = {} + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + # for the --sql use case, run migrations for each URL into + # individual files. + + engines = {} + for name in re.split(r",\s*", db_names): + engines[name] = rec = {} + rec["url"] = context.config.get_section_option(name, "sqlalchemy.url") + + for name, rec in engines.items(): + logger.info("Migrating database %s" % name) + file_ = "%s.sql" % name + logger.info("Writing output to %s" % file_) + with open(file_, "w") as buffer: + context.configure( + url=rec["url"], + output_buffer=buffer, + target_metadata=target_metadata.get(name), + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + with context.begin_transaction(): + context.run_migrations(engine_name=name) + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # for the direct-to-DB use case, start a transaction on all + # engines, then run all migrations, then commit all transactions. + + engines = {} + for name in re.split(r",\s*", db_names): + engines[name] = rec = {} + rec["engine"] = engine_from_config( + context.config.get_section(name, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + for name, rec in engines.items(): + engine = rec["engine"] + rec["connection"] = conn = engine.connect() + + if USE_TWOPHASE: + rec["transaction"] = conn.begin_twophase() + else: + rec["transaction"] = conn.begin() + + try: + for name, rec in engines.items(): + logger.info("Migrating database %s" % name) + context.configure( + connection=rec["connection"], + upgrade_token="%s_upgrades" % name, + downgrade_token="%s_downgrades" % name, + target_metadata=target_metadata.get(name), + ) + context.run_migrations(engine_name=name) + + if USE_TWOPHASE: + for rec in engines.values(): + rec["transaction"].prepare() + + for rec in engines.values(): + rec["transaction"].commit() + except: + for rec in engines.values(): + rec["transaction"].rollback() + raise + finally: + for rec in engines.values(): + rec["connection"].close() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako b/venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako new file mode 100644 index 0000000..8e667d8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/multidb/script.py.mako @@ -0,0 +1,51 @@ +<%! +import re + +%>"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade(engine_name: str) -> None: + """Upgrade schema.""" + globals()["upgrade_%s" % engine_name]() + + +def downgrade(engine_name: str) -> None: + """Downgrade schema.""" + globals()["downgrade_%s" % engine_name]() + +<% + db_names = config.get_main_option("databases") +%> + +## generate an "upgrade_() / downgrade_()" function +## for each database name in the ini file. + +% for db_name in re.split(r',\s*', db_names): + +def upgrade_${db_name}() -> None: + """Upgrade ${db_name} schema.""" + ${context.get("%s_upgrades" % db_name, "pass")} + + +def downgrade_${db_name}() -> None: + """Downgrade ${db_name} schema.""" + ${context.get("%s_downgrades" % db_name, "pass")} + +% endfor diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/README b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/README new file mode 100644 index 0000000..fdacc05 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/README @@ -0,0 +1 @@ +pyproject configuration, based on the generic configuration. \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7143d4d06bfe74bd9ce76ebc8e47e89d371f5b31 GIT binary patch literal 2607 zcmZuz%}*Og6rc63?cMcPfS4pe(LiGyM+pvT(x_F{C?stawL(#p9>T?1b_cW0dUrcJ z28WvB_Bu;&^>%~ABOFQpt-kW)E z-f#RzCZi%4&x%jYYcfKAaV8pKhuGc(;t4X4Ay}v`ObY_1V^*v#PKyC80xeBT0WDea zdSW^uAd;wMBUjydI!ThX6J0){zUz%4A{?(<5kf2QAD>nX*+_gZO{*{`VNSuiMD29s zr#r87NGgMc8gT+@#(_^Aa1yzn~pteR)Om!cGa{=WtKYiiXKTN*KsV+ zCPBi;BAe2?F%e1v!xOjtH+-HTwy>D-qfNN9AlM~KBEMJ*9g-T5J6P@ZrYF+(&Y zQ+OUGdpWR0*kor7-U>kF^qe2ria61HYo8hn^^D8ZVC z7l>tzV?5c!-UHKhO}iQd3_W3;l3=&Nk_Cbd$1XBV=827MLJZi&t;rjAZe!-)8E`kR znHG=GImu1UvS9yCwEc0iNc09H=SnyTPwFNMqE$`q(PK0S?{HZV(bK8vGVC;%+h851 zOIhz5OSAMjQg4nosCCa*U5#pWk2Njg$8D`njFRMwpz?D}qgBExb;2}5W163_Oh%|? zRc1`v@cgu4Y8KI1#c`Q8RZ9AaFzzj3~@Ulp3-e9sJl>tA71vpr4c zc7B#N>`J%DC4utW=JEc-d*7DloH{9MGe*PGmdvH{E#f_3j$5803-}IoYOocr>}t(A zsd#gaYtA++9-;H5PRfv=`Lbosl-(v&*ZyF9;&R!8!(4PV{ee~`UKvhOpE30^BX!r( z7)acv8#*n+;&Hd>_aEL}rE~r0N!Z?de4jdemeI~>li~3QmQTG zTT*@_o!dNJ+~^%^(l+iWi?7v8ek8H}hwmjUDhu3Ai zD}rE`O9R!O#~W)^$=@i7tHt<@OGzGgf9xZ~5~K|fq62OSu}-fcRNKdb{Z$9G$KZpB zqE0o0E>hHv!Gh7R?y4gh1it9b5@ zjIRGfmi8xjU@i-MmWkhrK9NdV{eE~N99eagIe*W$HzgI{XfLIZ9~Tb#CSTf zHEKElDj_`Z*5?QU&=7&sK4fX0=jdjjGW5$*uwMO~jd(DkL5pKvLeH5Yw_ zzcsoA&OoSk{R9mc`Q?kcza1wPPiS=3k9l(TJl zpd}AH{jfcFr8Rivx0yBhqmA6qW^ezKi$7o7NlJZ*`#DSOM*lQ{K!+bk8*2;~oOGU_b->K!?_KCbOvVEyfC~RFo(%|y7(0pASdMypS Ql7{}2&b*Qa=@>-%50-aiLI3~& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako new file mode 100644 index 0000000..3d10f0e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/alembic.ini.mako @@ -0,0 +1,44 @@ +# A generic, single database configuration. + +[alembic] + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +sqlalchemy.url = driver://user:pass@localhost/dbname + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py new file mode 100644 index 0000000..36112a3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/env.py @@ -0,0 +1,78 @@ +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako new file mode 100644 index 0000000..7edd43b --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/pyproject.toml.mako @@ -0,0 +1,84 @@ +[tool.alembic] + +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = "${script_location}" + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = "%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s" +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = "%%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s" + +# additional paths to be prepended to sys.path. defaults to the current working directory. +prepend_sys_path = [ + "." +] + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# version_locations = [ +# "%(here)s/alembic/versions", +# "%(here)s/foo/bar" +# ] + + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = "utf-8" + +# This section defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples +# [[tool.alembic.post_write_hooks]] +# format using "black" - use the console_scripts runner, +# against the "black" entrypoint +# name = "black" +# type = "console_scripts" +# entrypoint = "black" +# options = "-l 79 REVISION_SCRIPT_FILENAME" +# +# [[tool.alembic.post_write_hooks]] +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# name = "ruff" +# type = "module" +# module = "ruff" +# options = "check --fix REVISION_SCRIPT_FILENAME" +# +# [[tool.alembic.post_write_hooks]] +# Alternatively, use the exec runner to execute a binary found on your PATH +# name = "ruff" +# type = "exec" +# executable = "ruff" +# options = "check --fix REVISION_SCRIPT_FILENAME" + diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README new file mode 100644 index 0000000..dfd718d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/README @@ -0,0 +1 @@ +pyproject configuration, with an async dbapi. \ No newline at end of file diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc69587dba92e2d0654a2222c9f983ba3c54c402 GIT binary patch literal 3409 zcmb7GO>Eo96&{L|L{YY6$Fbws*<|dxcC<~bP2$uow%x+Yw#7EB5d^!3^+lk`u}p;` z)gfhDZc=!g{sIdWy=*Rf$YBpHoB}!Y*o%GbC4wEu%Q}amKWJ|*lwt#)`rc5KVOn`4Qr~Tt(kxvb?e-(;qcbtWVyBO7 z!=A8!erc)S>^Ie)DoYtN#S&)HlvWf|gMJqHX=n$a?Spm@^l9a(aAt44-R^J|Br;J? zB6SJ+gT4J+=3gkgn3Do+#j@Eg*IBVj&{L|eYaX|W9HWf>H65npSTI!uG0wai?{PDv~nYN7^bVz*+-I-{oR%=nb8F^4*gnXrp@3k!GN zqrOX*LEV~e*|48&vnPsMPdXQUUQ0FC+8%NG2~%R)8n7zSKSKB>&s@%);F!q{JYG%=eyPXB3q|- zxLby;c=@Vcudt%G>Q=3ldeLKi%`#XX478TFt>t{R4&HTc%wD^m_uw#BtGaPdFEK9< zC#fu3M&4(Ys;&FLsn)BZ(xOP#JgAth)`O9gdn~qtkRyOy!rdBBZIPFwBt6*FXhWmV zCa&#i*SBsRD(R*&+)#!O`m%?kGY2DM2P2b@zx!V4FYPP9fY^WFaRr-Ytj4zNYhK@Zn%}#1~VMgAWi7&jB3^Xus8)A$$41x$DnD2-V ztv^5on9&(4x3dq}odQS@0X%skb@Q`l)+zWA<+VV7uKo_Dbkz4E0A2y4yF#7(4+4-j z0>l<`F@6q|h{(ysML_X05rYpQbrvd!7!(U5h7SW744Q87q|5ku;LoGjx1ic09ltyt z`(yFB@`Jyn2L2B>q(R5i;f0L6lZh{!OQQEX1>^o9{H-5@O>P6G^;+NWSijuC=FUvg zGkS`>t&ZJHsf*m@Q?U6-Y#V(K1^SF?nBL9e}5qP=wVwRZxyfjmz>{vP~! z_$M$@gieUnPsEz@B!1%d`1o2FJ0MMeFT*XOY#Q%5fQ_|->-zH+E$n(A0X9sBgF&GEU$_}tUw zJ@xAc*$anfMjl=L&DEDlWhk+A`>2l$j5o8BjqK!Jb_y}-<`GuE)ebVF&CEn2GqFE; zV=pt;ROg3P#E=HwtLyk)t846YNN`WLhgNFgmpB%Azag^EW}@4*VX% zBDfQ**7u9sAKl&`nA#hdey+^$91H-gB1SLfhWIqJLB@7Va4DB&!${=wa4(2jR}Fcduyq#~-tVZ}*;tmQ>F zZ4qC4{4y*kjv%aXtbPNyHJiP|ufqVMlJ^iQ2q8)OGs(OlU;aB8e?cz4Aa4V4q{3DE z>x;j-xRn4>(VFsDLmqo3Pk*jte!14nPB*gC`^pT&_0YMkY-?axnmBq(ktQC$c|@Rj pdbWj4E0dLIYf6?bgP=0LH6N None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_async_migrations() -> None: + """In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + connectable = async_engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + await connectable.dispose() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode.""" + + asyncio.run(run_async_migrations()) + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako new file mode 100644 index 0000000..7edd43b --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/pyproject.toml.mako @@ -0,0 +1,84 @@ +[tool.alembic] + +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = "${script_location}" + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = "%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s" +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = "%%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s" + +# additional paths to be prepended to sys.path. defaults to the current working directory. +prepend_sys_path = [ + "." +] + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# version_locations = [ +# "%(here)s/alembic/versions", +# "%(here)s/foo/bar" +# ] + + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = "utf-8" + +# This section defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples +# [[tool.alembic.post_write_hooks]] +# format using "black" - use the console_scripts runner, +# against the "black" entrypoint +# name = "black" +# type = "console_scripts" +# entrypoint = "black" +# options = "-l 79 REVISION_SCRIPT_FILENAME" +# +# [[tool.alembic.post_write_hooks]] +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# name = "ruff" +# type = "module" +# module = "ruff" +# options = "check --fix REVISION_SCRIPT_FILENAME" +# +# [[tool.alembic.post_write_hooks]] +# Alternatively, use the exec runner to execute a binary found on your PATH +# name = "ruff" +# type = "exec" +# executable = "ruff" +# options = "check --fix REVISION_SCRIPT_FILENAME" + diff --git a/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/templates/pyproject_async/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__init__.py b/venv/lib/python3.12/site-packages/alembic/testing/__init__.py new file mode 100644 index 0000000..3291508 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/__init__.py @@ -0,0 +1,32 @@ +from sqlalchemy.testing import config +from sqlalchemy.testing import emits_warning +from sqlalchemy.testing import engines +from sqlalchemy.testing import exclusions +from sqlalchemy.testing import mock +from sqlalchemy.testing import provide_metadata +from sqlalchemy.testing import skip_if +from sqlalchemy.testing import uses_deprecated +from sqlalchemy.testing.config import combinations +from sqlalchemy.testing.config import fixture +from sqlalchemy.testing.config import requirements as requires +from sqlalchemy.testing.config import Variation +from sqlalchemy.testing.config import variation + +from .assertions import assert_raises +from .assertions import assert_raises_message +from .assertions import emits_python_deprecation_warning +from .assertions import eq_ +from .assertions import eq_ignore_whitespace +from .assertions import expect_deprecated +from .assertions import expect_raises +from .assertions import expect_raises_message +from .assertions import expect_sqlalchemy_deprecated +from .assertions import expect_sqlalchemy_deprecated_20 +from .assertions import expect_warnings +from .assertions import is_ +from .assertions import is_false +from .assertions import is_not_ +from .assertions import is_true +from .assertions import ne_ +from .fixtures import TestBase +from .util import resolve_lambda diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0237c0fc97d0e662fa62493ad7961e88b726c13a GIT binary patch literal 1444 zcmaKryKWmf6oxsLEXlGZS+ZnXmMqE64Gt1b;w-RR1Sx{8gDn%CMj9U zi)^z++B{3%z`&$gY>_TNx>S44(d1$*u!F(Rk3SE|!{M3twOWU~cn7%7>%=wS25$h@gGRU;HF=YG7r4b+#7*EmzDL{w zZu2(r9&m?uh}*#Xe4n@je83Ng_kj=jA@Kolmv@N|fsgnRaTmD9d&Eb;ecmVT0Uz^Y z;y&ChzneJv_4n5va=-qmJTHF!%++J%7f50H0mNwwDhiL%1V<8AD3v# zWh#C9Z8DvumYd%*xMlp1K|dcbSD^^~nT%GqR~r7`xOe^pGHB!>`R(9=3(8>56=O%BWnY%=EQzEmCc!)u zZ`2$2S7|P7lDeZYoxh^voifO=TQQi$`|?(B!XpaCLo^*j^kwPUdA3} z?0&}1GIl3pF7Y^H=NWsPu@~gH|6mw&f5}*n;vsnL{FJeuGxmV|QO1V!Ld@U;8|jCz F&A;FDoooOA literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/assertions.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d3ef45b9043f52d53c0d11a634e32974ea44cee GIT binary patch literal 7516 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/env.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2a8c286223519a70f8a8f44cae4cc0bbb7abbe0 GIT binary patch literal 16858 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/fixtures.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..228b1d9bcdc05bc74652445ce984db020f7174ec GIT binary patch literal 19075 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/requirements.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d4caeaef919ef9ddf548d0e547de64e440d0081 GIT binary patch literal 10438 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 UH6W||w_***>Omkmtg+bfZ#0iAApigX literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe1ae6a7c9141221f7299e94cad00bf5d75bdf2e GIT binary patch literal 5134 zcmbVQ|4$sp6`$GN+dJ+nm@hW@V%CrVSHqnHaT^@$1Q(MyX`HBbq=d>^FSmQJu(x}e z-2;Q}6xvuaI8Ke5FA&*Ma8=2;Zq)n5ulGe8RL*MM)9&o`* z)Dg_h&b;^L&71e$=k5L_5(y$`i*1+Hh6qAm(g&|G?f|c!g26nJk&HEz!GjpnF{3eq z4E(Yho8bmIXO7c+8Gew5u}|hTeAGP_mTKtcF?ja9uEP)p4XSQtm6Rn8D^@zEX%ZQx&tFqLVVO7I z(j>7fMa6iH9?p*tUVjZEbe4rfU{D+mQGi2OK9B5rX;5JDs2`oIMd%!xXGVduvdyyB zK@x2kov-JRRpmY*lxMt^0>hv@9(4@st-8Nd`UUnZn_`AhiaFUu_mSCAFnnd3VbfUV z&IIyIKk~Hm<{Z@^~x1sG6Sb`Z8JQtfcQ3L%E*L~rbW4#1 z%b1XeYzjk$p(&Cc6rhbMlM@D!JBK6_DBxyu86Y+jL1*=_WU47i(P@l;pRR4t`5E2YMyVZ}^JngT(dN|s_inX^wE5- z0ibwiXEVO!Z^ql3_ApRvGHt^TGi}MJyF8;3nvzMBcGb{%#w2xgrXIQgB*!gGD|M=l;&*t78Z{Hw*+d|)gl(+aSbBgv;*8CLOWFa?|Ke5l4hn8s zYDCFQCQ7y@6ku!?Qkl)?vN9w^fIx#lhPA_+qtoU_4OO>&RxYb4HmfOmoFfA8x&k;z zy(R(3Lyjik#iyKO+BM&~H}WJc*&hOlqv489o% zUkJ^H=K9`?uCg%m4UfV*=AL-B7l=Z;XMTJmw7VGEy&6Cb&HwRp!Qd*7YIdypP_*X4 z8?$e$)b}jbeNo@D66skEKYKe`y8>1_@kMRdO0;X8#gXncmhuEqqtfJ-16~g<>`34RZxB|OTY2?u;Ri>hO zyA^zS-)Om_*dWSdj{>R|(AaIGVQ;kY3-YPgRW6r6($rRCF#P5G_D941Jae!sVU=%2HdA%I}lUef+%SK zddmQbrX}eqk8)fhsx+hlY0jiofvcJa>TY^Q@c^Nj*k-dv*5)W;+aXaL){G&@Nuo%0 z(?y>(nN>_XJdo2TwWZw7EeRg<~1oxFWimlKntHQpa#Po|d zpX&L0-GQZ+x#J7XpR|75x>$Q@Z?XQ#<+=mYuRwP@7@e_h#F~n+rj?*D{nMMA|LjY@ zd})T9Id(g;<3^;Z7-?GMu12mzmduq%_w>N6P)#w^JogH8%UtvQwJKEqtVyl&eq&!R zwyW3iJKtmF{o=iOq|MJxsai6&7RMbp!69QjF37iC$S>h|P|C7;dFOt=wiFUH!J%;i}7O04^{7gl1u z%Yoi?phQqa&?C5NAV&w7qLnQ<>dm{F(eOMaJpa6VQ`ME~ke|W&arifPLiYeP;GwFx z!?paIi~0_(3MgDR{VI70*0w|?mFlFU4j9a%s%_!>IQTfk2Y~jQu!ZZjaZJA|u&QoR z6T1LK^GqotSBa*SRzz4PufePa|4v~#jaC`fSARRUV{ZSuQ_F$Id#eog)!&TOoeMv3 z3e#T&Bj`P@mQ9sI9e8PCaMb(=2AjS=u#hh?d^~}CS%8lxdB)tc>01K3UEptr_cAB@ zu9x;JFjg7+&8j>@IGN8gD%P=z3O*e$zu*J(=OFdqG_3%9ZUqqcyCLkPT(C*BRA7pV zKCJ4B9ZBaX{FWXEY~Fna7~ODyM>Znft4|XyyGecyC%66Zej-^G0Sry$2!WS%+izr@ zk358;A_P5~BN}-F#^eO;sDC@WBnhK(#FvoMU7?~)`u{R?)94Ntzr*$XBUE#{X4gW) zCruwW-DuobY~1%*@=yC$YL3qEUxjLJwZvye7I*-{B{Iw0jl^b{TcO5psy literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/__pycache__/warnings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57b865013ba1f996940b61517a187d5642245373 GIT binary patch literal 1112 zcmb7D%}*0S6rbr%%eJ%#^#cPUOC&K(WH;(j0zxnb4}^q71DCShPTMWJopokt*+vr& z#(zN$9=sa(C*38cDuQxD#VCC2pROxp!CB!-vF`kK`ebg;QlU^iJ$|naYG6xQ1vQw7)r}R*wW33ErlZwN zTCpodEr&~H)h1Lch`~!(Ql+xQh-Rs!kI*%_F!0$ zqL^WR91aw0hhusi$C#NF*B@M3nOndf#RAVJl_%XMg80f@=%&wDj(NW9D!97bv1erT zFS;I~{6V)}dOk`O3K*c%4AZJ@VWWyGRl~7!hS9UfuJ9 zl;C62%XW!i^rC2IK=$=Ys2AWU2BN7Z8fl=BeRS;`N;c7G1C4G!+$}Wq#fH9kfF6DM zdv>Ow&m5rHUS&6XU%!8V<_;r$(Zpx<@{tPSLrrzOp^kq{rnetA$EF)&)4!BRJn{Re z4>@ux%!v%lmt1egf=G#OJ4!y<1S|Oq9xh6C~JE$8@X`c6}!TT5#-_-IxIrTdIwi*0bwf*;NW*K*@_Lp1id2G{s1>H{aFA2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/assertions.py b/venv/lib/python3.12/site-packages/alembic/testing/assertions.py new file mode 100644 index 0000000..e76103d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/assertions.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import contextlib +import re +import sys +from typing import Any +from typing import Dict + +from sqlalchemy import exc as sa_exc +from sqlalchemy.engine import default +from sqlalchemy.engine import URL +from sqlalchemy.testing.assertions import _expect_warnings +from sqlalchemy.testing.assertions import eq_ # noqa +from sqlalchemy.testing.assertions import is_ # noqa +from sqlalchemy.testing.assertions import is_false # noqa +from sqlalchemy.testing.assertions import is_not_ # noqa +from sqlalchemy.testing.assertions import is_true # noqa +from sqlalchemy.testing.assertions import ne_ # noqa +from sqlalchemy.util import decorator + + +def _assert_proper_exception_context(exception): + """assert that any exception we're catching does not have a __context__ + without a __cause__, and that __suppress_context__ is never set. + + Python 3 will report nested as exceptions as "during the handling of + error X, error Y occurred". That's not what we want to do. we want + these exceptions in a cause chain. + + """ + + if ( + exception.__context__ is not exception.__cause__ + and not exception.__suppress_context__ + ): + assert False, ( + "Exception %r was correctly raised but did not set a cause, " + "within context %r as its cause." + % (exception, exception.__context__) + ) + + +def assert_raises(except_cls, callable_, *args, **kw): + return _assert_raises(except_cls, callable_, args, kw, check_context=True) + + +def assert_raises_context_ok(except_cls, callable_, *args, **kw): + return _assert_raises(except_cls, callable_, args, kw) + + +def assert_raises_message(except_cls, msg, callable_, *args, **kwargs): + return _assert_raises( + except_cls, callable_, args, kwargs, msg=msg, check_context=True + ) + + +def assert_raises_message_context_ok( + except_cls, msg, callable_, *args, **kwargs +): + return _assert_raises(except_cls, callable_, args, kwargs, msg=msg) + + +def _assert_raises( + except_cls, callable_, args, kwargs, msg=None, check_context=False +): + with _expect_raises(except_cls, msg, check_context) as ec: + callable_(*args, **kwargs) + return ec.error + + +class _ErrorContainer: + error: Any = None + + +@contextlib.contextmanager +def _expect_raises( + except_cls, msg=None, check_context=False, text_exact=False +): + ec = _ErrorContainer() + if check_context: + are_we_already_in_a_traceback = sys.exc_info()[0] + try: + yield ec + success = False + except except_cls as err: + ec.error = err + success = True + if msg is not None: + if text_exact: + assert str(err) == msg, f"{msg} != {err}" + else: + assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}" + if check_context and not are_we_already_in_a_traceback: + _assert_proper_exception_context(err) + print(str(err).encode("utf-8")) + + # assert outside the block so it works for AssertionError too ! + assert success, "Callable did not raise an exception" + + +def expect_raises(except_cls, check_context=True): + return _expect_raises(except_cls, check_context=check_context) + + +def expect_raises_message( + except_cls, msg, check_context=True, text_exact=False +): + return _expect_raises( + except_cls, msg=msg, check_context=check_context, text_exact=text_exact + ) + + +def eq_ignore_whitespace(a, b, msg=None): + a = re.sub(r"^\s+?|\n", "", a) + a = re.sub(r" {2,}", " ", a) + b = re.sub(r"^\s+?|\n", "", b) + b = re.sub(r" {2,}", " ", b) + + assert a == b, msg or "%r != %r" % (a, b) + + +_dialect_mods: Dict[Any, Any] = {} + + +def _get_dialect(name): + if name is None or name == "default": + return default.DefaultDialect() + else: + d = URL.create(name).get_dialect()() + + if name == "postgresql": + d.implicit_returning = True + elif name == "mssql": + d.legacy_schema_aliasing = False + d.default_schema_name = "dbo" + return d + + +def expect_warnings(*messages, **kw): + """Context manager which expects one or more warnings. + + With no arguments, squelches all SAWarnings emitted via + sqlalchemy.util.warn and sqlalchemy.util.warn_limited. Otherwise + pass string expressions that will match selected warnings via regex; + all non-matching warnings are sent through. + + The expect version **asserts** that the warnings were in fact seen. + + Note that the test suite sets SAWarning warnings to raise exceptions. + + """ + return _expect_warnings(Warning, messages, **kw) + + +def emits_python_deprecation_warning(*messages): + """Decorator form of expect_warnings(). + + Note that emits_warning does **not** assert that the warnings + were in fact seen. + + """ + + @decorator + def decorate(fn, *args, **kw): + with _expect_warnings(DeprecationWarning, assert_=False, *messages): + return fn(*args, **kw) + + return decorate + + +def expect_deprecated(*messages, **kw): + return _expect_warnings(DeprecationWarning, messages, **kw) + + +def expect_sqlalchemy_deprecated(*messages, **kw): + return _expect_warnings(sa_exc.SADeprecationWarning, messages, **kw) + + +def expect_sqlalchemy_deprecated_20(*messages, **kw): + return _expect_warnings(sa_exc.RemovedIn20Warning, messages, **kw) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/env.py b/venv/lib/python3.12/site-packages/alembic/testing/env.py new file mode 100644 index 0000000..ad4de78 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/env.py @@ -0,0 +1,581 @@ +import importlib.machinery +import logging +import os +from pathlib import Path +import shutil +import textwrap + +from sqlalchemy.testing import config +from sqlalchemy.testing import provision + +from . import util as testing_util +from .. import command +from .. import script +from .. import util +from ..script import Script +from ..script import ScriptDirectory + + +def _get_staging_directory(): + if provision.FOLLOWER_IDENT: + return f"scratch_{provision.FOLLOWER_IDENT}" + else: + return "scratch" + + +_restore_log = None + + +def _replace_logger(): + global _restore_log + if _restore_log is None: + _restore_log = (logging.root, logging.Logger.manager) + logging.root = logging.RootLogger(logging.WARNING) + logging.Logger.root = logging.root + logging.Logger.manager = logging.Manager(logging.root) + + +def _restore_logger(): + global _restore_log + + if _restore_log is not None: + logging.root, logging.Logger.manager = _restore_log + logging.Logger.root = logging.root + _restore_log = None + + +def staging_env(create=True, template="generic", sourceless=False): + _replace_logger() + cfg = _testing_config() + if create: + path = _join_path(_get_staging_directory(), "scripts") + assert not os.path.exists(path), ( + "staging directory %s already exists; poor cleanup?" % path + ) + + command.init(cfg, path, template=template) + if sourceless: + try: + # do an import so that a .pyc/.pyo is generated. + util.load_python_file(path, "env.py") + except AttributeError: + # we don't have the migration context set up yet + # so running the .env py throws this exception. + # theoretically we could be using py_compiler here to + # generate .pyc/.pyo without importing but not really + # worth it. + pass + assert sourceless in ( + "pep3147_envonly", + "simple", + "pep3147_everything", + ), sourceless + make_sourceless( + _join_path(path, "env.py"), + "pep3147" if "pep3147" in sourceless else "simple", + ) + + sc = script.ScriptDirectory.from_config(cfg) + return sc + + +def clear_staging_env(): + from sqlalchemy.testing import engines + + engines.testing_reaper.close_all() + shutil.rmtree(_get_staging_directory(), True) + _restore_logger() + + +def script_file_fixture(txt): + dir_ = _join_path(_get_staging_directory(), "scripts") + path = _join_path(dir_, "script.py.mako") + with open(path, "w") as f: + f.write(txt) + + +def env_file_fixture(txt): + dir_ = _join_path(_get_staging_directory(), "scripts") + txt = ( + """ +from alembic import context + +config = context.config +""" + + txt + ) + + path = _join_path(dir_, "env.py") + pyc_path = util.pyc_file_from_path(path) + if pyc_path: + os.unlink(pyc_path) + + with open(path, "w") as f: + f.write(txt) + + +def _sqlite_file_db(tempname="foo.db", future=False, scope=None, **options): + dir_ = _join_path(_get_staging_directory(), "scripts") + url = "sqlite:///%s/%s" % (dir_, tempname) + if scope: + options["scope"] = scope + return testing_util.testing_engine(url=url, future=future, options=options) + + +def _sqlite_testing_config(sourceless=False, future=False): + dir_ = _join_path(_get_staging_directory(), "scripts") + url = f"sqlite:///{dir_}/foo.db" + + sqlalchemy_future = future or ("future" in config.db.__class__.__module__) + + return _write_config_file( + f""" +[alembic] +script_location = {dir_} +sqlalchemy.url = {url} +sourceless = {"true" if sourceless else "false"} +{"sqlalchemy.future = true" if sqlalchemy_future else ""} + +[loggers] +keys = root,sqlalchemy + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = DEBUG +handlers = +qualname = sqlalchemy.engine + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + """ + ) + + +def _multi_dir_testing_config(sourceless=False, extra_version_location=""): + dir_ = _join_path(_get_staging_directory(), "scripts") + sqlalchemy_future = "future" in config.db.__class__.__module__ + + url = "sqlite:///%s/foo.db" % dir_ + + return _write_config_file( + f""" +[alembic] +script_location = {dir_} +sqlalchemy.url = {url} +sqlalchemy.future = {"true" if sqlalchemy_future else "false"} +sourceless = {"true" if sourceless else "false"} +path_separator = space +version_locations = %(here)s/model1/ %(here)s/model2/ %(here)s/model3/ \ +{extra_version_location} + +[loggers] +keys = root + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + """ + ) + + +def _no_sql_pyproject_config(dialect="postgresql", directives=""): + """use a postgresql url with no host so that + connections guaranteed to fail""" + dir_ = _join_path(_get_staging_directory(), "scripts") + + return _write_toml_config( + f""" +[tool.alembic] +script_location ="{dir_}" +{textwrap.dedent(directives)} + + """, + f""" +[alembic] +sqlalchemy.url = {dialect}:// + +[loggers] +keys = root + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + +""", + ) + + +def _no_sql_testing_config(dialect="postgresql", directives=""): + """use a postgresql url with no host so that + connections guaranteed to fail""" + dir_ = _join_path(_get_staging_directory(), "scripts") + return _write_config_file( + f""" +[alembic] +script_location ={dir_} +sqlalchemy.url = {dialect}:// +{directives} + +[loggers] +keys = root + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + +""" + ) + + +def _write_toml_config(tomltext, initext): + cfg = _write_config_file(initext) + with open(cfg.toml_file_name, "w") as f: + f.write(tomltext) + return cfg + + +def _write_config_file(text): + cfg = _testing_config() + with open(cfg.config_file_name, "w") as f: + f.write(text) + return cfg + + +def _testing_config(): + from alembic.config import Config + + if not os.access(_get_staging_directory(), os.F_OK): + os.mkdir(_get_staging_directory()) + return Config( + _join_path(_get_staging_directory(), "test_alembic.ini"), + _join_path(_get_staging_directory(), "pyproject.toml"), + ) + + +def write_script( + scriptdir, rev_id, content, encoding="ascii", sourceless=False +): + old = scriptdir.revision_map.get_revision(rev_id) + path = old.path + + content = textwrap.dedent(content) + if encoding: + content = content.encode(encoding) + with open(path, "wb") as fp: + fp.write(content) + pyc_path = util.pyc_file_from_path(path) + if pyc_path: + os.unlink(pyc_path) + script = Script._from_path(scriptdir, path) + old = scriptdir.revision_map.get_revision(script.revision) + if old.down_revision != script.down_revision: + raise Exception("Can't change down_revision on a refresh operation.") + scriptdir.revision_map.add_revision(script, _replace=True) + + if sourceless: + make_sourceless( + path, "pep3147" if sourceless == "pep3147_everything" else "simple" + ) + + +def make_sourceless(path, style): + import py_compile + + py_compile.compile(path) + + if style == "simple": + pyc_path = util.pyc_file_from_path(path) + suffix = importlib.machinery.BYTECODE_SUFFIXES[0] + filepath, ext = os.path.splitext(path) + simple_pyc_path = filepath + suffix + shutil.move(pyc_path, simple_pyc_path) + pyc_path = simple_pyc_path + else: + assert style in ("pep3147", "simple") + pyc_path = util.pyc_file_from_path(path) + + assert os.access(pyc_path, os.F_OK) + + os.unlink(path) + + +def three_rev_fixture(cfg): + a = util.rev_id() + b = util.rev_id() + c = util.rev_id() + + script = ScriptDirectory.from_config(cfg) + script.generate_revision(a, "revision a", refresh=True, head="base") + write_script( + script, + a, + f"""\ +"Rev A" +revision = '{a}' +down_revision = None + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 1") + + +def downgrade(): + op.execute("DROP STEP 1") + +""", + ) + + script.generate_revision(b, "revision b", refresh=True, head=a) + write_script( + script, + b, + f"""# coding: utf-8 +"Rev B, méil, %3" +revision = '{b}' +down_revision = '{a}' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 2") + + +def downgrade(): + op.execute("DROP STEP 2") + +""", + encoding="utf-8", + ) + + script.generate_revision(c, "revision c", refresh=True, head=b) + write_script( + script, + c, + f"""\ +"Rev C" +revision = '{c}' +down_revision = '{b}' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 3") + + +def downgrade(): + op.execute("DROP STEP 3") + +""", + ) + return a, b, c + + +def multi_heads_fixture(cfg, a, b, c): + """Create a multiple head fixture from the three-revs fixture""" + + # a->b->c + # -> d -> e + # -> f + d = util.rev_id() + e = util.rev_id() + f = util.rev_id() + + script = ScriptDirectory.from_config(cfg) + script.generate_revision( + d, "revision d from b", head=b, splice=True, refresh=True + ) + write_script( + script, + d, + f"""\ +"Rev D" +revision = '{d}' +down_revision = '{b}' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 4") + + +def downgrade(): + op.execute("DROP STEP 4") + +""", + ) + + script.generate_revision( + e, "revision e from d", head=d, splice=True, refresh=True + ) + write_script( + script, + e, + f"""\ +"Rev E" +revision = '{e}' +down_revision = '{d}' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 5") + + +def downgrade(): + op.execute("DROP STEP 5") + +""", + ) + + script.generate_revision( + f, "revision f from b", head=b, splice=True, refresh=True + ) + write_script( + script, + f, + f"""\ +"Rev F" +revision = '{f}' +down_revision = '{b}' + +from alembic import op + + +def upgrade(): + op.execute("CREATE STEP 6") + + +def downgrade(): + op.execute("DROP STEP 6") + +""", + ) + + return d, e, f + + +def _multidb_testing_config(engines): + """alembic.ini fixture to work exactly with the 'multidb' template""" + + dir_ = _join_path(_get_staging_directory(), "scripts") + + sqlalchemy_future = "future" in config.db.__class__.__module__ + + databases = ", ".join(engines.keys()) + engines = "\n\n".join( + f"[{key}]\nsqlalchemy.url = {value.url}" + for key, value in engines.items() + ) + + return _write_config_file( + f""" +[alembic] +script_location = {dir_} +sourceless = false +sqlalchemy.future = {"true" if sqlalchemy_future else "false"} +databases = {databases} + +{engines} +[loggers] +keys = root + +[handlers] +keys = console + +[logger_root] +level = WARNING +handlers = console +qualname = + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatters] +keys = generic + +[formatter_generic] +format = %%(levelname)-5.5s [%%(name)s] %%(message)s +datefmt = %%H:%%M:%%S + """ + ) + + +def _join_path(base: str, *more: str): + return str(Path(base).joinpath(*more).as_posix()) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/fixtures.py b/venv/lib/python3.12/site-packages/alembic/testing/fixtures.py new file mode 100644 index 0000000..73e4212 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/fixtures.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +import configparser +from contextlib import contextmanager +import io +import os +import re +import shutil +from typing import Any +from typing import Dict +from typing import Generator +from typing import Literal +from typing import overload + +from sqlalchemy import Column +from sqlalchemy import create_mock_engine +from sqlalchemy import inspect +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import testing +from sqlalchemy import text +from sqlalchemy.testing import config +from sqlalchemy.testing import mock +from sqlalchemy.testing.assertions import eq_ +from sqlalchemy.testing.fixtures import FutureEngineMixin +from sqlalchemy.testing.fixtures import TablesTest as SQLAlchemyTablesTest +from sqlalchemy.testing.fixtures import TestBase as SQLAlchemyTestBase +from sqlalchemy.testing.util import drop_all_tables_from_metadata + +import alembic +from .assertions import _get_dialect +from .env import _get_staging_directory +from ..environment import EnvironmentContext +from ..migration import MigrationContext +from ..operations import Operations +from ..util import sqla_compat +from ..util.sqla_compat import sqla_2 + +testing_config = configparser.ConfigParser() +testing_config.read(["test.cfg"]) + + +class TestBase(SQLAlchemyTestBase): + is_sqlalchemy_future = sqla_2 + + @testing.fixture() + def clear_staging_dir(self): + yield + location = _get_staging_directory() + for filename in os.listdir(location): + file_path = os.path.join(location, filename) + if os.path.isfile(file_path) or os.path.islink(file_path): + os.unlink(file_path) + elif os.path.isdir(file_path): + shutil.rmtree(file_path) + + @contextmanager + def pushd(self, dirname) -> Generator[None, None, None]: + current_dir = os.getcwd() + try: + os.chdir(dirname) + yield + finally: + os.chdir(current_dir) + + @testing.fixture() + def pop_alembic_config_env(self): + yield + os.environ.pop("ALEMBIC_CONFIG", None) + + @testing.fixture() + def ops_context(self, migration_context): + with migration_context.begin_transaction(_per_migration=True): + yield Operations(migration_context) + + @testing.fixture + def migration_context(self, connection): + return MigrationContext.configure( + connection, opts=dict(transaction_per_migration=True) + ) + + @testing.fixture + def as_sql_migration_context(self, connection): + return MigrationContext.configure( + connection, opts=dict(transaction_per_migration=True, as_sql=True) + ) + + @testing.fixture + def connection(self): + global _connection_fixture_connection + + with config.db.connect() as conn: + _connection_fixture_connection = conn + yield conn + + _connection_fixture_connection = None + + @testing.fixture + def restore_operations(self): + """Restore runners for modified operations""" + + saved_impls = None + op_cls = None + + def _save_attrs(_op_cls): + nonlocal saved_impls, op_cls + saved_impls = _op_cls._to_impl._registry.copy() + op_cls = _op_cls + + yield _save_attrs + + if op_cls is not None and saved_impls is not None: + op_cls._to_impl._registry = saved_impls + + @config.fixture() + def metadata(self, request): + """Provide bound MetaData for a single test, dropping afterwards.""" + + from sqlalchemy.sql import schema + + metadata = schema.MetaData() + request.instance.metadata = metadata + yield metadata + del request.instance.metadata + + if ( + _connection_fixture_connection + and _connection_fixture_connection.in_transaction() + ): + trans = _connection_fixture_connection.get_transaction() + trans.rollback() + with _connection_fixture_connection.begin(): + drop_all_tables_from_metadata( + metadata, _connection_fixture_connection + ) + else: + drop_all_tables_from_metadata(metadata, config.db) + + +_connection_fixture_connection = None + + +class TablesTest(TestBase, SQLAlchemyTablesTest): + pass + + +FutureEngineMixin.is_sqlalchemy_future = True + + +def capture_db(dialect="postgresql://"): + buf = [] + + def dump(sql, *multiparams, **params): + buf.append(str(sql.compile(dialect=engine.dialect))) + + engine = create_mock_engine(dialect, dump) + return engine, buf + + +_engs: Dict[Any, Any] = {} + + +@overload +@contextmanager +def capture_context_buffer( + bytes_io: Literal[True], **kw: Any +) -> Generator[io.BytesIO, None, None]: ... + + +@overload +@contextmanager +def capture_context_buffer( + **kw: Any, +) -> Generator[io.StringIO, None, None]: ... + + +@contextmanager +def capture_context_buffer( + **kw: Any, +) -> Generator[io.StringIO | io.BytesIO, None, None]: + if kw.pop("bytes_io", False): + buf = io.BytesIO() + else: + buf = io.StringIO() + + kw.update({"dialect_name": "sqlite", "output_buffer": buf}) + conf = EnvironmentContext.configure + + def configure(*arg, **opt): + opt.update(**kw) + return conf(*arg, **opt) + + with mock.patch.object(EnvironmentContext, "configure", configure): + yield buf + + +@contextmanager +def capture_engine_context_buffer( + **kw: Any, +) -> Generator[io.StringIO, None, None]: + from .env import _sqlite_file_db + from sqlalchemy import event + + buf = io.StringIO() + + eng = _sqlite_file_db() + + conn = eng.connect() + + @event.listens_for(conn, "before_cursor_execute") + def bce(conn, cursor, statement, parameters, context, executemany): + buf.write(statement + "\n") + + kw.update({"connection": conn}) + conf = EnvironmentContext.configure + + def configure(*arg, **opt): + opt.update(**kw) + return conf(*arg, **opt) + + with mock.patch.object(EnvironmentContext, "configure", configure): + yield buf + + +def op_fixture( + dialect="default", + as_sql=False, + naming_convention=None, + literal_binds=False, + native_boolean=None, +): + opts = {} + if naming_convention: + opts["target_metadata"] = MetaData(naming_convention=naming_convention) + + class buffer_: + def __init__(self): + self.lines = [] + + def write(self, msg): + msg = msg.strip() + msg = re.sub(r"[\n\t]", "", msg) + if as_sql: + # the impl produces soft tabs, + # so search for blocks of 4 spaces + msg = re.sub(r" ", "", msg) + msg = re.sub(r"\;\n*$", "", msg) + + self.lines.append(msg) + + def flush(self): + pass + + buf = buffer_() + + class ctx(MigrationContext): + def get_buf(self): + return buf + + def clear_assertions(self): + buf.lines[:] = [] + + def assert_(self, *sql): + # TODO: make this more flexible about + # whitespace and such + eq_(buf.lines, [re.sub(r"[\n\t]", "", s) for s in sql]) + + def assert_contains(self, sql): + for stmt in buf.lines: + if re.sub(r"[\n\t]", "", sql) in stmt: + return + else: + assert False, "Could not locate fragment %r in %r" % ( + sql, + buf.lines, + ) + + if as_sql: + opts["as_sql"] = as_sql + if literal_binds: + opts["literal_binds"] = literal_binds + + ctx_dialect = _get_dialect(dialect) + if native_boolean is not None: + ctx_dialect.supports_native_boolean = native_boolean + # this is new as of SQLAlchemy 1.2.7 and is used by SQL Server, + # which breaks assumptions in the alembic test suite + ctx_dialect.non_native_boolean_check_constraint = True + if not as_sql: + + def execute(stmt, *multiparam, **param): + if isinstance(stmt, str): + stmt = text(stmt) + assert stmt.supports_execution + sql = str(stmt.compile(dialect=ctx_dialect)) + + buf.write(sql) + + connection = mock.Mock(dialect=ctx_dialect, execute=execute) + else: + opts["output_buffer"] = buf + connection = None + context = ctx(ctx_dialect, connection, opts) + + alembic.op._proxy = Operations(context) + return context + + +class AlterColRoundTripFixture: + # since these tests are about syntax, use more recent SQLAlchemy as some of + # the type / server default compare logic might not work on older + # SQLAlchemy versions as seems to be the case for SQLAlchemy 1.1 on Oracle + + __requires__ = ("alter_column",) + + def setUp(self): + self.conn = config.db.connect() + self.ctx = MigrationContext.configure(self.conn) + self.op = Operations(self.ctx) + self.metadata = MetaData() + + def _compare_type(self, t1, t2): + c1 = Column("q", t1) + c2 = Column("q", t2) + assert not self.ctx.impl.compare_type( + c1, c2 + ), "Type objects %r and %r didn't compare as equivalent" % (t1, t2) + + def _compare_server_default(self, t1, s1, t2, s2): + c1 = Column("q", t1, server_default=s1) + c2 = Column("q", t2, server_default=s2) + assert not self.ctx.impl.compare_server_default( + c1, c2, s2, s1 + ), "server defaults %r and %r didn't compare as equivalent" % (s1, s2) + + def tearDown(self): + sqla_compat._safe_rollback_connection_transaction(self.conn) + with self.conn.begin(): + self.metadata.drop_all(self.conn) + self.conn.close() + + def _run_alter_col(self, from_, to_, compare=None): + column = Column( + from_.get("name", "colname"), + from_.get("type", String(10)), + nullable=from_.get("nullable", True), + server_default=from_.get("server_default", None), + # comment=from_.get("comment", None) + ) + t = Table("x", self.metadata, column) + + with sqla_compat._ensure_scope_for_ddl(self.conn): + t.create(self.conn) + insp = inspect(self.conn) + old_col = insp.get_columns("x")[0] + + # TODO: conditional comment support + self.op.alter_column( + "x", + column.name, + existing_type=column.type, + existing_server_default=( + column.server_default + if column.server_default is not None + else False + ), + existing_nullable=True if column.nullable else False, + # existing_comment=column.comment, + nullable=to_.get("nullable", None), + # modify_comment=False, + server_default=to_.get("server_default", False), + new_column_name=to_.get("name", None), + type_=to_.get("type", None), + ) + + insp = inspect(self.conn) + new_col = insp.get_columns("x")[0] + + if compare is None: + compare = to_ + + eq_( + new_col["name"], + compare["name"] if "name" in compare else column.name, + ) + self._compare_type( + new_col["type"], compare.get("type", old_col["type"]) + ) + eq_(new_col["nullable"], compare.get("nullable", column.nullable)) + self._compare_server_default( + new_col["type"], + new_col.get("default", None), + compare.get("type", old_col["type"]), + ( + compare["server_default"].text + if "server_default" in compare + else ( + column.server_default.arg.text + if column.server_default is not None + else None + ) + ), + ) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/plugin/__init__.py b/venv/lib/python3.12/site-packages/alembic/testing/plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bd69520250ac1e328769610e62182b61f8f8a51 GIT binary patch literal 224 zcmZ8bIS#@w5R5q_gv1}{5CyD&7Ew_neqcFaNtPtGWt$+U;Tbd(w7i99@B);M3a4eH z-I-afH1isbhEc#{a;KKx?fOU43zu8uqbb`xBhv}4X@9;~$W)0S;={QRHPx_1n>(dp zhnMl*s1qj6f)>6+ZZoCn=($BxQG!sRte~J6w0D`3tNCIHmK@HsNOF;)1yNubQvwdH nBbkCO&M8UYoFqBU=h}Y<>r#bm+?amo!@}5Be7cM=(+%neEm%M> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/plugin/__pycache__/bootstrap.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e58edb6fb03fc9d0e14d42380187ec14f0dc3463 GIT binary patch literal 285 zcmXw#Jx;?w5QTS>2qH_lgF6%" + + +class CompareCheckConstraint: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + return ( + isinstance(other, schema.CheckConstraint) + and self.constraint.name == other.name + and (str(self.constraint.sqltext) == str(other.sqltext)) + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + + def __ne__(self, other): + return not self.__eq__(other) + + +class CompareForeignKey: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.ForeignKeyConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +class ComparePrimaryKey: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.PrimaryKeyConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +class CompareUniqueConstraint: + def __init__(self, constraint): + self.constraint = constraint + + def __eq__(self, other): + r1 = ( + isinstance(other, schema.UniqueConstraint) + and self.constraint.name == other.name + and (other.table.name == self.constraint.table.name) + and other.table.schema == self.constraint.table.schema + ) + if not r1: + return False + + for c1, c2 in zip_longest(self.constraint.columns, other.columns): + if (c1 is None and c2 is not None) or ( + c2 is None and c1 is not None + ): + return False + if CompareColumn(c1) != c2: + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py new file mode 100644 index 0000000..3da498d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/__init__.py @@ -0,0 +1,7 @@ +from .test_autogen_comments import * # noqa +from .test_autogen_computed import * # noqa +from .test_autogen_diffs import * # noqa +from .test_autogen_fks import * # noqa +from .test_autogen_identity import * # noqa +from .test_environment import * # noqa +from .test_op import * # noqa diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..98b898111a4759dbb7753c27fbfca8bb579ec8a2 GIT binary patch literal 455 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�W*uZ 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 J3V(^z|38SEu>b%7 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_comments.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a15c60284e122853702b063002da1705d80d443e GIT binary patch literal 6715 zcmdUzU1%HG6@c$Z8fi2dX>G@e?d)!?q=~aGu@yVn)Xj#JWYg@zCMIQF*owk*tb1+C z8A;Bak-Jj8kV2qdLLq%hX!Bs`F4Q=%c`Wp$@B8AiUMhwd0^NPcn`i(2qjc9Nlr>j66yia z1L=U!gP;e~L7|6052ZswmqC})GU#C%F~hk?Is&>vqo$IJrlUevL08kN&|{#-(lLny z$a{opHwlfi2#c+R+?}MgQeyD6$9OR)IpKE;W-*rsO}=Cn3>y^X3bT!ihHYT;hc?gV zr?Gj$m^9gA$sN{pzGxSwS^iS?W4p-N1heeNLANQ$uItc0lq+Pe;cVD;!LV2cSM&-_ zgJtU?h&zOlv_wfdK&5n$2GSuKyh+kB4S@_(8DxZpK`Jx?GD;PYDvg4SQ5B?CiVbQ` zZ*@d`P;7WBi8oDp9R7cS8{fPGVa? zr;JHjG>vk$oVv)YYj$BSHO_vMyvhr+EMr@#Iimy{wq^=**{PClF+QKou+%)u&!^1n zWNNNt&lK_}het+JR@P=ib4KQxG0m)$VY1w0Hj~181h7+95t_x7Zn#M69x20fCFdyi z)7i&a(c3*BuY;a1=$RQKKh4VLnxQwmtMOfM?^XfC4e~I)|5kc2Q2qHviC47Vik5h& zb=^LF@AT5)Kc9JUYE2ufbieg9Do52P2@*}T?y`{}o%`>^{}{hJxperej-!k6H%e^r zXSYZHkl2DHiN8Vo3gF!fgYXRF)fod-`rs6*GP-RTI}N7Vs$$Ps4NtAU&~VFfF|k6u z(P`rxA3NWNq8r2n-;Zh!3V4YSj;8B$8kE*!#}%l5jE&0?KYQZ`aWI-1$7T8JAZpgd zlW^r65L7nITu!ydxM9FsIQ5@E_;9;u-o5ZycxnFr{DaAr@;~gg}tNx|+|5NKge`ys5L}WYs!f_5?w@>fX9IrJpTIsj7O^MjAhzpGAf*+E7d0M(`^;*)7R}P zEyrnMg&nRLVx9Q&xGnd1F^*wcg&l#8@W=cJQi~p#A4Sm&;$GLonl|J0-n%@~nqi}H z$Q4+~`W%Fhm)!-z&AJSU0OG~{?U^;@ojS@_`-Z+&hFbuBYL5Wl5lDw^d{&~+$bH3g zp)Vr@Z)X)Sj#~Kb?T3)>1n6I~ z2@D46!_|!VHuT)~j(GQ{ts$<(%grWWuoLTdAa;#32Eb$Ye(~4KYuZnqJSXyP=-Ui( z2!R`M#Rnz638!8uuAXQ6zN1Q`n9_jMM95CS$%!)C;c45yRHdl6Qaj z1-jfU7#@c4JcR-yVkeO+z{g_A1M<>x(CMtPmM@y7_~C$n6fFO zXxhtB-{|=}MuNe<6FfiuOTSR1rxr)ogI+$-+CDIlt{?GRSjPGrh;|L)EA59b!5qMk zSG3Lzh4c+pdf%w@9;x&uAC0{IaA@R``F4O=f3bsqfz#W@*$fAZnXib(j7>Mm6yN5gKjewrNye z@BhpDM4UrsiYg61o37h<1H-JKV1xKfk73 z5U#xueMsQ#(p>}kLru{iAB;nCR?i!dN!1-y*WvYAG*Q=d{d&LwM}RQ2YqRaTF&|oI!CG#W@hmM7$qF_KClSwJ0#)wN8V$Nh)0jZhY`) zU-HJ~%}}@8vw6Hz?%y2fkdJL1IUuJtPl&n!S>3!Uf!w+(9h2Qxj+h8*U}%c^zt=gjrBhSNlgEj6Ef&iYfmG D9TtIZ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_computed.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..029d4fa3383f462e4016dce52c1667c332f53c5e GIT binary patch literal 8513 zcmeHMUu+Xc8lPQz?X|sjY$p_&gy4h%HG#N+K<^TurAa7(619c93il*yCZ=vMGiKqL%{bM_c z1EsXpN!Lj{`_0Vv&CEA5`+dJ}_8(ran?Rb_ay9Yy03lyv!AcI3c<@gk#)w2DCP`9E zlwpu&lWf!mPg{~r*`s#7&JHve<#d_@+7Wf=v;$~o)Csgx;*)&J6?FmalH5sm$`keI zv?u9F2~k0(1)#lAuTFb`_CK>MS9o%RDAhz4{z0CX@KWC%-MCsObN zkxFHk9K2~aw27AXvEj0NSO_zkC*7M&#xrSEKC8Av+0mRy_NLN6IoqJJOQt~bM`TrO z5mgar?wHt-l4Qy8E#fs{=?6@y2K ze6~l9tFbd8O#_R9EeQo%obXU-uMDI;m5HB(Cy%|pCMt3q=WM0i-h2!-V?-uVMj}yG zVqj0$s9myMAW=@TL++3`$ek>a`R<@4o_|UXSRp6;c=)+45Nn+zH_#?q)V->-2c!h} zdEw_PkS-~Z_VoF~0d1YRRx%Y;xjB>S=~ZP3bpsv!akCtY>+7!^i{lDgi3I|e^+7*m zV??#;NjJ7EOWo#17gl#$%T-WG1=VF0Eky^J0p=)CJ;m$8OncZt1!&X+Dxd02P&p;1 zRYj{=K7D;>W9dw+N2W?b0Yx>ZoW_$QQ6I=t{gAMlU6GTWnk}xLRZx>q=+ULfiA+k4 zh#gXIQtVIkM_Od%q?+l8w998gZ_-RRoEs(5Ble}_m~tZ1lj!V=DKb5sh|7`Fa{6>6 zndpf0^r78};J0eO#mAChZ@snbgtVG15oa#u#Bj(wRD810EmtvxEl3@dkovG{T z(}Y;3aj;`B!~RXHZD3s?+d9xgX@G2q-1P)zJyp}5s#(weY0v&yPumxsw!6XVY;fan z^MZr;ONZTyHd0#khwa17b0xuH_XCh4OST?YTi^QYut2c&xnq&vvLbb?iZXhVU`tgl zS7O!NQIKq(+HabC){`MiB-tlR7I1L+zVk<`Teq*)_~x7(H%&dPajmzGAp@k_yObMC zmbSZy&QY?GV}Kc8ZeAlha4v&x_q*mfhGw@J?Dfi!cz7|(#kJG& z@^ZNz4el~?G6j!8ySmmYs@SET2J9+t(ZyEM8o^pm(6qwhAFy{En%;|}*9-2sr zv@dp2?$d0YnT*ElqPSU_Ta+Zz=-tB#ipM1!pm<8D6Eiet6c4b*Dx$_)mXof>D&Bha zC>|Qko)%NG##`?&^<$+wp(N6ZDyHKy#e<`{@>a>Fs8n+*r;_LZZ*a66+u2k-{B~%Z zl<4eKG$yX#@F7eTS5#Yv_9JG@+^`m=3jOt~+KAOeaN!j|0BA3~Yc55HS@ZD??;Y+O z>A1RiqU_q4&st{6_s{UJLFJqfoD+O^h2TiT)wS298DS^VyRL4zw&RngkD9J4*^N81 zLc^kqbGa9M#21|PR!w`Wvfj{r!nn51m8}~)GJ0g9W}+io79MVWXoJc{2XP6*ts~VJ z501Qb@$h)%48Qp6$0j})|ee`8Bq zdE4ha*BWpZH;I>jRFn8a8@8u4td?o~UpI+MO&gf8oT&vCBY_y*8#t)>> zhb~J`Z3c_?jPLid^t3$}F?)Z^nLQeU-f1;t$7l_bFJQI_Gu_;I0WxJy%ci-YTd+a5 zc)ns5kG_cQVa(9l(XE)Fp`*2!p>?C%G1~^2=0hAL)0iZ8ioHpd)h4)AZ-JD8&|`>yqsNIJLTUtWc0aY>NxF! zsejjrO9ZD*_uv%wVfG4Uxf#<}q4*sp`!=wdlf6|gjErwCBvdQRwbQ*oKjL9qLR&)(JC21JS_d`HqU%wRS2jWh?~vfr zeWWuhge=;vd-(;<7H}6+J$J-@4L$sDsuv!_u+31LHb0}L@1Uk=%ZA96;8?|I#rQkf zz$W9Bn{3Q(**E2X#nRDSc3>t1ZKS@ve}&p7={Uf2G6|CR-&XK&jd_px4ag6&gEkeh zrJk%-@LVhFxB+&E?ILM*fbC)i*#VnbvO&p~4{{*Lvge~_iB%E6=&3NsD&XMq92TH1 zXm#XUyI|yxnLjz+BRBDgLjs7Oi`!vbu0ZaG9oM#E++5hzjjm@|lAP9w*+m8&5XO}* zV}PQy)@B%&^U-9}GH|hjGixdDL*;Q4}@gXI#YdOT0DXuEG#6ABN#U)zkT$ zZe0aIWf3IsmVpFT1V+Cy5rV6IIYQ8EU`ceC@F-wjPt^6QiDVsKj`c^Za3^GX^-v}q z!pN!)f~ZhjOs6wys6!5=G7zgIWGP%iJuqX2cO# z8L578Hne*>w0p8|I&|drfm;XRG0m?Zc8|0zjl#aF*fdqKee~_gLz8=k-E-v|r^;(b zUz-e1Rt&oy7mQ_v8Z#`L5+W0az7Qgd9*4`l=m(UuDl94#s+(y0La4KZe8Kml7o$__ zYO~&LdW5b{8H?mFbs|R)uF`m++yE$Qv5uDxi2Hrn@@%Yr*gIRCelyGQ~ zv%$RF5CC3o8Ci4b@Obb`zVcxphi~*Q%((0YSj1a_Kx@EutERM-<8JNZf&QH1T79n1 z-A<$fTxq`qEk$%e4`GI;uHXveFcyzs z)(%+_Jh0aA=@tg)O&h2{>VN48C}4%eIUHcK;ro66U%vkvxOHta9REbk48IA=g>K&U zI5*$27u|eIJ-T_IZ`X4zjjr3fok;KHS{=^zumCskF}!q-#WZ&;1~9ZYiL?-lo$3{n zW($X{2hMV9oP2z zEC8STJaN6sJSgEEj{Ct7xBfvXPzy*cv@lCVD(~8>IQwQK1;T6yMd0$LXxZ7j2XYD4|xFaAxC)K&g3XGWvf literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83dc8986724d2d7ed842dfad910a0b2845c0ad6b GIT binary patch literal 12396 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_autogen_fks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2952bdc3434123aaf7792fa9ad6807cd974d2cf4 GIT binary patch literal 34763 zcmeHQdvF`ac|SZ3&jSdM;9C!fqDWIOTGY#WShg%tl5L4eBqmLm#t4KvijY8nK0ua< zQjMCrtz0_|m1Zh>qM6XKTayu+u+w&`rqfo_>9lE+HlV4*n5#Wi9&O{M(}6@f)2RKU z-?#S$4lh!&l!;HH#opb%Zui^Ye*4>Rm*4VuTohcBomZk?xj<1r#2fjtNuGtb!E>2n zDb^IDhD`$|6Z*~IHxHPJ-vWNifQ9(2;I|G~iJt~PJwOw`4g9tN8~E+4BW53V3^>5= zWL+`muxr3Y{BH2O2i(NZfS(y)h~EQ#&wz*cz2Nr_cukaRpTYiisqKPe$Y6R20(3BqE9UVD!|qNw9FwhTws9I1xDmzYdJ`L^#Psu$1y; zmOKkz1J7lOqXtYYHDG2<0~XdiU}Y^YPy;k;1=q&X;M!RmxDL*FIv^*Z{+@17{j!g> zzd&7;ZUz|E0kJ(rVy}?nsK2aJ6T60WL2MuE2G>8zba{kUsa`lfoZ{4~PU5Hb!#A%P z6stdUh8qn*#Yhbh;<>>X7r`ovU=7Qc2RvA<3zJYSm#LKUPX2)NCWB|@6viPJYZ^19 zXywkFvMYYeRVg*rIzwNTV#vQHN|T07mxev%P}0!E)a2sO{zF%X<> z7LJ85-)RSrwZo%8^VkD;6crx(3y-)fI6{LWzzH>>a1v5XLDC^MIyg9O6|6}vHYk{f zx4{1%fySrFBt{Fub>}y`hoCCD!zbCZv2Z$??moyR&!iF~-F@7-;D`CdY3T0B?vd~) z)K_vSF%lgd4PlX^5w80=F8*A1EPAqgWHdFDh;QAzrKdX?O>vt>!jUuKQ(Uq;9OD3F zBi;DvfY05@v+$bSg~B2-LqU_9M@9uN1_6AL>LtCt1VWLTRKp^mCbt1fT%g|e)W0-v z(JcM^$l=K{OCG~=()(>!8mHJB%vNn! z`UbND@3&oPm^6Q>W%dBX#rvIC4o&ua>8W|9^KB+L**V#q*|aaS@v(X4@x>Ykq#PhA z-`kuQJ$9!nS4;WpFMBR|CQeSa&HL6}q;EQyw;U_x94oSne|*i0N3u*KexoOF@ky); zPu;~M*_E4fHI#GZdlZy)0V^IDSq07^el>d6pwouVT6ET-(}50wbX%BZ z0n54ypqijG!E3}^ej_@Y(Af-*g2Vh)xD(t21jUrnon;YF40r(3*;5Yw4B%~^=~#5q z09W3-0E7gN7Hf6Dcdzp_1m9es93+kpP1>d!r<}8UuXkK;de35Zy7=u-i8>Ty-~kS` z3)eqDK?ReGqM(9eGWJ!{zkfkdaY=Pm2^OXlm3Yi@ijrHxL;>3Q?*`i4@o1FUC|eoS zVVR7ePSp3}iBM!H96!ZXpzkSRz#oqCr&v^^M%`1c35rDY&W4^AL!`h5y$GW*aj+1beQ1~2W=k`ubE-qQ6WZg52 zf%@sugjMJE-kBPMus)-(a#z!QrN3l2l>_jx{!?a{uhyMXX17-*-a_~N`D$YseneA} zrPjAriTnkr?`48)Km=LA8f8OUh$1N}mgGT?s8pi5Sw^!IT#3{WFbSB7#;@lR@THDi zfCoM0cmh%REQZLfMEO)u7HGfWkpxcXL9I>iEUSzfN1TA~O`a)-)!MnacK%J~>dRD7 z_rBgRWqz$?p4o^gYwvM&z!6TQ#Vg zN^lgyDC#PX_TmK$Zh$bj^*RO@TR^H+!VeqAY#=ckAtshi=6*FXK@o0}6k%nqFU|eQ zjK2bKR;T@f_1Ng4r>9K>&0(=Cjr# z6)9VhUsyh6+dkQlS-&UKwfFjt8_ZKA??MH-5-Qc-NT}3Bq*5UTrzekm4G{9xfANq) zzDh9&`C39V@BctEr`Y#hGgnb&>G=ME?hU}A&<~n1Lo?T512gr?n5xWomNjn3=x=Am zd;tGh8_caRCuQu=i~x@!E+gA3jR5=bzT#ovF%11UI{VRi0vv&XH8)X*hBZ6>Fg{0W zxb!fvTA_Tk`gZ7CG&Mxl{?dEOf!TtT51!U{j!f1mN0S|!s zs(d{=Td=|=f_(GD-VF1Qi1Xtg%P=dl{?+O_+Xqk&Vu;s+O~M!h(+7hAdsm1r6^QQ< z)HkU)0Ms{2%Klx4dj4q;WDUX-mA?z071Q>c2+T|74O*~HZ!3%M2C??3z+R#5{=fk0 z;;Q@3tW>G*7Xtgr4W)oxSvs73pV*eWpmA!g@2X>a6~^3v1$UKV!LLg8emah*5}w-) zs|Ac~7E<=xJ=)N6EM1;z&qdiS(`LZ}7ISFMDr%`Ui+8%|$^GE<{ch3&cL^1}0No7gWrbs#Hf20Tl!NvUpje4Sf&s(l_Im z5l&MkW4x@@gjMUvCn6;qFsh?Uu(E`~M17SgsL}xZhXL>SkDzk`oqlvaiq6N-`6YBd zj?Mve42aYB#|sMmBqP%pD~TV}`M zOxuwKiy3D39w$IvLq6o`SJC>qYQeag5HX>U;0lF?6JRIJfu9M5o;@3mp?NmkxkI6o zVDrqyS@9LmJ$p9FbIDMM--gZn2s(SvIfxFf#|exSXc&n9q|mC}aY0g5_ge5zVx*%O z$q#Kz-o_LBa5xrCb9^7Z`~*6m1ZSEeOarkeP$IzITHJO|DTRJOL^!qeHhT^Ekt zwr-+bx1TX}f%`GjLv-Wqe$&HrV~#aZHs&q5X^w7si{3UzZ@XpQL%S9%lx=$seYex5 zZL}*F(*|2VN3XwS-Yy2)kVD_?xM>UA51Ka6jbfRwBoX+pT#}^+weSsiRJe)8OmQ@8 zVXd-e$=Ob$f8owj&Qn_Dvk2*l!Wz?FdOk-BVum-IRt9*_b zr~GBIl@F{CF0C}RdBuag=17o>8)~p&zpE3)FjNIus-0<33nFwg{_uQOJ;n^ zP$#HqOP|o7#dUQj9X|~Dz@*6)h4tTLDjbh+WGl}!E!ah*2#E{JbZT^j3*n9-vh$8( z1sBpsVIH+_zbu zh6%nti6hqq>PLZ96X$L`w0A!6=xxeU-;r%vnOU=Iu4&h83*6moT63F*-<*wVUO7p> z;>kJbrrI1u`)djM;~pvL%Lb#d6vrpY2SyP4FUB9Pxsdp;#LJ*{)dRHUk;4k6OAMeWd0EIvQuW=ll6*AqIa2oN0^TPDX~f z;V^-0!9_NQiT+mU1rGSp!6?UyJIBb&Lp%;i(-!hE{Bz(J7;K$*N`(F-exK4nj9j@m zETkV_PV-<23^vZ=kii8iTh}`xFF-&eFI%NL?8gU4g7#Hty^4;in4#D6VLV4#O#!y?(IgM}3 zsV)2{-r)v3{snN7SQw~Q@e-xKSy@|yk^)R}*k=*){Y%G;pbPv)(IT~&Ge z#TM+=8rMb3qKm?}HPYLySM2jl5GU}WZ^21>T^G$c*mLF zylu64YC%Bin=c=`bZoM1^5nHu@B1Y zU>{md*5kX_i{w6y#D1(XuL5 z^(Nzzijs~Zq;>nvmNES5lmV^DS2<-stMJ_|wap5u7S?LsrBU0ARWSo4926zXvemYw zmBv_YtEM!DY8&pB2gTM{Jp!6~j8=`wi5g{~`7j&_eue0G##B6TO#?fff?*f)?Ttw?Xh}EGHS*cA^i(pZ_gzy6j{S zDja!w)R8BN`iXAH2`Ch_<}=dkD*D>>_*I9Cku&)aF517j2yapnz+pFE=^@|&7^ZGJ zY)&r>E$ZIo*=NQ*^S*|9=?U~GiZ*uL06)1zs63-mj^35p>r~1~p@fZrYA&6kNK|D1X698OLO-(4^}uoVIJ;x(eUQ z%X0T|Qm@&jBTx1KeD2qz-lO8YS{mIruAQ0}X=!;ntZPTQ5;{QBT4iM9yiHRoyDn`< zVLNNmUAm0e$LwV^2)m(HV4y)jN=6h>K$L&2d&)d!GYo4$5fuq(aNZ6qi5kn@(!S6@ zD|rCE&`_2YVy8(Vb`;`8t#?8RX^9;tp@jSrB~%OAk_)t@Vlk1}k*50Rol1_!A%;Tj zsCj+HscZv)*De1a)WNDWo(E5UC)4#m+9 zg++8fIQUD$Z!JON+i9!=iMxGE=z6c9R)jPEJif`Sh4kZC!m+WPz%te)!*jl#I5oyo>tOKJ2*DX3>yYBrZii zPBGwlCq_npm*0pT;xFQRRAl%U(P=~Hi|CLU%VqTa9y)q%i2rSThE3VUh`RYp_&}kC zfbQKA=3j+$Qt zW_?XrUtQK87>|s*vc5*ySKk6G8pd+K*Vj@W-xn?8y}#?4*#BK-l}yyV4b!Atk%&7 z{qL8tbA1Y(XV3|u^J#Rjdy(~QK8(HyI-f)55<0JUb{g&ILT7UudYZu zSbodDkj+!#)fb6p^nI{Ms>Ew35-*sa8+@Nf6R)v~cwSAsrXukSW=};z8c7H&-pXGj z{|+=4iNDR@%Y175fz~SG!I`j1ek+Q^+g+Gnk&s3ugE@$tgZw+Ns!06hHrb<(KDA6Q!?-jPB{Vw*Ja z){QoIwFu2Ro4ThzG(t`T?uVl&`*rgN9OZCqSU3(UpmzEsxBhv}vb6F{Yid)G>4%1s zS<1P874T4+9I87L5!C^5$unbhm56M&_na~fnximRe%R?%C1I`u+31CkmIH z^^^4{(RR4VaSyskt4eJZt4IRG_KzxC>Z_Ch^D-oWDv$uu zW`g~|{u2lGAMCB5@%b5SQKi-SKg8Rwq4RZcbge9|rb^o#<+e+2F4NF?;l0o|3fek7 zyDQW2=rz9bwqCWAwr(!f)(Fc)hd|Y&SZ^2G8eEIE$E&00m`QiCqqU2^Q;&Tk2)&UD zCb>8q*cE*)ni`ebDH4WuVxhMl2qzCfZ|#%28(Lo#wt&3%KqAJJHh8#98^jZNF*@N( zlQ{BddR*4iIMJM8)=r+D?aHj%3s(4k*F_rWG$&K+pu&p!#{-9uXGg%Gf#XpRte}HH zi~J>qEI>!{)VQZ;@ledILOc`$Z-&<4{|GZJIQO=q8h@}5iPX~{S612lUtu^Ejs6;M zjq5lMjihR>7>&g6hq0#r)Uc-S5gzb=hPjbCxrV+!2d9t!26_tsx1hUAA6Z3r7h`-H zyZdIy;{pZ5rN3SrVwce2tE&{9T2vGyEnU_-I|xX^xr)K#y(jvQKY5@(2x>5#w-r1$ z6pai8kBy%A$k8C1;F9sqRFLOJ5_~F{h>IxdfSo+>_hllfV)ZB<;!*&D@Fxnycylq@1{e_uk<3TjA{8|5>rsiC~6 z&tez*2P{PanBAjqzOz@6roIma_aEqg@ z@l`169cMfd43od4Q>{?N6Joq-s!Y_`LrT=5Le-jK)=jx)pU!kXem{lkpA@0`=OqZ0 z@|jh1J4vYIsK1Uvl`#}5H#~*6)B;L=b$VRU*_V~fzF^))tVA8e=E0S+LI&hIt*|fh z6X_fs9LV4z)(}{xybI=pvi+$pWw0=-QRjrCt}dV0jZrdr?8X)7E`8$E&LZ}G5_3Jq zRK`|WufbQ-R&|ocqnVCi+28kx!U3s`%=7Ye$&DZy78t8T)0ZrQ{c(05DK7 zMsSMr1WEGI_&)zUKk<*L=q7m0Z-a&W$1i;xCk%CDatJ$IbFjlTKd!iUULQAGzlzK# z-=kngSqNb7ldP4IG#9aqTAHj`SeLP^DMLoZvc})g$rt}!y!{?J1&U3c97&yfiE?BV zd**J-kt{?a$xy8vtpakQA~`};w-jcvmMdn~3jg3&W_nBT4K%?mz!t(ap}iG3c(qD- z(Z+_nXDk|QqPlAOcvSAwjuXB8!M=|kJxUs0aL9^W1yihi$-!DE6q*$TeB?PVkVjI( z!w`Nlk={`Tc8bLNHVO)^h3hrr=C)ba9J43eymq!_j@b(bPvcw|?H2M;!vJxhwO20% zZB|3PQc~iAV&W*W@bkk*I-_wQJi?%92@d$vYaGvWYy|=VW7EH)4CIYiCh@7Uc!=kt zBdw_9iGS^lWSQPxMmmh6{RD-hkeA$Z&6Qbyz|b7th?i_=Sprwp5G=)2DdW!TP)nuo zB5GPPF9~Q-B~IvgZ5UOw+%)WrAuW0nn-!^J(JIY=k`^2Xdk^&wYeJa!O*!9}VWh0nv zY+V9YG@wzD^e5s9)e;3L{s78XOguzduEK%DNCBomb#SXgK~d$IA%l=dr#!=wN!~C5 zix0pLHdWlM?+krDp;7*0`bi^#%8A`AMiu4EY+iJeVKfPpfWK2%0B zDoke;%E?6{CtB)-@lKq%7MSzN+&~U2`pOS2OM{X%AY-M*6$MRjp{JO*Ag74JQW2gQ zi;I~EOnE)kNMPV^Di;V0|DRCae*P!e_7%v;zhk&^G9v!*%Yt5CTuh!UBPNw6wFf2T zB`Bm`6=_*F+ds$bzV?JX?Wt5!mIy3mgaxO%L3yrF(7G(jcE^A*uX;J9= zq}!A6!k8FcZ_ty~;Q@FQWs=FI50+^leLpUe$|taGx(nK-b@kL4XdAHXCcl!J2z$+o zFPYri`nw559-=mv-I)h z>dk2AFcOv25YJURbQDcz80wB+#KtUOPL&b1vuR$b$`9? z-(?a(!C1>vkD62~Grg&Tj;{tkhdsPd*WY_BkXgTP-uGB#HV7i-R%wF(qjM$7T&bph za8~^I|3QH@ZYFU(OBxZ?s!x|&&nk#9Zq!&~v;CP47$@K7#_H?pFf_j>Rx@Z?MlA1z zCN$QPjB`TKB=DuufkFH%hWQh8{sJ5+pu}zH0$#^3e}iG%Br!1z|F`%gpiN$0*W$ma zzLFADUVTe_mACr9e+ORiZ=#b$M?mKtaL8X!Q07ql5Aper@wo>wSDTanFMRRe=-fhQ z0iBqvHJRrN3#8-gL`s z6W?#np%3=jt4I1R_q~|Els@Edoc>C&k|2NZ<@OciU;I5*qGt1lSf{O0o#Qw6iEheAR~u^VCM!ILsw$!BBX z7#^ZE%0oAj_Wa=@tZ1T&d=0Tv75t&_*;E2nNoCX0B##=0U`M?h{veiSTTu;0!X+c# zk4_yrgXmzcd42hgW3PBX=RO|)^a)y1CZ7Q(XEB*f?@%k>p*Fljt$K%AP5dkVjoR`K zwU+o>-l1CI`J(Nh-P8!X$!3QZDfBM(d3lS;n`!KtdSsD8@8W6uC+#LzCfGB3h9{M!5Ajg`+w`+bG~!#ss8tHI7mS%9bQO%+eA@+z>JlA6=w4`F!Pj5$#j~E z)9_@{RECK&G?uZzvvHR29PnJ6BRmg0ALj}0OZzhZxF2}GETn}@ARYidAP3XIOeh{A zd?+2th;fndBJkmOnDAlXBk>5~Bf!_i>u8Fh&QfySHA;>u0j2IPXRb6JouXs)8<-i2 z(T4BcTzWE-1LiXTV4rs+O-bU4 ztQ4*=o4B66J^m6^qQYu`ybvs83SE3F&k zU2@~%?z^~mkVxAGIU+YXWuwg9imWN(c&pjYO~>2ZIg^*0uTgg^#nv-kC%3>}Mddv} z>!hIii#@f%8z6vtEcoSJQp~hXsVKjlSe_(hLe>oB;(>i;i5Nn*r0hzx9hCw zrfH8dMz6dm1?rNgb-uDjP#WD*C~91=N}Z>54Aw@`RMkk#%a7#h!RkcKaW>s=%{eYw zob!}9nP2o-Ymxnnf_2E&lcw_2c%9S3;_`H!vHDH3dc9rF%7HzQsnDR+2HtOLn-FP9 z@7PAfHscM}_|8*ioql<1RZM>T^^X*hQW zraykxdp?&@dZiJ0GA&(6UFjWEv4NH?Aagt@na7l~EGf`P_xn=u`ss zWwK+>~a7nC{e36QJQdU*F|@foJ_cqD{pMhhbg zhf2+hUB5Ye=kQ8ve_1#N#cN{2niyFZ8ww{D-ud$E&9f`w0m!XI8`q=F>(Pe!&|Ik0 zdHZ5H+PBu$@hrfHBhLcCK=5%3Rlj>aJQpsGl)6@;2WR+4LTKi_Lg(!Jg^y;>72C?f zq4lbgrn1mcRdQc!TNc}ja#?s~O{}+i-WNNT#f};Jkx=(}u-LaS^5w+MiIwQ9WuY5; z58OCAd$#b=^*S?i4yLMK74~kzOH)-?ikd{Q^tC@bmg)hV4b!-dab_N=%*x!NtyQvb z(Qo0{b~x*3AmE&3tepX=8l1C{5D-To=w;G?GmgHs4O@$Ic5e=A0GpgX+<4SAeT9{6 ztOZorW!3AuEiTWEgF9E7bldKD?Ul7+7q+=q;W6HLOYID}?9zcQ+rXE6a z5Qz)G)WcYC1W63Zt3V7o>EM_88kX!OAW&IDEp!~;89Kas^}(>(eIOp_VZ+3Zz~B!A zg3p?$Km@_2EbRF{+PDKm>@N$kDnKYAM7W?}hBlS_%2iAP#;H`LF(tn zL2p>cXBtNpkHf7D0@R(oFBng*RQ*p!?gBC26GVqZH<0o_Lg zG@DB#&r8`cWs7+`v9pYB+wnt~N0Wfmis|e{q39civ21 zBBJ_a8vZd0%ru+j3C|+$5*7~JdfVv8ydx~SEfV91C^d-~pUNe|5+>^jP`Pv8uq?>F z6rE>p(Z8mvM3>7m-mCEvT@YK_>2{V^Wzu-vcCEecn_gx`4om<-s@;wot)u%#Ql7UI zPIkNg?M}UHI{j`cx|KzDwXIR3$k1?%F(NrtN~V=WE}O_srqhOKr_HkMy1KLe#ds35ju>$HqxJ$w1k`jXEUiMCN4|r znC3Xvp0)=aB7>1f9Q$LiX(dZ+(pJ^x$cS&^vd{o+(QecHR4<^S>Dra&m>=(6e~Z)n z4oIzDKMFP75A}Z+>R&tb>NkVSy9QbrS*7Oc%#-*qqnfw{<1$Lp(N-y>42{%VLohl#i< zIc;=XhzoHB!T>%geOa2)oKxSBp*qG9Xf}jQDtk#vPr{8SBYkRed~yopZG$)>NK$p+ zCB#_AZ&2UIQXW(~i2!9K1PuaMGVy}*wA$oE5XNm-vYKNAAqD;>s{AOO1z4J9^UMqN zVO)JUCk|joE_jbJp9|ucJnsS(SM*?v$+Q1R|FiGcl&hZ8?InA~6f9-7JZ}Iy$MQ0H zpKGM*nYcfFrfp&seBO?6O#7WUgyTu){W6a}rVUdY4I{&tPudS`f78*ri4 zyc@WEcXzEBCvo}D&?89h0|IaaAg2Sec#WD4GE_bYuq`$uez5!pcBA?cPzJT1IqZ2lXNdGLMIj~%&nJT5Sc95f(l90#dM)>^GS zC6OfCkUEVmDFvSn%*fxtN~X~Oqq!%^F_mt^m3Qsi^>&V$az*U&pgWiBv!tOe#tgy# zGcmu*_wRtwiPL595G2TGX1K#Y1_5JTS7V&POk746OE>5VgTAEU%c75EdRTn&ypo)_ z^7hV-RE_)9^L9Fyl+xPi9@6^@7*YEPkgL>#x_Y?tF9b`+7Mb6K?u1raj+divthMd? z^8K6dfAPUnimrccZm=*=xVYZfyx=dfrH{U-yIsH9*!_fss%Ji`zNN?(rqvh$dlsQdlH*V z%LbcOE~~@%Vt9bKUzKETTO)ByYqsUe(U;oA437Q^xK>xGCmmG(Tc3~2Ps~j$H+7fJ z09oFB^qY@of)9F+fBy0OCv%@H?~aw;0J7ZJ{Y}?Q;8AUC#=cz@jxRJXi9MxL--$id zF&T)wz}+eWcdMGlM@uKa6OVc{ejM>|Y_Gp-nyt@1EcTU!_ODvk02@ z5^l6@*|v{!TN>|J8<~B<7(@k006*RHY!d}{EQpu=sg7w|F880ZrfsasHW6@_Rbgd+ zl~r-!H$;F)ok?pV=w^)z+g(-#B3wA8yj|3sSdglCkz;e%iu3Kdox(au%p5Kmb~MR} zltX+Ry+r`JdVncQ$k6?J%9UCHs4K3Nme}lx_VN7of9kE(- zNBlOlbn1_R>=Y{^fY^mCt5MNqG)SxnXNmDO7jiRJ#6vJyk-g(ZRdHq=?&jn()bkv6 z?_U1|OVHqpDPZ81Xmb@vt%#i-__P(wZgqX#{Z;q9fp58T*Ly4C8IMkXNl>eSI1?H% zu8o1{H_XMYM0?6YFKKy$#GU~R&D_xJP;qco=-dJAhX3qn=RzSAv6@sJiS|Gt-wSHi zOCh7Bk1n)(>3RO2q1~3)xeH-f(_j9JpqiNwW8DP5_7oT^-$OsR?nb)iIJ1ioAvCZ4&EeaP<@TN>q4)PgM^pq4(m*H{+c#0C~msyBCqeZI$Slux|Rw=DB5 z_xavszV``pgm2wsDc|dx?O4ALvQLNUeSFvE59v0(pBxu?(r>l4WkHXug zoSarKLKCp&ly(ZpW0t1r|Dc*5Qtc0^eGjSq52?2Qq7FQy`W{vaTg~!kzK>bDe`$Z; Ny|?~{LYBPT{{nomYIFbq literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7ce4dd10c152aba2a74f329b284a783b2bf207f GIT binary patch literal 18792 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/testing/suite/__pycache__/test_op.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70f20ca27384125d1a990f8406e8087e3e1fd278 GIT binary patch literal 2904 zcmbtWO>7%Q6rTODy>Wh8r6iOlX$_RNE+pIhSV9!>)1OrQMW{_d?19nRGfB2xuie>k zTU#InDH2z1MIwhtaA;Ky6>va;LnXLzajBw)6_Joo550wkUV7rau^pVmv5*+cZ{N;) zZ)V>2erA8^?oJXIONSS|S7U_yi7$;2uQTi40CSBn;t-}W-PiKEqie_;z#ERCcoTTj zF%=&JKIX&}9|u0}#1)_L6M4(AfVWtO-;qx`NyR6D?{qpfq7!ah*xNY3zfT@#oj3K` zJL_5?H|rv&TPJ4->s}zNhbMXGO|vF&ddj=fyDP)fJd`PS*7X8-$~m6OmOP(v5vIIA zy%&ljM^pI%EBSon0otauD1Nfwm+}E{F@Bi`5}3prf#kDXp!A#+UNDQ&X*cV0U`)xc z$_GZxdhhGLsqhogHNj_Y3a~IuN9@?(Y^I0!vOF$UHV}~UWt2YWUE|#MX>N4d^ zDEi<)J@60HK<3FGiLOs8pH!-S6Tc=VR;=!O*1#QWU~zIO^Y!7ogJa9q_?khi%sMEO z9FoSUBS_XKVCEX(#L=Kgbf!55(-(+iG6QIgnLy(#1~kFqKrNO4+EKRB9Z~-Y@Bnab zWDO)9CdqWJu{dsq$f{&`L2U%#ZkD133{O?3r!@kMfrYS&-5`q+0l$KQyP-urRFneh zVwPg~V8#r&KNI4TDe(kq_acfEQWGrdA+DrbA9Q59>XWcUh-99uSY0(VKR>b5fBmUy zYJAz65d9$Av~L~y@zCww zAC6W>PcK`OZBTg=7HEUYM%w^1^d>Zn9ndhR#M7{1)PvOpNx=AwTk>V3Wo|S>V}Z^# zXa*ECSV^0W5uwUhJ9JvxDCqGtTL7vK4p)!8_VdBp;ZNzR{r0l;P8)naY=e*7K&J^A zt$_^F7jRkKkUNZIYYy#i&Y>1qW!hm?&!p{UDcCB6 zmfsB5?|p;cnYYe;+i|DsrIqfXMY(kHi*mIq{U~O1B)6mXe|kw|1ts6d_C6^FT6tYd zkNWW&x)d&xpb)fAkJdJbZ;Mv4@A~*v^yE&jTB;gYOV+8dLOonqu;aY=*ea4lnUxpIvH7%suoS7B&C2L ze8o^2by0e$%rU84c+aP&1Z%*afre@P1mWErRw_>0MERQdY>psgmI(-}Q5IJYTMT zE||CjhO5?u@eY8Nw4pQttiT==?MBq$P9K@6eMp4@zga~m$~t{7lspLOOhKr7y>{>n zD&RGvoOx`$Tm$D7aUACGZVAr=Su-?E`;Cm>Cqt`baFy&|tuqJilNVRXk+nF{_ssX* s(|Ye{y`N>O`|P{gvA=X(n^-r9KDvh7-^qUMv{oI;+`9URAgi|h2L+VD8~^|S literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py new file mode 100644 index 0000000..8329a1a --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/_autogen_fixtures.py @@ -0,0 +1,479 @@ +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import Literal +from typing import overload +from typing import Set + +from sqlalchemy import CHAR +from sqlalchemy import CheckConstraint +from sqlalchemy import Column +from sqlalchemy import event +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import inspect +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Numeric +from sqlalchemy import PrimaryKeyConstraint +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import Text +from sqlalchemy import text +from sqlalchemy import UniqueConstraint + +from ... import autogenerate +from ... import util +from ...autogenerate import api +from ...ddl.base import _fk_spec +from ...migration import MigrationContext +from ...operations import ops +from ...testing import config +from ...testing import eq_ +from ...testing.env import clear_staging_env +from ...testing.env import staging_env + +names_in_this_test: Set[Any] = set() + + +@event.listens_for(Table, "after_parent_attach") +def new_table(table, parent): + names_in_this_test.add(table.name) + + +def _default_include_object(obj, name, type_, reflected, compare_to): + if type_ == "table": + return name in names_in_this_test + else: + return True + + +_default_object_filters: Any = _default_include_object + +_default_name_filters: Any = None + + +class ModelOne: + __requires__ = ("unique_constraint_reflection",) + + schema: Any = None + + @classmethod + def _get_db_schema(cls): + schema = cls.schema + + m = MetaData(schema=schema) + + Table( + "user", + m, + Column("id", Integer, primary_key=True), + Column("name", String(50)), + Column("a1", Text), + Column("pw", String(50)), + Index("pw_idx", "pw"), + ) + + Table( + "address", + m, + Column("id", Integer, primary_key=True), + Column("email_address", String(100), nullable=False), + ) + + Table( + "order", + m, + Column("order_id", Integer, primary_key=True), + Column( + "amount", + Numeric(8, 2), + nullable=False, + server_default=text("0"), + ), + CheckConstraint("amount >= 0", name="ck_order_amount"), + ) + + Table( + "extra", + m, + Column("x", CHAR), + Column("uid", Integer, ForeignKey("user.id")), + ) + + return m + + @classmethod + def _get_model_schema(cls): + schema = cls.schema + + m = MetaData(schema=schema) + + Table( + "user", + m, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", Text, server_default="x"), + ) + + Table( + "address", + m, + Column("id", Integer, primary_key=True), + Column("email_address", String(100), nullable=False), + Column("street", String(50)), + UniqueConstraint("email_address", name="uq_email"), + ) + + Table( + "order", + m, + Column("order_id", Integer, primary_key=True), + Column( + "amount", + Numeric(10, 2), + nullable=True, + server_default=text("0"), + ), + Column("user_id", Integer, ForeignKey("user.id")), + CheckConstraint("amount > -1", name="ck_order_amount"), + ) + + Table( + "item", + m, + Column("id", Integer, primary_key=True), + Column("description", String(100)), + Column("order_id", Integer, ForeignKey("order.order_id")), + CheckConstraint("len(description) > 5"), + ) + return m + + +class NamingConvModel: + __requires__ = ("unique_constraint_reflection",) + configure_opts = {"conv_all_constraint_names": True} + naming_convention = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(constraint_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", + } + + @classmethod + def _get_db_schema(cls): + # database side - assume all constraints have a name that + # we would assume here is a "db generated" name. need to make + # sure these all render with op.f(). + m = MetaData() + Table( + "x1", + m, + Column("q", Integer), + Index("db_x1_index_q", "q"), + PrimaryKeyConstraint("q", name="db_x1_primary_q"), + ) + Table( + "x2", + m, + Column("q", Integer), + Column("p", ForeignKey("x1.q", name="db_x2_foreign_q")), + CheckConstraint("q > 5", name="db_x2_check_q"), + ) + Table( + "x3", + m, + Column("q", Integer), + Column("r", Integer), + Column("s", Integer), + UniqueConstraint("q", name="db_x3_unique_q"), + ) + Table( + "x4", + m, + Column("q", Integer), + PrimaryKeyConstraint("q", name="db_x4_primary_q"), + ) + Table( + "x5", + m, + Column("q", Integer), + Column("p", ForeignKey("x4.q", name="db_x5_foreign_q")), + Column("r", Integer), + Column("s", Integer), + PrimaryKeyConstraint("q", name="db_x5_primary_q"), + UniqueConstraint("r", name="db_x5_unique_r"), + CheckConstraint("s > 5", name="db_x5_check_s"), + ) + # SQLite and it's "no names needed" thing. bleh. + # we can't have a name for these so you'll see "None" for the name. + Table( + "unnamed_sqlite", + m, + Column("q", Integer), + Column("r", Integer), + PrimaryKeyConstraint("q"), + UniqueConstraint("r"), + ) + return m + + @classmethod + def _get_model_schema(cls): + from sqlalchemy.sql.naming import conv + + m = MetaData(naming_convention=cls.naming_convention) + Table( + "x1", m, Column("q", Integer, primary_key=True), Index(None, "q") + ) + Table( + "x2", + m, + Column("q", Integer), + Column("p", ForeignKey("x1.q")), + CheckConstraint("q > 5", name="token_x2check1"), + ) + Table( + "x3", + m, + Column("q", Integer), + Column("r", Integer), + Column("s", Integer), + UniqueConstraint("r", name="token_x3r"), + UniqueConstraint("s", name=conv("userdef_x3_unique_s")), + ) + Table( + "x4", + m, + Column("q", Integer, primary_key=True), + Index("userdef_x4_idx_q", "q"), + ) + Table( + "x6", + m, + Column("q", Integer, primary_key=True), + Column("p", ForeignKey("x4.q")), + Column("r", Integer), + Column("s", Integer), + UniqueConstraint("r", name="token_x6r"), + CheckConstraint("s > 5", "token_x6check1"), + CheckConstraint("s < 20", conv("userdef_x6_check_s")), + ) + return m + + +class _ComparesFKs: + def _assert_fk_diff( + self, + diff, + type_, + source_table, + source_columns, + target_table, + target_columns, + name=None, + conditional_name=None, + source_schema=None, + onupdate=None, + ondelete=None, + initially=None, + deferrable=None, + ): + # the public API for ForeignKeyConstraint was not very rich + # in 0.7, 0.8, so here we use the well-known but slightly + # private API to get at its elements + ( + fk_source_schema, + fk_source_table, + fk_source_columns, + fk_target_schema, + fk_target_table, + fk_target_columns, + fk_onupdate, + fk_ondelete, + fk_deferrable, + fk_initially, + ) = _fk_spec(diff[1]) + + eq_(diff[0], type_) + eq_(fk_source_table, source_table) + eq_(fk_source_columns, source_columns) + eq_(fk_target_table, target_table) + eq_(fk_source_schema, source_schema) + eq_(fk_onupdate, onupdate) + eq_(fk_ondelete, ondelete) + eq_(fk_initially, initially) + eq_(fk_deferrable, deferrable) + + eq_([elem.column.name for elem in diff[1].elements], target_columns) + if conditional_name is not None: + if conditional_name == "servergenerated": + fks = inspect(self.bind).get_foreign_keys(source_table) + server_fk_name = fks[0]["name"] + eq_(diff[1].name, server_fk_name) + else: + eq_(diff[1].name, conditional_name) + else: + eq_(diff[1].name, name) + + +class AutogenTest(_ComparesFKs): + def _flatten_diffs(self, diffs): + for d in diffs: + if isinstance(d, list): + yield from self._flatten_diffs(d) + else: + yield d + + @classmethod + def _get_bind(cls): + return config.db + + configure_opts: Dict[Any, Any] = {} + + @classmethod + def setup_class(cls): + staging_env() + cls.bind = cls._get_bind() + cls.m1 = cls._get_db_schema() + cls.m1.create_all(cls.bind) + cls.m2 = cls._get_model_schema() + + @classmethod + def teardown_class(cls): + cls.m1.drop_all(cls.bind) + clear_staging_env() + + def setUp(self): + self.conn = conn = self.bind.connect() + ctx_opts = { + "compare_type": True, + "compare_server_default": True, + "target_metadata": self.m2, + "upgrade_token": "upgrades", + "downgrade_token": "downgrades", + "alembic_module_prefix": "op.", + "sqlalchemy_module_prefix": "sa.", + "include_object": _default_object_filters, + "include_name": _default_name_filters, + } + if self.configure_opts: + ctx_opts.update(self.configure_opts) + self.context = context = MigrationContext.configure( + connection=conn, opts=ctx_opts + ) + + self.autogen_context = api.AutogenContext(context, self.m2) + + def tearDown(self): + self.conn.close() + + def _update_context( + self, object_filters=None, name_filters=None, include_schemas=None + ): + if include_schemas is not None: + self.autogen_context.opts["include_schemas"] = include_schemas + if object_filters is not None: + self.autogen_context._object_filters = [object_filters] + if name_filters is not None: + self.autogen_context._name_filters = [name_filters] + return self.autogen_context + + +class AutogenFixtureTest(_ComparesFKs): + + @overload + def _fixture( + self, + m1: MetaData, + m2: MetaData, + include_schemas=..., + opts=..., + object_filters=..., + name_filters=..., + *, + return_ops: Literal[True], + max_identifier_length=..., + ) -> ops.UpgradeOps: ... + + @overload + def _fixture( + self, + m1: MetaData, + m2: MetaData, + include_schemas=..., + opts=..., + object_filters=..., + name_filters=..., + *, + return_ops: Literal[False] = ..., + max_identifier_length=..., + ) -> list[Any]: ... + + def _fixture( + self, + m1: MetaData, + m2: MetaData, + include_schemas=False, + opts=None, + object_filters=_default_object_filters, + name_filters=_default_name_filters, + return_ops: bool = False, + max_identifier_length=None, + ) -> ops.UpgradeOps | list[Any]: + if max_identifier_length: + dialect = self.bind.dialect + existing_length = dialect.max_identifier_length + dialect.max_identifier_length = ( + dialect._user_defined_max_identifier_length + ) = max_identifier_length + try: + self._alembic_metadata, model_metadata = m1, m2 + for m in util.to_list(self._alembic_metadata): + m.create_all(self.bind) + + with self.bind.connect() as conn: + ctx_opts = { + "compare_type": True, + "compare_server_default": True, + "target_metadata": model_metadata, + "upgrade_token": "upgrades", + "downgrade_token": "downgrades", + "alembic_module_prefix": "op.", + "sqlalchemy_module_prefix": "sa.", + "include_object": object_filters, + "include_name": name_filters, + "include_schemas": include_schemas, + } + if opts: + ctx_opts.update(opts) + self.context = context = MigrationContext.configure( + connection=conn, opts=ctx_opts + ) + + autogen_context = api.AutogenContext(context, model_metadata) + uo = ops.UpgradeOps(ops=[]) + autogenerate._produce_net_changes(autogen_context, uo) + + if return_ops: + return uo + else: + return uo.as_diffs() + finally: + if max_identifier_length: + dialect = self.bind.dialect + dialect.max_identifier_length = ( + dialect._user_defined_max_identifier_length + ) = existing_length + + def setUp(self): + staging_env() + self.bind = config.db + + def tearDown(self): + if hasattr(self, "_alembic_metadata"): + for m in util.to_list(self._alembic_metadata): + m.drop_all(self.bind) + clear_staging_env() diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py new file mode 100644 index 0000000..7ef074f --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_comments.py @@ -0,0 +1,242 @@ +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ...testing import eq_ +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateCommentsTest(AutogenFixtureTest, TestBase): + __backend__ = True + + __requires__ = ("comments",) + + def test_existing_table_comment_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_add_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table("some_table", m1, Column("test", String(10), primary_key=True)) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_table_comment") + eq_(diffs[0][1].comment, "this is some table") + eq_(diffs[0][2], None) + + def test_remove_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table("some_table", m2, Column("test", String(10), primary_key=True)) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_table_comment") + eq_(diffs[0][1].comment, None) + + def test_alter_table_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + comment="this is some table", + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + comment="this is also some table", + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_table_comment") + eq_(diffs[0][1].comment, "this is also some table") + eq_(diffs[0][2], "this is some table") + + def test_existing_column_comment_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_add_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + diffs = self._fixture(m1, m2) + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + None, + "the amount", + ) + ] + ], + ) + + def test_remove_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float), + ) + + diffs = self._fixture(m1, m2) + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + "the amount", + None, + ) + ] + ], + ) + + def test_alter_column_comment(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the amount"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + Column("amount", Float, comment="the adjusted amount"), + ) + + diffs = self._fixture(m1, m2) + + eq_( + diffs, + [ + [ + ( + "modify_comment", + None, + "some_table", + "amount", + { + "existing_nullable": True, + "existing_type": mock.ANY, + "existing_server_default": False, + }, + "the amount", + "the adjusted amount", + ) + ] + ], + ) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py new file mode 100644 index 0000000..586691b --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_computed.py @@ -0,0 +1,157 @@ +from contextlib import nullcontext + +import sqlalchemy as sa +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import expect_warnings +from ...testing import is_ +from ...testing import is_true +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateComputedTest(AutogenFixtureTest, TestBase): + __requires__ = ("computed_columns",) + __backend__ = True + + def _fixture_ctx(self): + if config.requirements.computed_columns_warn_no_persisted.enabled: + ctx = expect_warnings() + else: + ctx = nullcontext() + return ctx + + def test_add_computed_column(self): + m1 = MetaData() + m2 = MetaData() + + Table("user", m1, Column("id", Integer, primary_key=True)) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("foo", Integer, sa.Computed("5")), + ) + + with self._fixture_ctx(): + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_column") + eq_(diffs[0][2], "user") + eq_(diffs[0][3].name, "foo") + c = diffs[0][3].computed + + is_true(isinstance(c, sa.Computed)) + is_(c.persisted, None) + eq_(str(c.sqltext), "5") + + def test_remove_computed_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("foo", Integer, sa.Computed("5")), + ) + + Table("user", m2, Column("id", Integer, primary_key=True)) + + with self._fixture_ctx(): + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_column") + eq_(diffs[0][2], "user") + c = diffs[0][3] + eq_(c.name, "foo") + + is_true(isinstance(c.computed, sa.Computed)) + is_true(isinstance(c.server_default, sa.Computed)) + + @testing.combinations( + lambda: (None, sa.Computed("bar*5")), + (lambda: (sa.Computed("bar*5"), None)), + lambda: ( + sa.Computed("bar*5"), + sa.Computed("bar * 42", persisted=True), + ), + lambda: (sa.Computed("bar*5"), sa.Computed("bar * 42")), + ) + def test_cant_change_computed_warning(self, test_case): + arg_before, arg_after = testing.resolve_lambda(test_case, **locals()) + m1 = MetaData() + m2 = MetaData() + + arg_before = [] if arg_before is None else [arg_before] + arg_after = [] if arg_after is None else [arg_after] + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_before), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_after), + ) + + with mock.patch("alembic.util.warn") as mock_warn, self._fixture_ctx(): + diffs = self._fixture(m1, m2) + + eq_( + mock_warn.mock_calls, + [mock.call("Computed default on user.foo cannot be modified")], + ) + + eq_(list(diffs), []) + + @testing.combinations( + lambda: (None, None), + lambda: (sa.Computed("5"), sa.Computed("5")), + lambda: (sa.Computed("bar*5"), sa.Computed("bar*5")), + lambda: (sa.Computed("bar*5"), sa.Computed("bar * \r\n\t5")), + ) + def test_computed_unchanged(self, test_case): + arg_before, arg_after = testing.resolve_lambda(test_case, **locals()) + m1 = MetaData() + m2 = MetaData() + + arg_before = [] if arg_before is None else [arg_before] + arg_after = [] if arg_after is None else [arg_after] + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_before), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("bar", Integer), + Column("foo", Integer, *arg_after), + ) + + with mock.patch("alembic.util.warn") as mock_warn, self._fixture_ctx(): + diffs = self._fixture(m1, m2) + eq_(mock_warn.mock_calls, []) + + eq_(list(diffs), []) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py new file mode 100644 index 0000000..75bcd37 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_diffs.py @@ -0,0 +1,273 @@ +from sqlalchemy import BigInteger +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table +from sqlalchemy.testing import in_ + +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import is_ +from ...testing import TestBase + + +class AlterColumnTest(AutogenFixtureTest, TestBase): + __backend__ = True + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, primary_key=pk)) + Table("a", m2, Column("x", Integer, comment="x", primary_key=pk)) + + alter_col = self._assert_alter_col(m1, m2, pk) + eq_(alter_col.modify_comment, "x") + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_notnull(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, nullable=False, primary_key=pk)) + Table( + "a", + m2, + Column("x", Integer, nullable=False, comment="x", primary_key=pk), + ) + + self._assert_alter_col(m1, m2, pk, nullable=False) + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_comment(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, comment="old", primary_key=pk)) + Table("a", m2, Column("x", Integer, comment="new", primary_key=pk)) + + alter_col = self._assert_alter_col(m1, m2, pk) + eq_(alter_col.existing_comment, "old") + + @testing.combinations((True,), (False,)) + @config.requirements.comments + def test_all_existings_filled_in_server_default(self, pk): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", m1, Column("x", Integer, server_default="5", primary_key=pk) + ) + Table( + "a", + m2, + Column( + "x", Integer, server_default="5", comment="new", primary_key=pk + ), + ) + + alter_col = self._assert_alter_col(m1, m2, pk) + in_("5", alter_col.existing_server_default.arg.text) + + def _assert_alter_col(self, m1, m2, pk, nullable=None): + ops = self._fixture(m1, m2, return_ops=True) + modify_table = ops.ops[-1] + alter_col = modify_table.ops[0] + + if nullable is None: + eq_(alter_col.existing_nullable, not pk) + else: + eq_(alter_col.existing_nullable, nullable) + assert alter_col.existing_type._compare_type_affinity(Integer()) + return alter_col + + +class AutoincrementTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("integer_subtype_comparisons",) + + def test_alter_column_autoincrement_none(self): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, nullable=False)) + Table("a", m2, Column("x", Integer, nullable=True)) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + def test_alter_column_autoincrement_pk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("x", Integer, primary_key=True, autoincrement=False), + ) + Table( + "a", + m2, + Column("x", BigInteger, primary_key=True, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_pk_implicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table("a", m1, Column("x", Integer, primary_key=True)) + Table("a", m2, Column("x", BigInteger, primary_key=True)) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_pk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", m1, Column("x", Integer, primary_key=True, autoincrement=True) + ) + Table( + "a", + m2, + Column("x", BigInteger, primary_key=True, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_nonpk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, autoincrement=False), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_nonpk_implicit_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger), + ) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + def test_alter_column_autoincrement_nonpk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", Integer, autoincrement=True), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", BigInteger, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) + + def test_alter_column_autoincrement_compositepk_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, primary_key=True, autoincrement=False), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, primary_key=True, autoincrement=False), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], False) + + def test_alter_column_autoincrement_compositepk_implicit_false(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True), + Column("x", Integer, primary_key=True), + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True), + Column("x", BigInteger, primary_key=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + assert "autoincrement" not in ops.ops[0].ops[0].kw + + @config.requirements.autoincrement_on_composite_pk + def test_alter_column_autoincrement_compositepk_explicit_true(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "a", + m1, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", Integer, primary_key=True, autoincrement=True), + # on SQLA 1.0 and earlier, this being present + # trips the "add KEY for the primary key" so that the + # AUTO_INCREMENT keyword is accepted by MySQL. SQLA 1.1 and + # greater the columns are just reorganized. + mysql_engine="InnoDB", + ) + Table( + "a", + m2, + Column("id", Integer, primary_key=True, autoincrement=False), + Column("x", BigInteger, primary_key=True, autoincrement=True), + ) + + ops = self._fixture(m1, m2, return_ops=True) + is_(ops.ops[0].ops[0].kw["autoincrement"], True) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py new file mode 100644 index 0000000..d69736e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_fks.py @@ -0,0 +1,1191 @@ +from sqlalchemy import Column +from sqlalchemy import ForeignKeyConstraint +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import String +from sqlalchemy import Table + +from ._autogen_fixtures import AutogenFixtureTest +from ...testing import combinations +from ...testing import config +from ...testing import eq_ +from ...testing import mock +from ...testing import TestBase + + +class AutogenerateForeignKeysTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("foreign_key_constraint_reflection",) + + def test_remove_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"]), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["test2"], + "some_table", + ["test"], + conditional_name="servergenerated", + ) + + def test_add_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"]), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], "add_fk", "user", ["test2"], "some_table", ["test"] + ) + + def test_no_change(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", Integer), + ForeignKeyConstraint(["test2"], ["some_table.id"]), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", Integer), + ForeignKeyConstraint(["test2"], ["some_table.id"]), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_no_change_composite_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + @config.requirements.foreign_key_name_reflection + def test_casing_convention_changed_so_put_drops_first(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("test", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["test2"], ["some_table.test"], name="MyFK"), + ) + + Table( + "some_table", + m2, + Column("test", String(10), primary_key=True), + ) + + # foreign key autogen currently does not take "name" into account, + # so change the def just for the purposes of testing the + # add/drop order for now. + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("test2", String(10)), + ForeignKeyConstraint(["a1"], ["some_table.test"], name="myfk"), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["test2"], + "some_table", + ["test"], + name="MyFK", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["a1"], + "some_table", + ["test"], + name="myfk", + ) + + def test_add_composite_fk_with_name(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + name="fk_test_name", + ), + ) + + diffs = self._fixture(m1, m2) + self._assert_fk_diff( + diffs[0], + "add_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + name="fk_test_name", + ) + + @config.requirements.no_name_normalize + def test_remove_composite_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + name="fk_test_name", + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("a1", String(10), server_default="x"), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + conditional_name="fk_test_name", + ) + + def test_add_fk_colkeys(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), key="tid1", primary_key=True), + Column("id_2", String(10), key="tid2", primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10), key="oid1"), + Column("other_id_2", String(10), key="oid2"), + ForeignKeyConstraint( + ["oid1", "oid2"], + ["some_table.tid1", "some_table.tid2"], + name="fk_test_name", + ), + ) + + diffs = self._fixture(m1, m2) + + self._assert_fk_diff( + diffs[0], + "add_fk", + "user", + ["other_id_1", "other_id_2"], + "some_table", + ["id_1", "id_2"], + name="fk_test_name", + ) + + def test_no_change_colkeys(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id_1", String(10), primary_key=True), + Column("id_2", String(10), primary_key=True), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10)), + Column("other_id_2", String(10)), + ForeignKeyConstraint( + ["other_id_1", "other_id_2"], + ["some_table.id_1", "some_table.id_2"], + ), + ) + + Table( + "some_table", + m2, + Column("id_1", String(10), key="tid1", primary_key=True), + Column("id_2", String(10), key="tid2", primary_key=True), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("other_id_1", String(10), key="oid1"), + Column("other_id_2", String(10), key="oid2"), + ForeignKeyConstraint( + ["oid1", "oid2"], ["some_table.tid1", "some_table.tid2"] + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + +class IncludeHooksTest(AutogenFixtureTest, TestBase): + __backend__ = True + __requires__ = ("fk_names",) + + @combinations(("object",), ("name",)) + @config.requirements.no_name_normalize + def test_remove_connection_fk(self, hook_type): + m1 = MetaData() + m2 = MetaData() + + ref = Table( + "ref", + m1, + Column("id", Integer, primary_key=True), + ) + t1 = Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.x], [ref.c.id], name="fk1") + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.y], [ref.c.id], name="fk2") + ) + + ref = Table( + "ref", + m2, + Column("id", Integer, primary_key=True), + ) + Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + ) + + if hook_type == "object": + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and reflected + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + elif hook_type == "name": + + def include_name(name, type_, parent_names): + if name == "fk1": + if type_ == "index": # MariaDB thing + return True + eq_(type_, "foreign_key_constraint") + eq_( + parent_names, + { + "schema_name": None, + "table_name": "t", + "schema_qualified_table_name": "t", + }, + ) + return False + else: + return True + + diffs = self._fixture(m1, m2, name_filters=include_name) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "t", + ["y"], + "ref", + ["id"], + conditional_name="fk2", + ) + eq_(len(diffs), 1) + + def test_add_metadata_fk(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "ref", + m1, + Column("id", Integer, primary_key=True), + ) + Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + ) + + ref = Table( + "ref", + m2, + Column("id", Integer, primary_key=True), + ) + t2 = Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + ) + t2.append_constraint( + ForeignKeyConstraint([t2.c.x], [ref.c.id], name="fk1") + ) + t2.append_constraint( + ForeignKeyConstraint([t2.c.y], [ref.c.id], name="fk2") + ) + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and not reflected + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + + self._assert_fk_diff( + diffs[0], "add_fk", "t", ["y"], "ref", ["id"], name="fk2" + ) + eq_(len(diffs), 1) + + @combinations(("object",), ("name",)) + @config.requirements.no_name_normalize + def test_change_fk(self, hook_type): + m1 = MetaData() + m2 = MetaData() + + r1a = Table( + "ref_a", + m1, + Column("a", Integer, primary_key=True), + ) + Table( + "ref_b", + m1, + Column("a", Integer, primary_key=True), + Column("b", Integer, primary_key=True), + ) + t1 = Table( + "t", + m1, + Column("x", Integer), + Column("y", Integer), + Column("z", Integer), + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.x], [r1a.c.a], name="fk1") + ) + t1.append_constraint( + ForeignKeyConstraint([t1.c.y], [r1a.c.a], name="fk2") + ) + + Table( + "ref_a", + m2, + Column("a", Integer, primary_key=True), + ) + r2b = Table( + "ref_b", + m2, + Column("a", Integer, primary_key=True), + Column("b", Integer, primary_key=True), + ) + t2 = Table( + "t", + m2, + Column("x", Integer), + Column("y", Integer), + Column("z", Integer), + ) + t2.append_constraint( + ForeignKeyConstraint( + [t2.c.x, t2.c.z], [r2b.c.a, r2b.c.b], name="fk1" + ) + ) + t2.append_constraint( + ForeignKeyConstraint( + [t2.c.y, t2.c.z], [r2b.c.a, r2b.c.b], name="fk2" + ) + ) + + if hook_type == "object": + + def include_object(object_, name, type_, reflected, compare_to): + return not ( + isinstance(object_, ForeignKeyConstraint) + and type_ == "foreign_key_constraint" + and name == "fk1" + ) + + diffs = self._fixture(m1, m2, object_filters=include_object) + elif hook_type == "name": + + def include_name(name, type_, parent_names): + if type_ == "index": + return True # MariaDB thing + + if name == "fk1": + eq_(type_, "foreign_key_constraint") + eq_( + parent_names, + { + "schema_name": None, + "table_name": "t", + "schema_qualified_table_name": "t", + }, + ) + return False + else: + return True + + diffs = self._fixture(m1, m2, name_filters=include_name) + + if hook_type == "object": + self._assert_fk_diff( + diffs[0], "remove_fk", "t", ["y"], "ref_a", ["a"], name="fk2" + ) + self._assert_fk_diff( + diffs[1], + "add_fk", + "t", + ["y", "z"], + "ref_b", + ["a", "b"], + name="fk2", + ) + eq_(len(diffs), 2) + elif hook_type == "name": + eq_( + {(d[0], d[1].name) for d in diffs}, + {("add_fk", "fk2"), ("add_fk", "fk1"), ("remove_fk", "fk2")}, + ) + + +class AutogenerateFKOptionsTest(AutogenFixtureTest, TestBase): + __backend__ = True + + def _fk_opts_fixture(self, old_opts, new_opts): + m1 = MetaData() + m2 = MetaData() + + Table( + "some_table", + m1, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m1, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("tid", Integer), + ForeignKeyConstraint(["tid"], ["some_table.id"], **old_opts), + ) + + Table( + "some_table", + m2, + Column("id", Integer, primary_key=True), + Column("test", String(10)), + ) + + Table( + "user", + m2, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("tid", Integer), + ForeignKeyConstraint(["tid"], ["some_table.id"], **new_opts), + ) + + return self._fixture(m1, m2) + + @config.requirements.fk_ondelete_is_reflected + def test_add_ondelete(self): + diffs = self._fk_opts_fixture({}, {"ondelete": "cascade"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete="cascade", + ) + + @config.requirements.fk_ondelete_is_reflected + def test_remove_ondelete(self): + diffs = self._fk_opts_fixture({"ondelete": "CASCADE"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete="CASCADE", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + ondelete=None, + ) + + def test_nochange_ondelete(self): + """test case sensitivity""" + diffs = self._fk_opts_fixture( + {"ondelete": "caSCAde"}, {"ondelete": "CasCade"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate_is_reflected + def test_add_onupdate(self): + diffs = self._fk_opts_fixture({}, {"onupdate": "cascade"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="cascade", + ) + + @config.requirements.fk_onupdate_is_reflected + def test_remove_onupdate(self): + diffs = self._fk_opts_fixture({"onupdate": "CASCADE"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="CASCADE", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ) + + @config.requirements.fk_onupdate + def test_nochange_onupdate(self): + """test case sensitivity""" + diffs = self._fk_opts_fixture( + {"onupdate": "caSCAde"}, {"onupdate": "CasCade"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_restrict + def test_nochange_ondelete_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + diffs = self._fk_opts_fixture( + {"ondelete": "restrict"}, {"ondelete": "restrict"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate_restrict + def test_nochange_onupdate_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + diffs = self._fk_opts_fixture( + {"onupdate": "restrict"}, {"onupdate": "restrict"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_noaction + def test_nochange_ondelete_noaction(self): + """test the NO ACTION option which generally comes back as None""" + + diffs = self._fk_opts_fixture( + {"ondelete": "no action"}, {"ondelete": "no action"} + ) + eq_(diffs, []) + + @config.requirements.fk_onupdate + def test_nochange_onupdate_noaction(self): + """test the NO ACTION option which generally comes back as None""" + + diffs = self._fk_opts_fixture( + {"onupdate": "no action"}, {"onupdate": "no action"} + ) + eq_(diffs, []) + + @config.requirements.fk_ondelete_restrict + def test_change_ondelete_from_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + # note that this is impossible to detect if we change + # from RESTRICT to NO ACTION on MySQL. + diffs = self._fk_opts_fixture( + {"ondelete": "restrict"}, {"ondelete": "cascade"} + ) + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ondelete=mock.ANY, # MySQL reports None, PG reports RESTRICT + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=None, + ondelete="cascade", + ) + + @config.requirements.fk_ondelete_restrict + def test_change_onupdate_from_restrict(self): + """test the RESTRICT option which MySQL doesn't report on""" + + # note that this is impossible to detect if we change + # from RESTRICT to NO ACTION on MySQL. + diffs = self._fk_opts_fixture( + {"onupdate": "restrict"}, {"onupdate": "cascade"} + ) + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate=mock.ANY, # MySQL reports None, PG reports RESTRICT + ondelete=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="cascade", + ondelete=None, + ) + + @config.requirements.fk_ondelete_is_reflected + @config.requirements.fk_onupdate_is_reflected + def test_ondelete_onupdate_combo(self): + diffs = self._fk_opts_fixture( + {"onupdate": "CASCADE", "ondelete": "SET NULL"}, + {"onupdate": "RESTRICT", "ondelete": "RESTRICT"}, + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="CASCADE", + ondelete="SET NULL", + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + onupdate="RESTRICT", + ondelete="RESTRICT", + ) + + @config.requirements.fk_initially + def test_add_initially_deferred(self): + diffs = self._fk_opts_fixture({}, {"initially": "deferred"}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="deferred", + ) + + @config.requirements.fk_initially + def test_remove_initially_deferred(self): + diffs = self._fk_opts_fixture({"initially": "deferred"}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="DEFERRED", + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + ) + + @config.requirements.fk_deferrable + @config.requirements.fk_initially + def test_add_initially_immediate_plus_deferrable(self): + diffs = self._fk_opts_fixture( + {}, {"initially": "immediate", "deferrable": True} + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially="immediate", + deferrable=True, + ) + + @config.requirements.fk_deferrable + @config.requirements.fk_initially + def test_remove_initially_immediate_plus_deferrable(self): + diffs = self._fk_opts_fixture( + {"initially": "immediate", "deferrable": True}, {} + ) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, # immediate is the default + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + initially=None, + deferrable=None, + ) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_one(self): + diffs = self._fk_opts_fixture( + {"deferrable": True, "initially": "immediate"}, + {"deferrable": True, "initially": "immediate"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_two(self): + diffs = self._fk_opts_fixture( + {"deferrable": True, "initially": "deferred"}, + {"deferrable": True, "initially": "deferred"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_initially + @config.requirements.fk_deferrable + def test_add_initially_deferrable_nochange_three(self): + diffs = self._fk_opts_fixture( + {"deferrable": None, "initially": "deferred"}, + {"deferrable": None, "initially": "deferred"}, + ) + + eq_(diffs, []) + + @config.requirements.fk_deferrable + def test_add_deferrable(self): + diffs = self._fk_opts_fixture({}, {"deferrable": True}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=None, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=True, + ) + + @config.requirements.fk_deferrable_is_reflected + def test_remove_deferrable(self): + diffs = self._fk_opts_fixture({"deferrable": True}, {}) + + self._assert_fk_diff( + diffs[0], + "remove_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=True, + conditional_name="servergenerated", + ) + + self._assert_fk_diff( + diffs[1], + "add_fk", + "user", + ["tid"], + "some_table", + ["id"], + deferrable=None, + ) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py new file mode 100644 index 0000000..3dee9fc --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_autogen_identity.py @@ -0,0 +1,226 @@ +import sqlalchemy as sa +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import MetaData +from sqlalchemy import Table + +from alembic.util import sqla_compat +from ._autogen_fixtures import AutogenFixtureTest +from ... import testing +from ...testing import config +from ...testing import eq_ +from ...testing import is_true +from ...testing import TestBase + + +class AutogenerateIdentityTest(AutogenFixtureTest, TestBase): + __requires__ = ("identity_columns",) + __backend__ = True + + def test_add_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + Table("user", m1, Column("other", sa.Text)) + + Table( + "user", + m2, + Column("other", sa.Text), + Column( + "id", + Integer, + sa.Identity(start=5, increment=7), + primary_key=True, + ), + ) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "add_column") + eq_(diffs[0][2], "user") + eq_(diffs[0][3].name, "id") + i = diffs[0][3].identity + + is_true(isinstance(i, sa.Identity)) + eq_(i.start, 5) + eq_(i.increment, 7) + + def test_remove_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column( + "id", + Integer, + sa.Identity(start=2, increment=3), + primary_key=True, + ), + ) + + Table("user", m2) + + diffs = self._fixture(m1, m2) + + eq_(diffs[0][0], "remove_column") + eq_(diffs[0][2], "user") + c = diffs[0][3] + eq_(c.name, "id") + + is_true(isinstance(c.identity, sa.Identity)) + eq_(c.identity.start, 2) + eq_(c.identity.increment, 3) + + def test_no_change_identity_column(self): + m1 = MetaData() + m2 = MetaData() + + for m in (m1, m2): + id_ = sa.Identity(start=2) + Table("user", m, Column("id", Integer, id_)) + + diffs = self._fixture(m1, m2) + + eq_(diffs, []) + + def test_dialect_kwargs_changes(self): + m1 = MetaData() + m2 = MetaData() + + if sqla_compat.identity_has_dialect_kwargs: + args = {"oracle_on_null": True, "oracle_order": True} + else: + args = {"on_null": True, "order": True} + + Table("user", m1, Column("id", Integer, sa.Identity(start=2))) + id_ = sa.Identity(start=2, **args) + Table("user", m2, Column("id", Integer, id_)) + + diffs = self._fixture(m1, m2) + if config.db.name == "oracle": + is_true(len(diffs), 1) + eq_(diffs[0][0][0], "modify_default") + else: + eq_(diffs, []) + + @testing.combinations( + (None, dict(start=2)), + (dict(start=2), None), + (dict(start=2), dict(start=2, increment=7)), + (dict(always=False), dict(always=True)), + ( + dict(start=1, minvalue=0, maxvalue=100, cycle=True), + dict(start=1, minvalue=0, maxvalue=100, cycle=False), + ), + ( + dict(start=10, increment=3, maxvalue=9999), + dict(start=10, increment=1, maxvalue=3333), + ), + ) + @config.requirements.identity_columns_alter + def test_change_identity(self, before, after): + arg_before = (sa.Identity(**before),) if before else () + arg_after = (sa.Identity(**after),) if after else () + + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, *arg_before), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer, *arg_after), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + old = diffs[5] + new = diffs[6] + + def check(kw, idt): + if kw: + is_true(isinstance(idt, sa.Identity)) + for k, v in kw.items(): + eq_(getattr(idt, k), v) + else: + is_true(idt in (None, False)) + + check(before, old) + check(after, new) + + def test_add_identity_to_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer, sa.Identity(start=2, maxvalue=1000)), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + eq_(diffs[5], None) + added = diffs[6] + + is_true(isinstance(added, sa.Identity)) + eq_(added.start, 2) + eq_(added.maxvalue, 1000) + + def test_remove_identity_from_column(self): + m1 = MetaData() + m2 = MetaData() + + Table( + "user", + m1, + Column("id", Integer, sa.Identity(start=2, maxvalue=1000)), + Column("other", sa.Text), + ) + + Table( + "user", + m2, + Column("id", Integer), + Column("other", sa.Text), + ) + + diffs = self._fixture(m1, m2) + + eq_(len(diffs[0]), 1) + diffs = diffs[0][0] + eq_(diffs[0], "modify_default") + eq_(diffs[2], "user") + eq_(diffs[3], "id") + eq_(diffs[6], None) + removed = diffs[5] + + is_true(isinstance(removed, sa.Identity)) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py new file mode 100644 index 0000000..df2d9af --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_environment.py @@ -0,0 +1,364 @@ +import io + +from ...migration import MigrationContext +from ...testing import assert_raises +from ...testing import config +from ...testing import eq_ +from ...testing import is_ +from ...testing import is_false +from ...testing import is_not_ +from ...testing import is_true +from ...testing import ne_ +from ...testing.fixtures import TestBase + + +class MigrationTransactionTest(TestBase): + __backend__ = True + + conn = None + + def _fixture(self, opts): + self.conn = conn = config.db.connect() + + if opts.get("as_sql", False): + self.context = MigrationContext.configure( + dialect=conn.dialect, opts=opts + ) + self.context.output_buffer = self.context.impl.output_buffer = ( + io.StringIO() + ) + else: + self.context = MigrationContext.configure( + connection=conn, opts=opts + ) + return self.context + + def teardown_method(self): + if self.conn: + self.conn.close() + + def test_proxy_transaction_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + + is_false(self.conn.in_transaction()) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + proxy.rollback() + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + proxy.commit() + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + with proxy: + pass + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + def go(): + with proxy: + raise Exception("hi") + + assert_raises(Exception, go) + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_explicit_rollback(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + with proxy: + is_true(self.conn.in_transaction()) + proxy.rollback() + is_false(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + + def test_proxy_transaction_contextmanager_explicit_commit(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + proxy = context.begin_transaction(_per_migration=True) + is_true(self.conn.in_transaction()) + + with proxy: + is_true(self.conn.in_transaction()) + proxy.commit() + is_false(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + + def test_transaction_per_migration_transactional_ddl(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": True} + ) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_migration_non_transactional_ddl(self): + context = self._fixture( + {"transaction_per_migration": True, "transactional_ddl": False} + ) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_transactional_ddl(self): + context = self._fixture({"transactional_ddl": True}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_true(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_true(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_non_transactional_ddl(self): + context = self._fixture({"transactional_ddl": False}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + def test_transaction_per_all_sqlmode(self): + context = self._fixture({"as_sql": True}) + + context.execute("step 1") + with context.begin_transaction(): + context.execute("step 2") + with context.begin_transaction(_per_migration=True): + context.execute("step 3") + + context.execute("step 4") + context.execute("step 5") + + if context.impl.transactional_ddl: + self._assert_impl_steps( + "step 1", + "BEGIN", + "step 2", + "step 3", + "step 4", + "COMMIT", + "step 5", + ) + else: + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + def test_transaction_per_migration_sqlmode(self): + context = self._fixture( + {"as_sql": True, "transaction_per_migration": True} + ) + + context.execute("step 1") + with context.begin_transaction(): + context.execute("step 2") + with context.begin_transaction(_per_migration=True): + context.execute("step 3") + + context.execute("step 4") + context.execute("step 5") + + if context.impl.transactional_ddl: + self._assert_impl_steps( + "step 1", + "step 2", + "BEGIN", + "step 3", + "COMMIT", + "step 4", + "step 5", + ) + else: + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + @config.requirements.autocommit_isolation + def test_autocommit_block(self): + context = self._fixture({"transaction_per_migration": True}) + + is_false(self.conn.in_transaction()) + + with context.begin_transaction(): + is_false(self.conn.in_transaction()) + with context.begin_transaction(_per_migration=True): + is_true(self.conn.in_transaction()) + + with context.autocommit_block(): + # in 1.x, self.conn is separate due to the + # execution_options call. however for future they are the + # same connection and there is a "transaction" block + # despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options[ + "isolation_level" + ], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get( + "isolation_level", None + ), + "AUTOCOMMIT", + ) + is_true(self.conn.in_transaction()) + + is_false(self.conn.in_transaction()) + is_false(self.conn.in_transaction()) + + @config.requirements.autocommit_isolation + def test_autocommit_block_no_transaction(self): + context = self._fixture({"transaction_per_migration": True}) + + is_false(self.conn.in_transaction()) + + with context.autocommit_block(): + is_true(context.connection.in_transaction()) + + # in 1.x, self.conn is separate due to the execution_options + # call. however for future they are the same connection and there + # is a "transaction" block despite autocommit + if self.is_sqlalchemy_future: + is_(context.connection, self.conn) + else: + is_not_(context.connection, self.conn) + is_false(self.conn.in_transaction()) + + eq_( + context.connection._execution_options["isolation_level"], + "AUTOCOMMIT", + ) + + ne_( + context.connection._execution_options.get("isolation_level", None), + "AUTOCOMMIT", + ) + + is_false(self.conn.in_transaction()) + + def test_autocommit_block_transactional_ddl_sqlmode(self): + context = self._fixture( + { + "transaction_per_migration": True, + "transactional_ddl": True, + "as_sql": True, + } + ) + + with context.begin_transaction(): + context.execute("step 1") + with context.begin_transaction(_per_migration=True): + context.execute("step 2") + + with context.autocommit_block(): + context.execute("step 3") + + context.execute("step 4") + + context.execute("step 5") + + self._assert_impl_steps( + "step 1", + "BEGIN", + "step 2", + "COMMIT", + "step 3", + "BEGIN", + "step 4", + "COMMIT", + "step 5", + ) + + def test_autocommit_block_nontransactional_ddl_sqlmode(self): + context = self._fixture( + { + "transaction_per_migration": True, + "transactional_ddl": False, + "as_sql": True, + } + ) + + with context.begin_transaction(): + context.execute("step 1") + with context.begin_transaction(_per_migration=True): + context.execute("step 2") + + with context.autocommit_block(): + context.execute("step 3") + + context.execute("step 4") + + context.execute("step 5") + + self._assert_impl_steps( + "step 1", "step 2", "step 3", "step 4", "step 5" + ) + + def _assert_impl_steps(self, *steps): + to_check = self.context.output_buffer.getvalue() + + self.context.impl.output_buffer = buf = io.StringIO() + for step in steps: + if step == "BEGIN": + self.context.impl.emit_begin() + elif step == "COMMIT": + self.context.impl.emit_commit() + else: + self.context.impl._exec(step) + + eq_(to_check, buf.getvalue()) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py new file mode 100644 index 0000000..a63b3f2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/suite/test_op.py @@ -0,0 +1,42 @@ +"""Test against the builders in the op.* module.""" + +from sqlalchemy import Column +from sqlalchemy import event +from sqlalchemy import Integer +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy.sql import text + +from ...testing.fixtures import AlterColRoundTripFixture +from ...testing.fixtures import TestBase + + +@event.listens_for(Table, "after_parent_attach") +def _add_cols(table, metadata): + if table.name == "tbl_with_auto_appended_column": + table.append_column(Column("bat", Integer)) + + +class BackendAlterColumnTest(AlterColRoundTripFixture, TestBase): + __backend__ = True + + def test_rename_column(self): + self._run_alter_col({}, {"name": "newname"}) + + def test_modify_type_int_str(self): + self._run_alter_col({"type": Integer()}, {"type": String(50)}) + + def test_add_server_default_int(self): + self._run_alter_col({"type": Integer}, {"server_default": text("5")}) + + def test_modify_server_default_int(self): + self._run_alter_col( + {"type": Integer, "server_default": text("2")}, + {"server_default": text("5")}, + ) + + def test_modify_nullable_to_non(self): + self._run_alter_col({}, {"nullable": False}) + + def test_modify_non_nullable_to_nullable(self): + self._run_alter_col({"nullable": False}, {"nullable": True}) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/util.py b/venv/lib/python3.12/site-packages/alembic/testing/util.py new file mode 100644 index 0000000..4517a69 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/util.py @@ -0,0 +1,126 @@ +# testing/util.py +# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php +from __future__ import annotations + +import types +from typing import Union + +from sqlalchemy.util import inspect_getfullargspec + +from ..util import sqla_2 + + +def flag_combinations(*combinations): + """A facade around @testing.combinations() oriented towards boolean + keyword-based arguments. + + Basically generates a nice looking identifier based on the keywords + and also sets up the argument names. + + E.g.:: + + @testing.flag_combinations( + dict(lazy=False, passive=False), + dict(lazy=True, passive=False), + dict(lazy=False, passive=True), + dict(lazy=False, passive=True, raiseload=True), + ) + + + would result in:: + + @testing.combinations( + ('', False, False, False), + ('lazy', True, False, False), + ('lazy_passive', True, True, False), + ('lazy_passive', True, True, True), + id_='iaaa', + argnames='lazy,passive,raiseload' + ) + + """ + from sqlalchemy.testing import config + + keys = set() + + for d in combinations: + keys.update(d) + + keys = sorted(keys) + + return config.combinations( + *[ + ("_".join(k for k in keys if d.get(k, False)),) + + tuple(d.get(k, False) for k in keys) + for d in combinations + ], + id_="i" + ("a" * len(keys)), + argnames=",".join(keys), + ) + + +def resolve_lambda(__fn, **kw): + """Given a no-arg lambda and a namespace, return a new lambda that + has all the values filled in. + + This is used so that we can have module-level fixtures that + refer to instance-level variables using lambdas. + + """ + + pos_args = inspect_getfullargspec(__fn)[0] + pass_pos_args = {arg: kw.pop(arg) for arg in pos_args} + glb = dict(__fn.__globals__) + glb.update(kw) + new_fn = types.FunctionType(__fn.__code__, glb) + return new_fn(**pass_pos_args) + + +def metadata_fixture(ddl="function"): + """Provide MetaData for a pytest fixture.""" + + from sqlalchemy.testing import config + from . import fixture_functions + + def decorate(fn): + def run_ddl(self): + from sqlalchemy import schema + + metadata = self.metadata = schema.MetaData() + try: + result = fn(self, metadata) + metadata.create_all(config.db) + # TODO: + # somehow get a per-function dml erase fixture here + yield result + finally: + metadata.drop_all(config.db) + + return fixture_functions.fixture(scope=ddl)(run_ddl) + + return decorate + + +def _safe_int(value: str) -> Union[int, str]: + try: + return int(value) + except: + return value + + +def testing_engine(url=None, options=None, future=False): + from sqlalchemy.testing import config + from sqlalchemy.testing.engines import testing_engine + + if not future: + future = getattr(config._current.options, "future_engine", False) + + if not sqla_2: + kw = {"future": future} if future else {} + else: + kw = {} + return testing_engine(url, options, **kw) diff --git a/venv/lib/python3.12/site-packages/alembic/testing/warnings.py b/venv/lib/python3.12/site-packages/alembic/testing/warnings.py new file mode 100644 index 0000000..86d45a0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/testing/warnings.py @@ -0,0 +1,31 @@ +# testing/warnings.py +# Copyright (C) 2005-2021 the SQLAlchemy authors and contributors +# +# +# This module is part of SQLAlchemy and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + + +import warnings + +from sqlalchemy import exc as sa_exc + + +def setup_filters(): + """Set global warning behavior for the test suite.""" + + warnings.resetwarnings() + + warnings.filterwarnings("error", category=sa_exc.SADeprecationWarning) + warnings.filterwarnings("error", category=sa_exc.SAWarning) + + # some selected deprecations... + warnings.filterwarnings("error", category=DeprecationWarning) + try: + import pytest + except ImportError: + pass + else: + warnings.filterwarnings( + "once", category=pytest.PytestDeprecationWarning + ) diff --git a/venv/lib/python3.12/site-packages/alembic/util/__init__.py b/venv/lib/python3.12/site-packages/alembic/util/__init__.py new file mode 100644 index 0000000..8f3f685 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/__init__.py @@ -0,0 +1,33 @@ +from .editor import open_in_editor as open_in_editor +from .exc import AutogenerateDiffsDetected as AutogenerateDiffsDetected +from .exc import CommandError as CommandError +from .exc import DatabaseNotAtHead as DatabaseNotAtHead +from .langhelpers import _with_legacy_names as _with_legacy_names +from .langhelpers import asbool as asbool +from .langhelpers import dedupe_tuple as dedupe_tuple +from .langhelpers import Dispatcher as Dispatcher +from .langhelpers import DispatchPriority as DispatchPriority +from .langhelpers import EMPTY_DICT as EMPTY_DICT +from .langhelpers import immutabledict as immutabledict +from .langhelpers import memoized_property as memoized_property +from .langhelpers import ModuleClsProxy as ModuleClsProxy +from .langhelpers import not_none as not_none +from .langhelpers import PriorityDispatcher as PriorityDispatcher +from .langhelpers import PriorityDispatchResult as PriorityDispatchResult +from .langhelpers import rev_id as rev_id +from .langhelpers import to_list as to_list +from .langhelpers import to_tuple as to_tuple +from .langhelpers import unique_list as unique_list +from .messaging import err as err +from .messaging import format_as_comma as format_as_comma +from .messaging import msg as msg +from .messaging import obfuscate_url_pw as obfuscate_url_pw +from .messaging import status as status +from .messaging import warn as warn +from .messaging import warn_deprecated as warn_deprecated +from .messaging import write_outstream as write_outstream +from .pyfiles import coerce_resource_to_filename as coerce_resource_to_filename +from .pyfiles import load_python_file as load_python_file +from .pyfiles import pyc_file_from_path as pyc_file_from_path +from .pyfiles import template_to_file as template_to_file +from .sqla_compat import sqla_2 as sqla_2 diff --git a/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/util/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..490f12d54c56d686ad7f9be3934f9a9af9a60937 GIT binary patch literal 1491 zcmZ9M%WfMt6ox6wmSoGi`hLHqF^s?&WBvoD|t^n6~jkpTD$QOxg zz)O6IcoDeH>%>dI%Y2!*4&2}k;$`3_ZxT0vTf9Zw1a9*-aSOP^JH&0^F7Fa|fLHhm zaTj=%uM)2SukkhFRp52LPP}F|l1;uzybip@w}>}@xA`{lCh!j5A>IPs<-5e&zJ#&~G@gw4W;A4JFd;omHPlykJ@9}%YN9MESKEGdLljF0Kzu{Ea zS!vp|nM#FDg&OE!9XMMTd9WjuDkp>L>EY1#R8VnH0~lPsv`He zHe7`7mBgT~xYc1Sj2g*!B2t+s4{k;JzO@D?8K^;?DG~C_DCjTsw9jOS#|rbeXT58u ztL~2va*`3Wk%SO4^|L^{&d~PsB#i<*iad*{Vh0z|(8sKab^*q&Yb2S?(D~FqD z4XsOL5YmenD+g9N@gr#3wm-~$3^f+HGa|bMui}FYITmaFR=O107PexbGN&*U^S5rH zC`#Bo_~4XGz}kteaYN*v$jE1r&JyY+5%m9gUTNGw`m8b`50_ z1^+5s9c3A%fzm{2p|nvtC|#5llvR{9ly#8lBHSY{M>-w34Kz1VwotZFc2IUv_E7dg zrqyhMb@1*0twWR}lw*)-oi+x?0-JXy;QY?sgR&2KG`5L~WFPL2ywi74Px&{&X3>?p zJ%r2j8eW5sGC4_<@MD|l;Y4`lZgi}o8=3E!Z;(fYBEe@PN8c3U)+8pkEg3v|uOHh2S3uV1{RZvNQJplKltg;iw(} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/util/__pycache__/compat.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff1288d45d51e934d2885486c35e10dc155d094d GIT binary patch literal 5256 zcmb6cTWlN0aqmSQd6Z6~o;Gdi^l)SSw<)pDw*HRtl;TW--Hgp8y#5+nSA9>8) z(J}=(H4J|wNPq|kP^(A^C_sTYa0=+tKLwKS{*a*oDHpjgnii;k3OZ7N#9y7=<598_ zr|C)Do!yz8ot>GTnf+T+Qy9UwH1whIk7k6vCIkNyJb+nmXA!!NG^8;m$}&lYAuwyQ zSuV*@m^1mTkQ6A)n}KXF8Kkgaib)Y@1v8WkK_4)~St%*iagk(%;+m38&wxWk1XiZl9go1aQ0&e}U|T6R#-fu*i(W%on=Z~C@%P>AZ*m@-+^xs3KsC{m z?8)&I>P`0OJ8?`4YwaI0AvnADr}bzZ*8)jJ>(sio&J(QGt91jeFSqkYR_*wSRXfQl zP0?c4_+&ru>GO93xL@0KO-K%G&cNJ&wi_r1i$Z+x9yvmsaV0gEvmDhiteg$Q$O$W# zF{aO`*w!(O#jNhAn(C+khbM9lE}pTBoO2q6{2A4mfu1{_D-z06j*eBwqN~p8Zxr-g zT0aMfP9taM^|X_k)}2hjG*vuJ#(?SYF&PVIVGj@C3VF*mF2&hXcS6)p=t+F|OMe94ntVtXCel_muR5doqAR3L%hu=RhTNlc;xUlOCO7^HS`{Pf0HD)x)(v3YDd3#TpSE&3 z99K-Gaw>?Ea>J=q)&ju^97(0#D5$1~!8>6s?jlW$G&@K`G)S900N6ojuAvQ<5t=+2 zf+p67!>2TBF4xc{Q4}#C)#V5P*OAlE^K9KfFx~h{=w=;%7U52q2CI|pTz*b9FTja- z(8CPwgDIOVR}?Cs8zR#wLW7Bj2MIn3%@wo}KrQX>72hr{w!icHve-i<{`Dt}8zj`J zLO$(_K{h7#rouw(0bxsMwjnM|)V~r_Vn1bW*4Lc{8Bb$k08oL1brbH1cBd7}!rk^KbMWHDUkY$6X(F-DqFFbi41D-ykM5T#4*{YkXmFQCN$1E;#QGFKSB@ zkxJyWnur+XF;uw?;&Uq;omW!6MSxq zhL~Bxb=&8!FEP{1Jm4;KB@VOs*8kX2;p596 zU9R>&d8^}Au+snRg1o9EzCnCz-@mh0$S?HyR@O;mal%VM?nsoUL8eYY0d1AgAo z_hq+I~3j+B@7ua zi!?Z1-K8Ax0&pov#)_KL6y3D-If&ojW)=uRdD)PYO^+Tpd3ltc!xPGwGEBoEyj2@2 zs`@D)Y@kC zo31!jFiZyu^aspnJnV)I8(@b-N0+l5>;}ORRR;hb>vIQkgjot}A{CxXr@P{sJ2C{0I`G3&S6# zZwLEVWcRr&CWe5@iL zyJdg&tEzmmES_8owgMOJc5q-->hNyTQgS&~9z0r+j;^(JErwUR6P4~nwQYYnvLAL@ zkq0aC;F4XDhi@cT_KjEIUmh=u<7*+Nw4Zp7o{5WDeF5J6r~(k)wI&^j(`G9gb-(GU}1p4 zSgHnAkVp-dO4GNV+e8QO`YL2emL!qFFH@t0zD}2dEDCQJd=Bt$!p|>MMHJ~;kzy4o zw)oQWb7d)3m5!GAqx3BCfL8<(->p}IQXgpviy%$CRzd*=D(!2=G zd>9_HN=YGaCXXdy1E+X}P!MC_vxIDyFD1}g_9&73MdN}_<(~9T7xGCa<9o=HD^q~y z7(BT!m=h9!r~A4}31c_Zi1uENh&INfe)Pwr+H_gN!lML$y2hD-?BG3*gJY z1Yb;IrYqZTm{9Z0=-J{Zc~WAD%yw>@9rY3zCIQvu$%D`p4IAz?qsgI;&QZ91o4a!$ScTdd|G4$y}qc_L&@kkb_iV0g7YEvQH)qnDa` z)wZYS1MwLzzaGN|(2-nVzXdJSL=5w705JpqLVNC@o;xW16$-)s9n}353V(%~?x4;) zC7&-6`tWPS4&b7m-?|~%WhV39h?3UW4lFc*$JS?4&vCAjo3+0x(2n{9Z4%K zcNa5DS|X@I2y##i7f~*?f~J7blMM$*k40}iwP-K0)gZ(|0)hrTj2$xhM;s40N)X6m$}3rKkv6aXQLsQ5AHDlPaf+X+f(%XNnm?r-1G(b_zNTbXTzp z=#16rbeFTmtf0Fbt(+_7FiIh-Ck1u%vwE%WkCVk7tIx`QEEjuQeBN69mIk~&>wuMm zwtq2^KlnZ0SRRKN!*#vD2yD+~6VO-4g3SU3lsxCzF0?B19XlWk0nll4mYDM;({LQF zan&|CSH1~DpzZjD;rq5b1EhM(=Ozv3dkphD1%}N^zQ^nZ5O$7x<+9;g*C_R7g z?(2fY1<*X?dpckJ*N}P4m>)dU!N8_#I4%YbIZgH^r}H^W6B}I%;e;? ztJm+|dT(-4x0%jJa0c{&f`4GlgI$B^J|)wH64xXn>1b}4mOiiXt()Lt z&vh1I5iEe?B38VJ$QZSW6%U6sxn3;P%SHf4%)pYXkNS(FWWgMrymd#MlvoorV4~L0 zQQx3OS!~ud%4Af(3l890=RG~}bPwDzqJL;Rjy^?n-(WCS*QiK8E$&~nxVG`laX^-lYlO7w1M@&wR=T1-Yc$bBZ9wc&Ckw=^aDHn&CwFl^uY?JYg&ncgu(LF6yH+V$G377|Q`Z(u zB9=z8(9b7BwdD%h%M~OT4DltP0G$PsanSOO>$|Kl>y=5tn6fI4QL}4>tAx!5o?n znUIq=@mB8Wnz@-9UQxExp4GS4-r49q{W9^V)o+w6YpZ_yrMYjj7UK)FhB*9Izlpi2FpO00i8}C}_)`2{D8<4nQ6i z3qOnHSJC}(gnk7nVkxmC@o=vqh970>5c-Ee8Nz!nMaZpDRIbZr0;4-9;HgKHxprD; zaUgxjRSee7*YR9CN$itCx_A~RCyX9llOb`zdSJUt$)KyHS5MYaOKVA~E6ey%PhE*- zmr=kmBZ}2dQy<~HG?7n)N|Y2r<%8>Y-XFVJyEEbGtYQZ|ZL~l-?NuOT^xFi zB%0x9oJCZlzmuU4YiU*rj3dd#CTv~inx_Bp+-Qc3~Qr3P=yjBeyBiN zAvAz7Q5=Yv;p+)I;%t%ch)|z4A(m|mY_Jg9p#=FZ%v4}-$(;27zG14Z5X!z0%rYKL zA}Vy99_G_>+=wa<1p6GSWwezW+RPnWes`;9aO22pn?2`VA|-kL6J-UjTxevvS5G{A zvvGK6{n+PcR&H!%`&Z}xnH_E%9Dbn2C3OlfeV?aiXV+(V%T8a7BVb6dVD^UXkEdiqD+W3tP za6QRtBX6#K+3m^quDChz#@Ul_sx>EI^(c>Y80&3gMnglHJ7OD*&+Lb{uv zonW&jk3a$q<=bO--w9D(rQN`!Jh(z>h6G_!ye1+fb%^68KIJiVJ0fzwaMo)3)t@zoat7aMAB^@Hc? z;7Y2Y9ekYmEVK6Z`dgdYiEp&C&$Y8(PJK1^bZ%3-#83OsioB)ut=?U`w5c6mNp59w ztH*!yMx%G&;Z1(rhngzt)aYrL%!IF9c-%|;$?_8*_Mb16XW+H?2Yd%Xo8T>v=436) z;Hh9ksGN;&j{z7M%C6yh5FK_olugefd8wxImV2|-1y3~M8`kO5qMM1kyhC|@d5Xlk zBImpU49cq;@G*Y#fO8b2YGHPKLmmHD_VR{$S?K5SL|&l>Km&q0pu+35f!53I9)I24 z1O6Eus{8jtg_58`-B6zJTmmP>P4DL7H{YHjy##!E5h^e)PLtEokzVE$m)E-5({SVY zM8r+;mn1iJKYt)hmrBzWn30r96g(WKpANX&Ic}niB#JFn%i(ma{LS|i)jlz4k|M6}-~dv=o0|KW&&uZUCpKluzL2mk;8 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/util/__pycache__/exc.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3493954e4fcfe109bf8df55bf16b058316e6050e GIT binary patch literal 1821 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/util/__pycache__/messaging.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dfed105329795c598022cf35e5eb7879496261c4 GIT binary patch literal 5180 zcmcH+U2GJ`d1im^_Wpc#HgGl$_W1|!CD^CLBoLsq4z{rqFe#XjT#8QX+qHei-ri+) z&*m;i2%1*fG7kt)@Qw55HbZ&2kSQeW)agv(+oq$W~%J1`FjRcgQ4z4aNC zs=RdUoB3wu`)0rS{^zegpBq8Dxb;Hv?_PxdK__VC z*%)iWtiq-F7;nOy;)pq5pHrMMr#T8S0Y+YNrQI>NiQ@p+6Z4p`6JT%5Yr+D+zL>9a zPED)^=v<0F9f$=mVvy`ewc5;8N4fSgwq~)q|Ar&EZJbo3N@+tT)wA}jI$o}THZ4Xj zdUHtpqB#)i}jRazU8kQ_>Fw`bK6@at1ARp&M&dEi9z?=o+#U1qH@ z)?m`?v@xrr-c091F`$5<0OFA;L&ptV!}pz6;m|cc}hJ8>E^u zj#!(_!_7j~bqY!KlK%{E%OKV+HAow!z!^TaS!$GmXSmoF=^1I0R0}IxO{fl_4xnp# z{2cSFW@#PlMx?N`9w?v9b8Sd&ky}TZc0{P%0Do_x4-ZeGlk8h)5=UC@(#u4!;Sp6e zql@gO(LV}@<}}{s^XYThE})r459F$ETs|^fE~ktH+Tpj{-_Fz z0I)ft7*0`<(<8}*!3{v`zXbd5=ae76A2o*UU^kX$!V_I#IOTqb1wjwLBHqI#ya9mGfz{`qsXX9Y>;UI_ds151h>sAB#a~Qn^7tNzp>2pJdAghUtlvKwIUeprF zq~RoTRuL0&#A!IkWYCCC40lpXs+umU3EALB^17@U4wFtc9Fh$5uslhUx@3mRCXT^PyRv!1Z?YTD}h4Lx4L?w{PivjePY$JVA}ydU=^=i1>iN%==mqClwP3-wpwJqU@uqf(28lFFy}Ew zMMi6^+G^EOQ}pB~n#9?*NiB!klu5twsp9pMZ%t&I_?BZJlQV11e{f$k!#;RdtRb6Ons4W}wk zDoIr~57MZ}z{JQVN(BTIjckDljmFwh=>CZwPN4?^;)QAV%o__rD3Pw2EwxtNm9-+Oy0>0Q83;mB z5&a7NnknKhn5v-l)i9G1D&%Vh2sf3WMpYW&h6;iu=v`sr1H8Nch6WDIDjA3hGIeLt z0_%h(WrLGXCUwx6)AFZ^u<5mIS+hVwI$@z7ej3fUQ)roUzCZZh;PluMw*lbkj+q@Z zk$K^ni+wk^XX)m^djr#le|BI6u+eH%rV}a(Q%DB@SZnF2wW6e$(5ga8t%}Inp}jc|umeZ-hyasBjz2 z;3#BxjzU;HX02UfEPd~Q?`mr_OJ0TrvwNNH&`?D+>wW*=dk1IQK7Mzu@HMyV|9B5V zJr^`YOCysFqqbMh5*b7exxT_Y*-d#{!!^WuxK>=MV9FBN3mC%`s}8y~H&_ZquB5)^ zdaNsOuxU3M7)@3D9`p^R0c(~9_AZ**@wDL(476gl5JoXM_q22puF|PGlpUPDS|+5- z(ted06DY2UCPgSJs)^tfuTv+tuz1@YB<@5n2z7Ijmw3ss5kW}OE{8ZBSP^095q1}ta2SGC zO;SzjNl}R>!KYQ-2x_?zO@1c_aAoN(b!b9$L^_}|P((0X!+nPj9vkc(9xzjo83#!l z-4$qAGfBiOcoFScMOH}|b|Oy8g*F3hE?8xdqw-yzrYUeebD+T*5*2M`va%>!VD(M- zX@7!lO+n%e%scCsh0q1|Gxw+Ni~E*@j&D4*)jY)A@OG9RsCL7&V>wWN-uIEO!bKZ?5(8!9P3y$Xz?UBn*Dz^ezjYbMCY5n?n79P=7<%2pLlFSw2LC zX*cRmrVo7p0QeA^!g;*5yw)q8fmVHlFhz9avErs`4Yog!pu~J{S-qMs;e=IRk=PJ;iqT9iA*{zc8+F9TGYdvp|B zoe+8@gG*(Ss^NY^RC2Oa`@-vDg3i_^2fuX!O(W)sK9Y9Ra9NW4_Md`SmuXJ08LP5eL~uaZI|$u?Y>e5sq}P?$a|&%m}_qbq>VT z+AU(5bPP7B>cm7diP>S`%)JJw%2}8aeh-iKS81jywAoL@x7SF+eqmDC96x^C`W)py z@|@w0$47G@895#&G^!c?L`G5IEkd8CUE)Z>QoXzKGKEJ7q1oDSLdis*7*>d&^^f7u z^I5t=QwX81ZYhUaq?yPd2EC(*N@842=euMTsAa1Jalz~B030V1Lx{&!5ne!X5}`-B zMM)YERY`#@&rnWL>dp1Fy(E}*UR9lPh8H#>GObnIU2 z_`wp|TXwkd`deJ!7FTtiFtntN{&G8QJTH#^8yah3VJG*{ZX1#cODNtvPO|9104KWth z@9=f^c^Fq%KrS-?J!oQaaK(%90Dd4~yqk8f;IbPH;Dfl#hHzua<+a98qcuJkwZQ@%mC=-RVK9jrEVrYUnRq&!*lE^ks*ucRTxK HH-`Q{E>Ba> literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/util/__pycache__/pyfiles.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4365dd62c2e9a4ff1e75beedfee596a72c03d826 GIT binary patch literal 6429 zcmbVQU2GfImA=FI;Xj8I^+WwQqGUG~C0Vi^#Wox}ZEQ)Fn?&)h9kgLLWhl-lrc82( zJ0siDQd>31VnwX6lF$NHfB+SWeNd4+_%V;&v=0Re6k@5C>{vzvG+m%?Ol2T|_hrwy z!y#ptaIt$O-h1c%oO|xf`Mz`R{k7lkA&|C@f1FOW5%MQ|uoG`pxPu{%ko!a-3YQ^S zF3xdScVrxK2b6q<&kAv&-X_LHXcIDGR*FmYHYaOyWL)*q9e1<&p16mV-nf^QzPJxc zG2_n$;sG{S%E;MZJjm)!sE6VqR(CV75gG3O?&{*(?y{!of{E1gR4Lm}Q{k5Rgm5IpzV&sZuEr48Epo@(n>!q9d^=~3FD zd|}BE>-`J9wisu6lewH`B#pF|(`Vt?J#{y2TsM-bTlhd#U0b9nRfh-9o0_tiQEx2e zRe0iG%Pm3SdMl4plNqRsZ|9(jBh|Ypm9^tM*9|qB&m;}?FYtkIN#A8Hn@#4FDM~du z4Grzdd|u5di92cIW+JEF$)t0to~nOXg0r0P^Af@xoOj?yHn5E)!yZKnQEdxPT@ZRO)$MoR-Dip zEjSlI!)KEsJYTxK3P)PcFG$T1V0Q3lugj4oa+I7RI(Nsxk$2!VVar|q9dd^|3U$M8 z>#`8AYt2#-73ou_EerPGD!5wdZ-neQ3jn-l(i z_|&d*zGruxpAUG{v%^#k{^1Ul3$;seea5%ZvlyXMmwHJ8k)rXx9LI_}IZJ3O9c zVa&K|OvfCiq%Y3Tr|+7QqGnV>U7ob*=+SwNMw3ya zZs#X7npSg$szeQ%OsR8#rLmupn2(};m|clHph~5;K~5r{1dC7V3EiM(z!INW%%xMB zqMDMLW2K-0I85;lO&hA|!bMPjvuzf3prCFVP12=lXrsuIX4p{g8VMD_m9SMZ1>7%1 zlZbgtzulZ*k_1K74O7+@jr<}^#8p8zidJJfZr!ogL;#zTHOA!TO+IJLo8ml*)p6Nq zBRw(3Z)#a}JUIt+kz7tMk58)lEknzX&#HH#*QmCjrVM>NpIpkS3H_#)PtPwU*b7Oi z*jEh=ZA2=ep%VU&?VN_J z5_-85dijA?4o$B~kNZfVZR1!eI`NV4*|DE6_`+k1v5AxA!Fm&I!>D-cj;{V9M zt(7BZ_JU`tKL7fa;+2i*dvAU1gppt4@UJlYpIWb1^z8dPD*mC8f9Qeq+2WpmszxOD zkza+YK6(9W@#=oCrxF}11;@(4lYb2LZ@y3t4g4|Gwb{8PmqSC3oq|75^N^0n{h7^~ zwW+WDF!yT|@D*lH9tVirTXS&!k?M)lU-6v(r30RqJ*ctgiGC8^#r=UxA@X0YiAy5? zxhP%=I6n_{PxkPi55MN0B;qg5y5ZrMM4WWHe<^lAJ=I`Zn2aa~zU2-I&9L_cQ*XEQJhk;mPX~AdLnq|S!it}tX0B(FP zs}(rK(G9}v@hd#E@QTnwCds?NW-CI0F9^$^!3uxREf83(qq(+SJG4fp-PSA%ye-`> zSBQa&vb4}+*P3PX-CZX!ae415FboAMF@Sjx1c=0FQje)zRcZ(AqwJ%)=g$R`jTGGYNvJ8qI0BXmU>1GK+=^(~O%iJ*PxdTFywOp$lpm zEyrf!@U*QJupg{s%eE(}5K90pYIXG*iq+SC!kwpRN)*58Y%E|pG~E<%RZ~pspoS^x z`ApiN=+8|FC<%fU(?!+y)38r!im8kSOoDa_+=R?v`jLrg0`&!2G@U@U*kL+RDiDn+ zT!TtXG((Kb5Xwaggop3pH0tm=O zA_G7A=QZEHE3hsX41yXQ&;upr!(_)hM{j)YxpyOVJ?d zS;$Ox8si9q0Rla%8A>HnH`T;E)v^goj_CsI{m>~zTtU3%xn z)aAD(rxMrSo}Rw?-Kp!Qmj(4!NEH+437p|V>@ifkQ$QwZ1!D^y;Hje(AfhZlwSk@t zt(F=%gT4+UuE9_LDP({n4+(ay7m9_g$+A4KI_XXf5sW<~tzNB3#1*PIyGqWkJ!f}Sj_kT3H5c)Q*T;+FtC#oXNJSnj$%7Sn zq9jlJl?Y-xpvxPsc>7D<{%yYOJy!9Kl)NLq^PZ@Ndn@7NrSS0`z8oI?t#7m@!f=ol zY^Se+nyJI?vEfAj<^KktLx-ML04g*cCYpK!POyAE0R$VN_=3xBZI%kSJ&?j%EnvC; z2j=92g0R5O-_5aDTM-MQ!rubwqgP%-7nz^MQLDIn5VuEjh}w9yw}Q>KH0 z(d&zI3`KMjn*tfoCSfg;$S*NtH@#OGG}&o6<}w8(4K504$PD62=w0v#Vd1GhH?1(F z(Qo50$!Y}H0i1A{j$a>PhRK3jkgaU-h7~=32gc09PyY>M3}}{b*b>U#Bde49T`$}( zY!%3UXRh4!l4Uy+ZM9{fDntgx-?SyEVNh9Ibjot8ZH0 ztPyR1^ahcB3J-_xgbKuH5U%}Az`ui^Y{nok2ERE=ts;)bJa4NTCXiI8NkR56b}Ay62?gM7DVsBArR0nQuN`_1E=tMU_7ZoOb+*S7x{=JK zknIf0SkQD`Tcj$g$)b-K$4pDduWRuU#Um5T?m0#E*>-1g(M_b+T-*cQs+ZvNgHtd>&& zCg-r{Jb*L3b#`;Z9A~C-R=A|YQ6z!Lugv|8R&qfgYv2&Hh8)(&LxDgeOHG{lBvk@aj zI*O85Bsq($o78|#`3!od`~ zuVU9Y6pv5L9a8j57S}Kv!RVTOS?0%{xGq?f^(xh%0}V?5J!EkD;kZX+@K415h(sTf z;3Lxikn}tx9S@27AqlWtenk2nklwWvW ztcg&9Q|aukIkDy;z5O*DQ}YpTpytO~fJmML8EOX?y10?rM3`%@!b~2@FT~yh5z5+V sH`m4*u{=FnCZoB(OfshVhMC{NL?{wYiPH9m| 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 (3, 14) +py313 = sys.version_info >= (3, 13) +py312 = sys.version_info >= (3, 12) +py311 = sys.version_info >= (3, 11) + + +# produce a wrapper that allows encoded text to stream +# into a given buffer, but doesn't close it. +# not sure of a more idiomatic approach to this. +class EncodedIO(io.TextIOWrapper): + def close(self) -> None: + pass + + +if py311: + import tomllib as tomllib +else: + import tomli as tomllib # type: ignore # noqa + + +if py312: + + def path_walk( + path: Path, *, top_down: bool = True + ) -> Iterator[tuple[Path, list[str], list[str]]]: + return Path.walk(path) + + def path_relative_to( + path: Path, other: Path, *, walk_up: bool = False + ) -> Path: + return path.relative_to(other, walk_up=walk_up) + +else: + + def path_walk( + path: Path, *, top_down: bool = True + ) -> Iterator[tuple[Path, list[str], list[str]]]: + for root, dirs, files in os.walk(path, topdown=top_down): + yield Path(root), dirs, files + + def path_relative_to( + path: Path, other: Path, *, walk_up: bool = False + ) -> Path: + """ + Calculate the relative path of 'path' with respect to 'other', + optionally allowing 'path' to be outside the subtree of 'other'. + + OK I used AI for this, sorry + + """ + try: + return path.relative_to(other) + except ValueError: + if walk_up: + other_ancestors = list(other.parents) + [other] + for ancestor in other_ancestors: + try: + return path.relative_to(ancestor) + except ValueError: + continue + raise ValueError( + f"{path} is not in the same subtree as {other}" + ) + else: + raise + + +def importlib_metadata_get(group: str) -> Sequence[EntryPoint]: + """provide a facade for metadata.entry_points(). + + This is no longer a "compat" function as of Python 3.10, however + the function is widely referenced in the test suite and elsewhere so is + still in this module for compatibility reasons. + + """ + return metadata.entry_points().select(group=group) + + +def formatannotation_fwdref( + annotation: Any, base_module: Any | None = None +) -> str: + """vendored from python 3.7""" + # copied over _formatannotation from sqlalchemy 2.0 + + if isinstance(annotation, str): + return annotation + + if getattr(annotation, "__module__", None) == "typing": + return repr(annotation).replace("typing.", "").replace("~", "") + if isinstance(annotation, type): + if annotation.__module__ in ("builtins", base_module): + return repr(annotation.__qualname__) + return annotation.__module__ + "." + annotation.__qualname__ + elif isinstance(annotation, typing.TypeVar): + return repr(annotation).replace("~", "") + return repr(annotation).replace("~", "") + + +def read_config_parser( + file_config: ConfigParser, + file_argument: list[str | os.PathLike[str]], +) -> list[str]: + return file_config.read(file_argument, encoding="locale") diff --git a/venv/lib/python3.12/site-packages/alembic/util/editor.py b/venv/lib/python3.12/site-packages/alembic/util/editor.py new file mode 100644 index 0000000..f1d1557 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/editor.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +import os +from os.path import exists +from os.path import join +from os.path import splitext +from subprocess import check_call +from typing import Dict +from typing import List +from typing import Mapping +from typing import Optional + +from .compat import is_posix +from .exc import CommandError + + +def open_in_editor( + filename: str, environ: Optional[Dict[str, str]] = None +) -> None: + """ + Opens the given file in a text editor. If the environment variable + ``EDITOR`` is set, this is taken as preference. + + Otherwise, a list of commonly installed editors is tried. + + If no editor matches, an :py:exc:`OSError` is raised. + + :param filename: The filename to open. Will be passed verbatim to the + editor command. + :param environ: An optional drop-in replacement for ``os.environ``. Used + mainly for testing. + """ + env = os.environ if environ is None else environ + try: + editor = _find_editor(env) + check_call([editor, filename]) + except Exception as exc: + raise CommandError("Error executing editor (%s)" % (exc,)) from exc + + +def _find_editor(environ: Mapping[str, str]) -> str: + candidates = _default_editors() + for i, var in enumerate(("EDITOR", "VISUAL")): + if var in environ: + user_choice = environ[var] + if exists(user_choice): + return user_choice + if os.sep not in user_choice: + candidates.insert(i, user_choice) + + for candidate in candidates: + path = _find_executable(candidate, environ) + if path is not None: + return path + raise OSError( + "No suitable editor found. Please set the " + '"EDITOR" or "VISUAL" environment variables' + ) + + +def _find_executable( + candidate: str, environ: Mapping[str, str] +) -> Optional[str]: + # Assuming this is on the PATH, we need to determine it's absolute + # location. Otherwise, ``check_call`` will fail + if not is_posix and splitext(candidate)[1] != ".exe": + candidate += ".exe" + for path in environ.get("PATH", "").split(os.pathsep): + value = join(path, candidate) + if exists(value): + return value + return None + + +def _default_editors() -> List[str]: + # Look for an editor. Prefer the user's choice by env-var, fall back to + # most commonly installed editor (nano/vim) + if is_posix: + return ["sensible-editor", "editor", "nano", "vim", "code"] + else: + return ["code.exe", "notepad++.exe", "notepad.exe"] diff --git a/venv/lib/python3.12/site-packages/alembic/util/exc.py b/venv/lib/python3.12/site-packages/alembic/util/exc.py new file mode 100644 index 0000000..4658f78 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/exc.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import Any +from typing import List +from typing import Tuple +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from alembic.autogenerate import RevisionContext + + +class CommandError(Exception): + """Base command error for all exceptions""" + + +class DatabaseNotAtHead(CommandError): + """Indicates the database is not at current head revisions. + + Raised by the :func:`.command.current` command when the + :paramref:`.command.current.check_heads` parameter is used. + + .. versionadded:: 1.17.1 + + """ + + +class AutogenerateDiffsDetected(CommandError): + """Raised when diffs were detected by the :func:`.command.check` + command. + + .. versionadded:: 1.9.0 + + """ + + def __init__( + self, + message: str, + revision_context: RevisionContext, + diffs: List[Tuple[Any, ...]], + ) -> None: + super().__init__(message) + self.revision_context = revision_context + self.diffs = diffs diff --git a/venv/lib/python3.12/site-packages/alembic/util/langhelpers.py b/venv/lib/python3.12/site-packages/alembic/util/langhelpers.py new file mode 100644 index 0000000..cf0df23 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/langhelpers.py @@ -0,0 +1,445 @@ +from __future__ import annotations + +import collections +from collections.abc import Iterable +import enum +import textwrap +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import List +from typing import Mapping +from typing import MutableMapping +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TypeVar +import uuid +import warnings + +from sqlalchemy.util import asbool as asbool # noqa: F401 +from sqlalchemy.util import immutabledict as immutabledict # noqa: F401 +from sqlalchemy.util import to_list as to_list # noqa: F401 +from sqlalchemy.util import unique_list as unique_list + +from .compat import inspect_getfullargspec + +if True: + # zimports workaround :( + from sqlalchemy.util import ( # noqa: F401 + memoized_property as memoized_property, + ) + + +EMPTY_DICT: Mapping[Any, Any] = immutabledict() +_T = TypeVar("_T", bound=Any) + +_C = TypeVar("_C", bound=Callable[..., Any]) + + +class _ModuleClsMeta(type): + def __setattr__(cls, key: str, value: Callable[..., Any]) -> None: + super().__setattr__(key, value) + cls._update_module_proxies(key) # type: ignore + + +class ModuleClsProxy(metaclass=_ModuleClsMeta): + """Create module level proxy functions for the + methods on a given class. + + The functions will have a compatible signature + as the methods. + + """ + + _setups: Dict[ + Type[Any], + Tuple[ + Set[str], + List[Tuple[MutableMapping[str, Any], MutableMapping[str, Any]]], + ], + ] = collections.defaultdict(lambda: (set(), [])) + + @classmethod + def _update_module_proxies(cls, name: str) -> None: + attr_names, modules = cls._setups[cls] + for globals_, locals_ in modules: + cls._add_proxied_attribute(name, globals_, locals_, attr_names) + + def _install_proxy(self) -> None: + attr_names, modules = self._setups[self.__class__] + for globals_, locals_ in modules: + globals_["_proxy"] = self + for attr_name in attr_names: + globals_[attr_name] = getattr(self, attr_name) + + def _remove_proxy(self) -> None: + attr_names, modules = self._setups[self.__class__] + for globals_, locals_ in modules: + globals_["_proxy"] = None + for attr_name in attr_names: + del globals_[attr_name] + + @classmethod + def create_module_class_proxy( + cls, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + ) -> None: + attr_names, modules = cls._setups[cls] + modules.append((globals_, locals_)) + cls._setup_proxy(globals_, locals_, attr_names) + + @classmethod + def _setup_proxy( + cls, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + attr_names: Set[str], + ) -> None: + for methname in dir(cls): + cls._add_proxied_attribute(methname, globals_, locals_, attr_names) + + @classmethod + def _add_proxied_attribute( + cls, + methname: str, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + attr_names: Set[str], + ) -> None: + if not methname.startswith("_"): + meth = getattr(cls, methname) + if callable(meth): + locals_[methname] = cls._create_method_proxy( + methname, globals_, locals_ + ) + else: + attr_names.add(methname) + + @classmethod + def _create_method_proxy( + cls, + name: str, + globals_: MutableMapping[str, Any], + locals_: MutableMapping[str, Any], + ) -> Callable[..., Any]: + fn = getattr(cls, name) + + def _name_error(name: str, from_: Exception) -> NoReturn: + raise NameError( + "Can't invoke function '%s', as the proxy object has " + "not yet been " + "established for the Alembic '%s' class. " + "Try placing this code inside a callable." + % (name, cls.__name__) + ) from from_ + + globals_["_name_error"] = _name_error + + translations = getattr(fn, "_legacy_translations", []) + if translations: + spec = inspect_getfullargspec(fn) + if spec[0] and spec[0][0] == "self": + spec[0].pop(0) + + outer_args = inner_args = "*args, **kw" + translate_str = "args, kw = _translate(%r, %r, %r, args, kw)" % ( + fn.__name__, + tuple(spec), + translations, + ) + + def translate( + fn_name: str, spec: Any, translations: Any, args: Any, kw: Any + ) -> Any: + return_kw = {} + return_args = [] + + for oldname, newname in translations: + if oldname in kw: + warnings.warn( + "Argument %r is now named %r " + "for method %s()." % (oldname, newname, fn_name) + ) + return_kw[newname] = kw.pop(oldname) + return_kw.update(kw) + + args = list(args) + if spec[3]: + pos_only = spec[0][: -len(spec[3])] + else: + pos_only = spec[0] + for arg in pos_only: + if arg not in return_kw: + try: + return_args.append(args.pop(0)) + except IndexError: + raise TypeError( + "missing required positional argument: %s" + % arg + ) + return_args.extend(args) + + return return_args, return_kw + + globals_["_translate"] = translate + else: + outer_args = "*args, **kw" + inner_args = "*args, **kw" + translate_str = "" + + func_text = textwrap.dedent( + """\ + def %(name)s(%(args)s): + %(doc)r + %(translate)s + try: + p = _proxy + except NameError as ne: + _name_error('%(name)s', ne) + return _proxy.%(name)s(%(apply_kw)s) + e + """ + % { + "name": name, + "translate": translate_str, + "args": outer_args, + "apply_kw": inner_args, + "doc": fn.__doc__, + } + ) + lcl: MutableMapping[str, Any] = {} + + exec(func_text, cast("Dict[str, Any]", globals_), lcl) + return cast("Callable[..., Any]", lcl[name]) + + +def _with_legacy_names(translations: Any) -> Any: + def decorate(fn: _C) -> _C: + fn._legacy_translations = translations # type: ignore[attr-defined] + return fn + + return decorate + + +def rev_id() -> str: + return uuid.uuid4().hex[-12:] + + +@overload +def to_tuple(x: Any, default: Tuple[Any, ...]) -> Tuple[Any, ...]: ... + + +@overload +def to_tuple(x: None, default: Optional[_T] = ...) -> _T: ... + + +@overload +def to_tuple( + x: Any, default: Optional[Tuple[Any, ...]] = None +) -> Tuple[Any, ...]: ... + + +def to_tuple( + x: Any, default: Optional[Tuple[Any, ...]] = None +) -> Optional[Tuple[Any, ...]]: + if x is None: + return default + elif isinstance(x, str): + return (x,) + elif isinstance(x, Iterable): + return tuple(x) + else: + return (x,) + + +def dedupe_tuple(tup: Tuple[str, ...]) -> Tuple[str, ...]: + return tuple(unique_list(tup)) + + +class PriorityDispatchResult(enum.Enum): + """indicate an action after running a function within a + :class:`.PriorityDispatcher` + + .. versionadded:: 1.18.0 + + """ + + CONTINUE = enum.auto() + """Continue running more functions. + + Any return value that is not PriorityDispatchResult.STOP is equivalent + to this. + + """ + + STOP = enum.auto() + """Stop running any additional functions within the subgroup""" + + +class DispatchPriority(enum.IntEnum): + """Indicate which of three sub-collections a function inside a + :class:`.PriorityDispatcher` should be placed. + + .. versionadded:: 1.18.0 + + """ + + FIRST = 50 + """Run the funciton in the first batch of functions (highest priority)""" + + MEDIUM = 25 + """Run the function at normal priority (this is the default)""" + + LAST = 10 + """Run the function in the last batch of functions""" + + +class Dispatcher: + def __init__(self) -> None: + self._registry: Dict[Tuple[Any, ...], Any] = {} + + def dispatch_for( + self, + target: Any, + *, + qualifier: str = "default", + replace: bool = False, + ) -> Callable[[_C], _C]: + def decorate(fn: _C) -> _C: + if (target, qualifier) in self._registry and not replace: + raise ValueError( + "Can not set dispatch function for object " + f"{target!r}: key already exists. To replace " + "existing function, use replace=True." + ) + self._registry[(target, qualifier)] = fn + return fn + + return decorate + + def dispatch(self, obj: Any, qualifier: str = "default") -> Any: + if isinstance(obj, str): + targets: Sequence[Any] = [obj] + elif isinstance(obj, type): + targets = obj.__mro__ + else: + targets = type(obj).__mro__ + + if qualifier != "default": + qualifiers = [qualifier, "default"] + else: + qualifiers = ["default"] + + for spcls in targets: + for qualifier in qualifiers: + if (spcls, qualifier) in self._registry: + return self._registry[(spcls, qualifier)] + else: + raise ValueError("no dispatch function for object: %s" % obj) + + def branch(self) -> Dispatcher: + """Return a copy of this dispatcher that is independently + writable.""" + + d = Dispatcher() + d._registry.update(self._registry) + return d + + +class PriorityDispatcher: + """registers lists of functions at multiple levels of priorty and provides + a target to invoke them in priority order. + + .. versionadded:: 1.18.0 - PriorityDispatcher replaces the job + of Dispatcher(uselist=True) + + """ + + def __init__(self) -> None: + self._registry: dict[tuple[Any, ...], Any] = collections.defaultdict( + list + ) + + def dispatch_for( + self, + target: str, + *, + priority: DispatchPriority = DispatchPriority.MEDIUM, + qualifier: str = "default", + subgroup: str | None = None, + ) -> Callable[[_C], _C]: + """return a decorator callable that registers a function at a + given priority, with a given qualifier, to fire off for a given + subgroup. + + It's important this remains as a decorator to support third party + plugins who are populating the dispatcher using that style. + + """ + + def decorate(fn: _C) -> _C: + self._registry[(target, qualifier, priority)].append( + (fn, subgroup) + ) + return fn + + return decorate + + def dispatch( + self, target: str, *, qualifier: str = "default" + ) -> Callable[..., None]: + """Provide a callable for the given target and qualifier.""" + + if qualifier != "default": + qualifiers = [qualifier, "default"] + else: + qualifiers = ["default"] + + def go(*arg: Any, **kw: Any) -> Any: + results_by_subgroup: dict[str, PriorityDispatchResult] = {} + for priority in DispatchPriority: + for qualifier in qualifiers: + for fn, subgroup in self._registry.get( + (target, qualifier, priority), () + ): + if ( + results_by_subgroup.get( + subgroup, PriorityDispatchResult.CONTINUE + ) + is PriorityDispatchResult.STOP + ): + continue + + result = fn(*arg, **kw) + results_by_subgroup[subgroup] = result + + return go + + def branch(self) -> PriorityDispatcher: + """Return a copy of this dispatcher that is independently + writable.""" + + d = PriorityDispatcher() + d.populate_with(self) + return d + + def populate_with(self, other: PriorityDispatcher) -> None: + """Populate this PriorityDispatcher with the contents of another one. + + Additive, does not remove existing contents. + """ + for k in other._registry: + new_list = other._registry[k] + self._registry[k].extend(new_list) + + +def not_none(value: Optional[_T]) -> _T: + assert value is not None + return value diff --git a/venv/lib/python3.12/site-packages/alembic/util/messaging.py b/venv/lib/python3.12/site-packages/alembic/util/messaging.py new file mode 100644 index 0000000..4c08f16 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/messaging.py @@ -0,0 +1,122 @@ +from __future__ import annotations + +from collections.abc import Iterable +from contextlib import contextmanager +import logging +import sys +import textwrap +from typing import Iterator +from typing import Optional +from typing import TextIO +from typing import Union +import warnings + +from sqlalchemy.engine import url + +log = logging.getLogger(__name__) + +# disable "no handler found" errors +logging.getLogger("alembic").addHandler(logging.NullHandler()) + + +try: + import fcntl + import termios + import struct + + ioctl = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack("HHHH", 0, 0, 0, 0)) + _h, TERMWIDTH, _hp, _wp = struct.unpack("HHHH", ioctl) + if TERMWIDTH <= 0: # can occur if running in emacs pseudo-tty + TERMWIDTH = None +except (ImportError, OSError): + TERMWIDTH = None + + +def write_outstream( + stream: TextIO, *text: Union[str, bytes], quiet: bool = False +) -> None: + if quiet: + return + encoding = getattr(stream, "encoding", "ascii") or "ascii" + for t in text: + if not isinstance(t, bytes): + t = t.encode(encoding, "replace") + t = t.decode(encoding) + try: + stream.write(t) + except OSError: + # suppress "broken pipe" errors. + # no known way to handle this on Python 3 however + # as the exception is "ignored" (noisily) in TextIOWrapper. + break + + +@contextmanager +def status( + status_msg: str, newline: bool = False, quiet: bool = False +) -> Iterator[None]: + msg(status_msg + " ...", newline, flush=True, quiet=quiet) + try: + yield + except: + if not quiet: + write_outstream(sys.stdout, " FAILED\n") + raise + else: + if not quiet: + write_outstream(sys.stdout, " done\n") + + +def err(message: str, quiet: bool = False) -> None: + log.error(message) + msg(f"FAILED: {message}", quiet=quiet) + sys.exit(-1) + + +def obfuscate_url_pw(input_url: str) -> str: + return url.make_url(input_url).render_as_string(hide_password=True) + + +def warn(msg: str, stacklevel: int = 2) -> None: + warnings.warn(msg, UserWarning, stacklevel=stacklevel) + + +def warn_deprecated(msg: str, stacklevel: int = 2) -> None: + warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel) + + +def msg( + msg: str, newline: bool = True, flush: bool = False, quiet: bool = False +) -> None: + if quiet: + return + if TERMWIDTH is None: + write_outstream(sys.stdout, msg) + if newline: + write_outstream(sys.stdout, "\n") + else: + # left indent output lines + indent = " " + lines = textwrap.wrap( + msg, + TERMWIDTH, + initial_indent=indent, + subsequent_indent=indent, + ) + if len(lines) > 1: + for line in lines[0:-1]: + write_outstream(sys.stdout, line, "\n") + write_outstream(sys.stdout, lines[-1], ("\n" if newline else "")) + if flush: + sys.stdout.flush() + + +def format_as_comma(value: Optional[Union[str, Iterable[str]]]) -> str: + if value is None: + return "" + elif isinstance(value, str): + return value + elif isinstance(value, Iterable): + return ", ".join(value) + else: + raise ValueError("Don't know how to comma-format %r" % value) diff --git a/venv/lib/python3.12/site-packages/alembic/util/pyfiles.py b/venv/lib/python3.12/site-packages/alembic/util/pyfiles.py new file mode 100644 index 0000000..135a42d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/pyfiles.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +import atexit +from contextlib import ExitStack +import importlib +from importlib import resources +import importlib.machinery +import importlib.util +import os +import pathlib +import re +import tempfile +from types import ModuleType +from typing import Any +from typing import Optional +from typing import Union + +from mako import exceptions +from mako.template import Template + +from .exc import CommandError + + +def template_to_file( + template_file: Union[str, os.PathLike[str]], + dest: Union[str, os.PathLike[str]], + output_encoding: str, + *, + append_with_newlines: bool = False, + **kw: Any, +) -> None: + template = Template(filename=_preserving_path_as_str(template_file)) + try: + output = template.render_unicode(**kw).encode(output_encoding) + except: + with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as ntf: + ntf.write( + exceptions.text_error_template() + .render_unicode() + .encode(output_encoding) + ) + fname = ntf.name + raise CommandError( + "Template rendering failed; see %s for a " + "template-oriented traceback." % fname + ) + else: + with open(dest, "ab" if append_with_newlines else "wb") as f: + if append_with_newlines: + f.write("\n\n".encode(output_encoding)) + f.write(output) + + +def coerce_resource_to_filename(fname_or_resource: str) -> pathlib.Path: + """Interpret a filename as either a filesystem location or as a package + resource. + + Names that are non absolute paths and contain a colon + are interpreted as resources and coerced to a file location. + + """ + # TODO: there seem to be zero tests for the package resource codepath + if not os.path.isabs(fname_or_resource) and ":" in fname_or_resource: + tokens = fname_or_resource.split(":") + + # from https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-filename # noqa E501 + + file_manager = ExitStack() + atexit.register(file_manager.close) + + ref = resources.files(tokens[0]) + for tok in tokens[1:]: + ref = ref / tok + fname_or_resource = file_manager.enter_context( # type: ignore[assignment] # noqa: E501 + resources.as_file(ref) + ) + return pathlib.Path(fname_or_resource) + + +def pyc_file_from_path( + path: Union[str, os.PathLike[str]], +) -> Optional[pathlib.Path]: + """Given a python source path, locate the .pyc.""" + + pathpath = pathlib.Path(path) + candidate = pathlib.Path( + importlib.util.cache_from_source(pathpath.as_posix()) + ) + if candidate.exists(): + return candidate + + # even for pep3147, fall back to the old way of finding .pyc files, + # to support sourceless operation + ext = pathpath.suffix + for ext in importlib.machinery.BYTECODE_SUFFIXES: + if pathpath.with_suffix(ext).exists(): + return pathpath.with_suffix(ext) + else: + return None + + +def load_python_file( + dir_: Union[str, os.PathLike[str]], filename: Union[str, os.PathLike[str]] +) -> ModuleType: + """Load a file from the given path as a Python module.""" + + dir_ = pathlib.Path(dir_) + filename_as_path = pathlib.Path(filename) + filename = filename_as_path.name + + module_id = re.sub(r"\W", "_", filename) + path = dir_ / filename + ext = path.suffix + if ext == ".py": + if path.exists(): + module = load_module_py(module_id, path) + else: + pyc_path = pyc_file_from_path(path) + if pyc_path is None: + raise ImportError("Can't find Python file %s" % path) + else: + module = load_module_py(module_id, pyc_path) + elif ext in (".pyc", ".pyo"): + module = load_module_py(module_id, path) + else: + assert False + return module + + +def load_module_py( + module_id: str, path: Union[str, os.PathLike[str]] +) -> ModuleType: + spec = importlib.util.spec_from_file_location(module_id, path) + assert spec + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore + return module + + +def _preserving_path_as_str(path: Union[str, os.PathLike[str]]) -> str: + """receive str/pathlike and return a string. + + Does not convert an incoming string path to a Path first, to help with + unit tests that are doing string path round trips without OS-specific + processing if not necessary. + + """ + if isinstance(path, str): + return path + elif isinstance(path, pathlib.PurePath): + return str(path) + else: + return str(pathlib.Path(path)) diff --git a/venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py b/venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py new file mode 100644 index 0000000..ff2f2c9 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/util/sqla_compat.py @@ -0,0 +1,510 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import contextlib +import re +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import Optional +from typing import Protocol +from typing import Set +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import __version__ +from sqlalchemy import schema +from sqlalchemy import sql +from sqlalchemy import types as sqltypes +from sqlalchemy.schema import CheckConstraint +from sqlalchemy.schema import Column +from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.sql import visitors +from sqlalchemy.sql.base import DialectKWArgs +from sqlalchemy.sql.elements import BindParameter +from sqlalchemy.sql.elements import ColumnClause +from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.elements import UnaryExpression +from sqlalchemy.sql.naming import _NONE_NAME as _NONE_NAME # type: ignore[attr-defined] # noqa: E501 +from sqlalchemy.sql.visitors import traverse +from typing_extensions import TypeGuard + +if TYPE_CHECKING: + from sqlalchemy import ClauseElement + from sqlalchemy import Identity + from sqlalchemy import Index + from sqlalchemy import Table + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Dialect + from sqlalchemy.engine import Transaction + from sqlalchemy.sql.base import ColumnCollection + from sqlalchemy.sql.compiler import SQLCompiler + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import SchemaItem + +_CE = TypeVar("_CE", bound=Union["ColumnElement[Any]", "SchemaItem"]) + + +class _CompilerProtocol(Protocol): + def __call__(self, element: Any, compiler: Any, **kw: Any) -> str: ... + + +def _safe_int(value: str) -> Union[int, str]: + try: + return int(value) + except: + return value + + +_vers = tuple( + [_safe_int(x) for x in re.findall(r"(\d+|[abc]\d)", __version__)] +) +# https://docs.sqlalchemy.org/en/latest/changelog/changelog_14.html#change-0c6e0cc67dfe6fac5164720e57ef307d +sqla_14_18 = _vers >= (1, 4, 18) +sqla_14_26 = _vers >= (1, 4, 26) +sqla_2 = _vers >= (2,) +sqla_2_0_25 = _vers >= (2, 25) +sqla_2_1 = _vers >= (2, 1) +sqlalchemy_version = __version__ + +if TYPE_CHECKING: + + def compiles( + element: Type[ClauseElement], *dialects: str + ) -> Callable[[_CompilerProtocol], _CompilerProtocol]: ... + +else: + from sqlalchemy.ext.compiler import compiles # noqa: I100,I202 + + +identity_has_dialect_kwargs = issubclass(schema.Identity, DialectKWArgs) + + +def _get_identity_options_dict( + identity: Union[Identity, schema.Sequence, None], + dialect_kwargs: bool = False, +) -> Dict[str, Any]: + if identity is None: + return {} + elif identity_has_dialect_kwargs: + assert hasattr(identity, "_as_dict") + as_dict = identity._as_dict() + if dialect_kwargs: + assert isinstance(identity, DialectKWArgs) + as_dict.update(identity.dialect_kwargs) + else: + as_dict = {} + if isinstance(identity, schema.Identity): + # always=None means something different than always=False + as_dict["always"] = identity.always + if identity.on_null is not None: + as_dict["on_null"] = identity.on_null + # attributes common to Identity and Sequence + attrs = ( + "start", + "increment", + "minvalue", + "maxvalue", + "nominvalue", + "nomaxvalue", + "cycle", + "cache", + "order", + ) + as_dict.update( + { + key: getattr(identity, key, None) + for key in attrs + if getattr(identity, key, None) is not None + } + ) + return as_dict + + +if sqla_2: + from sqlalchemy.sql.base import _NoneName +else: + from sqlalchemy.util import symbol as _NoneName # type: ignore[assignment] + + +_ConstraintName = Union[None, str, _NoneName] +_ConstraintNameDefined = Union[str, _NoneName] + + +def constraint_name_defined( + name: _ConstraintName, +) -> TypeGuard[_ConstraintNameDefined]: + return name is _NONE_NAME or isinstance(name, (str, _NoneName)) + + +def constraint_name_string(name: _ConstraintName) -> TypeGuard[str]: + return isinstance(name, str) + + +def constraint_name_or_none(name: _ConstraintName) -> Optional[str]: + return name if constraint_name_string(name) else None + + +AUTOINCREMENT_DEFAULT = "auto" + + +@contextlib.contextmanager +def _ensure_scope_for_ddl( + connection: Optional[Connection], +) -> Iterator[None]: + try: + in_transaction = connection.in_transaction # type: ignore[union-attr] + except AttributeError: + # catch for MockConnection, None + in_transaction = None + pass + + # yield outside the catch + if in_transaction is None: + yield + else: + if not in_transaction(): + assert connection is not None + with connection.begin(): + yield + else: + yield + + +def _safe_begin_connection_transaction( + connection: Connection, +) -> Transaction: + transaction = connection.get_transaction() + if transaction: + return transaction + else: + return connection.begin() + + +def _safe_commit_connection_transaction( + connection: Connection, +) -> None: + transaction = connection.get_transaction() + if transaction: + transaction.commit() + + +def _safe_rollback_connection_transaction( + connection: Connection, +) -> None: + transaction = connection.get_transaction() + if transaction: + transaction.rollback() + + +def _get_connection_in_transaction(connection: Optional[Connection]) -> bool: + try: + in_transaction = connection.in_transaction # type: ignore + except AttributeError: + # catch for MockConnection + return False + else: + return in_transaction() + + +def _idx_table_bound_expressions(idx: Index) -> Iterable[ColumnElement[Any]]: + return idx.expressions # type: ignore + + +def _copy(schema_item: _CE, **kw) -> _CE: + if hasattr(schema_item, "_copy"): + return schema_item._copy(**kw) + else: + return schema_item.copy(**kw) # type: ignore[union-attr] + + +def _connectable_has_table( + connectable: Connection, tablename: str, schemaname: Union[str, None] +) -> bool: + return connectable.dialect.has_table(connectable, tablename, schemaname) + + +def _exec_on_inspector(inspector, statement, **params): + with inspector._operation_context() as conn: + return conn.execute(statement, params) + + +def _nullability_might_be_unset(metadata_column): + from sqlalchemy.sql import schema + + return metadata_column._user_defined_nullable is schema.NULL_UNSPECIFIED + + +def _server_default_is_computed(*server_default) -> bool: + return any(isinstance(sd, schema.Computed) for sd in server_default) + + +def _server_default_is_identity(*server_default) -> bool: + return any(isinstance(sd, schema.Identity) for sd in server_default) + + +def _table_for_constraint(constraint: Constraint) -> Table: + if isinstance(constraint, ForeignKeyConstraint): + table = constraint.parent + assert table is not None + return table # type: ignore[return-value] + else: + return constraint.table + + +def _columns_for_constraint(constraint): + if isinstance(constraint, ForeignKeyConstraint): + return [fk.parent for fk in constraint.elements] + elif isinstance(constraint, CheckConstraint): + return _find_columns(constraint.sqltext) + else: + return list(constraint.columns) + + +def _resolve_for_variant(type_, dialect): + if _type_has_variants(type_): + base_type, mapping = _get_variant_mapping(type_) + return mapping.get(dialect.name, base_type) + else: + return type_ + + +if hasattr(sqltypes.TypeEngine, "_variant_mapping"): # 2.0 + + def _type_has_variants(type_): + return bool(type_._variant_mapping) + + def _get_variant_mapping(type_): + return type_, type_._variant_mapping + +else: + + def _type_has_variants(type_): + return type(type_) is sqltypes.Variant + + def _get_variant_mapping(type_): + return type_.impl, type_.mapping + + +def _get_table_key(name: str, schema: Optional[str]) -> str: + if schema is None: + return name + else: + return schema + "." + name + + +def _fk_spec(constraint: ForeignKeyConstraint) -> Any: + if TYPE_CHECKING: + assert constraint.columns is not None + assert constraint.elements is not None + assert isinstance(constraint.parent, Table) + + source_columns = [ + constraint.columns[key].name for key in constraint.column_keys + ] + + source_table = constraint.parent.name + source_schema = constraint.parent.schema + target_schema = constraint.elements[0].column.table.schema + target_table = constraint.elements[0].column.table.name + target_columns = [element.column.name for element in constraint.elements] + ondelete = constraint.ondelete + onupdate = constraint.onupdate + deferrable = constraint.deferrable + initially = constraint.initially + return ( + source_schema, + source_table, + source_columns, + target_schema, + target_table, + target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) + + +def _fk_is_self_referential(constraint: ForeignKeyConstraint) -> bool: + spec = constraint.elements[0]._get_colspec() + tokens = spec.split(".") + tokens.pop(-1) # colname + tablekey = ".".join(tokens) + assert constraint.parent is not None + return tablekey == constraint.parent.key + + +def _is_type_bound(constraint: Constraint) -> bool: + # this deals with SQLAlchemy #3260, don't copy CHECK constraints + # that will be generated by the type. + # new feature added for #3260 + return constraint._type_bound + + +def _find_columns(clause): + """locate Column objects within the given expression.""" + + cols: Set[ColumnElement[Any]] = set() + traverse(clause, {}, {"column": cols.add}) + return cols + + +def _remove_column_from_collection( + collection: ColumnCollection, column: Union[Column[Any], ColumnClause[Any]] +) -> None: + """remove a column from a ColumnCollection.""" + + # workaround for older SQLAlchemy, remove the + # same object that's present + assert column.key is not None + to_remove = collection[column.key] + + # SQLAlchemy 2.0 will use more ReadOnlyColumnCollection + # (renamed from ImmutableColumnCollection) + if hasattr(collection, "_immutable") or hasattr(collection, "_readonly"): + collection._parent.remove(to_remove) + else: + collection.remove(to_remove) + + +def _textual_index_column( + table: Table, text_: Union[str, TextClause, ColumnElement[Any]] +) -> Union[ColumnElement[Any], Column[Any]]: + """a workaround for the Index construct's severe lack of flexibility""" + if isinstance(text_, str): + c = Column(text_, sqltypes.NULLTYPE) + table.append_column(c) + return c + elif isinstance(text_, TextClause): + return _textual_index_element(table, text_) + elif isinstance(text_, _textual_index_element): + return _textual_index_column(table, text_.text) + elif isinstance(text_, sql.ColumnElement): + return _copy_expression(text_, table) + else: + raise ValueError("String or text() construct expected") + + +def _copy_expression(expression: _CE, target_table: Table) -> _CE: + def replace(col): + if ( + isinstance(col, Column) + and col.table is not None + and col.table is not target_table + ): + if col.name in target_table.c: + return target_table.c[col.name] + else: + c = _copy(col) + target_table.append_column(c) + return c + else: + return None + + return visitors.replacement_traverse( # type: ignore[call-overload] + expression, {}, replace + ) + + +class _textual_index_element(sql.ColumnElement): + """Wrap around a sqlalchemy text() construct in such a way that + we appear like a column-oriented SQL expression to an Index + construct. + + The issue here is that currently the Postgresql dialect, the biggest + recipient of functional indexes, keys all the index expressions to + the corresponding column expressions when rendering CREATE INDEX, + so the Index we create here needs to have a .columns collection that + is the same length as the .expressions collection. Ultimately + SQLAlchemy should support text() expressions in indexes. + + See SQLAlchemy issue 3174. + + """ + + __visit_name__ = "_textual_idx_element" + + def __init__(self, table: Table, text: TextClause) -> None: + self.table = table + self.text = text + self.key = text.text + self.fake_column = schema.Column(self.text.text, sqltypes.NULLTYPE) + table.append_column(self.fake_column) + + def get_children(self, **kw): + return [self.fake_column] + + +@compiles(_textual_index_element) +def _render_textual_index_column( + element: _textual_index_element, compiler: SQLCompiler, **kw +) -> str: + return compiler.process(element.text, **kw) + + +class _literal_bindparam(BindParameter): + pass + + +@compiles(_literal_bindparam) +def _render_literal_bindparam( + element: _literal_bindparam, compiler: SQLCompiler, **kw +) -> str: + return compiler.render_literal_bindparam(element, **kw) + + +def _get_constraint_final_name( + constraint: Union[Index, Constraint], dialect: Optional[Dialect] +) -> Optional[str]: + if constraint.name is None: + return None + assert dialect is not None + # for SQLAlchemy 1.4 we would like to have the option to expand + # the use of "deferred" names for constraints as well as to have + # some flexibility with "None" name and similar; make use of new + # SQLAlchemy API to return what would be the final compiled form of + # the name for this dialect. + return dialect.identifier_preparer.format_constraint( + constraint, _alembic_quote=False + ) + + +def _constraint_is_named( + constraint: Union[Constraint, Index], dialect: Optional[Dialect] +) -> bool: + if constraint.name is None: + return False + assert dialect is not None + name = dialect.identifier_preparer.format_constraint( + constraint, _alembic_quote=False + ) + return name is not None + + +def is_expression_index(index: Index) -> bool: + for expr in index.expressions: + if is_expression(expr): + return True + return False + + +def is_expression(expr: Any) -> bool: + while isinstance(expr, UnaryExpression): + expr = expr.element + if not isinstance(expr, ColumnClause) or expr.is_literal: + return True + return False + + +def _inherit_schema_deprecated() -> bool: + # at some point in 2.1 inherit_schema was replaced with a property + # so that's preset at the class level, while before it wasn't. + return sqla_2_1 and hasattr(sqltypes.Enum, "inherit_schema") diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA new file mode 100644 index 0000000..9bf7a9e --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/METADATA @@ -0,0 +1,145 @@ +Metadata-Version: 2.4 +Name: annotated-doc +Version: 0.0.4 +Summary: Document parameters, class attributes, return types, and variables inline, with Annotated. +Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= +License-Expression: MIT +License-File: LICENSE +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python +Classifier: Topic :: Internet +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development +Classifier: Typing :: Typed +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Project-URL: Homepage, https://github.com/fastapi/annotated-doc +Project-URL: Documentation, https://github.com/fastapi/annotated-doc +Project-URL: Repository, https://github.com/fastapi/annotated-doc +Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues +Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md +Requires-Python: >=3.8 +Description-Content-Type: text/markdown + +# Annotated Doc + +Document parameters, class attributes, return types, and variables inline, with `Annotated`. + + + Test + + + Coverage + + + Package version + + + Supported Python versions + + +## Installation + +```bash +pip install annotated-doc +``` + +Or with `uv`: + +```Python +uv add annotated-doc +``` + +## Usage + +Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable. + +For example, to document a parameter `name` in a function `hi` you could do: + +```Python +from typing import Annotated + +from annotated_doc import Doc + +def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None: + print(f"Hi, {name}!") +``` + +You can also use it to document class attributes: + +```Python +from typing import Annotated + +from annotated_doc import Doc + +class User: + name: Annotated[str, Doc("The user's name")] + age: Annotated[int, Doc("The user's age")] +``` + +The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`. + +## Who Uses This + +`annotated-doc` was made for: + +* [FastAPI](https://fastapi.tiangolo.com/) +* [Typer](https://typer.tiangolo.com/) +* [SQLModel](https://sqlmodel.tiangolo.com/) +* [Asyncer](https://asyncer.tiangolo.com/) + +`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/). + +## Reasons not to use `annotated-doc` + +You are already comfortable with one of the existing docstring formats, like: + +* Sphinx +* numpydoc +* Google +* Keras + +Your team is already comfortable using them. + +You prefer having the documentation about parameters all together in a docstring, separated from the code defining them. + +You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use. + +## Reasons to use `annotated-doc` + +* No micro-syntax to learn for newcomers, it’s **just Python** syntax. +* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc. +* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create. +* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring. +* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation. +* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**. +* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed. +* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions. +* A more formalized way to document other symbols, like type aliases, that could use Annotated. +* **Support** for apps using FastAPI, Typer and others. +* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer. + +## History + +I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition. + +The conclusion was that this was better done as an external effort, in a third-party library. + +So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends. + +## License + +This project is licensed under the terms of the MIT license. diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD new file mode 100644 index 0000000..549e005 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/RECORD @@ -0,0 +1,11 @@ +annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566 +annotated_doc-0.0.4.dist-info/RECORD,, +annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90 +annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34 +annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086 +annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52 +annotated_doc/__pycache__/__init__.cpython-312.pyc,, +annotated_doc/__pycache__/main.cpython-312.pyc,, +annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075 +annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL new file mode 100644 index 0000000..045c8ac --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: pdm-backend (2.4.5) +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt new file mode 100644 index 0000000..c3ad472 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt @@ -0,0 +1,4 @@ +[console_scripts] + +[gui_scripts] + diff --git a/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..7a25446 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2025 Sebastián Ramírez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/annotated_doc/__init__.py b/venv/lib/python3.12/site-packages/annotated_doc/__init__.py new file mode 100644 index 0000000..a0152a7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc/__init__.py @@ -0,0 +1,3 @@ +from .main import Doc as Doc + +__version__ = "0.0.4" diff --git a/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..077ea2b90cdb02568aa193416d9521b459fd1174 GIT binary patch literal 279 zcmXv|yH3ME5WKU4ctoaxj)D%kz#AYXqDmd5PIJ2W7JS6_(A{BVcYFm6qUT%q14~Fq zRCGw`QgN1GirJYRX?NbU?0`hPp1sIvoL__aNAf2&M^QWzK?JRdqDxANNR~;P=A&;K zqn!5X%#_{Eq?i=bd7kzo#ie#>%=$574LUE4X6%h@hLhU{Dx(niN<7rOlO4>^uY<8L z$H&u!Gxt~q50bR@ipCcdbl(i8pRP))=b$EYeR(Dg7WPpNGd!W49YOzYxMs@c;k- literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc b/venv/lib/python3.12/site-packages/annotated_doc/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4f5b52f868b204e02b9b9a20352ce47041bb639 GIT binary patch literal 1927 zcmZux&2Jk;6rc639mh>DkhJ1MpmC}+Hg#;3UeXFxLQ0`rtV#hP1)=qLcN|Z%-r3Hq zT^xecLk=8!OC&y&0|%mB_&>Nf0+edi8~;EcNJu&H-mE_w)X{3*d-LAT`@OIEb$-4; zVEwrAy}0QR@+S)81+rOZZvfdR8S*jl$sX~orE7*!yVuDownm=_nghFZ*!-e@pHiEfS&<=2Kc<6=d&Oez-|`gO9Q7;NbGg#u^9ZW z^R@^%jXRwB((8437_(T&kjge?G#*5pvM`jR!=4u57Hxi*o^ZcerLm+fUZA7Z)7+;b zq+Yz^_s;DldKBk(fr!24Dx=Qsv7EUA;paG6YoP7KkA2 zak;&8PgLpGc-c3lrHau4WJ8nr3-KknwDt`T=U_AohT0&Xy>2>x*V-j6aOoP5Bdc*_ zDIAYVCUGk%7txeHAc ziT1$NT=j*=>wO;f>w#$1Ba`2owHxo(p|$vG#Jp{`#dZBc9_w8u!df&)@~$gF5xefr z9Hgr@mT=lNz6y`Yvv=P6*7+m<))`2|vv40RjMPNC1k66X3A^OMSoZ?`F5_o@us2A> zZtOT%lXI)0@?ZeDm<6KoPO8uw!cja@Q#h&gHku(Ofb-hrC;rKm^6{1O@bdMaZw!m8 z!~Ck61Le!XRY9{);t6m7IW8Ay#_Q=wzd_UtoMC-BOm|FuR1r@8hZ2=jl&SPCnk~Tg zm^?2oJXt?kymq{J?YH8}Fu!5|CRqtHqfE-Jbq#)o;gf}JhxI*X2D-9WcGJX=cKk89a9<>MtqZUnzlyhDF z5P~nH9fN!cZ7Q%~=x-h@|FrOXaryZ|X*gGUR$MqKmX3?1!_Q8Z*N)*Ut_|~RCLxtM ztT@h_>m~))?Mi5Ugatn?J2u+3SbiDFO1!;u(_DoRzH|Tt&Wyd^zRIr#U-& z>)@kP0^~6M2TAGy6WFL&sS`}Z)bV-#gEXW&3TMQ4$7VXUEz9~hbJe=_H-XLk@E=e) B=R^Pi literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/annotated_doc/main.py b/venv/lib/python3.12/site-packages/annotated_doc/main.py new file mode 100644 index 0000000..7063c59 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_doc/main.py @@ -0,0 +1,36 @@ +class Doc: + """Define the documentation of a type annotation using `Annotated`, to be + used in class attributes, function and method parameters, return values, + and variables. + + The value should be a positional-only string literal to allow static tools + like editors and documentation generators to use it. + + This complements docstrings. + + The string value passed is available in the attribute `documentation`. + + Example: + + ```Python + from typing import Annotated + from annotated_doc import Doc + + def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None: + print(f"Hi, {name}!") + ``` + """ + + def __init__(self, documentation: str, /) -> None: + self.documentation = documentation + + def __repr__(self) -> str: + return f"Doc({self.documentation!r})" + + def __hash__(self) -> int: + return hash(self.documentation) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Doc): + return NotImplemented + return self.documentation == other.documentation diff --git a/venv/lib/python3.12/site-packages/annotated_doc/py.typed b/venv/lib/python3.12/site-packages/annotated_doc/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA new file mode 100644 index 0000000..3ac05cf --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/METADATA @@ -0,0 +1,295 @@ +Metadata-Version: 2.3 +Name: annotated-types +Version: 0.7.0 +Summary: Reusable constraint types to use with typing.Annotated +Project-URL: Homepage, https://github.com/annotated-types/annotated-types +Project-URL: Source, https://github.com/annotated-types/annotated-types +Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases +Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin , Zac Hatfield-Dodds +License-File: LICENSE +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: Environment :: MacOS X +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Information Technology +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Requires-Python: >=3.8 +Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9' +Description-Content-Type: text/markdown + +# annotated-types + +[![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) +[![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types) +[![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types) +[![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE) + +[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of +adding context-specific metadata to existing types, and specifies that +`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special +logic for `x`. + +This package provides metadata objects which can be used to represent common +constraints such as upper and lower bounds on scalar values and collection sizes, +a `Predicate` marker for runtime checks, and +descriptions of how we intend these metadata to be interpreted. In some cases, +we also note alternative representations which do not require this package. + +## Install + +```bash +pip install annotated-types +``` + +## Examples + +```python +from typing import Annotated +from annotated_types import Gt, Len, Predicate + +class MyClass: + age: Annotated[int, Gt(18)] # Valid: 19, 20, ... + # Invalid: 17, 18, "19", 19.0, ... + factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ... + # Invalid: 4, 8, -2, 5.0, "prime", ... + + my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50] + # Invalid: (1, 2), ["abc"], [0] * 20 +``` + +## Documentation + +_While `annotated-types` avoids runtime checks for performance, users should not +construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`. +Downstream implementors may choose to raise an error, emit a warning, silently ignore +a metadata item, etc., if the metadata objects described below are used with an +incompatible type - or for any other reason!_ + +### Gt, Ge, Lt, Le + +Express inclusive and/or exclusive bounds on orderable values - which may be numbers, +dates, times, strings, sets, etc. Note that the boundary value need not be of the +same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]` +is fine, for example, and implies that the value is an integer x such that `x > 1.5`. + +We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)` +as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on +the `annotated-types` package. + +To be explicit, these types have the following meanings: + +* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum +* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum +* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum +* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum + +### Interval + +`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single +metadata object. `None` attributes should be ignored, and non-`None` attributes +treated as per the single bounds above. + +### MultipleOf + +`MultipleOf(multiple_of=x)` might be interpreted in two ways: + +1. Python semantics, implying `value % multiple_of == 0`, or +2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1), + where `int(value / multiple_of) == value / multiple_of`. + +We encourage users to be aware of these two common interpretations and their +distinct behaviours, especially since very large or non-integer numbers make +it easy to cause silent data corruption due to floating-point imprecision. + +We encourage libraries to carefully document which interpretation they implement. + +### MinLen, MaxLen, Len + +`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive. + +As well as `Len()` which can optionally include upper and lower bounds, we also +provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)` +and `Len(max_length=y)` respectively. + +`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`. + +Examples of usage: + +* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less +* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less +* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more +* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6 +* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8 + +#### Changed in v0.4.0 + +* `min_inclusive` has been renamed to `min_length`, no change in meaning +* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive** +* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic + meaning of the upper bound in slices vs. `Len` + +See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion. + +### Timezone + +`Timezone` can be used with a `datetime` or a `time` to express which timezones +are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime. +`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis)) +expresses that any timezone-aware datetime is allowed. You may also pass a specific +timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) +object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only +allow a specific timezone, though we note that this is often a symptom of fragile design. + +#### Changed in v0.x.x + +* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of + `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries. + +### Unit + +`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of +a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]` +would be a float representing a velocity in meters per second. + +Please note that `annotated_types` itself makes no attempt to parse or validate +the unit string in any way. That is left entirely to downstream libraries, +such as [`pint`](https://pint.readthedocs.io) or +[`astropy.units`](https://docs.astropy.org/en/stable/units/). + +An example of how a library might use this metadata: + +```python +from annotated_types import Unit +from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args + +# given a type annotated with a unit: +Meters = Annotated[float, Unit("m")] + + +# you can cast the annotation to a specific unit type with any +# callable that accepts a string and returns the desired type +T = TypeVar("T") +def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None: + if get_origin(tp) is Annotated: + for arg in get_args(tp): + if isinstance(arg, Unit): + return unit_cls(arg.unit) + return None + + +# using `pint` +import pint +pint_unit = cast_unit(Meters, pint.Unit) + + +# using `astropy.units` +import astropy.units as u +astropy_unit = cast_unit(Meters, u.Unit) +``` + +### Predicate + +`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values. +Users should prefer the statically inspectable metadata above, but if you need +the full power and flexibility of arbitrary runtime predicates... here it is. + +For some common constraints, we provide generic types: + +* `IsLower = Annotated[T, Predicate(str.islower)]` +* `IsUpper = Annotated[T, Predicate(str.isupper)]` +* `IsDigit = Annotated[T, Predicate(str.isdigit)]` +* `IsFinite = Annotated[T, Predicate(math.isfinite)]` +* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]` +* `IsNan = Annotated[T, Predicate(math.isnan)]` +* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]` +* `IsInfinite = Annotated[T, Predicate(math.isinf)]` +* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]` + +so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer +(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`. + +Some libraries might have special logic to handle known or understandable predicates, +for example by checking for `str.isdigit` and using its presence to both call custom +logic to enforce digit-only strings, and customise some generated external schema. +Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in +favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`. + +To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner. + +We do not specify what behaviour should be expected for predicates that raise +an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently +skip invalid constraints, or statically raise an error; or it might try calling it +and then propagate or discard the resulting +`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object` +exception. We encourage libraries to document the behaviour they choose. + +### Doc + +`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used. + +It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools. + +It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`. + +This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md). + +### Integrating downstream types with `GroupedMetadata` + +Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata. +This can help reduce verbosity and cognitive overhead for users. +For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata: + +```python +from dataclasses import dataclass +from typing import Iterator +from annotated_types import GroupedMetadata, Ge + +@dataclass +class Field(GroupedMetadata): + ge: int | None = None + description: str | None = None + + def __iter__(self) -> Iterator[object]: + # Iterating over a GroupedMetadata object should yield annotated-types + # constraint metadata objects which describe it as fully as possible, + # and may include other unknown objects too. + if self.ge is not None: + yield Ge(self.ge) + if self.description is not None: + yield Description(self.description) +``` + +Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently. + +Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself. + +Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`. + +### Consuming metadata + +We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103). + +It is up to the implementer to determine how this metadata is used. +You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases. + +## Design & History + +This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic +and Hypothesis, with the goal of making it as easy as possible for end-users to +provide more informative annotations for use by runtime libraries. + +It is deliberately minimal, and following PEP-593 allows considerable downstream +discretion in what (if anything!) they choose to support. Nonetheless, we expect +that staying simple and covering _only_ the most common use-cases will give users +and maintainers the best experience we can. If you'd like more constraints for your +types - follow our lead, by defining them and documenting them downstream! diff --git a/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD new file mode 100644 index 0000000..a66e278 --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/RECORD @@ -0,0 +1,10 @@ +annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046 +annotated_types-0.7.0.dist-info/RECORD,, +annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87 +annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083 +annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819 +annotated_types/__pycache__/__init__.cpython-312.pyc,, +annotated_types/__pycache__/test_cases.cpython-312.pyc,, +annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421 diff --git a/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL new file mode 100644 index 0000000..516596c --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.24.2 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..d99323a --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 the contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/annotated_types/__init__.py b/venv/lib/python3.12/site-packages/annotated_types/__init__.py new file mode 100644 index 0000000..74e0dee --- /dev/null +++ b/venv/lib/python3.12/site-packages/annotated_types/__init__.py @@ -0,0 +1,432 @@ +import math +import sys +import types +from dataclasses import dataclass +from datetime import tzinfo +from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union + +if sys.version_info < (3, 8): + from typing_extensions import Protocol, runtime_checkable +else: + from typing import Protocol, runtime_checkable + +if sys.version_info < (3, 9): + from typing_extensions import Annotated, Literal +else: + from typing import Annotated, Literal + +if sys.version_info < (3, 10): + EllipsisType = type(Ellipsis) + KW_ONLY = {} + SLOTS = {} +else: + from types import EllipsisType + + KW_ONLY = {"kw_only": True} + SLOTS = {"slots": True} + + +__all__ = ( + 'BaseMetadata', + 'GroupedMetadata', + 'Gt', + 'Ge', + 'Lt', + 'Le', + 'Interval', + 'MultipleOf', + 'MinLen', + 'MaxLen', + 'Len', + 'Timezone', + 'Predicate', + 'LowerCase', + 'UpperCase', + 'IsDigits', + 'IsFinite', + 'IsNotFinite', + 'IsNan', + 'IsNotNan', + 'IsInfinite', + 'IsNotInfinite', + 'doc', + 'DocInfo', + '__version__', +) + +__version__ = '0.7.0' + + +T = TypeVar('T') + + +# arguments that start with __ are considered +# positional only +# see https://peps.python.org/pep-0484/#positional-only-arguments + + +class SupportsGt(Protocol): + def __gt__(self: T, __other: T) -> bool: + ... + + +class SupportsGe(Protocol): + def __ge__(self: T, __other: T) -> bool: + ... + + +class SupportsLt(Protocol): + def __lt__(self: T, __other: T) -> bool: + ... + + +class SupportsLe(Protocol): + def __le__(self: T, __other: T) -> bool: + ... + + +class SupportsMod(Protocol): + def __mod__(self: T, __other: T) -> T: + ... + + +class SupportsDiv(Protocol): + def __div__(self: T, __other: T) -> T: + ... + + +class BaseMetadata: + """Base class for all metadata. + + This exists mainly so that implementers + can do `isinstance(..., BaseMetadata)` while traversing field annotations. + """ + + __slots__ = () + + +@dataclass(frozen=True, **SLOTS) +class Gt(BaseMetadata): + """Gt(gt=x) implies that the value must be greater than x. + + It can be used with any type that supports the ``>`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + gt: SupportsGt + + +@dataclass(frozen=True, **SLOTS) +class Ge(BaseMetadata): + """Ge(ge=x) implies that the value must be greater than or equal to x. + + It can be used with any type that supports the ``>=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + ge: SupportsGe + + +@dataclass(frozen=True, **SLOTS) +class Lt(BaseMetadata): + """Lt(lt=x) implies that the value must be less than x. + + It can be used with any type that supports the ``<`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + lt: SupportsLt + + +@dataclass(frozen=True, **SLOTS) +class Le(BaseMetadata): + """Le(le=x) implies that the value must be less than or equal to x. + + It can be used with any type that supports the ``<=`` operator, + including numbers, dates and times, strings, sets, and so on. + """ + + le: SupportsLe + + +@runtime_checkable +class GroupedMetadata(Protocol): + """A grouping of multiple objects, like typing.Unpack. + + `GroupedMetadata` on its own is not metadata and has no meaning. + All of the constraints and metadata should be fully expressable + in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`. + + Concrete implementations should override `GroupedMetadata.__iter__()` + to add their own metadata. + For example: + + >>> @dataclass + >>> class Field(GroupedMetadata): + >>> gt: float | None = None + >>> description: str | None = None + ... + >>> def __iter__(self) -> Iterable[object]: + >>> if self.gt is not None: + >>> yield Gt(self.gt) + >>> if self.description is not None: + >>> yield Description(self.gt) + + Also see the implementation of `Interval` below for an example. + + Parsers should recognize this and unpack it so that it can be used + both with and without unpacking: + + - `Annotated[int, Field(...)]` (parser must unpack Field) + - `Annotated[int, *Field(...)]` (PEP-646) + """ # noqa: trailing-whitespace + + @property + def __is_annotated_types_grouped_metadata__(self) -> Literal[True]: + return True + + def __iter__(self) -> Iterator[object]: + ... + + if not TYPE_CHECKING: + __slots__ = () # allow subclasses to use slots + + def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: + # Basic ABC like functionality without the complexity of an ABC + super().__init_subclass__(*args, **kwargs) + if cls.__iter__ is GroupedMetadata.__iter__: + raise TypeError("Can't subclass GroupedMetadata without implementing __iter__") + + def __iter__(self) -> Iterator[object]: # noqa: F811 + raise NotImplementedError # more helpful than "None has no attribute..." type errors + + +@dataclass(frozen=True, **KW_ONLY, **SLOTS) +class Interval(GroupedMetadata): + """Interval can express inclusive or exclusive bounds with a single object. + + It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which + are interpreted the same way as the single-bound constraints. + """ + + gt: Union[SupportsGt, None] = None + ge: Union[SupportsGe, None] = None + lt: Union[SupportsLt, None] = None + le: Union[SupportsLe, None] = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack an Interval into zero or more single-bounds.""" + if self.gt is not None: + yield Gt(self.gt) + if self.ge is not None: + yield Ge(self.ge) + if self.lt is not None: + yield Lt(self.lt) + if self.le is not None: + yield Le(self.le) + + +@dataclass(frozen=True, **SLOTS) +class MultipleOf(BaseMetadata): + """MultipleOf(multiple_of=x) might be interpreted in two ways: + + 1. Python semantics, implying ``value % multiple_of == 0``, or + 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of`` + + We encourage users to be aware of these two common interpretations, + and libraries to carefully document which they implement. + """ + + multiple_of: Union[SupportsDiv, SupportsMod] + + +@dataclass(frozen=True, **SLOTS) +class MinLen(BaseMetadata): + """ + MinLen() implies minimum inclusive length, + e.g. ``len(value) >= min_length``. + """ + + min_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, **SLOTS) +class MaxLen(BaseMetadata): + """ + MaxLen() implies maximum inclusive length, + e.g. ``len(value) <= max_length``. + """ + + max_length: Annotated[int, Ge(0)] + + +@dataclass(frozen=True, **SLOTS) +class Len(GroupedMetadata): + """ + Len() implies that ``min_length <= len(value) <= max_length``. + + Upper bound may be omitted or ``None`` to indicate no upper length bound. + """ + + min_length: Annotated[int, Ge(0)] = 0 + max_length: Optional[Annotated[int, Ge(0)]] = None + + def __iter__(self) -> Iterator[BaseMetadata]: + """Unpack a Len into zone or more single-bounds.""" + if self.min_length > 0: + yield MinLen(self.min_length) + if self.max_length is not None: + yield MaxLen(self.max_length) + + +@dataclass(frozen=True, **SLOTS) +class Timezone(BaseMetadata): + """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive). + + ``Annotated[datetime, Timezone(None)]`` must be a naive datetime. + ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be + tz-aware but any timezone is allowed. + + You may also pass a specific timezone string or tzinfo object such as + ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that + you only allow a specific timezone, though we note that this is often + a symptom of poor design. + """ + + tz: Union[str, tzinfo, EllipsisType, None] + + +@dataclass(frozen=True, **SLOTS) +class Unit(BaseMetadata): + """Indicates that the value is a physical quantity with the specified unit. + + It is intended for usage with numeric types, where the value represents the + magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]`` + or ``speed: Annotated[float, Unit('m/s')]``. + + Interpretation of the unit string is left to the discretion of the consumer. + It is suggested to follow conventions established by python libraries that work + with physical quantities, such as + + - ``pint`` : + - ``astropy.units``: + + For indicating a quantity with a certain dimensionality but without a specific unit + it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`. + Note, however, ``annotated_types`` itself makes no use of the unit string. + """ + + unit: str + + +@dataclass(frozen=True, **SLOTS) +class Predicate(BaseMetadata): + """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values. + + Users should prefer statically inspectable metadata, but if you need the full + power and flexibility of arbitrary runtime predicates... here it is. + + We provide a few predefined predicates for common string constraints: + ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and + ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which + can be given special handling, and avoid indirection like ``lambda s: s.lower()``. + + Some libraries might have special logic to handle certain predicates, e.g. by + checking for `str.isdigit` and using its presence to both call custom logic to + enforce digit-only strings, and customise some generated external schema. + + We do not specify what behaviour should be expected for predicates that raise + an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently + skip invalid constraints, or statically raise an error; or it might try calling it + and then propagate or discard the resulting exception. + """ + + func: Callable[[Any], bool] + + def __repr__(self) -> str: + if getattr(self.func, "__name__", "") == "": + return f"{self.__class__.__name__}({self.func!r})" + if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and ( + namespace := getattr(self.func.__self__, "__name__", None) + ): + return f"{self.__class__.__name__}({namespace}.{self.func.__name__})" + if isinstance(self.func, type(str.isascii)): # method descriptor + return f"{self.__class__.__name__}({self.func.__qualname__})" + return f"{self.__class__.__name__}({self.func.__name__})" + + +@dataclass +class Not: + func: Callable[[Any], bool] + + def __call__(self, __v: Any) -> bool: + return not self.func(__v) + + +_StrType = TypeVar("_StrType", bound=str) + +LowerCase = Annotated[_StrType, Predicate(str.islower)] +""" +Return True if the string is a lowercase string, False otherwise. + +A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string. +""" # noqa: E501 +UpperCase = Annotated[_StrType, Predicate(str.isupper)] +""" +Return True if the string is an uppercase string, False otherwise. + +A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string. +""" # noqa: E501 +IsDigit = Annotated[_StrType, Predicate(str.isdigit)] +IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63 +""" +Return True if the string is a digit string, False otherwise. + +A string is a digit string if all characters in the string are digits and there is at least one character in the string. +""" # noqa: E501 +IsAscii = Annotated[_StrType, Predicate(str.isascii)] +""" +Return True if all characters in the string are ASCII, False otherwise. + +ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too. +""" + +_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex]) +IsFinite = Annotated[_NumericType, Predicate(math.isfinite)] +"""Return True if x is neither an infinity nor a NaN, and False otherwise.""" +IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))] +"""Return True if x is one of infinity or NaN, and False otherwise""" +IsNan = Annotated[_NumericType, Predicate(math.isnan)] +"""Return True if x is a NaN (not a number), and False otherwise.""" +IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))] +"""Return True if x is anything but NaN (not a number), and False otherwise.""" +IsInfinite = Annotated[_NumericType, Predicate(math.isinf)] +"""Return True if x is a positive or negative infinity, and False otherwise.""" +IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))] +"""Return True if x is neither a positive or negative infinity, and False otherwise.""" + +try: + from typing_extensions import DocInfo, doc # type: ignore [attr-defined] +except ImportError: + + @dataclass(frozen=True, **SLOTS) + class DocInfo: # type: ignore [no-redef] + """ " + The return value of doc(), mainly to be used by tools that want to extract the + Annotated documentation at runtime. + """ + + documentation: str + """The documentation string passed to doc().""" + + def doc( + documentation: str, + ) -> DocInfo: + """ + Add documentation to a type annotation inside of Annotated. + + For example: + + >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ... + """ + return DocInfo(documentation) diff --git a/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/annotated_types/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90fa6f1848db43c4f5eee7dd9d7bdd67b15c79b0 GIT binary patch literal 18648 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc b/venv/lib/python3.12/site-packages/annotated_types/__pycache__/test_cases.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3c065a649bac0c178fdbf336d3897a6cb57b994 GIT binary patch literal 13267 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 Iterable[Case]: + # Gt, Ge, Lt, Le + yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Gt(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(date(2000, 1, 1))], + [date(2000, 1, 2), date(2000, 1, 3)], + [date(2000, 1, 1), date(1999, 12, 31)], + ) + yield Case( + Annotated[datetime, at.Gt(Decimal('1.123'))], + [Decimal('1.1231'), Decimal('123')], + [Decimal('1.123'), Decimal('0')], + ) + + yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1)) + yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1)) + yield Case( + Annotated[datetime, at.Ge(datetime(2000, 1, 1))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(1998, 1, 1), datetime(1999, 12, 31)], + ) + + yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4)) + yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Lt(datetime(2000, 1, 1))], + [datetime(1999, 12, 31), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000)) + yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9)) + yield Case( + Annotated[datetime, at.Le(datetime(2000, 1, 1))], + [datetime(2000, 1, 1), datetime(1999, 12, 31)], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + ) + + # Interval + yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1)) + yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1)) + yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1)) + yield Case( + Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))], + [datetime(2000, 1, 2), datetime(2000, 1, 3)], + [datetime(2000, 1, 1), datetime(2000, 1, 4)], + ) + + yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4)) + yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1)) + + # lengths + + yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) + yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) + + yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10)) + yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) + + yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10)) + yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234')) + + yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}]) + yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4})) + yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4))) + + # Timezone + + yield Case( + Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)] + ) + yield Case( + Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)] + ) + yield Case( + Annotated[datetime, at.Timezone(timezone.utc)], + [datetime(2000, 1, 1, tzinfo=timezone.utc)], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + yield Case( + Annotated[datetime, at.Timezone('Europe/London')], + [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))], + [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], + ) + + # Quantity + + yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m')) + + # predicate types + + yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom']) + yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC']) + yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2']) + yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀']) + + yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5]) + + yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf]) + yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23]) + yield Case(at.IsNan[float], [math.nan], [1.23, math.inf]) + yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan]) + yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23]) + yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf]) + + # check stacked predicates + yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan]) + + # doc + yield Case(Annotated[int, at.doc("A number")], [1, 2], []) + + # custom GroupedMetadata + class MyCustomGroupedMetadata(at.GroupedMetadata): + def __iter__(self) -> Iterator[at.Predicate]: + yield at.Predicate(lambda x: float(x).is_integer()) + + yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5]) diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA new file mode 100644 index 0000000..dbeb198 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/METADATA @@ -0,0 +1,96 @@ +Metadata-Version: 2.4 +Name: anyio +Version: 4.12.1 +Summary: High-level concurrency and networking framework on top of asyncio or Trio +Author-email: Alex Grönholm +License-Expression: MIT +Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ +Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html +Project-URL: Source code, https://github.com/agronholm/anyio +Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Framework :: AnyIO +Classifier: Typing :: Typed +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.9 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" +Requires-Dist: idna>=2.8 +Requires-Dist: typing_extensions>=4.5; python_version < "3.13" +Provides-Extra: trio +Requires-Dist: trio>=0.32.0; python_version >= "3.10" and extra == "trio" +Requires-Dist: trio>=0.31.0; python_version < "3.10" and extra == "trio" +Dynamic: license-file + +.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/anyio/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/anyio?branch=master + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest + :target: https://anyio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation +.. image:: https://badges.gitter.im/gitterHQ/gitter.svg + :target: https://gitter.im/python-trio/AnyIO + :alt: Gitter chat + +AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or +Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony +with the native SC of Trio itself. + +Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or +Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full +refactoring necessary. It will blend in with the native libraries of your chosen backend. + +To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it +`here `_. + +Documentation +------------- + +View full documentation at: https://anyio.readthedocs.io/ + +Features +-------- + +AnyIO offers the following functionality: + +* Task groups (nurseries_ in trio terminology) +* High-level networking (TCP, UDP and UNIX sockets) + + * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python + 3.8) + * async/await style UDP sockets (unlike asyncio where you still have to use Transports and + Protocols) + +* A versatile API for byte streams and object streams +* Inter-task synchronization and communication (locks, conditions, events, semaphores, object + streams) +* Worker threads +* Subprocesses +* Subinterpreter support for code parallelization (on Python 3.13 and later) +* Asynchronous file I/O (using worker threads) +* Signal handling +* Asynchronous version of the functools_ module + +AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. +It even works with the popular Hypothesis_ library. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _Trio: https://github.com/python-trio/trio +.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency +.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning +.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs +.. _pytest: https://docs.pytest.org/en/latest/ +.. _functools: https://docs.python.org/3/library/functools.html +.. _Hypothesis: https://hypothesis.works/ diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD new file mode 100644 index 0000000..4b3b57c --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/RECORD @@ -0,0 +1,92 @@ +anyio-4.12.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +anyio-4.12.1.dist-info/METADATA,sha256=DfiDab9Tmmcfy802lOLTMEHJQShkOSbopCwqCYbLuJk,4277 +anyio-4.12.1.dist-info/RECORD,, +anyio-4.12.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 +anyio-4.12.1.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 +anyio-4.12.1.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 +anyio-4.12.1.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 +anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170 +anyio/__pycache__/__init__.cpython-312.pyc,, +anyio/__pycache__/from_thread.cpython-312.pyc,, +anyio/__pycache__/functools.cpython-312.pyc,, +anyio/__pycache__/lowlevel.cpython-312.pyc,, +anyio/__pycache__/pytest_plugin.cpython-312.pyc,, +anyio/__pycache__/to_interpreter.cpython-312.pyc,, +anyio/__pycache__/to_process.cpython-312.pyc,, +anyio/__pycache__/to_thread.cpython-312.pyc,, +anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_backends/__pycache__/__init__.cpython-312.pyc,, +anyio/_backends/__pycache__/_asyncio.cpython-312.pyc,, +anyio/_backends/__pycache__/_trio.cpython-312.pyc,, +anyio/_backends/_asyncio.py,sha256=xG6qv60mgGnL0mK82dxjH2b8hlkMlJ-x2BqIq3qv70Y,98863 +anyio/_backends/_trio.py,sha256=30Rctb7lm8g63ZHljVPVnj5aH-uK6oQvphjwUBoAzuI,41456 +anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_core/__pycache__/__init__.cpython-312.pyc,, +anyio/_core/__pycache__/_asyncio_selector_thread.cpython-312.pyc,, +anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc,, +anyio/_core/__pycache__/_eventloop.cpython-312.pyc,, +anyio/_core/__pycache__/_exceptions.cpython-312.pyc,, +anyio/_core/__pycache__/_fileio.cpython-312.pyc,, +anyio/_core/__pycache__/_resources.cpython-312.pyc,, +anyio/_core/__pycache__/_signals.cpython-312.pyc,, +anyio/_core/__pycache__/_sockets.cpython-312.pyc,, +anyio/_core/__pycache__/_streams.cpython-312.pyc,, +anyio/_core/__pycache__/_subprocesses.cpython-312.pyc,, +anyio/_core/__pycache__/_synchronization.cpython-312.pyc,, +anyio/_core/__pycache__/_tasks.cpython-312.pyc,, +anyio/_core/__pycache__/_tempfile.cpython-312.pyc,, +anyio/_core/__pycache__/_testing.cpython-312.pyc,, +anyio/_core/__pycache__/_typedattr.cpython-312.pyc,, +anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 +anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215 +anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448 +anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407 +anyio/_core/_fileio.py,sha256=uc7t10Vb-If7GbdWM_zFf-ajUe6uek63fSt7IBLlZW0,25731 +anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435 +anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016 +anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977 +anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806 +anyio/_core/_subprocesses.py,sha256=EXm5igL7dj55iYkPlbYVAqtbqxJxjU-6OndSTIx9SRg,8047 +anyio/_core/_synchronization.py,sha256=MgVVqFzvt580tHC31LiOcq1G6aryut--xRG4Ff8KwxQ,20869 +anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435 +anyio/_core/_tempfile.py,sha256=lHb7CW4FyIlpkf5ADAf4VmLHCKwEHF9nxqNyBCFFUiA,19697 +anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340 +anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508 +anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869 +anyio/abc/__pycache__/__init__.cpython-312.pyc,, +anyio/abc/__pycache__/_eventloop.cpython-312.pyc,, +anyio/abc/__pycache__/_resources.cpython-312.pyc,, +anyio/abc/__pycache__/_sockets.cpython-312.pyc,, +anyio/abc/__pycache__/_streams.cpython-312.pyc,, +anyio/abc/__pycache__/_subprocesses.cpython-312.pyc,, +anyio/abc/__pycache__/_tasks.cpython-312.pyc,, +anyio/abc/__pycache__/_testing.cpython-312.pyc,, +anyio/abc/_eventloop.py,sha256=GlzgB3UJGgG6Kr7olpjOZ-o00PghecXuofVDQ_5611Q,10749 +anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783 +anyio/abc/_sockets.py,sha256=ECTY0jLEF18gryANHR3vFzXzGdZ-xPwELq1QdgOb0Jo,13258 +anyio/abc/_streams.py,sha256=005GKSCXGprxnhucILboSqc2JFovECZk9m3p-qqxXVc,7640 +anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067 +anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721 +anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821 +anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141 +anyio/functools.py,sha256=HWj7GBEmc0Z-mZg3uok7Z7ZJn0rEC_0Pzbt0nYUDaTQ,10973 +anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158 +anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/pytest_plugin.py,sha256=3jAFQn0jv_pyoWE2GBBlHaj9sqXj4e8vob0_hgrsXE8,10244 +anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/streams/__pycache__/__init__.cpython-312.pyc,, +anyio/streams/__pycache__/buffered.cpython-312.pyc,, +anyio/streams/__pycache__/file.cpython-312.pyc,, +anyio/streams/__pycache__/memory.cpython-312.pyc,, +anyio/streams/__pycache__/stapled.cpython-312.pyc,, +anyio/streams/__pycache__/text.cpython-312.pyc,, +anyio/streams/__pycache__/tls.cpython-312.pyc,, +anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263 +anyio/streams/file.py,sha256=4WZ7XGz5WNu39FQHvqbe__TQ0HDP9OOhgO1mk9iVpVU,4470 +anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740 +anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390 +anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765 +anyio/streams/tls.py,sha256=Jpxy0Mfbcp1BxHCwE-YjSSFaLnIBbnnwur-excYThs4,15368 +anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100 +anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798 +anyio/to_thread.py,sha256=menEgXYmUV7Fjg_9WqCV95P9MAtQS8BzPGGcWB_QnfQ,2687 diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL new file mode 100644 index 0000000..e7fa31b --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt new file mode 100644 index 0000000..44dd9bd --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +anyio = anyio.pytest_plugin diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000..104eebf --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt new file mode 100644 index 0000000..c77c069 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio-4.12.1.dist-info/top_level.txt @@ -0,0 +1 @@ +anyio diff --git a/venv/lib/python3.12/site-packages/anyio/__init__.py b/venv/lib/python3.12/site-packages/anyio/__init__.py new file mode 100644 index 0000000..d23c5a5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/__init__.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin +from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin +from ._core._eventloop import current_time as current_time +from ._core._eventloop import get_all_backends as get_all_backends +from ._core._eventloop import get_available_backends as get_available_backends +from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class +from ._core._eventloop import run as run +from ._core._eventloop import sleep as sleep +from ._core._eventloop import sleep_forever as sleep_forever +from ._core._eventloop import sleep_until as sleep_until +from ._core._exceptions import BrokenResourceError as BrokenResourceError +from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter +from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess +from ._core._exceptions import BusyResourceError as BusyResourceError +from ._core._exceptions import ClosedResourceError as ClosedResourceError +from ._core._exceptions import ConnectionFailed as ConnectionFailed +from ._core._exceptions import DelimiterNotFound as DelimiterNotFound +from ._core._exceptions import EndOfStream as EndOfStream +from ._core._exceptions import IncompleteRead as IncompleteRead +from ._core._exceptions import NoEventLoopError as NoEventLoopError +from ._core._exceptions import RunFinishedError as RunFinishedError +from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError +from ._core._exceptions import WouldBlock as WouldBlock +from ._core._fileio import AsyncFile as AsyncFile +from ._core._fileio import Path as Path +from ._core._fileio import open_file as open_file +from ._core._fileio import wrap_file as wrap_file +from ._core._resources import aclose_forcefully as aclose_forcefully +from ._core._signals import open_signal_receiver as open_signal_receiver +from ._core._sockets import TCPConnectable as TCPConnectable +from ._core._sockets import UNIXConnectable as UNIXConnectable +from ._core._sockets import as_connectable as as_connectable +from ._core._sockets import connect_tcp as connect_tcp +from ._core._sockets import connect_unix as connect_unix +from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket +from ._core._sockets import ( + create_connected_unix_datagram_socket as create_connected_unix_datagram_socket, +) +from ._core._sockets import create_tcp_listener as create_tcp_listener +from ._core._sockets import create_udp_socket as create_udp_socket +from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket +from ._core._sockets import create_unix_listener as create_unix_listener +from ._core._sockets import getaddrinfo as getaddrinfo +from ._core._sockets import getnameinfo as getnameinfo +from ._core._sockets import notify_closing as notify_closing +from ._core._sockets import wait_readable as wait_readable +from ._core._sockets import wait_socket_readable as wait_socket_readable +from ._core._sockets import wait_socket_writable as wait_socket_writable +from ._core._sockets import wait_writable as wait_writable +from ._core._streams import create_memory_object_stream as create_memory_object_stream +from ._core._subprocesses import open_process as open_process +from ._core._subprocesses import run_process as run_process +from ._core._synchronization import CapacityLimiter as CapacityLimiter +from ._core._synchronization import ( + CapacityLimiterStatistics as CapacityLimiterStatistics, +) +from ._core._synchronization import Condition as Condition +from ._core._synchronization import ConditionStatistics as ConditionStatistics +from ._core._synchronization import Event as Event +from ._core._synchronization import EventStatistics as EventStatistics +from ._core._synchronization import Lock as Lock +from ._core._synchronization import LockStatistics as LockStatistics +from ._core._synchronization import ResourceGuard as ResourceGuard +from ._core._synchronization import Semaphore as Semaphore +from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics +from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED +from ._core._tasks import CancelScope as CancelScope +from ._core._tasks import create_task_group as create_task_group +from ._core._tasks import current_effective_deadline as current_effective_deadline +from ._core._tasks import fail_after as fail_after +from ._core._tasks import move_on_after as move_on_after +from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile +from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile +from ._core._tempfile import TemporaryDirectory as TemporaryDirectory +from ._core._tempfile import TemporaryFile as TemporaryFile +from ._core._tempfile import gettempdir as gettempdir +from ._core._tempfile import gettempdirb as gettempdirb +from ._core._tempfile import mkdtemp as mkdtemp +from ._core._tempfile import mkstemp as mkstemp +from ._core._testing import TaskInfo as TaskInfo +from ._core._testing import get_current_task as get_current_task +from ._core._testing import get_running_tasks as get_running_tasks +from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked +from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider +from ._core._typedattr import TypedAttributeSet as TypedAttributeSet +from ._core._typedattr import typed_attribute as typed_attribute + +# Re-export imports so they look like they live directly in this package +for __value in list(locals().values()): + if getattr(__value, "__module__", "").startswith("anyio."): + __value.__module__ = __name__ + + +del __value + + +def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]: + """Support deprecated aliases.""" + if attr == "BrokenWorkerIntepreter": + import warnings + + warnings.warn( + "The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.", + DeprecationWarning, + stacklevel=2, + ) + return BrokenWorkerInterpreter + + raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..561b69577498e99aefbbe89bf90a3a88941c7ff2 GIT binary patch literal 4656 zcmZ{n$!{FT6~=4GA!oR+;wDoXOCl+1;b@`ujXOn&A}J0j$t5f8=5v6n%04=EB( zU<5umfPuis!3L5bKI9TOhx`e?2028DV8oeZ00T)5zA3SN@hRVXGfjz#LQo%n_1>$c zYk5`rLqkKYz|XG+eqr}?3GojqL_gIfhKQ{h(1O6E8? zF5}EqPL*3NtC_3837KF{fRi%GoCMd%8s-{st*m9P1=qI&2B*<& zl1}Ku)_sAaRZt#A&pSj0*!|j#5%=^IynZ@P!%A?Ab5 zVfTnU!klvY+px0(?fEVLk~yE6*|yfrsTVa~gb3o?|`*J}=KR zp9Wu$7nsjD7u`$p67yN-vU^2dVIBrwl~@_W)&XPL*rcjO)B3GkepW1a-h%X#K0XTiNI?=nw=7v&=JO>jnLm}kICa*6pCcv&tp z-v-~4_n2qF_vL-&JKzWM0rQ;mmMf)Xo(Dga51ALhZ_Brt?}8u6N6d@P4_reU%o*@I z@*UD#msIf&kA7c zVB4l?JLZa`UiNg7Cu@3Hg=N#&l93-Amg!WsQcRwpa3@Lik30w?gfGKkry$ zj-TB?lQq1WQy3Ns@dYznC!?QJp0Qdo)@*4r7ZqB}EPW%*a8|A69cLTi?HnH1Yo6&C zT4j}8n*7G4@rClY)0!f(X?bqu!Cq6N85r5p1eJs8%0(m0(wf#s#k^-fL3w``^AoD5 z5_Ux1${7JZM}=_r{jVGK49g77HEp^t0^7=g=*4jCAXFYNcuU#5r_rcS8p3OuYLDuW z`=z$(uyakz(zdtiV`xNYmBAK2a+0vfhVJH!l{R(UGg*--kn|7+jU zc36%tHSm%l8n~-mUvC@!%42LqBZ#(GJ+EADcU#xtJQ**FP2*N9pdt$112gBdQu*VmlEp5t1`n$)|5FM)Nys7CZ)MS)v z=GJl0sjJ<>o=R;?qnX=AW@&V3Ib+OB&&@ARP9VB&oO4Mgi;qV!TJX>eHjFjx=V{aR zL>Wu1uF}!nREC9RacqyO)UM(j8|G>$MKriR+`hMKXr03%TT9B#`P$Um<-^sU$@xA` z|Eu=aUBiTp!xLhi&_%w z&tdh#l%;SLWRY*Il9)`nXm4)k!P2@)4ZJ>Npwvjp4qj^bL@FP= zrbhV=8c5k57SyzczNYrEY_&m%Ytz9E=jo1oPHm;IFDS_b>72QZ7J_v@M_DdVdehFTH14m>v}3QNbKBv%@0}ew zeI^~?B0Pyqvw^onkmik%HVj;9p9qPIYQD$(oZr#f&(t}AjvV3 z<0L0YPLd3fq)ASZoF+L#au)KUd%n;DJxs>7XiWK)@>Qk+eU9p!CwH_s^om{AzGv_7 z`&M6|h>IkbNG_9HA-PI&jpRDX4U!R(QAnY_+#6?0Jx2C%k_nPYk|~mDNTDf;MSiBa z);GyT`&i#1xlJ-la))G&WFE5nouR^2>#gYp^4uj^B*{=rv_hPA`N%GjZ<*vC1;puP zQm95gH=UqRy=gjm6%-PN!Bj|;j~M3{9R^!=xUNmAeV>Gm-EV|e#B}_=PhKtMp6mKY zC{h{^7NE~7W3gCK9Qv1dP!w}Tak(hQ7#51+Xi=OfimOF&u_$JW;x6kX>v&NBE)~U% zq5$J}xhO6a#p$9LUd4Hv~oQ7m!*##|aI6t&Lr@X|$rUsOaGD$Yg~ zNB&(cn%h5``q|Bo=RcZ%*53E5ssBHsBG&)j^j{N=A8dV6|LMxJ#Nb~N!+%N)|L(yT giR)kX_I;4}BH8o23Q^B%MOFQ`)%CI4F`n`N0;VT)GXMYp literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/from_thread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5d2a63df3b9cd4ba813294637f1331d5ff137fc GIT binary patch literal 25899 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/lowlevel.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b19e2ea52dc39f250885f8a409437c17ac9cacda GIT binary patch literal 8022 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 E0%H9vMF0Q* literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/pytest_plugin.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ab9f138d90ac85361816e144f3ec1aa1b7a20a9 GIT binary patch literal 14353 zcmcJ0Yj6}tnqXB|Rli%^dZHInNdid?QVRsgM&{*_K!8Vr0mhz@hiRIwl4#KnO;$-D zjpUU*p0(uQ)*@zhff!#{zVq$KXEv7ov9bJQu)X(~KWZdj_twcbnq4m9{O3*so`7XH zF7ErX9^Ht>F&7(KpscLSFTeaU^ULp**}wMr+zdP)w7ehVTN&mb@WXrrJ;Odc!ZFM> z=DUo-jvufxCL6-B3q?^+$s{kxJmJlzuUhR%yFb_BKBll-({S_fKk*%HQTc zF;EX~liH;UfZ8sFq)qT{voUhX$Y8E#GbdHvtonr2zoZVSIuA$a8`{*EZE_UoIcbMf zn}?(055Xbrls4zJ*+JW!Hz~}$r8=moG{<^}092!eT5vx`jQ=%!78dGCQ z*(!9#l!UBaNJ)T^dpS9w@zIE)0zK~2W9LuFKN^$i7muBNg)9kG6~nR`j*LfQl)mtJ zAVE$_N|4veqho}~NvOrn!|~X85+M^>F>OGL3}CH>$O>R73}5x!D(Jl=M(02d%a^0_ zD4j1P_Y4qvYAgve6|{W?@`?G%qf=XS*o5nY!H-TB=OD!?aUhkHHNQm%p%^9u*4&yP zzOY^uR&d6n#^!_`7{&pA&>{P<7D}%%s#Q0|sK#bs7?V*6KgGnDA=~Mv;dqJ(5)1B! zD&nQj6k{=mij zTxTXm<->%e2r!MqWL;b`@rlxLA(fCjBIl*Ccw{m*+0iR27uD2g$AElEJV{a`a#U41 zMk5mmIjmerjmCy1!ir4BV^O(dTuzR60Ka#PPQY$T?h5Vf>`;JH+ed+2U{fj`bfj1f zj2a$|j}6C?q0tGg2KFt9L{)PR!jYsDPU(D2T97XIQ+@==472Jeo^N@7*NUS)>#w+O zoBi>UvwGE4I^XyHcUN3ZH@U^WPrSF;d#!s{J>~Pm*CSU)K5AO=G~evLHGDhrS0kTo zS=rvVTDs|d;r-}GyKizgPcJ$@Jg`)^GhNwvt8ck<-;#IVeRt_n*{jR$*OvI#9zv%~ z)M8=yKntJ=S^e)}#$I7qWOoFj&rDLqeMg@h%TC)=!)^fPHfkfB>N2uc+051)Bj&Z{ zRJ#c|f~&aUQH#uiDb8d+mYHHNvx<#{jW*5ZwJ$bdbMGnJNNMiN%uTT-)0*!Hqn4Sq z5<3Z$L_pK$#{OrvYhVLC1+xiZawSSbj*=PMaptXj$^drbW6l-W9?WGn!bO+~_}BEW z+{Y#65_6gRK68ov3+@v8v2B3hHVt}74ZH|W6+y8?YB4Fnq#P2>83kp9n~C5oX+BT^ zG6@@6Nh^sGIikv8%K5q&)rw4c8kZxAtY|_so>F8`AzouohLe#5NM`fP*b$nOY8KdN zu**}Rb`+FZB0QuSdv>Fy(YqkKpVZL@V&P_{>y4BJx3qM#c?VNS4 zRyO>?GwWORh|8YFCC|36?M!)SF}_^(((K_?Z&k+InD#cVG#$7jtay*CRR`}?cPyTo z^{-a9WUBY1tM_ERWf^Zv+S{_|U-rKEkh9geXZ>G08KHDOlIAzf46NH6!mib#&G(#} z?>j55H>904vqfL}H?Nk8OQmf~-Zn^SNA+oM{ff6C8|qy0cFy+SII-?v0(FnS<`~ze z|Ll1Hty3l4)RawFx;Y4hqXcG8Z*U=SXvAU zj7MT6o5bDZKQXpB3k(S|U32R~Jdu)Q&5qJJs!q0pEphCGsLMGKtHf4AsyFH&jVRR{ zBV%f+F$l(oLz0IgV{ujEFUavx5YG09B*l_3%#B6}$XSI-X?>joh8zVLoihkp;z!|6 zLDrmMvb_79{x|!x&a!NvGV9w1vniDPp5X;M0S(F&9{m4>nr|9>o`hjWELjJQj7Q>3 zc_HysQNIQgX(?3`ieW}9)t)Pp1hXx930f7xZZ{ze?9BjY$k!j1id}hp9RVf67lFq0F4&Ytnp8)>)i&zV@}i3HEh2g^A)W-8DG)wJ$9FSU0{5>*Ez=b0wk^w-7{x^de4Pa>f$95 z86B0$Q`}RG<%v$^H$PgpHV7py|<$m;)CfZv-5CS-qV`81@=_8YcUj;PSP2&@rI- z=pHm}OwB=b&+e%u#0h-~m+&4e;h%E)^1(#s=FQ5H*YlRk5-8x8Sf<{bqtF7Gq9+Ge z7{3WO#*k*fRA72XEU(2P3e3Jjw3@i4xuk8*G0mx#8JROPe*i}AG(RoOIf*dEOxYwB z)ybR-zF7FO;aAS5nc}AeQ*`cOrtPX_Sb`C1=uJ{HWiM!2a#J?5?O|BVAA_L(Gwz!0 zj|BnD>0}wSLW^p{GnLm$H#H$R1^-M;sEJ=-t~53=I~j$&1g7uz0UMU)jJY5;K>7hM z8Gt0{(VV8!L~trKUIoWUbB4jAi4TRtm_r#4IY&5*R#ljKBh;!QsCBe55cj&H4^f8j zQ0%e_qD%8&sqqnX=X%ua7ePV}E1El=0&9^XCp(ayoruh?8*Lhoooap*wWjF@t@Hwe zX~T%d4UN$3gJ))DMmGu^hG3f9YFGyb889U92t)g5$I5|_0VV?qB?HN0Y85VT_H0cc%Tfqjo$(2h1<3mkv^NHD+tS;5);$+t>+TG+eg4Lvp7-7dDL z%6|8SD#(8ta6$Q}RV?Ofgq|&qPelime9Y1%gBN8Jr@nw_N6-R-{uX%+6Y5Ue2}~_X z(vP4(FqTP~gW&pTUSk2!lvluXC&M%sbZBm)28=!(?^%Qoq(}_hV&Dz~R7dzaV*LRm z3ffJ$s!E)UTNyO zrLHvX%X+G>4`=E+)9~NZnXTJ8d;H3YY<)0O-c;9ed*nOOD%m%WqkTYq@WAGI#4sG2a>J zv2mZ;ILL!`cB*7zC`J@D3R1;-P$t7S`|vWzrstWrpf*`knvlhubVpfel~;hfY^fp( zKT}B3CqZt4thLP3Jjr+&6ehCAhNqBwFWF3aDsdOVZl4C#IAXZaa4gv~IpsHDO>zs_X_Qy}VC%BfoaQ(U=V`ydOER10q&7cib z;HSjkT%no3GFQrZW`a8NK^uudg2Is7RY)R|$x%yt%M9QA2O{Q|deX2r2>wR7+7rrT31 zo!_26eeH)=f0!u`-760*o%-&qd(~5SeR9RKEnC-;sSBm+LYcZf>AF3);>&eCcgE9o z{j(>s<#n0zo$2zOw_29VyJwHARyQm(Elk}ywOsw;>?^+w)c;*+)oN)4%>V6IRw|CJ zlpehwsJQ02>d6FJ(g6%z1YW#d`bA(rSp8+k*mXbS-40pj_GevX+|N{&JO+Mw$Muf; z9nUpJ5@ubq?pY6*_$-+CXj%i<#;2f5YwZ+p3~ci(Ve%|Ps}8NRcU|wg^CRMgY(j9q zk&EFW=L#F;1?5fwC;45^oM*%eE$TQih5c5b^x_N9V{=aK%z4cH4_4rF9F0Phge{k5 zg2`bkJQAyi;li^$4Q|5-2Iw|CBN(9D@boh8@ljh0+?umsX@iXA!QSL?AOFmD#mzI* z!Zfd1A<8+|oZFmdw0>P6K?}*l!gS{>XBG}hpRr>337~?7ziIvw20N=wQnxT<_q0RZ zY<^E!UPmu;Rs^b@YQ0tGRG~zF)J7|}#mo&^e4?;d>ZhDjj<;+)^A=Du?Rdj+i8Y<4 zXRK*nx2A*kfvAZK-?(fLA7vlLA@iqP{t?ieZO-ZpCJO~Nit`N*dUEEJV!U^wY5xo% zhFSkHI{;E082pZ|5Tp3Li0${0@^R3h*8C zC#BoC|0wKo=q4x1|1&{8XKRvkP@?g8U>7XaoJbQo0vZQGUw2Ii0FX&curW~(-Wv}8 zXe<&(g$EvtZk3wuNh~>(BIgklr;xmb2{If(jZcuB2-+H2@hkGg`Ba2R$1to-#zs{* z9i0HZmB3@dpci#Fl7m{2yp7GaV)Dn>mIE5WE`xC+rVtKgFKEw_H#a7pfaO6I$-9k_a6Vc&{p$69Uk2Lm?-GPQfswR)9wBD{Qaw?%?r~jrF*h34a^_EHgI(yQ`Vj?YhNjQajkUIeN(zAEtN(4&Ym3Tt?)vu@qBqW_E4M9e zdTsV7B&)?W*T22kv0U7}&NzM5w}&8jUR<3mtG;&P>WPKpza02*;MUPE%Jx4Jpmx2C zDXU!b`2X=i4O96&_AvzAE1MrMLUHwK{mxs>%k_I7Ft+l}>)dM1)`jTJ-j&Xt&jy#a zpZuccJK4sqi(N~#JJ$sOfEg>V{-<>hG<@>NVIqE_V6^hn7GLjn{xchUcn9|xU)#IG z_Sp{C;dbG(y}`q+!skr_rmX^$eBRCh%;!6V!+RZIQl;Po5e+N>PTaw(#sGFCp*$6I zMhlT@Qsg*lmBV}EbDAM$$k%b%vx zJaPR6z-HmkI7Y}PwRqO`45a{RJ%(UsAEGq5#>mVdD=}~(iiOKicF6`8et21s91sT0 zT?w*FPPo7%ye$kmCitLR^B*Cyd@@DUBXA^~A`_7lFyU^L1`v$LV`Om@Dn&HU;YJAD z>JZPv@tGt-Mu7l#Y$ygFN(wGysN%&~Qfh<9xiUu2(32@qf;0436prF3M#Q9u5h9!D zWGUpPH8gr2jYkydj-Xk?~Xvj*o^#BI5x7+#47rGQ|Dqp@cDg&^?_4W7OZpGbRxZ_Q&Fq2zPOS zPP|nBq^dHE1PIrsM2ZpcgW?ly;`uREMC|4;&z-~Jht=q47!iku^hG>(jt<4B9h07^ zHC+?hqa2Y!;t`l%C6<6I4|KTD3G|K!rETJdP6{K3Qk*$BlYyp^g_k0GD(H($O9jFK z%Cu;%qKwg>To@pCH3UTgx-s|wtikjQxVgHh;x!?RoCjAiWL4v0Nl=y$KcxIB<1Hf| z1B@DvFbYZ&(|>}1BQi!fJXyJ66NJo~{|(>@DrYznDX*I4ueevMH@_df(fW&Rv;4pD zKPx=`Kp`7sC;yyI4Q{kEaL|RZP~;h7C&9r?5|$jdTdN!z4z-EhQbdh(pL;_G2%SFB z^Gf*inNx>f?mq{|nij4?V7+~(04k_244#|8M9}>dy&y+nR)+O0Zfq&wX>~&1l}KEW zB_F`};q~uvE^vIh`$;~6>}1)-nZsFrfRO06fah7n1j^oT_L-5U*LNTdvmf(T# z9~iC5#p95IC}=7CXO5W#ExNJf%CEp5D>26b{ur;HGF&Ntp1*XZ+Vqzu|JvFTr)aDHsy-C{&1A~}{gFO%GE)6JbpuDg5Zq-$sA3!xqG zZ>N56?1QTgRL^RTmyd*x4IDnBIq}pYHagw~cNgG*6l9>&sOr*h&9uSn(fzCOxa&2? ziPQ8dn&tr?2C<==LjD~X)HU9+tDIVBqARhvZw`9q)Ia^sbe|hfc{mQxzif$AwZEBf4y6P`~?~mU7 zBe+d*&41PZk^Ptc5B)2F9cx7;+0wGv-qqsjpVh20MZPTyLbeVJabNM4`6JmHF;f#t z*Mx44|K;?@)3;x}bLKw{{%UZ!t@XnT=5Cx5~|+h zLxgtX`JC?5f_lXmB{&@5;LbuN9@t4KIJqPL0l%DJ!og|1Izg!-e*f7SyUMb|33A(QWO`uIU4DI+=@rMDye++zo@#$_v_H@5p z_%G=ahx2Tg=BUYkhUzxxlKd@XXg{zokRLTWgy_Kpqg!CInqKgI9qy6ow}AEB%gKqe zXM=Vt`S|bBf;jJ_~)l1T7|gdbA~Ul3qL0cim5+ z0_ySn!K&A<_T<&u2{I5p4AoCH(IMA6Eb22a@xP9A2AyOAQPmn<_0tUu``q~MLFbDe zP7o1s(5TjbF|p|)geO<9AlHZId@2<;WChSV@KmjSM9b%$=bpoQ5fXr<7XvIzJ8Jt^)new6U~>u1}*t5DDCqyJEc!PO}P#Od*S@wT0jsL*-{yXFT4YN1R?EMY1<2TIV zG;{bjOf8gu$+UdQH2hCy%Ojh>+8#2H{NBs3onJCFYn(m9m8QAU`Qdxq<}dm7wXMM! z|59<|G84=;HqZE${PoLBbGEo-rs$#F!G4d0=$t|Nj#s{``wiE%I)-kt!j?L9?Sz+tfZAI*X ztbhADhwoj`0lxS5(D$0)dLY0zwihYndgIg0AJ`h$fKd&(^`n430NvNUSvP+n-LikV z<=_f?=z)!8s}SmC7H-|aV4#o#->hKyqoQsW3>Qe&C-XXpt*|1Z l?m!F;h`9qXoBf;BA*(u+*D=dB8688eK>68$_& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/to_interpreter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94fbc1ba503ae4d5dc199303bddad81c9ba2f472 GIT binary patch literal 10379 zcmcIKTXY-8b+cG3umC{{0@WS7w)@ z$bt#0yqLsI=#!jCJxx!TCa0F7By~S}`jd~Ui4ym;AA~6%&>Ol@PIHc{KiE`{9kn05 zcNPnP5bdO|kuW=RXXehGJ9qBAbBF&_S?MH@zT5Mi=uevn`B$u%NiYiBX39dybuvL@ zB6Bek=YkwZWJ}Njbw0)idG@sit?VlV1^8NHwzxfLhkisBVq)A8bg;55=8RVaE8?!8 zD_$9_1ejeGV^wi?&>i;#J*@7CRmW?BHLUE6)yC_Bb*x+wtB*GX8#tl}Q+|`k;#YP} zzf1^qz>JkJ%Z}iVcw?}UBMFf~b`~KYF}{k8Z-L(KSW~5C%r9*Bw0^?;8>|`}B ztJ#BHthNJdN|(|;X$f{SNF#%EGDr`D>_mu#D7|u%+$^`;=AjBH=wnpwDx=cLpsib> zvJ~9SaJ8}7+k<-mU!~k}o6FJ5rrX>EiK_>;Q|ZGP5P$DrK>XusSi z_WKVrK~_F9qd-9%4##5Qi!sH= z>-~OI-e+lYT1mW^q|*vL zUYrk_+{58iI1<(7P8m}J$aZ!n0eF3u2e>B>D)Q*_(~ZxhVv25?NWidZpM_RqpDLq| zJ8)Hr%;1_w!_k-`>$V6`ped2OxIr&MtndTG+~x@=Tn90=kTgkiUndKev_MNfOt4iQ%L`fkX zKC5n>oB_65!}OA>+knkifOoudHKHOR5~N33|K(&{=?`C&XJX;`=zRZ(qE2hcRR5SV zD?Lw>Q%Xcr`%~e$xDrw?CsWbMxsa;RE76G3e?>`L>5oM(_NV5w%gMw(|K6wiRoKHP zkUil`irODe%te#^upK2E?N80=t~?+Vf&HbeKuk0I)Tbd^BsrVlXnVW9@y7n;^Gl;S zk<{(HabWqGRhRVPLp$-byiYhs%X)3Y4eRouw_MHtk>jEMNUejx*Mti4HAg%DExv`n z&b9M@A++-mfEQB!q;Z>{1kiP&kRS)=*aDi81FdLLtTNBelPFt3ss&k;1^C)ZWnXs4 zHh|gZ>^`S%4;g|*ahG*rGL{T$x+4}=wa|>J$dUYMWb7;{6Dd^6V=Go5XWuj?gY>0iwI%qYIH(5+tPu%B7^*WkuC! za&F#jQknR zSCKqC$dVxmv>)p8E|ax52SU{Ikip4v@|7dpdS`F8^FXHaK(=!*(>eIlBX88bp{;d} zXFP4o{PN51iNw>q-q5)0xL@0_bmGH@m87l%&K!)~Xn5jg?1#N40-s!Z5XQH?^Fm-9J^&{uMa_Fn&3@kR_xDNTp|eao zU0|zy;&3OR{JD*CZ2-NbH|T87dQvx zojo&tX86o0r}HuTq-Uhwy?xSCzI@XbIu6JyN`yU#aSe9>q1g9#f9`qy#|4OQt6bwK56$hTUj9dvF$O)D6ExjNda^D z&KQjOUl3(TAd@Zw*SvTHw01Oc>8Ng1z|o0-5EoKjE&pE`RpS`4v(gT8uw%T%RuxqL zKST3%QZ%GfIH)X{_6dw@p;fb+owgexbC)-Mp#20tj-tz{i-mJw*DLe;Xq;ceA#j(%;J6ez8Rwn`5QzhHA!q?? zu2B_m4npy8Gy$Kmrct%rFf+aQxchJkFsY{@1NWih$!y0!rek2OI!v%UD;o>p`8f-P->i@RkfLC20c zceVxlf|wRH^r~2zhTWmnz%g{C%2L`^g2@gpp$%L@+c|bdQIXJ3LgsVpHp67XofWO5 z?kU&BOo((IYyxFMG=gK9B#mMT#R*O-UXjs$8EzmCrjQO}i@>a%&r#+M(obQd6^)=S zf~5j=Fqx!#5#dwV!cWFE6&($}>c$L<{%U2`4V@OEaszKqv*vopK3z^Z59w>7QVnvP6O$I8fSW8WKFtJ$|Syxz0>=2JI^R%SD< z&Wx*d>D+SPO6|J4=3CQmy4%(pny<%RjjilkYv@_#f8%Z{I;HHSjM05SH$8~iAiy*Vry-W-&Sa|uWa3AGiZKh)xtT_4$% z60VkoYvk{>csL)^19%KdwWG|LQhJT^S;pr3)R~JVcWqUo5J`zrMG9+DOo3~u^gO$( zOK@4=2N44o#5*u%!d$?|)Ec_VkDG))1*nSG70_!nyH;CIuhopLy2cDHsqJ1h z?~A1d1}A33SfOy;ysR2B^9QN(>o875CxG2g*=kE9&?nMCXrqTQ!w>>S5@dxZo;O$-)pHHeo;3kjjHKFhoh*sexAT-M&WUAi$anZRgei&{GW9eBa zV9a2ITmYTFz@@pVa@Io+(-Y&!D|J@9uH*Q!RP19~VGCm#MiUf_%jtHLPFuc!$~Z3@ z;0CD>yE2zTr=jUJ_^EiGgKev6gnO*exMW)wtG-e7m8!SI9dLma8iAvN>&vJA8v*>`D$}(vs$WWVwyU1k&fh>0o$ygE9?+bu1Mq5z;&zPeU&AIN_9Jw3p`da5}Yi z6EY3dP%bg}@$|v^yUY>AuQ}@!ZfRL!bmzHVpu50PpptuK{Bioz)=|48F~h$D zSZjh4TSj_2$X>$a2lo8iY@Y{+071K13(myBKfvE>gqqo0 z)&cU$4`IITWntofAPa~8-(_LT+Bi5}APp0bui$h4Eom_M_+uV|@Ye9VAbd`Aw=lRR zX_po0QWS#ZEHZ%+d1*GPT`qw`tYQWYA4FvmQdrt*yZugv%I}wE!!!X`@4-RoOrq3& z_c2t15d9kbbR?O`zk&Em?f<7GBQ^#&R0$qsq&Y=nXpzc-?(dV-B*PU+%8HZ<1CiiP z0tCEB!4XN0M<^vaHCGmShwG#yP~NU50k_ff=W8et10qVB%5%b3Cdd5l=*DREt7v6Ni=f zGkJ~;rouEFXQ&2&s~7`C1IMuB+b~Ep$X1+6!ixpq1Z2hCFcz2BFb-Jd*Md zTo1(j@xvety$0`KF)1+?W0zATp z1tTgm2r&VOtQsqU05LqN;Up+{xN2j?G#pi7$3Eg2Y*2z3VE|QnRNPV~Jje|NgM!=1 zr6HDMGzOZAJ$v}-DH=G8LC8fvg1&&Tic`tt^i0ZdFKr0nArzt8Kt8TUHFz3SE=5&% z^<$zY_~O}y^_Tn}BOx%bbHF4^vhJJ!Xztk{$hn{JH0C3bxH z@DOo%-zPk-tNEL=ORf#EJ}Wk6#Ks$a_r#tJaG~5^aet?BC7l)b=j_n$QO*eqdi2mn zJUj7`u63in`I|3&Z4@8qS|8=?(D*yHT$a6$TzQnMf@S?Kx06)9!2RxF#sAUrGQW3u z|EcP*pVum%vvJFH!$-&uY*6|k7Z7Z>c_{sa3v}_Xdjg{64yl0poeD0{ZoT8OV!65- zuI_gRxWHlSou_#$A0FBP4L_^p0!M8>t3y;jYih&%$dDyq;eUPHdhCVp>}tH~)`Jx1D9`FR$;) zv@vQ)>EfJZD$p%zxu}qfb~84|aGIKUt1(vdwq?Zl!vz(!JgV6uBDiQtkt8T#(+Zio z87)v5ZOM!HtZpb$xHwM2vYEQ1DJ&s3bW2jz9g);bD3XL5I=zSupTP{(u8K^|Ux#sT znf`qj8^$050gj-qp6@Oo zKX3fS#d-7Idf`I(i{ZSdybDwH^9^Nx;vuCae1&ABQM?Sp`y7(VnC{z$kBO5rU{m2S zk-~|AhdKzC$6`>!Kp9OVh*#%Sy&{r~#b6V`bx^4d!R``>KYTGl@wTYjw7C=*c8a%h z_Vhv@wq;SfN8%B~V?9b=fi5WM z>enF$8^Uq_LfUgyzQVF--*ghL>Q}_^E8={Iw7f$)e@(hGr2Adc^$t1s4%zn(X}-_f zvV3iZuU(#fli&4g>z-BXo_DPW%fX+>Sw*33y-LdQ`0X8F-}{cnO#$j3)HMiT1R=}$ ziOrD}>N7(94ey(R^egKQ_qRuHS5lGY0u3U zS6dIPTI-i*)~pBC+xuVluC^UowR&$r`=Q@>yg7^4A?BVWj#`LGY#wN;wk;n2V6X!x zh3tb^PMTX*Ni|FXYqQ#NJmKo$t=?6;Jh)b|Yf*f=!h6HHR?)F2-uHAb z+m>f;y!h&8GoJ3%p2KUNBa4;mtp{&hSxIJE53UYOthK(dN}4xqR<15rM|$?WXFtew mt~;O1@$kK0(UcS53t^F}&K$NtpvhLX>44Hh%DFisBKlvc1ycJ9FpfWblHAV}~4so^1#peRzJDC(drnG#POBqh<74OxaE%uoV_D>DO8 zl2CPy(4Dh+AJ02%STZ zAQrKN9|Z^(K_FJ;Qo%RrCtakRQZ7nP)h@N1YFrvfDZe&AyJ+A?tlF;&=v{i5*7ywp zqstg5auvznT7PlCnEgWnz~ca_Vu z(O(g$bXCf9k-sY7a5)0iu4?(a*k2P^jHl(oSw8TYI7z-Vj`=}zn^2>+$pcy z>-Pu(45efz9|E33@^D8p(m=K`6y}18bntE3;#QP!hTNDJQ9RapYXN9L`n3R`v51N z^!O!uG$i!!$AmGdR1wh~4u$;g%%Ta?w#<2K5KU19}f23AJ zJu$2D5SpN%?G=F~qbN#_lOpb04ig%M%BPp9*R$m_Y8?_`WSKC5muf{&M)IOfG%7{k z#y6vhs!kCCs8F#&;aA$|M9&c`<%w$M3EmRbOjIe{`BbU*1dR4Y>x_2JI^oEdD%9!E z#Z?L|G71H}bqbwN6}ew55mzg@1-0RN$pokk3TS0eYAb5)MT@vb;ZepqLmxvkx~PUf z9HpZgR*fft)!=vpis~jd=1UanJ`N2}oH;CRQp#BEWm>7f5q4fw*Ncu+!gvf(!^9SaE1xQ( z$?aK6*|TRH6dVLrh5wW}B+xt&Jn-Og&smjzp;ryBnkUX!#H~tC6ZlqH5m+6o7q=^! z$EX>j*rR+!jp9y)p6FGmeERLBmkqCHPh3teG9%ZERyaHVe>-nLk$^QSvxQe_&N4Bi zl;l&kNa4vzq0UG#Q?znjo+)CBPrxqVi^V-k3#=(x^p~oitCUurCB%J7S;5LY$TTs_ zC%^(LpPk2<;_uL5)`AvCm0t2`wD^Ca-%KhLrJ^Qf+&SrUdlPoxp?up4Rk#E5Cn8E1 z(fw!Qz8&zGQd{m*{f$1eGp?L~IqRJ5B;u<;13chl&RM^yTgG$qPav;+nhMX;%ADtb z-PJdt(#A z7RQtd*Ugp4+7Fi$&hwNi)7syWlZ3O>(*#Fd&p^P|G$S78m}6jX2AT0l(2EUrX3{4f zFUa@s$07kR-UY@NWIRm9bYrpxdz)TvYG_=p@YGWUHrG>6F`=;Bpif}9AU4BT#uH>^ zPSGQrU?x32(HA_%jE8u}2km=90WbiwRpfSa#&@e+>;AqOJ`xP#`aTiH46+>M7|&5Y z5*Gba3|Q0wU(h2$pAi8w9UL0iGqiWF+-x&+_|#K5maMJX?8HM3GKYLYHZ&{AQLW>)GX+hc$=hS!ZT2yvHYS0y7-y zgPkVs4TZveJRjn>GSEyg#0ZgaIK<<%k{z()d?*--z^1{>j32leXfC(fK|I!+&{-!A z^1xwYv)yrGgb%`M!G^_~4DUT)@QBQmkMpyFEG#cGG2q!mIAwa3Gmu^SoVS2gi5C7} z#@@nfVE6r$%J>O7G^d-$c?OEcK`4q|&*s9dhGb2jwM@VY&`xA;+dMzmpj6i)JX!Y13X$eu<9srzjXlpW6x(r5DiL3G}o%q`ZvZy zHvYN|UWxTSLMo;8!J*90_==Gr= zU@fe{Hd;mt1CRrMiX;H-z%6zEU8iXDpb4ZYQ(6USNPHUxDdi?8m?O2A^SYE#D?RW8 z+;2H=E2@TD3eBjWR_AXiS=Z}^Z{ba-zup0{OfpW@E$nk1=o@?F!0;`+4f-D2)i*je zG%|d5?I9V>P)E*?b3zy}9d0W#c8QQQ!f||___ffE(;`s7Z5G@R@PWc=8Nz7@cf;++LjkVca}$@le<|KIIlT{-n>#wSy)++3xopZ4Xb0$3wvlZR`Vn&Q$g4Wc7x4^@cb5e%+C%-k!8?pB;qE3s0VZ@?vkI zdi~t+Z2x?J%2XPw?uwhbmdol=WxL)Ue0y8WHhPOx*{x}$vRKnJYHUlceki{Bq2GAl zZv3YciPgK4t9HkXj@vcJT7J%P))A{|OIX@xH7SE-{)sDw+U1h!h4I${OC_B#V`r?S zGi5EG-FvO1Vs>A83o2^)DrMjPRk|24dx)=>O^%z$2*Wp)yfiX5vM{h@Y)GrrMWrdT z{haZv@s;A`QsyGHRJ!&CA}niCm9-bz&bK8hTavbxxUJ<537L0riCp}rFH2V6L_|^P zRkJNkLVEYM2AL}t>X%k^C01=-GHpqjOTR30UKC>Hj(h0}==e{X1g1OWxKob1bbUqSyE`m> zH2GVLqqkc1+qJe{v-&-P=%wiQRBB9Xb&&s_S>0Qqd#_ZBX$JxP?^Wx1o$B`*RhVuj zFzs9g^!v0L+I+u=gdT?_5_SeahBWMEG2{*TdAQ{eUj)$@k3o=W)!GRB2b?N?5I*sF zVM7t|IzE=yu};Y+4VI)mO){0`ih`X~UQa5^(eNz#Ol;^X36%LC|v_=jW`HWCO6l6i1wV6gAtn0xfV z$k@oPk-dB!E+qlX^TT81&0|f^W5=7vKAmJcG;&~1-vRf&p2yrny?guI zV?+D;K!Jq7>>U?*jN4!d1EG@u!g|7hC9|^H<1xM>k;0T9>A6#!HzHd!cv^!VP!?|9 zip%vvWB5&?u%*lI!6Y;bsJsB0y%5m_C5-W1P{dwNB7>nx$&8gZpy%0OX>`xR67QstIJW%nI4K&W>SH!{gh8kwq6b#2MIj(A;1qOLP;Ts1o|?@F1~uBP`viag(oi7o_Al|AFpnY+uN5q`mfmgQ{~myI<_Y}`r{q_iH^azt$luE z(Ur2*CvDAfTl12wC1tBRH+*(@amSKvL(0~Ww5^HT)?6G}vTeI*f=+H*QCV$PeX1;7 zsg|vv9!t9xic>YM$(jxEnhlAXjmh$j@$!u~NGP~N0@n>1S<0?~LLQzQUf8r`tiQDh zgulHNl~EGIHpj}g%?>O(nn6#$I(^}p^Uu6B{SVLl{WG^oRdw+lq_P*! z!i-lGRek?S(V#MBQT6hZSd(u~HMAxhI^zwUZ|+YtY)aN_njgTresfxbY*h;rS4x_f zE7ztf2R_ig{exKfL73R8GPtfv%Wl)CVa??YZ**Vkesem$X6M_xW20k<`bS`%mn)eI zt>;@)rB$yUyztoh$6`&JVz$l8)%D5h&Ukg_Z8ceCo!5f&_NK+jL|J>%+#WNxr>&^S z_R^lYJqy|;BlBfZ`Qn~M(^Ap88%V9&MWj_!(Jo@yUiIo=ymi~J4<%arl8t={d;hhP z%7tU`lGQ02le9I(ZA};LZ?*oaE!Nz#WZQYiAlH>!xYMVSH`@LCEnSV&4R>!BBcuH$ zQp+2+^ySrf$2wk~oHZ`f)k(TGPS-9rEzvE@bX}5ejMI&a(IvWbnXX9CRY`hBoZj)a zHc1br4Y+~3>0-H`uhU(qqU-BBYP`~L!FSzFTVc7r202%4{#u@7c?IsKE0Ja0-DO+T zlJnt&b7!n%7rt7{;09eTVQwO-s49O)mpN0mhh>}Ln!Pd&O;_DZtAXQg+J>6?iLcW` z86m$;?^WRieDKWt-nKJMB3t9#JG%V*++8t2*?Gz)w=&1`PXc!Feq`Ujp7=SjkD&gl z6{vR%&HE_y&IT*c@2ZL6HJW!dB&Kz{==~1zeX9}2H5J3_)$ea2hTHV-Z`NRXM>XXC zOHGd&lHb)6!`n5#YoIXg?4d_&st?wej!^0kHN*%(f2gG}ZRptw1s|!25sT&{4TWia z4b=FknHb%!{-~vNw1WC*2Qg|Z{upV2{#d&T@;`3W0nf+nMi|q_-NdL}^YJzf(EmpD zFvHE{Ck=S8pEMDeZl*BZM!@(!S+5@M)_u~2D}PE5(Eq2DdZbwQsZIwSe`<4#TGXGe zbBr3*F{2vjn1uw+n2msrV|D`CiB;5%c57qZBn%?9UAr^dL7{ag`1taJ2A6 zEeCM4y20hkImKem0iKY5zo77CQl$-eDg!8yB^2jyrm$~p8LbF;ryrh100e#<9n3*| zPSy0wdVCWd$iNO6OlT!0gCUA6a>5*)ViZv2K9D9D2^{5kj96q0C%M(!?W%xQ8N(z4 zSgq(KV>)GkyX)A>b87hpNO%{H4&w;>#*!)|NIGvg0?q^YZu}FF=Oko1K)`M#letIn z9LpBbKLa7)hX?Q(%xblL+0>LYt%;k~Bur~#^xChM4P`eG3HjFA*Xn-R`q!<$XuDFk z@y(uuwF@*4%_QlDINh*#Xo+46_uh-eX)Q3{6|iCQY)KjVSr5@uNxoBoc++tHoPfxP@i`_?vKYh}k+&`NI9 zfcfNbk4#@Ueh~5j$WgX$jC`=@6fpx>5FC{p0y2Ru#1$cm9@M_l(v!ptHKUGFEGeTm z#;97xm^AoR8s1PeQT0#Ij4F5@I4GIpl?+FLB-jC#!wj9zohVmw^C_!-2t{dLD0soh zVI+C32fac^bC?f52J%)b+^hylvNH)UzB;}S-dJmt(tOHq5o?vNs7{f6M*m>$iF$=M zpWgRMtN$)J@$;w-eg*KuCRRIRC|DnCUatsIQpB5I4&V0TJ!1?uvh;Vhrux3E{V(>N zE^5fXghg|2o#U!$Ft3%_1ZDWWcXGZBP}dk%k^{^H_9*3`tSYaHlFt(GVsr`=y-g_v zMQ?-`dF>3mJ^?1CeOi0w*O@erjeQ4y>;e2(J<35r?54iG&zlcuqi~EJnK6EkJ)r%* ztCo}Z+RDD2`eBKf;h#A1Gq|>v?v&UMz6Ff-5ZdVXlxQu@l#a}#GL7V8t1M#$NH zjncoc?}z)6ls!(_7xYV1eM(=Ps;-UEwX>S}inOXq*9`u`g~R6$Uo3sYamkUW?wTD2 zKafou!FOC(eZ{m2*w5{+hg@MdqKe#=5{%E4UNbAC_Ku_5k&yd}q$RM*y5cj3H?ddNy^^tv7* zWh#%E8d4Rj=QUYJXmRST#$?A(ykjU)x%;g4T18#H=;Y;-$@czudw-&GU|tK-=}a#b z%@rl-syJP>u=`@;TlH@q`&Dbqxg+k_u|z+dqKga42bSmtSOH_%yzjNjB|~e8sn7s2qcImWC8l|I?v+fYVtT?g5tQaAM!IIn~hga~;ktZN7JVJu;U=uCC;& z(JDiQgwByFulliEP3O3Gdpj~$gUy2FE3cXzN|n~m_Ai$?&iT*!7dKqiB%61{n|CD2 z9{%ybJ+M}Q?XGYfcFDWi+QAm|?gsV1R`lB)Jtc!y^nOW?X~>9vw^cW2B0nHigL?7< zy&BV2-Cz^>L6vH-j{Klbjp-KMkdFLdBaP#SR6`W`A*BZTL!)lDmi(}^dAExESCtxQ z`3yVt^7fu?#Lv0-q3iJB%+L1@D?isea-@|}e#Cb~J`mI9l`JyGBT+};=X|n-H(j>k zCv{8|Uhje&|0q5V3`Bqf$}~0=v$RBE7H5~F2fQx=PjDET0@DmMh1(6IgD2AVl)pvmirplBpo4&2!YtV=GE3_66`OA)w%G5qMdU^+r~ch*M!e5_xQJ z$T*y22BVed+T8LJB7Q-X0XP}slc6rjqA)1xLdf7_CE5Humc|Jgc&$t+fuD%t7s@O^ znX#3P*+TrTEo3k|8GIx0=w%d8Mh3C%Bhm1<>xY5)dHm}e8NdXjUjDtO(55^q$oQdT zJjw9_JSn?e*IMJ|XL1pX;p$lwSYG#Sj(1k;}XAudcmwqG)~ zK34Iv*)HogejGk8p<56m*{r`yZ_A)<+xfqRJZxPG@QY{!Cx;;Z9o3~N%Ah)}y=6p% z{xekc8M1ta^q-^Z&r!n{sP_xR{1H7GM~{AvIzC5hKSvwBK5~MajI+~M$cn3!_4gFK2Oz4Fe+{PTzX58=_rU-F literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/__pycache__/to_thread.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcef473639e57acb48386e265fda09249f901fe2 GIT binary patch literal 3225 zcmcgu&2JmW6`$oUKg6XzNGW!lIP$bM9J6u6d=xdn)OHO!27;z;kf;el*H{jBN8(z` z9b#rz3RM+V8>8)^hn(sjQuJIn{R7%l@4d+N!RjJHQzS*A8w0s$;Zxt6T~e~*bC%N1 zo7p#S-n`%My_vsFPL>Hge|Yvo|7Re5ik;D8rGl~d1qio^M?51SJ)>n9#LKmE&^H6K zWkPEORxjVm!#MHsL7`V{6}4OlO1+8J1jt3N6qI{*%hvKlQ0Yy!CJn;Po5x4X?YCZe z<;R4`39x2^wdvM$Z>BY4kg%jjs{123!i!$zj*-r7)u2D^O^*As-jrv;Z~8-Xf;{f$ z)x4Qoh1Q%m>s4=AttSR%eQp)GYO)##$>pQ-a@Ck%p-@PMW@5u6&`vp zIDMJ*nd?V`*HdHl+^z}gyEe|rC6&z9V%!gSQut8_$}34}(Rq_ct1L)LZwPfsE~~|> zbu+254huaII`HSR(B(m5r>%A^DP|74S@;O9RDl0q5n}A!0^$3_0N^)rKcCyk{nUs? zdH{cvgU|Z$%#W-)I1?U&MEO)AN2ngHfeXm(yi`m4|dG4qb$YuK7{-z&Ml3aSs4e)DJ16 zYeKGY3EvT=g|fEv+LG39w+{rQ-KL_i?fHuG5aIJEL{Mu+Os&v0=0|?GOqYbDKA3k! z4{*q)XuFv??ww6%={3PW@LNUr1{P&gW2rdp#F6ew9>p@GfMLw(88FYn0hP>G+|zdX zdoI_$)l*{~PW|OjNIu$7v(F^!>5w#G&u+#)JvsXZVNp%m7Ya5ZLKe{dIN^pcu8*B| zm_<$6ZZFE1L%bqEGm;(L?wNqIRg9A#p$2SOyMYJ-VqFeo_b9+7*1|gVQzW`Vfieae zf`1^2?e~K38Yuv)Vp*VzyYrjOFOP$EH%f_gI4?qu`k`iQWHYLiz z$c+{PMl`9%`;xmXLUIH?*b_?-kgw!gyZuTEm00Cc`W_$Kf2=abp(B9C8b=OwL;*e= zx@yFM48|;Ln-Hb6RM2b$V;uvhpwS~KBv8Tt>7IaSpr`5P6oL{0L}RDU!1@B}7i=q~ zZdTmJ#sL;E?$y({Xlg2YO}q+;7`-k;|5Yi4Y~nUTK~>!E3#mhd1+FVaC}Kz|RHgxF zBg&Zz846=hvg(?7nk}c(1Beazj7f;O2?{Kt1D^+;(p!BbhxAtYxI|=We{KE!MF6~g z0MPSoy21xQ0e~~B8yLYhB%m)AB9T@11@Kmv|2DdQfCI{h%1SSVgV%mYzrFC{!V9lx z5WY$XSjZ1m1T|YcyYki{q@Ebu2v$^Z(QSR z^bIL)ayL?qJ{$D7qq?H+FAW^U<*M)U24J|_2>ec?KZv>_e0kx;ml{w~`15!cv1P6r zI&_Ww(Derr%e;Oa13|qDuP?~|Hp#F_O0!$V`R(J+Zk3LGT&fIn#^eu-&nolVN1y!9 zM1G?DV2adE-m9LvU%ha*dSSa%`L(xo`rKcS4oy-G5%~^u>O(+a!A0@xaXxmJwofh^(>xd45m8A(D7+*_P z>h*xM$VZ91`XkxHm`S|O=>h$^0zGvEUYq2>9H~5W&z`?;pTBFLzh__EvM$OK@Tp#r zPeHSO`f_$=UB8~*Yp#zjGdFIWrK2m(4f!-G&m30Kj8CkN5P?MAuiGfn4Jc6|*J-tW z+#ISeBsU7mv4mtL3(%2#N6SHvNoV{z#ez5j;bN-*!b6eJPpIhJgmtFgy z2`Fxs{$U^eUHP7Ua0ssI2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_backends/__init__.py b/venv/lib/python3.12/site-packages/anyio/_backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_backends/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c00db56507837a6af6b9c26086a9cfaae514691 GIT binary patch literal 217 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�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 r3(W_9tZt4j`l;AuDMRqBG`3X54pW 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 (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + +if sys.version_info >= (3, 11): + from asyncio import Runner + from typing import TypeVarTuple, Unpack +else: + import contextvars + import enum + import signal + from asyncio import coroutines, events, exceptions, tasks + + from exceptiongroup import BaseExceptionGroup + from typing_extensions import TypeVarTuple, Unpack + + class _State(enum.Enum): + CREATED = "created" + INITIALIZED = "initialized" + CLOSED = "closed" + + class Runner: + # Copied from CPython 3.11 + def __init__( + self, + *, + debug: bool | None = None, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ): + self._state = _State.CREATED + self._debug = debug + self._loop_factory = loop_factory + self._loop: AbstractEventLoop | None = None + self._context = None + self._interrupt_count = 0 + self._set_event_loop = False + + def __enter__(self) -> Runner: + self._lazy_init() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def close(self) -> None: + """Shutdown and close event loop.""" + loop = self._loop + if self._state is not _State.INITIALIZED or loop is None: + return + try: + _cancel_all_tasks(loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + if hasattr(loop, "shutdown_default_executor"): + loop.run_until_complete(loop.shutdown_default_executor()) + else: + loop.run_until_complete(_shutdown_default_executor(loop)) + finally: + if self._set_event_loop: + events.set_event_loop(None) + loop.close() + self._loop = None + self._state = _State.CLOSED + + def get_loop(self) -> AbstractEventLoop: + """Return embedded event loop.""" + self._lazy_init() + return self._loop + + def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval: + """Run a coroutine inside the embedded event loop.""" + if not coroutines.iscoroutine(coro): + raise ValueError(f"a coroutine was expected, got {coro!r}") + + if events._get_running_loop() is not None: + # fail fast with short traceback + raise RuntimeError( + "Runner.run() cannot be called from a running event loop" + ) + + self._lazy_init() + + if context is None: + context = self._context + task = context.run(self._loop.create_task, coro) + + if ( + threading.current_thread() is threading.main_thread() + and signal.getsignal(signal.SIGINT) is signal.default_int_handler + ): + sigint_handler = partial(self._on_sigint, main_task=task) + try: + signal.signal(signal.SIGINT, sigint_handler) + except ValueError: + # `signal.signal` may throw if `threading.main_thread` does + # not support signals (e.g. embedded interpreter with signals + # not registered - see gh-91880) + sigint_handler = None + else: + sigint_handler = None + + self._interrupt_count = 0 + try: + return self._loop.run_until_complete(task) + except exceptions.CancelledError: + if self._interrupt_count > 0: + uncancel = getattr(task, "uncancel", None) + if uncancel is not None and uncancel() == 0: + raise KeyboardInterrupt # noqa: B904 + raise # CancelledError + finally: + if ( + sigint_handler is not None + and signal.getsignal(signal.SIGINT) is sigint_handler + ): + signal.signal(signal.SIGINT, signal.default_int_handler) + + def _lazy_init(self) -> None: + if self._state is _State.CLOSED: + raise RuntimeError("Runner is closed") + if self._state is _State.INITIALIZED: + return + if self._loop_factory is None: + self._loop = events.new_event_loop() + if not self._set_event_loop: + # Call set_event_loop only once to avoid calling + # attach_loop multiple times on child watchers + events.set_event_loop(self._loop) + self._set_event_loop = True + else: + self._loop = self._loop_factory() + if self._debug is not None: + self._loop.set_debug(self._debug) + self._context = contextvars.copy_context() + self._state = _State.INITIALIZED + + def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None: + self._interrupt_count += 1 + if self._interrupt_count == 1 and not main_task.done(): + main_task.cancel() + # wakeup loop if it is blocked by select() with long timeout + self._loop.call_soon_threadsafe(lambda: None) + return + raise KeyboardInterrupt() + + def _cancel_all_tasks(loop: AbstractEventLoop) -> None: + to_cancel = tasks.all_tasks(loop) + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + async def _shutdown_default_executor(loop: AbstractEventLoop) -> None: + """Schedule the shutdown of the default executor.""" + + def _do_shutdown(future: asyncio.futures.Future) -> None: + try: + loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined] + loop.call_soon_threadsafe(future.set_result, None) + except Exception as ex: + loop.call_soon_threadsafe(future.set_exception, ex) + + loop._executor_shutdown_called = True + if loop._default_executor is None: + return + future = loop.create_future() + thread = threading.Thread(target=_do_shutdown, args=(future,)) + thread.start() + try: + await future + finally: + thread.join() + + +T_Retval = TypeVar("T_Retval") +T_contra = TypeVar("T_contra", contravariant=True) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + +_root_task: RunVar[asyncio.Task | None] = RunVar("_root_task") + + +def find_root_task() -> asyncio.Task: + root_task = _root_task.get(None) + if root_task is not None and not root_task.done(): + return root_task + + # Look for a task that has been started via run_until_complete() + for task in all_tasks(): + if task._callbacks and not task.done(): + callbacks = [cb for cb, context in task._callbacks] + for cb in callbacks: + if ( + cb is _run_until_complete_cb + or getattr(cb, "__module__", None) == "uvloop.loop" + ): + _root_task.set(task) + return task + + # Look up the topmost task in the AnyIO task tree, if possible + task = cast(asyncio.Task, current_task()) + state = _task_states.get(task) + if state: + cancel_scope = state.cancel_scope + while cancel_scope and cancel_scope._parent_scope is not None: + cancel_scope = cancel_scope._parent_scope + + if cancel_scope is not None: + return cast(asyncio.Task, cancel_scope._host_task) + + return task + + +def get_callable_name(func: Callable) -> str: + module = getattr(func, "__module__", None) + qualname = getattr(func, "__qualname__", None) + return ".".join([x for x in (module, qualname) if x]) + + +# +# Event loop +# + +_run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary() + + +def _task_started(task: asyncio.Task) -> bool: + """Return ``True`` if the task has been started and has not finished.""" + # The task coro should never be None here, as we never add finished tasks to the + # task list + coro = task.get_coro() + assert coro is not None + try: + return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED) + except AttributeError: + # task coro is async_genenerator_asend https://bugs.python.org/issue37771 + raise Exception(f"Cannot determine if task {task} has started or not") from None + + +# +# Timeouts and cancellation +# + + +def is_anyio_cancellation(exc: CancelledError) -> bool: + # Sometimes third party frameworks catch a CancelledError and raise a new one, so as + # a workaround we have to look at the previous ones in __context__ too for a + # matching cancel message + while True: + if ( + exc.args + and isinstance(exc.args[0], str) + and exc.args[0].startswith("Cancelled via cancel scope ") + ): + return True + + if isinstance(exc.__context__, CancelledError): + exc = exc.__context__ + continue + + return False + + +class CancelScope(BaseCancelScope): + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return object.__new__(cls) + + def __init__(self, deadline: float = math.inf, shield: bool = False): + self._deadline = deadline + self._shield = shield + self._parent_scope: CancelScope | None = None + self._child_scopes: set[CancelScope] = set() + self._cancel_called = False + self._cancel_reason: str | None = None + self._cancelled_caught = False + self._active = False + self._timeout_handle: asyncio.TimerHandle | None = None + self._cancel_handle: asyncio.Handle | None = None + self._tasks: set[asyncio.Task] = set() + self._host_task: asyncio.Task | None = None + if sys.version_info >= (3, 11): + self._pending_uncancellations: int | None = 0 + else: + self._pending_uncancellations = None + + def __enter__(self) -> CancelScope: + if self._active: + raise RuntimeError( + "Each CancelScope may only be used for a single 'with' block" + ) + + self._host_task = host_task = cast(asyncio.Task, current_task()) + self._tasks.add(host_task) + try: + task_state = _task_states[host_task] + except KeyError: + task_state = TaskState(None, self) + _task_states[host_task] = task_state + else: + self._parent_scope = task_state.cancel_scope + task_state.cancel_scope = self + if self._parent_scope is not None: + # If using an eager task factory, the parent scope may not even contain + # the host task + self._parent_scope._child_scopes.add(self) + self._parent_scope._tasks.discard(host_task) + + self._timeout() + self._active = True + + # Start cancelling the host task if the scope was cancelled before entering + if self._cancel_called: + self._deliver_cancellation(self) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + del exc_tb + + if not self._active: + raise RuntimeError("This cancel scope is not active") + if current_task() is not self._host_task: + raise RuntimeError( + "Attempted to exit cancel scope in a different task than it was " + "entered in" + ) + + assert self._host_task is not None + host_task_state = _task_states.get(self._host_task) + if host_task_state is None or host_task_state.cancel_scope is not self: + raise RuntimeError( + "Attempted to exit a cancel scope that isn't the current tasks's " + "current cancel scope" + ) + + try: + self._active = False + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._tasks.remove(self._host_task) + if self._parent_scope is not None: + self._parent_scope._child_scopes.remove(self) + self._parent_scope._tasks.add(self._host_task) + + host_task_state.cancel_scope = self._parent_scope + + # Restart the cancellation effort in the closest visible, cancelled parent + # scope if necessary + self._restart_cancellation_in_parent() + + # We only swallow the exception iff it was an AnyIO CancelledError, either + # directly as exc_val or inside an exception group and there are no cancelled + # parent cancel scopes visible to us here + if self._cancel_called and not self._parent_cancellation_is_visible_to_us: + # For each level-cancel() call made on the host task, call uncancel() + while self._pending_uncancellations: + self._host_task.uncancel() + self._pending_uncancellations -= 1 + + # Update cancelled_caught and check for exceptions we must not swallow + cannot_swallow_exc_val = False + if exc_val is not None: + for exc in iterate_exceptions(exc_val): + if isinstance(exc, CancelledError) and is_anyio_cancellation( + exc + ): + self._cancelled_caught = True + else: + cannot_swallow_exc_val = True + + return self._cancelled_caught and not cannot_swallow_exc_val + else: + if self._pending_uncancellations: + assert self._parent_scope is not None + assert self._parent_scope._pending_uncancellations is not None + self._parent_scope._pending_uncancellations += ( + self._pending_uncancellations + ) + self._pending_uncancellations = 0 + + return False + finally: + self._host_task = None + del exc_val + + @property + def _effectively_cancelled(self) -> bool: + cancel_scope: CancelScope | None = self + while cancel_scope is not None: + if cancel_scope._cancel_called: + return True + + if cancel_scope.shield: + return False + + cancel_scope = cancel_scope._parent_scope + + return False + + @property + def _parent_cancellation_is_visible_to_us(self) -> bool: + return ( + self._parent_scope is not None + and not self.shield + and self._parent_scope._effectively_cancelled + ) + + def _timeout(self) -> None: + if self._deadline != math.inf: + loop = get_running_loop() + if loop.time() >= self._deadline: + self.cancel("deadline exceeded") + else: + self._timeout_handle = loop.call_at(self._deadline, self._timeout) + + def _deliver_cancellation(self, origin: CancelScope) -> bool: + """ + Deliver cancellation to directly contained tasks and nested cancel scopes. + + Schedule another run at the end if we still have tasks eligible for + cancellation. + + :param origin: the cancel scope that originated the cancellation + :return: ``True`` if the delivery needs to be retried on the next cycle + + """ + should_retry = False + current = current_task() + for task in self._tasks: + should_retry = True + if task._must_cancel: # type: ignore[attr-defined] + continue + + # The task is eligible for cancellation if it has started + if task is not current and (task is self._host_task or _task_started(task)): + waiter = task._fut_waiter # type: ignore[attr-defined] + if not isinstance(waiter, asyncio.Future) or not waiter.done(): + task.cancel(origin._cancel_reason) + if ( + task is origin._host_task + and origin._pending_uncancellations is not None + ): + origin._pending_uncancellations += 1 + + # Deliver cancellation to child scopes that aren't shielded or running their own + # cancellation callbacks + for scope in self._child_scopes: + if not scope._shield and not scope.cancel_called: + should_retry = scope._deliver_cancellation(origin) or should_retry + + # Schedule another callback if there are still tasks left + if origin is self: + if should_retry: + self._cancel_handle = get_running_loop().call_soon( + self._deliver_cancellation, origin + ) + else: + self._cancel_handle = None + + return should_retry + + def _restart_cancellation_in_parent(self) -> None: + """ + Restart the cancellation effort in the closest directly cancelled parent scope. + + """ + scope = self._parent_scope + while scope is not None: + if scope._cancel_called: + if scope._cancel_handle is None: + scope._deliver_cancellation(scope) + + break + + # No point in looking beyond any shielded scope + if scope._shield: + break + + scope = scope._parent_scope + + def cancel(self, reason: str | None = None) -> None: + if not self._cancel_called: + if self._timeout_handle: + self._timeout_handle.cancel() + self._timeout_handle = None + + self._cancel_called = True + self._cancel_reason = f"Cancelled via cancel scope {id(self):x}" + if task := current_task(): + self._cancel_reason += f" by {task}" + + if reason: + self._cancel_reason += f"; reason: {reason}" + + if self._host_task is not None: + self._deliver_cancellation(self) + + @property + def deadline(self) -> float: + return self._deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self._deadline = float(value) + if self._timeout_handle is not None: + self._timeout_handle.cancel() + self._timeout_handle = None + + if self._active and not self._cancel_called: + self._timeout() + + @property + def cancel_called(self) -> bool: + return self._cancel_called + + @property + def cancelled_caught(self) -> bool: + return self._cancelled_caught + + @property + def shield(self) -> bool: + return self._shield + + @shield.setter + def shield(self, value: bool) -> None: + if self._shield != value: + self._shield = value + if not value: + self._restart_cancellation_in_parent() + + +# +# Task states +# + + +class TaskState: + """ + Encapsulates auxiliary task information that cannot be added to the Task instance + itself because there are no guarantees about its implementation. + """ + + __slots__ = "parent_id", "cancel_scope", "__weakref__" + + def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None): + self.parent_id = parent_id + self.cancel_scope = cancel_scope + + +_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary() + + +# +# Task groups +# + + +class _AsyncioTaskStatus(abc.TaskStatus): + def __init__(self, future: asyncio.Future, parent_id: int): + self._future = future + self._parent_id = parent_id + + def started(self, value: T_contra | None = None) -> None: + try: + self._future.set_result(value) + except asyncio.InvalidStateError: + if not self._future.cancelled(): + raise RuntimeError( + "called 'started' twice on the same task status" + ) from None + + task = cast(asyncio.Task, current_task()) + _task_states[task].parent_id = self._parent_id + + +if sys.version_info >= (3, 12): + _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__ +else: + _eager_task_factory_code = None + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self.cancel_scope: CancelScope = CancelScope() + self._active = False + self._exceptions: list[BaseException] = [] + self._tasks: set[asyncio.Task] = set() + self._on_completed_fut: asyncio.Future[None] | None = None + + async def __aenter__(self) -> TaskGroup: + self.cancel_scope.__enter__() + self._active = True + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + if exc_val is not None: + self.cancel_scope.cancel() + if not isinstance(exc_val, CancelledError): + self._exceptions.append(exc_val) + + loop = get_running_loop() + try: + if self._tasks: + with CancelScope() as wait_scope: + while self._tasks: + self._on_completed_fut = loop.create_future() + + try: + await self._on_completed_fut + except CancelledError as exc: + # Shield the scope against further cancellation attempts, + # as they're not productive (#695) + wait_scope.shield = True + self.cancel_scope.cancel() + + # Set exc_val from the cancellation exception if it was + # previously unset. However, we should not replace a native + # cancellation exception with one raise by a cancel scope. + if exc_val is None or ( + isinstance(exc_val, CancelledError) + and not is_anyio_cancellation(exc) + ): + exc_val = exc + + self._on_completed_fut = None + else: + # If there are no child tasks to wait on, run at least one checkpoint + # anyway + await AsyncIOBackend.cancel_shielded_checkpoint() + + self._active = False + if self._exceptions: + # The exception that got us here should already have been + # added to self._exceptions so it's ok to break exception + # chaining and avoid adding a "During handling of above..." + # for each nesting level. + raise BaseExceptionGroup( + "unhandled errors in a TaskGroup", self._exceptions + ) from None + elif exc_val: + raise exc_val + except BaseException as exc: + if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__): + return True + + raise + + return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb) + finally: + del exc_val, exc_tb, self._exceptions + + def _spawn( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + args: tuple[Unpack[PosArgsT]], + name: object, + task_status_future: asyncio.Future | None = None, + ) -> asyncio.Task: + def task_done(_task: asyncio.Task) -> None: + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_discard_from_awaited_by( + _task, self.cancel_scope._host_task + ) + + task_state = _task_states[_task] + assert task_state.cancel_scope is not None + assert _task in task_state.cancel_scope._tasks + task_state.cancel_scope._tasks.remove(_task) + self._tasks.remove(task) + del _task_states[_task] + + if self._on_completed_fut is not None and not self._tasks: + try: + self._on_completed_fut.set_result(None) + except asyncio.InvalidStateError: + pass + + try: + exc = _task.exception() + except CancelledError as e: + while isinstance(e.__context__, CancelledError): + e = e.__context__ + + exc = e + + if exc is not None: + # The future can only be in the cancelled state if the host task was + # cancelled, so return immediately instead of adding one more + # CancelledError to the exceptions list + if task_status_future is not None and task_status_future.cancelled(): + return + + if task_status_future is None or task_status_future.done(): + if not isinstance(exc, CancelledError): + self._exceptions.append(exc) + + if not self.cancel_scope._effectively_cancelled: + self.cancel_scope.cancel() + else: + task_status_future.set_exception(exc) + elif task_status_future is not None and not task_status_future.done(): + task_status_future.set_exception( + RuntimeError("Child exited without calling task_status.started()") + ) + + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + kwargs = {} + if task_status_future: + parent_id = id(current_task()) + kwargs["task_status"] = _AsyncioTaskStatus( + task_status_future, id(self.cancel_scope._host_task) + ) + else: + parent_id = id(self.cancel_scope._host_task) + + coro = func(*args, **kwargs) + if not iscoroutine(coro): + prefix = f"{func.__module__}." if hasattr(func, "__module__") else "" + raise TypeError( + f"Expected {prefix}{func.__qualname__}() to return a coroutine, but " + f"the return value ({coro!r}) is not a coroutine object" + ) + + name = get_callable_name(func) if name is None else str(name) + loop = asyncio.get_running_loop() + if ( + (factory := loop.get_task_factory()) + and getattr(factory, "__code__", None) is _eager_task_factory_code + and (closure := getattr(factory, "__closure__", None)) + ): + custom_task_constructor = closure[0].cell_contents + task = custom_task_constructor(coro, loop=loop, name=name) + else: + task = create_task(coro, name=name) + + # Make the spawned task inherit the task group's cancel scope + _task_states[task] = TaskState( + parent_id=parent_id, cancel_scope=self.cancel_scope + ) + self.cancel_scope._tasks.add(task) + self._tasks.add(task) + if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: + asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task) + + task.add_done_callback(task_done) + return task + + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + self._spawn(func, args, name) + + async def start( + self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None + ) -> Any: + future: asyncio.Future = asyncio.Future() + task = self._spawn(func, args, name, future) + + # If the task raises an exception after sending a start value without a switch + # point between, the task group is cancelled and this method never proceeds to + # process the completed future. That's why we have to have a shielded cancel + # scope here. + try: + return await future + except CancelledError: + # Cancel the task and wait for it to exit before returning + task.cancel() + with CancelScope(shield=True), suppress(CancelledError): + await task + + raise + + +# +# Threads +# + +_Retval_Queue_Type = tuple[Optional[T_Retval], Optional[BaseException]] + + +class WorkerThread(Thread): + MAX_IDLE_TIME = 10 # seconds + + def __init__( + self, + root_task: asyncio.Task, + workers: set[WorkerThread], + idle_workers: deque[WorkerThread], + ): + super().__init__(name="AnyIO worker thread") + self.root_task = root_task + self.workers = workers + self.idle_workers = idle_workers + self.loop = root_task._loop + self.queue: Queue[ + tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None + ] = Queue(2) + self.idle_since = AsyncIOBackend.current_time() + self.stopping = False + + def _report_result( + self, future: asyncio.Future, result: Any, exc: BaseException | None + ) -> None: + self.idle_since = AsyncIOBackend.current_time() + if not self.stopping: + self.idle_workers.append(self) + + if not future.cancelled(): + if exc is not None: + if isinstance(exc, StopIteration): + new_exc = RuntimeError("coroutine raised StopIteration") + new_exc.__cause__ = exc + exc = new_exc + + future.set_exception(exc) + else: + future.set_result(result) + + def run(self) -> None: + with claim_worker_thread(AsyncIOBackend, self.loop): + while True: + item = self.queue.get() + if item is None: + # Shutdown command received + return + + context, func, args, future, cancel_scope = item + if not future.cancelled(): + result = None + exception: BaseException | None = None + threadlocals.current_cancel_scope = cancel_scope + try: + result = context.run(func, *args) + except BaseException as exc: + exception = exc + finally: + del threadlocals.current_cancel_scope + + if not self.loop.is_closed(): + self.loop.call_soon_threadsafe( + self._report_result, future, result, exception + ) + + del result, exception + + self.queue.task_done() + del item, context, func, args, future, cancel_scope + + def stop(self, f: asyncio.Task | None = None) -> None: + self.stopping = True + self.queue.put_nowait(None) + self.workers.discard(self) + try: + self.idle_workers.remove(self) + except ValueError: + pass + + +_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar( + "_threadpool_idle_workers" +) +_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers") + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class StreamReaderWrapper(abc.ByteReceiveStream): + _stream: asyncio.StreamReader + + async def receive(self, max_bytes: int = 65536) -> bytes: + data = await self._stream.read(max_bytes) + if data: + return data + else: + raise EndOfStream + + async def aclose(self) -> None: + self._stream.set_exception(ClosedResourceError()) + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class StreamWriterWrapper(abc.ByteSendStream): + _stream: asyncio.StreamWriter + _closed: bool = field(init=False, default=False) + + async def send(self, item: bytes) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + stream_paused = self._stream._protocol._paused # type: ignore[attr-defined] + try: + self._stream.write(item) + await self._stream.drain() + except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc: + # If closed by us and/or the peer: + # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError + # * on uvloop and Winloop, write() eventually starts raising RuntimeError + if self._closed: + raise ClosedResourceError from exc + elif self._stream.is_closing(): + raise BrokenResourceError from exc + + raise + + if not stream_paused: + await AsyncIOBackend.cancel_shielded_checkpoint() + + async def aclose(self) -> None: + self._closed = True + self._stream.close() + await AsyncIOBackend.checkpoint() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: asyncio.subprocess.Process + _stdin: StreamWriterWrapper | None + _stdout: StreamReaderWrapper | None + _stderr: StreamReaderWrapper | None + + async def aclose(self) -> None: + with CancelScope(shield=True) as scope: + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + scope.shield = False + try: + await self.wait() + except BaseException: + scope.shield = True + self.kill() + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: int) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +def _forcibly_shutdown_process_pool_on_exit( + workers: set[Process], _task: object +) -> None: + """ + Forcibly shuts down worker processes belonging to this event loop.""" + child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined] + if sys.version_info < (3, 12): + try: + child_watcher = asyncio.get_event_loop_policy().get_child_watcher() + except NotImplementedError: + pass + + # Close as much as possible (w/o async/await) to avoid warnings + for process in workers.copy(): + if process.returncode is None: + continue + + process._stdin._stream._transport.close() # type: ignore[union-attr] + process._stdout._stream._transport.close() # type: ignore[union-attr] + process._stderr._stream._transport.close() # type: ignore[union-attr] + process.kill() + if child_watcher: + child_watcher.remove_child_handler(process.pid) + + +async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None: + """ + Shuts down worker processes belonging to this event loop. + + NOTE: this only works when the event loop was started using asyncio.run() or + anyio.run(). + + """ + process: abc.Process + try: + await sleep(math.inf) + except asyncio.CancelledError: + workers = workers.copy() + for process in workers: + if process.returncode is None: + process.kill() + + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class StreamProtocol(asyncio.Protocol): + read_queue: deque[bytes] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + is_at_eof: bool = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque() + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + cast(asyncio.Transport, transport).set_write_buffer_limits(0) + + def connection_lost(self, exc: Exception | None) -> None: + if exc: + self.exception = BrokenResourceError() + self.exception.__cause__ = exc + + self.read_event.set() + self.write_event.set() + + def data_received(self, data: bytes) -> None: + # ProactorEventloop sometimes sends bytearray instead of bytes + self.read_queue.append(bytes(data)) + self.read_event.set() + + def eof_received(self) -> bool | None: + self.is_at_eof = True + self.read_event.set() + return True + + def pause_writing(self) -> None: + self.write_event = asyncio.Event() + + def resume_writing(self) -> None: + self.write_event.set() + + +class DatagramProtocol(asyncio.DatagramProtocol): + read_queue: deque[tuple[bytes, IPSockAddrType]] + read_event: asyncio.Event + write_event: asyncio.Event + exception: Exception | None = None + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + self.read_queue = deque(maxlen=100) # arbitrary value + self.read_event = asyncio.Event() + self.write_event = asyncio.Event() + self.write_event.set() + + def connection_lost(self, exc: Exception | None) -> None: + self.read_event.set() + self.write_event.set() + + def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None: + addr = convert_ipv6_sockaddr(addr) + self.read_queue.append((data, addr)) + self.read_event.set() + + def error_received(self, exc: Exception) -> None: + self.exception = exc + + def pause_writing(self) -> None: + self.write_event.clear() + + def resume_writing(self) -> None: + self.write_event.set() + + +class SocketStream(abc.SocketStream): + def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def receive(self, max_bytes: int = 65536) -> bytes: + with self._receive_guard: + if ( + not self._protocol.read_event.is_set() + and not self._transport.is_closing() + and not self._protocol.is_at_eof + ): + self._transport.resume_reading() + await self._protocol.read_event.wait() + self._transport.pause_reading() + else: + await AsyncIOBackend.checkpoint() + + try: + chunk = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + elif self._protocol.exception: + raise self._protocol.exception from None + else: + raise EndOfStream from None + + if len(chunk) > max_bytes: + # Split the oversized chunk + chunk, leftover = chunk[:max_bytes], chunk[max_bytes:] + self._protocol.read_queue.appendleft(leftover) + + # If the read queue is empty, clear the flag so that the next call will + # block until data is available + if not self._protocol.read_queue: + self._protocol.read_event.clear() + + return chunk + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + + if self._closed: + raise ClosedResourceError + elif self._protocol.exception is not None: + raise self._protocol.exception + + try: + self._transport.write(item) + except RuntimeError as exc: + if self._transport.is_closing(): + raise BrokenResourceError from exc + else: + raise + + await self._protocol.write_event.wait() + + async def send_eof(self) -> None: + try: + self._transport.write_eof() + except OSError: + pass + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + try: + self._transport.write_eof() + except OSError: + pass + + self._transport.close() + await sleep(0) + self._transport.abort() + + +class _RawSocketMixin: + _receive_future: asyncio.Future | None = None + _send_future: asyncio.Future | None = None + _closing = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._receive_future + loop.remove_reader(self.__raw_socket) + + f = self._receive_future = asyncio.Future() + loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: + def callback(f: object) -> None: + del self._send_future + loop.remove_writer(self.__raw_socket) + + f = self._send_future = asyncio.Future() + loop.add_writer(self.__raw_socket, f.set_result, None) + f.add_done_callback(callback) + return f + + async def aclose(self) -> None: + if not self._closing: + self._closing = True + if self.__raw_socket.fileno() != -1: + self.__raw_socket.close() + + if self._receive_future: + self._receive_future.set_result(None) + if self._send_future: + self._send_future.set_result(None) + + +class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream): + async def send_eof(self) -> None: + with self._send_guard: + self._raw_socket.shutdown(socket.SHUT_WR) + + async def receive(self, max_bytes: int = 65536) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(max_bytes) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = self._raw_socket.send(view) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + view = view[bytes_sent:] + + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + loop = get_running_loop() + fds = array.array("i") + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, flags, addr = self._raw_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + loop = get_running_loop() + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + # The ignore can be removed after mypy picks up + # https://github.com/python/typeshed/pull/5545 + self._raw_socket.sendmsg( + [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)] + ) + break + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + +class TCPSocketListener(abc.SocketListener): + _accept_scope: CancelScope | None = None + _closed = False + + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = cast(asyncio.BaseEventLoop, get_running_loop()) + self._accept_guard = ResourceGuard("accepting connections from") + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + async def accept(self) -> abc.SocketStream: + if self._closed: + raise ClosedResourceError + + with self._accept_guard: + await AsyncIOBackend.checkpoint() + with CancelScope() as self._accept_scope: + try: + client_sock, _addr = await self._loop.sock_accept(self._raw_socket) + except asyncio.CancelledError: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + if self._closed: + raise ClosedResourceError from None + + raise + finally: + self._accept_scope = None + + client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + transport, protocol = await self._loop.connect_accepted_socket( + StreamProtocol, client_sock + ) + return SocketStream(transport, protocol) + + async def aclose(self) -> None: + if self._closed: + return + + self._closed = True + if self._accept_scope: + # Workaround for https://bugs.python.org/issue41317 + try: + self._loop.remove_reader(self._raw_socket) + except (ValueError, NotImplementedError): + pass + + self._accept_scope.cancel() + await sleep(0) + + self._raw_socket.close() + + +class UNIXSocketListener(abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + self.__raw_socket = raw_socket + self._loop = get_running_loop() + self._accept_guard = ResourceGuard("accepting connections from") + self._closed = False + + async def accept(self) -> abc.SocketStream: + await AsyncIOBackend.checkpoint() + with self._accept_guard: + while True: + try: + client_sock, _ = self.__raw_socket.accept() + client_sock.setblocking(False) + return UNIXSocketStream(client_sock) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + self._loop.add_reader(self.__raw_socket, f.set_result, None) + f.add_done_callback( + lambda _: self._loop.remove_reader(self.__raw_socket) + ) + await f + except OSError as exc: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + + async def aclose(self) -> None: + self._closed = True + self.__raw_socket.close() + + @property + def _raw_socket(self) -> socket.socket: + return self.__raw_socket + + +class UDPSocket(abc.UDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + return self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(*item) + + +class ConnectedUDPSocket(abc.ConnectedUDPSocket): + def __init__( + self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol + ): + self._transport = transport + self._protocol = protocol + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + self._closed = False + + @property + def _raw_socket(self) -> socket.socket: + return self._transport.get_extra_info("socket") + + async def aclose(self) -> None: + self._closed = True + if not self._transport.is_closing(): + self._transport.close() + + async def receive(self) -> bytes: + with self._receive_guard: + await AsyncIOBackend.checkpoint() + + # If the buffer is empty, ask for more data + if not self._protocol.read_queue and not self._transport.is_closing(): + self._protocol.read_event.clear() + await self._protocol.read_event.wait() + + try: + packet = self._protocol.read_queue.popleft() + except IndexError: + if self._closed: + raise ClosedResourceError from None + else: + raise BrokenResourceError from None + + return packet[0] + + async def send(self, item: bytes) -> None: + with self._send_guard: + await AsyncIOBackend.checkpoint() + await self._protocol.write_event.wait() + if self._closed: + raise ClosedResourceError + elif self._transport.is_closing(): + raise BrokenResourceError + else: + self._transport.sendto(item) + + +class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket): + async def receive(self) -> UNIXDatagramPacketType: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recvfrom(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: UNIXDatagramPacketType) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.sendto(*item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket): + async def receive(self) -> bytes: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._receive_guard: + while True: + try: + data = self._raw_socket.recv(65536) + except BlockingIOError: + await self._wait_until_readable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return data + + async def send(self, item: bytes) -> None: + loop = get_running_loop() + await AsyncIOBackend.checkpoint() + with self._send_guard: + while True: + try: + self._raw_socket.send(item) + except BlockingIOError: + await self._wait_until_writable(loop) + except OSError as exc: + if self._closing: + raise ClosedResourceError from None + else: + raise BrokenResourceError from exc + else: + return + + +_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events") +_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events") + + +# +# Synchronization +# + + +class Event(BaseEvent): + def __new__(cls) -> Event: + return object.__new__(cls) + + def __init__(self) -> None: + self._event = asyncio.Event() + + def set(self) -> None: + self._event.set() + + def is_set(self) -> bool: + return self._event.is_set() + + async def wait(self) -> None: + if self.is_set(): + await AsyncIOBackend.checkpoint() + else: + await self._event.wait() + + def statistics(self) -> EventStatistics: + return EventStatistics(len(self._event._waiters)) + + +class Lock(BaseLock): + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self._owner_task: asyncio.Task | None = None + self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque() + + async def acquire(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._owner_task = task + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + if self._owner_task == task: + raise RuntimeError("Attempted to acquire an already held Lock") + + fut: asyncio.Future[None] = asyncio.Future() + item = task, fut + self._waiters.append(item) + try: + await fut + except CancelledError: + self._waiters.remove(item) + if self._owner_task is task: + self.release() + + raise + + self._waiters.remove(item) + + def acquire_nowait(self) -> None: + task = cast(asyncio.Task, current_task()) + if self._owner_task is None and not self._waiters: + self._owner_task = task + return + + if self._owner_task is task: + raise RuntimeError("Attempted to acquire an already held Lock") + + raise WouldBlock + + def locked(self) -> bool: + return self._owner_task is not None + + def release(self) -> None: + if self._owner_task != current_task(): + raise RuntimeError("The current task is not holding this lock") + + for task, fut in self._waiters: + if not fut.cancelled(): + self._owner_task = task + fut.set_result(None) + return + + self._owner_task = None + + def statistics(self) -> LockStatistics: + task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None + return LockStatistics(self.locked(), task_info, len(self._waiters)) + + +class Semaphore(BaseSemaphore): + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + super().__init__(initial_value, max_value=max_value) + self._value = initial_value + self._max_value = max_value + self._fast_acquire = fast_acquire + self._waiters: deque[asyncio.Future[None]] = deque() + + async def acquire(self) -> None: + if self._value > 0 and not self._waiters: + await AsyncIOBackend.checkpoint_if_cancelled() + self._value -= 1 + + # Unless on the "fast path", yield control of the event loop so that other + # tasks can run too + if not self._fast_acquire: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except CancelledError: + self.release() + raise + + return + + fut: asyncio.Future[None] = asyncio.Future() + self._waiters.append(fut) + try: + await fut + except CancelledError: + try: + self._waiters.remove(fut) + except ValueError: + self.release() + + raise + + def acquire_nowait(self) -> None: + if self._value == 0: + raise WouldBlock + + self._value -= 1 + + def release(self) -> None: + if self._max_value is not None and self._value == self._max_value: + raise ValueError("semaphore released too many times") + + for fut in self._waiters: + if not fut.cancelled(): + fut.set_result(None) + self._waiters.remove(fut) + return + + self._value += 1 + + @property + def value(self) -> int: + return self._value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + return SemaphoreStatistics(len(self._waiters)) + + +class CapacityLimiter(BaseCapacityLimiter): + _total_tokens: float = 0 + + def __new__(cls, total_tokens: float) -> CapacityLimiter: + return object.__new__(cls) + + def __init__(self, total_tokens: float): + self._borrowers: set[Any] = set() + self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict() + self.total_tokens = total_tokens + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + @property + def total_tokens(self) -> float: + return self._total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and not math.isinf(value): + raise TypeError("total_tokens must be an int or math.inf") + + if value < 0: + raise ValueError("total_tokens must be >= 0") + + waiters_to_notify = max(value - self._total_tokens, 0) + self._total_tokens = value + + # Notify waiting tasks that they have acquired the limiter + while self._wait_queue and waiters_to_notify: + event = self._wait_queue.popitem(last=False)[1] + event.set() + waiters_to_notify -= 1 + + @property + def borrowed_tokens(self) -> int: + return len(self._borrowers) + + @property + def available_tokens(self) -> float: + return self._total_tokens - len(self._borrowers) + + def _notify_next_waiter(self) -> None: + """Notify the next task in line if this limiter has free capacity now.""" + if self._wait_queue and len(self._borrowers) < self._total_tokens: + event = self._wait_queue.popitem(last=False)[1] + event.set() + + def acquire_nowait(self) -> None: + self.acquire_on_behalf_of_nowait(current_task()) + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + if borrower in self._borrowers: + raise RuntimeError( + "this borrower is already holding one of this CapacityLimiter's tokens" + ) + + if self._wait_queue or len(self._borrowers) >= self._total_tokens: + raise WouldBlock + + self._borrowers.add(borrower) + + async def acquire(self) -> None: + return await self.acquire_on_behalf_of(current_task()) + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await AsyncIOBackend.checkpoint_if_cancelled() + try: + self.acquire_on_behalf_of_nowait(borrower) + except WouldBlock: + event = asyncio.Event() + self._wait_queue[borrower] = event + try: + await event.wait() + except BaseException: + self._wait_queue.pop(borrower, None) + if event.is_set(): + self._notify_next_waiter() + + raise + + self._borrowers.add(borrower) + else: + try: + await AsyncIOBackend.cancel_shielded_checkpoint() + except BaseException: + self.release() + raise + + def release(self) -> None: + self.release_on_behalf_of(current_task()) + + def release_on_behalf_of(self, borrower: object) -> None: + try: + self._borrowers.remove(borrower) + except KeyError: + raise RuntimeError( + "this borrower isn't holding any of this CapacityLimiter's tokens" + ) from None + + self._notify_next_waiter() + + def statistics(self) -> CapacityLimiterStatistics: + return CapacityLimiterStatistics( + self.borrowed_tokens, + self.total_tokens, + tuple(self._borrowers), + len(self._wait_queue), + ) + + +_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter") + + +# +# Operating system signals +# + + +class _SignalReceiver: + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + self._loop = get_running_loop() + self._signal_queue: deque[Signals] = deque() + self._future: asyncio.Future = asyncio.Future() + self._handled_signals: set[Signals] = set() + + def _deliver(self, signum: Signals) -> None: + self._signal_queue.append(signum) + if not self._future.done(): + self._future.set_result(None) + + def __enter__(self) -> _SignalReceiver: + for sig in set(self._signals): + self._loop.add_signal_handler(sig, self._deliver, sig) + self._handled_signals.add(sig) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + for sig in self._handled_signals: + self._loop.remove_signal_handler(sig) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + await AsyncIOBackend.checkpoint() + if not self._signal_queue: + self._future = asyncio.Future() + await self._future + + return self._signal_queue.popleft() + + +# +# Testing and debugging +# + + +class AsyncIOTaskInfo(TaskInfo): + def __init__(self, task: asyncio.Task): + task_state = _task_states.get(task) + if task_state is None: + parent_id = None + else: + parent_id = task_state.parent_id + + coro = task.get_coro() + assert coro is not None, "created TaskInfo from a completed Task" + super().__init__(id(task), parent_id, task.get_name(), coro) + self._task = weakref.ref(task) + + def has_pending_cancellation(self) -> bool: + if not (task := self._task()): + # If the task isn't around anymore, it won't have a pending cancellation + return False + + if task._must_cancel: # type: ignore[attr-defined] + return True + elif ( + isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined] + and task._fut_waiter.cancelled() # type: ignore[attr-defined] + ): + return True + + if task_state := _task_states.get(task): + if cancel_scope := task_state.cancel_scope: + return cancel_scope._effectively_cancelled + + return False + + +class TestRunner(abc.TestRunner): + _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]] + + def __init__( + self, + *, + debug: bool | None = None, + use_uvloop: bool = False, + loop_factory: Callable[[], AbstractEventLoop] | None = None, + ) -> None: + if use_uvloop and loop_factory is None: + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + self._runner = Runner(debug=debug, loop_factory=loop_factory) + self._exceptions: list[BaseException] = [] + self._runner_task: asyncio.Task | None = None + + def __enter__(self) -> TestRunner: + self._runner.__enter__() + self.get_loop().set_exception_handler(self._exception_handler) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._runner.__exit__(exc_type, exc_val, exc_tb) + + def get_loop(self) -> AbstractEventLoop: + return self._runner.get_loop() + + def _exception_handler( + self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] + ) -> None: + if isinstance(context.get("exception"), Exception): + self._exceptions.append(context["exception"]) + else: + loop.default_exception_handler(context) + + def _raise_async_exceptions(self) -> None: + # Re-raise any exceptions raised in asynchronous callbacks + if self._exceptions: + exceptions, self._exceptions = self._exceptions, [] + if len(exceptions) == 1: + raise exceptions[0] + elif exceptions: + raise BaseExceptionGroup( + "Multiple exceptions occurred in asynchronous callbacks", exceptions + ) + + async def _run_tests_and_fixtures( + self, + receive_stream: MemoryObjectReceiveStream[ + tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]] + ], + ) -> None: + from _pytest.outcomes import OutcomeException + + with receive_stream, self._send_stream: + async for coro, future in receive_stream: + try: + retval = await coro + except CancelledError as exc: + if not future.cancelled(): + future.cancel(*exc.args) + + raise + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + if not isinstance(exc, (Exception, OutcomeException)): + raise + else: + if not future.cancelled(): + future.set_result(retval) + + async def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if not self._runner_task: + self._send_stream, receive_stream = create_memory_object_stream[ + tuple[Awaitable[Any], asyncio.Future] + ](1) + self._runner_task = self.get_loop().create_task( + self._run_tests_and_fixtures(receive_stream) + ) + + coro = func(*args, **kwargs) + future: asyncio.Future[T_Retval] = self.get_loop().create_future() + self._send_stream.send_nowait((coro, future)) + return await future + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + self._raise_async_exceptions() + + yield fixturevalue + + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(asyncgen.asend, None) + ) + except StopAsyncIteration: + self._raise_async_exceptions() + else: + self.get_loop().run_until_complete(asyncgen.aclose()) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + retval = self.get_loop().run_until_complete( + self._call_in_runner_task(fixture_func, **kwargs) + ) + self._raise_async_exceptions() + return retval + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + try: + self.get_loop().run_until_complete( + self._call_in_runner_task(test_func, **kwargs) + ) + except Exception as exc: + self._exceptions.append(exc) + + self._raise_async_exceptions() + + +class AsyncIOBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + @wraps(func) + async def wrapper() -> T_Retval: + task = cast(asyncio.Task, current_task()) + task.set_name(get_callable_name(func)) + _task_states[task] = TaskState(None, None) + + try: + return await func(*args) + finally: + del _task_states[task] + + debug = options.get("debug", None) + loop_factory = options.get("loop_factory", None) + if loop_factory is None and options.get("use_uvloop", False): + if sys.platform != "win32": + import uvloop + + loop_factory = uvloop.new_event_loop + else: + import winloop + + loop_factory = winloop.new_event_loop + + with Runner(debug=debug, loop_factory=loop_factory) as runner: + return runner.run(wrapper()) + + @classmethod + def current_token(cls) -> object: + return get_running_loop() + + @classmethod + def current_time(cls) -> float: + return get_running_loop().time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return CancelledError + + @classmethod + async def checkpoint(cls) -> None: + await sleep(0) + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + task = current_task() + if task is None: + return + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return + + while cancel_scope: + if cancel_scope.cancel_called: + await sleep(0) + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + with CancelScope(shield=True): + await sleep(0) + + @classmethod + async def sleep(cls, delay: float) -> None: + await sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + if (task := current_task()) is None: + return math.inf + + try: + cancel_scope = _task_states[task].cancel_scope + except KeyError: + return math.inf + + deadline = math.inf + while cancel_scope: + deadline = min(deadline, cancel_scope.deadline) + if cancel_scope._cancel_called: + deadline = -math.inf + break + elif cancel_scope.shield: + break + else: + cancel_scope = cancel_scope._parent_scope + + return deadline + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> abc.Event: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> abc.Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> abc.Semaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> abc.CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( # type: ignore[return] + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: abc.CapacityLimiter | None = None, + ) -> T_Retval: + await cls.checkpoint() + + # If this is the first run in this event loop thread, set up the necessary + # variables + try: + idle_workers = _threadpool_idle_workers.get() + workers = _threadpool_workers.get() + except LookupError: + idle_workers = deque() + workers = set() + _threadpool_idle_workers.set(idle_workers) + _threadpool_workers.set(workers) + + async with limiter or cls.current_default_thread_limiter(): + with CancelScope(shield=not abandon_on_cancel) as scope: + future = asyncio.Future[T_Retval]() + root_task = find_root_task() + if not idle_workers: + worker = WorkerThread(root_task, workers, idle_workers) + worker.start() + workers.add(worker) + root_task.add_done_callback( + worker.stop, context=contextvars.Context() + ) + else: + worker = idle_workers.pop() + + # Prune any other workers that have been idle for MAX_IDLE_TIME + # seconds or longer + now = cls.current_time() + while idle_workers: + if ( + now - idle_workers[0].idle_since + < WorkerThread.MAX_IDLE_TIME + ): + break + + expired_worker = idle_workers.popleft() + expired_worker.root_task.remove_done_callback( + expired_worker.stop + ) + expired_worker.stop() + + context = copy_context() + context.run(set_current_async_library, None) + if abandon_on_cancel or scope._parent_scope is None: + worker_scope = scope + else: + worker_scope = scope._parent_scope + + worker.queue.put_nowait((context, func, args, future, worker_scope)) + return await future + + @classmethod + def check_cancelled(cls) -> None: + scope: CancelScope | None = threadlocals.current_cancel_scope + while scope is not None: + if scope.cancel_called: + raise CancelledError(f"Cancelled by cancel scope {id(scope):x}") + + if scope.shield: + return + + scope = scope._parent_scope + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + async def task_wrapper() -> T_Retval: + __tracebackhide__ = True + if scope is not None: + task = cast(asyncio.Task, current_task()) + _task_states[task] = TaskState(None, scope) + scope._tasks.add(task) + try: + return await func(*args) + except CancelledError as exc: + raise concurrent.futures.CancelledError(str(exc)) from None + finally: + if scope is not None: + scope._tasks.discard(task) + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + context = copy_context() + context.run(set_current_async_library, "asyncio") + scope = getattr(threadlocals, "current_cancel_scope", None) + f: concurrent.futures.Future[T_Retval] = context.run( + asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop + ) + return f.result() + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + @wraps(func) + def wrapper() -> None: + try: + set_current_async_library("asyncio") + f.set_result(func(*args)) + except BaseException as exc: + f.set_exception(exc) + if not isinstance(exc, Exception): + raise + + loop = cast( + "AbstractEventLoop", token or threadlocals.current_token.native_token + ) + if loop.is_closed(): + raise RunFinishedError + + f: concurrent.futures.Future[T_Retval] = Future() + loop.call_soon_threadsafe(wrapper) + return f.result() + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + await cls.checkpoint() + if isinstance(command, PathLike): + command = os.fspath(command) + + if isinstance(command, (str, bytes)): + process = await asyncio.create_subprocess_shell( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + else: + process = await asyncio.create_subprocess_exec( + *command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + **kwargs, + ) + + stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None + stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None + stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + create_task( + _shutdown_process_pool_on_exit(workers), + name="AnyIO process pool shutdown task", + ) + find_root_task().add_done_callback( + partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type] + ) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> abc.SocketStream: + transport, protocol = cast( + tuple[asyncio.Transport, StreamProtocol], + await get_running_loop().create_connection( + StreamProtocol, host, port, local_addr=local_address + ), + ) + transport.pause_reading() + return SocketStream(transport, protocol) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + await cls.checkpoint() + loop = get_running_loop() + raw_socket = socket.socket(socket.AF_UNIX) + raw_socket.setblocking(False) + while True: + try: + raw_socket.connect(path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return UNIXSocketStream(raw_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, + local_addr=local_address, + remote_addr=remote_address, + family=family, + reuse_port=reuse_port, + ) + if protocol.exception: + transport.close() + raise protocol.exception + + if not remote_address: + return UDPSocket(transport, protocol) + else: + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def create_unix_datagram_socket( # type: ignore[override] + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + await cls.checkpoint() + loop = get_running_loop() + + if remote_path: + while True: + try: + raw_socket.connect(remote_path) + except BlockingIOError: + f: asyncio.Future = asyncio.Future() + loop.add_writer(raw_socket, f.set_result, None) + f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) + await f + except BaseException: + raw_socket.close() + raise + else: + return ConnectedUNIXDatagramSocket(raw_socket) + else: + return UNIXDatagramSocket(raw_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await get_running_loop().getaddrinfo( + host, port, family=family, type=type, proto=proto, flags=flags + ) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await get_running_loop().getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + read_events = _read_events.get() + except LookupError: + read_events = {} + _read_events.set(read_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if read_events.get(fd): + raise BusyResourceError("reading from") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_reader(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_reader(fd, cb) + remove_reader = selector.remove_reader + else: + remove_reader = loop.remove_reader + + read_events[fd] = fut + try: + success = await fut + finally: + try: + del read_events[fd] + except KeyError: + pass + else: + remove_reader(fd) + + if not success: + raise ClosedResourceError + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + write_events = _write_events.get() + except LookupError: + write_events = {} + _write_events.set(write_events) + + fd = obj if isinstance(obj, int) else obj.fileno() + if write_events.get(fd): + raise BusyResourceError("writing to") + + loop = get_running_loop() + fut: asyncio.Future[bool] = loop.create_future() + + def cb() -> None: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + try: + fut.set_result(True) + except asyncio.InvalidStateError: + pass + + try: + loop.add_writer(fd, cb) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + selector = get_selector() + selector.add_writer(fd, cb) + remove_writer = selector.remove_writer + else: + remove_writer = loop.remove_writer + + write_events[fd] = fut + try: + success = await fut + finally: + try: + del write_events[fd] + except KeyError: + pass + else: + remove_writer(fd) + + if not success: + raise ClosedResourceError + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + fd = obj if isinstance(obj, int) else obj.fileno() + loop = get_running_loop() + + try: + write_events = _write_events.get() + except LookupError: + pass + else: + try: + fut = write_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_writer(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_writer(fd) + + try: + read_events = _read_events.get() + except LookupError: + pass + else: + try: + fut = read_events.pop(fd) + except KeyError: + pass + else: + try: + fut.set_result(False) + except asyncio.InvalidStateError: + pass + + try: + loop.remove_reader(fd) + except NotImplementedError: + from anyio._core._asyncio_selector_thread import get_selector + + get_selector().remove_reader(fd) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener: + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + transport, protocol = await get_running_loop().create_connection( + StreamProtocol, sock=sock + ) + return SocketStream(transport, protocol) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + return UNIXSocketStream(sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return UDPSocket(transport, protocol) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + transport, protocol = await get_running_loop().create_datagram_endpoint( + DatagramProtocol, sock=sock + ) + return ConnectedUDPSocket(transport, protocol) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + return UNIXDatagramSocket(sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + return ConnectedUNIXDatagramSocket(sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _default_thread_limiter.get() + except LookupError: + limiter = CapacityLimiter(40) + _default_thread_limiter.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type] + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()] + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + await cls.checkpoint() + this_task = current_task() + while True: + for task in all_tasks(): + if task is this_task: + continue + + waiter = task._fut_waiter # type: ignore[attr-defined] + if waiter is None or waiter.done(): + await sleep(0.1) + break + else: + return + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = AsyncIOBackend diff --git a/venv/lib/python3.12/site-packages/anyio/_backends/_trio.py b/venv/lib/python3.12/site-packages/anyio/_backends/_trio.py new file mode 100644 index 0000000..f460a7f --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_backends/_trio.py @@ -0,0 +1,1346 @@ +from __future__ import annotations + +import array +import math +import os +import socket +import sys +import types +import weakref +from collections.abc import ( + AsyncGenerator, + AsyncIterator, + Awaitable, + Callable, + Collection, + Coroutine, + Iterable, + Sequence, +) +from contextlib import AbstractContextManager +from dataclasses import dataclass +from io import IOBase +from os import PathLike +from signal import Signals +from socket import AddressFamily, SocketKind +from types import TracebackType +from typing import ( + IO, + TYPE_CHECKING, + Any, + Generic, + NoReturn, + TypeVar, + cast, + overload, +) + +import trio.from_thread +import trio.lowlevel +from outcome import Error, Outcome, Value +from trio.lowlevel import ( + current_root_task, + current_task, + notify_closing, + wait_readable, + wait_writable, +) +from trio.socket import SocketType as TrioSocketType +from trio.to_thread import run_sync + +from .. import ( + CapacityLimiterStatistics, + EventStatistics, + LockStatistics, + RunFinishedError, + TaskInfo, + WouldBlock, + abc, +) +from .._core._eventloop import claim_worker_thread +from .._core._exceptions import ( + BrokenResourceError, + BusyResourceError, + ClosedResourceError, + EndOfStream, +) +from .._core._sockets import convert_ipv6_sockaddr +from .._core._streams import create_memory_object_stream +from .._core._synchronization import ( + CapacityLimiter as BaseCapacityLimiter, +) +from .._core._synchronization import Event as BaseEvent +from .._core._synchronization import Lock as BaseLock +from .._core._synchronization import ( + ResourceGuard, + SemaphoreStatistics, +) +from .._core._synchronization import Semaphore as BaseSemaphore +from .._core._tasks import CancelScope as BaseCancelScope +from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType +from ..abc._eventloop import AsyncBackend, StrOrBytesPath +from ..streams.memory import MemoryObjectSendStream + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from exceptiongroup import BaseExceptionGroup + from typing_extensions import TypeVarTuple, Unpack + +T = TypeVar("T") +T_Retval = TypeVar("T_Retval") +T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType) +PosArgsT = TypeVarTuple("PosArgsT") +P = ParamSpec("P") + + +# +# Event loop +# + +RunVar = trio.lowlevel.RunVar + + +# +# Timeouts and cancellation +# + + +class CancelScope(BaseCancelScope): + def __new__( + cls, original: trio.CancelScope | None = None, **kwargs: object + ) -> CancelScope: + return object.__new__(cls) + + def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None: + self.__original = original or trio.CancelScope(**kwargs) + + def __enter__(self) -> CancelScope: + self.__original.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + return self.__original.__exit__(exc_type, exc_val, exc_tb) + + def cancel(self, reason: str | None = None) -> None: + self.__original.cancel(reason) + + @property + def deadline(self) -> float: + return self.__original.deadline + + @deadline.setter + def deadline(self, value: float) -> None: + self.__original.deadline = value + + @property + def cancel_called(self) -> bool: + return self.__original.cancel_called + + @property + def cancelled_caught(self) -> bool: + return self.__original.cancelled_caught + + @property + def shield(self) -> bool: + return self.__original.shield + + @shield.setter + def shield(self, value: bool) -> None: + self.__original.shield = value + + +# +# Task groups +# + + +class TaskGroup(abc.TaskGroup): + def __init__(self) -> None: + self._active = False + self._nursery_manager = trio.open_nursery(strict_exception_groups=True) + self.cancel_scope = None # type: ignore[assignment] + + async def __aenter__(self) -> TaskGroup: + self._active = True + self._nursery = await self._nursery_manager.__aenter__() + self.cancel_scope = CancelScope(self._nursery.cancel_scope) + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + try: + # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type + return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value] + except BaseExceptionGroup as exc: + if not exc.split(trio.Cancelled)[1]: + raise trio.Cancelled._create() from exc + + raise + finally: + del exc_val, exc_tb + self._active = False + + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + self._nursery.start_soon(func, *args, name=name) + + async def start( + self, func: Callable[..., Awaitable[Any]], *args: object, name: object = None + ) -> Any: + if not self._active: + raise RuntimeError( + "This task group is not active; no new tasks can be started." + ) + + return await self._nursery.start(func, *args, name=name) + + +# +# Subprocesses +# + + +@dataclass(eq=False) +class ReceiveStreamWrapper(abc.ByteReceiveStream): + _stream: trio.abc.ReceiveStream + + async def receive(self, max_bytes: int | None = None) -> bytes: + try: + data = await self._stream.receive_some(max_bytes) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + if data: + return bytes(data) + else: + raise EndOfStream + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class SendStreamWrapper(abc.ByteSendStream): + _stream: trio.abc.SendStream + + async def send(self, item: bytes) -> None: + try: + await self._stream.send_all(item) + except trio.ClosedResourceError as exc: + raise ClosedResourceError from exc.__cause__ + except trio.BrokenResourceError as exc: + raise BrokenResourceError from exc.__cause__ + + async def aclose(self) -> None: + await self._stream.aclose() + + +@dataclass(eq=False) +class Process(abc.Process): + _process: trio.Process + _stdin: abc.ByteSendStream | None + _stdout: abc.ByteReceiveStream | None + _stderr: abc.ByteReceiveStream | None + + async def aclose(self) -> None: + with CancelScope(shield=True): + if self._stdin: + await self._stdin.aclose() + if self._stdout: + await self._stdout.aclose() + if self._stderr: + await self._stderr.aclose() + + try: + await self.wait() + except BaseException: + self.kill() + with CancelScope(shield=True): + await self.wait() + raise + + async def wait(self) -> int: + return await self._process.wait() + + def terminate(self) -> None: + self._process.terminate() + + def kill(self) -> None: + self._process.kill() + + def send_signal(self, signal: Signals) -> None: + self._process.send_signal(signal) + + @property + def pid(self) -> int: + return self._process.pid + + @property + def returncode(self) -> int | None: + return self._process.returncode + + @property + def stdin(self) -> abc.ByteSendStream | None: + return self._stdin + + @property + def stdout(self) -> abc.ByteReceiveStream | None: + return self._stdout + + @property + def stderr(self) -> abc.ByteReceiveStream | None: + return self._stderr + + +class _ProcessPoolShutdownInstrument(trio.abc.Instrument): + def after_run(self) -> None: + super().after_run() + + +current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar( + "current_default_worker_process_limiter" +) + + +async def _shutdown_process_pool(workers: set[abc.Process]) -> None: + try: + await trio.sleep(math.inf) + except trio.Cancelled: + for process in workers: + if process.returncode is None: + process.kill() + + with CancelScope(shield=True): + for process in workers: + await process.aclose() + + +# +# Sockets and networking +# + + +class _TrioSocketMixin(Generic[T_SockAddr]): + def __init__(self, trio_socket: TrioSocketType) -> None: + self._trio_socket = trio_socket + self._closed = False + + def _check_closed(self) -> None: + if self._closed: + raise ClosedResourceError + if self._trio_socket.fileno() < 0: + raise BrokenResourceError + + @property + def _raw_socket(self) -> socket.socket: + return self._trio_socket._sock # type: ignore[attr-defined] + + async def aclose(self) -> None: + if self._trio_socket.fileno() >= 0: + self._closed = True + self._trio_socket.close() + + def _convert_socket_error(self, exc: BaseException) -> NoReturn: + if isinstance(exc, trio.ClosedResourceError): + raise ClosedResourceError from exc + elif self._trio_socket.fileno() < 0 and self._closed: + raise ClosedResourceError from None + elif isinstance(exc, OSError): + raise BrokenResourceError from exc + else: + raise exc + + +class SocketStream(_TrioSocketMixin, abc.SocketStream): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self, max_bytes: int = 65536) -> bytes: + with self._receive_guard: + try: + data = await self._trio_socket.recv(max_bytes) + except BaseException as exc: + self._convert_socket_error(exc) + + if data: + return data + else: + raise EndOfStream + + async def send(self, item: bytes) -> None: + with self._send_guard: + view = memoryview(item) + while view: + try: + bytes_sent = await self._trio_socket.send(view) + except BaseException as exc: + self._convert_socket_error(exc) + + view = view[bytes_sent:] + + async def send_eof(self) -> None: + self._trio_socket.shutdown(socket.SHUT_WR) + + +class UNIXSocketStream(SocketStream, abc.UNIXSocketStream): + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + if not isinstance(msglen, int) or msglen < 0: + raise ValueError("msglen must be a non-negative integer") + if not isinstance(maxfds, int) or maxfds < 1: + raise ValueError("maxfds must be a positive integer") + + fds = array.array("i") + await trio.lowlevel.checkpoint() + with self._receive_guard: + while True: + try: + message, ancdata, flags, addr = await self._trio_socket.recvmsg( + msglen, socket.CMSG_LEN(maxfds * fds.itemsize) + ) + except BaseException as exc: + self._convert_socket_error(exc) + else: + if not message and not ancdata: + raise EndOfStream + + break + + for cmsg_level, cmsg_type, cmsg_data in ancdata: + if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: + raise RuntimeError( + f"Received unexpected ancillary data; message = {message!r}, " + f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" + ) + + fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) + + return message, list(fds) + + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + if not message: + raise ValueError("message must not be empty") + if not fds: + raise ValueError("fds must not be empty") + + filenos: list[int] = [] + for fd in fds: + if isinstance(fd, int): + filenos.append(fd) + elif isinstance(fd, IOBase): + filenos.append(fd.fileno()) + + fdarray = array.array("i", filenos) + await trio.lowlevel.checkpoint() + with self._send_guard: + while True: + try: + await self._trio_socket.sendmsg( + [message], + [ + ( + socket.SOL_SOCKET, + socket.SCM_RIGHTS, + fdarray, + ) + ], + ) + break + except BaseException as exc: + self._convert_socket_error(exc) + + +class TCPSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> SocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + return SocketStream(trio_socket) + + +class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener): + def __init__(self, raw_socket: socket.socket): + super().__init__(trio.socket.from_stdlib_socket(raw_socket)) + self._accept_guard = ResourceGuard("accepting connections from") + + async def accept(self) -> UNIXSocketStream: + with self._accept_guard: + try: + trio_socket, _addr = await self._trio_socket.accept() + except BaseException as exc: + self._convert_socket_error(exc) + + return UNIXSocketStream(trio_socket) + + +class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> tuple[bytes, IPSockAddrType]: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, convert_ipv6_sockaddr(addr) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UDPPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> UNIXDatagramPacketType: + with self._receive_guard: + try: + data, addr = await self._trio_socket.recvfrom(65536) + return data, addr + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: UNIXDatagramPacketType) -> None: + with self._send_guard: + try: + await self._trio_socket.sendto(*item) + except BaseException as exc: + self._convert_socket_error(exc) + + +class ConnectedUNIXDatagramSocket( + _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket +): + def __init__(self, trio_socket: TrioSocketType) -> None: + super().__init__(trio_socket) + self._receive_guard = ResourceGuard("reading from") + self._send_guard = ResourceGuard("writing to") + + async def receive(self) -> bytes: + with self._receive_guard: + try: + return await self._trio_socket.recv(65536) + except BaseException as exc: + self._convert_socket_error(exc) + + async def send(self, item: bytes) -> None: + with self._send_guard: + try: + await self._trio_socket.send(item) + except BaseException as exc: + self._convert_socket_error(exc) + + +# +# Synchronization +# + + +class Event(BaseEvent): + def __new__(cls) -> Event: + return object.__new__(cls) + + def __init__(self) -> None: + self.__original = trio.Event() + + def is_set(self) -> bool: + return self.__original.is_set() + + async def wait(self) -> None: + return await self.__original.wait() + + def statistics(self) -> EventStatistics: + orig_statistics = self.__original.statistics() + return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting) + + def set(self) -> None: + self.__original.set() + + +class Lock(BaseLock): + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False) -> None: + self._fast_acquire = fast_acquire + self.__original = trio.Lock() + + @staticmethod + def _convert_runtime_error_msg(exc: RuntimeError) -> None: + if exc.args == ("attempt to re-acquire an already held Lock",): + exc.args = ("Attempted to acquire an already held Lock",) + + async def acquire(self) -> None: + if not self._fast_acquire: + try: + await self.__original.acquire() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + except RuntimeError as exc: + self._convert_runtime_error_msg(exc) + raise + + def locked(self) -> bool: + return self.__original.locked() + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> LockStatistics: + orig_statistics = self.__original.statistics() + owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None + return LockStatistics( + orig_statistics.locked, owner, orig_statistics.tasks_waiting + ) + + +class Semaphore(BaseSemaphore): + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self.__original = trio.Semaphore(initial_value, max_value=max_value) + + async def acquire(self) -> None: + if not self._fast_acquire: + await self.__original.acquire() + return + + # This is the "fast path" where we don't let other tasks run + await trio.lowlevel.checkpoint_if_cancelled() + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + await self.__original._lot.park() + + def acquire_nowait(self) -> None: + try: + self.__original.acquire_nowait() + except trio.WouldBlock: + raise WouldBlock from None + + @property + def max_value(self) -> int | None: + return self.__original.max_value + + @property + def value(self) -> int: + return self.__original.value + + def release(self) -> None: + self.__original.release() + + def statistics(self) -> SemaphoreStatistics: + orig_statistics = self.__original.statistics() + return SemaphoreStatistics(orig_statistics.tasks_waiting) + + +class CapacityLimiter(BaseCapacityLimiter): + def __new__( + cls, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> CapacityLimiter: + return object.__new__(cls) + + def __init__( + self, + total_tokens: float | None = None, + *, + original: trio.CapacityLimiter | None = None, + ) -> None: + if original is not None: + self.__original = original + else: + assert total_tokens is not None + self.__original = trio.CapacityLimiter(total_tokens) + + async def __aenter__(self) -> None: + return await self.__original.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.__original.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + return self.__original.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + self.__original.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + return self.__original.borrowed_tokens + + @property + def available_tokens(self) -> float: + return self.__original.available_tokens + + def acquire_nowait(self) -> None: + self.__original.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self.__original.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self.__original.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self.__original.acquire_on_behalf_of(borrower) + + def release(self) -> None: + return self.__original.release() + + def release_on_behalf_of(self, borrower: object) -> None: + return self.__original.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + orig = self.__original.statistics() + return CapacityLimiterStatistics( + borrowed_tokens=orig.borrowed_tokens, + total_tokens=orig.total_tokens, + borrowers=tuple(orig.borrowers), + tasks_waiting=orig.tasks_waiting, + ) + + +_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper") + + +# +# Signal handling +# + + +class _SignalReceiver: + _iterator: AsyncIterator[int] + + def __init__(self, signals: tuple[Signals, ...]): + self._signals = signals + + def __enter__(self) -> _SignalReceiver: + self._cm = trio.open_signal_receiver(*self._signals) + self._iterator = self._cm.__enter__() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool | None: + return self._cm.__exit__(exc_type, exc_val, exc_tb) + + def __aiter__(self) -> _SignalReceiver: + return self + + async def __anext__(self) -> Signals: + signum = await self._iterator.__anext__() + return Signals(signum) + + +# +# Testing and debugging +# + + +class TestRunner(abc.TestRunner): + def __init__(self, **options: Any) -> None: + from queue import Queue + + self._call_queue: Queue[Callable[[], object]] = Queue() + self._send_stream: MemoryObjectSendStream | None = None + self._options = options + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + if self._send_stream: + self._send_stream.close() + while self._send_stream is not None: + self._call_queue.get()() + + async def _run_tests_and_fixtures(self) -> None: + self._send_stream, receive_stream = create_memory_object_stream(1) + with receive_stream: + async for coro, outcome_holder in receive_stream: + try: + retval = await coro + except BaseException as exc: + outcome_holder.append(Error(exc)) + else: + outcome_holder.append(Value(retval)) + + def _main_task_finished(self, outcome: object) -> None: + self._send_stream = None + + def _call_in_runner_task( + self, + func: Callable[P, Awaitable[T_Retval]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T_Retval: + if self._send_stream is None: + trio.lowlevel.start_guest_run( + self._run_tests_and_fixtures, + run_sync_soon_threadsafe=self._call_queue.put, + done_callback=self._main_task_finished, + **self._options, + ) + while self._send_stream is None: + self._call_queue.get()() + + outcome_holder: list[Outcome] = [] + self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder)) + while not outcome_holder: + self._call_queue.get()() + + return outcome_holder[0].unwrap() + + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], + kwargs: dict[str, Any], + ) -> Iterable[T_Retval]: + asyncgen = fixture_func(**kwargs) + fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None) + + yield fixturevalue + + try: + self._call_in_runner_task(asyncgen.asend, None) + except StopAsyncIteration: + pass + else: + self._call_in_runner_task(asyncgen.aclose) + raise RuntimeError("Async generator fixture did not stop") + + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], + kwargs: dict[str, Any], + ) -> T_Retval: + return self._call_in_runner_task(fixture_func, **kwargs) + + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + self._call_in_runner_task(test_func, **kwargs) + + +class TrioTaskInfo(TaskInfo): + def __init__(self, task: trio.lowlevel.Task): + parent_id = None + if task.parent_nursery and task.parent_nursery.parent_task: + parent_id = id(task.parent_nursery.parent_task) + + super().__init__(id(task), parent_id, task.name, task.coro) + self._task = weakref.proxy(task) + + def has_pending_cancellation(self) -> bool: + try: + return self._task._cancel_status.effectively_cancelled + except ReferenceError: + # If the task is no longer around, it surely doesn't have a cancellation + # pending + return False + + +class TrioBackend(AsyncBackend): + @classmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + return trio.run(func, *args) + + @classmethod + def current_token(cls) -> object: + return trio.lowlevel.current_trio_token() + + @classmethod + def current_time(cls) -> float: + return trio.current_time() + + @classmethod + def cancelled_exception_class(cls) -> type[BaseException]: + return trio.Cancelled + + @classmethod + async def checkpoint(cls) -> None: + await trio.lowlevel.checkpoint() + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + await trio.lowlevel.checkpoint_if_cancelled() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + await trio.lowlevel.cancel_shielded_checkpoint() + + @classmethod + async def sleep(cls, delay: float) -> None: + await trio.sleep(delay) + + @classmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> abc.CancelScope: + return CancelScope(deadline=deadline, shield=shield) + + @classmethod + def current_effective_deadline(cls) -> float: + return trio.current_effective_deadline() + + @classmethod + def create_task_group(cls) -> abc.TaskGroup: + return TaskGroup() + + @classmethod + def create_event(cls) -> abc.Event: + return Event() + + @classmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + return Lock(fast_acquire=fast_acquire) + + @classmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> abc.Semaphore: + return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) + + @classmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + return CapacityLimiter(total_tokens) + + @classmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: abc.CapacityLimiter | None = None, + ) -> T_Retval: + def wrapper() -> T_Retval: + with claim_worker_thread(TrioBackend, token): + return func(*args) + + token = TrioBackend.current_token() + return await run_sync( + wrapper, + abandon_on_cancel=abandon_on_cancel, + limiter=cast(trio.CapacityLimiter, limiter), + ) + + @classmethod + def check_cancelled(cls) -> None: + trio.from_thread.check_cancelled() + + @classmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + trio_token = cast("trio.lowlevel.TrioToken | None", token) + try: + return trio.from_thread.run_sync(func, *args, trio_token=trio_token) + except trio.RunFinishedError: + raise RunFinishedError from None + + @classmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + def convert_item(item: StrOrBytesPath) -> str: + str_or_bytes = os.fspath(item) + if isinstance(str_or_bytes, str): + return str_or_bytes + else: + return os.fsdecode(str_or_bytes) + + if isinstance(command, (str, bytes, PathLike)): + process = await trio.lowlevel.open_process( + convert_item(command), + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=True, + **kwargs, + ) + else: + process = await trio.lowlevel.open_process( + [convert_item(item) for item in command], + stdin=stdin, + stdout=stdout, + stderr=stderr, + shell=False, + **kwargs, + ) + + stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None + stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None + stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None + return Process(process, stdin_stream, stdout_stream, stderr_stream) + + @classmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: + trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers) + + @classmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + family = socket.AF_INET6 if ":" in host else socket.AF_INET + trio_socket = trio.socket.socket(family) + trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if local_address: + await trio_socket.bind(local_address) + + try: + await trio_socket.connect((host, port)) + except BaseException: + trio_socket.close() + raise + + return SocketStream(trio_socket) + + @classmethod + async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: + trio_socket = trio.socket.socket(socket.AF_UNIX) + try: + await trio_socket.connect(path) + except BaseException: + trio_socket.close() + raise + + return UNIXSocketStream(trio_socket) + + @classmethod + def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener: + return TCPSocketListener(sock) + + @classmethod + def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener: + return UNIXSocketListener(sock) + + @classmethod + async def create_udp_socket( + cls, + family: socket.AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM) + + if reuse_port: + trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + if local_address: + await trio_socket.bind(local_address) + + if remote_address: + await trio_socket.connect(remote_address) + return ConnectedUDPSocket(trio_socket) + else: + return UDPSocket(trio_socket) + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: None + ) -> abc.UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes + ) -> abc.ConnectedUNIXDatagramSocket: ... + + @classmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket.socket, remote_path: str | bytes | None + ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: + trio_socket = trio.socket.from_stdlib_socket(raw_socket) + + if remote_path: + await trio_socket.connect(remote_path) + return ConnectedUNIXDatagramSocket(trio_socket) + else: + return UNIXDatagramSocket(trio_socket) + + @classmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + return await trio.socket.getaddrinfo(host, port, family, type, proto, flags) + + @classmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + return await trio.socket.getnameinfo(sockaddr, flags) + + @classmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_readable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("reading from") from None + + @classmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + try: + await wait_writable(obj) + except trio.ClosedResourceError as exc: + raise ClosedResourceError().with_traceback(exc.__traceback__) from None + except trio.BusyResourceError: + raise BusyResourceError("writing to") from None + + @classmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + notify_closing(obj) + + @classmethod + async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener: + return TCPSocketListener(sock) + + @classmethod + async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return SocketStream(trio_sock) + + @classmethod + async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXSocketStream(trio_sock) + + @classmethod + async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UDPSocket(trio_sock) + + @classmethod + async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUDPSocket(trio_sock) + + @classmethod + async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return UNIXDatagramSocket(trio_sock) + + @classmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket.socket + ) -> ConnectedUNIXDatagramSocket: + trio_sock = trio.socket.from_stdlib_socket(sock) + return ConnectedUNIXDatagramSocket(trio_sock) + + @classmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + try: + return _capacity_limiter_wrapper.get() + except LookupError: + limiter = CapacityLimiter( + original=trio.to_thread.current_default_thread_limiter() + ) + _capacity_limiter_wrapper.set(limiter) + return limiter + + @classmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + return _SignalReceiver(signals) + + @classmethod + def get_current_task(cls) -> TaskInfo: + task = current_task() + return TrioTaskInfo(task) + + @classmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + root_task = current_root_task() + assert root_task + task_infos = [TrioTaskInfo(root_task)] + nurseries = root_task.child_nurseries + while nurseries: + new_nurseries: list[trio.Nursery] = [] + for nursery in nurseries: + for task in nursery.child_tasks: + task_infos.append(TrioTaskInfo(task)) + new_nurseries.extend(task.child_nurseries) + + nurseries = new_nurseries + + return task_infos + + @classmethod + async def wait_all_tasks_blocked(cls) -> None: + from trio.testing import wait_all_tasks_blocked + + await wait_all_tasks_blocked() + + @classmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + return TestRunner(**options) + + +backend_class = TrioBackend diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__init__.py b/venv/lib/python3.12/site-packages/anyio/_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..445b75314ab0647b26e1a9819cccb00f46d209db GIT binary patch literal 213 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 Ny8d03qZ-v0@qZ|7EB62Z literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_contextmanagers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..35ee0a5996797e2e790e86609aeb6b3100d50208 GIT binary patch literal 9019 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 bNfLh$;(IV+h2ETKj`m{2$~z1j@z5r` literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_fileio.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b07c7ed20840c17e35686fb1cad4b735fdcce971 GIT binary patch literal 43401 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 Gf&U-E829Y} literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_signals.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5782e523bb95dcf08c396175a5f70220648e271 GIT binary patch literal 1409 zcmZux&x;&I6t141yE9ouVmzqG!-G1I?bfWP!32T^SNOX^vv~3U@Dfo_1{MVIklVs~keqzg)ibbYHB`N-diCD-zV}}Lw6`~4G`@TD zTlr6$u|M5rGg@8n58TYa#jS>AaHUkZx!unyO!H|r|3?*wcN>jMSt2SxtsR# zonm*o8?fExOLx=$SDopg+8GU=I`gAoJ>*Jh%dOPP(01>(sZ?@k1#+vA4qm!FHx_wf z-`C2D&+Nxs@r7_V{fS&Co|~tExAek7*qFQL@th}*gh~_VV>2i|)@LlZc$^62nIRF|H%h3XgM97s?3(%zl>m*YdD+(|kS2(EE$0bL; zJsfXt!Rp3_fVV`r2w^t!55vjfhlfX>&SoS39)(?^8Ut5QN0*^idTAi>gh1gLfn$yT z(UA?2aFWR+gG!1#HMLn6iT4L;SP$S4-@ivf*p4{XxfLO!*1=-)8^d|;<}<;CTg5c literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_sockets.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bebc8cf2a0823b44173b5c42fbca7147cb96673 GIT binary patch literal 40665 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�m|TS!Bl!wwpLodUB}n5VUm?MlBJvdz%wai7 O1xv-vGK4WI`~L@sGw3k@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_streams.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..31c7e3870ae64646de39158049b097e582851a1b GIT binary patch literal 2362 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tasks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d22ea0cc24f08caf696130007d8a2126ffbc360f GIT binary patch literal 7717 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 bNOv;===Sa|^%-?xLx;NWww6|Ri97u_cu|jo literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_tempfile.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce0bc570a80191564994c9cedebbe574750e4529 GIT binary patch literal 28165 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/_core/__pycache__/_typedattr.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1d614d11a056650ed61fb93700e39f6180b687d GIT binary patch literal 3863 zcma)9U2GKB6~6Pc|7)**V#g#EOacLx+Fk;MwpP=Y;-pZB8wIG8rD!rcbC(^Mo!Q(w zYqP5*vXIp}B9Wp>l_MopP#&s8e(7spsytR|AD}3T8Bvw0t*X44I1-4bo^$7CZ4*Vk z(q7$r=iKvm&v(w*zYGlI35*Z-ePI7JN65c$6Av|9NE;u)Vue`5k{r^Ms*;3j*^!${ zRcWeKRqPcf)l65@%}h1Z%vQ6_Tr~&hs+Dr`%|f*x5t-18m0p!XuGOO0XOjH@><3pP z+0~LYXl0kw>X1x+O03)xvGS{ObaPmo72vFxoQ-q_%B3%mpezLi!*xC1@NLiKWjV+m zHyp>PIW)+;Y_wXod$Ftr$}zVSWX^V4^cMyTQgz!koFMDXQ|5Sv^#y7PoOU3AbrDb%QRObz^W!F18r)=IfX=R?e^A*RgRazat z;kk#WpFdRLwoj*8hI!ezNO{F@JGNKRO^?xvF0?^qrdyq$BzAhD8yf{}2H?jbn#dyg zcVYPY_ZEN7hGDnGKj0f2(#9T`e+?d@q$*jYDqB)jv6N*}mn?aySXGzF6{(s6Z>hoX z*4H>g{qC>w8jLgT7G{8h=1 zR)q9OjD%ToVILt^<(_;g1q`+gOOEN7^!8X!?n(0!m)?HL&%}h5(v#npE%kRwOnFUG ziJy(XzoqoZJ7fJolt_n^Q*%K{*ZXF1T@NI^o5v6rp|5p!X*SoIHs=5!&Gx~2TRl-u z2bqf$UI%(*$6DyPMEs8AB&0U#~AMbLTVE(qVga0bcV~ zYAKc6%yiP_LNLDdeIOOh+ZJWr<3BMt)%utfD;hy^!R8Ijeyxx?0yMqiGoz1CKD`BX z=o*L|X#?T6LTrKw);I~r0-=~DjzKyV`5(dUH>kFqBg!9 z9PVi~s@bmTv@IwIwkvpq8p1v{ZaY5NTYVzUg{sJG5{`mg-;H<=6h$}$DIu);t#4B0J3z=W%`TF)Lq|f{n142p zEwWh!K84a1A)OtBgCJvtJpjkc=qiz+o`=mh%l|G=#PVM~_qcpCS;+6gVuj?OPF#Z3 z_R*6t$6Zxq(oaX z=n}Tkp zWiX@Jhy9EQ?L@S;ze$@lKnp(@^Gp%HneTe+T;vT;``?S|sZ`)45%1V>><{J8*04z{lUe zHFjkArTc?qX#Dz><+iv zAscZ|tjg(4r)V_8rZ(g|5?_v&N3%gef;H<07LRz%5S5j{gj%C2~i7`Ce+^OF5q!*-%J&a2;3c z6R--@BjRLDJpvSgdM?_XyO+wZ9sSV;8Ba~%QQ^J}t56Iwk6p;np7JO*E%i41QGuo< z?9NJ?TWX_bGR!Ir^JtLvJNOq%TxHmlhQ^?6K|!ZiOxhBixQMOL>%z6dVZvnm3n7{o zVdypwJj(tc?4N$fUI04GS^NY{>xv{ve<#oWmrVVG9K1~q{)Zg;XKv`-iQBoS7Snf% pqwk)&UDOtHcZws|Pb}s((z5i#gM3MXMhT_|d!{6aH<*MI{s%H&^B@2K literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py b/venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py new file mode 100644 index 0000000..9f35bae --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_asyncio_selector_thread.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import asyncio +import socket +import threading +from collections.abc import Callable +from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + +_selector_lock = threading.Lock() +_selector: Selector | None = None + + +class Selector: + def __init__(self) -> None: + self._thread = threading.Thread(target=self.run, name="AnyIO socket selector") + self._selector = DefaultSelector() + self._send, self._receive = socket.socketpair() + self._send.setblocking(False) + self._receive.setblocking(False) + # This somewhat reduces the amount of memory wasted queueing up data + # for wakeups. With these settings, maximum number of 1-byte sends + # before getting BlockingIOError: + # Linux 4.8: 6 + # macOS (darwin 15.5): 1 + # Windows 10: 525347 + # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send + # blocking, even on non-blocking sockets, so don't do that.) + self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) + self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) + # On Windows this is a TCP socket so this might matter. On other + # platforms this fails b/c AF_UNIX sockets aren't actually TCP. + try: + self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except OSError: + pass + + self._selector.register(self._receive, EVENT_READ) + self._closed = False + + def start(self) -> None: + self._thread.start() + threading._register_atexit(self._stop) # type: ignore[attr-defined] + + def _stop(self) -> None: + global _selector + self._closed = True + self._notify_self() + self._send.close() + self._thread.join() + self._selector.unregister(self._receive) + self._receive.close() + self._selector.close() + _selector = None + assert not self._selector.get_map(), ( + "selector still has registered file descriptors after shutdown" + ) + + def _notify_self(self) -> None: + try: + self._send.send(b"\x00") + except BlockingIOError: + pass + + def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)}) + else: + if EVENT_READ in key.data: + raise ValueError( + "this file descriptor is already registered for reading" + ) + + key.data[EVENT_READ] = loop, callback + self._selector.modify(fd, key.events | EVENT_READ, key.data) + + self._notify_self() + + def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: + loop = asyncio.get_running_loop() + try: + key = self._selector.get_key(fd) + except KeyError: + self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)}) + else: + if EVENT_WRITE in key.data: + raise ValueError( + "this file descriptor is already registered for writing" + ) + + key.data[EVENT_WRITE] = loop, callback + self._selector.modify(fd, key.events | EVENT_WRITE, key.data) + + self._notify_self() + + def remove_reader(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_READ: + del key.data[EVENT_READ] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def remove_writer(self, fd: FileDescriptorLike) -> bool: + try: + key = self._selector.get_key(fd) + except KeyError: + return False + + if new_events := key.events ^ EVENT_WRITE: + del key.data[EVENT_WRITE] + self._selector.modify(fd, new_events, key.data) + else: + self._selector.unregister(fd) + + return True + + def run(self) -> None: + while not self._closed: + for key, events in self._selector.select(): + if key.fileobj is self._receive: + try: + while self._receive.recv(4096): + pass + except BlockingIOError: + pass + + continue + + if events & EVENT_READ: + loop, callback = key.data[EVENT_READ] + self.remove_reader(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + if events & EVENT_WRITE: + loop, callback = key.data[EVENT_WRITE] + self.remove_writer(key.fd) + try: + loop.call_soon_threadsafe(callback) + except RuntimeError: + pass # the loop was already closed + + +def get_selector() -> Selector: + global _selector + + with _selector_lock: + if _selector is None: + _selector = Selector() + _selector.start() + + return _selector diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py b/venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py new file mode 100644 index 0000000..302f32b --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_contextmanagers.py @@ -0,0 +1,200 @@ +from __future__ import annotations + +from abc import abstractmethod +from contextlib import AbstractAsyncContextManager, AbstractContextManager +from inspect import isasyncgen, iscoroutine, isgenerator +from types import TracebackType +from typing import Protocol, TypeVar, cast, final + +_T_co = TypeVar("_T_co", covariant=True) +_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None") + + +class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]): + def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ... + + +class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]): + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ... + + +class ContextManagerMixin: + """ + Mixin class providing context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via :meth:`__contextmanager__` + which should return a generator. The mechanics are meant to mirror those of + :func:`@contextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractContextManager[object, bool | None] | None = None + + @final + def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__contextmanager__() + if not isinstance(cm, AbstractContextManager): + if isgenerator(cm): + raise TypeError( + "__contextmanager__() returned a generator object instead of " + "a context manager. Did you forget to add the @contextmanager " + "decorator?" + ) + + raise TypeError( + f"__contextmanager__() did not return a context manager object, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__contextmanager__() returned " + f"self. Did you forget to add the @contextmanager decorator and a " + f"'yield' statement?" + ) + + value = cm.__enter__() + self.__cm = cm + return value + + @final + def __exit__( + self: _SupportsCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, ContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __contextmanager__(self) -> AbstractContextManager[object, bool | None]: + """ + Implement your context manager logic here. + + This method **must** be decorated with + :func:`@contextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: a context manager object + """ + + +class AsyncContextManagerMixin: + """ + Mixin class providing async context manager functionality via a generator-based + implementation. + + This class allows you to implement a context manager via + :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of + :func:`@asynccontextmanager `. + + .. note:: Classes using this mix-in are not reentrant as context managers, meaning + that once you enter it, you can't re-enter before first exiting it. + + .. seealso:: :doc:`contextmanagers` + """ + + __cm: AbstractAsyncContextManager[object, bool | None] | None = None + + @final + async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co: + # Needed for mypy to assume self still has the __cm member + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is not None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has already been entered" + ) + + cm = self.__asynccontextmanager__() + if not isinstance(cm, AbstractAsyncContextManager): + if isasyncgen(cm): + raise TypeError( + "__asynccontextmanager__() returned an async generator instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator?" + ) + elif iscoroutine(cm): + cm.close() + raise TypeError( + "__asynccontextmanager__() returned a coroutine object instead of " + "an async context manager. Did you forget to add the " + "@asynccontextmanager decorator and a 'yield' statement?" + ) + + raise TypeError( + f"__asynccontextmanager__() did not return an async context manager, " + f"but {cm.__class__!r}" + ) + + if cm is self: + raise TypeError( + f"{self.__class__.__qualname__}.__asynccontextmanager__() returned " + f"self. Did you forget to add the @asynccontextmanager decorator and a " + f"'yield' statement?" + ) + + value = await cm.__aenter__() + self.__cm = cm + return value + + @final + async def __aexit__( + self: _SupportsAsyncCtxMgr[object, _ExitT_co], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> _ExitT_co: + assert isinstance(self, AsyncContextManagerMixin) + if self.__cm is None: + raise RuntimeError( + f"this {self.__class__.__qualname__} has not been entered yet" + ) + + # Prevent circular references + cm = self.__cm + del self.__cm + + return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb)) + + @abstractmethod + def __asynccontextmanager__( + self, + ) -> AbstractAsyncContextManager[object, bool | None]: + """ + Implement your async context manager logic here. + + This method **must** be decorated with + :func:`@asynccontextmanager `. + + .. note:: Remember that the ``yield`` will raise any exception raised in the + enclosed context block, so use a ``finally:`` block to clean up resources! + + :return: an async context manager object + """ diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py b/venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py new file mode 100644 index 0000000..59a69cc --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_eventloop.py @@ -0,0 +1,234 @@ +from __future__ import annotations + +import math +import sys +import threading +from collections.abc import Awaitable, Callable, Generator +from contextlib import contextmanager +from contextvars import Token +from importlib import import_module +from typing import TYPE_CHECKING, Any, TypeVar + +from ._exceptions import NoEventLoopError + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +sniffio: Any +try: + import sniffio +except ModuleNotFoundError: + sniffio = None + +if TYPE_CHECKING: + from ..abc import AsyncBackend + +# This must be updated when new backends are introduced +BACKENDS = "asyncio", "trio" + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +threadlocals = threading.local() +loaded_backends: dict[str, type[AsyncBackend]] = {} + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, +) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param backend: name of the asynchronous event loop implementation – currently + either ``asyncio`` or ``trio`` + :param backend_options: keyword arguments to call the backend ``run()`` + implementation with (documented :ref:`here `) + :return: the return value of the coroutine function + :raises RuntimeError: if an asynchronous event loop is already running in this + thread + :raises LookupError: if the named backend is not found + + """ + if asynclib_name := current_async_library(): + raise RuntimeError(f"Already running {asynclib_name} in this thread") + + try: + async_backend = get_async_backend(backend) + except ImportError as exc: + raise LookupError(f"No such backend: {backend}") from exc + + token = None + if asynclib_name is None: + # Since we're in control of the event loop, we can cache the name of the async + # library + token = set_current_async_library(backend) + + try: + backend_options = backend_options or {} + return async_backend.run(func, args, {}, backend_options) + finally: + reset_current_async_library(token) + + +async def sleep(delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + + """ + return await get_async_backend().sleep(delay) + + +async def sleep_forever() -> None: + """ + Pause the current task until it's cancelled. + + This is a shortcut for ``sleep(math.inf)``. + + .. versionadded:: 3.1 + + """ + await sleep(math.inf) + + +async def sleep_until(deadline: float) -> None: + """ + Pause the current task until the given time. + + :param deadline: the absolute time to wake up at (according to the internal + monotonic clock of the event loop) + + .. versionadded:: 3.1 + + """ + now = current_time() + await sleep(max(deadline - now, 0)) + + +def current_time() -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_time() + + +def get_all_backends() -> tuple[str, ...]: + """Return a tuple of the names of all built-in backends.""" + return BACKENDS + + +def get_available_backends() -> tuple[str, ...]: + """ + Test for the availability of built-in backends. + + :return a tuple of the built-in backend names that were successfully imported + + .. versionadded:: 4.12 + + """ + available_backends: list[str] = [] + for backend_name in get_all_backends(): + try: + get_async_backend(backend_name) + except ImportError: + continue + + available_backends.append(backend_name) + + return tuple(available_backends) + + +def get_cancelled_exc_class() -> type[BaseException]: + """ + Return the current async library's cancellation exception class. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().cancelled_exception_class() + + +# +# Private API +# + + +@contextmanager +def claim_worker_thread( + backend_class: type[AsyncBackend], token: object +) -> Generator[Any, None, None]: + from ..lowlevel import EventLoopToken + + threadlocals.current_token = EventLoopToken(backend_class, token) + try: + yield + finally: + del threadlocals.current_token + + +def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]: + if asynclib_name is None: + asynclib_name = current_async_library() + if not asynclib_name: + raise NoEventLoopError( + f"Not currently running on any asynchronous event loop. " + f"Available async backends: {', '.join(get_all_backends())}" + ) + + # We use our own dict instead of sys.modules to get the already imported back-end + # class because the appropriate modules in sys.modules could potentially be only + # partially initialized + try: + return loaded_backends[asynclib_name] + except KeyError: + module = import_module(f"anyio._backends._{asynclib_name}") + loaded_backends[asynclib_name] = module.backend_class + return module.backend_class + + +def current_async_library() -> str | None: + if sniffio is None: + # If sniffio is not installed, we assume we're either running asyncio or nothing + import asyncio + + try: + asyncio.get_running_loop() + return "asyncio" + except RuntimeError: + pass + else: + try: + return sniffio.current_async_library() + except sniffio.AsyncLibraryNotFoundError: + pass + + return None + + +def set_current_async_library(asynclib_name: str | None) -> Token | None: + # no-op if sniffio is not installed + if sniffio is None: + return None + + return sniffio.current_async_library_cvar.set(asynclib_name) + + +def reset_current_async_library(token: Token | None) -> None: + if token is not None: + sniffio.current_async_library_cvar.reset(token) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py b/venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py new file mode 100644 index 0000000..3776bed --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_exceptions.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import sys +from collections.abc import Generator +from textwrap import dedent +from typing import Any + +if sys.version_info < (3, 11): + from exceptiongroup import BaseExceptionGroup + + +class BrokenResourceError(Exception): + """ + Raised when trying to use a resource that has been rendered unusable due to external + causes (e.g. a send stream whose peer has disconnected). + """ + + +class BrokenWorkerProcess(Exception): + """ + Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or + otherwise misbehaves. + """ + + +class BrokenWorkerInterpreter(Exception): + """ + Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is + raised in the subinterpreter. + """ + + def __init__(self, excinfo: Any): + # This was adapted from concurrent.futures.interpreter.ExecutionFailed + msg = excinfo.formatted + if not msg: + if excinfo.type and excinfo.msg: + msg = f"{excinfo.type.__name__}: {excinfo.msg}" + else: + msg = excinfo.type.__name__ or excinfo.msg + + super().__init__(msg) + self.excinfo = excinfo + + def __str__(self) -> str: + try: + formatted = self.excinfo.errdisplay + except Exception: + return super().__str__() + else: + return dedent( + f""" + {super().__str__()} + + Uncaught in the interpreter: + + {formatted} + """.strip() + ) + + +class BusyResourceError(Exception): + """ + Raised when two tasks are trying to read from or write to the same resource + concurrently. + """ + + def __init__(self, action: str): + super().__init__(f"Another task is already {action} this resource") + + +class ClosedResourceError(Exception): + """Raised when trying to use a resource that has been closed.""" + + +class ConnectionFailed(OSError): + """ + Raised when a connection attempt fails. + + .. note:: This class inherits from :exc:`OSError` for backwards compatibility. + """ + + +def iterate_exceptions( + exception: BaseException, +) -> Generator[BaseException, None, None]: + if isinstance(exception, BaseExceptionGroup): + for exc in exception.exceptions: + yield from iterate_exceptions(exc) + else: + yield exception + + +class DelimiterNotFound(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + maximum number of bytes has been read without the delimiter being found. + """ + + def __init__(self, max_bytes: int) -> None: + super().__init__( + f"The delimiter was not found among the first {max_bytes} bytes" + ) + + +class EndOfStream(Exception): + """ + Raised when trying to read from a stream that has been closed from the other end. + """ + + +class IncompleteRead(Exception): + """ + Raised during + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or + :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the + connection is closed before the requested amount of bytes has been read. + """ + + def __init__(self) -> None: + super().__init__( + "The stream was closed before the read operation could be completed" + ) + + +class TypedAttributeLookupError(LookupError): + """ + Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute + is not found and no default value has been given. + """ + + +class WouldBlock(Exception): + """Raised by ``X_nowait`` functions if ``X()`` would block.""" + + +class NoEventLoopError(RuntimeError): + """ + Raised by several functions that require an event loop to be running in the current + thread when there is no running event loop. + + This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` + if not calling from an AnyIO worker thread, and no ``token`` was passed. + """ + + +class RunFinishedError(RuntimeError): + """ + Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event + loop associated with the explicitly passed token has already finished. + """ + + def __init__(self) -> None: + super().__init__( + "The event loop associated with the given token has already finished" + ) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_fileio.py b/venv/lib/python3.12/site-packages/anyio/_core/_fileio.py new file mode 100644 index 0000000..061f0d7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_fileio.py @@ -0,0 +1,797 @@ +from __future__ import annotations + +import os +import pathlib +import sys +from collections.abc import ( + AsyncIterator, + Callable, + Iterable, + Iterator, + Sequence, +) +from dataclasses import dataclass +from functools import partial +from os import PathLike +from typing import ( + IO, + TYPE_CHECKING, + Any, + AnyStr, + ClassVar, + Final, + Generic, + overload, +) + +from .. import to_thread +from ..abc import AsyncResource + +if TYPE_CHECKING: + from types import ModuleType + + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer +else: + ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object + + +class AsyncFile(AsyncResource, Generic[AnyStr]): + """ + An asynchronous file object. + + This class wraps a standard file object and provides async friendly versions of the + following blocking methods (where available on the original file object): + + * read + * read1 + * readline + * readlines + * readinto + * readinto1 + * write + * writelines + * truncate + * seek + * tell + * flush + + All other methods are directly passed through. + + This class supports the asynchronous context manager protocol which closes the + underlying file at the end of the context block. + + This class also supports asynchronous iteration:: + + async with await open_file(...) as f: + async for line in f: + print(line) + """ + + def __init__(self, fp: IO[AnyStr]) -> None: + self._fp: Any = fp + + def __getattr__(self, name: str) -> object: + return getattr(self._fp, name) + + @property + def wrapped(self) -> IO[AnyStr]: + """The wrapped file object.""" + return self._fp + + async def __aiter__(self) -> AsyncIterator[AnyStr]: + while True: + line = await self.readline() + if line: + yield line + else: + break + + async def aclose(self) -> None: + return await to_thread.run_sync(self._fp.close) + + async def read(self, size: int = -1) -> AnyStr: + return await to_thread.run_sync(self._fp.read, size) + + async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes: + return await to_thread.run_sync(self._fp.read1, size) + + async def readline(self) -> AnyStr: + return await to_thread.run_sync(self._fp.readline) + + async def readlines(self) -> list[AnyStr]: + return await to_thread.run_sync(self._fp.readlines) + + async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto, b) + + async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: + return await to_thread.run_sync(self._fp.readinto1, b) + + @overload + async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... + + @overload + async def write(self: AsyncFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + return await to_thread.run_sync(self._fp.write, b) + + @overload + async def writelines( + self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + + @overload + async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... + + async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None: + return await to_thread.run_sync(self._fp.writelines, lines) + + async def truncate(self, size: int | None = None) -> int: + return await to_thread.run_sync(self._fp.truncate, size) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + return await to_thread.run_sync(self._fp.seek, offset, whence) + + async def tell(self) -> int: + return await to_thread.run_sync(self._fp.tell) + + async def flush(self) -> None: + return await to_thread.run_sync(self._fp.flush) + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., +) -> AsyncFile[bytes]: ... + + +@overload +async def open_file( + file: str | PathLike[str] | int, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + closefd: bool = ..., + opener: Callable[[str, int], int] | None = ..., +) -> AsyncFile[str]: ... + + +async def open_file( + file: str | PathLike[str] | int, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + closefd: bool = True, + opener: Callable[[str, int], int] | None = None, +) -> AsyncFile[Any]: + """ + Open a file asynchronously. + + The arguments are exactly the same as for the builtin :func:`open`. + + :return: an asynchronous file object + + """ + fp = await to_thread.run_sync( + open, file, mode, buffering, encoding, errors, newline, closefd, opener + ) + return AsyncFile(fp) + + +def wrap_file(file: IO[AnyStr]) -> AsyncFile[AnyStr]: + """ + Wrap an existing file as an asynchronous file. + + :param file: an existing file-like object + :return: an asynchronous file object + + """ + return AsyncFile(file) + + +@dataclass(eq=False) +class _PathIterator(AsyncIterator["Path"]): + iterator: Iterator[PathLike[str]] + + async def __anext__(self) -> Path: + nextval = await to_thread.run_sync( + next, self.iterator, None, abandon_on_cancel=True + ) + if nextval is None: + raise StopAsyncIteration from None + + return Path(nextval) + + +class Path: + """ + An asynchronous version of :class:`pathlib.Path`. + + This class cannot be substituted for :class:`pathlib.Path` or + :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike` + interface. + + It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for + the deprecated :meth:`~pathlib.Path.link_to` method. + + Some methods may be unavailable or have limited functionality, based on the Python + version: + + * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later) + * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later) + * :attr:`~pathlib.Path.info` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later) + * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only + available on Python 3.13 or later) + * :meth:`~pathlib.Path.move` (available on Python 3.14 or later) + * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later) + * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available + on Python 3.12 or later) + * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later) + + Any methods that do disk I/O need to be awaited on. These methods are: + + * :meth:`~pathlib.Path.absolute` + * :meth:`~pathlib.Path.chmod` + * :meth:`~pathlib.Path.cwd` + * :meth:`~pathlib.Path.exists` + * :meth:`~pathlib.Path.expanduser` + * :meth:`~pathlib.Path.group` + * :meth:`~pathlib.Path.hardlink_to` + * :meth:`~pathlib.Path.home` + * :meth:`~pathlib.Path.is_block_device` + * :meth:`~pathlib.Path.is_char_device` + * :meth:`~pathlib.Path.is_dir` + * :meth:`~pathlib.Path.is_fifo` + * :meth:`~pathlib.Path.is_file` + * :meth:`~pathlib.Path.is_junction` + * :meth:`~pathlib.Path.is_mount` + * :meth:`~pathlib.Path.is_socket` + * :meth:`~pathlib.Path.is_symlink` + * :meth:`~pathlib.Path.lchmod` + * :meth:`~pathlib.Path.lstat` + * :meth:`~pathlib.Path.mkdir` + * :meth:`~pathlib.Path.open` + * :meth:`~pathlib.Path.owner` + * :meth:`~pathlib.Path.read_bytes` + * :meth:`~pathlib.Path.read_text` + * :meth:`~pathlib.Path.readlink` + * :meth:`~pathlib.Path.rename` + * :meth:`~pathlib.Path.replace` + * :meth:`~pathlib.Path.resolve` + * :meth:`~pathlib.Path.rmdir` + * :meth:`~pathlib.Path.samefile` + * :meth:`~pathlib.Path.stat` + * :meth:`~pathlib.Path.symlink_to` + * :meth:`~pathlib.Path.touch` + * :meth:`~pathlib.Path.unlink` + * :meth:`~pathlib.Path.walk` + * :meth:`~pathlib.Path.write_bytes` + * :meth:`~pathlib.Path.write_text` + + Additionally, the following methods return an async iterator yielding + :class:`~.Path` objects: + + * :meth:`~pathlib.Path.glob` + * :meth:`~pathlib.Path.iterdir` + * :meth:`~pathlib.Path.rglob` + """ + + __slots__ = "_path", "__weakref__" + + __weakref__: Any + + def __init__(self, *args: str | PathLike[str]) -> None: + self._path: Final[pathlib.Path] = pathlib.Path(*args) + + def __fspath__(self) -> str: + return self._path.__fspath__() + + def __str__(self) -> str: + return self._path.__str__() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.as_posix()!r})" + + def __bytes__(self) -> bytes: + return self._path.__bytes__() + + def __hash__(self) -> int: + return self._path.__hash__() + + def __eq__(self, other: object) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__eq__(target) + + def __lt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__lt__(target) + + def __le__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__le__(target) + + def __gt__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__gt__(target) + + def __ge__(self, other: pathlib.PurePath | Path) -> bool: + target = other._path if isinstance(other, Path) else other + return self._path.__ge__(target) + + def __truediv__(self, other: str | PathLike[str]) -> Path: + return Path(self._path / other) + + def __rtruediv__(self, other: str | PathLike[str]) -> Path: + return Path(other) / self + + @property + def parts(self) -> tuple[str, ...]: + return self._path.parts + + @property + def drive(self) -> str: + return self._path.drive + + @property + def root(self) -> str: + return self._path.root + + @property + def anchor(self) -> str: + return self._path.anchor + + @property + def parents(self) -> Sequence[Path]: + return tuple(Path(p) for p in self._path.parents) + + @property + def parent(self) -> Path: + return Path(self._path.parent) + + @property + def name(self) -> str: + return self._path.name + + @property + def suffix(self) -> str: + return self._path.suffix + + @property + def suffixes(self) -> list[str]: + return self._path.suffixes + + @property + def stem(self) -> str: + return self._path.stem + + async def absolute(self) -> Path: + path = await to_thread.run_sync(self._path.absolute) + return Path(path) + + def as_posix(self) -> str: + return self._path.as_posix() + + def as_uri(self) -> str: + return self._path.as_uri() + + if sys.version_info >= (3, 13): + parser: ClassVar[ModuleType] = pathlib.Path.parser + + @classmethod + def from_uri(cls, uri: str) -> Path: + return Path(pathlib.Path.from_uri(uri)) + + def full_match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.full_match(path_pattern, case_sensitive=case_sensitive) + + def match( + self, path_pattern: str, *, case_sensitive: bool | None = None + ) -> bool: + return self._path.match(path_pattern, case_sensitive=case_sensitive) + else: + + def match(self, path_pattern: str) -> bool: + return self._path.match(path_pattern) + + if sys.version_info >= (3, 14): + + @property + def info(self) -> Any: # TODO: add return type annotation when Typeshed gets it + return self._path.info + + async def copy( + self, + target: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Path: + func = partial( + self._path.copy, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return Path(await to_thread.run_sync(func, pathlib.Path(target))) + + async def copy_into( + self, + target_dir: str | os.PathLike[str], + *, + follow_symlinks: bool = True, + preserve_metadata: bool = False, + ) -> Path: + func = partial( + self._path.copy_into, + follow_symlinks=follow_symlinks, + preserve_metadata=preserve_metadata, + ) + return Path(await to_thread.run_sync(func, pathlib.Path(target_dir))) + + async def move(self, target: str | os.PathLike[str]) -> Path: + # Upstream does not handle anyio.Path properly as a PathLike + target = pathlib.Path(target) + return Path(await to_thread.run_sync(self._path.move, target)) + + async def move_into( + self, + target_dir: str | os.PathLike[str], + ) -> Path: + return Path(await to_thread.run_sync(self._path.move_into, target_dir)) + + def is_relative_to(self, other: str | PathLike[str]) -> bool: + try: + self.relative_to(other) + return True + except ValueError: + return False + + async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: + func = partial(os.chmod, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, mode) + + @classmethod + async def cwd(cls) -> Path: + path = await to_thread.run_sync(pathlib.Path.cwd) + return cls(path) + + async def exists(self) -> bool: + return await to_thread.run_sync(self._path.exists, abandon_on_cancel=True) + + async def expanduser(self) -> Path: + return Path( + await to_thread.run_sync(self._path.expanduser, abandon_on_cancel=True) + ) + + if sys.version_info < (3, 12): + # Python 3.11 and earlier + def glob(self, pattern: str) -> AsyncIterator[Path]: + gen = self._path.glob(pattern) + return _PathIterator(gen) + elif (3, 12) <= sys.version_info < (3, 13): + # changed in Python 3.12: + # - The case_sensitive parameter was added. + def glob( + self, + pattern: str, + *, + case_sensitive: bool | None = None, + ) -> AsyncIterator[Path]: + gen = self._path.glob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Path]: + gen = self._path.glob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen) + + async def group(self) -> str: + return await to_thread.run_sync(self._path.group, abandon_on_cancel=True) + + async def hardlink_to( + self, target: str | bytes | PathLike[str] | PathLike[bytes] + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(os.link, target, self) + + @classmethod + async def home(cls) -> Path: + home_path = await to_thread.run_sync(pathlib.Path.home) + return cls(home_path) + + def is_absolute(self) -> bool: + return self._path.is_absolute() + + async def is_block_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_block_device, abandon_on_cancel=True + ) + + async def is_char_device(self) -> bool: + return await to_thread.run_sync( + self._path.is_char_device, abandon_on_cancel=True + ) + + async def is_dir(self) -> bool: + return await to_thread.run_sync(self._path.is_dir, abandon_on_cancel=True) + + async def is_fifo(self) -> bool: + return await to_thread.run_sync(self._path.is_fifo, abandon_on_cancel=True) + + async def is_file(self) -> bool: + return await to_thread.run_sync(self._path.is_file, abandon_on_cancel=True) + + if sys.version_info >= (3, 12): + + async def is_junction(self) -> bool: + return await to_thread.run_sync(self._path.is_junction) + + async def is_mount(self) -> bool: + return await to_thread.run_sync( + os.path.ismount, self._path, abandon_on_cancel=True + ) + + def is_reserved(self) -> bool: + return self._path.is_reserved() + + async def is_socket(self) -> bool: + return await to_thread.run_sync(self._path.is_socket, abandon_on_cancel=True) + + async def is_symlink(self) -> bool: + return await to_thread.run_sync(self._path.is_symlink, abandon_on_cancel=True) + + async def iterdir(self) -> AsyncIterator[Path]: + gen = ( + self._path.iterdir() + if sys.version_info < (3, 13) + else await to_thread.run_sync(self._path.iterdir, abandon_on_cancel=True) + ) + async for path in _PathIterator(gen): + yield path + + def joinpath(self, *args: str | PathLike[str]) -> Path: + return Path(self._path.joinpath(*args)) + + async def lchmod(self, mode: int) -> None: + await to_thread.run_sync(self._path.lchmod, mode) + + async def lstat(self) -> os.stat_result: + return await to_thread.run_sync(self._path.lstat, abandon_on_cancel=True) + + async def mkdir( + self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False + ) -> None: + await to_thread.run_sync(self._path.mkdir, mode, parents, exist_ok) + + @overload + async def open( + self, + mode: OpenBinaryMode, + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[bytes]: ... + + @overload + async def open( + self, + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + errors: str | None = ..., + newline: str | None = ..., + ) -> AsyncFile[str]: ... + + async def open( + self, + mode: str = "r", + buffering: int = -1, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> AsyncFile[Any]: + fp = await to_thread.run_sync( + self._path.open, mode, buffering, encoding, errors, newline + ) + return AsyncFile(fp) + + async def owner(self) -> str: + return await to_thread.run_sync(self._path.owner, abandon_on_cancel=True) + + async def read_bytes(self) -> bytes: + return await to_thread.run_sync(self._path.read_bytes) + + async def read_text( + self, encoding: str | None = None, errors: str | None = None + ) -> str: + return await to_thread.run_sync(self._path.read_text, encoding, errors) + + if sys.version_info >= (3, 12): + + def relative_to( + self, *other: str | PathLike[str], walk_up: bool = False + ) -> Path: + # relative_to() should work with any PathLike but it doesn't + others = [pathlib.Path(other) for other in other] + return Path(self._path.relative_to(*others, walk_up=walk_up)) + + else: + + def relative_to(self, *other: str | PathLike[str]) -> Path: + return Path(self._path.relative_to(*other)) + + async def readlink(self) -> Path: + target = await to_thread.run_sync(os.readlink, self._path) + return Path(target) + + async def rename(self, target: str | pathlib.PurePath | Path) -> Path: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.rename, target) + return Path(target) + + async def replace(self, target: str | pathlib.PurePath | Path) -> Path: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.replace, target) + return Path(target) + + async def resolve(self, strict: bool = False) -> Path: + func = partial(self._path.resolve, strict=strict) + return Path(await to_thread.run_sync(func, abandon_on_cancel=True)) + + if sys.version_info < (3, 12): + # Pre Python 3.12 + def rglob(self, pattern: str) -> AsyncIterator[Path]: + gen = self._path.rglob(pattern) + return _PathIterator(gen) + elif (3, 12) <= sys.version_info < (3, 13): + # Changed in Python 3.12: + # - The case_sensitive parameter was added. + def rglob( + self, pattern: str, *, case_sensitive: bool | None = None + ) -> AsyncIterator[Path]: + gen = self._path.rglob(pattern, case_sensitive=case_sensitive) + return _PathIterator(gen) + elif sys.version_info >= (3, 13): + # Changed in Python 3.13: + # - The recurse_symlinks parameter was added. + # - The pattern parameter accepts a path-like object. + def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block + self, + pattern: str | PathLike[str], + *, + case_sensitive: bool | None = None, + recurse_symlinks: bool = False, + ) -> AsyncIterator[Path]: + gen = self._path.rglob( + pattern, # type: ignore[arg-type] + case_sensitive=case_sensitive, + recurse_symlinks=recurse_symlinks, + ) + return _PathIterator(gen) + + async def rmdir(self) -> None: + await to_thread.run_sync(self._path.rmdir) + + async def samefile(self, other_path: str | PathLike[str]) -> bool: + if isinstance(other_path, Path): + other_path = other_path._path + + return await to_thread.run_sync( + self._path.samefile, other_path, abandon_on_cancel=True + ) + + async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result: + func = partial(os.stat, follow_symlinks=follow_symlinks) + return await to_thread.run_sync(func, self._path, abandon_on_cancel=True) + + async def symlink_to( + self, + target: str | bytes | PathLike[str] | PathLike[bytes], + target_is_directory: bool = False, + ) -> None: + if isinstance(target, Path): + target = target._path + + await to_thread.run_sync(self._path.symlink_to, target, target_is_directory) + + async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: + await to_thread.run_sync(self._path.touch, mode, exist_ok) + + async def unlink(self, missing_ok: bool = False) -> None: + try: + await to_thread.run_sync(self._path.unlink) + except FileNotFoundError: + if not missing_ok: + raise + + if sys.version_info >= (3, 12): + + async def walk( + self, + top_down: bool = True, + on_error: Callable[[OSError], object] | None = None, + follow_symlinks: bool = False, + ) -> AsyncIterator[tuple[Path, list[str], list[str]]]: + def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None: + try: + return next(gen) + except StopIteration: + return None + + gen = self._path.walk(top_down, on_error, follow_symlinks) + while True: + value = await to_thread.run_sync(get_next_value) + if value is None: + return + + root, dirs, paths = value + yield Path(root), dirs, paths + + def with_name(self, name: str) -> Path: + return Path(self._path.with_name(name)) + + def with_stem(self, stem: str) -> Path: + return Path(self._path.with_name(stem + self._path.suffix)) + + def with_suffix(self, suffix: str) -> Path: + return Path(self._path.with_suffix(suffix)) + + def with_segments(self, *pathsegments: str | PathLike[str]) -> Path: + return Path(*pathsegments) + + async def write_bytes(self, data: bytes) -> int: + return await to_thread.run_sync(self._path.write_bytes, data) + + async def write_text( + self, + data: str, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> int: + # Path.write_text() does not support the "newline" parameter before Python 3.10 + def sync_write_text() -> int: + with self._path.open( + "w", encoding=encoding, errors=errors, newline=newline + ) as fp: + return fp.write(data) + + return await to_thread.run_sync(sync_write_text) + + +PathLike.register(Path) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_resources.py b/venv/lib/python3.12/site-packages/anyio/_core/_resources.py new file mode 100644 index 0000000..b9a5344 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_resources.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from ..abc import AsyncResource +from ._tasks import CancelScope + + +async def aclose_forcefully(resource: AsyncResource) -> None: + """ + Close an asynchronous resource in a cancelled scope. + + Doing this closes the resource without waiting on anything. + + :param resource: the resource to close + + """ + with CancelScope() as scope: + scope.cancel() + await resource.aclose() diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_signals.py b/venv/lib/python3.12/site-packages/anyio/_core/_signals.py new file mode 100644 index 0000000..e24c79e --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_signals.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from collections.abc import AsyncIterator +from contextlib import AbstractContextManager +from signal import Signals + +from ._eventloop import get_async_backend + + +def open_signal_receiver( + *signals: Signals, +) -> AbstractContextManager[AsyncIterator[Signals]]: + """ + Start receiving operating system signals. + + :param signals: signals to receive (e.g. ``signal.SIGINT``) + :return: an asynchronous context manager for an asynchronous iterator which yields + signal numbers + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. warning:: Windows does not support signals natively so it is best to avoid + relying on this in cross-platform applications. + + .. warning:: On asyncio, this permanently replaces any previous signal handler for + the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`. + + """ + return get_async_backend().open_signal_receiver(*signals) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_sockets.py b/venv/lib/python3.12/site-packages/anyio/_core/_sockets.py new file mode 100644 index 0000000..6c99b3a --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_sockets.py @@ -0,0 +1,1003 @@ +from __future__ import annotations + +import errno +import os +import socket +import ssl +import stat +import sys +from collections.abc import Awaitable +from dataclasses import dataclass +from ipaddress import IPv4Address, IPv6Address, ip_address +from os import PathLike, chmod +from socket import AddressFamily, SocketKind +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +from .. import ConnectionFailed, to_thread +from ..abc import ( + ByteStreamConnectable, + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPAddressType, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, +) +from ..streams.stapled import MultiListener +from ..streams.tls import TLSConnectable, TLSStream +from ._eventloop import get_async_backend +from ._resources import aclose_forcefully +from ._synchronization import Event +from ._tasks import create_task_group, move_on_after + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike +else: + FileDescriptorLike = object + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +if sys.version_info < (3, 13): + from typing_extensions import deprecated +else: + from warnings import deprecated + +IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515 + +AnyIPAddressFamily = Literal[ + AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6 +] +IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6] + + +# tls_hostname given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str, + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# ssl_context given +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + ssl_context: ssl.SSLContext, + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=True +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + tls: Literal[True], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> TLSStream: ... + + +# tls=False +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + tls: Literal[False], + ssl_context: ssl.SSLContext | None = ..., + tls_standard_compatible: bool = ..., + tls_hostname: str | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +# No TLS arguments +@overload +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = ..., + happy_eyeballs_delay: float = ..., +) -> SocketStream: ... + + +async def connect_tcp( + remote_host: IPAddressType, + remote_port: int, + *, + local_host: IPAddressType | None = None, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_standard_compatible: bool = True, + tls_hostname: str | None = None, + happy_eyeballs_delay: float = 0.25, +) -> SocketStream | TLSStream: + """ + Connect to a host using the TCP protocol. + + This function implements the stateless version of the Happy Eyeballs algorithm (RFC + 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses, + each one is tried until one connection attempt succeeds. If the first attempt does + not connected within 250 milliseconds, a second attempt is started using the next + address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if + available) is tried first. + + When the connection has been established, a TLS handshake will be done if either + ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``. + + :param remote_host: the IP address or host name to connect to + :param remote_port: port on the target host to connect to + :param local_host: the interface address or name to bind the socket to before + connecting + :param tls: ``True`` to do a TLS handshake with the connected stream and return a + :class:`~anyio.streams.tls.TLSStream` instead + :param ssl_context: the SSL context object to use (if omitted, a default context is + created) + :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake + before closing the stream and requires that the server does this as well. + Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream. + Some protocols, such as HTTP, require this option to be ``False``. + See :meth:`~ssl.SSLContext.wrap_socket` for details. + :param tls_hostname: host name to check the server certificate against (defaults to + the value of ``remote_host``) + :param happy_eyeballs_delay: delay (in seconds) before starting the next connection + attempt + :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream + :raises ConnectionFailed: if the connection fails + + """ + # Placed here due to https://github.com/python/mypy/issues/7057 + connected_stream: SocketStream | None = None + + async def try_connect(remote_host: str, event: Event) -> None: + nonlocal connected_stream + try: + stream = await asynclib.connect_tcp(remote_host, remote_port, local_address) + except OSError as exc: + oserrors.append(exc) + return + else: + if connected_stream is None: + connected_stream = stream + tg.cancel_scope.cancel() + else: + await stream.aclose() + finally: + event.set() + + asynclib = get_async_backend() + local_address: IPSockAddrType | None = None + family = socket.AF_UNSPEC + if local_host: + gai_res = await getaddrinfo(str(local_host), None) + family, *_, local_address = gai_res[0] + + target_host = str(remote_host) + try: + addr_obj = ip_address(remote_host) + except ValueError: + addr_obj = None + + if addr_obj is not None: + if isinstance(addr_obj, IPv6Address): + target_addrs = [(socket.AF_INET6, addr_obj.compressed)] + else: + target_addrs = [(socket.AF_INET, addr_obj.compressed)] + else: + # getaddrinfo() will raise an exception if name resolution fails + gai_res = await getaddrinfo( + target_host, remote_port, family=family, type=socket.SOCK_STREAM + ) + + # Organize the list so that the first address is an IPv6 address (if available) + # and the second one is an IPv4 addresses. The rest can be in whatever order. + v6_found = v4_found = False + target_addrs = [] + for af, *_, sa in gai_res: + if af == socket.AF_INET6 and not v6_found: + v6_found = True + target_addrs.insert(0, (af, sa[0])) + elif af == socket.AF_INET and not v4_found and v6_found: + v4_found = True + target_addrs.insert(1, (af, sa[0])) + else: + target_addrs.append((af, sa[0])) + + oserrors: list[OSError] = [] + try: + async with create_task_group() as tg: + for _af, addr in target_addrs: + event = Event() + tg.start_soon(try_connect, addr, event) + with move_on_after(happy_eyeballs_delay): + await event.wait() + + if connected_stream is None: + cause = ( + oserrors[0] + if len(oserrors) == 1 + else ExceptionGroup("multiple connection attempts failed", oserrors) + ) + raise OSError("All connection attempts failed") from cause + finally: + oserrors.clear() + + if tls or tls_hostname or ssl_context: + try: + return await TLSStream.wrap( + connected_stream, + server_side=False, + hostname=tls_hostname or str(remote_host), + ssl_context=ssl_context, + standard_compatible=tls_standard_compatible, + ) + except BaseException: + await aclose_forcefully(connected_stream) + raise + + return connected_stream + + +async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream: + """ + Connect to the given UNIX socket. + + Not available on Windows. + + :param path: path to the socket + :return: a socket stream object + :raises ConnectionFailed: if the connection fails + + """ + path = os.fspath(path) + return await get_async_backend().connect_unix(path) + + +async def create_tcp_listener( + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC, + backlog: int = 65536, + reuse_port: bool = False, +) -> MultiListener[SocketStream]: + """ + Create a TCP socket listener. + + :param local_port: port number to listen on + :param local_host: IP address of the interface to listen on. If omitted, listen on + all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address + family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6. + :param family: address family (used if ``local_host`` was omitted) + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a multi-listener object containing one or more socket listeners + :raises OSError: if there's an error creating a socket, or binding to one or more + interfaces failed + + """ + asynclib = get_async_backend() + backlog = min(backlog, 65536) + local_host = str(local_host) if local_host is not None else None + + def setup_raw_socket( + fam: AddressFamily, + bind_addr: tuple[str, int] | tuple[str, int, int, int], + *, + v6only: bool = True, + ) -> socket.socket: + sock = socket.socket(fam) + try: + sock.setblocking(False) + + if fam == AddressFamily.AF_INET6: + sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only) + + # For Windows, enable exclusive address use. For others, enable address + # reuse. + if sys.platform == "win32": + sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) + else: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + if reuse_port: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + + # Workaround for #554 + if fam == socket.AF_INET6 and "%" in bind_addr[0]: + addr, scope_id = bind_addr[0].split("%", 1) + bind_addr = (addr, bind_addr[1], 0, int(scope_id)) + + sock.bind(bind_addr) + sock.listen(backlog) + except BaseException: + sock.close() + raise + + return sock + + # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug + # where we don't get the correct scope ID for IPv6 link-local addresses when passing + # type=socket.SOCK_STREAM to getaddrinfo(): + # https://github.com/MagicStack/uvloop/issues/539 + gai_res = await getaddrinfo( + local_host, + local_port, + family=family, + type=socket.SOCK_STREAM if sys.platform == "win32" else 0, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + + # The set comprehension is here to work around a glibc bug: + # https://sourceware.org/bugzilla/show_bug.cgi?id=14969 + sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM}) + + # Special case for dual-stack binding on the "any" interface + if ( + local_host is None + and family == AddressFamily.AF_UNSPEC + and socket.has_dualstack_ipv6() + and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res) + ): + raw_socket = setup_raw_socket( + AddressFamily.AF_INET6, ("::", local_port), v6only=False + ) + listener = asynclib.create_tcp_listener(raw_socket) + return MultiListener([listener]) + + errors: list[OSError] = [] + try: + for _ in range(len(sockaddrs)): + listeners: list[SocketListener] = [] + bound_ephemeral_port = local_port + try: + for fam, *_, sockaddr in sockaddrs: + sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:] + raw_socket = setup_raw_socket(fam, sockaddr) + + # Store the assigned port if an ephemeral port was requested, so + # we'll bind to the same port on all interfaces + if local_port == 0 and len(gai_res) > 1: + bound_ephemeral_port = raw_socket.getsockname()[1] + + listeners.append(asynclib.create_tcp_listener(raw_socket)) + except BaseException as exc: + for listener in listeners: + await listener.aclose() + + # If an ephemeral port was requested but binding the assigned port + # failed for another interface, rotate the address list and try again + if ( + isinstance(exc, OSError) + and exc.errno == errno.EADDRINUSE + and local_port == 0 + and bound_ephemeral_port + ): + errors.append(exc) + sockaddrs.append(sockaddrs.pop(0)) + continue + + raise + + return MultiListener(listeners) + + raise OSError( + f"Could not create {len(sockaddrs)} listeners with a consistent port" + ) from ExceptionGroup("Several bind attempts failed", errors) + finally: + del errors # Prevent reference cycles + + +async def create_unix_listener( + path: str | bytes | PathLike[Any], + *, + mode: int | None = None, + backlog: int = 65536, +) -> SocketListener: + """ + Create a UNIX socket listener. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param backlog: maximum number of queued incoming connections (up to a maximum of + 2**16, or 65536) + :return: a listener object + + .. versionchanged:: 3.0 + If a socket already exists on the file system in the given path, it will be + removed first. + + """ + backlog = min(backlog, 65536) + raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM) + try: + raw_socket.listen(backlog) + return get_async_backend().create_unix_listener(raw_socket) + except BaseException: + raw_socket.close() + raise + + +async def create_udp_socket( + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + *, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> UDPSocket: + """ + Create a UDP socket. + + If ``port`` has been given, the socket will be bound to this port on the local + machine, making this socket suitable for providing UDP based services. + + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a UDP socket + + """ + if family is AddressFamily.AF_UNSPEC and not local_host: + raise ValueError('Either "family" or "local_host" must be given') + + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + elif family is AddressFamily.AF_INET6: + local_address = ("::", 0) + else: + local_address = ("0.0.0.0", 0) + + sock = await get_async_backend().create_udp_socket( + family, local_address, None, reuse_port + ) + return cast(UDPSocket, sock) + + +async def create_connected_udp_socket( + remote_host: IPAddressType, + remote_port: int, + *, + family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, + local_host: IPAddressType | None = None, + local_port: int = 0, + reuse_port: bool = False, +) -> ConnectedUDPSocket: + """ + Create a connected UDP socket. + + Connected UDP sockets can only communicate with the specified remote host/port, an + any packets sent from other sources are dropped. + + :param remote_host: remote host to set as the default target + :param remote_port: port on the remote host to set as the default target + :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically + determined from ``local_host`` or ``remote_host`` if omitted + :param local_host: IP address or host name of the local interface to bind to + :param local_port: local port to bind to + :param reuse_port: ``True`` to allow multiple sockets to bind to the same + address/port (not supported on Windows) + :return: a connected UDP socket + + """ + local_address = None + if local_host: + gai_res = await getaddrinfo( + str(local_host), + local_port, + family=family, + type=socket.SOCK_DGRAM, + flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + local_address = gai_res[0][-1] + + gai_res = await getaddrinfo( + str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM + ) + family = cast(AnyIPAddressFamily, gai_res[0][0]) + remote_address = gai_res[0][-1] + + sock = await get_async_backend().create_udp_socket( + family, local_address, remote_address, reuse_port + ) + return cast(ConnectedUDPSocket, sock) + + +async def create_unix_datagram_socket( + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> UNIXDatagramSocket: + """ + Create a UNIX datagram socket. + + Not available on Windows. + + If ``local_path`` has been given, the socket will be bound to this path, making this + socket suitable for receiving datagrams from other processes. Other processes can + send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a UNIX datagram socket + + """ + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket(raw_socket, None) + + +async def create_connected_unix_datagram_socket( + remote_path: str | bytes | PathLike[Any], + *, + local_path: None | str | bytes | PathLike[Any] = None, + local_mode: int | None = None, +) -> ConnectedUNIXDatagramSocket: + """ + Create a connected UNIX datagram socket. + + Connected datagram sockets can only communicate with the specified remote path. + + If ``local_path`` has been given, the socket will be bound to this path, making + this socket suitable for receiving datagrams from other processes. Other processes + can send datagrams to this socket only if ``local_path`` is set. + + If a socket already exists on the file system in the ``local_path``, it will be + removed first. + + :param remote_path: the path to set as the default target + :param local_path: the path on which to bind to + :param local_mode: permissions to set on the local socket + :return: a connected UNIX datagram socket + + """ + remote_path = os.fspath(remote_path) + raw_socket = await setup_unix_local_socket( + local_path, local_mode, socket.SOCK_DGRAM + ) + return await get_async_backend().create_unix_datagram_socket( + raw_socket, remote_path + ) + + +async def getaddrinfo( + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, +) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]: + """ + Look up a numeric IP address given a host name. + + Internationalized domain names are translated according to the (non-transitional) + IDNA 2008 standard. + + .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of + (host, port), unlike what :func:`socket.getaddrinfo` does. + + :param host: host name + :param port: port number + :param family: socket family (`'AF_INET``, ...) + :param type: socket type (``SOCK_STREAM``, ...) + :param proto: protocol number + :param flags: flags to pass to upstream ``getaddrinfo()`` + :return: list of tuples containing (family, type, proto, canonname, sockaddr) + + .. seealso:: :func:`socket.getaddrinfo` + + """ + # Handle unicode hostnames + if isinstance(host, str): + try: + encoded_host: bytes | None = host.encode("ascii") + except UnicodeEncodeError: + import idna + + encoded_host = idna.encode(host, uts46=True) + else: + encoded_host = host + + gai_res = await get_async_backend().getaddrinfo( + encoded_host, port, family=family, type=type, proto=proto, flags=flags + ) + return [ + (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr)) + for family, type, proto, canonname, sockaddr in gai_res + # filter out IPv6 results when IPv6 is disabled + if not isinstance(sockaddr[0], int) + ] + + +def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]: + """ + Look up the host name of an IP address. + + :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4) + :param flags: flags to pass to upstream ``getnameinfo()`` + :return: a tuple of (host name, service name) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: :func:`socket.getnameinfo` + + """ + return get_async_backend().getnameinfo(sockaddr, flags) + + +@deprecated("This function is deprecated; use `wait_readable` instead") +def wait_socket_readable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_readable` instead. + + Wait until the given socket has data to be read. + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(sock.fileno()) + + +@deprecated("This function is deprecated; use `wait_writable` instead") +def wait_socket_writable(sock: socket.socket) -> Awaitable[None]: + """ + .. deprecated:: 4.7.0 + Use :func:`wait_writable` instead. + + Wait until the given socket can be written to. + + This does **NOT** work on Windows when using the asyncio backend with a proactor + event loop (default on py3.8+). + + .. warning:: Only use this on raw sockets that have not been wrapped by any higher + level constructs like socket streams! + + :param sock: a socket object + :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the + socket to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the socket + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_writable(sock.fileno()) + + +def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object has data to be read. + + On Unix systems, ``obj`` must either be an integer file descriptor, or else an + object with a ``.fileno()`` method which returns an integer file descriptor. Any + kind of file descriptor can be passed, though the exact semantics will depend on + your kernel. For example, this probably won't do anything useful for on-disk files. + + On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an + object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File + descriptors aren't supported, and neither are handles that refer to anything besides + a ``SOCKET``. + + On backends where this functionality is not natively provided (asyncio + ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread + which is set to shut down when the interpreter shuts down. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become readable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become readable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().wait_readable(obj) + + +def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]: + """ + Wait until the given object can be written to. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the + object to become writable + :raises ~anyio.BusyResourceError: if another task is already waiting for the object + to become writable + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. seealso:: See the documentation of :func:`wait_readable` for the definition of + ``obj`` and notes on backend compatibility. + + .. warning:: Don't use this on raw sockets that have been wrapped by any higher + level constructs like socket streams! + + """ + return get_async_backend().wait_writable(obj) + + +def notify_closing(obj: FileDescriptorLike) -> None: + """ + Call this before closing a file descriptor (on Unix) or socket (on + Windows). This will cause any `wait_readable` or `wait_writable` + calls on the given object to immediately wake up and raise + `~anyio.ClosedResourceError`. + + This doesn't actually close the object – you still have to do that + yourself afterwards. Also, you want to be careful to make sure no + new tasks start waiting on the object in between when you call this + and when it's actually closed. So to close something properly, you + usually want to do these steps in order: + + 1. Explicitly mark the object as closed, so that any new attempts + to use it will abort before they start. + 2. Call `notify_closing` to wake up any already-existing users. + 3. Actually close the object. + + It's also possible to do them in a different order if that's more + convenient, *but only if* you make sure not to have any checkpoints in + between the steps. This way they all happen in a single atomic + step, so other tasks won't be able to tell what order they happened + in anyway. + + :param obj: an object with a ``.fileno()`` method or an integer handle + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + get_async_backend().notify_closing(obj) + + +# +# Private API +# + + +def convert_ipv6_sockaddr( + sockaddr: tuple[str, int, int, int] | tuple[str, int], +) -> tuple[str, int]: + """ + Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format. + + If the scope ID is nonzero, it is added to the address, separated with ``%``. + Otherwise the flow id and scope id are simply cut off from the tuple. + Any other kinds of socket addresses are returned as-is. + + :param sockaddr: the result of :meth:`~socket.socket.getsockname` + :return: the converted socket address + + """ + # This is more complicated than it should be because of MyPy + if isinstance(sockaddr, tuple) and len(sockaddr) == 4: + host, port, flowinfo, scope_id = sockaddr + if scope_id: + # PyPy (as of v7.3.11) leaves the interface name in the result, so + # we discard it and only get the scope ID from the end + # (https://foss.heptapod.net/pypy/pypy/-/issues/3938) + host = host.split("%")[0] + + # Add scope_id to the address + return f"{host}%{scope_id}", port + else: + return host, port + else: + return sockaddr + + +async def setup_unix_local_socket( + path: None | str | bytes | PathLike[Any], + mode: int | None, + socktype: int, +) -> socket.socket: + """ + Create a UNIX local socket object, deleting the socket at the given path if it + exists. + + Not available on Windows. + + :param path: path of the socket + :param mode: permissions to set on the socket + :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM + + """ + path_str: str | None + if path is not None: + path_str = os.fsdecode(path) + + # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call + if not path_str.startswith("\0"): + # Copied from pathlib... + try: + stat_result = os.stat(path) + except OSError as e: + if e.errno not in ( + errno.ENOENT, + errno.ENOTDIR, + errno.EBADF, + errno.ELOOP, + ): + raise + else: + if stat.S_ISSOCK(stat_result.st_mode): + os.unlink(path) + else: + path_str = None + + raw_socket = socket.socket(socket.AF_UNIX, socktype) + raw_socket.setblocking(False) + + if path_str is not None: + try: + await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True) + if mode is not None: + await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True) + except BaseException: + raw_socket.close() + raise + + return raw_socket + + +@dataclass +class TCPConnectable(ByteStreamConnectable): + """ + Connects to a TCP server at the given host and port. + + :param host: host name or IP address of the server + :param port: TCP port number of the server + """ + + host: str | IPv4Address | IPv6Address + port: int + + def __post_init__(self) -> None: + if self.port < 1 or self.port > 65535: + raise ValueError("TCP port number out of range") + + @override + async def connect(self) -> SocketStream: + try: + return await connect_tcp(self.host, self.port) + except OSError as exc: + raise ConnectionFailed( + f"error connecting to {self.host}:{self.port}: {exc}" + ) from exc + + +@dataclass +class UNIXConnectable(ByteStreamConnectable): + """ + Connects to a UNIX domain socket at the given path. + + :param path: the file system path of the socket + """ + + path: str | bytes | PathLike[str] | PathLike[bytes] + + @override + async def connect(self) -> UNIXSocketStream: + try: + return await connect_unix(self.path) + except OSError as exc: + raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc + + +def as_connectable( + remote: ByteStreamConnectable + | tuple[str | IPv4Address | IPv6Address, int] + | str + | bytes + | PathLike[str], + /, + *, + tls: bool = False, + ssl_context: ssl.SSLContext | None = None, + tls_hostname: str | None = None, + tls_standard_compatible: bool = True, +) -> ByteStreamConnectable: + """ + Return a byte stream connectable from the given object. + + If a bytestream connectable is given, it is returned unchanged. + If a tuple of (host, port) is given, a TCP connectable is returned. + If a string or bytes path is given, a UNIX connectable is returned. + + If ``tls=True``, the connectable will be wrapped in a + :class:`~.streams.tls.TLSConnectable`. + + :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket + :param tls: if ``True``, wrap the plaintext connectable in a + :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings) + :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided, + a secure default will be created) + :param tls_hostname: if ``tls=True``, host name of the server to use for checking + the server certificate (defaults to the host portion of the address for TCP + connectables) + :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream + skip the closing handshake when closing the connection, so it won't raise an + exception if the server does the same + + """ + connectable: TCPConnectable | UNIXConnectable | TLSConnectable + if isinstance(remote, ByteStreamConnectable): + return remote + elif isinstance(remote, tuple) and len(remote) == 2: + connectable = TCPConnectable(*remote) + elif isinstance(remote, (str, bytes, PathLike)): + connectable = UNIXConnectable(remote) + else: + raise TypeError(f"cannot convert {remote!r} to a connectable") + + if tls: + if not tls_hostname and isinstance(connectable, TCPConnectable): + tls_hostname = str(connectable.host) + + connectable = TLSConnectable( + connectable, + ssl_context=ssl_context, + hostname=tls_hostname, + standard_compatible=tls_standard_compatible, + ) + + return connectable diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_streams.py b/venv/lib/python3.12/site-packages/anyio/_core/_streams.py new file mode 100644 index 0000000..2b9c7df --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_streams.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import math +from typing import TypeVar +from warnings import warn + +from ..streams.memory import ( + MemoryObjectReceiveStream, + MemoryObjectSendStream, + _MemoryObjectStreamState, +) + +T_Item = TypeVar("T_Item") + + +class create_memory_object_stream( + tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]], +): + """ + Create a memory object stream. + + The stream's item type can be annotated like + :func:`create_memory_object_stream[T_Item]`. + + :param max_buffer_size: number of items held in the buffer until ``send()`` starts + blocking + :param item_type: old way of marking the streams with the right generic type for + static typing (does nothing on AnyIO 4) + + .. deprecated:: 4.0 + Use ``create_memory_object_stream[YourItemType](...)`` instead. + :return: a tuple of (send stream, receive stream) + + """ + + def __new__( # type: ignore[misc] + cls, max_buffer_size: float = 0, item_type: object = None + ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]: + if max_buffer_size != math.inf and not isinstance(max_buffer_size, int): + raise ValueError("max_buffer_size must be either an integer or math.inf") + if max_buffer_size < 0: + raise ValueError("max_buffer_size cannot be negative") + if item_type is not None: + warn( + "The item_type argument has been deprecated in AnyIO 4.0. " + "Use create_memory_object_stream[YourItemType](...) instead.", + DeprecationWarning, + stacklevel=2, + ) + + state = _MemoryObjectStreamState[T_Item](max_buffer_size) + return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state)) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py b/venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py new file mode 100644 index 0000000..36d9b30 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_subprocesses.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +import sys +from collections.abc import AsyncIterable, Iterable, Mapping, Sequence +from io import BytesIO +from os import PathLike +from subprocess import PIPE, CalledProcessError, CompletedProcess +from typing import IO, Any, Union, cast + +from ..abc import Process +from ._eventloop import get_async_backend +from ._tasks import create_task_group + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"] + + +async def run_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + input: bytes | None = None, + stdin: int | IO[Any] | None = None, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + check: bool = True, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> CompletedProcess[bytes]: + """ + Run an external command in a subprocess and wait until it completes. + + .. seealso:: :func:`subprocess.run` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param input: bytes passed to the standard input of the subprocess + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None`; ``input`` overrides this + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or `None` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or `None` + :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the + process terminates with a return code other than 0 + :param cwd: If not ``None``, change the working directory to this before running the + command + :param env: if not ``None``, this mapping replaces the inherited environment + variables from the parent process + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (Python >= 3.9, POSIX only) + :param group: effective group to run the process as (Python >= 3.9, POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9, + POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (Python >= 3.9, POSIX only) + :return: an object representing the completed process + :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process + exits with a nonzero return code + + """ + + async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: + buffer = BytesIO() + async for chunk in stream: + buffer.write(chunk) + + stream_contents[index] = buffer.getvalue() + + if stdin is not None and input is not None: + raise ValueError("only one of stdin and input is allowed") + + async with await open_process( + command, + stdin=PIPE if input else stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + user=user, + group=group, + extra_groups=extra_groups, + umask=umask, + ) as process: + stream_contents: list[bytes | None] = [None, None] + async with create_task_group() as tg: + if process.stdout: + tg.start_soon(drain_stream, process.stdout, 0) + + if process.stderr: + tg.start_soon(drain_stream, process.stderr, 1) + + if process.stdin and input: + await process.stdin.send(input) + await process.stdin.aclose() + + await process.wait() + + output, errors = stream_contents + if check and process.returncode != 0: + raise CalledProcessError(cast(int, process.returncode), command, output, errors) + + return CompletedProcess(command, cast(int, process.returncode), output, errors) + + +async def open_process( + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None = PIPE, + stdout: int | IO[Any] | None = PIPE, + stderr: int | IO[Any] | None = PIPE, + cwd: StrOrBytesPath | None = None, + env: Mapping[str, str] | None = None, + startupinfo: Any = None, + creationflags: int = 0, + start_new_session: bool = False, + pass_fds: Sequence[int] = (), + user: str | int | None = None, + group: str | int | None = None, + extra_groups: Iterable[str | int] | None = None, + umask: int = -1, +) -> Process: + """ + Start an external command in a subprocess. + + .. seealso:: :class:`subprocess.Popen` + + :param command: either a string to pass to the shell, or an iterable of strings + containing the executable name or path and its arguments + :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a + file-like object, or ``None`` + :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + a file-like object, or ``None`` + :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, + :data:`subprocess.STDOUT`, a file-like object, or ``None`` + :param cwd: If not ``None``, the working directory is changed before executing + :param env: If env is not ``None``, it must be a mapping that defines the + environment variables for the new process + :param creationflags: flags that can be used to control the creation of the + subprocess (see :class:`subprocess.Popen` for the specifics) + :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used + to specify process startup parameters (Windows only) + :param start_new_session: if ``true`` the setsid() system call will be made in the + child process prior to the execution of the subprocess. (POSIX only) + :param pass_fds: sequence of file descriptors to keep open between the parent and + child processes. (POSIX only) + :param user: effective user to run the process as (POSIX only) + :param group: effective group to run the process as (POSIX only) + :param extra_groups: supplementary groups to set in the subprocess (POSIX only) + :param umask: if not negative, this umask is applied in the child process before + running the given command (POSIX only) + :return: an asynchronous process object + + """ + kwargs: dict[str, Any] = {} + if user is not None: + kwargs["user"] = user + + if group is not None: + kwargs["group"] = group + + if extra_groups is not None: + kwargs["extra_groups"] = group + + if umask >= 0: + kwargs["umask"] = umask + + return await get_async_backend().open_process( + command, + stdin=stdin, + stdout=stdout, + stderr=stderr, + cwd=cwd, + env=env, + startupinfo=startupinfo, + creationflags=creationflags, + start_new_session=start_new_session, + pass_fds=pass_fds, + **kwargs, + ) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py b/venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py new file mode 100644 index 0000000..c0ef27a --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_synchronization.py @@ -0,0 +1,753 @@ +from __future__ import annotations + +import math +from collections import deque +from collections.abc import Callable +from dataclasses import dataclass +from types import TracebackType +from typing import TypeVar + +from ..lowlevel import checkpoint_if_cancelled +from ._eventloop import get_async_backend +from ._exceptions import BusyResourceError, NoEventLoopError +from ._tasks import CancelScope +from ._testing import TaskInfo, get_current_task + +T = TypeVar("T") + + +@dataclass(frozen=True) +class EventStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait` + """ + + tasks_waiting: int + + +@dataclass(frozen=True) +class CapacityLimiterStatistics: + """ + :ivar int borrowed_tokens: number of tokens currently borrowed by tasks + :ivar float total_tokens: total number of available tokens + :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from + this limiter + :ivar int tasks_waiting: number of tasks waiting on + :meth:`~.CapacityLimiter.acquire` or + :meth:`~.CapacityLimiter.acquire_on_behalf_of` + """ + + borrowed_tokens: int + total_tokens: float + borrowers: tuple[object, ...] + tasks_waiting: int + + +@dataclass(frozen=True) +class LockStatistics: + """ + :ivar bool locked: flag indicating if this lock is locked or not + :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the + lock is not held by any task) + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire` + """ + + locked: bool + owner: TaskInfo | None + tasks_waiting: int + + +@dataclass(frozen=True) +class ConditionStatistics: + """ + :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait` + :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying + :class:`~.Lock` + """ + + tasks_waiting: int + lock_statistics: LockStatistics + + +@dataclass(frozen=True) +class SemaphoreStatistics: + """ + :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire` + + """ + + tasks_waiting: int + + +class Event: + def __new__(cls) -> Event: + try: + return get_async_backend().create_event() + except NoEventLoopError: + return EventAdapter() + + def set(self) -> None: + """Set the flag, notifying all listeners.""" + raise NotImplementedError + + def is_set(self) -> bool: + """Return ``True`` if the flag is set, ``False`` if not.""" + raise NotImplementedError + + async def wait(self) -> None: + """ + Wait until the flag has been set. + + If the flag has already been set when this method is called, it returns + immediately. + + """ + raise NotImplementedError + + def statistics(self) -> EventStatistics: + """Return statistics about the current state of this event.""" + raise NotImplementedError + + +class EventAdapter(Event): + _internal_event: Event | None = None + _is_set: bool = False + + def __new__(cls) -> EventAdapter: + return object.__new__(cls) + + @property + def _event(self) -> Event: + if self._internal_event is None: + self._internal_event = get_async_backend().create_event() + if self._is_set: + self._internal_event.set() + + return self._internal_event + + def set(self) -> None: + if self._internal_event is None: + self._is_set = True + else: + self._event.set() + + def is_set(self) -> bool: + if self._internal_event is None: + return self._is_set + + return self._internal_event.is_set() + + async def wait(self) -> None: + await self._event.wait() + + def statistics(self) -> EventStatistics: + if self._internal_event is None: + return EventStatistics(tasks_waiting=0) + + return self._internal_event.statistics() + + +class Lock: + def __new__(cls, *, fast_acquire: bool = False) -> Lock: + try: + return get_async_backend().create_lock(fast_acquire=fast_acquire) + except NoEventLoopError: + return LockAdapter(fast_acquire=fast_acquire) + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Release the lock.""" + raise NotImplementedError + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + raise NotImplementedError + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class LockAdapter(Lock): + _internal_lock: Lock | None = None + + def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter: + return object.__new__(cls) + + def __init__(self, *, fast_acquire: bool = False): + self._fast_acquire = fast_acquire + + @property + def _lock(self) -> Lock: + if self._internal_lock is None: + self._internal_lock = get_async_backend().create_lock( + fast_acquire=self._fast_acquire + ) + + return self._internal_lock + + async def __aenter__(self) -> None: + await self._lock.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + if self._internal_lock is not None: + self._internal_lock.release() + + async def acquire(self) -> None: + """Acquire the lock.""" + await self._lock.acquire() + + def acquire_nowait(self) -> None: + """ + Acquire the lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + + def release(self) -> None: + """Release the lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is currently held.""" + return self._lock.locked() + + def statistics(self) -> LockStatistics: + """ + Return statistics about the current state of this lock. + + .. versionadded:: 3.0 + + """ + if self._internal_lock is None: + return LockStatistics(False, None, 0) + + return self._internal_lock.statistics() + + +class Condition: + _owner_task: TaskInfo | None = None + + def __init__(self, lock: Lock | None = None): + self._lock = lock or Lock() + self._waiters: deque[Event] = deque() + + async def __aenter__(self) -> None: + await self.acquire() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + def _check_acquired(self) -> None: + if self._owner_task != get_current_task(): + raise RuntimeError("The current task is not holding the underlying lock") + + async def acquire(self) -> None: + """Acquire the underlying lock.""" + await self._lock.acquire() + self._owner_task = get_current_task() + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + self._lock.acquire_nowait() + self._owner_task = get_current_task() + + def release(self) -> None: + """Release the underlying lock.""" + self._lock.release() + + def locked(self) -> bool: + """Return True if the lock is set.""" + return self._lock.locked() + + def notify(self, n: int = 1) -> None: + """Notify exactly n listeners.""" + self._check_acquired() + for _ in range(n): + try: + event = self._waiters.popleft() + except IndexError: + break + + event.set() + + def notify_all(self) -> None: + """Notify all the listeners.""" + self._check_acquired() + for event in self._waiters: + event.set() + + self._waiters.clear() + + async def wait(self) -> None: + """Wait for a notification.""" + await checkpoint_if_cancelled() + self._check_acquired() + event = Event() + self._waiters.append(event) + self.release() + try: + await event.wait() + except BaseException: + if not event.is_set(): + self._waiters.remove(event) + + raise + finally: + with CancelScope(shield=True): + await self.acquire() + + async def wait_for(self, predicate: Callable[[], T]) -> T: + """ + Wait until a predicate becomes true. + + :param predicate: a callable that returns a truthy value when the condition is + met + :return: the result of the predicate + + .. versionadded:: 4.11.0 + + """ + while not (result := predicate()): + await self.wait() + + return result + + def statistics(self) -> ConditionStatistics: + """ + Return statistics about the current state of this condition. + + .. versionadded:: 3.0 + """ + return ConditionStatistics(len(self._waiters), self._lock.statistics()) + + +class Semaphore: + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + try: + return get_async_backend().create_semaphore( + initial_value, max_value=max_value, fast_acquire=fast_acquire + ) + except NoEventLoopError: + return SemaphoreAdapter(initial_value, max_value=max_value) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ): + if not isinstance(initial_value, int): + raise TypeError("initial_value must be an integer") + if initial_value < 0: + raise ValueError("initial_value must be >= 0") + if max_value is not None: + if not isinstance(max_value, int): + raise TypeError("max_value must be an integer or None") + if max_value < initial_value: + raise ValueError( + "max_value must be equal to or higher than initial_value" + ) + + self._fast_acquire = fast_acquire + + async def __aenter__(self) -> Semaphore: + await self.acquire() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.release() + + async def acquire(self) -> None: + """Decrement the semaphore value, blocking if necessary.""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire the underlying lock, without blocking. + + :raises ~anyio.WouldBlock: if the operation would block + + """ + raise NotImplementedError + + def release(self) -> None: + """Increment the semaphore value.""" + raise NotImplementedError + + @property + def value(self) -> int: + """The current value of the semaphore.""" + raise NotImplementedError + + @property + def max_value(self) -> int | None: + """The maximum value of the semaphore.""" + raise NotImplementedError + + def statistics(self) -> SemaphoreStatistics: + """ + Return statistics about the current state of this semaphore. + + .. versionadded:: 3.0 + """ + raise NotImplementedError + + +class SemaphoreAdapter(Semaphore): + _internal_semaphore: Semaphore | None = None + + def __new__( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> SemaphoreAdapter: + return object.__new__(cls) + + def __init__( + self, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> None: + super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) + self._initial_value = initial_value + self._max_value = max_value + + @property + def _semaphore(self) -> Semaphore: + if self._internal_semaphore is None: + self._internal_semaphore = get_async_backend().create_semaphore( + self._initial_value, max_value=self._max_value + ) + + return self._internal_semaphore + + async def acquire(self) -> None: + await self._semaphore.acquire() + + def acquire_nowait(self) -> None: + self._semaphore.acquire_nowait() + + def release(self) -> None: + self._semaphore.release() + + @property + def value(self) -> int: + if self._internal_semaphore is None: + return self._initial_value + + return self._semaphore.value + + @property + def max_value(self) -> int | None: + return self._max_value + + def statistics(self) -> SemaphoreStatistics: + if self._internal_semaphore is None: + return SemaphoreStatistics(tasks_waiting=0) + + return self._semaphore.statistics() + + +class CapacityLimiter: + def __new__(cls, total_tokens: float) -> CapacityLimiter: + try: + return get_async_backend().create_capacity_limiter(total_tokens) + except NoEventLoopError: + return CapacityLimiterAdapter(total_tokens) + + async def __aenter__(self) -> None: + raise NotImplementedError + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + raise NotImplementedError + + @property + def total_tokens(self) -> float: + """ + The total number of tokens available for borrowing. + + This is a read-write property. If the total number of tokens is increased, the + proportionate number of tasks waiting on this limiter will be granted their + tokens. + + .. versionchanged:: 3.0 + The property is now writable. + .. versionchanged:: 4.12 + The value can now be set to 0. + + """ + raise NotImplementedError + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + raise NotImplementedError + + @property + def borrowed_tokens(self) -> int: + """The number of tokens that have currently been borrowed.""" + raise NotImplementedError + + @property + def available_tokens(self) -> float: + """The number of tokens currently available to be borrowed""" + raise NotImplementedError + + def acquire_nowait(self) -> None: + """ + Acquire a token for the current task without waiting for one to become + available. + + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + """ + Acquire a token without waiting for one to become available. + + :param borrower: the entity borrowing a token + :raises ~anyio.WouldBlock: if there are no tokens available for borrowing + + """ + raise NotImplementedError + + async def acquire(self) -> None: + """ + Acquire a token for the current task, waiting if necessary for one to become + available. + + """ + raise NotImplementedError + + async def acquire_on_behalf_of(self, borrower: object) -> None: + """ + Acquire a token, waiting if necessary for one to become available. + + :param borrower: the entity borrowing a token + + """ + raise NotImplementedError + + def release(self) -> None: + """ + Release the token held by the current task. + + :raises RuntimeError: if the current task has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def release_on_behalf_of(self, borrower: object) -> None: + """ + Release the token held by the given borrower. + + :raises RuntimeError: if the borrower has not borrowed a token from this + limiter. + + """ + raise NotImplementedError + + def statistics(self) -> CapacityLimiterStatistics: + """ + Return statistics about the current state of this limiter. + + .. versionadded:: 3.0 + + """ + raise NotImplementedError + + +class CapacityLimiterAdapter(CapacityLimiter): + _internal_limiter: CapacityLimiter | None = None + + def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter: + return object.__new__(cls) + + def __init__(self, total_tokens: float) -> None: + self.total_tokens = total_tokens + + @property + def _limiter(self) -> CapacityLimiter: + if self._internal_limiter is None: + self._internal_limiter = get_async_backend().create_capacity_limiter( + self._total_tokens + ) + + return self._internal_limiter + + async def __aenter__(self) -> None: + await self._limiter.__aenter__() + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + return await self._limiter.__aexit__(exc_type, exc_val, exc_tb) + + @property + def total_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.total_tokens + + @total_tokens.setter + def total_tokens(self, value: float) -> None: + if not isinstance(value, int) and value is not math.inf: + raise TypeError("total_tokens must be an int or math.inf") + elif value < 1: + raise ValueError("total_tokens must be >= 1") + + if self._internal_limiter is None: + self._total_tokens = value + return + + self._limiter.total_tokens = value + + @property + def borrowed_tokens(self) -> int: + if self._internal_limiter is None: + return 0 + + return self._internal_limiter.borrowed_tokens + + @property + def available_tokens(self) -> float: + if self._internal_limiter is None: + return self._total_tokens + + return self._internal_limiter.available_tokens + + def acquire_nowait(self) -> None: + self._limiter.acquire_nowait() + + def acquire_on_behalf_of_nowait(self, borrower: object) -> None: + self._limiter.acquire_on_behalf_of_nowait(borrower) + + async def acquire(self) -> None: + await self._limiter.acquire() + + async def acquire_on_behalf_of(self, borrower: object) -> None: + await self._limiter.acquire_on_behalf_of(borrower) + + def release(self) -> None: + self._limiter.release() + + def release_on_behalf_of(self, borrower: object) -> None: + self._limiter.release_on_behalf_of(borrower) + + def statistics(self) -> CapacityLimiterStatistics: + if self._internal_limiter is None: + return CapacityLimiterStatistics( + borrowed_tokens=0, + total_tokens=self.total_tokens, + borrowers=(), + tasks_waiting=0, + ) + + return self._internal_limiter.statistics() + + +class ResourceGuard: + """ + A context manager for ensuring that a resource is only used by a single task at a + time. + + Entering this context manager while the previous has not exited it yet will trigger + :exc:`BusyResourceError`. + + :param action: the action to guard against (visible in the :exc:`BusyResourceError` + when triggered, e.g. "Another task is already {action} this resource") + + .. versionadded:: 4.1 + """ + + __slots__ = "action", "_guarded" + + def __init__(self, action: str = "using"): + self.action: str = action + self._guarded = False + + def __enter__(self) -> None: + if self._guarded: + raise BusyResourceError(self.action) + + self._guarded = True + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._guarded = False diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_tasks.py b/venv/lib/python3.12/site-packages/anyio/_core/_tasks.py new file mode 100644 index 0000000..0688bfe --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_tasks.py @@ -0,0 +1,173 @@ +from __future__ import annotations + +import math +from collections.abc import Generator +from contextlib import contextmanager +from types import TracebackType + +from ..abc._tasks import TaskGroup, TaskStatus +from ._eventloop import get_async_backend + + +class _IgnoredTaskStatus(TaskStatus[object]): + def started(self, value: object = None) -> None: + pass + + +TASK_STATUS_IGNORED = _IgnoredTaskStatus() + + +class CancelScope: + """ + Wraps a unit of work that can be made separately cancellable. + + :param deadline: The time (clock value) when this scope is cancelled automatically + :param shield: ``True`` to shield the cancel scope from external cancellation + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + def __new__( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline) + + def cancel(self, reason: str | None = None) -> None: + """ + Cancel this scope immediately. + + :param reason: a message describing the reason for the cancellation + + """ + raise NotImplementedError + + @property + def deadline(self) -> float: + """ + The time (clock value) when this scope is cancelled automatically. + + Will be ``float('inf')`` if no timeout has been set. + + """ + raise NotImplementedError + + @deadline.setter + def deadline(self, value: float) -> None: + raise NotImplementedError + + @property + def cancel_called(self) -> bool: + """``True`` if :meth:`cancel` has been called.""" + raise NotImplementedError + + @property + def cancelled_caught(self) -> bool: + """ + ``True`` if this scope suppressed a cancellation exception it itself raised. + + This is typically used to check if any work was interrupted, or to see if the + scope was cancelled due to its deadline being reached. The value will, however, + only be ``True`` if the cancellation was triggered by the scope itself (and not + an outer scope). + + """ + raise NotImplementedError + + @property + def shield(self) -> bool: + """ + ``True`` if this scope is shielded from external cancellation. + + While a scope is shielded, it will not receive cancellations from outside. + + """ + raise NotImplementedError + + @shield.setter + def shield(self, value: bool) -> None: + raise NotImplementedError + + def __enter__(self) -> CancelScope: + raise NotImplementedError + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + raise NotImplementedError + + +@contextmanager +def fail_after( + delay: float | None, shield: bool = False +) -> Generator[CancelScope, None, None]: + """ + Create a context manager which raises a :class:`TimeoutError` if does not finish in + time. + + :param delay: maximum allowed time (in seconds) before raising the exception, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a context manager that yields a cancel scope + :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + current_time = get_async_backend().current_time + deadline = (current_time() + delay) if delay is not None else math.inf + with get_async_backend().create_cancel_scope( + deadline=deadline, shield=shield + ) as cancel_scope: + yield cancel_scope + + if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline: + raise TimeoutError + + +def move_on_after(delay: float | None, shield: bool = False) -> CancelScope: + """ + Create a cancel scope with a deadline that expires after the given delay. + + :param delay: maximum allowed time (in seconds) before exiting the context block, or + ``None`` to disable the timeout + :param shield: ``True`` to shield the cancel scope from external cancellation + :return: a cancel scope + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + deadline = ( + (get_async_backend().current_time() + delay) if delay is not None else math.inf + ) + return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield) + + +def current_effective_deadline() -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the current + task. + + :return: a clock value from the event loop's internal clock (or ``float('inf')`` if + there is no deadline in effect, or ``float('-inf')`` if the current scope has + been cancelled) + :rtype: float + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_effective_deadline() + + +def create_task_group() -> TaskGroup: + """ + Create a task group. + + :return: a task group + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().create_task_group() diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py b/venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py new file mode 100644 index 0000000..fbb6b14 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_tempfile.py @@ -0,0 +1,616 @@ +from __future__ import annotations + +import os +import sys +import tempfile +from collections.abc import Iterable +from io import BytesIO, TextIOWrapper +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AnyStr, + Generic, + overload, +) + +from .. import to_thread +from .._core._fileio import AsyncFile +from ..lowlevel import checkpoint_if_cancelled + +if TYPE_CHECKING: + from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer + + +class TemporaryFile(Generic[AnyStr]): + """ + An asynchronous temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager interface to a temporary file. + The file is created using Python's standard `tempfile.TemporaryFile` function in a + background thread, and is wrapped as an asynchronous file using `AsyncFile`. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: TemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: TemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + mode: OpenTextMode | OpenBinaryMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self.mode = mode + self.buffering = buffering + self.encoding = encoding + self.newline = newline + self.suffix: str | None = suffix + self.prefix: str | None = prefix + self.dir: str | None = dir + self.errors = errors + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile( + self.mode, + self.buffering, + self.encoding, + self.newline, + self.suffix, + self.prefix, + self.dir, + errors=self.errors, + ) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class NamedTemporaryFile(Generic[AnyStr]): + """ + An asynchronous named temporary file that is automatically created and cleaned up. + + This class provides an asynchronous context manager for a temporary file with a + visible name in the file system. It uses Python's standard + :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with + :class:`AsyncFile` for asynchronous operations. + + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file. Only applicable in + text mode. + :param newline: Controls how universal newlines mode works (only applicable in text + mode). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param delete: Whether to delete the file when it is closed. + :param errors: The error handling scheme used for encoding/decoding errors. + :param delete_on_close: (Python 3.12+) Whether to delete the file on close. + """ + + _async_file: AsyncFile[AnyStr] + + @overload + def __init__( + self: NamedTemporaryFile[bytes], + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + @overload + def __init__( + self: NamedTemporaryFile[str], + mode: OpenTextMode, + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + delete: bool = ..., + *, + errors: str | None = ..., + delete_on_close: bool = ..., + ): ... + + def __init__( + self, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + delete: bool = True, + *, + errors: str | None = None, + delete_on_close: bool = True, + ) -> None: + self._params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "delete": delete, + "errors": errors, + } + if sys.version_info >= (3, 12): + self._params["delete_on_close"] = delete_on_close + + async def __aenter__(self) -> AsyncFile[AnyStr]: + fp = await to_thread.run_sync( + lambda: tempfile.NamedTemporaryFile(**self._params) + ) + self._async_file = AsyncFile(fp) + return self._async_file + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self._async_file.aclose() + + +class SpooledTemporaryFile(AsyncFile[AnyStr]): + """ + An asynchronous spooled temporary file that starts in memory and is spooled to disk. + + This class provides an asynchronous interface to a spooled temporary file, much like + Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous + write operations and provides a method to force a rollover to disk. + + :param max_size: Maximum size in bytes before the file is rolled over to disk. + :param mode: The mode in which the file is opened. Defaults to "w+b". + :param buffering: The buffering policy (-1 means the default buffering). + :param encoding: The encoding used to decode or encode the file (text mode only). + :param newline: Controls how universal newlines mode works (text mode only). + :param suffix: The suffix for the temporary file name. + :param prefix: The prefix for the temporary file name. + :param dir: The directory in which the temporary file is created. + :param errors: The error handling scheme used for encoding/decoding errors. + """ + + _rolled: bool = False + + @overload + def __init__( + self: SpooledTemporaryFile[bytes], + max_size: int = ..., + mode: OpenBinaryMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + @overload + def __init__( + self: SpooledTemporaryFile[str], + max_size: int = ..., + mode: OpenTextMode = ..., + buffering: int = ..., + encoding: str | None = ..., + newline: str | None = ..., + suffix: str | None = ..., + prefix: str | None = ..., + dir: str | None = ..., + *, + errors: str | None = ..., + ): ... + + def __init__( + self, + max_size: int = 0, + mode: OpenBinaryMode | OpenTextMode = "w+b", + buffering: int = -1, + encoding: str | None = None, + newline: str | None = None, + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + *, + errors: str | None = None, + ) -> None: + self._tempfile_params: dict[str, Any] = { + "mode": mode, + "buffering": buffering, + "encoding": encoding, + "newline": newline, + "suffix": suffix, + "prefix": prefix, + "dir": dir, + "errors": errors, + } + self._max_size = max_size + if "b" in mode: + super().__init__(BytesIO()) # type: ignore[arg-type] + else: + super().__init__( + TextIOWrapper( # type: ignore[arg-type] + BytesIO(), + encoding=encoding, + errors=errors, + newline=newline, + write_through=True, + ) + ) + + async def aclose(self) -> None: + if not self._rolled: + self._fp.close() + return + + await super().aclose() + + async def _check(self) -> None: + if self._rolled or self._fp.tell() <= self._max_size: + return + + await self.rollover() + + async def rollover(self) -> None: + if self._rolled: + return + + self._rolled = True + buffer = self._fp + buffer.seek(0) + self._fp = await to_thread.run_sync( + lambda: tempfile.TemporaryFile(**self._tempfile_params) + ) + await self.write(buffer.read()) + buffer.close() + + @property + def closed(self) -> bool: + return self._fp.closed + + async def read(self, size: int = -1) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read(size) + + return await super().read(size) # type: ignore[return-value] + + async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.read1(size) + + return await super().read1(size) + + async def readline(self) -> AnyStr: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readline() + + return await super().readline() # type: ignore[return-value] + + async def readlines(self) -> list[AnyStr]: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.readlines() + + return await super().readlines() # type: ignore[return-value] + + async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + self._fp.readinto(b) + + return await super().readinto(b) + + async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + self._fp.readinto(b) + + return await super().readinto1(b) + + async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.seek(offset, whence) + + return await super().seek(offset, whence) + + async def tell(self) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.tell() + + return await super().tell() + + async def truncate(self, size: int | None = None) -> int: + if not self._rolled: + await checkpoint_if_cancelled() + return self._fp.truncate(size) + + return await super().truncate(size) + + @overload + async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ... + @overload + async def write(self: SpooledTemporaryFile[str], b: str) -> int: ... + + async def write(self, b: ReadableBuffer | str) -> int: + """ + Asynchronously write data to the spooled temporary file. + + If the file has not yet been rolled over, the data is written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param s: The data to write. + :return: The number of bytes written. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.write(b) + await self._check() + return result + + return await super().write(b) # type: ignore[misc] + + @overload + async def writelines( + self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer] + ) -> None: ... + @overload + async def writelines( + self: SpooledTemporaryFile[str], lines: Iterable[str] + ) -> None: ... + + async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None: + """ + Asynchronously write a list of lines to the spooled temporary file. + + If the file has not yet been rolled over, the lines are written synchronously, + and a rollover is triggered if the size exceeds the maximum size. + + :param lines: An iterable of lines to write. + :raises RuntimeError: If the underlying file is not initialized. + + """ + if not self._rolled: + await checkpoint_if_cancelled() + result = self._fp.writelines(lines) + await self._check() + return result + + return await super().writelines(lines) # type: ignore[misc] + + +class TemporaryDirectory(Generic[AnyStr]): + """ + An asynchronous temporary directory that is created and cleaned up automatically. + + This class provides an asynchronous context manager for creating a temporary + directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to + perform directory creation and cleanup operations in a background thread. + + :param suffix: Suffix to be added to the temporary directory name. + :param prefix: Prefix to be added to the temporary directory name. + :param dir: The parent directory where the temporary directory is created. + :param ignore_cleanup_errors: Whether to ignore errors during cleanup + (Python 3.10+). + :param delete: Whether to delete the directory upon closing (Python 3.12+). + """ + + def __init__( + self, + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + *, + ignore_cleanup_errors: bool = False, + delete: bool = True, + ) -> None: + self.suffix: AnyStr | None = suffix + self.prefix: AnyStr | None = prefix + self.dir: AnyStr | None = dir + self.ignore_cleanup_errors = ignore_cleanup_errors + self.delete = delete + + self._tempdir: tempfile.TemporaryDirectory | None = None + + async def __aenter__(self) -> str: + params: dict[str, Any] = { + "suffix": self.suffix, + "prefix": self.prefix, + "dir": self.dir, + } + if sys.version_info >= (3, 10): + params["ignore_cleanup_errors"] = self.ignore_cleanup_errors + + if sys.version_info >= (3, 12): + params["delete"] = self.delete + + self._tempdir = await to_thread.run_sync( + lambda: tempfile.TemporaryDirectory(**params) + ) + return await to_thread.run_sync(self._tempdir.__enter__) + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + if self._tempdir is not None: + await to_thread.run_sync( + self._tempdir.__exit__, exc_type, exc_value, traceback + ) + + async def cleanup(self) -> None: + if self._tempdir is not None: + await to_thread.run_sync(self._tempdir.cleanup) + + +@overload +async def mkstemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, + text: bool = False, +) -> tuple[int, str]: ... + + +@overload +async def mkstemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, + text: bool = False, +) -> tuple[int, bytes]: ... + + +async def mkstemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, + text: bool = False, +) -> tuple[int, str | bytes]: + """ + Asynchronously create a temporary file and return an OS-level handle and the file + name. + + This function wraps `tempfile.mkstemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the file name. + :param prefix: Prefix to be added to the file name. + :param dir: Directory in which the temporary file is created. + :param text: Whether the file is opened in text mode. + :return: A tuple containing the file descriptor and the file name. + + """ + return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text) + + +@overload +async def mkdtemp( + suffix: str | None = None, + prefix: str | None = None, + dir: str | None = None, +) -> str: ... + + +@overload +async def mkdtemp( + suffix: bytes | None = None, + prefix: bytes | None = None, + dir: bytes | None = None, +) -> bytes: ... + + +async def mkdtemp( + suffix: AnyStr | None = None, + prefix: AnyStr | None = None, + dir: AnyStr | None = None, +) -> str | bytes: + """ + Asynchronously create a temporary directory and return its path. + + This function wraps `tempfile.mkdtemp` and executes it in a background thread. + + :param suffix: Suffix to be added to the directory name. + :param prefix: Prefix to be added to the directory name. + :param dir: Parent directory where the temporary directory is created. + :return: The path of the created temporary directory. + + """ + return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir) + + +async def gettempdir() -> str: + """ + Asynchronously return the name of the directory used for temporary files. + + This function wraps `tempfile.gettempdir` and executes it in a background thread. + + :return: The path of the temporary directory as a string. + + """ + return await to_thread.run_sync(tempfile.gettempdir) + + +async def gettempdirb() -> bytes: + """ + Asynchronously return the name of the directory used for temporary files in bytes. + + This function wraps `tempfile.gettempdirb` and executes it in a background thread. + + :return: The path of the temporary directory as bytes. + + """ + return await to_thread.run_sync(tempfile.gettempdirb) diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_testing.py b/venv/lib/python3.12/site-packages/anyio/_core/_testing.py new file mode 100644 index 0000000..369e65c --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_testing.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Awaitable, Generator +from typing import Any, cast + +from ._eventloop import get_async_backend + + +class TaskInfo: + """ + Represents an asynchronous task. + + :ivar int id: the unique identifier of the task + :ivar parent_id: the identifier of the parent task, if any + :vartype parent_id: Optional[int] + :ivar str name: the description of the task (if any) + :ivar ~collections.abc.Coroutine coro: the coroutine object of the task + """ + + __slots__ = "_name", "id", "parent_id", "name", "coro" + + def __init__( + self, + id: int, + parent_id: int | None, + name: str | None, + coro: Generator[Any, Any, Any] | Awaitable[Any], + ): + func = get_current_task + self._name = f"{func.__module__}.{func.__qualname__}" + self.id: int = id + self.parent_id: int | None = parent_id + self.name: str | None = name + self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro + + def __eq__(self, other: object) -> bool: + if isinstance(other, TaskInfo): + return self.id == other.id + + return NotImplemented + + def __hash__(self) -> int: + return hash(self.id) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})" + + def has_pending_cancellation(self) -> bool: + """ + Return ``True`` if the task has a cancellation pending, ``False`` otherwise. + + """ + return False + + +def get_current_task() -> TaskInfo: + """ + Return the current task. + + :return: a representation of the current task + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().get_current_task() + + +def get_running_tasks() -> list[TaskInfo]: + """ + Return a list of running tasks in the current event loop. + + :return: a list of task info objects + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return cast("list[TaskInfo]", get_async_backend().get_running_tasks()) + + +async def wait_all_tasks_blocked() -> None: + """Wait until all other tasks are waiting for something.""" + await get_async_backend().wait_all_tasks_blocked() diff --git a/venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py b/venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py new file mode 100644 index 0000000..f358a44 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/_core/_typedattr.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from collections.abc import Callable, Mapping +from typing import Any, TypeVar, final, overload + +from ._exceptions import TypedAttributeLookupError + +T_Attr = TypeVar("T_Attr") +T_Default = TypeVar("T_Default") +undefined = object() + + +def typed_attribute() -> Any: + """Return a unique object, used to mark typed attributes.""" + return object() + + +class TypedAttributeSet: + """ + Superclass for typed attribute collections. + + Checks that every public attribute of every subclass has a type annotation. + """ + + def __init_subclass__(cls) -> None: + annotations: dict[str, Any] = getattr(cls, "__annotations__", {}) + for attrname in dir(cls): + if not attrname.startswith("_") and attrname not in annotations: + raise TypeError( + f"Attribute {attrname!r} is missing its type annotation" + ) + + super().__init_subclass__() + + +class TypedAttributeProvider: + """Base class for classes that wish to provide typed extra attributes.""" + + @property + def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]: + """ + A mapping of the extra attributes to callables that return the corresponding + values. + + If the provider wraps another provider, the attributes from that wrapper should + also be included in the returned mapping (but the wrapper may override the + callables from the wrapped instance). + + """ + return {} + + @overload + def extra(self, attribute: T_Attr) -> T_Attr: ... + + @overload + def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ... + + @final + def extra(self, attribute: Any, default: object = undefined) -> object: + """ + extra(attribute, default=undefined) + + Return the value of the given typed extra attribute. + + :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to + look for + :param default: the value that should be returned if no value is found for the + attribute + :raises ~anyio.TypedAttributeLookupError: if the search failed and no default + value was given + + """ + try: + getter = self.extra_attributes[attribute] + except KeyError: + if default is undefined: + raise TypedAttributeLookupError("Attribute not found") from None + else: + return default + + return getter() diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__init__.py b/venv/lib/python3.12/site-packages/anyio/abc/__init__.py new file mode 100644 index 0000000..d560ce3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/__init__.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from ._eventloop import AsyncBackend as AsyncBackend +from ._resources import AsyncResource as AsyncResource +from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket +from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket +from ._sockets import IPAddressType as IPAddressType +from ._sockets import IPSockAddrType as IPSockAddrType +from ._sockets import SocketAttribute as SocketAttribute +from ._sockets import SocketListener as SocketListener +from ._sockets import SocketStream as SocketStream +from ._sockets import UDPPacketType as UDPPacketType +from ._sockets import UDPSocket as UDPSocket +from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType +from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket +from ._sockets import UNIXSocketStream as UNIXSocketStream +from ._streams import AnyByteReceiveStream as AnyByteReceiveStream +from ._streams import AnyByteSendStream as AnyByteSendStream +from ._streams import AnyByteStream as AnyByteStream +from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable +from ._streams import AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream +from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream +from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream +from ._streams import ByteReceiveStream as ByteReceiveStream +from ._streams import ByteSendStream as ByteSendStream +from ._streams import ByteStream as ByteStream +from ._streams import ByteStreamConnectable as ByteStreamConnectable +from ._streams import Listener as Listener +from ._streams import ObjectReceiveStream as ObjectReceiveStream +from ._streams import ObjectSendStream as ObjectSendStream +from ._streams import ObjectStream as ObjectStream +from ._streams import ObjectStreamConnectable as ObjectStreamConnectable +from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream +from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream +from ._streams import UnreliableObjectStream as UnreliableObjectStream +from ._subprocesses import Process as Process +from ._tasks import TaskGroup as TaskGroup +from ._tasks import TaskStatus as TaskStatus +from ._testing import TestRunner as TestRunner + +# Re-exported here, for backwards compatibility +# isort: off +from .._core._synchronization import ( + CapacityLimiter as CapacityLimiter, + Condition as Condition, + Event as Event, + Lock as Lock, + Semaphore as Semaphore, +) +from .._core._tasks import CancelScope as CancelScope +from ..from_thread import BlockingPortal as BlockingPortal + +# Re-export imports so they look like they live directly in this package +for __value in list(locals().values()): + if getattr(__value, "__module__", "").startswith("anyio.abc."): + __value.__module__ = __name__ + +del __value diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6989dd52e50c6ba2d816e86b214ab8f6c0643b7 GIT binary patch literal 2393 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_streams.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8937a3a1bfc8d820d01555d194ac91ebd08beda GIT binary patch literal 9914 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_subprocesses.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a7d63b111f7921a58435d488e6b8a0fbb756f1c GIT binary patch literal 3268 zcmcInOOG2x5bn0eZ?BWhCITW9wIZU#A{i?|5y(Q2jS?-Zgk_Zt3i5&Rc&5EOWFDq_ zyk;yefgJb;xgc?lB5wQ(PTXLm2qcI^ZYBl^a-wQxJUiZO9xKEg%GFiXT~%LKSGT`k zSf~>CzB&1|wNxhLR~*c~Ld?)<2bep=Bqp_qLpzirpR;pLzLSsmysbEeP62quEZ9Y- z)G0-L(JnicPQ|Hqs+8o&3NcH!iCN~Qd--_nPA#Z3s}FI|q+v~WUC-Bj%X1|R8+uRr zLN|Pe`&*s~lG28?>FTz8NaG0$%QA3{Yg~FmVen=yT)Y_g{2Dj7waqtt!F4BWplE}; zW;`}x$1zpnA3FUFm^*}%4mC+9XVOldD`wry-B#`;_MHMq7R-D`Qj8=@Mp6Pv!z^SZ zWnPI^D(=YA6|0f%0AP}ONp-bIchD^Fp{qx_MYFPpZXwZC_s}(hTJum?S{I(dr5vAM zcA1X&Z3)lyhLUY_Apv^k^;z4nbt&6jIqVH$b9LPtaJQ=(2>tBK0rMpxbXo`I3ZZ~u zK)=n8XfrqdxXP06hHm+6==zq;{4LIs1sVU$@}*i$&Dw(dL*cd~BUCcOGdV_+nUz|v z-6eNvs7P-2C5FTvk6K%v!&`dK9NPNW8n-TSdDHg>tu;Pkkn|_q@MUYD2M*WdmN&5a zfhM`wwhZ3d=I(aOwtB5W0Hx->p`Jb0l9tcU40PkBzR6`vcLU3V#f_G>0}7YwAPAKa zBsi`oxT-i>fQV!f$nWIuTjU>!h4w{Gp>ybgjKDddQ`91IfZ8-o@H!Wc5b%Rm}yC7=(a6cx0|k*M0%>(?W(Cin701G z#_C5*2GZxwY_f!tgOfK_SFT^a_I|g^d@mbN#<8k0$vGTPuia*CJhV?1km}FU>ZYF8 z1i~d6K;o?w2Z0SMsp)uehHWZ30v1y&zguJqlsp&MA5wQ$EQBLdQ<)V0H|(ygUcLIC zvAYS8KNmIyt#+yrM+^WGhmjy`2Evy8cGuu{DOA`tna*U?b!QvDQ~nv?UTE*Jmb7@W zZsyz%bfG(mllb%}4&9;CZZ!trta%dP*E%@l;e9Ju6LVhsBsZBbK(G`78YYO ziK#Lc*HbOye!MiRsEqJ+FwVe~ZFC6NZkUc;&or~uOS>AC`jo294=i(Bn|X)9`Tcnv zr5U69xMl9uC{?yP*ErdhE*adeEZ{y%kCB0`cQEu{5aojOP0JlG%p!`07xx#;xn~9A z4gD97rU>p~!nlzM2gWP=i|4Ip#lwXFy!IUxOq+`e##iT}T7ySbG2YKGZ@E4kuoGYq zDp(&)MZAL6_)v_*;v{g1$+!yU>~j_w#Tq#uVO7%{ z53YFR>zelIP`49JNz+Wv&@_SfsaQm^gybb8M}dUpfq++x@B{HG%8nsnNRA_U4M}tr zVVjJahNvNj1fR+B0g&6|C*|E=3QLc2g+hIrC&j}Pec3p`(G09U(?~~&X$g%t6=mT=%XY%Hs#mkhg(65}oNW?^8{tFt~GHCz+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_tasks.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..048c3532fe749919eaf987c11b8e6994a5cdb511 GIT binary patch literal 5167 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 ICbUKV4ZL=H#Q*>R literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/abc/__pycache__/_testing.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..105c5aeb8a07d62532ca4848d4058ad40ecb91b9 GIT binary patch literal 2863 zcmd5;&2QXP5P$ZrcfDRG=||eqh!U%UMhjtAPzg{}A=*+^MIu5FA>=5s-r8>y6Mro4 zd6Rh6LlHUj4?rphBqVNC`A@jPrLu%l)DyRD7jfu`nP=~2Hw{&fcvthrGxNqXznSOp zuQM|Sf$OKGAN>neLLTGI=+bgV8SVjdk9fpW0um~=q9CsZs;$CW3QA$wE{B?}NqIS_ zgjKs5>b4%%>{@8phLmeTJv41IoUvyVq7pXaRURn$6YN=)+$3IgpLjYe|5AHUD&%vi z+A#jYn}(9rX%xi?P5d|#s|_`)UcGkxQ*^0eJIh(yI(#XBZBF1SF z^Gv@^gMhXJmesDuJnkia#4`O>0%{O~uhP{h&8ll@mwicjraEhG9zjlvaFpTqaDR_5 zVk;hj7Kd|=`*cHrhi_|EA5vb6n^Z=w1sDQ!&6I%U<*7Pb9+FDv| z%w)zI6Upbj2vY8I!-DHZt;;I~`d6lxw_ZoczwOZW+UyGz||y2(U~MyVfzbGPLH08sJGW;e}hjsxj4?l}GV zL&Tb6=}Cx8z(F8)$iunQck4r7iCacGamnORc#ounp8$sA#zvW+f+f@0p6evgOj#AV z9U5d6DQXMcgvH^fr6D|b&rcwj)6>C7;XIfWu?$RdZh@Zxfm<{qu=zC^i_%aj0dQqW z@G;O37KJ|^2WxY59;}YPm-IMm8e_wiF6A^FDV$C(a#yh8BZ-9)JbFiWZ0}OODORj) zmhQ&fv*4~50>r{f;=Hqt^8#I!Y#qAd48n<4FjT0aJhN8nv%tfK#r*(bkL4ofSsfmS zQhX$@vBOnaj{Bv&9A=TO%qpwo5evi-5XuWN52wSC-T)-0ue?A_| z1kU=nS)XrkR8MsGl=awQaTs2%wWnN6_jL|OqiDn0_}Tom=K#_XAT*eI6O4o(OhJRg zuaeh=&w{3*^K-ED7x0y{evJxtW6xz>JgpnnZEH1#neYl4%une?s`KN$qPYfRl(pd% zilcC3a0w0S?Kln!Nu$CqpzP9Utgbhk&1GwP=&n0!Ad1qB4Vmt`G_oJe zO0KS%kIr5hXt2W0lZ%5Yayl{2KCJ;)*rROW#rEd$_PS<1Jpa+4gzG0?$@Nif=BWnv z()SY;xIA6ino3rKuQKWeREWk2M6=Vw(F3sI3pmrTXGK= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +if TYPE_CHECKING: + from _typeshed import FileDescriptorLike + + from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore + from .._core._tasks import CancelScope + from .._core._testing import TaskInfo + from ._sockets import ( + ConnectedUDPSocket, + ConnectedUNIXDatagramSocket, + IPSockAddrType, + SocketListener, + SocketStream, + UDPSocket, + UNIXDatagramSocket, + UNIXSocketStream, + ) + from ._subprocesses import Process + from ._tasks import TaskGroup + from ._testing import TestRunner + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +StrOrBytesPath: TypeAlias = Union[str, bytes, "PathLike[str]", "PathLike[bytes]"] + + +class AsyncBackend(metaclass=ABCMeta): + @classmethod + @abstractmethod + def run( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + options: dict[str, Any], + ) -> T_Retval: + """ + Run the given coroutine function in an asynchronous event loop. + + The current thread must not be already running an event loop. + + :param func: a coroutine function + :param args: positional arguments to ``func`` + :param kwargs: positional arguments to ``func`` + :param options: keyword arguments to call the backend ``run()`` implementation + with + :return: the return value of the coroutine function + """ + + @classmethod + @abstractmethod + def current_token(cls) -> object: + """ + Return an object that allows other threads to run code inside the event loop. + + :return: a token object, specific to the event loop running in the current + thread + """ + + @classmethod + @abstractmethod + def current_time(cls) -> float: + """ + Return the current value of the event loop's internal clock. + + :return: the clock value (seconds) + """ + + @classmethod + @abstractmethod + def cancelled_exception_class(cls) -> type[BaseException]: + """Return the exception class that is raised in a task if it's cancelled.""" + + @classmethod + @abstractmethod + async def checkpoint(cls) -> None: + """ + Check if the task has been cancelled, and allow rescheduling of other tasks. + + This is effectively the same as running :meth:`checkpoint_if_cancelled` and then + :meth:`cancel_shielded_checkpoint`. + """ + + @classmethod + async def checkpoint_if_cancelled(cls) -> None: + """ + Check if the current task group has been cancelled. + + This will check if the task has been cancelled, but will not allow other tasks + to be scheduled if not. + + """ + if cls.current_effective_deadline() == -math.inf: + await cls.checkpoint() + + @classmethod + async def cancel_shielded_checkpoint(cls) -> None: + """ + Allow the rescheduling of other tasks. + + This will give other tasks the opportunity to run, but without checking if the + current task group has been cancelled, unlike with :meth:`checkpoint`. + + """ + with cls.create_cancel_scope(shield=True): + await cls.sleep(0) + + @classmethod + @abstractmethod + async def sleep(cls, delay: float) -> None: + """ + Pause the current task for the specified duration. + + :param delay: the duration, in seconds + """ + + @classmethod + @abstractmethod + def create_cancel_scope( + cls, *, deadline: float = math.inf, shield: bool = False + ) -> CancelScope: + pass + + @classmethod + @abstractmethod + def current_effective_deadline(cls) -> float: + """ + Return the nearest deadline among all the cancel scopes effective for the + current task. + + :return: + - a clock value from the event loop's internal clock + - ``inf`` if there is no deadline in effect + - ``-inf`` if the current scope has been cancelled + :rtype: float + """ + + @classmethod + @abstractmethod + def create_task_group(cls) -> TaskGroup: + pass + + @classmethod + @abstractmethod + def create_event(cls) -> Event: + pass + + @classmethod + @abstractmethod + def create_lock(cls, *, fast_acquire: bool) -> Lock: + pass + + @classmethod + @abstractmethod + def create_semaphore( + cls, + initial_value: int, + *, + max_value: int | None = None, + fast_acquire: bool = False, + ) -> Semaphore: + pass + + @classmethod + @abstractmethod + def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + async def run_sync_in_worker_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + abandon_on_cancel: bool = False, + limiter: CapacityLimiter | None = None, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def check_cancelled(cls) -> None: + pass + + @classmethod + @abstractmethod + def run_async_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + def run_sync_from_thread( + cls, + func: Callable[[Unpack[PosArgsT]], T_Retval], + args: tuple[Unpack[PosArgsT]], + token: object, + ) -> T_Retval: + pass + + @classmethod + @abstractmethod + async def open_process( + cls, + command: StrOrBytesPath | Sequence[StrOrBytesPath], + *, + stdin: int | IO[Any] | None, + stdout: int | IO[Any] | None, + stderr: int | IO[Any] | None, + **kwargs: Any, + ) -> Process: + pass + + @classmethod + @abstractmethod + def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None: + pass + + @classmethod + @abstractmethod + async def connect_tcp( + cls, host: str, port: int, local_address: IPSockAddrType | None = None + ) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + def create_tcp_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + def create_unix_listener(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def create_udp_socket( + cls, + family: AddressFamily, + local_address: IPSockAddrType | None, + remote_address: IPSockAddrType | None, + reuse_port: bool, + ) -> UDPSocket | ConnectedUDPSocket: + pass + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: None + ) -> UNIXDatagramSocket: ... + + @classmethod + @overload + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes + ) -> ConnectedUNIXDatagramSocket: ... + + @classmethod + @abstractmethod + async def create_unix_datagram_socket( + cls, raw_socket: socket, remote_path: str | bytes | None + ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def getaddrinfo( + cls, + host: bytes | str | None, + port: str | int | None, + *, + family: int | AddressFamily = 0, + type: int | SocketKind = 0, + proto: int = 0, + flags: int = 0, + ) -> Sequence[ + tuple[ + AddressFamily, + SocketKind, + int, + str, + tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], + ] + ]: + pass + + @classmethod + @abstractmethod + async def getnameinfo( + cls, sockaddr: IPSockAddrType, flags: int = 0 + ) -> tuple[str, str]: + pass + + @classmethod + @abstractmethod + async def wait_readable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wait_writable(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + def notify_closing(cls, obj: FileDescriptorLike) -> None: + pass + + @classmethod + @abstractmethod + async def wrap_listener_socket(cls, sock: socket) -> SocketListener: + pass + + @classmethod + @abstractmethod + async def wrap_stream_socket(cls, sock: socket) -> SocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream: + pass + + @classmethod + @abstractmethod + async def wrap_udp_socket(cls, sock: socket) -> UDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket: + pass + + @classmethod + @abstractmethod + async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + async def wrap_connected_unix_datagram_socket( + cls, sock: socket + ) -> ConnectedUNIXDatagramSocket: + pass + + @classmethod + @abstractmethod + def current_default_thread_limiter(cls) -> CapacityLimiter: + pass + + @classmethod + @abstractmethod + def open_signal_receiver( + cls, *signals: Signals + ) -> AbstractContextManager[AsyncIterator[Signals]]: + pass + + @classmethod + @abstractmethod + def get_current_task(cls) -> TaskInfo: + pass + + @classmethod + @abstractmethod + def get_running_tasks(cls) -> Sequence[TaskInfo]: + pass + + @classmethod + @abstractmethod + async def wait_all_tasks_blocked(cls) -> None: + pass + + @classmethod + @abstractmethod + def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: + pass diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_resources.py b/venv/lib/python3.12/site-packages/anyio/abc/_resources.py new file mode 100644 index 0000000..10df115 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_resources.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from abc import ABCMeta, abstractmethod +from types import TracebackType +from typing import TypeVar + +T = TypeVar("T") + + +class AsyncResource(metaclass=ABCMeta): + """ + Abstract base class for all closeable asynchronous resources. + + Works as an asynchronous context manager which returns the instance itself on enter, + and calls :meth:`aclose` on exit. + """ + + __slots__ = () + + async def __aenter__(self: T) -> T: + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + await self.aclose() + + @abstractmethod + async def aclose(self) -> None: + """Close the resource.""" diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_sockets.py b/venv/lib/python3.12/site-packages/anyio/abc/_sockets.py new file mode 100644 index 0000000..3ff60d4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_sockets.py @@ -0,0 +1,405 @@ +from __future__ import annotations + +import errno +import socket +import sys +from abc import abstractmethod +from collections.abc import Callable, Collection, Mapping +from contextlib import AsyncExitStack +from io import IOBase +from ipaddress import IPv4Address, IPv6Address +from socket import AddressFamily +from typing import Any, TypeVar, Union + +from .._core._eventloop import get_async_backend +from .._core._typedattr import ( + TypedAttributeProvider, + TypedAttributeSet, + typed_attribute, +) +from ._streams import ByteStream, Listener, UnreliableObjectStream +from ._tasks import TaskGroup + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +IPAddressType: TypeAlias = Union[str, IPv4Address, IPv6Address] +IPSockAddrType: TypeAlias = tuple[str, int] +SockAddrType: TypeAlias = Union[IPSockAddrType, str] +UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType] +UNIXDatagramPacketType: TypeAlias = tuple[bytes, str] +T_Retval = TypeVar("T_Retval") + + +def _validate_socket( + sock_or_fd: socket.socket | int, + sock_type: socket.SocketKind, + addr_family: socket.AddressFamily = socket.AF_UNSPEC, + *, + require_connected: bool = False, + require_bound: bool = False, +) -> socket.socket: + if isinstance(sock_or_fd, int): + try: + sock = socket.socket(fileno=sock_or_fd) + except OSError as exc: + if exc.errno == errno.ENOTSOCK: + raise ValueError( + "the file descriptor does not refer to a socket" + ) from exc + elif require_connected: + raise ValueError("the socket must be connected") from exc + elif require_bound: + raise ValueError("the socket must be bound to a local address") from exc + else: + raise + elif isinstance(sock_or_fd, socket.socket): + sock = sock_or_fd + else: + raise TypeError( + f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead" + ) + + try: + if require_connected: + try: + sock.getpeername() + except OSError as exc: + raise ValueError("the socket must be connected") from exc + + if require_bound: + try: + if sock.family in (socket.AF_INET, socket.AF_INET6): + bound_addr = sock.getsockname()[1] + else: + bound_addr = sock.getsockname() + except OSError: + bound_addr = None + + if not bound_addr: + raise ValueError("the socket must be bound to a local address") + + if addr_family != socket.AF_UNSPEC and sock.family != addr_family: + raise ValueError( + f"address family mismatch: expected {addr_family.name}, got " + f"{sock.family.name}" + ) + + if sock.type != sock_type: + raise ValueError( + f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}" + ) + except BaseException: + # Avoid ResourceWarning from the locally constructed socket object + if isinstance(sock_or_fd, int): + sock.detach() + + raise + + sock.setblocking(False) + return sock + + +class SocketAttribute(TypedAttributeSet): + """ + .. attribute:: family + :type: socket.AddressFamily + + the address family of the underlying socket + + .. attribute:: local_address + :type: tuple[str, int] | str + + the local address the underlying socket is connected to + + .. attribute:: local_port + :type: int + + for IP based sockets, the local port the underlying socket is bound to + + .. attribute:: raw_socket + :type: socket.socket + + the underlying stdlib socket object + + .. attribute:: remote_address + :type: tuple[str, int] | str + + the remote address the underlying socket is connected to + + .. attribute:: remote_port + :type: int + + for IP based sockets, the remote port the underlying socket is connected to + """ + + family: AddressFamily = typed_attribute() + local_address: SockAddrType = typed_attribute() + local_port: int = typed_attribute() + raw_socket: socket.socket = typed_attribute() + remote_address: SockAddrType = typed_attribute() + remote_port: int = typed_attribute() + + +class _SocketProvider(TypedAttributeProvider): + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + from .._core._sockets import convert_ipv6_sockaddr as convert + + attributes: dict[Any, Callable[[], Any]] = { + SocketAttribute.family: lambda: self._raw_socket.family, + SocketAttribute.local_address: lambda: convert( + self._raw_socket.getsockname() + ), + SocketAttribute.raw_socket: lambda: self._raw_socket, + } + try: + peername: tuple[str, int] | None = convert(self._raw_socket.getpeername()) + except OSError: + peername = None + + # Provide the remote address for connected sockets + if peername is not None: + attributes[SocketAttribute.remote_address] = lambda: peername + + # Provide local and remote ports for IP based sockets + if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6): + attributes[SocketAttribute.local_port] = ( + lambda: self._raw_socket.getsockname()[1] + ) + if peername is not None: + remote_port = peername[1] + attributes[SocketAttribute.remote_port] = lambda: remote_port + + return attributes + + @property + @abstractmethod + def _raw_socket(self) -> socket.socket: + pass + + +class SocketStream(ByteStream, _SocketProvider): + """ + Transports bytes over a socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream: + """ + Wrap an existing socket object or file descriptor as a socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket stream + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True) + return await get_async_backend().wrap_stream_socket(sock) + + +class UNIXSocketStream(SocketStream): + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream: + """ + Wrap an existing socket object or file descriptor as a UNIX socket stream. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX socket stream + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_unix_stream_socket(sock) + + @abstractmethod + async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: + """ + Send file descriptors along with a message to the peer. + + :param message: a non-empty bytestring + :param fds: a collection of files (either numeric file descriptors or open file + or socket objects) + """ + + @abstractmethod + async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: + """ + Receive file descriptors along with a message from the peer. + + :param msglen: length of the message to expect from the peer + :param maxfds: maximum number of file descriptors to expect from the peer + :return: a tuple of (message, file descriptors) + """ + + +class SocketListener(Listener[SocketStream], _SocketProvider): + """ + Listens to incoming socket connections. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> SocketListener: + """ + Wrap an existing socket object or file descriptor as a socket listener. + + The newly created listener takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a socket listener + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True) + return await get_async_backend().wrap_listener_socket(sock) + + @abstractmethod + async def accept(self) -> SocketStream: + """Accept an incoming connection.""" + + async def serve( + self, + handler: Callable[[SocketStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + from .. import create_task_group + + async with AsyncExitStack() as stack: + if task_group is None: + task_group = await stack.enter_async_context(create_task_group()) + + while True: + stream = await self.accept() + task_group.start_soon(handler, stream) + + +class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider): + """ + Represents an unconnected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket: + """ + Wrap an existing socket object or file descriptor as a UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must be bound to a local address. + + :param sock_or_fd: a socket object or file descriptor + :return: a UDP socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True) + return await get_async_backend().wrap_udp_socket(sock) + + async def sendto(self, data: bytes, host: str, port: int) -> None: + """ + Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))). + + """ + return await self.send((data, (host, port))) + + +class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents an connected UDP socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket: + """ + Wrap an existing socket object or file descriptor as a connected UDP socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UDP socket + + """ + sock = _validate_socket( + sock_or_fd, + socket.SOCK_DGRAM, + require_connected=True, + ) + return await get_async_backend().wrap_connected_udp_socket(sock) + + +class UNIXDatagramSocket( + UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider +): + """ + Represents an unconnected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> UNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + + :param sock_or_fd: a socket object or file descriptor + :return: a UNIX datagram socket + + """ + sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX) + return await get_async_backend().wrap_unix_datagram_socket(sock) + + async def sendto(self, data: bytes, path: str) -> None: + """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path)).""" + return await self.send((data, path)) + + +class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider): + """ + Represents a connected Unix datagram socket. + + Supports all relevant extra attributes from :class:`~SocketAttribute`. + """ + + @classmethod + async def from_socket( + cls, + sock_or_fd: socket.socket | int, + ) -> ConnectedUNIXDatagramSocket: + """ + Wrap an existing socket object or file descriptor as a connected UNIX datagram + socket. + + The newly created socket wrapper takes ownership of the socket being passed in. + The existing socket must already be connected. + + :param sock_or_fd: a socket object or file descriptor + :return: a connected UNIX datagram socket + + """ + sock = _validate_socket( + sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True + ) + return await get_async_backend().wrap_connected_unix_datagram_socket(sock) diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_streams.py b/venv/lib/python3.12/site-packages/anyio/abc/_streams.py new file mode 100644 index 0000000..369df3f --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_streams.py @@ -0,0 +1,239 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Callable +from typing import Any, Generic, TypeVar, Union + +from .._core._exceptions import EndOfStream +from .._core._typedattr import TypedAttributeProvider +from ._resources import AsyncResource +from ._tasks import TaskGroup + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class UnreliableObjectReceiveStream( + Generic[T_co], AsyncResource, TypedAttributeProvider +): + """ + An interface for receiving objects. + + This interface makes no guarantees that the received messages arrive in the order in + which they were sent, or that no messages are missed. + + Asynchronously iterating over objects of this type will yield objects matching the + given type parameter. + """ + + def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]: + return self + + async def __anext__(self) -> T_co: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self) -> T_co: + """ + Receive the next item. + + :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly + closed + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectSendStream( + Generic[T_contra], AsyncResource, TypedAttributeProvider +): + """ + An interface for sending objects. + + This interface makes no guarantees that the messages sent will reach the + recipient(s) in the same order in which they were sent, or at all. + """ + + @abstractmethod + async def send(self, item: T_contra) -> None: + """ + Send an item to the peer(s). + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if the send stream has been explicitly + closed + :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable + due to external causes + """ + + +class UnreliableObjectStream( + UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item] +): + """ + A bidirectional message stream which does not guarantee the order or reliability of + message delivery. + """ + + +class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]): + """ + A receive message stream which guarantees that messages are received in the same + order in which they were sent, and that no messages are missed. + """ + + +class ObjectSendStream(UnreliableObjectSendStream[T_contra]): + """ + A send message stream which guarantees that messages are delivered in the same order + in which they were sent, without missing any messages in the middle. + """ + + +class ObjectStream( + ObjectReceiveStream[T_Item], + ObjectSendStream[T_Item], + UnreliableObjectStream[T_Item], +): + """ + A bidirectional message stream which guarantees the order and reliability of message + delivery. + """ + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +class ByteReceiveStream(AsyncResource, TypedAttributeProvider): + """ + An interface for receiving bytes from a single peer. + + Iterating this byte stream will yield a byte string of arbitrary length, but no more + than 65536 bytes. + """ + + def __aiter__(self) -> ByteReceiveStream: + return self + + async def __anext__(self) -> bytes: + try: + return await self.receive() + except EndOfStream: + raise StopAsyncIteration from None + + @abstractmethod + async def receive(self, max_bytes: int = 65536) -> bytes: + """ + Receive at most ``max_bytes`` bytes from the peer. + + .. note:: Implementers of this interface should not return an empty + :class:`bytes` object, and users should ignore them. + + :param max_bytes: maximum number of bytes to receive + :return: the received bytes + :raises ~anyio.EndOfStream: if this stream has been closed from the other end + """ + + +class ByteSendStream(AsyncResource, TypedAttributeProvider): + """An interface for sending bytes to a single peer.""" + + @abstractmethod + async def send(self, item: bytes) -> None: + """ + Send the given bytes to the peer. + + :param item: the bytes to send + """ + + +class ByteStream(ByteReceiveStream, ByteSendStream): + """A bidirectional byte stream.""" + + @abstractmethod + async def send_eof(self) -> None: + """ + Send an end-of-file indication to the peer. + + You should not try to send any further data to this stream after calling this + method. This method is idempotent (does nothing on successive calls). + """ + + +#: Type alias for all unreliable bytes-oriented receive streams. +AnyUnreliableByteReceiveStream: TypeAlias = Union[ + UnreliableObjectReceiveStream[bytes], ByteReceiveStream +] +#: Type alias for all unreliable bytes-oriented send streams. +AnyUnreliableByteSendStream: TypeAlias = Union[ + UnreliableObjectSendStream[bytes], ByteSendStream +] +#: Type alias for all unreliable bytes-oriented streams. +AnyUnreliableByteStream: TypeAlias = Union[UnreliableObjectStream[bytes], ByteStream] +#: Type alias for all bytes-oriented receive streams. +AnyByteReceiveStream: TypeAlias = Union[ObjectReceiveStream[bytes], ByteReceiveStream] +#: Type alias for all bytes-oriented send streams. +AnyByteSendStream: TypeAlias = Union[ObjectSendStream[bytes], ByteSendStream] +#: Type alias for all bytes-oriented streams. +AnyByteStream: TypeAlias = Union[ObjectStream[bytes], ByteStream] + + +class Listener(Generic[T_co], AsyncResource, TypedAttributeProvider): + """An interface for objects that let you accept incoming connections.""" + + @abstractmethod + async def serve( + self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None + ) -> None: + """ + Accept incoming connections as they come in and start tasks to handle them. + + :param handler: a callable that will be used to handle each accepted connection + :param task_group: the task group that will be used to start tasks for handling + each accepted connection (if omitted, an ad-hoc task group will be created) + """ + + +class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ObjectStream[T_co]: + """ + Connect to the remote endpoint. + + :return: an object stream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +class ByteStreamConnectable(metaclass=ABCMeta): + @abstractmethod + async def connect(self) -> ByteStream: + """ + Connect to the remote endpoint. + + :return: a bytestream connected to the remote end + :raises ConnectionFailed: if the connection fails + """ + + +#: Type alias for all connectables returning bytestreams or bytes-oriented object streams +AnyByteStreamConnectable: TypeAlias = Union[ + ObjectStreamConnectable[bytes], ByteStreamConnectable +] diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py b/venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py new file mode 100644 index 0000000..ce0564c --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_subprocesses.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from abc import abstractmethod +from signal import Signals + +from ._resources import AsyncResource +from ._streams import ByteReceiveStream, ByteSendStream + + +class Process(AsyncResource): + """An asynchronous version of :class:`subprocess.Popen`.""" + + @abstractmethod + async def wait(self) -> int: + """ + Wait until the process exits. + + :return: the exit code of the process + """ + + @abstractmethod + def terminate(self) -> None: + """ + Terminates the process, gracefully if possible. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGTERM`` to the process. + + .. seealso:: :meth:`subprocess.Popen.terminate` + """ + + @abstractmethod + def kill(self) -> None: + """ + Kills the process. + + On Windows, this calls ``TerminateProcess()``. + On POSIX systems, this sends ``SIGKILL`` to the process. + + .. seealso:: :meth:`subprocess.Popen.kill` + """ + + @abstractmethod + def send_signal(self, signal: Signals) -> None: + """ + Send a signal to the subprocess. + + .. seealso:: :meth:`subprocess.Popen.send_signal` + + :param signal: the signal number (e.g. :data:`signal.SIGHUP`) + """ + + @property + @abstractmethod + def pid(self) -> int: + """The process ID of the process.""" + + @property + @abstractmethod + def returncode(self) -> int | None: + """ + The return code of the process. If the process has not yet terminated, this will + be ``None``. + """ + + @property + @abstractmethod + def stdin(self) -> ByteSendStream | None: + """The stream for the standard input of the process.""" + + @property + @abstractmethod + def stdout(self) -> ByteReceiveStream | None: + """The stream for the standard output of the process.""" + + @property + @abstractmethod + def stderr(self) -> ByteReceiveStream | None: + """The stream for the standard error output of the process.""" diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_tasks.py b/venv/lib/python3.12/site-packages/anyio/abc/_tasks.py new file mode 100644 index 0000000..516b3ec --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_tasks.py @@ -0,0 +1,117 @@ +from __future__ import annotations + +import sys +from abc import ABCMeta, abstractmethod +from collections.abc import Awaitable, Callable +from types import TracebackType +from typing import TYPE_CHECKING, Any, Protocol, overload + +if sys.version_info >= (3, 13): + from typing import TypeVar +else: + from typing_extensions import TypeVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if TYPE_CHECKING: + from .._core._tasks import CancelScope + +T_Retval = TypeVar("T_Retval") +T_contra = TypeVar("T_contra", contravariant=True, default=None) +PosArgsT = TypeVarTuple("PosArgsT") + + +class TaskStatus(Protocol[T_contra]): + @overload + def started(self: TaskStatus[None]) -> None: ... + + @overload + def started(self, value: T_contra) -> None: ... + + def started(self, value: T_contra | None = None) -> None: + """ + Signal that the task has started. + + :param value: object passed back to the starter of the task + """ + + +class TaskGroup(metaclass=ABCMeta): + """ + Groups several asynchronous tasks together. + + :ivar cancel_scope: the cancel scope inherited by all child tasks + :vartype cancel_scope: CancelScope + + .. note:: On asyncio, support for eager task factories is considered to be + **experimental**. In particular, they don't follow the usual semantics of new + tasks being scheduled on the next iteration of the event loop, and may thus + cause unexpected behavior in code that wasn't written with such semantics in + mind. + """ + + cancel_scope: CancelScope + + @abstractmethod + def start_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[Any]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> None: + """ + Start a new task in this task group. + + :param func: a coroutine function + :param args: positional arguments to call the function with + :param name: name of the task, for the purposes of introspection and debugging + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def start( + self, + func: Callable[..., Awaitable[Any]], + *args: object, + name: object = None, + ) -> Any: + """ + Start a new task and wait until it signals for readiness. + + The target callable must accept a keyword argument ``task_status`` (of type + :class:`TaskStatus`). Awaiting on this method will return whatever was passed to + ``task_status.started()`` (``None`` by default). + + .. note:: The :class:`TaskStatus` class is generic, and the type argument should + indicate the type of the value that will be passed to + ``task_status.started()``. + + :param func: a coroutine function that accepts the ``task_status`` keyword + argument + :param args: positional arguments to call the function with + :param name: an optional name for the task, for introspection and debugging + :return: the value passed to ``task_status.started()`` + :raises RuntimeError: if the task finishes without calling + ``task_status.started()`` + + .. seealso:: :ref:`start_initialize` + + .. versionadded:: 3.0 + """ + + @abstractmethod + async def __aenter__(self) -> TaskGroup: + """Enter the task group context and allow starting new tasks.""" + + @abstractmethod + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + """Exit the task group context waiting for all tasks to finish.""" diff --git a/venv/lib/python3.12/site-packages/anyio/abc/_testing.py b/venv/lib/python3.12/site-packages/anyio/abc/_testing.py new file mode 100644 index 0000000..7c50ed7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/abc/_testing.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import types +from abc import ABCMeta, abstractmethod +from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable +from typing import Any, TypeVar + +_T = TypeVar("_T") + + +class TestRunner(metaclass=ABCMeta): + """ + Encapsulates a running event loop. Every call made through this object will use the + same event loop. + """ + + def __enter__(self) -> TestRunner: + return self + + @abstractmethod + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> bool | None: ... + + @abstractmethod + def run_asyncgen_fixture( + self, + fixture_func: Callable[..., AsyncGenerator[_T, Any]], + kwargs: dict[str, Any], + ) -> Iterable[_T]: + """ + Run an async generator fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: an iterator yielding the value yielded from the async generator + """ + + @abstractmethod + def run_fixture( + self, + fixture_func: Callable[..., Coroutine[Any, Any, _T]], + kwargs: dict[str, Any], + ) -> _T: + """ + Run an async fixture. + + :param fixture_func: the fixture function + :param kwargs: keyword arguments to call the fixture function with + :return: the return value of the fixture function + """ + + @abstractmethod + def run_test( + self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] + ) -> None: + """ + Run an async test function. + + :param test_func: the test function + :param kwargs: keyword arguments to call the test function with + """ diff --git a/venv/lib/python3.12/site-packages/anyio/from_thread.py b/venv/lib/python3.12/site-packages/anyio/from_thread.py new file mode 100644 index 0000000..837de5e --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/from_thread.py @@ -0,0 +1,578 @@ +from __future__ import annotations + +__all__ = ( + "BlockingPortal", + "BlockingPortalProvider", + "check_cancelled", + "run", + "run_sync", + "start_blocking_portal", +) + +import sys +from collections.abc import Awaitable, Callable, Generator +from concurrent.futures import Future +from contextlib import ( + AbstractAsyncContextManager, + AbstractContextManager, + contextmanager, +) +from dataclasses import dataclass, field +from functools import partial +from inspect import isawaitable +from threading import Lock, Thread, current_thread, get_ident +from types import TracebackType +from typing import ( + Any, + Generic, + TypeVar, + cast, + overload, +) + +from ._core._eventloop import ( + get_cancelled_exc_class, + threadlocals, +) +from ._core._eventloop import run as run_eventloop +from ._core._exceptions import NoEventLoopError +from ._core._synchronization import Event +from ._core._tasks import CancelScope, create_task_group +from .abc._tasks import TaskStatus +from .lowlevel import EventLoopToken, current_token + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +T_co = TypeVar("T_co", covariant=True) +PosArgsT = TypeVarTuple("PosArgsT") + + +def _token_or_error(token: EventLoopToken | None) -> EventLoopToken: + if token is not None: + return token + + try: + return threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "Not running inside an AnyIO worker thread, and no event loop token was " + "provided" + ) from None + + +def run( + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_Retval: + """ + Call a coroutine function from a worker thread. + + :param func: a coroutine function + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the coroutine function + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_async_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + token: EventLoopToken | None = None, +) -> T_Retval: + """ + Call a function in the event loop thread from a worker thread. + + :param func: a callable + :param args: positional arguments for the callable + :param token: an event loop token to use to get back to the event loop thread + (required if calling this function from outside an AnyIO worker thread) + :return: the return value of the callable + :raises MissingTokenError: if no token was provided and called from outside an + AnyIO worker thread + :raises RunFinishedError: if the event loop tied to ``token`` is no longer running + + .. versionchanged:: 4.11.0 + Added the ``token`` parameter. + + """ + explicit_token = token is not None + token = _token_or_error(token) + return token.backend_class.run_sync_from_thread( + func, args, token=token.native_token if explicit_token else None + ) + + +class _BlockingAsyncContextManager(Generic[T_co], AbstractContextManager): + _enter_future: Future[T_co] + _exit_future: Future[bool | None] + _exit_event: Event + _exit_exc_info: tuple[ + type[BaseException] | None, BaseException | None, TracebackType | None + ] = (None, None, None) + + def __init__( + self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal + ): + self._async_cm = async_cm + self._portal = portal + + async def run_async_cm(self) -> bool | None: + try: + self._exit_event = Event() + value = await self._async_cm.__aenter__() + except BaseException as exc: + self._enter_future.set_exception(exc) + raise + else: + self._enter_future.set_result(value) + + try: + # Wait for the sync context manager to exit. + # This next statement can raise `get_cancelled_exc_class()` if + # something went wrong in a task group in this async context + # manager. + await self._exit_event.wait() + finally: + # In case of cancellation, it could be that we end up here before + # `_BlockingAsyncContextManager.__exit__` is called, and an + # `_exit_exc_info` has been set. + result = await self._async_cm.__aexit__(*self._exit_exc_info) + + return result + + def __enter__(self) -> T_co: + self._enter_future = Future() + self._exit_future = self._portal.start_task_soon(self.run_async_cm) + return self._enter_future.result() + + def __exit__( + self, + __exc_type: type[BaseException] | None, + __exc_value: BaseException | None, + __traceback: TracebackType | None, + ) -> bool | None: + self._exit_exc_info = __exc_type, __exc_value, __traceback + self._portal.call(self._exit_event.set) + return self._exit_future.result() + + +class _BlockingPortalTaskStatus(TaskStatus): + def __init__(self, future: Future): + self._future = future + + def started(self, value: object = None) -> None: + self._future.set_result(value) + + +class BlockingPortal: + """ + An object that lets external threads run code in an asynchronous event loop. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + """ + + def __init__(self) -> None: + self._token = current_token() + self._event_loop_thread_id: int | None = get_ident() + self._stop_event = Event() + self._task_group = create_task_group() + + async def __aenter__(self) -> BlockingPortal: + await self._task_group.__aenter__() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> bool: + await self.stop() + return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) + + def _check_running(self) -> None: + if self._event_loop_thread_id is None: + raise RuntimeError("This portal is not running") + if self._event_loop_thread_id == get_ident(): + raise RuntimeError( + "This method cannot be called from the event loop thread" + ) + + async def sleep_until_stopped(self) -> None: + """Sleep until :meth:`stop` is called.""" + await self._stop_event.wait() + + async def stop(self, cancel_remaining: bool = False) -> None: + """ + Signal the portal to shut down. + + This marks the portal as no longer accepting new calls and exits from + :meth:`sleep_until_stopped`. + + :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False`` + to let them finish before returning + + """ + self._event_loop_thread_id = None + self._stop_event.set() + if cancel_remaining: + self._task_group.cancel_scope.cancel("the blocking portal is shutting down") + + async def _call_func( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + future: Future[T_Retval], + ) -> None: + def callback(f: Future[T_Retval]) -> None: + if f.cancelled(): + if self._event_loop_thread_id == get_ident(): + scope.cancel("the future was cancelled") + elif self._event_loop_thread_id is not None: + self.call(scope.cancel, "the future was cancelled") + + try: + retval_or_awaitable = func(*args, **kwargs) + if isawaitable(retval_or_awaitable): + with CancelScope() as scope: + future.add_done_callback(callback) + retval = await retval_or_awaitable + else: + retval = retval_or_awaitable + except get_cancelled_exc_class(): + future.cancel() + future.set_running_or_notify_cancel() + except BaseException as exc: + if not future.cancelled(): + future.set_exception(exc) + + # Let base exceptions fall through + if not isinstance(exc, Exception): + raise + else: + if not future.cancelled(): + future.set_result(retval) + finally: + scope = None # type: ignore[assignment] + + def _spawn_task_from_thread( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + args: tuple[Unpack[PosArgsT]], + kwargs: dict[str, Any], + name: object, + future: Future[T_Retval], + ) -> None: + """ + Spawn a new task using the given callable. + + :param func: a callable + :param args: positional arguments to be passed to the callable + :param kwargs: keyword arguments to be passed to the callable + :param name: name of the task (will be coerced to a string if not ``None``) + :param future: a future that will resolve to the return value of the callable, + or the exception raised during its execution + + """ + run_sync( + partial(self._task_group.start_soon, name=name), + self._call_func, + func, + args, + kwargs, + future, + token=self._token, + ) + + @overload + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + ) -> T_Retval: ... + + @overload + def call( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: ... + + def call( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + ) -> T_Retval: + """ + Call the given function in the event loop thread. + + If the callable returns a coroutine object, it is awaited on. + + :param func: any callable + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + + """ + return cast(T_Retval, self.start_task_soon(func, *args).result()) + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + @overload + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: ... + + def start_task_soon( + self, + func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], + *args: Unpack[PosArgsT], + name: object = None, + ) -> Future[T_Retval]: + """ + Start a task in the portal's task group. + + The task will be run inside a cancel scope which can be cancelled by cancelling + the returned future. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a future that resolves with the return value of the callable if the + task completes successfully, or with the exception raised in the task + :raises RuntimeError: if the portal is not running or if this method is called + from within the event loop thread + :rtype: concurrent.futures.Future[T_Retval] + + .. versionadded:: 3.0 + + """ + self._check_running() + f: Future[T_Retval] = Future() + self._spawn_task_from_thread(func, args, {}, name, f) + return f + + def start_task( + self, + func: Callable[..., Awaitable[T_Retval]], + *args: object, + name: object = None, + ) -> tuple[Future[T_Retval], Any]: + """ + Start a task in the portal's task group and wait until it signals for readiness. + + This method works the same way as :meth:`.abc.TaskGroup.start`. + + :param func: the target function + :param args: positional arguments passed to ``func`` + :param name: name of the task (will be coerced to a string if not ``None``) + :return: a tuple of (future, task_status_value) where the ``task_status_value`` + is the value passed to ``task_status.started()`` from within the target + function + :rtype: tuple[concurrent.futures.Future[T_Retval], Any] + + .. versionadded:: 3.0 + + """ + + def task_done(future: Future[T_Retval]) -> None: + if not task_status_future.done(): + if future.cancelled(): + task_status_future.cancel() + elif future.exception(): + task_status_future.set_exception(future.exception()) + else: + exc = RuntimeError( + "Task exited without calling task_status.started()" + ) + task_status_future.set_exception(exc) + + self._check_running() + task_status_future: Future = Future() + task_status = _BlockingPortalTaskStatus(task_status_future) + f: Future = Future() + f.add_done_callback(task_done) + self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f) + return f, task_status_future.result() + + def wrap_async_context_manager( + self, cm: AbstractAsyncContextManager[T_co] + ) -> AbstractContextManager[T_co]: + """ + Wrap an async context manager as a synchronous context manager via this portal. + + Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping + in the middle until the synchronous context manager exits. + + :param cm: an asynchronous context manager + :return: a synchronous context manager + + .. versionadded:: 2.1 + + """ + return _BlockingAsyncContextManager(cm, self) + + +@dataclass +class BlockingPortalProvider: + """ + A manager for a blocking portal. Used as a context manager. The first thread to + enter this context manager causes a blocking portal to be started with the specific + parameters, and the last thread to exit causes the portal to be shut down. Thus, + there will be exactly one blocking portal running in this context as long as at + least one thread has entered this context manager. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + + .. versionadded:: 4.4 + """ + + backend: str = "asyncio" + backend_options: dict[str, Any] | None = None + _lock: Lock = field(init=False, default_factory=Lock) + _leases: int = field(init=False, default=0) + _portal: BlockingPortal = field(init=False) + _portal_cm: AbstractContextManager[BlockingPortal] | None = field( + init=False, default=None + ) + + def __enter__(self) -> BlockingPortal: + with self._lock: + if self._portal_cm is None: + self._portal_cm = start_blocking_portal( + self.backend, self.backend_options + ) + self._portal = self._portal_cm.__enter__() + + self._leases += 1 + return self._portal + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + portal_cm: AbstractContextManager[BlockingPortal] | None = None + with self._lock: + assert self._portal_cm + assert self._leases > 0 + self._leases -= 1 + if not self._leases: + portal_cm = self._portal_cm + self._portal_cm = None + del self._portal + + if portal_cm: + portal_cm.__exit__(None, None, None) + + +@contextmanager +def start_blocking_portal( + backend: str = "asyncio", + backend_options: dict[str, Any] | None = None, + *, + name: str | None = None, +) -> Generator[BlockingPortal, Any, None]: + """ + Start a new event loop in a new thread and run a blocking portal in its main task. + + The parameters are the same as for :func:`~anyio.run`. + + :param backend: name of the backend + :param backend_options: backend options + :param name: name of the thread + :return: a context manager that yields a blocking portal + + .. versionchanged:: 3.0 + Usage as a context manager is now required. + + """ + + async def run_portal() -> None: + async with BlockingPortal() as portal_: + if name is None: + current_thread().name = f"{backend}-portal-{id(portal_):x}" + + future.set_result(portal_) + await portal_.sleep_until_stopped() + + def run_blocking_portal() -> None: + if future.set_running_or_notify_cancel(): + try: + run_eventloop( + run_portal, backend=backend, backend_options=backend_options + ) + except BaseException as exc: + if not future.done(): + future.set_exception(exc) + + future: Future[BlockingPortal] = Future() + thread = Thread(target=run_blocking_portal, daemon=True, name=name) + thread.start() + try: + cancel_remaining_tasks = False + portal = future.result() + try: + yield portal + except BaseException: + cancel_remaining_tasks = True + raise + finally: + try: + portal.call(portal.stop, cancel_remaining_tasks) + except RuntimeError: + pass + finally: + thread.join() + + +def check_cancelled() -> None: + """ + Check if the cancel scope of the host task's running the current worker thread has + been cancelled. + + If the host task's current cancel scope has indeed been cancelled, the + backend-specific cancellation exception will be raised. + + :raises RuntimeError: if the current thread was not spawned by + :func:`.to_thread.run_sync` + + """ + try: + token: EventLoopToken = threadlocals.current_token + except AttributeError: + raise NoEventLoopError( + "This function can only be called inside an AnyIO worker thread" + ) from None + + token.backend_class.check_cancelled() diff --git a/venv/lib/python3.12/site-packages/anyio/functools.py b/venv/lib/python3.12/site-packages/anyio/functools.py new file mode 100644 index 0000000..b80afe6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/functools.py @@ -0,0 +1,375 @@ +from __future__ import annotations + +__all__ = ( + "AsyncCacheInfo", + "AsyncCacheParameters", + "AsyncLRUCacheWrapper", + "cache", + "lru_cache", + "reduce", +) + +import functools +import sys +from collections import OrderedDict +from collections.abc import ( + AsyncIterable, + Awaitable, + Callable, + Coroutine, + Hashable, + Iterable, +) +from functools import update_wrapper +from inspect import iscoroutinefunction +from typing import ( + Any, + Generic, + NamedTuple, + TypedDict, + TypeVar, + cast, + final, + overload, +) +from weakref import WeakKeyDictionary + +from ._core._synchronization import Lock +from .lowlevel import RunVar, checkpoint + +if sys.version_info >= (3, 11): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + +T = TypeVar("T") +S = TypeVar("S") +P = ParamSpec("P") +lru_cache_items: RunVar[ + WeakKeyDictionary[ + AsyncLRUCacheWrapper[Any, Any], + OrderedDict[Hashable, tuple[_InitialMissingType, Lock] | tuple[Any, None]], + ] +] = RunVar("lru_cache_items") + + +class _InitialMissingType: + pass + + +initial_missing: _InitialMissingType = _InitialMissingType() + + +class AsyncCacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int | None + currsize: int + + +class AsyncCacheParameters(TypedDict): + maxsize: int | None + typed: bool + always_checkpoint: bool + + +class _LRUMethodWrapper(Generic[T]): + def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object): + self.__wrapper = wrapper + self.__instance = instance + + def cache_info(self) -> AsyncCacheInfo: + return self.__wrapper.cache_info() + + def cache_parameters(self) -> AsyncCacheParameters: + return self.__wrapper.cache_parameters() + + def cache_clear(self) -> None: + self.__wrapper.cache_clear() + + async def __call__(self, *args: Any, **kwargs: Any) -> T: + if self.__instance is None: + return await self.__wrapper(*args, **kwargs) + + return await self.__wrapper(self.__instance, *args, **kwargs) + + +@final +class AsyncLRUCacheWrapper(Generic[P, T]): + def __init__( + self, + func: Callable[P, Awaitable[T]], + maxsize: int | None, + typed: bool, + always_checkpoint: bool, + ): + self.__wrapped__ = func + self._hits: int = 0 + self._misses: int = 0 + self._maxsize = max(maxsize, 0) if maxsize is not None else None + self._currsize: int = 0 + self._typed = typed + self._always_checkpoint = always_checkpoint + update_wrapper(self, func) + + def cache_info(self) -> AsyncCacheInfo: + return AsyncCacheInfo(self._hits, self._misses, self._maxsize, self._currsize) + + def cache_parameters(self) -> AsyncCacheParameters: + return { + "maxsize": self._maxsize, + "typed": self._typed, + "always_checkpoint": self._always_checkpoint, + } + + def cache_clear(self) -> None: + if cache := lru_cache_items.get(None): + cache.pop(self, None) + self._hits = self._misses = self._currsize = 0 + + async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: + # Easy case first: if maxsize == 0, no caching is done + if self._maxsize == 0: + value = await self.__wrapped__(*args, **kwargs) + self._misses += 1 + return value + + # The key is constructed as a flat tuple to avoid memory overhead + key: tuple[Any, ...] = args + if kwargs: + # initial_missing is used as a separator + key += (initial_missing,) + sum(kwargs.items(), ()) + + if self._typed: + key += tuple(type(arg) for arg in args) + if kwargs: + key += (initial_missing,) + tuple(type(val) for val in kwargs.values()) + + try: + cache = lru_cache_items.get() + except LookupError: + cache = WeakKeyDictionary() + lru_cache_items.set(cache) + + try: + cache_entry = cache[self] + except KeyError: + cache_entry = cache[self] = OrderedDict() + + cached_value: T | _InitialMissingType + try: + cached_value, lock = cache_entry[key] + except KeyError: + # We're the first task to call this function + cached_value, lock = ( + initial_missing, + Lock(fast_acquire=not self._always_checkpoint), + ) + cache_entry[key] = cached_value, lock + + if lock is None: + # The value was already cached + self._hits += 1 + cache_entry.move_to_end(key) + if self._always_checkpoint: + await checkpoint() + + return cast(T, cached_value) + + async with lock: + # Check if another task filled the cache while we acquired the lock + if (cached_value := cache_entry[key][0]) is initial_missing: + self._misses += 1 + if self._maxsize is not None and self._currsize >= self._maxsize: + cache_entry.popitem(last=False) + else: + self._currsize += 1 + + value = await self.__wrapped__(*args, **kwargs) + cache_entry[key] = value, None + else: + # Another task filled the cache while we were waiting for the lock + self._hits += 1 + cache_entry.move_to_end(key) + value = cast(T, cached_value) + + return value + + def __get__( + self, instance: object, owner: type | None = None + ) -> _LRUMethodWrapper[T]: + wrapper = _LRUMethodWrapper(self, instance) + update_wrapper(wrapper, self.__wrapped__) + return wrapper + + +class _LRUCacheWrapper(Generic[T]): + def __init__(self, maxsize: int | None, typed: bool, always_checkpoint: bool): + self._maxsize = maxsize + self._typed = typed + self._always_checkpoint = always_checkpoint + + @overload + def __call__( # type: ignore[overload-overlap] + self, func: Callable[P, Coroutine[Any, Any, T]], / + ) -> AsyncLRUCacheWrapper[P, T]: ... + + @overload + def __call__( + self, func: Callable[..., T], / + ) -> functools._lru_cache_wrapper[T]: ... + + def __call__( + self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], / + ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]: + if iscoroutinefunction(f): + return AsyncLRUCacheWrapper( + f, self._maxsize, self._typed, self._always_checkpoint + ) + + return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f) # type: ignore[arg-type] + + +@overload +def cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def cache( + func: Callable[..., T] | Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]: + """ + A convenient shortcut for :func:`lru_cache` with ``maxsize=None``. + + This is the asynchronous equivalent to :func:`functools.cache`. + + """ + return lru_cache(maxsize=None)(func) + + +@overload +def lru_cache( + *, maxsize: int | None = ..., typed: bool = ..., always_checkpoint: bool = ... +) -> _LRUCacheWrapper[Any]: ... + + +@overload +def lru_cache( # type: ignore[overload-overlap] + func: Callable[P, Coroutine[Any, Any, T]], / +) -> AsyncLRUCacheWrapper[P, T]: ... + + +@overload +def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... + + +def lru_cache( + func: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T] | None = None, + /, + *, + maxsize: int | None = 128, + typed: bool = False, + always_checkpoint: bool = False, +) -> ( + AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T] | _LRUCacheWrapper[Any] +): + """ + An asynchronous version of :func:`functools.lru_cache`. + + If a synchronous function is passed, the standard library + :func:`functools.lru_cache` is applied instead. + + :param always_checkpoint: if ``True``, every call to the cached function will be + guaranteed to yield control to the event loop at least once + + .. note:: Caches and locks are managed on a per-event loop basis. + + """ + if func is None: + return _LRUCacheWrapper[Any](maxsize, typed, always_checkpoint) + + if not callable(func): + raise TypeError("the first argument must be callable") + + return _LRUCacheWrapper[T](maxsize, typed, always_checkpoint)(func) + + +@overload +async def reduce( + function: Callable[[T, S], Awaitable[T]], + iterable: Iterable[S] | AsyncIterable[S], + /, + initial: T, +) -> T: ... + + +@overload +async def reduce( + function: Callable[[T, T], Awaitable[T]], + iterable: Iterable[T] | AsyncIterable[T], + /, +) -> T: ... + + +async def reduce( # type: ignore[misc] + function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]], + iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S], + /, + initial: T | _InitialMissingType = initial_missing, +) -> T: + """ + Asynchronous version of :func:`functools.reduce`. + + :param function: a coroutine function that takes two arguments: the accumulated + value and the next element from the iterable + :param iterable: an iterable or async iterable + :param initial: the initial value (if missing, the first element of the iterable is + used as the initial value) + + """ + element: Any + function_called = False + if isinstance(iterable, AsyncIterable): + async_it = iterable.__aiter__() + if initial is initial_missing: + try: + value = cast(T, await async_it.__anext__()) + except StopAsyncIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + async for element in async_it: + value = await function(value, element) + function_called = True + elif isinstance(iterable, Iterable): + it = iter(iterable) + if initial is initial_missing: + try: + value = cast(T, next(it)) + except StopIteration: + raise TypeError( + "reduce() of empty sequence with no initial value" + ) from None + else: + value = cast(T, initial) + + for element in it: + value = await function(value, element) + function_called = True + else: + raise TypeError("reduce() argument 2 must be an iterable or async iterable") + + # Make sure there is at least one checkpoint, even if an empty iterable and an + # initial value were given + if not function_called: + await checkpoint() + + return value diff --git a/venv/lib/python3.12/site-packages/anyio/lowlevel.py b/venv/lib/python3.12/site-packages/anyio/lowlevel.py new file mode 100644 index 0000000..ffbb75a --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/lowlevel.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +__all__ = ( + "EventLoopToken", + "RunvarToken", + "RunVar", + "checkpoint", + "checkpoint_if_cancelled", + "cancel_shielded_checkpoint", + "current_token", +) + +import enum +from dataclasses import dataclass +from types import TracebackType +from typing import Any, Generic, Literal, TypeVar, final, overload +from weakref import WeakKeyDictionary + +from ._core._eventloop import get_async_backend +from .abc import AsyncBackend + +T = TypeVar("T") +D = TypeVar("D") + + +async def checkpoint() -> None: + """ + Check for cancellation and allow the scheduler to switch to another task. + + Equivalent to (but more efficient than):: + + await checkpoint_if_cancelled() + await cancel_shielded_checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint() + + +async def checkpoint_if_cancelled() -> None: + """ + Enter a checkpoint if the enclosing cancel scope has been cancelled. + + This does not allow the scheduler to switch to a different task. + + .. versionadded:: 3.0 + + """ + await get_async_backend().checkpoint_if_cancelled() + + +async def cancel_shielded_checkpoint() -> None: + """ + Allow the scheduler to switch to another task but without checking for cancellation. + + Equivalent to (but potentially more efficient than):: + + with CancelScope(shield=True): + await checkpoint() + + .. versionadded:: 3.0 + + """ + await get_async_backend().cancel_shielded_checkpoint() + + +@final +@dataclass(frozen=True, repr=False) +class EventLoopToken: + """ + An opaque object that holds a reference to an event loop. + + .. versionadded:: 4.11.0 + """ + + backend_class: type[AsyncBackend] + native_token: object + + +def current_token() -> EventLoopToken: + """ + Return a token object that can be used to call code in the current event loop from + another thread. + + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + .. versionadded:: 4.11.0 + + """ + backend_class = get_async_backend() + raw_token = backend_class.current_token() + return EventLoopToken(backend_class, raw_token) + + +_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary() + + +class _NoValueSet(enum.Enum): + NO_VALUE_SET = enum.auto() + + +class RunvarToken(Generic[T]): + __slots__ = "_var", "_value", "_redeemed" + + def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): + self._var = var + self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value + self._redeemed = False + + def __enter__(self) -> RunvarToken[T]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self._var.reset(self) + + +class RunVar(Generic[T]): + """ + Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. + + Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that + will reset the variable to its previous value when the context block is exited. + """ + + __slots__ = "_name", "_default" + + NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET + + def __init__( + self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ): + self._name = name + self._default = default + + @property + def _current_vars(self) -> dict[RunVar[T], T]: + native_token = current_token().native_token + try: + return _run_vars[native_token] + except KeyError: + run_vars = _run_vars[native_token] = {} + return run_vars + + @overload + def get(self, default: D) -> T | D: ... + + @overload + def get(self) -> T: ... + + def get( + self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET + ) -> T | D: + try: + return self._current_vars[self] + except KeyError: + if default is not RunVar.NO_VALUE_SET: + return default + elif self._default is not RunVar.NO_VALUE_SET: + return self._default + + raise LookupError( + f'Run variable "{self._name}" has no value and no default set' + ) + + def set(self, value: T) -> RunvarToken[T]: + current_vars = self._current_vars + token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET)) + current_vars[self] = value + return token + + def reset(self, token: RunvarToken[T]) -> None: + if token._var is not self: + raise ValueError("This token does not belong to this RunVar") + + if token._redeemed: + raise ValueError("This token has already been used") + + if token._value is _NoValueSet.NO_VALUE_SET: + try: + del self._current_vars[self] + except KeyError: + pass + else: + self._current_vars[self] = token._value + + token._redeemed = True + + def __repr__(self) -> str: + return f"" diff --git a/venv/lib/python3.12/site-packages/anyio/py.typed b/venv/lib/python3.12/site-packages/anyio/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/anyio/pytest_plugin.py b/venv/lib/python3.12/site-packages/anyio/pytest_plugin.py new file mode 100644 index 0000000..4222816 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/pytest_plugin.py @@ -0,0 +1,302 @@ +from __future__ import annotations + +import socket +import sys +from collections.abc import Callable, Generator, Iterator +from contextlib import ExitStack, contextmanager +from inspect import isasyncgenfunction, iscoroutinefunction, ismethod +from typing import Any, cast + +import pytest +from _pytest.fixtures import SubRequest +from _pytest.outcomes import Exit + +from . import get_available_backends +from ._core._eventloop import ( + current_async_library, + get_async_backend, + reset_current_async_library, + set_current_async_library, +) +from ._core._exceptions import iterate_exceptions +from .abc import TestRunner + +if sys.version_info < (3, 11): + from exceptiongroup import ExceptionGroup + +_current_runner: TestRunner | None = None +_runner_stack: ExitStack | None = None +_runner_leases = 0 + + +def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]: + if isinstance(backend, str): + return backend, {} + elif isinstance(backend, tuple) and len(backend) == 2: + if isinstance(backend[0], str) and isinstance(backend[1], dict): + return cast(tuple[str, dict[str, Any]], backend) + + raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") + + +@contextmanager +def get_runner( + backend_name: str, backend_options: dict[str, Any] +) -> Iterator[TestRunner]: + global _current_runner, _runner_leases, _runner_stack + if _current_runner is None: + asynclib = get_async_backend(backend_name) + _runner_stack = ExitStack() + if current_async_library() is None: + # Since we're in control of the event loop, we can cache the name of the + # async library + token = set_current_async_library(backend_name) + _runner_stack.callback(reset_current_async_library, token) + + backend_options = backend_options or {} + _current_runner = _runner_stack.enter_context( + asynclib.create_test_runner(backend_options) + ) + + _runner_leases += 1 + try: + yield _current_runner + finally: + _runner_leases -= 1 + if not _runner_leases: + assert _runner_stack is not None + _runner_stack.close() + _runner_stack = _current_runner = None + + +def pytest_addoption(parser: pytest.Parser) -> None: + parser.addini( + "anyio_mode", + default="strict", + help='AnyIO plugin mode (either "strict" or "auto")', + ) + + +def pytest_configure(config: pytest.Config) -> None: + config.addinivalue_line( + "markers", + "anyio: mark the (coroutine function) test to be run asynchronously via anyio.", + ) + if ( + config.getini("anyio_mode") == "auto" + and config.pluginmanager.has_plugin("asyncio") + and config.getini("asyncio_mode") == "auto" + ): + config.issue_config_time_warning( + pytest.PytestConfigWarning( + "AnyIO auto mode has been enabled together with pytest-asyncio auto " + "mode. This may cause unexpected behavior." + ), + 1, + ) + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]: + def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any: + # Rebind any fixture methods to the request instance + if ( + request.instance + and ismethod(func) + and type(func.__self__) is type(request.instance) + ): + local_func = func.__func__.__get__(request.instance) + else: + local_func = func + + backend_name, backend_options = extract_backend_and_options(anyio_backend) + if has_backend_arg: + kwargs["anyio_backend"] = anyio_backend + + if has_request_arg: + kwargs["request"] = request + + with get_runner(backend_name, backend_options) as runner: + if isasyncgenfunction(local_func): + yield from runner.run_asyncgen_fixture(local_func, kwargs) + else: + yield runner.run_fixture(local_func, kwargs) + + # Only apply this to coroutine functions and async generator functions in requests + # that involve the anyio_backend fixture + func = fixturedef.func + if isasyncgenfunction(func) or iscoroutinefunction(func): + if "anyio_backend" in request.fixturenames: + fixturedef.func = wrapper + original_argname = fixturedef.argnames + + if not (has_backend_arg := "anyio_backend" in fixturedef.argnames): + fixturedef.argnames += ("anyio_backend",) + + if not (has_request_arg := "request" in fixturedef.argnames): + fixturedef.argnames += ("request",) + + try: + return (yield) + finally: + fixturedef.func = func + fixturedef.argnames = original_argname + + return (yield) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pycollect_makeitem( + collector: pytest.Module | pytest.Class, name: str, obj: object +) -> None: + if collector.istestfunction(obj, name): + inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj + if iscoroutinefunction(inner_func): + anyio_auto_mode = collector.config.getini("anyio_mode") == "auto" + marker = collector.get_closest_marker("anyio") + own_markers = getattr(obj, "pytestmark", ()) + if ( + anyio_auto_mode + or marker + or any(marker.name == "anyio" for marker in own_markers) + ): + pytest.mark.usefixtures("anyio_backend")(obj) + + +@pytest.hookimpl(tryfirst=True) +def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None: + def run_with_hypothesis(**kwargs: Any) -> None: + with get_runner(backend_name, backend_options) as runner: + runner.run_test(original_func, kwargs) + + backend = pyfuncitem.funcargs.get("anyio_backend") + if backend: + backend_name, backend_options = extract_backend_and_options(backend) + + if hasattr(pyfuncitem.obj, "hypothesis"): + # Wrap the inner test function unless it's already wrapped + original_func = pyfuncitem.obj.hypothesis.inner_test + if original_func.__qualname__ != run_with_hypothesis.__qualname__: + if iscoroutinefunction(original_func): + pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis + + return None + + if iscoroutinefunction(pyfuncitem.obj): + funcargs = pyfuncitem.funcargs + testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} + with get_runner(backend_name, backend_options) as runner: + try: + runner.run_test(pyfuncitem.obj, testargs) + except ExceptionGroup as excgrp: + for exc in iterate_exceptions(excgrp): + if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)): + raise exc from excgrp + + raise + + return True + + return None + + +@pytest.fixture(scope="module", params=get_available_backends()) +def anyio_backend(request: Any) -> Any: + return request.param + + +@pytest.fixture +def anyio_backend_name(anyio_backend: Any) -> str: + if isinstance(anyio_backend, str): + return anyio_backend + else: + return anyio_backend[0] + + +@pytest.fixture +def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]: + if isinstance(anyio_backend, str): + return {} + else: + return anyio_backend[1] + + +class FreePortFactory: + """ + Manages port generation based on specified socket kind, ensuring no duplicate + ports are generated. + + This class provides functionality for generating available free ports on the + system. It is initialized with a specific socket kind and can generate ports + for given address families while avoiding reuse of previously generated ports. + + Users should not instantiate this class directly, but use the + ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple + uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead. + """ + + def __init__(self, kind: socket.SocketKind) -> None: + self._kind = kind + self._generated = set[int]() + + @property + def kind(self) -> socket.SocketKind: + """ + The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or + :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability + + """ + return self._kind + + def __call__(self, family: socket.AddressFamily | None = None) -> int: + """ + Return an unbound port for the given address family. + + :param family: if omitted, both IPv4 and IPv6 addresses will be tried + :return: a port number + + """ + if family is not None: + families = [family] + else: + families = [socket.AF_INET] + if socket.has_ipv6: + families.append(socket.AF_INET6) + + while True: + port = 0 + with ExitStack() as stack: + for family in families: + sock = stack.enter_context(socket.socket(family, self._kind)) + addr = "::1" if family == socket.AF_INET6 else "127.0.0.1" + try: + sock.bind((addr, port)) + except OSError: + break + + if not port: + port = sock.getsockname()[1] + else: + if port not in self._generated: + self._generated.add(port) + return port + + +@pytest.fixture(scope="session") +def free_tcp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_STREAM) + + +@pytest.fixture(scope="session") +def free_udp_port_factory() -> FreePortFactory: + return FreePortFactory(socket.SOCK_DGRAM) + + +@pytest.fixture +def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int: + return free_tcp_port_factory() + + +@pytest.fixture +def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int: + return free_udp_port_factory() diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__init__.py b/venv/lib/python3.12/site-packages/anyio/streams/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..447c27ba8fdf8c3ec73d08d938aafc6d82de2ae8 GIT binary patch literal 215 zcmZ8bI|>3Z5Z$;6B6tTovCu(n#mZW|L5S<+}Kv4U`C094P!3H^V+Q1gC zqn*`fOdNwzzCg*THgxoo(N>foR9IG!(;kd>sV`MJGvk~7s?Z@4*R~gGh%nrWZ!CncaZo?GwLH84 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/buffered.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df1ba8d0af129d7ad5ffb2731056b6540255ff33 GIT binary patch literal 9094 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/file.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..264c733ef6e15963cc79cd7fcc2307d4284f3418 GIT binary patch literal 7595 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 W5};;>=n5Kp#klgq-w6<0oc{(!HnDF2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/memory.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f7ac35fa26fb915dd5af71bf30a835dd71b213a GIT binary patch literal 15046 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 AO#lD@ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc b/venv/lib/python3.12/site-packages/anyio/streams/__pycache__/stapled.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21355d6dcf06743ebadbdd662cc0566aa0bc23f7 GIT binary patch literal 7615 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 lJj~-fU^=gE(JIz1bM#>VQuN-mrJJu literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/anyio/streams/buffered.py b/venv/lib/python3.12/site-packages/anyio/streams/buffered.py new file mode 100644 index 0000000..57c7cd7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/buffered.py @@ -0,0 +1,188 @@ +from __future__ import annotations + +__all__ = ( + "BufferedByteReceiveStream", + "BufferedByteStream", + "BufferedConnectable", +) + +import sys +from collections.abc import Callable, Iterable, Mapping +from dataclasses import dataclass, field +from typing import Any, SupportsIndex + +from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead +from ..abc import ( + AnyByteReceiveStream, + AnyByteStream, + AnyByteStreamConnectable, + ByteReceiveStream, + ByteStream, + ByteStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class BufferedByteReceiveStream(ByteReceiveStream): + """ + Wraps any bytes-based receive stream and uses a buffer to provide sophisticated + receiving capabilities in the form of a byte stream. + """ + + receive_stream: AnyByteReceiveStream + _buffer: bytearray = field(init=False, default_factory=bytearray) + _closed: bool = field(init=False, default=False) + + async def aclose(self) -> None: + await self.receive_stream.aclose() + self._closed = True + + @property + def buffer(self) -> bytes: + """The bytes currently in the buffer.""" + return bytes(self._buffer) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.receive_stream.extra_attributes + + def feed_data(self, data: Iterable[SupportsIndex], /) -> None: + """ + Append data directly into the buffer. + + Any data in the buffer will be consumed by receive operations before receiving + anything from the wrapped stream. + + :param data: the data to append to the buffer (can be bytes or anything else + that supports ``__index__()``) + + """ + self._buffer.extend(data) + + async def receive(self, max_bytes: int = 65536) -> bytes: + if self._closed: + raise ClosedResourceError + + if self._buffer: + chunk = bytes(self._buffer[:max_bytes]) + del self._buffer[:max_bytes] + return chunk + elif isinstance(self.receive_stream, ByteReceiveStream): + return await self.receive_stream.receive(max_bytes) + else: + # With a bytes-oriented object stream, we need to handle any surplus bytes + # we get from the receive() call + chunk = await self.receive_stream.receive() + if len(chunk) > max_bytes: + # Save the surplus bytes in the buffer + self._buffer.extend(chunk[max_bytes:]) + return chunk[:max_bytes] + else: + return chunk + + async def receive_exactly(self, nbytes: int) -> bytes: + """ + Read exactly the given amount of bytes from the stream. + + :param nbytes: the number of bytes to read + :return: the bytes read + :raises ~anyio.IncompleteRead: if the stream was closed before the requested + amount of bytes could be read from the stream + + """ + while True: + remaining = nbytes - len(self._buffer) + if remaining <= 0: + retval = self._buffer[:nbytes] + del self._buffer[:nbytes] + return bytes(retval) + + try: + if isinstance(self.receive_stream, ByteReceiveStream): + chunk = await self.receive_stream.receive(remaining) + else: + chunk = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + self._buffer.extend(chunk) + + async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes: + """ + Read from the stream until the delimiter is found or max_bytes have been read. + + :param delimiter: the marker to look for in the stream + :param max_bytes: maximum number of bytes that will be read before raising + :exc:`~anyio.DelimiterNotFound` + :return: the bytes read (not including the delimiter) + :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter + was found + :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the + bytes read up to the maximum allowed + + """ + delimiter_size = len(delimiter) + offset = 0 + while True: + # Check if the delimiter can be found in the current buffer + index = self._buffer.find(delimiter, offset) + if index >= 0: + found = self._buffer[:index] + del self._buffer[: index + len(delimiter) :] + return bytes(found) + + # Check if the buffer is already at or over the limit + if len(self._buffer) >= max_bytes: + raise DelimiterNotFound(max_bytes) + + # Read more data into the buffer from the socket + try: + data = await self.receive_stream.receive() + except EndOfStream as exc: + raise IncompleteRead from exc + + # Move the offset forward and add the new data to the buffer + offset = max(len(self._buffer) - delimiter_size + 1, 0) + self._buffer.extend(data) + + +class BufferedByteStream(BufferedByteReceiveStream, ByteStream): + """ + A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed + through to the wrapped stream as-is. + """ + + def __init__(self, stream: AnyByteStream): + """ + :param stream: the stream to be wrapped + + """ + super().__init__(stream) + self._stream = stream + + @override + async def send_eof(self) -> None: + await self._stream.send_eof() + + @override + async def send(self, item: bytes) -> None: + await self._stream.send(item) + + +class BufferedConnectable(ByteStreamConnectable): + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the connectable to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> BufferedByteStream: + stream = await self.connectable.connect() + return BufferedByteStream(stream) diff --git a/venv/lib/python3.12/site-packages/anyio/streams/file.py b/venv/lib/python3.12/site-packages/anyio/streams/file.py new file mode 100644 index 0000000..82d2da8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/file.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +__all__ = ( + "FileReadStream", + "FileStreamAttribute", + "FileWriteStream", +) + +from collections.abc import Callable, Mapping +from io import SEEK_SET, UnsupportedOperation +from os import PathLike +from pathlib import Path +from typing import Any, BinaryIO, cast + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + TypedAttributeSet, + to_thread, + typed_attribute, +) +from ..abc import ByteReceiveStream, ByteSendStream + + +class FileStreamAttribute(TypedAttributeSet): + #: the open file descriptor + file: BinaryIO = typed_attribute() + #: the path of the file on the file system, if available (file must be a real file) + path: Path = typed_attribute() + #: the file number, if available (file must be a real file or a TTY) + fileno: int = typed_attribute() + + +class _BaseFileStream: + def __init__(self, file: BinaryIO): + self._file = file + + async def aclose(self) -> None: + await to_thread.run_sync(self._file.close) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict[Any, Callable[[], Any]] = { + FileStreamAttribute.file: lambda: self._file, + } + + if hasattr(self._file, "name"): + attributes[FileStreamAttribute.path] = lambda: Path(self._file.name) + + try: + self._file.fileno() + except UnsupportedOperation: + pass + else: + attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno() + + return attributes + + +class FileReadStream(_BaseFileStream, ByteReceiveStream): + """ + A byte stream that reads from a file in the file system. + + :param file: a file that has been opened for reading in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path(cls, path: str | PathLike[str]) -> FileReadStream: + """ + Create a file read stream by opening the given file. + + :param path: path of the file to read from + + """ + file = await to_thread.run_sync(Path(path).open, "rb") + return cls(cast(BinaryIO, file)) + + async def receive(self, max_bytes: int = 65536) -> bytes: + try: + data = await to_thread.run_sync(self._file.read, max_bytes) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc + + if data: + return data + else: + raise EndOfStream + + async def seek(self, position: int, whence: int = SEEK_SET) -> int: + """ + Seek the file to the given position. + + .. seealso:: :meth:`io.IOBase.seek` + + .. note:: Not all file descriptors are seekable. + + :param position: position to seek the file to + :param whence: controls how ``position`` is interpreted + :return: the new absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.seek, position, whence) + + async def tell(self) -> int: + """ + Return the current stream position. + + .. note:: Not all file descriptors are seekable. + + :return: the current absolute position + :raises OSError: if the file is not seekable + + """ + return await to_thread.run_sync(self._file.tell) + + +class FileWriteStream(_BaseFileStream, ByteSendStream): + """ + A byte stream that writes to a file in the file system. + + :param file: a file that has been opened for writing in binary mode + + .. versionadded:: 3.0 + """ + + @classmethod + async def from_path( + cls, path: str | PathLike[str], append: bool = False + ) -> FileWriteStream: + """ + Create a file write stream by opening the given file for writing. + + :param path: path of the file to write to + :param append: if ``True``, open the file for appending; if ``False``, any + existing file at the given path will be truncated + + """ + mode = "ab" if append else "wb" + file = await to_thread.run_sync(Path(path).open, mode) + return cls(cast(BinaryIO, file)) + + async def send(self, item: bytes) -> None: + try: + await to_thread.run_sync(self._file.write, item) + except ValueError: + raise ClosedResourceError from None + except OSError as exc: + raise BrokenResourceError from exc diff --git a/venv/lib/python3.12/site-packages/anyio/streams/memory.py b/venv/lib/python3.12/site-packages/anyio/streams/memory.py new file mode 100644 index 0000000..a3fa0c3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/memory.py @@ -0,0 +1,325 @@ +from __future__ import annotations + +__all__ = ( + "MemoryObjectReceiveStream", + "MemoryObjectSendStream", + "MemoryObjectStreamStatistics", +) + +import warnings +from collections import OrderedDict, deque +from dataclasses import dataclass, field +from types import TracebackType +from typing import Generic, NamedTuple, TypeVar + +from .. import ( + BrokenResourceError, + ClosedResourceError, + EndOfStream, + WouldBlock, +) +from .._core._testing import TaskInfo, get_current_task +from ..abc import Event, ObjectReceiveStream, ObjectSendStream +from ..lowlevel import checkpoint + +T_Item = TypeVar("T_Item") +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + + +class MemoryObjectStreamStatistics(NamedTuple): + current_buffer_used: int #: number of items stored in the buffer + #: maximum number of items that can be stored on this stream (or :data:`math.inf`) + max_buffer_size: float + open_send_streams: int #: number of unclosed clones of the send stream + open_receive_streams: int #: number of unclosed clones of the receive stream + #: number of tasks blocked on :meth:`MemoryObjectSendStream.send` + tasks_waiting_send: int + #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive` + tasks_waiting_receive: int + + +@dataclass(eq=False) +class _MemoryObjectItemReceiver(Generic[T_Item]): + task_info: TaskInfo = field(init=False, default_factory=get_current_task) + item: T_Item = field(init=False) + + def __repr__(self) -> str: + # When item is not defined, we get following error with default __repr__: + # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item' + item = getattr(self, "item", None) + return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})" + + +@dataclass(eq=False) +class _MemoryObjectStreamState(Generic[T_Item]): + max_buffer_size: float = field() + buffer: deque[T_Item] = field(init=False, default_factory=deque) + open_send_channels: int = field(init=False, default=0) + open_receive_channels: int = field(init=False, default=0) + waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field( + init=False, default_factory=OrderedDict + ) + waiting_senders: OrderedDict[Event, T_Item] = field( + init=False, default_factory=OrderedDict + ) + + def statistics(self) -> MemoryObjectStreamStatistics: + return MemoryObjectStreamStatistics( + len(self.buffer), + self.max_buffer_size, + self.open_send_channels, + self.open_receive_channels, + len(self.waiting_senders), + len(self.waiting_receivers), + ) + + +@dataclass(eq=False) +class MemoryObjectReceiveStream(Generic[T_co], ObjectReceiveStream[T_co]): + _state: _MemoryObjectStreamState[T_co] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_receive_channels += 1 + + def receive_nowait(self) -> T_co: + """ + Receive the next item if it can be done without waiting. + + :return: the received item + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been + closed from the sending end + :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks + waiting to send + + """ + if self._closed: + raise ClosedResourceError + + if self._state.waiting_senders: + # Get the item from the next sender + send_event, item = self._state.waiting_senders.popitem(last=False) + self._state.buffer.append(item) + send_event.set() + + if self._state.buffer: + return self._state.buffer.popleft() + elif not self._state.open_send_channels: + raise EndOfStream + + raise WouldBlock + + async def receive(self) -> T_co: + await checkpoint() + try: + return self.receive_nowait() + except WouldBlock: + # Add ourselves in the queue + receive_event = Event() + receiver = _MemoryObjectItemReceiver[T_co]() + self._state.waiting_receivers[receive_event] = receiver + + try: + await receive_event.wait() + finally: + self._state.waiting_receivers.pop(receive_event, None) + + try: + return receiver.item + except AttributeError: + raise EndOfStream from None + + def clone(self) -> MemoryObjectReceiveStream[T_co]: + """ + Create a clone of this receive stream. + + Each clone can be closed separately. Only when all clones have been closed will + the receiving end of the memory stream be considered closed by the sending ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectReceiveStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_receive_channels -= 1 + if self._state.open_receive_channels == 0: + send_events = list(self._state.waiting_senders.keys()) + for event in send_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectReceiveStream[T_co]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) + + +@dataclass(eq=False) +class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]): + _state: _MemoryObjectStreamState[T_contra] + _closed: bool = field(init=False, default=False) + + def __post_init__(self) -> None: + self._state.open_send_channels += 1 + + def send_nowait(self, item: T_contra) -> None: + """ + Send an item immediately if it can be done without waiting. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting + to receive + + """ + if self._closed: + raise ClosedResourceError + if not self._state.open_receive_channels: + raise BrokenResourceError + + while self._state.waiting_receivers: + receive_event, receiver = self._state.waiting_receivers.popitem(last=False) + if not receiver.task_info.has_pending_cancellation(): + receiver.item = item + receive_event.set() + return + + if len(self._state.buffer) < self._state.max_buffer_size: + self._state.buffer.append(item) + else: + raise WouldBlock + + async def send(self, item: T_contra) -> None: + """ + Send an item to the stream. + + If the buffer is full, this method blocks until there is again room in the + buffer or the item can be sent directly to a receiver. + + :param item: the item to send + :raises ~anyio.ClosedResourceError: if this send stream has been closed + :raises ~anyio.BrokenResourceError: if the stream has been closed from the + receiving end + + """ + await checkpoint() + try: + self.send_nowait(item) + except WouldBlock: + # Wait until there's someone on the receiving end + send_event = Event() + self._state.waiting_senders[send_event] = item + try: + await send_event.wait() + except BaseException: + self._state.waiting_senders.pop(send_event, None) + raise + + if send_event in self._state.waiting_senders: + del self._state.waiting_senders[send_event] + raise BrokenResourceError from None + + def clone(self) -> MemoryObjectSendStream[T_contra]: + """ + Create a clone of this send stream. + + Each clone can be closed separately. Only when all clones have been closed will + the sending end of the memory stream be considered closed by the receiving ends. + + :return: the cloned stream + + """ + if self._closed: + raise ClosedResourceError + + return MemoryObjectSendStream(_state=self._state) + + def close(self) -> None: + """ + Close the stream. + + This works the exact same way as :meth:`aclose`, but is provided as a special + case for the benefit of synchronous callbacks. + + """ + if not self._closed: + self._closed = True + self._state.open_send_channels -= 1 + if self._state.open_send_channels == 0: + receive_events = list(self._state.waiting_receivers.keys()) + self._state.waiting_receivers.clear() + for event in receive_events: + event.set() + + async def aclose(self) -> None: + self.close() + + def statistics(self) -> MemoryObjectStreamStatistics: + """ + Return statistics about the current state of this stream. + + .. versionadded:: 3.0 + """ + return self._state.statistics() + + def __enter__(self) -> MemoryObjectSendStream[T_contra]: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> None: + self.close() + + def __del__(self) -> None: + if not self._closed: + warnings.warn( + f"Unclosed <{self.__class__.__name__} at {id(self):x}>", + ResourceWarning, + stacklevel=1, + source=self, + ) diff --git a/venv/lib/python3.12/site-packages/anyio/streams/stapled.py b/venv/lib/python3.12/site-packages/anyio/streams/stapled.py new file mode 100644 index 0000000..9248b68 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/stapled.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +__all__ = ( + "MultiListener", + "StapledByteStream", + "StapledObjectStream", +) + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import Any, Generic, TypeVar + +from ..abc import ( + ByteReceiveStream, + ByteSendStream, + ByteStream, + Listener, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + TaskGroup, +) + +T_Item = TypeVar("T_Item") +T_Stream = TypeVar("T_Stream") + + +@dataclass(eq=False) +class StapledByteStream(ByteStream): + """ + Combines two byte streams into a single, bidirectional byte stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ByteSendStream send_stream: the sending byte stream + :param ByteReceiveStream receive_stream: the receiving byte stream + """ + + send_stream: ByteSendStream + receive_stream: ByteReceiveStream + + async def receive(self, max_bytes: int = 65536) -> bytes: + return await self.receive_stream.receive(max_bytes) + + async def send(self, item: bytes) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]): + """ + Combines two object streams into a single, bidirectional object stream. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param ObjectSendStream send_stream: the sending object stream + :param ObjectReceiveStream receive_stream: the receiving object stream + """ + + send_stream: ObjectSendStream[T_Item] + receive_stream: ObjectReceiveStream[T_Item] + + async def receive(self) -> T_Item: + return await self.receive_stream.receive() + + async def send(self, item: T_Item) -> None: + await self.send_stream.send(item) + + async def send_eof(self) -> None: + await self.send_stream.aclose() + + async def aclose(self) -> None: + await self.send_stream.aclose() + await self.receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.send_stream.extra_attributes, + **self.receive_stream.extra_attributes, + } + + +@dataclass(eq=False) +class MultiListener(Generic[T_Stream], Listener[T_Stream]): + """ + Combines multiple listeners into one, serving connections from all of them at once. + + Any MultiListeners in the given collection of listeners will have their listeners + moved into this one. + + Extra attributes are provided from each listener, with each successive listener + overriding any conflicting attributes from the previous one. + + :param listeners: listeners to serve + :type listeners: Sequence[Listener[T_Stream]] + """ + + listeners: Sequence[Listener[T_Stream]] + + def __post_init__(self) -> None: + listeners: list[Listener[T_Stream]] = [] + for listener in self.listeners: + if isinstance(listener, MultiListener): + listeners.extend(listener.listeners) + del listener.listeners[:] # type: ignore[attr-defined] + else: + listeners.append(listener) + + self.listeners = listeners + + async def serve( + self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None + ) -> None: + from .. import create_task_group + + async with create_task_group() as tg: + for listener in self.listeners: + tg.start_soon(listener.serve, handler, task_group) + + async def aclose(self) -> None: + for listener in self.listeners: + await listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + attributes: dict = {} + for listener in self.listeners: + attributes.update(listener.extra_attributes) + + return attributes diff --git a/venv/lib/python3.12/site-packages/anyio/streams/text.py b/venv/lib/python3.12/site-packages/anyio/streams/text.py new file mode 100644 index 0000000..296cd25 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/text.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +__all__ = ( + "TextConnectable", + "TextReceiveStream", + "TextSendStream", + "TextStream", +) + +import codecs +import sys +from collections.abc import Callable, Mapping +from dataclasses import InitVar, dataclass, field +from typing import Any + +from ..abc import ( + AnyByteReceiveStream, + AnyByteSendStream, + AnyByteStream, + AnyByteStreamConnectable, + ObjectReceiveStream, + ObjectSendStream, + ObjectStream, + ObjectStreamConnectable, +) + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + + +@dataclass(eq=False) +class TextReceiveStream(ObjectReceiveStream[str]): + """ + Stream wrapper that decodes bytes to strings using the given encoding. + + Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any + completely received unicode characters as soon as they come in. + + :param transport_stream: any bytes-based receive stream + :param encoding: character encoding to use for decoding bytes to strings (defaults + to ``utf-8``) + :param errors: handling scheme for decoding errors (defaults to ``strict``; see the + `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteReceiveStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _decoder: codecs.IncrementalDecoder = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + decoder_class = codecs.getincrementaldecoder(encoding) + self._decoder = decoder_class(errors=errors) + + async def receive(self) -> str: + while True: + chunk = await self.transport_stream.receive() + decoded = self._decoder.decode(chunk) + if decoded: + return decoded + + async def aclose(self) -> None: + await self.transport_stream.aclose() + self._decoder.reset() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextSendStream(ObjectSendStream[str]): + """ + Sends strings to the wrapped stream as bytes using the given encoding. + + :param AnyByteSendStream transport_stream: any bytes-based send stream + :param str encoding: character encoding to use for encoding strings to bytes + (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteSendStream + encoding: InitVar[str] = "utf-8" + errors: str = "strict" + _encoder: Callable[..., tuple[bytes, int]] = field(init=False) + + def __post_init__(self, encoding: str) -> None: + self._encoder = codecs.getencoder(encoding) + + async def send(self, item: str) -> None: + encoded = self._encoder(item, self.errors)[0] + await self.transport_stream.send(encoded) + + async def aclose(self) -> None: + await self.transport_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return self.transport_stream.extra_attributes + + +@dataclass(eq=False) +class TextStream(ObjectStream[str]): + """ + A bidirectional stream that decodes bytes to strings on receive and encodes strings + to bytes on send. + + Extra attributes will be provided from both streams, with the receive stream + providing the values in case of a conflict. + + :param AnyByteStream transport_stream: any bytes-based stream + :param str encoding: character encoding to use for encoding/decoding strings to/from + bytes (defaults to ``utf-8``) + :param str errors: handling scheme for encoding errors (defaults to ``strict``; see + the `codecs module documentation`_ for a comprehensive list of options) + + .. _codecs module documentation: + https://docs.python.org/3/library/codecs.html#codec-objects + """ + + transport_stream: AnyByteStream + encoding: InitVar[str] = "utf-8" + errors: InitVar[str] = "strict" + _receive_stream: TextReceiveStream = field(init=False) + _send_stream: TextSendStream = field(init=False) + + def __post_init__(self, encoding: str, errors: str) -> None: + self._receive_stream = TextReceiveStream( + self.transport_stream, encoding=encoding, errors=errors + ) + self._send_stream = TextSendStream( + self.transport_stream, encoding=encoding, errors=errors + ) + + async def receive(self) -> str: + return await self._receive_stream.receive() + + async def send(self, item: str) -> None: + await self._send_stream.send(item) + + async def send_eof(self) -> None: + await self.transport_stream.send_eof() + + async def aclose(self) -> None: + await self._send_stream.aclose() + await self._receive_stream.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self._send_stream.extra_attributes, + **self._receive_stream.extra_attributes, + } + + +class TextConnectable(ObjectStreamConnectable[str]): + def __init__(self, connectable: AnyByteStreamConnectable): + """ + :param connectable: the bytestream endpoint to wrap + + """ + self.connectable = connectable + + @override + async def connect(self) -> TextStream: + stream = await self.connectable.connect() + return TextStream(stream) diff --git a/venv/lib/python3.12/site-packages/anyio/streams/tls.py b/venv/lib/python3.12/site-packages/anyio/streams/tls.py new file mode 100644 index 0000000..b507488 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/streams/tls.py @@ -0,0 +1,424 @@ +from __future__ import annotations + +__all__ = ( + "TLSAttribute", + "TLSConnectable", + "TLSListener", + "TLSStream", +) + +import logging +import re +import ssl +import sys +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from functools import wraps +from ssl import SSLContext +from typing import Any, TypeVar + +from .. import ( + BrokenResourceError, + EndOfStream, + aclose_forcefully, + get_cancelled_exc_class, + to_thread, +) +from .._core._typedattr import TypedAttributeSet, typed_attribute +from ..abc import ( + AnyByteStream, + AnyByteStreamConnectable, + ByteStream, + ByteStreamConnectable, + Listener, + TaskGroup, +) + +if sys.version_info >= (3, 10): + from typing import TypeAlias +else: + from typing_extensions import TypeAlias + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") +_PCTRTT: TypeAlias = tuple[tuple[str, str], ...] +_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...] + + +class TLSAttribute(TypedAttributeSet): + """Contains Transport Layer Security related attributes.""" + + #: the selected ALPN protocol + alpn_protocol: str | None = typed_attribute() + #: the channel binding for type ``tls-unique`` + channel_binding_tls_unique: bytes = typed_attribute() + #: the selected cipher + cipher: tuple[str, str, int] = typed_attribute() + #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert` + # for more information) + peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute() + #: the peer certificate in binary form + peer_certificate_binary: bytes | None = typed_attribute() + #: ``True`` if this is the server side of the connection + server_side: bool = typed_attribute() + #: ciphers shared by the client during the TLS handshake (``None`` if this is the + #: client side) + shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute() + #: the :class:`~ssl.SSLObject` used for encryption + ssl_object: ssl.SSLObject = typed_attribute() + #: ``True`` if this stream does (and expects) a closing TLS handshake when the + #: stream is being closed + standard_compatible: bool = typed_attribute() + #: the TLS protocol version (e.g. ``TLSv1.2``) + tls_version: str = typed_attribute() + + +@dataclass(eq=False) +class TLSStream(ByteStream): + """ + A stream wrapper that encrypts all sent data and decrypts received data. + + This class has no public initializer; use :meth:`wrap` instead. + All extra attributes from :class:`~TLSAttribute` are supported. + + :var AnyByteStream transport_stream: the wrapped stream + + """ + + transport_stream: AnyByteStream + standard_compatible: bool + _ssl_object: ssl.SSLObject + _read_bio: ssl.MemoryBIO + _write_bio: ssl.MemoryBIO + + @classmethod + async def wrap( + cls, + transport_stream: AnyByteStream, + *, + server_side: bool | None = None, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> TLSStream: + """ + Wrap an existing stream with Transport Layer Security. + + This performs a TLS handshake with the peer. + + :param transport_stream: a bytes-transporting stream to wrap + :param server_side: ``True`` if this is the server side of the connection, + ``False`` if this is the client side (if omitted, will be set to ``False`` + if ``hostname`` has been provided, ``False`` otherwise). Used only to create + a default context when an explicit context has not been provided. + :param hostname: host name of the peer (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure + default will be created) + :param standard_compatible: if ``False``, skip the closing handshake when + closing the connection, and don't raise an exception if the peer does the + same + :raises ~ssl.SSLError: if the TLS handshake fails + + """ + if server_side is None: + server_side = not hostname + + if not ssl_context: + purpose = ( + ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH + ) + ssl_context = ssl.create_default_context(purpose) + + # Re-enable detection of unexpected EOFs if it was disabled by Python + if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): + ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF + + bio_in = ssl.MemoryBIO() + bio_out = ssl.MemoryBIO() + + # External SSLContext implementations may do blocking I/O in wrap_bio(), + # but the standard library implementation won't + if type(ssl_context) is ssl.SSLContext: + ssl_object = ssl_context.wrap_bio( + bio_in, bio_out, server_side=server_side, server_hostname=hostname + ) + else: + ssl_object = await to_thread.run_sync( + ssl_context.wrap_bio, + bio_in, + bio_out, + server_side, + hostname, + None, + ) + + wrapper = cls( + transport_stream=transport_stream, + standard_compatible=standard_compatible, + _ssl_object=ssl_object, + _read_bio=bio_in, + _write_bio=bio_out, + ) + await wrapper._call_sslobject_method(ssl_object.do_handshake) + return wrapper + + async def _call_sslobject_method( + self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] + ) -> T_Retval: + while True: + try: + result = func(*args) + except ssl.SSLWantReadError: + try: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + data = await self.transport_stream.receive() + except EndOfStream: + self._read_bio.write_eof() + except OSError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + else: + self._read_bio.write(data) + except ssl.SSLWantWriteError: + await self.transport_stream.send(self._write_bio.read()) + except ssl.SSLSyscallError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + raise BrokenResourceError from exc + except ssl.SSLError as exc: + self._read_bio.write_eof() + self._write_bio.write_eof() + if isinstance(exc, ssl.SSLEOFError) or ( + exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror + ): + if self.standard_compatible: + raise BrokenResourceError from exc + else: + raise EndOfStream from None + + raise + else: + # Flush any pending writes first + if self._write_bio.pending: + await self.transport_stream.send(self._write_bio.read()) + + return result + + async def unwrap(self) -> tuple[AnyByteStream, bytes]: + """ + Does the TLS closing handshake. + + :return: a tuple of (wrapped byte stream, bytes left in the read buffer) + + """ + await self._call_sslobject_method(self._ssl_object.unwrap) + self._read_bio.write_eof() + self._write_bio.write_eof() + return self.transport_stream, self._read_bio.read() + + async def aclose(self) -> None: + if self.standard_compatible: + try: + await self.unwrap() + except BaseException: + await aclose_forcefully(self.transport_stream) + raise + + await self.transport_stream.aclose() + + async def receive(self, max_bytes: int = 65536) -> bytes: + data = await self._call_sslobject_method(self._ssl_object.read, max_bytes) + if not data: + raise EndOfStream + + return data + + async def send(self, item: bytes) -> None: + await self._call_sslobject_method(self._ssl_object.write, item) + + async def send_eof(self) -> None: + tls_version = self.extra(TLSAttribute.tls_version) + match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version) + if match: + major, minor = int(match.group(1)), int(match.group(2) or 0) + if (major, minor) < (1, 3): + raise NotImplementedError( + f"send_eof() requires at least TLSv1.3; current " + f"session uses {tls_version}" + ) + + raise NotImplementedError( + "send_eof() has not yet been implemented for TLS streams" + ) + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + **self.transport_stream.extra_attributes, + TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol, + TLSAttribute.channel_binding_tls_unique: ( + self._ssl_object.get_channel_binding + ), + TLSAttribute.cipher: self._ssl_object.cipher, + TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False), + TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert( + True + ), + TLSAttribute.server_side: lambda: self._ssl_object.server_side, + TLSAttribute.shared_ciphers: lambda: self._ssl_object.shared_ciphers() + if self._ssl_object.server_side + else None, + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + TLSAttribute.ssl_object: lambda: self._ssl_object, + TLSAttribute.tls_version: self._ssl_object.version, + } + + +@dataclass(eq=False) +class TLSListener(Listener[TLSStream]): + """ + A convenience listener that wraps another listener and auto-negotiates a TLS session + on every accepted connection. + + If the TLS handshake times out or raises an exception, + :meth:`handle_handshake_error` is called to do whatever post-mortem processing is + deemed necessary. + + Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute. + + :param Listener listener: the listener to wrap + :param ssl_context: the SSL context object + :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap` + :param handshake_timeout: time limit for the TLS handshake + (passed to :func:`~anyio.fail_after`) + """ + + listener: Listener[Any] + ssl_context: ssl.SSLContext + standard_compatible: bool = True + handshake_timeout: float = 30 + + @staticmethod + async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None: + """ + Handle an exception raised during the TLS handshake. + + This method does 3 things: + + #. Forcefully closes the original stream + #. Logs the exception (unless it was a cancellation exception) using the + ``anyio.streams.tls`` logger + #. Reraises the exception if it was a base exception or a cancellation exception + + :param exc: the exception + :param stream: the original stream + + """ + await aclose_forcefully(stream) + + # Log all except cancellation exceptions + if not isinstance(exc, get_cancelled_exc_class()): + # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using + # any asyncio implementation, so we explicitly pass the exception to log + # (https://github.com/python/cpython/issues/108668). Trio does not have this + # issue because it works around the CPython bug. + logging.getLogger(__name__).exception( + "Error during TLS handshake", exc_info=exc + ) + + # Only reraise base exceptions and cancellation exceptions + if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()): + raise + + async def serve( + self, + handler: Callable[[TLSStream], Any], + task_group: TaskGroup | None = None, + ) -> None: + @wraps(handler) + async def handler_wrapper(stream: AnyByteStream) -> None: + from .. import fail_after + + try: + with fail_after(self.handshake_timeout): + wrapped_stream = await TLSStream.wrap( + stream, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException as exc: + await self.handle_handshake_error(exc, stream) + else: + await handler(wrapped_stream) + + await self.listener.serve(handler_wrapper, task_group) + + async def aclose(self) -> None: + await self.listener.aclose() + + @property + def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: + return { + TLSAttribute.standard_compatible: lambda: self.standard_compatible, + } + + +class TLSConnectable(ByteStreamConnectable): + """ + Wraps another connectable and does TLS negotiation after a successful connection. + + :param connectable: the connectable to wrap + :param hostname: host name of the server (if host name checking is desired) + :param ssl_context: the SSLContext object to use (if not provided, a secure default + will be created) + :param standard_compatible: if ``False``, skip the closing handshake when closing + the connection, and don't raise an exception if the server does the same + """ + + def __init__( + self, + connectable: AnyByteStreamConnectable, + *, + hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + standard_compatible: bool = True, + ) -> None: + self.connectable = connectable + self.ssl_context: SSLContext = ssl_context or ssl.create_default_context( + ssl.Purpose.SERVER_AUTH + ) + if not isinstance(self.ssl_context, ssl.SSLContext): + raise TypeError( + "ssl_context must be an instance of ssl.SSLContext, not " + f"{type(self.ssl_context).__name__}" + ) + self.hostname = hostname + self.standard_compatible = standard_compatible + + @override + async def connect(self) -> TLSStream: + stream = await self.connectable.connect() + try: + return await TLSStream.wrap( + stream, + hostname=self.hostname, + ssl_context=self.ssl_context, + standard_compatible=self.standard_compatible, + ) + except BaseException: + await aclose_forcefully(stream) + raise diff --git a/venv/lib/python3.12/site-packages/anyio/to_interpreter.py b/venv/lib/python3.12/site-packages/anyio/to_interpreter.py new file mode 100644 index 0000000..694dbe7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/to_interpreter.py @@ -0,0 +1,246 @@ +from __future__ import annotations + +__all__ = ( + "run_sync", + "current_default_interpreter_limiter", +) + +import atexit +import os +import sys +from collections import deque +from collections.abc import Callable +from typing import Any, Final, TypeVar + +from . import current_time, to_thread +from ._core._exceptions import BrokenWorkerInterpreter +from ._core._synchronization import CapacityLimiter +from .lowlevel import RunVar + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +if sys.version_info >= (3, 14): + from concurrent.interpreters import ExecutionFailed, create + + def _interp_call( + func: Callable[..., Any], args: tuple[Any, ...] + ) -> tuple[Any, bool]: + try: + retval = func(*args) + except BaseException as exc: + return exc, True + else: + return retval, False + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter = create() + + def destroy(self) -> None: + self._interpreter.close() + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + try: + res, is_exception = self._interpreter.call(_interp_call, func, args) + except ExecutionFailed as exc: + raise BrokenWorkerInterpreter(exc.excinfo) from exc + + if is_exception: + raise res + + return res +elif sys.version_info >= (3, 13): + import _interpqueues + import _interpreters + + UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib + FMT_UNPICKLED: Final = 0 + FMT_PICKLED: Final = 1 + QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND) + QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND) + + _run_func = compile( + """ +import _interpqueues +from _interpreters import NotShareableError +from pickle import loads, dumps, HIGHEST_PROTOCOL + +QUEUE_PICKLE_ARGS = (1, 2) +QUEUE_UNPICKLE_ARGS = (0, 2) + +item = _interpqueues.get(queue_id)[0] +try: + func, args = loads(item) + retval = func(*args) +except BaseException as exc: + is_exception = True + retval = exc +else: + is_exception = False + +try: + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS) +except NotShareableError: + retval = dumps(retval, HIGHEST_PROTOCOL) + _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS) + """, + "", + "exec", + ) + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + self._interpreter_id = _interpreters.create() + self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS) + _interpreters.set___main___attrs( + self._interpreter_id, {"queue_id": self._queue_id} + ) + + def destroy(self) -> None: + _interpqueues.destroy(self._queue_id) + _interpreters.destroy(self._interpreter_id) + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + import pickle + + item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL) + _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS) + exc_info = _interpreters.exec(self._interpreter_id, _run_func) + if exc_info: + raise BrokenWorkerInterpreter(exc_info) + + res = _interpqueues.get(self._queue_id) + (res, is_exception), fmt = res[:2] + if fmt == FMT_PICKLED: + res = pickle.loads(res) + + if is_exception: + raise res + + return res +else: + + class _Worker: + last_used: float = 0 + + def __init__(self) -> None: + raise RuntimeError("subinterpreters require at least Python 3.13") + + def call( + self, + func: Callable[..., T_Retval], + args: tuple[Any, ...], + ) -> T_Retval: + raise NotImplementedError + + def destroy(self) -> None: + pass + + +DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value +MAX_WORKER_IDLE_TIME = ( + 30 # seconds a subinterpreter can be idle before becoming eligible for pruning +) + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_idle_workers = RunVar[deque[_Worker]]("_available_workers") +_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter") + + +def _stop_workers(workers: deque[_Worker]) -> None: + for worker in workers: + worker.destroy() + + workers.clear() + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a subinterpreter. + + .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet + available, so the code path for that Python version relies on an undocumented, + private API. As such, it is recommended to not rely on this function for anything + mission-critical on Python 3.13. + + :param func: a callable + :param args: the positional arguments for the callable + :param limiter: capacity limiter to use to limit the total number of subinterpreters + running (if omitted, the default limiter is used) + :return: the result of the call + :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter + + """ + if limiter is None: + limiter = current_default_interpreter_limiter() + + try: + idle_workers = _idle_workers.get() + except LookupError: + idle_workers = deque() + _idle_workers.set(idle_workers) + atexit.register(_stop_workers, idle_workers) + + async with limiter: + try: + worker = idle_workers.pop() + except IndexError: + worker = _Worker() + + try: + return await to_thread.run_sync( + worker.call, + func, + args, + limiter=limiter, + ) + finally: + # Prune workers that have been idle for too long + now = current_time() + while idle_workers: + if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME: + break + + await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter) + + worker.last_used = current_time() + idle_workers.append(worker) + + +def current_default_interpreter_limiter() -> CapacityLimiter: + """ + Return the capacity limiter used by default to limit the number of concurrently + running subinterpreters. + + Defaults to the number of CPU cores. + + :return: a capacity limiter object + + """ + try: + return _default_interpreter_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT) + _default_interpreter_limiter.set(limiter) + return limiter diff --git a/venv/lib/python3.12/site-packages/anyio/to_process.py b/venv/lib/python3.12/site-packages/anyio/to_process.py new file mode 100644 index 0000000..b289234 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/to_process.py @@ -0,0 +1,266 @@ +from __future__ import annotations + +__all__ = ( + "current_default_process_limiter", + "process_worker", + "run_sync", +) + +import os +import pickle +import subprocess +import sys +from collections import deque +from collections.abc import Callable +from importlib.util import module_from_spec, spec_from_file_location +from typing import TypeVar, cast + +from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class +from ._core._exceptions import BrokenWorkerProcess +from ._core._subprocesses import open_process +from ._core._synchronization import CapacityLimiter +from ._core._tasks import CancelScope, fail_after +from .abc import ByteReceiveStream, ByteSendStream, Process +from .lowlevel import RunVar, checkpoint_if_cancelled +from .streams.buffered import BufferedByteReceiveStream + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +WORKER_MAX_IDLE_TIME = 300 # 5 minutes + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + +_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers") +_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar( + "_process_pool_idle_workers" +) +_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") + + +async def run_sync( # type: ignore[return] + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + cancellable: bool = False, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker process. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the worker process running it will be abruptly terminated using SIGKILL + (or ``terminateProcess()`` on Windows). + + :param func: a callable + :param args: positional arguments for the callable + :param cancellable: ``True`` to allow cancellation of the operation while it's + running + :param limiter: capacity limiter to use to limit the total amount of processes + running (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + + async def send_raw_command(pickled_cmd: bytes) -> object: + try: + await stdin.send(pickled_cmd) + response = await buffered.receive_until(b"\n", 50) + status, length = response.split(b" ") + if status not in (b"RETURN", b"EXCEPTION"): + raise RuntimeError( + f"Worker process returned unexpected response: {response!r}" + ) + + pickled_response = await buffered.receive_exactly(int(length)) + except BaseException as exc: + workers.discard(process) + try: + process.kill() + with CancelScope(shield=True): + await process.aclose() + except ProcessLookupError: + pass + + if isinstance(exc, get_cancelled_exc_class()): + raise + else: + raise BrokenWorkerProcess from exc + + retval = pickle.loads(pickled_response) + if status == b"EXCEPTION": + assert isinstance(retval, BaseException) + raise retval + else: + return retval + + # First pickle the request before trying to reserve a worker process + await checkpoint_if_cancelled() + request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL) + + # If this is the first run in this event loop thread, set up the necessary variables + try: + workers = _process_pool_workers.get() + idle_workers = _process_pool_idle_workers.get() + except LookupError: + workers = set() + idle_workers = deque() + _process_pool_workers.set(workers) + _process_pool_idle_workers.set(idle_workers) + get_async_backend().setup_process_pool_exit_at_shutdown(workers) + + async with limiter or current_default_process_limiter(): + # Pop processes from the pool (starting from the most recently used) until we + # find one that hasn't exited yet + process: Process + while idle_workers: + process, idle_since = idle_workers.pop() + if process.returncode is None: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + + # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME + # seconds or longer + now = current_time() + killed_processes: list[Process] = [] + while idle_workers: + if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME: + break + + process_to_kill, idle_since = idle_workers.popleft() + process_to_kill.kill() + workers.remove(process_to_kill) + killed_processes.append(process_to_kill) + + with CancelScope(shield=True): + for killed_process in killed_processes: + await killed_process.aclose() + + break + + workers.remove(process) + else: + command = [sys.executable, "-u", "-m", __name__] + process = await open_process( + command, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + try: + stdin = cast(ByteSendStream, process.stdin) + buffered = BufferedByteReceiveStream( + cast(ByteReceiveStream, process.stdout) + ) + with fail_after(20): + message = await buffered.receive(6) + + if message != b"READY\n": + raise BrokenWorkerProcess( + f"Worker process returned unexpected response: {message!r}" + ) + + main_module_path = getattr(sys.modules["__main__"], "__file__", None) + pickled = pickle.dumps( + ("init", sys.path, main_module_path), + protocol=pickle.HIGHEST_PROTOCOL, + ) + await send_raw_command(pickled) + except (BrokenWorkerProcess, get_cancelled_exc_class()): + raise + except BaseException as exc: + process.kill() + raise BrokenWorkerProcess( + "Error during worker process initialization" + ) from exc + + workers.add(process) + + with CancelScope(shield=not cancellable): + try: + return cast(T_Retval, await send_raw_command(request)) + finally: + if process in workers: + idle_workers.append((process, current_time())) + + +def current_default_process_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of worker + processes. + + :return: a capacity limiter object + + """ + try: + return _default_process_limiter.get() + except LookupError: + limiter = CapacityLimiter(os.cpu_count() or 2) + _default_process_limiter.set(limiter) + return limiter + + +def process_worker() -> None: + # Redirect standard streams to os.devnull so that user code won't interfere with the + # parent-worker communication + stdin = sys.stdin + stdout = sys.stdout + sys.stdin = open(os.devnull) + sys.stdout = open(os.devnull, "w") + + stdout.buffer.write(b"READY\n") + while True: + retval = exception = None + try: + command, *args = pickle.load(stdin.buffer) + except EOFError: + return + except BaseException as exc: + exception = exc + else: + if command == "run": + func, args = args + try: + retval = func(*args) + except BaseException as exc: + exception = exc + elif command == "init": + main_module_path: str | None + sys.path, main_module_path = args + del sys.modules["__main__"] + if main_module_path and os.path.isfile(main_module_path): + # Load the parent's main module but as __mp_main__ instead of + # __main__ (like multiprocessing does) to avoid infinite recursion + try: + spec = spec_from_file_location("__mp_main__", main_module_path) + if spec and spec.loader: + main = module_from_spec(spec) + spec.loader.exec_module(main) + sys.modules["__main__"] = main + except BaseException as exc: + exception = exc + try: + if exception is not None: + status = b"EXCEPTION" + pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL) + else: + status = b"RETURN" + pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL) + except BaseException as exc: + exception = exc + status = b"EXCEPTION" + pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL) + + stdout.buffer.write(b"%s %d\n" % (status, len(pickled))) + stdout.buffer.write(pickled) + + # Respect SIGTERM + if isinstance(exception, SystemExit): + raise exception + + +if __name__ == "__main__": + process_worker() diff --git a/venv/lib/python3.12/site-packages/anyio/to_thread.py b/venv/lib/python3.12/site-packages/anyio/to_thread.py new file mode 100644 index 0000000..4be5b71 --- /dev/null +++ b/venv/lib/python3.12/site-packages/anyio/to_thread.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +__all__ = ( + "run_sync", + "current_default_thread_limiter", +) + +import sys +from collections.abc import Callable +from typing import TypeVar +from warnings import warn + +from ._core._eventloop import get_async_backend +from .abc import CapacityLimiter + +if sys.version_info >= (3, 11): + from typing import TypeVarTuple, Unpack +else: + from typing_extensions import TypeVarTuple, Unpack + +T_Retval = TypeVar("T_Retval") +PosArgsT = TypeVarTuple("PosArgsT") + + +async def run_sync( + func: Callable[[Unpack[PosArgsT]], T_Retval], + *args: Unpack[PosArgsT], + abandon_on_cancel: bool = False, + cancellable: bool | None = None, + limiter: CapacityLimiter | None = None, +) -> T_Retval: + """ + Call the given function with the given arguments in a worker thread. + + If the ``cancellable`` option is enabled and the task waiting for its completion is + cancelled, the thread will still run its course but its return value (or any raised + exception) will be ignored. + + :param func: a callable + :param args: positional arguments for the callable + :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run + unchecked on own) if the host task is cancelled, ``False`` to ignore + cancellations in the host task until the operation has completed in the worker + thread + :param cancellable: deprecated alias of ``abandon_on_cancel``; will override + ``abandon_on_cancel`` if both parameters are passed + :param limiter: capacity limiter to use to limit the total amount of threads running + (if omitted, the default limiter is used) + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + :return: an awaitable that yields the return value of the function. + + """ + if cancellable is not None: + abandon_on_cancel = cancellable + warn( + "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is " + "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead", + DeprecationWarning, + stacklevel=2, + ) + + return await get_async_backend().run_sync_in_worker_thread( + func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter + ) + + +def current_default_thread_limiter() -> CapacityLimiter: + """ + Return the capacity limiter that is used by default to limit the number of + concurrent threads. + + :return: a capacity limiter object + :raises NoEventLoopError: if no supported asynchronous event loop is running in the + current thread + + """ + return get_async_backend().current_default_thread_limiter() diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/METADATA b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/METADATA new file mode 100644 index 0000000..d1bc526 --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/METADATA @@ -0,0 +1,78 @@ +Metadata-Version: 2.4 +Name: certifi +Version: 2026.1.4 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: https://github.com/certifi/python-certifi +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Project-URL: Source, https://github.com/certifi/python-certifi +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.7 +License-File: LICENSE +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: project-url +Dynamic: requires-python +Dynamic: summary + +Certifi: Python SSL Certificates +================================ + +Certifi provides Mozilla's carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the identity +of TLS hosts. It has been extracted from the `Requests`_ project. + +Installation +------------ + +``certifi`` is available on PyPI. Simply install it with ``pip``:: + + $ pip install certifi + +Usage +----- + +To reference the installed certificate authority (CA) bundle, you can use the +built-in function:: + + >>> import certifi + + >>> certifi.where() + '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' + +Or from the command line:: + + $ python -m certifi + /usr/local/lib/python3.7/site-packages/certifi/cacert.pem + +Enjoy! + +.. _`Requests`: https://requests.readthedocs.io/en/master/ + +Addition/Removal of Certificates +-------------------------------- + +Certifi does not support any addition/removal or other modification of the +CA trust store content. This project is intended to provide a reliable and +highly portable root of trust to python deployments. Look to upstream projects +for methods to use alternate trust. diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/RECORD b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/RECORD new file mode 100644 index 0000000..abef4a1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/RECORD @@ -0,0 +1,14 @@ +certifi-2026.1.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +certifi-2026.1.4.dist-info/METADATA,sha256=FSfJEfKuMo6bJlofUrtRpn4PFTYtbYyXpHN_A3ZFpIY,2473 +certifi-2026.1.4.dist-info/RECORD,, +certifi-2026.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91 +certifi-2026.1.4.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989 +certifi-2026.1.4.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 +certifi/__init__.py,sha256=969deMMS7Uchipr0oO4dbRBUvRi0uNYCn07VmG1aTrg,94 +certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 +certifi/__pycache__/__init__.cpython-312.pyc,, +certifi/__pycache__/__main__.cpython-312.pyc,, +certifi/__pycache__/core.cpython-312.pyc,, +certifi/cacert.pem,sha256=Tzl1_zCrvzVEO0hgZK6Ly0Hf9wf_31dsdtKS-0WKoKk,270954 +certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394 +certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/WHEEL b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/WHEEL new file mode 100644 index 0000000..e7fa31b --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..62b076c --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +This package contains a modified version of ca-bundle.crt: + +ca-bundle.crt -- Bundle of CA Root Certificates + +This is a bundle of X.509 certificates of public Certificate Authorities +(CA). These were automatically extracted from Mozilla's root certificates +file (certdata.txt). This file can be found in the mozilla source tree: +https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt +It contains the certificates in PEM format and therefore +can be directly used with curl / libcurl / php_curl, or with +an Apache+mod_ssl webserver for SSL client authentication. +Just configure this file as the SSLCACertificateFile.# + +***** BEGIN LICENSE BLOCK ***** +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +***** END LICENSE BLOCK ***** +@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/top_level.txt new file mode 100644 index 0000000..963eac5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi-2026.1.4.dist-info/top_level.txt @@ -0,0 +1 @@ +certifi diff --git a/venv/lib/python3.12/site-packages/certifi/__init__.py b/venv/lib/python3.12/site-packages/certifi/__init__.py new file mode 100644 index 0000000..090fd58 --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi/__init__.py @@ -0,0 +1,4 @@ +from .core import contents, where + +__all__ = ["contents", "where"] +__version__ = "2026.01.04" diff --git a/venv/lib/python3.12/site-packages/certifi/__main__.py b/venv/lib/python3.12/site-packages/certifi/__main__.py new file mode 100644 index 0000000..8945b5d --- /dev/null +++ b/venv/lib/python3.12/site-packages/certifi/__main__.py @@ -0,0 +1,12 @@ +import argparse + +from certifi import contents, where + +parser = argparse.ArgumentParser() +parser.add_argument("-c", "--contents", action="store_true") +args = parser.parse_args() + +if args.contents: + print(contents()) +else: + print(where()) diff --git a/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/certifi/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83a748ce08d19c4c085fb8dc175f7d838610aa5e GIT binary patch literal 336 zcmYjMu};G<5Vez(mPTbmNGy~Ibs%mFVnR&J6>|noX>x5$li0FNs&p!Uz{Y~u`4$#N zlm#RvHl%Kya3zp=vuME=>=O0N2+2k`?_Mc!gR#*}MpcfW zQz7}J0*$KVF|rbil4X3%WMxuOoCJd*)7;>(jN(}|Ma^PVhEF&H-~~4Tfvnc8^IX_v lc%giIz~(!>XX)Fel)jV04}bT5{@OlRHs5yk?|ijwy&qsmUI+jH literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/certifi/__pycache__/__main__.cpython-312.pyc b/venv/lib/python3.12/site-packages/certifi/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ca7354e1a8568d4f90e6f4ddc29cd5d059ee816 GIT binary patch literal 651 zcmZuu&ubGw6rR~1*∓7=+*{C{iP)gL-WRq36(}lW}{}V5UR#L}H#e;Ygdh5yAG}~Mpn0eoP-}m0UnfX$!`bdut>+jjQj=zdcIqU^E zKhfYVico|Hs8d8^fMcU$#Ae6D$UuPlZke=oa$?F*T52qYgu6RQdhB}Yi*A!3n^4G)||3It1(d{#@@v1)a8dI-v?AU9nd9Uvt-TP+U_^HvIqxBbUzNU-+ E1Jrt>a{vGU literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc b/venv/lib/python3.12/site-packages/certifi/__pycache__/core.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1beb6e6c13edef686d199fe06e92431d4266730 GIT binary patch literal 2083 zcmbVN%}*Og6rcUVi*2$LD3~|`%T*iP53CZDw4$v@NTswOhyYcMRcW=^9q=04Yt0NF zrc%YBhxAl#;ea9zJrL19Ab&!o_5gJ%S6dF1TB+hjWu!`+`ewbm2A5NxH1EBcd2e>+ z_kJ_`V^>!cfjqmpYv1ca=nvirMD(4;9ym{tg>uLeEU_r$1kZ{&(TZF0*K$t6LO~7E zTGmolzCef!LUcz_DRtyJN|9VdK$d8z&^j#%N0&q^0xQIPUj;v26(OS&GP-itN?K0) zPe$}YMy#2U>Dt6m6BA}H*`t+e^s7*!(Z#$?Qzge*E#efxY?YL0isf<2E>mU{iw3iu za;oTIg9xXkEi^n{;p*6nC6WVTL=kOCbET4M~rJr z#@Mtsv-dGwVNNAGi`P>RiBrHPquGj4En%JJor=9w)hQ-xwu!TAxV)Av+K;o9D$6_N zTchueWvR{ZI~BuRF_tmS`uJr{hhPBF)x3{4(a=q{K{#1>qkW*Z(6Ox5<;0Pk*y-D0 zdyBuwZxT*}jCjJ2brSP@H)2qI$u447;hLH-8cGP+PcU>5vaLrf1dZ`1Pb}d;XnKW+ zKXk!ZVlW$6LuPyvp0JRi1z>u+9QXraCSqC;Q|J!7mSsJGA7ls@3ci4zOS5jo$MZbe zB2*^o#)Aq!yu0{-@V$_0pj@@!*yW1B@`Rt)l_(=_ClCObM|9ooFc>d~s}j6yQyBBA zK{yT`>ZFrrDeHMmFc}2@RcLe?)D}AHM)Aa9Z1}7nCDY%%zTa6(kJZx?N9lCGR|$Iga;yJNM1p?%d1x+ka}wLpAAr=~4xGm3V7> z1_~CV{li)n!s@~TzF5-ta*z#JDzoGjm?0cN5WLJx!r7crNiP^zUB*lgIY&5UNIzEtTqU^T_q`2Dk6xa74<_k{ zpe}GMfs(K7y}w_qU7z^htP0-+j`U?#S)#`djyA{wB1teAf$+%oeeyzk)t(23at`ro zCw2z+W?tNRsZAZKQ+{CyUvD*PBzJ>{fABAq0mmIg(E>#@D z;58j3m^!P(#I$vly$kFC+(eY$MsFJ=#l19FS8VrO{h;}m`iP7`2){{m1JtP`2*Tf@ zBFK$65@Nrhu7)fKZ=I@0j?|^zBdK?L{by None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] + + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the file + # in cases where we're inside of a zipimport situation until someone + # actually calls where(), but we don't want to re-extract the file + # on every call of where(), so we'll do it once then store it in a + # global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you to + # manage the cleanup of this file, so it doesn't actually return a + # path, it returns a context manager that will give you the path + # when you enter it and will do any cleanup when you leave it. In + # the common case of not needing a temporary file, it will just + # return the file system location and the __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +else: + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. + _CACERT_CTX = get_path("certifi", "cacert.pem") + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) + + return _CACERT_PATH + + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/venv/lib/python3.12/site-packages/certifi/py.typed b/venv/lib/python3.12/site-packages/certifi/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/INSTALLER b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/METADATA b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/METADATA new file mode 100644 index 0000000..8d32edc --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/METADATA @@ -0,0 +1,764 @@ +Metadata-Version: 2.4 +Name: charset-normalizer +Version: 3.4.4 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Author-email: "Ahmed R. TAHRI" +Maintainer-email: "Ahmed R. TAHRI" +License: MIT +Project-URL: Changelog, https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/ +Project-URL: Code, https://github.com/jawah/charset_normalizer +Project-URL: Issue tracker, https://github.com/jawah/charset_normalizer/issues +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode-backport +Dynamic: license-file + +

Charset Detection, for Everyone 👋

+ +

+ The Real First Universal Charset Detector
+
+ + + + Download Count Total + + + + +

+

+ Featured Packages
+ + Static Badge + + + Static Badge + +

+

+ In other language (unofficial port - by the community)
+ + Static Badge + +

+ +> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. + +

+ >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

+ +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:--------------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ❌ | ✅ | ✅ | +| `Universal**` | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ❌ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | LGPL-2.1
_restrictive_ | MIT | MPL-1.1
_restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ❌ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ❌ | ✅ | ❌ | +| `Whl Size (min)` | 193.6 kB | 42 kB | ~200 kB | +| `Supported Encoding` | 33 | 🎉 [99](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | + +

+Reading Normalized TextCat Reading Text +

+ +*\*\* : They are clearly using specific code for a specific encoding even if covering most of used one*
+ +## ⚡ Performance + +This package offer better performance than its counterpart Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|-----------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet](https://github.com/chardet/chardet) | 86 % | 63 ms | 16 file/sec | +| charset-normalizer | **98 %** | **10 ms** | 100 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|-----------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet](https://github.com/chardet/chardet) | 265 ms | 71 ms | 7 ms | +| charset-normalizer | 100 ms | 50 ms | 5 ms | + +_updated as of december 2024 using CPython 3.12_ + +Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload. + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Keep in mind that the stats are generous and that Chardet accuracy vs our is measured using Chardet initial capability +> (e.g. Supported Encoding) Challenge-them if you want. + +## ✨ Installation + +Using pip: + +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +or + +```bash +python -m charset_normalizer ./data/sample.1.fr.srt +``` + +🎉 Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair Unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess (aka. defining noise in rendered text). + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 +- Python 3.7: charset-normalizer < 4.0 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
+Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
+This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7297/badge)](https://www.bestpractices.dev/projects/7297) + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.4.4](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.4) (2025-10-13) + +### Changed +- Bound `setuptools` to a specific constraint `setuptools>=68,<=81`. +- Raised upper bound of mypyc for the optional pre-built extension to v1.18.2 + +### Removed +- `setuptools-scm` as a build dependency. + +### Misc +- Enforced hashes in `dev-requirements.txt` and created `ci-requirements.txt` for security purposes. +- Additional pre-built wheels for riscv64, s390x, and armv7l architectures. +- Restore ` multiple.intoto.jsonl` in GitHub releases in addition to individual attestation file per wheel. + +## [3.4.3](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.3) (2025-08-09) + +### Changed +- mypy(c) is no longer a required dependency at build time if `CHARSET_NORMALIZER_USE_MYPYC` isn't set to `1`. (#595) (#583) +- automatically lower confidence on small bytes samples that are not Unicode in `detect` output legacy function. (#391) + +### Added +- Custom build backend to overcome inability to mark mypy as an optional dependency in the build phase. +- Support for Python 3.14 + +### Fixed +- sdist archive contained useless directories. +- automatically fallback on valid UTF-16 or UTF-32 even if the md says it's noisy. (#633) + +### Misc +- SBOM are automatically published to the relevant GitHub release to comply with regulatory changes. + Each published wheel comes with its SBOM. We choose CycloneDX as the format. +- Prebuilt optimized wheel are no longer distributed by default for CPython 3.7 due to a change in cibuildwheel. + +## [3.4.2](https://github.com/Ousret/charset_normalizer/compare/3.4.1...3.4.2) (2025-05-02) + +### Fixed +- Addressed the DeprecationWarning in our CLI regarding `argparse.FileType` by backporting the target class into the package. (#591) +- Improved the overall reliability of the detector with CJK Ideographs. (#605) (#587) + +### Changed +- Optional mypyc compilation upgraded to version 1.15 for Python >= 3.8 + +## [3.4.1](https://github.com/Ousret/charset_normalizer/compare/3.4.0...3.4.1) (2024-12-24) + +### Changed +- Project metadata are now stored using `pyproject.toml` instead of `setup.cfg` using setuptools as the build backend. +- Enforce annotation delayed loading for a simpler and consistent types in the project. +- Optional mypyc compilation upgraded to version 1.14 for Python >= 3.8 + +### Added +- pre-commit configuration. +- noxfile. + +### Removed +- `build-requirements.txt` as per using `pyproject.toml` native build configuration. +- `bin/integration.py` and `bin/serve.py` in favor of downstream integration test (see noxfile). +- `setup.cfg` in favor of `pyproject.toml` metadata configuration. +- Unused `utils.range_scan` function. + +### Fixed +- Converting content to Unicode bytes may insert `utf_8` instead of preferred `utf-8`. (#572) +- Deprecation warning "'count' is passed as positional argument" when converting to Unicode bytes on Python 3.13+ + +## [3.4.0](https://github.com/Ousret/charset_normalizer/compare/3.3.2...3.4.0) (2024-10-08) + +### Added +- Argument `--no-preemptive` in the CLI to prevent the detector to search for hints. +- Support for Python 3.13 (#512) + +### Fixed +- Relax the TypeError exception thrown when trying to compare a CharsetMatch with anything else than a CharsetMatch. +- Improved the general reliability of the detector based on user feedbacks. (#520) (#509) (#498) (#407) (#537) +- Declared charset in content (preemptive detection) not changed when converting to utf-8 bytes. (#381) + +## [3.3.2](https://github.com/Ousret/charset_normalizer/compare/3.3.1...3.3.2) (2023-10-31) + +### Fixed +- Unintentional memory usage regression when using large payload that match several encoding (#376) +- Regression on some detection case showcased in the documentation (#371) + +### Added +- Noise (md) probe that identify malformed arabic representation due to the presence of letters in isolated form (credit to my wife) + +## [3.3.1](https://github.com/Ousret/charset_normalizer/compare/3.3.0...3.3.1) (2023-10-22) + +### Changed +- Optional mypyc compilation upgraded to version 1.6.1 for Python >= 3.8 +- Improved the general detection reliability based on reports from the community + +## [3.3.0](https://github.com/Ousret/charset_normalizer/compare/3.2.0...3.3.0) (2023-09-30) + +### Added +- Allow to execute the CLI (e.g. normalizer) through `python -m charset_normalizer.cli` or `python -m charset_normalizer` +- Support for 9 forgotten encoding that are supported by Python but unlisted in `encoding.aliases` as they have no alias (#323) + +### Removed +- (internal) Redundant utils.is_ascii function and unused function is_private_use_only +- (internal) charset_normalizer.assets is moved inside charset_normalizer.constant + +### Changed +- (internal) Unicode code blocks in constants are updated using the latest v15.0.0 definition to improve detection +- Optional mypyc compilation upgraded to version 1.5.1 for Python >= 3.8 + +### Fixed +- Unable to properly sort CharsetMatch when both chaos/noise and coherence were close due to an unreachable condition in \_\_lt\_\_ (#350) + +## [3.2.0](https://github.com/Ousret/charset_normalizer/compare/3.1.0...3.2.0) (2023-06-07) + +### Changed +- Typehint for function `from_path` no longer enforce `PathLike` as its first argument +- Minor improvement over the global detection reliability + +### Added +- Introduce function `is_binary` that relies on main capabilities, and optimized to detect binaries +- Propagate `enable_fallback` argument throughout `from_bytes`, `from_path`, and `from_fp` that allow a deeper control over the detection (default True) +- Explicit support for Python 3.12 + +### Fixed +- Edge case detection failure where a file would contain 'very-long' camel cased word (Issue #289) + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/RECORD b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/RECORD new file mode 100644 index 0000000..b6d1c8c --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/RECORD @@ -0,0 +1,35 @@ +../../../bin/normalizer,sha256=sEDgtcLbt1X7yuqViNrtyeazHufnZsN0it0I0RWvRdg,326 +charset_normalizer-3.4.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +charset_normalizer-3.4.4.dist-info/METADATA,sha256=jVuUFBti8dav19YLvWissTihVdF2ozUY4KKMw7jdkBQ,37303 +charset_normalizer-3.4.4.dist-info/RECORD,, +charset_normalizer-3.4.4.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190 +charset_normalizer-3.4.4.dist-info/entry_points.txt,sha256=ADSTKrkXZ3hhdOVFi6DcUEHQRS0xfxDIE_pEz4wLIXA,65 +charset_normalizer-3.4.4.dist-info/licenses/LICENSE,sha256=bQ1Bv-FwrGx9wkjJpj4lTQ-0WmDVCoJX0K-SxuJJuIc,1071 +charset_normalizer-3.4.4.dist-info/top_level.txt,sha256=7ASyzePr8_xuZWJsnqJjIBtyV8vhEo0wBCv1MPRRi3Q,19 +charset_normalizer/__init__.py,sha256=OKRxRv2Zhnqk00tqkN0c1BtJjm165fWXLydE52IKuHc,1590 +charset_normalizer/__main__.py,sha256=yzYxMR-IhKRHYwcSlavEv8oGdwxsR89mr2X09qXGdps,109 +charset_normalizer/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/__pycache__/api.cpython-312.pyc,, +charset_normalizer/__pycache__/cd.cpython-312.pyc,, +charset_normalizer/__pycache__/constant.cpython-312.pyc,, +charset_normalizer/__pycache__/legacy.cpython-312.pyc,, +charset_normalizer/__pycache__/md.cpython-312.pyc,, +charset_normalizer/__pycache__/models.cpython-312.pyc,, +charset_normalizer/__pycache__/utils.cpython-312.pyc,, +charset_normalizer/__pycache__/version.cpython-312.pyc,, +charset_normalizer/api.py,sha256=V07i8aVeCD8T2fSia3C-fn0i9t8qQguEBhsqszg32Ns,22668 +charset_normalizer/cd.py,sha256=WKTo1HDb-H9HfCDc3Bfwq5jzS25Ziy9SE2a74SgTq88,12522 +charset_normalizer/cli/__init__.py,sha256=D8I86lFk2-py45JvqxniTirSj_sFyE6sjaY_0-G1shc,136 +charset_normalizer/cli/__main__.py,sha256=dMaXG6IJXRvqq8z2tig7Qb83-BpWTln55ooiku5_uvg,12646 +charset_normalizer/cli/__pycache__/__init__.cpython-312.pyc,, +charset_normalizer/cli/__pycache__/__main__.cpython-312.pyc,, +charset_normalizer/constant.py,sha256=7UVY4ldYhmQMHUdgQ_sgZmzcQ0xxYxpBunqSZ-XJZ8U,42713 +charset_normalizer/legacy.py,sha256=sYBzSpzsRrg_wF4LP536pG64BItw7Tqtc3SMQAHvFLM,2731 +charset_normalizer/md.cpython-312-x86_64-linux-gnu.so,sha256=sZ7umtJLjKfA83NFJ7npkiDyr06zDT8cWtl6uIx2MsM,15912 +charset_normalizer/md.py,sha256=-_oN3h3_X99nkFfqamD3yu45DC_wfk5odH0Tr_CQiXs,20145 +charset_normalizer/md__mypyc.cpython-312-x86_64-linux-gnu.so,sha256=J2WWgLBQiO8sqdFsENp9u5V9uEH0tTwvTLszPdqhsv0,290584 +charset_normalizer/models.py,sha256=lKXhOnIPtiakbK3i__J9wpOfzx3JDTKj7Dn3Rg0VaRI,12394 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=sTejPgrdlNsKNucZfJCxJ95lMTLA0ShHLLE3n5wpT9Q,12170 +charset_normalizer/version.py,sha256=nKE4qBNk5WA4LIJ_yIH_aSDfvtsyizkWMg-PUG-UZVk,115 diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/WHEEL b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/WHEEL new file mode 100644 index 0000000..f3e8a97 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp312-cp312-manylinux_2_17_x86_64 +Tag: cp312-cp312-manylinux2014_x86_64 +Tag: cp312-cp312-manylinux_2_28_x86_64 + diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/entry_points.txt b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/entry_points.txt new file mode 100644 index 0000000..65619e7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer.cli:cli_detect diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/licenses/LICENSE b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/licenses/LICENSE new file mode 100644 index 0000000..9725772 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/top_level.txt b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/top_level.txt new file mode 100644 index 0000000..66958f0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer-3.4.4.dist-info/top_level.txt @@ -0,0 +1 @@ +charset_normalizer diff --git a/venv/lib/python3.12/site-packages/charset_normalizer/__init__.py b/venv/lib/python3.12/site-packages/charset_normalizer/__init__.py new file mode 100644 index 0000000..0d3a379 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer/__init__.py @@ -0,0 +1,48 @@ +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" + +from __future__ import annotations + +import logging + +from .api import from_bytes, from_fp, from_path, is_binary +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "is_binary", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/venv/lib/python3.12/site-packages/charset_normalizer/__main__.py b/venv/lib/python3.12/site-packages/charset_normalizer/__main__.py new file mode 100644 index 0000000..e0e76f7 --- /dev/null +++ b/venv/lib/python3.12/site-packages/charset_normalizer/__main__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebaf789f8ffd8ad198706f579cddd7703dde3c15 GIT binary patch literal 1802 zcmcIk&1)M+6rYu3$zIt`?Hme)(kU&ml_uK6hPH^*;Cz@E5;-JJ4|^F#yCdzyvmeaN zDpd+4&82@ruf3LB+(Kw5t@{V`vLckgLdYS9l3POPC8xgGmEx4-+C98|AM@t@-q-xO zuwWzjK0f{f|Fn+K6C+H&TFF>1GGM+(K5~(7`4wS>6}Ms;zACD2)vx(8q884$GhyAW zhYhz8&bqT;ulaQ`7uv2}@D0%n=iT{m!CffiS+N)%agP-IoOmZ(a+ktocNt{cJz7Dm zdSi78mFAnosXk5wgz(R4S%Vc4HY*!EZ5X zfAgQu-VGSO%Ba9wTq=#PM|{Yn0(s(!&oj+D9m}@8i3RUTDo0ocRO5h&MB!1KV#%nF zHTz2AzKlamBb-KiQG7GPUKt;=$cud*4chkQSo0y(%*VY^VfL9mfp-J0a6-L3I$#)B zEk|$~>lp4dM*ix7oey*X%wXf0rF(KN|sO_p{%! zeVqL9#ZmX$YY6vZVj)qQHVyj9C>vo7U$P#BDN~| z{A4vRQ|$%$;_DJqaPlY&JrVI>06RhgsGVRE+!|kAxO#1Sr<*qkF*^<=BBW(Yco>mv z7&2qXtRZuTn730l4Oui~!H{_%`SP2!YrUH{2!Z+&lG}vzQ=Ljih=fN7<&6oLyh;;Z zMmY>>;^lS02GkoBar1fz`zO?4{Cpe_B+g`RQ{TT> z=AJjZFdP%%NAv<*(95r#AP$*BdwweDEq=>6&(xld6Q>JL$`u*kfLbdjp`(xy6~qbe zkBDM&2)%cPEE+oS7CK4simbO!uQ`fqc5?E9skaQ5LkN$!CZwH=Ue2C@6jQ-AJd8j7`2|&n$61X#xsnp<=c&i oXlaa=vgKnB(8@!!Hb!gN`qutu57)QG>st@jFaCwL<@<2+zbme22mk;8 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc b/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/__main__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0e6af321bab3b4a0c109f0659e3df92ccf8140d GIT binary patch literal 377 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 A6aWAK literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc b/venv/lib/python3.12/site-packages/charset_normalizer/__pycache__/cd.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9777dddbb34106fffa7ae832c92013060977b71f GIT binary patch literal 13318 zcmd6OS#TUjdS+Fh)u`$NCvg!dG|mQzi{LF1Ji${0MOVv zMiB$eFm@=fZNVOG1jfr&4!Ft1t$Ww85^sOeSiS1JyL9} z_sguK8xS?Sk6RR~v+}CU%KX3o|Gz)=U;O?u23K46a{RydGt57d2kUYf33fZfGR(V- z#7Jy{NwT9XOVW;nW7L6PXTq6ujk;)_E8$LhMm;p`PI!~tC}-yTkY9$hNAf24q%bNZ z{iFV5U^I{njs}yV(GV@;66MK?(F&UOB`T9uqg6CrmZ(nFjMgOgjP6O+j@BmYM(dLG zqxC4OmH4+@qYYA>B;dEv!N}YP=J-+Of6HY(jqcr57APreB4u*m!{7&)DY>M2DO6J4 z>|>+`A7lNK8m02L+@qrPOn#-kQUylPA~i{s_zg?VQWbt%B~hxzZyR}oR8xEdTC|k5 z$8%WP^OoBlZ~Lqx()t-0OoY|>XeyP?L^JVpN*%#-#pP5?k&|*N6HT0#V`)iNkn25{ zo=IhB(w9(X#$wUfl>8Z)QpBzE7hWEI>6PJ;bC-v2=-k!oVMVDbLlBrkyA1GS~P>fs2E59?V_le%FIMFvZQl(PGD*& zy#}e|Od=B>8=uX{V`!F^;;Bi!9{Dq=IGLu=^O%Y`r=+MdtNV8p#LV?0pN;Y2&&e?C z_FvHbOLnnwWf^Oo6+VL+#wD>>$vT=%8S35pq0+LC+(5ieS5MV zm3`2XDYt8R>@siGyTCr^%T(HBUaL&vW*D?Pne}F!cUi?PxwFo!_XH}E-}{dHjyj|z z-yB91bH*IB#AQ6syB@sZVlvfsug>$#kG(O+BolMo!f5BwXVy#FXI*ocu~c^FcNj)- zWWDb=9+bOKYu+Bm4Lq?l=N=Q`=Ps0qxL%esGfGN~s%knGCtF1{*IK08OBLfN6jkhy zge;OBBa$5=PDdx@UfRK2?}eFEj0lYg=@HeL>FKmW`auTWXFWSx&6QbOPVLn_@yS$L zA)DAED@s~X6|zpBohG+`!R>-V_SfVfz5es52(OTDuY0fG7$((qUpz_PmWYq*ej_=S zOiMEfSs}QpyXhQsk3^+ccaxE*3Rxnhf|PqyI@Ab4IN7+G{KKG%0% zR_|oe(|sfIJ@H#g`nDX)sD0DX*`z$CPNk>g6OeORxf_qkeRt*5-9F5(Z+bQ}l}-)z z4jkxH;~BYUIvTr!MNs?5s8l&Kh7XyHCgO9l(if9@r)TvV>qGC9YbAoQHQ-NWAgl|_ z7aYSiEydToT~7nmTFr%g;G)J|d=}WV9%#=6+E>a}kFD<0I)?IrQ|p13bAgu^F8+OE z>&nr!#)AuI3gNbetA&Qfh3n7!6%P+CRptFnTa4e`zQ`7;Yud2z|ANbJI?jw)g`R<{`>l?u)E!a^A)jkVGR{HY6o+ zfW*7ZBs0$zvF)~rCMdEmOQ!%~dKl(+P-EUP@61A8Z?z)rDxzK1Myo7yrwi-AKB&mD zx5-Z5b!BbzW3g5bdR)wdJ{J)02GrHuP^tXJQxgy%NL>O-VTSAraYn`Cy?AB{^1DN( z4Y`fD6tYQmH#Sq!5DMKBot~Cc67+|ET8SqC{l?zceRj7xXC?JATOL&1S4@*HNB6yg zxiR{l9l8Tb$Q++?0Hvq#r+yo^1!j|D8k-g_KNI(@ivu}vATJJXF)nvaj%!@x9(EV% zTDKUduV(S`(_rJ$8~I@8%E^52XdzU$9ujjQak+La)ct9w`g1QTZ936#Q(%N@%~iFH z?wK8Hi*+^Db`*(snc}(@b=Ev9vGa~Bi`9ee294q%*o~Fqvd(#+Xo|%;I2N3ea}ujj z)NjQV!y38f-lJ=DgQBN_gO*I%3uQwu5VTjk0iY-%tQb{faY~NfjVETsL_Bo|E1F3I ze3eW*1}stRUb@tXv8*?z;;J}dA+nUlM#xN&F0nh*Y3Oqdz(np5-Tfjl3BKa#k9anU z9TRa-e4wj?Eh6Fz>46k%HK^aBZU@f;DzmECvgF#ZuoBK2m7hw|MPSp?uSe?9DQUtJb`Y zh0xx`?`mArGd{e0GS3fat^s8bJwAVp{|8FmWwOj|+cH6hc@?b@weuTl*b=DLd_xWA z2WHQzJIhEeBGC#g5V9}6p@nTVs9cuG*jRFxwDbO!cAWht(vEy@deL@1(r%K$t}C1S z3Eg$qh>`dI-KK-ior+In#`Lndnm*8f;K11JY27W)#KvwznNA-YJfL$|qSMiotjfA$ zavb*dRGdE84eltq=PIm*Xe#2=%hU{{K_xh+2>2X|54Q+QH?B$+#@vt9$#L7(E!mR8 zs1d`TI*%JbD8TqC*11N2&eH2^Tt|VgTIZW{eDkuf#`hGTylZ?Ho|e2%_~wmJ{bE+* z8VcUfy0;p2V6w>YP9bsM~Y z;WFLAFoe9x=zSGhe8+AkYYo(5w-qGdC9=RL$GjthC*#U8#0CUfwGp(+vZ}uXlk6R; zIM9mCI)G!0x6ip6ln0Vkez!AxMCC3b-un!ut9-4x=f~RnX$Pm4< zZhHh=X)c@)pXr34?z}JS%QAO~A;V>Ta6EYGc=+tLW!bVUN8FQnKFdo^;-aALt8l`Q zxBJSvviyV%yD)n&;S5|O>*RRe&stcDW~N7)5_3X zdo(dU6&=U+wg=pMB9V?o6Y5Z}UF{t-R*4h&-5}*4m2@w#`=MMt1NowhDc|#O_NS@G(pu=;!sU&ji+{&o(mIa=J(t^E-fHx4{CorQrlPce+a<=k zC{LMD;;0XjlDNmlU}4eH$&+cwaE57-1e2H2n|ReV_#E)%#aJSZQnQgN4)jLohnx%0 zuA_IO@kA8BZ;+YTA0jX7d!+D}hKd9zLSjbU+=^j$dV24z`W5}>g463E7k zUiHFI9qD0UA(?@Rc!4 zjF!-$AVWYa5Jg(It68x^$u!lmad`qR;h3S+^*weYgh3_QR)mA<#6)9Lgy;cDWUS$r-S1&XW?Z}UU^sV@LEq1{6a7y85B z%X!031GP(+mX0rfN2@!S4;<3CLmO@Fnyc|?bNJBlJYg63-5Xn5iAc&_2}!U$33T>X2)%Pm^_$$aAwQRAL%=BT?m$F(f_ z9u9#v1xMC+FvlMxWPkLz_2cN1(DeSRf+akNjHVJqvKe zI+5Rt{ART7I{vuj@rc%W6)tt5o(DLY<)^#f1b~-1VMujjc)S&{J2Rrk8*UFhEe2;eE{KxE*1D6Xz zTj_ZD0O;&I-?=jIgx{|Xoc$>JWZ>e?I%JN`k1EM(v<&5hp^sXh2*ZV-_-Ih;9Ll$X zv_AFG;9Bs)MzQBl@%`Rpv1FTY6NR=%F|GSVzT;$$KdHG+ex3IsbT&|i-QG{#OGLw1 z;!xh2F&w4mv0xvd3Au>(51n6AVnNw%7-6yz-)aMY;G(Q(75a0R=ugD7EPKXe5h}8X z!kJ8|YXSTzTB0p%v?@4$wk>76o7}B&K>NE+;Q7I~Ny>_k{q{Dcv9kyF;4s21H7+pg z(0IEq>s^-CCmcuAYiY!b)W?g8MkENFt9&1kE&0tEqWShiYZ);!LocQ$L^Y%Iimw>K zQzLFB_K6l6q9J6SU|Gsge8cy9q3&p(CNV;E$}l2qadoQC;9^bQejMaURJy0@X*?!PAtyfmBOi94Rp&XMl+Y4MTF%xG5l- z$!km+W+dX#y>XRJ!ibm==S6oX()VOVcafLqp1VY4si+w;zZ5iy@sV^-B07s#P*tG7 zxMlPTnq#prMr1A|#nl+p6YLF>MJD^#z)}N9E9nUA2$njXh-Z`^pwj=spZd4BQRk>h zT)41N-M+=tyN@pVC{fp#;~Vp)n?vBFV{r9yzGG-HxWU(Jd>C09ksgTuhS;&icziXQ z(D*FWuMM2}=u|#*qfl8(P^k_Q+i_S6g%MiV^rIG_T4>Vv_Cia)CNypEdzW9$@%svq z1Ao>0esex@eDQ)-2`;FEbk6}0TJl_YIsWJApQiI2M{?YeH7>k)2!s6MC?kX(dfxTF z7g6Fv| zItQ2rJ6@xfus9}1asu#VawN-du9W{YvtV*0%B$!n;-=IGV%~^NOS)5?Nm3Ki;6l8F zKq0oas#n5Rwk`Gy9imDw+q82G;jDT7&=o>AwUW{P z3JJ=r4jv-RYDFC+4Fc*Z)Qek;+jn&FN};Li?dyw|9?FH_9 za#aWzANctV?bwSSzn(kxYW~n`xzKBcQ2Dmo=?`qVJ-)I{CmJsdZ!-A#q6VSlhwXpz zozs zp0BgYN;ksVZc=iqg(8ak_VfC76lD}+podkgx9#9*Tj6rLNze<*)HUo+a+6*jfqKP>!Vm}s!wCz#*{#-a#dli&ey4iRcdKx3t#z3$4iS7=?UlW?!N z6BN=Zc#L;sdD;-D}!O_JEfa^7fR`# zM&gfHo74rTT6!?xZG?=;8d={*VhE9961;mr@Un#9MU0iK4`F$UlZSxE2!3={#vvBs z6k@QI`J8-$C?n4Wu(bcNe<47q2aK=Y;kR zuKM9^_&w`fSB~piss4oP+YF+cFDe+0&*Q@+*UC<;c~2C2`rtEc)WVoxeIxARrGn77 zF0{hES$_RPb@k*&wYkA7`M#@K+l!wF*EXG`60C%f2rp!VuUHtNdOS`7J+V?TCyEZw z?6w0w9}WCCC3fBgiyEiVe!m>{EhfABl5D|Fu@F`;cUceOy_MvITh{Yj7_m>6uuO@n zwznjN{m-yzy+w#9VGy}2ciUFT#j#j$Ht#E8GCc@Y`MwS+^9c521gkCO$JhtQNzEVg z2+}yIQGxAG!H`F&hBtyV^Zt4Ab~AB`-Y-M>tu7lZ20Q8*z-?S2j>`zAh;Fto};D4Smp)bqHIX1;qXt45dea z**#WY86x%cDnc<%&5}Tsu*;lr&I|KA3>yF33PmzPzk!K5GxCOgngiej)+z?r8wUj9 z$VP@9v>-TTgr6)kn;;+H)ik;d4*^YZmMt+$j{m^^G3;;y+|C$J;z{wGb(BqnYeNpT zk@I_~Z8a?$Cl{i&JETDOwD6VH42>CWmMoCjRkOfJ?cC zs?;Jfp-=e^GITa=0-h1FHvliZ%PuN0i!6+LlYj*LQ8Ty|KKXvtO8Lr}M+3R=Ni((; zEN4x^?Xy!9d#U}VOrX(@$zbn1KhpJoGvZpld6=g4_CqpCBf|CgrN?jn;^u;3y&1-v zUw2d2h8SW*Jy91&AzDZ03CT*Bqhvaz{0KP;Pj2JnMtCFxpMOYFBg*ejA%m zjGY%pH2o2IcN@8p#Z;DY)9Xy?tH>CRnJi;bbEqW5Z(5c1kD(ueyFRKyG{GqOj*vlO zS5`1x-3u2Bdl9g&+6cF5uKJC^Q`+FwC1Jg`J6GGiGPqXTr@eFo@%)0fN~?|Jypcjf zGvK|z)va@4juV&nu5o?O%G4PmP}99$b2wLXIA3#g zkw=^idup{|EqpTHe3E*SYfb%6_HTt;xKe1Zh6)AO3ODm$MlpA;JTo($kiR{iPAB$@T}Q)I4&j&rvSdCPb~z!)g0sE~9?uEWHH zUz{3=a3wGJ_oUj-$n8JiHisidikKBg(kZ#5afe(CTnG!P@BjO}L)tjQ_Pj=fK+FTvIAzM7W7YRtR($ zl24SY?np|CpLT!)&oX)=bJN{3nK;r!dD5dZ^td!V>uhj15mG=-B`C+pZJ6AykXsQ= zen1|H14n^`ik#uR0^}Jgmx%2fhpdN`zd;^Ekx}b#hbhFezh=V!$b^2)wEvoErvF9$ z+2vs!|C1rN0K*RanrZuIhn2Hk5oSHxS6G3q{=A8C@ZWFPa)j6e+fK&e-z2HeJIvg2 zHelu=^?4U^waR^K?EWo>->BcSNm83(=Fss?r^sGrarDYe^b8n@4R>JMO^P-zu-902 zs1PXMbkam;zmeE*muFL?JO;;~kJv~DYyaGDunNpLPX?vFcch9*+y_e4Pzw=AId(ZdXckg-U zo_p`P_f_HF_3z(D!GDvcS8Mk#C`y|U{QnK&jO9`66h%3%tW{J+wPY(fmK2Lcfb|^rn1pcAuQSDSas)f_%S}{uag7BSTR` z&sx-;woqHoj38UEEhHmI?R7FFWq{h-Hc;*JJepVgri7{eAoUkgkYXF84*1Z*dZi2| zW+0UdOBq7aAR%drIv8b#s(t-s4_nkBjHp9D4C4ENg%dVZ9cI9W6E;j8VZcTbHbQ;E zfQ=&T33apq8$;M=b*up!N7z_3!hnq@EJB@Nz$OwlL7im4CKEPEonpYABy5U0)qqVS zY^pllfXyImx;oQ<%_3~3`ji2iP1sXvqyd{lSfn~PfWfBo2%W3W51{G-!se?B4cOC! zEmRj7uxAKcq&{oFo+Ip8^?3vK0%6aqFB-6y2zyaoY`~%jTdYPKuo%Lk)mQ@-M_8=7 z#DFa&Y>B$efW;HGOkHlkRuHyaU1`7)2wSPHGGK{>tx}T=*vo_^sjCfGGGVLLH396r z1$JFa_!@Ox09RijY@Pb50b5Vlt7?h?+dx=~`kDdTNZ4!Y>jo^9u-Da12Fyy>CN<4~ zr4yE>ss_wPn5u3zU>SsMRx=HlMp&l0#eiiIwnfc0U^#?ktGNcuPFSv*XTaVdEKhY9 zFehOSHQ#`_2+LQu1~9VgHo~{6+XJ{-K-hM*(15*3SfTos0ehRUx72qG*t>+iqZS#k zV#12l5(8FBSc$sBfbAq~hq}vv?IvuOT4un?2`f|g7_hyB?NRp`u>FMXQx6!hgM=MW zD-75n!Yb7F4A^19-cyelu%m<>QI8q0@o*?Xf^`rqiMc7I8 zv;jLq*lD%efYlIIt)4Yt=LkEi)*7($gw?7S4A@1&E~s?|te&ts^#cQjEY^b$)Q=3< z$Ao>PHW;u=gf*y*2CRv&M)eZ|cA2nG)GG$;Q^KyOR}I)T!mg^#2JAXv&1y>kBfH)p zyhXhkz|~uX-BfQIusej^RzEXfKOpQg^{))r4+;A#^>YLE1!147KQdr{P1ujr9~-d0 zA?(NM-x{#LBkXV0zc*n2K-k}_KQUlGCG02aT?6(dVRzM^88A0tKU05hz4nq5i>uJtpiA>K_f*e-ZXa^}h|+{}A@y>i-5XVb=>i;5V32=~t1TIi~dYG7C$@BmdVq!ui69T5EiRmSoo?v>Lm_Cx}4W_S&=_i@K zVEUVw0g~wtW}t})lgvOcgG>xXlLEzOh>000?->Fn+{6r%OgNa~CT4_WhJzVtVxEx9 zNHC*J%xKAs0yDr@=g9VxE=EGhm)GG0#ipIWRAnm=`7U0+^Rf%woyB1SZPFL`x;i6$mV zGKpYbHZiLu^D>xZ6SGD#$zax+n01m_3+5FQ^QvTC0khu3q)28xm<=Z8HOXuMv(d!7 zE}4yBQccVz$)tj@nwT`nSiz*57*#UqU~BL=m#G= zZ9}4X8)+dvZp* zvlGm26H_LcaxjKST=w2x$?gGbh{+*QRF3ojJ_k)TR>*q}f;nVj-jmEBFo#Xd5y>0| zbJWBflgx22hUgp;#pVLV#}J=Gq6l4}_!y#eNED|dJ&n&9Q#;kNoiku+Ow3uy)POl> zVrnIG4$OHIb3roa!CW*kb&{zEV~E-zp%_IHA0Tgt-62u*E>L_75j-S{;RTA1A&!^5 zN71}s@G(U6kSL}{dIg_PP5NDxT3iETh~yzrEH6-e4Dmc9isq5t#OIc&w%f9{TVU>( zn9n402h0yl%wI|72Vj0^Vm_D555at4Vtyoxew;oCgvX{^BXXR-2o)p9|#m5!yW+=?Gglvk71_(iS`Oe+wsv&?RaH7 zI+zX<(s7LtV+pvVWAm>!Y|0uyXv zLL?ImCe+0AluRg?UM8luWcq+H?ihkp-Z20LW85(Wsk~zdQU`(wGu1Xo))oe4u!$KW znW139P0TRKgo82e3xZVM7X+yz!8~EQXOz6>2{5Bg%oxdx1~b;gjFZe*FcBtZyksK4 zOfWGMB{K=k}0S{23SZ_CG#Zm#^^st<>)_1odIU1>7H5go~OVVWBeeM zV|<`sj4^(Y$}v7rF!M~c&6l;!1GB)yER@UwFvb`^NaYwmNPPy(v!;8VllMFi#u&*5 zsT|1%sV{+9Y`P~(-m@4?w26t4Of;BS6B8$ySTIXW%u>lL0kh1+#7ky57-Os+q;jkd z6pSHOm%YdFIvB9Vcs)qvcpWGhW4s=ua=Z={OtMLfHByVUVAh$KS0u9z%&R75y<}3r z7^Ctam80??bt9P9P4}eAdtL{#$;4PCvk6R^iAk4?3dUw)HcQ3^Cd0&JN+tu0W@5HT zMgx;&VzMQZ1I8G42g$e_C>Ueh9VFxKAVk+es?$_kzO2m&#uZ>>?`@T=3+%Q43u(J# z3Xm@}F>gxdEilFiJ4odS8z>lKgdL=EgdL<7gDEl9Rw`@T0cNL(*(I5sV0N3BGRc&K zF~rf3_DW_S^7~Eq9FWXGFcl`|kYwHibJ)Zjk<3vr$4ty|$s7Y?jFf|9qzn|y`=)zN z$a~%gV~mu8WTXrf%xTj-XXHJn!Bhtr*?TpTtpM3=I^F z;cOfdosJ{DiqADuZO!s|u7hbYF*hXB0_LWPxh0vKU~Ze3JCeB#<}(xX1Ihdq7-I|_ zq;d=$q<#VBM}d3JTjU)-0{+*2Tw*^4)*U-b>~Dc}N6ixZdtlx1vc&!Y*iZcRA^+2p zA?GbA;mF;!i74Ys@fJHnLGEW~Eh)p0x(WZed<)(m$S-W&@k`2ZD*H>66*0s}d_<)1 zs~rk*qwpDxPwy?y@_TRN^+PH6-;?`O+lWFiehvL-iijmxlY3H=akjDDY=n3Nj}rI! z9XxU#((Bh}Er%_T{*k2LNa>$|{Bs5~t;GCRGXDamO)~As>-cyxf~bxTD%mMZc7gew zEcum*`5MgcCG)Su{F}VTN74f+JtXOol)fS9TPgiJN&g|G|0L-TQhH3%ABBXI@F4ZS zNd9lh{trq2OHvP2^kfg!BBdabddQOhjC=oy^z5Mq6B8oKh2ox`@(F&AQeVqby{J@g zS*j07eWlcor2ew*Hj)NNZXiivQW`|kU|I5asJ~0rKZKZ}k`cBSeL(gHvFM$$$py-rf9ls1uMl~Ni>=~7ZjvPo$(Nf}bgBuSIf7Lu~0luc5OlyXV3ODT_} zH>Bhs$tk6Ll3Y^SO42qdZ6~QfN`)l7DI|K{w@7|lvhR@eu9S*MDwa|SNu^TSLDEhs z?ILNnl*&jdm(m`R_DX3VN&AJQ&Y=7O$s8o9LQ027dQVD+Njf5>qa+=Z(s7b1g``fS ze3fM0C+UQc=W;;VG>O+ z`Hq{g2=6eRONlZ*l zPKa8$GBr8w<+UgmmjP0_J2*Yc6DG*$_@&*Hru=7^;u$V1hMHcD$9<`|H8Cb?O?=|2 z!a*_i+UJ0d;P>aeEg+Z=^`qpeOYJz^zHp4(M~iVFu*e%73bWLI8Zwk^k&o8P72 z+jWIuL`1}G&$s2OHg#5%R`h*vw1%ytQaB(!DT>QFMQ3V_0vwT?XHC~~Ga?e~sWkpzX)rc5- z9%xNL-+6k)6gaf(Y$2kX3WxYb!!u}+U_8B|9C#qjnyXa^F?5+N&0*W7DWO7$a#+(u z6+MNJj2fY95`@A*%ouAcqC#sjt=3$t_L&fRtXg5$lwQd7PPXP)p*&hyEaaXE(ASEZ zH$f^K#5Ef$%OhMi(rw>pt@?ZMC{5{&TKmM>wxaP2w4@aYpl7r#Hv`VDDfsrLr`J-K zBgd7M2~vt%(C7JJ`+^NgbZ7-ut%&k#tT|e?rt||Iy2h66%5Z5#Lg={yqtOb@mHv== zCs?zs1^DD>MMCbGtmS4x)RX~`f?*0YC9*wMT49KRqR1L8%_hvCgb693z?v%zQ!S)k zOKo;XhUf_eUnwo@yUd!K;mVF!Zq32XgGGV3{7lWB2eTCo5q;_xjfSfRgU-*iIqX?B zo2Cp!!O3fLg=@hy|=L`0?89a@H#3s*`m$j%m>?9_^e;ntAEj7&UFHRV^kaz*bC z2i|iH?3!)Q&>~2JZmwLrU0W=qkZ4z?rfQo+z8A`+S#!6+l0>b@%&|E%Wdx|+3HIC! zJEopotr2qJQ-6_K-J?rWMvAspYB^~xEeZL;&^1;K!$m6+Lf=)kZ4m^xY+9>8`cvC@ zz??LDHpbHvLcM4g9^2-$X%P?$p{*8ye#+4rg*fTYj5dQFqo^YI6;)Iu#NNp+I5f$h z2{|O%ns3e0l+nnCuC(Q0deM>~6!uEYj)=+Bvfvp)42;WeCy&u81T)C*4S|L~6Zp{X zmWNxZot7tYFIcb=a zm)IRSPOSovm=hiLZE&Xv;D;qx^D}KZ&{)jMiO|)WZx?g-M3BQ1G-tXEqbt{DcR3^0 z*wQmGoP;GNferJoJV{sxT@IU5TP)x)iDC{%WikE*DPpxP-JX$4X4E!`GQ;9>)3fbP z4CturyiANlF~l%{mB}FcM%%YX#5%0oux4mQ0vQmUZBNgNh~t&ZsZ|Rw4DOX<%g22Y z$(hzXG_T;>>xJQFox8`*lX&u8v8Xc5n(x#;gIGApT-0O3px`%a9nf8C(60H>p?k{OMT2rQ@=tNV?CLImEH3KCh%#S0VfPBv1 z<1=A|K1;K$ViFSz?Ir+!5(C6&MWS4&e=(kgGH`I7*^PrM5)7tG%*$qLfQke?JZAZdh}D?%;Zjax>&k4f1F@WCY}dLAXsrUB z5U<+o84hb+CY&SI#={L`IN*{_&4+@T5{c5IL<>f18Fg5U5}sbmGzS(BLNP+hy zoe)*}*dG3Y5ca@d3&OW75shHX-{xWl)zOC}dctVLfdX;_w8UEg#WDg0%oq zIE_w@?iHLF8bUO~xQ!IdckLOc<fLX#ZYR$vipomd6~C6s?ei2*C^+e~09z%b(-Q4Sl1 z^dA6^{*y5h5f~Ag@<$XILn{|TVYo;V7C&sMXxW!5ZIUQ6DvEX=-@}W^?|)EY$U4N% zd|$FH+m=piZ_GY<@iWC&hlcr(q*M!DXphUrs%nAoY*)nX1>v^;xln`KeRBpNk!$xq-<>x8{H_*@dSNOXE z?T?{}O}O2O&88E9e~<;EY-Isd%hie?Xw`(o2&?C6tq_DwMZgjfhizNpxB>umcJa<>Io_tWM}038`>!jMbHnO}^L& z0jJGxo+#F9otB@O<$6QYJ`?47$$o&hg`&kG{ABM>#_bYpbpa2GDadnZ_IyK+DZOyd zVq6Nt;&TugqJI(kU>}Sp+yvOuJIR`zQ-I(VI~bBzSrJ`X(T`fAkVBVQF)6~%5U{}o z(g%wd_UhCh5duKxi2al*9x=01+X~zC z5tXh$q-nO7l0v);<3!iXV+I#p1!lnVZ zs|XiQ=z3crwzb-45V{XMy65p2Z0LcLncs2d`Iz$c}YCwS6yZ9zt+%b_(2x=&1| z9qV0=c=}cc0rg&y=~AuR5Jqas5K&|){4yW)6bUI9J8#VVLxtUN5oa8ZOjkZyCMj7{Rk5rV zg^TVOCT)ZE z_I(8pi>N9F{;yRFSQ#ncSh%?}A}Y(}aA})>c>1ow$&-k<;Uii#5S$C3U|0$Q(ll!! zX5HjBTvmroO9CwVvH%Bgb{r3&P^3S;`?KdLfv5rzpSZ1WlGO#pawC>m5txkv68=3Q z4%W0y;5@yfVb?6D3*DiNhI)Cpi1RY^8;1U8kP7=JU@jA(<5D}GMN`J0*d|;G`!99n zZovVRBpQKVoQn#pae@15;yBPQWLyd-b_Yi=%m1~LY8AzmJiit$tb%2@ zcaTuYC7w4Y4>7xlOO(C9hpfodFo+{Ka7m^E6iwN0xFfJ{)QSWy_JY=c??!9BOQOZb zZpv_j($tN1&xcW3q(Fyqi-F2SG_2);@`S8%IWsW=E+gM7)|HoS$LXi0+!S`erEp|2 zW=6~^ILQ{Vk+ByWiBHM3;;1^eAR@tq6M8HotK^FjP5CJ*xrkpf82${tCLOp8*}~kBK;5@|WpuBuNw+w8EN|Z{MnA`4lC_N7S40#AA8ho1`0hSqsiyq1xT#fW7C6f=x_+eKzUN>vR;v)quvG<`H4AGH zj_a)HnJ$}jdF8jLHwhPX#nHRcg+iNfDICIE&Sae9zu}*Z zw7jo{8n_gOC&EMs5Md-^0p<57kc3O&6bg6Ju`e(j>KQgF{;pI0RjBoEq?R>v3&wlI z8oV__fBQg0;!@Zz7VA~6UEYs#{1){4FKC3AE09Zu#nS;51yWcZu%0m1bk;0H%#W0^uok30R=DiXxV?;baH&op>J2mX6c2oYla z0oiYiU8LmpqbP?F~;r*`GdLig9DUBz@x74)7*Z!i?v04Pa1NIjv5vMic z#|D2v!6lmSD-?Y4&u!R^Q-kuK&^7{JYyS)C#!Q1(m|D^hTpB0hqTsa{_OZAq7F>Md zqTsC&{le{V(FQ(&CS4XW{8kd|Jlb8@4zbY?kW2;c0+3y!Px15gXr}GjmSl+bmCb?a7TVO`E#GI;${hR!ZcoXI`6?x^ae)o7USC zlAibU+(=61JuQ+&3l>uH^n6YhctX?j=0rv=^n_;FwMD58ndZyXm2SaP`TxWFZ}FDg z@(3re_{%$QDG1E|})MU$kh!GpYE(2wI!FU=I4f za8Bg>MV_8{`9K!Tm&Dv@Jv@EzV%%L$oh(ZU5}h(E2AZ1e?e0P1sLDyr~l z$B%?r{!U!}UXVQ;qKp9*yu5`eQH*c+Ye9ujZH^xc^l!xFZ@YPc`FT-Sfw<7Uq8`W3#KkQx-K`Ra28b68 zIIjI^A@tN$fztgy_46=S;4Y#9wTeRMNotxZ3{WCBtxrHd>9mUsdXgRiWvNABBI9k2 zp9?(#&-Q)yh^9QfbFAs94iO=H`id;xN@r=#OcpR@;YGFz1Bm(^c+a3XejzTu6qgop z`4X4Hp@9{%d*#{a8TOr-B{@DJer42Z>dCp0bLXaJ;d@u{$%(UsFl&W@*pg=;mM3{a zZLV}tIy7!=3OZ5Lh(RwJp=QT@w%S@EQtyF4xlB1f)6^ZeSW~~l5!<@oC zqD?W;%rfTZ28t{&6fsOL3k|R_|8(HaMTR1a9AVws&EW-a0am*B=!8W``w3&G%dczU z(7>ARf7|N`%}AR&Zw{VkX*8uhmqzo~=|W@bI9khCP}mP&2vG6Gg;|(QBTdqr!hWv=xVa{7o=Gy-ve0xN7I5e^ z|Cwn69Ru*NVkU!zj!%(y^b@$spmt*DqaajTPE_d^t=)x`8_M7kP2QRfZ-_Q>p(yw39 z_tfbX`}Aw)+pnMLEWOpOu!U&rkyI!+aJd|cjDTBo;M&>OCH9Qjn= zbE0eaKK(+8xABU;?^xG|m7TjkXm8oqQL(G@(`$O;!H)9!jsuN)!)bj_b=U5LT|$%E z_FISbva8;cE&8p)?YHi9lw5DWaiG2VeecPS^aD4%d-iyXF6!rR=`Ba}n?-senyp zx6XE)Xx4Yub=K5%9lzqOzSiD+K;L;;uh`MGd%yQsV@Jy!eb-L!(MIpb=kzxN*4i;<>Kl4c;9G^&2hTlV$qOkG#jLJ1*bQcb(90mw8Kfb{6gQ zo-FG4aBusKbKcU+`n6BGir&|+U-cfW=s0nu>ujaI^Zkyxy?WEp_Ld{w(Leo!2gPv|QBDxhHPxO-&s)&Ug=;?>KY5^LDA;xU1vF zj?Rn4oeed5*@yc1>)!IcofqHpo;>5NzTVjYU#-x0o%HV8t?#aGzkW*J*P`ES@gBLY z@4DQ1{!ZuF5B2Mx=*92rcXoQq@DyeGiPL&nRr?J%Bn7XceOW{df`F`&$Tz7)GyuCtLpRv zd-XfV+nbMd?LMTRJ>A|??)~&YSLIdjC)d42r@SXGc~2L&x1dLkcNQOMzfsiLP^<6! zSl`*?Jyq&Ga<%hPvG>SzZ}U$5T2sgE1NyH0UAuR8-ulRU;)=fiXxF&|df9Go`QeVL zTi&BpT}5X)_cZCv@bhy0WJ_nuhn=@BwBO#_aq^75r$*liU)t@xbg<(@L+9?xowsjy ze%jb^@lr=^OJ_raey-8G=e+mWalK(*N7D^`=Z75^Zs_|D=oR}rY8t%d`#UZk(r;F` z->K=U|EP0MN#~_g9Zi?LN3V3$x9B@d^%<=)fB_1k;(gCDjxAL*#S+Ig|0^Xi_C8@oHt z?bL5Ibu=7rzjeU7uej^X<@TG$+pk~Hn@jcD{d&W7eaD@ynj2kJJN2Wxx=x+$`e0w@ zozvc9AA7Ggb-jPP^Uk4;Gk5g;rTU>d{o;|1E7jgTMSAUSeg7VP&*`pH?{%Ck*2_>~ zwSIViN6Q88kxQLrAL$1_@$P_?F1O#tz&xwpF6!FZtRL8=H<#!;_qN|U+ErESy;|ly zy+dy~sn>p>@7~q%$!)#prhf2R$AR76;|Du#T+z$k>nbYuR`1q#Rp}pB>P1!FODEb} zKGE-d=sjBIJ$9+9>a6$7d2iDh{WLn|X2*fO-hC*4Q{Q>RTY0$SR)hBnJiMf%e7Ama zufG3Ed-LIrJy$zRKG6@9w6}cJd1+TyQHkDkT0gl<-?y`)_`~*F2Rj=}y*Fy~19$YM zqK=&x^@AlHEoVD!?9y)@)hlqz`L4s5lhO6(yKYx?ojdLNJys7)Jeb`vZHq~__M(_C zTlQfl!@S;g^cp52~P)FI-t`l{x9e83qE*=q&B~->7E%Yjdf88ihIpa|AE9dj|B=K2+wP|SMO zHTuWRu$)Js-MB;SQ#~PD?3vay$FD`c*NVi{RZ$6X$u9;AF(|Xp;iEE$HpISBL=%}= zPHf?Or7Yu;U09=0$r`NLarh!YZU*Ag%xO<}EH;nD>apZ|ESkq+_gHd07N^JJ@K`cE zmTZqj^;pt97MI78;u-sGf~ldGhlCv=JU zvM$VTuny@F(2X=A1xW=`hExbxhguRQ;=1eE45Tb6Mk>;y2s3b@YBCPz>)3e0WO$YA z$`2?+YLGbsb|QrW8j!jH>rgY)4S_x*!;r;DQ&NZQM4d#&B0Z@KMW3-+A9{My*JT1d zMoN%60mq@H14bfss4cP&nXB7A>@%cYz%bN)w{;wIUmov3ZMDd!~-)>}Bv8CrzAOXKo)SyE)m(+*Ou#iEl^3r1);tH1%6?28yYi)8-;dF{8T}-^ponu}teI z?fCFIrym`n6hR%OnEffmy2okDKIU&-{#Mk7HIwE2Tp!j3F4ugO@k`AzZJ`uv1oPLc zIBmWoWyTS`bGgRjl+ibYZ%6i9Blla^MfMx)z39HqBDO#FO5Y0ov12B@ ztMU}d*hRAZVI`&d{*yA5{dYk2>viroz2!LFhv0+CbsUlQJHLbE_Ldu*-tqhO9x9K` zG2!~IVGEUD`88tl^&HmdsbVCzg@O) zTj^<84?kgBNuw;!mTN0z3%8Z>2x=?w>nWeBbhj+eBc-i`E!I|ABkSdn(pGv{mgAA% zR>~gSR_bq;M@U;KdsbU1&j4*De!baq+Dh5e+e&$6X)9%qZ7bm!sjbwX@n$mc1&BA>(G52vKeBc!dAXPmYY9_ei* zvU~}9PFo3&thUk<(mtQa)Zfpt|4ZMK_4@7Uw-1kiwi3U8?UVOQyOr?F)>i6ohet+R zscff|M_ya0zh9)DrT%`Gddd;kR^sn}f4zQx^ZS?dw^F}6iUZDF;ij91O*Nr<2ZD zES;lR8mmO6#nNeu<--@tK~wB^E;)FMrSp`?@+H!Fi~aRWXDyb_QY<^USQ@uPK3}oF zKIsf4vYle-e8tk}#nO3-r85-E&MTIkQ7i{%v2@B3sb8@i48_upC2}wo%g!s7PEsNV zb&3CZr85=#om);u#d2^KOJ^#UlSZ)|G{w?MOQh2kOD8RsgTGi#K61HeE0%+=SUP2~ z9E8Qv>5AoGD)zVUx3ko@SWXfp{(9vgFZTDp)T2b|St9$RMD|aKe^B}-0r?yy{^$1F z(?99>Cl%>eCCBiEY=q~}r9hS9(Lg8~ z*lCFYNX3t$2Uj6Tf!l;F4wqoR&M*qcjOPG^Z5D=8g0GjmI}K;%B;#aV-pP{y&d(jGxXH0uT*TJ^94Y=faQIGTaVF2{33fD`Kd@ZJ zDNf}{#u+-LIGkts0y`d#;~g7C%{G6{IFY9kIE3f)77uY8**jE!Aso+hHAVXznjger zHi0;W=Q~^2`EUl$a`RqCj=wOD=eh88Rt!h|EFWZ};E11Zy2YN1GkcQpUV&1aW;-_c zE5qqOlRNn7aMI86#r=--?m}f14t#@^dkL=(xWo;fpm1(ZG7j@8#VZ9zc6Z?mC63H) zp+YKhKwA11PnkGO=Nk4%1yAwN#Y+Wl>7;z%25uRLMUGAWO7X6M3gL|fS91QA<265t zw-*fVR1d{RMgi^dFpcHQ~99I8LI1G2Z4#APujog&oaEs?eyqw@_5BlAWEsi68 z#v4w1daupGSJK6gNiY?>AgX&&T%vH{oz^kOVgba8OPsjGh|5dj5-l!E#bt@OtPq!Y zaakrV%f%&8ToS}(wYVgS%Sv&1SzMCEWsSJ3!^I``(_bF<^v16>w%Np=6!0Io(9Sq8 z3DMC)U^1X7ATT4)6hM;!O$&kTG0jsn8PMpb3Bn;7r+u^7(eq^0Oj88sFVNAIr{@wa zS8m^_ahe|k3eoII3XvM55H&<<(5yqc&~uPNqBY*7lUAewO|@c9;uD3yJVmn@9aM<< z($h!$j!%5mk*06bf}VxuNYadMB|DI|0ewgd(vegV=O<+K&kym*9z6&tNNSOtgl<&m zM~!?QLNm8`GIFBd-rzayw=H+@F0~Lp7e}vAi&PmY5U^CB*GLO$ic}#>kp;+bq$ug| zEE?iDn;w!@4AP*x*XUr8AL9fqF_izJ%G101S2cb|B5lY{)I4cJ%2D&A2pN#{4>Tp# zE$$T3tJ^Gmf=61Da-=$GP5n!KNu4J;1ix3Y;~RW%YO>qcr+8ZYYA**`UmxcM|Lapc zQGB(B7xb@cczXQ(QO3VM#evxG8hNn)?lKSZuP?Iv^$ve|PQR9O^Y5B@Hv9VIO=|zE zom|gX`{aFc*YWjUzVEB^vYgx>e|3<{e|?GrxUcs}y-(fr^ubq{@jZS%PvTQIvMPB1 z8I(**h9?`5;mK}f_<&oI(*}Hx`hiRv=xuUt8VzJxav|y}I!2}wH z+aXKvO-W6j0^Ykwg@4T_Z}<2(?%pN3;r=d8_&Dx<8B2UBckcs|?$vR^M|}5pxP(TY zdIqnMbdS#q?-w)2Ro~+Szk3Z-@;)Ch-usAeI7`WWKA*hzF%`MT=Y{t^#kk&qCvu4I z5puabjy(b>7nj}QvQu34ipxG+xXyaVApz_Xm;K^WAueUOa3!UV!~XoebB?2eJR&X! z#pRf|9LI&rmOI|_=kJ~OgvOeVdB4-O)ZNtafesDyH2FTc9Sw?rcauku?~#L%3pj)p zzUp>>>A?-(I2#f6^j%SvAH zzq6(<5rt?Gr`4U-^uUwRSR>CRmtKd$-_pWf=IOH%-$cbvN?51B03c(~Eo3jMfd(Sg zKu`ajX@X2ZBcDbsJ+m+$sr~u!z^f(#zF>#^qJ|Xt;x-%V>F)BEV5)38zS=6bL)3cU zaRN_5x{<2XWuzcAOnQ)lq)hh+JoA;>dt`AQ=bMrJv9; zYb_tywAS)|t5xpgTICy^)>_^Twbt%7*_BKhupwEF3`wJg1~Lr}GB^!YvL+dqtVv^q zdV*|7al05M-_pwZsY9&%JW9#h6yAw(zsVb$t_BL$_x5WOBrn8 zeqisM*>eJ-UGp`C1#K$2aiCxmE7)TN~tvX^=hHAbY$) zdUgYE#9JFU)^BZ)p3%S?@zw@DCu(hYPwL(5pRra6Ukj|$fys)dD3@SqS*EpHOi^Tk zpPk_S_zQxkiD|LOiM5^i!;bE$>1XG7R(oHRcv)O%x)YNem37zS7X!Uw9CrL30bi~T zS%z=hYWRr|{?dk6!5uS2a2G9YcUudkF_LQejYU*YezOZdeO?8NOYWVi7_)&|~A-*Q#5Z{1tgo;Z~ap{GNXORDA zKF+7C{O3R&y#*fRusOt6Ssdb3m!}VEOvi8F@bgB8_%f|$U}~!250aua{DDDy!9`rc z#RY#i5YIwCmU8S7K$QT-t&U4gN=#0Uk6sy<+WnV3<5$JT^WWk045TlJuS&$<=8VVR zB2D&$I&rdUQ$3-ZHSy;kJi+4I9a>O1xH9?hi@p#6_(GMCkyVqcdsHW%nCSKiZ64Xx#2|r}3jHw=S*zW63&;TW5Tun%IdfmKp1APMt3R6NR)GVkQQkQ=2 zCEp-|2K(!p?;bJLH-xaEO31)NAr)$6tUGMHFPyMpO2`vc3#x}!=etME^bIF$gsI#} z!k$n<`j^`(Vk;-P2aNNLB5bq?8$;Mw6E=>p2qk0$?2});#yxVnZ#-cWjO89oBy5rr z(x-eu#n6g;ci+*z$%IW&Lc$J(SL9c&aSxi{dy=rJO2~k6SH+sj`R;)czG;L_H?=W? zu$d-o7GY1Bu-Sw~Dj|aoO|A^CRNaFo`sNTe*VM*5!seUovVgFKChTd#7AYb9%GXxR zuN>;`Ki2mQVb7Yd=Lmb=XanC1guQ5L_a(wGQyPsBWg!&vtI-TGj3R6d>02IGF{v`x z-EYjpI7Trw8%?{EQB2Op8sZtnTx&#EFpA07=sXFGVl4<63|}8wneQGl$(P6|rgvl6 zml<6R)Zl8#jIJ@4UCSuq)sUI>Ya8b`4|UIa(f10YuL3m~c0HphW^@CiuK_hU$wo$B zH>0VHZZg+kWi$<_VYs9-s+!B%7~Kps;HD2V7|jH_pvl%8``IM-!lgcq(Jkg0vKY+< zY8YTSjOGG0*w@Zz9?+qO)>h819_kLC;(LQp2T+5)PDb<1sEg69Kxfp)HBM>{cF%ml zw~f*5=CTEh76J`Co9|6V-vVmri?V{G#z_fs$V5*fu`mkx=j39rttTjL%-*Y`4`_(zFoE{?69RpX%n&-P`FZOL<6#q04Eg8Yp zs{4rHs>rYHjuWx}okdPx+jT;-4pyULEHSo8WUXihq`f{C9G5xO?VH zzHN+dmo+S?o9rGF=__Eg5U6O)R=w6eVutTcM&B}*eVbAI14YJq-(_@h4<%%J{es5u z<`DOc7kyE*$ory!K3N}9XKP&Rp8A|GhS6A{VlfG+bh!sl_Qf%}1gJrWrHn2ED*7$1 za)EomcwaoD%Ylk<5ndPK9`%%O1*0p0iqUPWT-_%MOdRY1jPaaG&gBWL;&8BH?R z@G_&Tfr`MvR=3t2G0&IG=o+BHAJ*2zxyQ}*tz~o_&|!yMm20cx+{35)USafApwsFn zH-y$%>*iD&+zIH_mC+*jnOSYC)UK(O>PW#PkP#y#b`FrDK)OT zwT*G^C!h7@Fq&(s_o1E82lzYjY=0~q?C`MsTT>S#~*f~BI zqg#Pu99K=Q33m^D(zlJ#?LeV#Rd@~QTfk@`P^{>OCRc^K`;GIx$>>`^5iA`FuL^PZ z9qW6W(RYBtcWT1x$#>pmba61v)u?&0yMKf)Dp<_F52Ar$ywxnIr_mC_Xe>~8Dm13i z636Hgpay-HGP(>XdZ@-#PdyaR=yIUw`9roU;i9 zG={b@x*aIii~0pkliiaS`3e{<1ZrsQO-A1WYOv1RjJ^Xjpo8ySMi+R&UtKHNQe zwl9kIR1czo8mu0}Xe>}ey>W~#0cy};DWl7P8uX24bU9F?|1!D~sKIv<7+qyX6B$hc zYIus58C?z3Fy4|GT>~`GLl4(7x(+BJJdEVY?(nI;R~UU2s9^-GXEeo(Zea8^pop0K zHrvSP>p%^oFqP3w<{GSwrkTs8GpYhLSjWcbW}t?dD}&KYpoqco9C7Y}6MPz@Tg+${ zquFLOhtXW1h%)^%mz~i(bJ;f-#i@+3hn$S&12yRDVsxtseYlO$?LZB_UchJ}P^>MO z*=db^lhL=#W#4A>9iWJnG2(3QL6dy%GP)Sse*+ptX9f?Vff{-jqsxIBo?-=~E6w#LFuDq;;n@-yO){e|GrHPbLo%alfExOEEu-sz8fK$c z7=0CJU<5o|&u9wJzzFbdVDvSh20LtI^mU+yagoaCCUXr|M$>>Atdq{D3e;eA8>5?n z8tjn4XeLmDS8I%J0cv>SEJm||8uZO!G}nyU8O<}-@CKteP&8`hWHjH5x)|LG)UYya zV|2T@YyqQ%Km#M-;hT)U1=R2qZ!`K1P{W-1E~ATknly`|1FQ$pKn*p-Fd7Tg;OlXW zE-{x~%IGqnhLty-(dFi{D;QmAMiUraWv(HSQT&TK#-4wf(bYf=b4fCzYs@vQWptgn z>?@4E3e?~a>lsZkqZ=4~&4fPO$mr`p4P!Hv(M>=Nmb5aO2K4FcA=hm`SnFQ2!k5md zYO2>~V{|jnz{v7tFq#R}&=(q`TYwr`%VIPes9|K~Fq&&d?TqG`Yj}fEoP`^E)5&N) zP{ZhUF}f9~!4BIP-44{CZvmr)K&RG+*Snf*?rG2a-emMGpxDjS*y?FF^ERXJ01b?b zhwn0qe zt^{g$wgg630S)NuOJp<&sKL)(W^^@B!)QrnbPZ6$Q>D$2QYv!^W8GRk7=*@LC>)gX<`cfIiKRRVVA6gkr zGoik8Mpd9<*jk(-_?XRO~vo z)^Bx>U+BwXG~0~kFq&&d?Tq3d!!qdWdxKHD#t|n(^-G$TxTielb26F_)L=6gqg#QB z2yaQv68EqfzHN+d2P%ALVf{k)n7O_JMhng8n~c5%RCx8)rmgO2FZ$kQ^c|og$_%X! zbw4rN_b#K0d$ad#uG#D!G0PW4Zwnqo0}ZtHFow}spoZRzV{{2nu^um}U*aA+&$pD( zWk7{TO{tpV?uYXSMwbH>zO$riiF*L{Z;Y-4DtvNS%`o?nslEh8R{=G&mdI!lP%)P* ztXb#|pYD5^(bYf=eVokb8gmV68C?f7@NB+U7=0BePV1VMTwmgTD#o{-(G;MD);2Ku z8c;E^rqoPv51r=Q$mr|lvZ;)20*aHI`pr$7-BVxiSs6_OiW7nQbxrHsPd@KUXH*4> zlj){~*B82HML)DLx*4d!z8Q>W0yS7iV{{8pqyI9R4b;%{IgI9-%i0;u18T6&8;s(0 zqd2>2nsR-LduEi+$!I=MgLPbtZUu^yoTkw0q3-E8KVx(|P_e6<(lo_A=^0-EqlMx=))4J>H+_PhSaf~i8m3^?3(Pe#< z8IkfC?)Muc{rLWr8FCEYpE?VDn&F=MqR3@5ZF5h3>Cww{FW%pMIda-JN_>bV=+Ucv z!k!ActBk7|`3?SNe9(hQ%EVdsgKhVM2RsVtF;Os&`t%tRR5b9x2xZK~`@wx423dN~ z{cW#)<;fLeD~2B*duXhC(0EK-?p_nC?O*m<@Tg~SNa&*;p=0nu@lfw?@UI;NJqT0A zL{tyJ{`Iahv?BfI%4}cgI!k2G{ouat{_}i2MENA4DSF4H7woB)k>u zU;b8Et}mQKyb$asG#XA4UIjv0=^IHBUIG#~iX^=LgEYrCh9tcBgEZAQjwHPDBW^rN z6G(dY!9$^dR7FA65%_9+S?gl+ldJYNoEg)IEB?HhKK%9jT@oq^LS#Ty&QvA!IZ#k-oYLsKiB@YxvTja@kGJSnTo;)Yh z*I(p?&y;8S28uj9ChX8?-yo5PyNLV{k%zO0e7MNNQ$&8a$iq)Wex%65OGJK@$kQw0 zOy3xh7amgns&Aag!#{=`O7M*rd3c9tccREoQo=^w??3c@|8Q{~G+O-MZ}9yAqrd0> z{zD%{S#T4~Q8nNjWjsdmGRt5gkFLu62LBL4(1Yp9nC-*wDkC2RD`OLa? z%`F{F$UBVKEO_{L8CotJ0~)~ZR}7;<(Zl`7gP7rj6atYIy~&6JE+mcS96@EajO?~Z z)<{Cqgllol=}!=nGOF7m8%7bb8ZIa6Nghqe@-f|Qtr$Z{G+YrD83lvHj3uN{I42NT z|4l-Cj&a>JJI7I(EpS?YpG6RoHom*&^znqGz?o(18zvC4dSZ8(JOTfRnM`H21K}rqP(a9|yeUxk(IRMw3+4Lz#gV|~!?_;4Jwx1F)c4E&7&-6`tiT$t6Wnf0iu8vN5*vBQ_S<#E9b>X%ttYV#kT(>Zh%l28$(kNUgL# z)a+0aSqe}J7f>3YQQL#nCINit!GQ|Y?Ik_7NP)I!3rMzr5(^nM&=y629%x7>pZaE( zlnu+B1!mvOyuW$x%{TLxcsz=rJ^PiL`X*5S8z2E+}dAx$ulDP#qK(*Yx3 zidoT=vXVCzji4FIhJco|pb<9ZtnAStBVtCgQL`=ECLkixPLK^7F*BZx3n+lzM4EgZ zX%Q0N6n%TyLa zT)I?Yv$l0M_0r2}M`vWHq~_rzxTmO$CwV#Tuk$@QZ;{;-vbk-zxs-<%g(a57wP3ngQ;FN0IPWva~6iRleZ-L<`hQ{k(BMkdR zk4*ySMDtcg34@b`*(|}WGmq_pH!kXMurS9e3?KktkiwT4hQn7jG@K*Yv>j$ta1IU` zK%6#|x6Kk@Va^~aPj#k=A($V1yrdfjHdU6N#Uo8`>?I2?QJsNmyfjNJvPdW{*^cAs zz=Pv9!?yLvFHcX|a>}KWO&I|vDp+Sp&)D>=?%+b%%5yNrKx?E#NnYh_s-->VcG-a` zW@A$?Qdq_ny9`!+guz60bfaz)74<|elvJvkUPKj$=mnk`JPnceV@wvx97(xvWLY(Js~N|p zm>+nB5)VoJOoECjKOaj3jClcS=D3wj$20Aj`oA4mguUn#Qc8JVH9{fm3uPUAhmAYK zo{s8Z6Gn#~!6?>rhXRsPh%zmDVRG;b4}~+;<1avHS_OBI7$`z0x4~rfrOa{(D2S7t zKQW4QRYzw-9q;nz8?xk1&* z>$)2(vx0JdtoZYv@%HCo$`7t9DUM!Dij<=v?cg0pz+{&z!X|0RW0|vc%ash>VXib! zDvle~9o=%63W;$e2QIlmPCEXToT1dFuEfB5DtlUT1k!t8#tlN;Y{wOIwn_P0hjN5Z zhFzH};g#SXd0KMa5IUkRMq3RTywAcc$U?Sw;H;NN!s@ zcl~?2a;%;>ciZ`V`Pa*Tkbd{AKfe3>cfWY=?i*8gU!C4MH?uWEw_?t|7>GpoWfbdO zpZ!JuFEd;C<(;;(yNRRq#4reb=ZnPF@WfW^>Yf;Ak2a9cEO)IBesXyy{QPcr-$vh0 zhu7qP!PI%UJ^wDB+?G$?l~1o;ynp<}#(QfEA60)?y)CU?s`KHkgTrx&++<^fqE}~5SC)0 z29kmy?;S9n6vmP=dknE2pcg z9A&CD8|_xzeXr*SAOKQU%DdMjdS-gMdtSeO{od=>uluhmD=RpJuHB2_A5C)H|DXpw zSk)VT^DlUgyTOT^$Va#+KgIKOZ;F_r<|#9~H%BZ{>y#Du7SS3hiYQ>$E2k=>RZ~?_*OV*jo^nU4r>dhhQ#H}rsoJP#$`h@ds*Bc7)khnq z8lsI;jnSs5rfBn2bF^iuCE7aG%5x^}c}^^UixX`hnp6m<+E|($X%(Ti56xQYE|%&* zsxvROouyVHwJI-FV5u&oy7N-KEVUY`HF>EWEVUM?p1jmfmRg6@`n=RGmfC>S#=O*S zmfD2W=DgG%6BjazEkA7i5csBG&-)X-V%uAm57igkR4>cBD?hK;k)PkriJfBmTUMh) z->l&4x<}=GeAW?&#o~!TA{>v&c(g~Pq(2x4UI@{{bSQBmK0O_h?hzb5bGCKt)X9^l zCjDndk00}oo*A2%@Q=MPdU|y1?6K2lvevV>JC<#Kb#h|t)c7&~nPX$8CdWrlzve$Z zI{ExD|4YYS`^>4+pzxgyCZ*U=QiWj*llgLk* z#d6W~mSxH!+C($1R?#k6a4id!ixr|Zum5ecWj;r?=442g$3uxw5MaL?Nlu4jbF&o! z!gD~G5I83%q(Cqs1S0`i7LsyE6o6DAic*5eilOns3@a!PvVxE3&zu!P zZze)9Q3&7{j?P3vQFNd%9ljijp{B%zxTqBkYQvE=l1K*)t>NB8MX9vv#8rnV=GchsmJ}@n~o$a867{0(0THq4AJ>DG{F;nhaeLUY6n)fkb&| zCNLWf`SIPE@cCJggmgI^3=N^Fmxm(Zb3-$;Xm4y}aPPh$Ih+XXnF$0h1*Svt5PeP# zCHyhaY#*=IW8!0lDNwq~Tub!hP0qJ`@t?8cJY+UhJXl z8U`v<3`m5PpvDVv3u?k|d!l;Iz`)ByQfNtBkV5Dc2ER(>ltv8<$HIwlAR@?Mf}sM? zKj5Vfd`PCR2`cPXR?l2x3E31K##lrR`VVxLdA5Re5q6v`VFCzEnQm^^isx;qlTf)NDX6%&RBpAjY*E)r5W zNDNPyP9W9zw%TL;MU5r^N;jI`5c|@*%$e6^lOI^5PQ1&S!G5x3QYev>Vp+?%cs!!L zk%kFXdnu1)G9C+kU}6svp;5cfTF*z~frQVVwfX(A00yN$TjBRd;~2hl@AUiMNCqO> zn{vNjj0gRGshcYHQ8Ylc+h(Nr42XJG8lPRk+)5a%EK?o9NMuyZdyNRc(uqOrv^C( zwGK}fz1%FM|D;4&kNd3I8h@V1^ata~SRz~LR|Tc^==K|YMN9EO;>roXAvjrvY`y=y z6oRbMONV3rD{)Db3P})sPb1aupYuHm8-se*aeymRe_rDgF*B(|uu|y;EFlr;7ih1EDW;L^5?kYR+nHeTk6f@ivjC{tv=XxHA zIwpMsgu7-Hcj`!Q&j?;N(lc$ls1?mSwBj0T1;zocg+b&m^`T?=S+K=Y;{>S&b?3^? z;kI;rbD;OD{5&t3FMSU6n&$a4V7X^FqCGB)3LSb#)uyK8dZp-Q3=TffDl(Yd`bemQKR0`+?01G!7bp+JCCMWPI?)c zpdnGCA&>JW)2g^F6rc>JbKdO$F=VEU`s5l!;69p+WU_PB)~=2mB2GOR)SQONmA>&OuVShEX2FQq^FCDIud442G2}MUw>ryC4`jjandnK95Y`YK^!^G*VUF zq^OCEtJN3dme|?XQH+t}HSTUn?zQUd+^PZpRL8ghGpZn{7#2us6>>67Q2KZk$dbBc zLos-aZGDU2BaRL9Kbl<;SKonM?WjCrN`h`K&FYA0<ipivctauQ`jew4(@F}sK_A^O&CL9dMlk#|SCK3+91gPq0 zJ24?bZXul&*j3P%{`}Fxlr?j<+Ak-e0?IN>=6M_`Tc?^v*os8XZc>_2^*sV+gKhl) zVCr+Bo?e;*?b0>6^}SCJ``SrcB6a&w8r_?``&3Zm(JY1h-=d-Ilkx%cUm?df6`X+Z(T9DWZh<8ib<3`U~^QHK`3 z`1J(wV;1t5?Av*%CCeE$k|Zo0hV4YJiQ_)hZl$5p-`%l{%Hm0ZDF_DRCM>X!05efT z4*I;b9bsql-^ch?p;bi{8D%Qhk^qH7yp<*c$r)H?r8g+4iJ~G(VN#^)QG`!2<&RNb z{u-hZD)KtbQ+pI=Ps+LPu6x(b*%h~su8FnwgG&3s75CHYI$^E2Iu;Xay`xI+=vwcD z(mS!z`w}y)^rYQAj5V&hdp>ft-h6G@)%(N4w>@hI&nO4aEcc&HyUrG6%%~O^ZMcCD zjbO;G-;$?DG^r`#6HF)XeiXWxjQfu$uQLg6hsq>$6}{V+$0SNZ{ZA?LP6AS0<}p$! zl;uq3J-XE-0sov@y^{p&QP9oRihK8xxHdSZ;NLZtvW-2a1dLJVQ1pHJo-P3`)+aZe zy!DyQGN*NDv(;?ftyv~4*72MK9M>cu75_(+fUX>soTl~a(klss+TTLr!C{~S5>FSg z`NEGaj^~TOqSXOf&nlv9rm8mHrb*kS_5uWaozwv{*tIL$HH2 z@=^AECfB41Dc&7slB@Ci_od!zmRb= zaaC7{Cnlc$7a>X;(YjdLH=>U0F0AMoBf6w`qB-ggQ!>=My#?t}Q!=vMvuW8qT9#6$ zW%(kCQIBSzZ#RrbIf_%=gZxREoJNQT=~P3!cCHv3B)0w!Udg1G=TF?8OFMdUruh*C zeHu`lPvsls_rWsS3+s-1C}kUbOc^$d&q);hSNiyFL}XnzTc4o08?5Vfu&xi9tv

cvQ;{tR3mm0BKt&!mP?8!5ldhiQvUh+gJM(XjVbGI*ZuPIFjwWRlYBxu zV(bu-KC*{2KC%p|8s8ds9tdz94wp3x9XeIxd)EsQ$innIYu-LMEDtVx2&K4Wjm(s6 zv?!$iXu{;@?)~%)&b4LfVE&mJZl{X4XaJ_E#xFS{b7dK-f`|woqI=Ril>91n1l?Qfs5-W%6x=z99bA29mH&=)m)}LlAd4N` z&`3S9=yngSAAZo$ee1+`yoHV#rDNs~LwCRYgRlPnSO1`QtK-o6OZWX9588NW_N^1! zu+6N#zltUKqqZNpx1RsfJ^s{I`>96`YwH=yW1GD_Smx}_!LpBQ3~6L`?1QmSe7nja zyr$g31==)13(bAu99EM#P?#%)tO?R!C-XYhI3IQ~sp|7aqjU6oo(Wmf0JXhN2~P>@ z>H|PwpY%=2=%R#VvV1oM1W*VwKJSafj2Fi3imT}?Q^PQ_ z^m)^3)Y0d?^tI>f=ZFHG*Jlx^EP+M9cLktl*iC9eK zz5ZCt^wBADnVIw?4KPCqW7o}Tg!w1)5PP7T!m*ZHl_V50ml)ZM5nozr((K2u1tY9f zl5*7gO-kOTs$LR6HOW&V)-BzlUvH?_dt|jZ`e8EMcj(5+ z&8FwJ><7zMzr$4?tEfn_mr(( zWOQ*IV_WvoqJ3ezdDoVG7u35$M=izh6ja2r_qfF|vfVvVb`XHKh4z;l80g~sqh&V( z;ezf~YvL?>njSZ@+yyJ=^b{Q3ilh5e3}fuEjdS9afG{9upy23J9DSc!Z4RDF`pX2B zJ-Zwo+fChN2kv-Vps(CO5aJ{z%Wi@^^a3wIU$V5JZZqoQj=KKwasxrIqV<*C1bLuz zmc0b|xYlsFk)S4O?HW_7vT{nPqttO1vl~chwngE%{&7;zfa$bYAnn@{9SIp-rpb3Nt>6(~OWl z^&3wzFc|@0k$fw|S0l7a4Vyb(3h7^j3PuF0{P~bUc3yen2g&D4-N3vALVS&e@R?}e zE6WR2u4UqX*d)v2Jli1V!~>*Wu>El{pq2JSjx?&zE|?p*n6|yxK+BpXNj6uzQO-Th zb$p5Hf=jCx+;G-~k`pw6KICqY$QD`(sNNiH*?g9aI-VUOYEoW#9dM|6X zn5ma~Qn@q?Q#6gX=BgLR1Z}%cOUM|@$i*W%0A&L58sZn*I#t`T!rsR@O3!#ib!nj=W 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/render.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db3bed9ff1558f0904e9374996bb64e018d5cef3 GIT binary patch literal 47392 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%0A+XKjOl;{{>g`5!dh$SNaiG{}H#5-6IJ6 z1(){`SN;)K@v%AZk)`4a&R`+l9NJpT-Bx-3p?@>M#~)jf>TGcwqm{%I~B5KJQq=v6;CR{xn!r% z4l5sI(CHb$xzKnnT79rw=Y!dRncvP{(vx1|xoCAPS@DETv&o(2i;{fNv^ibekSuOU z@$0ju5+hR9QkqDXLfHzNTpm5Qw`I+EvaZ7umKFNmQA?>&OF<~xVOUC` zS_(qhEjfU;C5yME(D^1a4)Bye4FZuv^lc9FtfwhJP1zx(TK1vT-dZo$o}ScOc(UHY zQ|>K!{2=$4!CX2VG_-$$;RCi^f#$<70irT?l2j>5`<2>yyRxDL!%^qmq5y1{!^VWus62I9by^ zKES$^o{>FSGoGv(cv3AeG!XjRZ}2vLAI#JEeM=!PpP#Mg)>O?`RL>Wbe(EjbvF+DK zBd?6jH9XNbg{4_1geJ*?mCoF^fHcterwY0?s8TLieBMt$g89faeeLOAZU)%4dB}%xtO@Jxj`^=SFlh zelJ=pm#lbZi+7+iMB6U@q0<+&3p@=EyQl7th=1A$eOT4HEX628dsBb zQpiP>VQ)R0EpN$7ar=FM<}1va=}E`{PX-mw>{@Cky`(3-#FM?mbE(wK=1_T6*51I^ z&$|nr<;y}tu>ZdJi@D)cATtU|6=8y98fzqs*0t_Gp z0I_6!*3N?XI(bV@7A)sXMQOe|$yd)DP1hVu)*MXntyxnMx*$-KHRH)3;3)&35aqw` zM2JRV8_$wwx7G4_^Nvu~jOSv(+N>2%IS+-T5kaBHcptx$L8oUUuqvL5R_Brx&+J;` z3f0IJ!jn}9PZ>CcqzWOVREQ!8nd#cLKUuRs#UIF;ocw+^a`9xK@RUJOi0XOYi4cK$ z1Z|_p;v7iL$(rUAVokMFsY+_GR4F{w7AuuP96>_4^!Bk_>{WkHMh8wQ70>}fD9&4v R_SD?-)TBIhSEU0>{$CN=8Oi_v literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c81a8dd510fc885cbddc0ed99cecdb4d3221d96 GIT binary patch literal 9557 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 Any: + """Compare a database schema to that given in a + :class:`~sqlalchemy.schema.MetaData` instance. + + The database connection is presented in the context + of a :class:`.MigrationContext` object, which + provides database connectivity as well as optional + comparison functions to use for datatypes and + server defaults - see the "autogenerate" arguments + at :meth:`.EnvironmentContext.configure` + for details on these. + + The return format is a list of "diff" directives, + each representing individual differences:: + + from alembic.migration import MigrationContext + from alembic.autogenerate import compare_metadata + from sqlalchemy import ( + create_engine, + MetaData, + Column, + Integer, + String, + Table, + text, + ) + import pprint + + engine = create_engine("sqlite://") + + with engine.begin() as conn: + conn.execute( + text( + ''' + create table foo ( + id integer not null primary key, + old_data varchar, + x integer + ) + ''' + ) + ) + conn.execute(text("create table bar (data varchar)")) + + metadata = MetaData() + Table( + "foo", + metadata, + Column("id", Integer, primary_key=True), + Column("data", Integer), + Column("x", Integer, nullable=False), + ) + Table("bat", metadata, Column("info", String)) + + mc = MigrationContext.configure(engine.connect()) + + diff = compare_metadata(mc, metadata) + pprint.pprint(diff, indent=2, width=20) + + Output:: + + [ + ( + "add_table", + Table( + "bat", + MetaData(), + Column("info", String(), table=), + schema=None, + ), + ), + ( + "remove_table", + Table( + "bar", + MetaData(), + Column("data", VARCHAR(), table=), + schema=None, + ), + ), + ( + "add_column", + None, + "foo", + Column("data", Integer(), table=), + ), + [ + ( + "modify_nullable", + None, + "foo", + "x", + { + "existing_comment": None, + "existing_server_default": False, + "existing_type": INTEGER(), + }, + True, + False, + ) + ], + ( + "remove_column", + None, + "foo", + Column("old_data", VARCHAR(), table=), + ), + ] + + :param context: a :class:`.MigrationContext` + instance. + :param metadata: a :class:`~sqlalchemy.schema.MetaData` + instance. + + .. seealso:: + + :func:`.produce_migrations` - produces a :class:`.MigrationScript` + structure based on metadata comparison. + + """ + + migration_script = produce_migrations(context, metadata) + assert migration_script.upgrade_ops is not None + return migration_script.upgrade_ops.as_diffs() + + +def produce_migrations( + context: MigrationContext, metadata: MetaData +) -> MigrationScript: + """Produce a :class:`.MigrationScript` structure based on schema + comparison. + + This function does essentially what :func:`.compare_metadata` does, + but then runs the resulting list of diffs to produce the full + :class:`.MigrationScript` object. For an example of what this looks like, + see the example in :ref:`customizing_revision`. + + .. seealso:: + + :func:`.compare_metadata` - returns more fundamental "diff" + data from comparing a schema. + + """ + + autogen_context = AutogenContext(context, metadata=metadata) + + migration_script = ops.MigrationScript( + rev_id=None, + upgrade_ops=ops.UpgradeOps([]), + downgrade_ops=ops.DowngradeOps([]), + ) + + compare._populate_migration_script(autogen_context, migration_script) + + return migration_script + + +def render_python_code( + up_or_down_op: Union[UpgradeOps, DowngradeOps], + sqlalchemy_module_prefix: str = "sa.", + alembic_module_prefix: str = "op.", + render_as_batch: bool = False, + imports: Sequence[str] = (), + render_item: Optional[RenderItemFn] = None, + migration_context: Optional[MigrationContext] = None, + user_module_prefix: Optional[str] = None, +) -> str: + """Render Python code given an :class:`.UpgradeOps` or + :class:`.DowngradeOps` object. + + This is a convenience function that can be used to test the + autogenerate output of a user-defined :class:`.MigrationScript` structure. + + :param up_or_down_op: :class:`.UpgradeOps` or :class:`.DowngradeOps` object + :param sqlalchemy_module_prefix: module prefix for SQLAlchemy objects + :param alembic_module_prefix: module prefix for Alembic constructs + :param render_as_batch: use "batch operations" style for rendering + :param imports: sequence of import symbols to add + :param render_item: callable to render items + :param migration_context: optional :class:`.MigrationContext` + :param user_module_prefix: optional string prefix for user-defined types + + .. versionadded:: 1.11.0 + + """ + opts = { + "sqlalchemy_module_prefix": sqlalchemy_module_prefix, + "alembic_module_prefix": alembic_module_prefix, + "render_item": render_item, + "render_as_batch": render_as_batch, + "user_module_prefix": user_module_prefix, + } + + if migration_context is None: + from ..runtime.migration import MigrationContext + from sqlalchemy.engine.default import DefaultDialect + + migration_context = MigrationContext.configure( + dialect=DefaultDialect() + ) + + autogen_context = AutogenContext(migration_context, opts=opts) + autogen_context.imports = set(imports) + return render._indent( + render._render_cmd_body(up_or_down_op, autogen_context) + ) + + +def _render_migration_diffs( + context: MigrationContext, template_args: Dict[Any, Any] +) -> None: + """legacy, used by test_autogen_composition at the moment""" + + autogen_context = AutogenContext(context) + + upgrade_ops = ops.UpgradeOps([]) + compare._produce_net_changes(autogen_context, upgrade_ops) + + migration_script = ops.MigrationScript( + rev_id=None, + upgrade_ops=upgrade_ops, + downgrade_ops=upgrade_ops.reverse(), + ) + + render._render_python_into_templatevars( + autogen_context, migration_script, template_args + ) + + +class AutogenContext: + """Maintains configuration and state that's specific to an + autogenerate operation.""" + + metadata: Union[MetaData, Sequence[MetaData], None] = None + """The :class:`~sqlalchemy.schema.MetaData` object + representing the destination. + + This object is the one that is passed within ``env.py`` + to the :paramref:`.EnvironmentContext.configure.target_metadata` + parameter. It represents the structure of :class:`.Table` and other + objects as stated in the current database model, and represents the + destination structure for the database being examined. + + While the :class:`~sqlalchemy.schema.MetaData` object is primarily + known as a collection of :class:`~sqlalchemy.schema.Table` objects, + it also has an :attr:`~sqlalchemy.schema.MetaData.info` dictionary + that may be used by end-user schemes to store additional schema-level + objects that are to be compared in custom autogeneration schemes. + + """ + + connection: Optional[Connection] = None + """The :class:`~sqlalchemy.engine.base.Connection` object currently + connected to the database backend being compared. + + This is obtained from the :attr:`.MigrationContext.bind` and is + ultimately set up in the ``env.py`` script. + + """ + + dialect: Dialect + """The :class:`~sqlalchemy.engine.Dialect` object currently in use. + + This is normally obtained from the + :attr:`~sqlalchemy.engine.base.Connection.dialect` attribute. + + """ + + imports: Set[str] = None # type: ignore[assignment] + """A ``set()`` which contains string Python import directives. + + The directives are to be rendered into the ``${imports}`` section + of a script template. The set is normally empty and can be modified + within hooks such as the + :paramref:`.EnvironmentContext.configure.render_item` hook. + + .. seealso:: + + :ref:`autogen_render_types` + + """ + + migration_context: MigrationContext + """The :class:`.MigrationContext` established by the ``env.py`` script.""" + + comparators: PriorityDispatcher + + def __init__( + self, + migration_context: MigrationContext, + metadata: Union[MetaData, Sequence[MetaData], None] = None, + opts: Optional[Dict[str, Any]] = None, + autogenerate: bool = True, + ) -> None: + if ( + autogenerate + and migration_context is not None + and migration_context.as_sql + ): + raise util.CommandError( + "autogenerate can't use as_sql=True as it prevents querying " + "the database for schema information" + ) + + # branch off from the "global" comparators. This collection + # is empty in Alembic except that it is populated by third party + # extensions that don't use the plugin system. so we will build + # off of whatever is in there. + if autogenerate: + self.comparators = compare.comparators.branch() + Plugin.populate_autogenerate_priority_dispatch( + self.comparators, + include_plugins=migration_context.opts.get( + "autogenerate_plugins", ["alembic.autogenerate.*"] + ), + ) + + if opts is None: + opts = migration_context.opts + + self.metadata = metadata = ( + opts.get("target_metadata", None) if metadata is None else metadata + ) + + if ( + autogenerate + and metadata is None + and migration_context is not None + and migration_context.script is not None + ): + raise util.CommandError( + "Can't proceed with --autogenerate option; environment " + "script %s does not provide " + "a MetaData object or sequence of objects to the context." + % (migration_context.script.env_py_location) + ) + + include_object = opts.get("include_object", None) + include_name = opts.get("include_name", None) + + object_filters = [] + name_filters = [] + if include_object: + object_filters.append(include_object) + if include_name: + name_filters.append(include_name) + + self._object_filters = object_filters + self._name_filters = name_filters + + self.migration_context = migration_context + self.connection = self.migration_context.bind + self.dialect = self.migration_context.dialect + + self.imports = set() + self.opts: Dict[str, Any] = opts + self._has_batch: bool = False + + @util.memoized_property + def inspector(self) -> Inspector: + if self.connection is None: + raise TypeError( + "can't return inspector as this " + "AutogenContext has no database connection" + ) + return inspect(self.connection) + + @contextlib.contextmanager + def _within_batch(self) -> Iterator[None]: + self._has_batch = True + yield + self._has_batch = False + + def run_name_filters( + self, + name: Optional[str], + type_: NameFilterType, + parent_names: NameFilterParentNames, + ) -> bool: + """Run the context's name filters and return True if the targets + should be part of the autogenerate operation. + + This method should be run for every kind of name encountered within the + reflection side of an autogenerate operation, giving the environment + the chance to filter what names should be reflected as database + objects. The filters here are produced directly via the + :paramref:`.EnvironmentContext.configure.include_name` parameter. + + """ + if "schema_name" in parent_names: + if type_ == "table": + table_name = name + else: + table_name = parent_names.get("table_name", None) + if table_name: + schema_name = parent_names["schema_name"] + if schema_name: + parent_names["schema_qualified_table_name"] = "%s.%s" % ( + schema_name, + table_name, + ) + else: + parent_names["schema_qualified_table_name"] = table_name + + for fn in self._name_filters: + if not fn(name, type_, parent_names): + return False + else: + return True + + def run_object_filters( + self, + object_: SchemaItem, + name: sqla_compat._ConstraintName, + type_: NameFilterType, + reflected: bool, + compare_to: Optional[SchemaItem], + ) -> bool: + """Run the context's object filters and return True if the targets + should be part of the autogenerate operation. + + This method should be run for every kind of object encountered within + an autogenerate operation, giving the environment the chance + to filter what objects should be included in the comparison. + The filters here are produced directly via the + :paramref:`.EnvironmentContext.configure.include_object` parameter. + + """ + for fn in self._object_filters: + if not fn(object_, name, type_, reflected, compare_to): + return False + else: + return True + + run_filters = run_object_filters + + @util.memoized_property + def sorted_tables(self) -> List[Table]: + """Return an aggregate of the :attr:`.MetaData.sorted_tables` + collection(s). + + For a sequence of :class:`.MetaData` objects, this + concatenates the :attr:`.MetaData.sorted_tables` collection + for each individual :class:`.MetaData` in the order of the + sequence. It does **not** collate the sorted tables collections. + + """ + result = [] + for m in util.to_list(self.metadata): + result.extend(m.sorted_tables) + return result + + @util.memoized_property + def table_key_to_table(self) -> Dict[str, Table]: + """Return an aggregate of the :attr:`.MetaData.tables` dictionaries. + + The :attr:`.MetaData.tables` collection is a dictionary of table key + to :class:`.Table`; this method aggregates the dictionary across + multiple :class:`.MetaData` objects into one dictionary. + + Duplicate table keys are **not** supported; if two :class:`.MetaData` + objects contain the same table key, an exception is raised. + + """ + result: Dict[str, Table] = {} + for m in util.to_list(self.metadata): + intersect = set(result).intersection(set(m.tables)) + if intersect: + raise ValueError( + "Duplicate table keys across multiple " + "MetaData objects: %s" + % (", ".join('"%s"' % key for key in sorted(intersect))) + ) + + result.update(m.tables) + return result + + +class RevisionContext: + """Maintains configuration and state that's specific to a revision + file generation operation.""" + + generated_revisions: List[MigrationScript] + process_revision_directives: Optional[ProcessRevisionDirectiveFn] + + def __init__( + self, + config: Config, + script_directory: ScriptDirectory, + command_args: Dict[str, Any], + process_revision_directives: Optional[ + ProcessRevisionDirectiveFn + ] = None, + ) -> None: + self.config = config + self.script_directory = script_directory + self.command_args = command_args + self.process_revision_directives = process_revision_directives + self.template_args = { + "config": config # Let templates use config for + # e.g. multiple databases + } + self.generated_revisions = [self._default_revision()] + + def _to_script( + self, migration_script: MigrationScript + ) -> Optional[Script]: + template_args: Dict[str, Any] = self.template_args.copy() + + if getattr(migration_script, "_needs_render", False): + autogen_context = self._last_autogen_context + + # clear out existing imports if we are doing multiple + # renders + autogen_context.imports = set() + if migration_script.imports: + autogen_context.imports.update(migration_script.imports) + render._render_python_into_templatevars( + autogen_context, migration_script, template_args + ) + + assert migration_script.rev_id is not None + return self.script_directory.generate_revision( + migration_script.rev_id, + migration_script.message, + refresh=True, + head=migration_script.head, + splice=migration_script.splice, + branch_labels=migration_script.branch_label, + version_path=migration_script.version_path, + depends_on=migration_script.depends_on, + **template_args, + ) + + def run_autogenerate( + self, rev: _GetRevArg, migration_context: MigrationContext + ) -> None: + self._run_environment(rev, migration_context, True) + + def run_no_autogenerate( + self, rev: _GetRevArg, migration_context: MigrationContext + ) -> None: + self._run_environment(rev, migration_context, False) + + def _run_environment( + self, + rev: _GetRevArg, + migration_context: MigrationContext, + autogenerate: bool, + ) -> None: + if autogenerate: + if self.command_args["sql"]: + raise util.CommandError( + "Using --sql with --autogenerate does not make any sense" + ) + if set(self.script_directory.get_revisions(rev)) != set( + self.script_directory.get_revisions("heads") + ): + raise util.CommandError("Target database is not up to date.") + + upgrade_token = migration_context.opts["upgrade_token"] + downgrade_token = migration_context.opts["downgrade_token"] + + migration_script = self.generated_revisions[-1] + if not getattr(migration_script, "_needs_render", False): + migration_script.upgrade_ops_list[-1].upgrade_token = upgrade_token + migration_script.downgrade_ops_list[-1].downgrade_token = ( + downgrade_token + ) + migration_script._needs_render = True + else: + migration_script._upgrade_ops.append( + ops.UpgradeOps([], upgrade_token=upgrade_token) + ) + migration_script._downgrade_ops.append( + ops.DowngradeOps([], downgrade_token=downgrade_token) + ) + + autogen_context = AutogenContext( + migration_context, autogenerate=autogenerate + ) + self._last_autogen_context: AutogenContext = autogen_context + + if autogenerate: + compare._populate_migration_script( + autogen_context, migration_script + ) + + if self.process_revision_directives: + self.process_revision_directives( + migration_context, rev, self.generated_revisions + ) + + hook = migration_context.opts["process_revision_directives"] + if hook: + hook(migration_context, rev, self.generated_revisions) + + for migration_script in self.generated_revisions: + migration_script._needs_render = True + + def _default_revision(self) -> MigrationScript: + command_args: Dict[str, Any] = self.command_args + op = ops.MigrationScript( + rev_id=command_args["rev_id"] or util.rev_id(), + message=command_args["message"], + upgrade_ops=ops.UpgradeOps([]), + downgrade_ops=ops.DowngradeOps([]), + head=command_args["head"], + splice=command_args["splice"], + branch_label=command_args["branch_label"], + version_path=command_args["version_path"], + depends_on=command_args["depends_on"], + ) + return op + + def generate_scripts(self) -> Iterator[Optional[Script]]: + for generated_revision in self.generated_revisions: + yield self._to_script(generated_revision) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py new file mode 100644 index 0000000..a49640c --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__init__.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from . import comments +from . import constraints +from . import schema +from . import server_defaults +from . import tables +from . import types +from ... import util +from ...runtime.plugins import Plugin + +if TYPE_CHECKING: + from ..api import AutogenContext + from ...operations.ops import MigrationScript + from ...operations.ops import UpgradeOps + + +log = logging.getLogger(__name__) + +comparators = util.PriorityDispatcher() +"""global registry which alembic keeps empty, but copies when creating +a new AutogenContext. + +This is to support a variety of third party plugins that hook their autogen +functionality onto this collection. + +""" + + +def _populate_migration_script( + autogen_context: AutogenContext, migration_script: MigrationScript +) -> None: + upgrade_ops = migration_script.upgrade_ops_list[-1] + downgrade_ops = migration_script.downgrade_ops_list[-1] + + _produce_net_changes(autogen_context, upgrade_ops) + upgrade_ops.reverse_into(downgrade_ops) + + +def _produce_net_changes( + autogen_context: AutogenContext, upgrade_ops: UpgradeOps +) -> None: + assert autogen_context.dialect is not None + + autogen_context.comparators.dispatch( + "autogenerate", qualifier=autogen_context.dialect.name + )(autogen_context, upgrade_ops) + + +Plugin.setup_plugin_from_module(schema, "alembic.autogenerate.schemas") +Plugin.setup_plugin_from_module(tables, "alembic.autogenerate.tables") +Plugin.setup_plugin_from_module(types, "alembic.autogenerate.types") +Plugin.setup_plugin_from_module( + constraints, "alembic.autogenerate.constraints" +) +Plugin.setup_plugin_from_module( + server_defaults, "alembic.autogenerate.defaults" +) +Plugin.setup_plugin_from_module(comments, "alembic.autogenerate.comments") diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59aa062e76391a480dde9d4a903d472b856721a1 GIT binary patch literal 2457 zcma)7&2Jk;6rcU@`rC0F=R3u&>nJfTw%VXZ6;&ljKWIzcNWg)W&}!qIIP2~gv+EX{ zAXx|`j=i-94mpC#fg}F|7h6qbYb8rXg+p$Z0+(Lk&DyTrVx)}aH@|uB&Fq^uzj^ap zG#WzC4%U89r~L@Mpo3xK`+zw{EJBZvf)qwWIwLX+#jM7PEcBel>Ac8;j#qq|PZvbN z!vbKx==ZQ6a6k-rH~=^(20a`E91=qw4gn5}VZdP}0yrW@JRH@cdQ6Nl2=gSN#FQy* zN{@?i&lcCF^@N!4@HF5VafX3lF>-mhUm!vhzmh9{!^Be z#J9Y-_?m86nE~ArtQD(V9Ii0xaX?8vfy~k7MK#$gq|BbcE3C|&z{{`9pTH}qES$hg zP!>mCH8!_$L^qOS+^}pIrXxG5Y1l;=M?d*&`dug6y%RRgWCtyRma zaYPdf!`S;($1G!G%QPJPr9;=eqn3#m-Nz-OT2vMK)B=@)?^R6lM`LU$bBxpF8jxSUJZxoVJhl*-_&5*$P~%H`_ipq#}cAD909${(+X` z-FUShsbpHVq^Y*!CKU65;YB~vB_)fPO0|R~13OY_Pd3Wfb|VBsE^RD9noU@1O75pd zg6z}WxN&`$8OioiQ2F`IeVtDkw1T-V5<&($C69;WQe*T zYgpe=ONBwO7>*7XfIF5k}Gx+kC3Ur`r5_i(l^q<_-$& z%%xW5Qaf{{mATU6kS_->iBEVg%s%|;Zy)Q+k`ydiUl~v!KfaNCzed>g<%!mQk*F_C zHqyFLPfxUsRigT-i5|mR^grfTWliWOElzW^M8z}+S0LD_62s+-rh#+gJ~X=|LB$#s z^nuE)xFJc}t-=f}NrYB9*RPpnsBdLgfbs`Dq4&^5gz|#WQbGb$1gW4;HwjY_1>sJS zs^O?Q&RbrIv`LI=IoVPPjna*o7QJ4yZs(!CcmaD6xxq4aK7#LJ;szv%azc_^4kB?A z+e9^q>eQ|dD1!;f(l630=MltRuwl!V)c>foOH5tTp*m^!C*&Ql$EBBn%YEe)LZ3?d zNZ9aJqaMdFOc!Ndpja2B|3q_LwAe+-E;{AO8BebM&c(ZYw#%RI^5@`k`xhGJwlLok z<{t;13a1;~^VsV5N;|gNimg74tu?|uR$%yE!q1%R%%16SwBNk$_0N65V;_urNk5Yv z7_*%Vm#Hy3FlKv6#H1TS8>Lz(bs#j?uOFft9h4eZT{}eA$5p=O+SVbuI;Mik(wyG- ei(?rUVjeBn;b)q&@BUYR5NN)A^BKD3CGB4YlvzLk literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/comments.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..355ca782c3e81f38e3464869513544f231089c93 GIT binary patch literal 3796 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 z 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/schema.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08c9a6aeccecd0300986929aaee48c6c76e5a52d GIT binary patch literal 2395 zcmZuz&2JM&6rcU@dSfTHlVB&1C?ugIaUeELqzVCzS`rGOtrHbE)KX<_ypv?jelfd- znmUrz_5c+UQ7`0Bsa&h{59kqbLh2<}K#oR43ZfjiSq)Swr@mQxai|z;=l$NiH#2YE zy!Xa`c65XgwELsKXunDbJ!g#Hga+XD9s#(ARHSk`GPo?qG0f|HmIo^6f+1!_Z%hOn z$Ob$d04!xC4@-K`kh3!2pepNaMkpKN5SBW+%64&xN0={+_y)EpKCrnW8=L0lKBSs!~)SQqPeA6 z&9N0Xzw#A!iaMLxHrLR!2r!XPi>|ed%^Ayd@wZGIx?(RAMaA>B1Dv#=7ne0N&o|EC z^)mFZ3%557qxVo574`{zxXPFL3V#uO&w+gZ?508?;A^(1%BlQDQ+HKxgTAE9`#xM1 z%Y1?D|G!r7V&&bz)j(MwU%DOsXjv>oecYlOP55>PsVo&bTXG+%g0I1$_iK~`?`U-U zGh0+eEe%sp(F(T;yUn7z!92f$^87cw2(1ReU-8x)AC-fusG*H!~v0 ztxDdU_jsk@f1ce?!e6IF3kO@+N2&10|&ljY4?oMHT0q{0cS{=QG-+nM)QiyaZ^DQmG+j@q4YP=-QHoNis%5 zdCN2*>>)jnk3lYL%lD0Lo*CI6XuCX-ObPMGihuzFzv%UEtypUNjD)Tk*a8*296UVCJ z*tQ&7pRe^#Rr{xE{U@vaC+h+VB=-;~?n?2s@9v*}72pFCuVA&|T6nw~9^Vwl|NXlg zc5vFkQ4c@pojoKz{8)rO-+V7%7$FfpuhyOb;MLmwQll~9t;(DwZo znB-|6gmrHJ7=)%D(B8eVqHlXe-(gz~l2Pbs0J5cMlL^45;B#0#si18!R1-(4;>c6E ze_Kwx4hR8h4Dm?37d@unIA~JV}b=I1{k?Owvp$W-JpYdub?_TPlJc z&gBTJcQmM5%kT&-Q^_saKne2-VGjs_e+Gz#oNKzGv+7Z5WWHE*HNBCI{ViDvo3IK_ z5xm680+bBGo(&q??~asWYo6IOV%aR}*(01{+3KAj3I)N{44i7Z$TOd|K^gf1Y{Z1h zxkkOnQ30&>JnDJvH$+m*VCKy?@)-k?M#s}+8d&CLXA!!(z;WDb32{fBqsR{G+d+vP zH24Ayyg=j6gxHSw(F<{MM?AhGCLxA{y_Myf)Kisu*5xf}s3O#*0q)p#{77A3bn=Wx ScLRNU0pMSqMZ1@j1D?n=i literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/server_defaults.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..070e41e2a5293f3d32a8da7c3f81b819dadafb79 GIT binary patch literal 10561 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 b(8j(-7%8)jNSXad%4{Q0#}mUY6VLw#zjR~A literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/tables.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f5f6a58bb4a222498129647f07fc2b956f3449a GIT binary patch literal 11663 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/types.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17b8ff1fbf1289a09edf2ee3d0aada22488a4270 GIT binary patch literal 4397 zcmcIn%WoUU8J{JWyGt%#vMB4}7iC$JDcfW%2kHR0uH~0xT2+h6TW4L1b9eB_mvj0G7&OT{fbHD8muJu|kX^sz8r|URjSD zi9&*niRwvXtT4vpV)_ANyfDsiTu&Jjg$afe`axr|Fv*bsQ3KVV_?sA%>hyEi2d2VI zn2i1_r^?CS@`XdAdk3xMiSii89UiodMj&?pydtP2uWEKp zaY~!3RI{k&pq_(1_WM3i?YizjkNip9a@2CsR16iW!iuHW4OAMxQg^J1YOYwOqdr8@ z*cIJTsV8u^hQh0Qy`q_=5N@KE5W_;a-2=={n!&2yGgLkfHA78O?A-t%O@5@5&yIvZ2k$qeHx)Z=TAuu; znzN~yX=hVS8&+A>FQ-ci>MvPFO`+;qMs#^p?8elGn(b(2rO2vrRuq+u4b22YaV7f` zo!OErhC_n7RdIRE+^|4PLO~NKdTT3MH84Fi95+;Ag;cUk6MNmLp*jj~iq*ob z-CIGwgH0sDrKG*|_BXjr%TRO5dbzGEkF-a*tE&C6W7Tqb_0#k!wW?~#v2!)0VW>rW z)2eA3jiRm62Uqn@cLrH_@0YUdPPHP*FKPVvlI zrHZ5GdIywaYsuCc?l`QMotbwsw(8VtbQTsl1ON7~A=@I|BpI9jEcVCP)~(KoGh26G z^0S>(x}BPDrRKNpbi`y^Jk}DAeR-ih_g)Kr;(MO`+}qGRye6ZvqY8zISw zj+knTb1iZ1%ZKg73oZDG7y8mS_e<}{iCtI%DdDmC2{H4ysQ@x_lZ^Bwu_;!ee}5`- z;4ew1?`74#Zw(~x5hv`|z@mdq9=un0-t&5Ucy!HAJ+uqdtN)x z59}f0dVnk9!>t|$&mPh0ul=#qlxTjmOp`12*~K~FYpzcCXi$~||IOTZe*XL%`LUC4 zO8?;v4Q_Dc9b|&}>u=9o1->Z(+k320+EmO6kPWqr^gK;F7Uq`eGfYNy#|*6kegaVj z+=7Jx_du}@KKG87P#Mm`Py@aQ^p+m(pC{^DlRzsi$|I z-04K8+R-De=#d^1$U-=>7iTN!EwvL){^43HJl$Jge*?2^d8s8Yz2KMVaoGE4##m)B z+8M;*QQqBWjZrc39`ely&UlP`)At`(KrdC%R`w;vCcRnu9vR&y1Xb|JKJPh6zrQ-} zqd^G~K2#m0K^WQku15~`n;K~J5wO{yzwdK?FS1=Gkf$f0AtN(qV9r5LLW5_ryk^uO zJPt=0<{NYgMfyHL7qNN@vJtlJ&VbcJJS#ax)b_43F2k4#j{6g2TVyXqqQ`#Nn0KZY z`##Y@>pjtmpJ>O=w&G{MKKZxX-^6ci-S}3F|IeoSTHbGzeZzyLq7kt$_6z_EW5d1B zhG}s4Wv(3fVeRaGNSj}1@e8k{8OAe-S7Wp7vBlQdVmC}ei+dy#61q_ml6|q`Lt+c9u>~gfPZVPm zk%=3k;l&OL$fPTcs)_m z<<~7s&m`$-9DvbyXr41reH8P*a_%$jjP99s#`*nuC~6((r$5Ci@=1oL7ZD>TuvVAP zTc(;h=0=LejXEqsEfy)h99$7zv+&7MaRrD$P*OAziccAe21t=$+z32_6&+(wgWWF0 zpzrc^N7E@@oEPvW2U1ypSrs0bs_EDiiN~GT*V_wa_!M%J!={l{Y8pEgH*P^x^gaQz zR?TJ?;U;L^bTmWF_UwXf(hXNs=Qiv_rLI(D-)^u$Mjo@cJ{J4XtSB1ISA^X!3yW{1 z_aO$?b@gZT4s@ZF*{31z204z~Cda=c@ojQ&n;hCE>1{IghMasuPHdBBTMns-QM4m=Kdq3x*oaZh71_N55MgRZ+ literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/__pycache__/util.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec413834d6d77cfc844599367704679fe63cc989 GIT binary patch literal 10980 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 XKE6qy*!&?g$ujoaeP0qR=~Dg|iJC&R literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py new file mode 100644 index 0000000..70de74e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/comments.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import logging +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from ...operations import ops +from ...util import PriorityDispatchResult + +if TYPE_CHECKING: + + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Table + + from ..api import AutogenContext + from ...operations.ops import AlterColumnOp + from ...operations.ops import ModifyTableOps + from ...runtime.plugins import Plugin + +log = logging.getLogger(__name__) + + +def _compare_column_comment( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: quoted_name, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + assert autogen_context.dialect is not None + if not autogen_context.dialect.supports_comments: + return PriorityDispatchResult.CONTINUE + + metadata_comment = metadata_col.comment + conn_col_comment = conn_col.comment + if conn_col_comment is None and metadata_comment is None: + return PriorityDispatchResult.CONTINUE + + alter_column_op.existing_comment = conn_col_comment + + if conn_col_comment != metadata_comment: + alter_column_op.modify_comment = metadata_comment + log.info("Detected column comment '%s.%s'", tname, cname) + + return PriorityDispatchResult.STOP + else: + return PriorityDispatchResult.CONTINUE + + +def _compare_table_comment( + autogen_context: AutogenContext, + modify_table_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Optional[Table], + metadata_table: Optional[Table], +) -> PriorityDispatchResult: + assert autogen_context.dialect is not None + if not autogen_context.dialect.supports_comments: + return PriorityDispatchResult.CONTINUE + + # if we're doing CREATE TABLE, comments will be created inline + # with the create_table op. + if conn_table is None or metadata_table is None: + return PriorityDispatchResult.CONTINUE + + if conn_table.comment is None and metadata_table.comment is None: + return PriorityDispatchResult.CONTINUE + + if metadata_table.comment is None and conn_table.comment is not None: + modify_table_ops.ops.append( + ops.DropTableCommentOp( + tname, existing_comment=conn_table.comment, schema=schema + ) + ) + return PriorityDispatchResult.STOP + elif metadata_table.comment != conn_table.comment: + modify_table_ops.ops.append( + ops.CreateTableCommentOp( + tname, + metadata_table.comment, + existing_comment=conn_table.comment, + schema=schema, + ) + ) + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def setup(plugin: Plugin) -> None: + plugin.add_autogenerate_comparator( + _compare_column_comment, + "column", + "comments", + ) + plugin.add_autogenerate_comparator( + _compare_table_comment, + "table", + "comments", + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py new file mode 100644 index 0000000..ae1f20e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/constraints.py @@ -0,0 +1,812 @@ +# mypy: allow-untyped-defs, allow-untyped-calls, allow-incomplete-defs + +from __future__ import annotations + +import logging +from typing import Any +from typing import cast +from typing import Collection +from typing import Dict +from typing import Mapping +from typing import Optional +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy import schema as sa_schema +from sqlalchemy import text +from sqlalchemy.sql import expression +from sqlalchemy.sql.schema import ForeignKeyConstraint +from sqlalchemy.sql.schema import Index +from sqlalchemy.sql.schema import UniqueConstraint + +from .util import _InspectorConv +from ... import util +from ...ddl._autogen import is_index_sig +from ...ddl._autogen import is_uq_sig +from ...operations import ops +from ...util import PriorityDispatchResult +from ...util import sqla_compat + +if TYPE_CHECKING: + from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint + from sqlalchemy.engine.interfaces import ReflectedIndex + from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Table + + from ...autogenerate.api import AutogenContext + from ...ddl._autogen import _constraint_sig + from ...ddl.impl import DefaultImpl + from ...operations.ops import AlterColumnOp + from ...operations.ops import ModifyTableOps + from ...runtime.plugins import Plugin + +_C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index]) + + +log = logging.getLogger(__name__) + + +def _compare_indexes_and_uniques( + autogen_context: AutogenContext, + modify_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Optional[Table], + metadata_table: Optional[Table], +) -> PriorityDispatchResult: + inspector = autogen_context.inspector + is_create_table = conn_table is None + is_drop_table = metadata_table is None + impl = autogen_context.migration_context.impl + + # 1a. get raw indexes and unique constraints from metadata ... + if metadata_table is not None: + metadata_unique_constraints = { + uq + for uq in metadata_table.constraints + if isinstance(uq, sa_schema.UniqueConstraint) + } + metadata_indexes = set(metadata_table.indexes) + else: + metadata_unique_constraints = set() + metadata_indexes = set() + + conn_uniques: Collection[UniqueConstraint] = frozenset() + conn_indexes: Collection[Index] = frozenset() + + supports_unique_constraints = False + + unique_constraints_duplicate_unique_indexes = False + + if conn_table is not None: + conn_uniques_reflected: Collection[ReflectedUniqueConstraint] = ( + frozenset() + ) + conn_indexes_reflected: Collection[ReflectedIndex] = frozenset() + + # 1b. ... and from connection, if the table exists + try: + conn_uniques_reflected = _InspectorConv( + inspector + ).get_unique_constraints(tname, schema=schema) + + supports_unique_constraints = True + except NotImplementedError: + pass + except TypeError: + # number of arguments is off for the base + # method in SQLAlchemy due to the cache decorator + # not being present + pass + else: + conn_uniques_reflected = [ + uq + for uq in conn_uniques_reflected + if autogen_context.run_name_filters( + uq["name"], + "unique_constraint", + {"table_name": tname, "schema_name": schema}, + ) + ] + for uq in conn_uniques_reflected: + if uq.get("duplicates_index"): + unique_constraints_duplicate_unique_indexes = True + try: + conn_indexes_reflected = _InspectorConv(inspector).get_indexes( + tname, schema=schema + ) + except NotImplementedError: + pass + else: + conn_indexes_reflected = [ + ix + for ix in conn_indexes_reflected + if autogen_context.run_name_filters( + ix["name"], + "index", + {"table_name": tname, "schema_name": schema}, + ) + ] + + # 2. convert conn-level objects from raw inspector records + # into schema objects + if is_drop_table: + # for DROP TABLE uniques are inline, don't need them + conn_uniques = set() + else: + conn_uniques = { + _make_unique_constraint(impl, uq_def, conn_table) + for uq_def in conn_uniques_reflected + } + + conn_indexes = { + index + for index in ( + _make_index(impl, ix, conn_table) + for ix in conn_indexes_reflected + ) + if index is not None + } + + # 2a. if the dialect dupes unique indexes as unique constraints + # (mysql and oracle), correct for that + + if unique_constraints_duplicate_unique_indexes: + _correct_for_uq_duplicates_uix( + conn_uniques, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + autogen_context.dialect, + impl, + ) + + # 3. give the dialect a chance to omit indexes and constraints that + # we know are either added implicitly by the DB or that the DB + # can't accurately report on + impl.correct_for_autogen_constraints( + conn_uniques, # type: ignore[arg-type] + conn_indexes, # type: ignore[arg-type] + metadata_unique_constraints, + metadata_indexes, + ) + + # 4. organize the constraints into "signature" collections, the + # _constraint_sig() objects provide a consistent facade over both + # Index and UniqueConstraint so we can easily work with them + # interchangeably + metadata_unique_constraints_sig = { + impl._create_metadata_constraint_sig(uq) + for uq in metadata_unique_constraints + } + + metadata_indexes_sig = { + impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes + } + + conn_unique_constraints = { + impl._create_reflected_constraint_sig(uq) for uq in conn_uniques + } + + conn_indexes_sig = { + impl._create_reflected_constraint_sig(ix) for ix in conn_indexes + } + + # 5. index things by name, for those objects that have names + metadata_names = { + cast(str, c.md_name_to_sql_name(autogen_context)): c + for c in metadata_unique_constraints_sig.union(metadata_indexes_sig) + if c.is_named + } + + conn_uniques_by_name: Dict[ + sqla_compat._ConstraintName, + _constraint_sig[sa_schema.UniqueConstraint], + ] + conn_indexes_by_name: Dict[ + sqla_compat._ConstraintName, _constraint_sig[sa_schema.Index] + ] + + conn_uniques_by_name = {c.name: c for c in conn_unique_constraints} + conn_indexes_by_name = {c.name: c for c in conn_indexes_sig} + conn_names = { + c.name: c + for c in conn_unique_constraints.union(conn_indexes_sig) + if sqla_compat.constraint_name_string(c.name) + } + + doubled_constraints = { + name: (conn_uniques_by_name[name], conn_indexes_by_name[name]) + for name in set(conn_uniques_by_name).intersection( + conn_indexes_by_name + ) + } + + # 6. index things by "column signature", to help with unnamed unique + # constraints. + conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints} + metadata_uniques_by_sig = { + uq.unnamed: uq for uq in metadata_unique_constraints_sig + } + unnamed_metadata_uniques = { + uq.unnamed: uq + for uq in metadata_unique_constraints_sig + if not sqla_compat._constraint_is_named( + uq.const, autogen_context.dialect + ) + } + + # assumptions: + # 1. a unique constraint or an index from the connection *always* + # has a name. + # 2. an index on the metadata side *always* has a name. + # 3. a unique constraint on the metadata side *might* have a name. + # 4. The backend may double up indexes as unique constraints and + # vice versa (e.g. MySQL, Postgresql) + + def obj_added( + obj: ( + _constraint_sig[sa_schema.UniqueConstraint] + | _constraint_sig[sa_schema.Index] + ), + ): + if is_index_sig(obj): + if autogen_context.run_object_filters( + obj.const, obj.name, "index", False, None + ): + modify_ops.ops.append(ops.CreateIndexOp.from_index(obj.const)) + log.info( + "Detected added index %r on '%s'", + obj.name, + obj.column_names, + ) + elif is_uq_sig(obj): + if not supports_unique_constraints: + # can't report unique indexes as added if we don't + # detect them + return + if is_create_table or is_drop_table: + # unique constraints are created inline with table defs + return + if autogen_context.run_object_filters( + obj.const, obj.name, "unique_constraint", False, None + ): + modify_ops.ops.append( + ops.AddConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected added unique constraint %r on '%s'", + obj.name, + obj.column_names, + ) + else: + assert False + + def obj_removed( + obj: ( + _constraint_sig[sa_schema.UniqueConstraint] + | _constraint_sig[sa_schema.Index] + ), + ): + if is_index_sig(obj): + if obj.is_unique and not supports_unique_constraints: + # many databases double up unique constraints + # as unique indexes. without that list we can't + # be sure what we're doing here + return + + if autogen_context.run_object_filters( + obj.const, obj.name, "index", True, None + ): + modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const)) + log.info("Detected removed index %r on %r", obj.name, tname) + elif is_uq_sig(obj): + if is_create_table or is_drop_table: + # if the whole table is being dropped, we don't need to + # consider unique constraint separately + return + if autogen_context.run_object_filters( + obj.const, obj.name, "unique_constraint", True, None + ): + modify_ops.ops.append( + ops.DropConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected removed unique constraint %r on %r", + obj.name, + tname, + ) + else: + assert False + + def obj_changed( + old: ( + _constraint_sig[sa_schema.UniqueConstraint] + | _constraint_sig[sa_schema.Index] + ), + new: ( + _constraint_sig[sa_schema.UniqueConstraint] + | _constraint_sig[sa_schema.Index] + ), + msg: str, + ): + if is_index_sig(old): + assert is_index_sig(new) + + if autogen_context.run_object_filters( + new.const, new.name, "index", False, old.const + ): + log.info( + "Detected changed index %r on %r: %s", old.name, tname, msg + ) + modify_ops.ops.append(ops.DropIndexOp.from_index(old.const)) + modify_ops.ops.append(ops.CreateIndexOp.from_index(new.const)) + elif is_uq_sig(old): + assert is_uq_sig(new) + + if autogen_context.run_object_filters( + new.const, new.name, "unique_constraint", False, old.const + ): + log.info( + "Detected changed unique constraint %r on %r: %s", + old.name, + tname, + msg, + ) + modify_ops.ops.append( + ops.DropConstraintOp.from_constraint(old.const) + ) + modify_ops.ops.append( + ops.AddConstraintOp.from_constraint(new.const) + ) + else: + assert False + + for removed_name in sorted(set(conn_names).difference(metadata_names)): + conn_obj = conn_names[removed_name] + if ( + is_uq_sig(conn_obj) + and conn_obj.unnamed in unnamed_metadata_uniques + ): + continue + elif removed_name in doubled_constraints: + conn_uq, conn_idx = doubled_constraints[removed_name] + if ( + all( + conn_idx.unnamed != meta_idx.unnamed + for meta_idx in metadata_indexes_sig + ) + and conn_uq.unnamed not in metadata_uniques_by_sig + ): + obj_removed(conn_uq) + obj_removed(conn_idx) + else: + obj_removed(conn_obj) + + for existing_name in sorted(set(metadata_names).intersection(conn_names)): + metadata_obj = metadata_names[existing_name] + + if existing_name in doubled_constraints: + conn_uq, conn_idx = doubled_constraints[existing_name] + if is_index_sig(metadata_obj): + conn_obj = conn_idx + else: + conn_obj = conn_uq + else: + conn_obj = conn_names[existing_name] + + if type(conn_obj) != type(metadata_obj): + obj_removed(conn_obj) + obj_added(metadata_obj) + else: + # TODO: for plugins, let's do is_index_sig / is_uq_sig + # here so we know index or unique, then + # do a sub-dispatch, + # autogen_context.comparators.dispatch("index") + # or + # autogen_context.comparators.dispatch("unique_constraint") + # + comparison = metadata_obj.compare_to_reflected(conn_obj) + + if comparison.is_different: + # constraint are different + obj_changed(conn_obj, metadata_obj, comparison.message) + elif comparison.is_skip: + # constraint cannot be compared, skip them + thing = ( + "index" if is_index_sig(conn_obj) else "unique constraint" + ) + log.info( + "Cannot compare %s %r, assuming equal and skipping. %s", + thing, + conn_obj.name, + comparison.message, + ) + else: + # constraint are equal + assert comparison.is_equal + + for added_name in sorted(set(metadata_names).difference(conn_names)): + obj = metadata_names[added_name] + obj_added(obj) + + for uq_sig in unnamed_metadata_uniques: + if uq_sig not in conn_uniques_by_sig: + obj_added(unnamed_metadata_uniques[uq_sig]) + + return PriorityDispatchResult.CONTINUE + + +def _correct_for_uq_duplicates_uix( + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + dialect, + impl, +): + # dedupe unique indexes vs. constraints, since MySQL / Oracle + # doesn't really have unique constraints as a separate construct. + # but look in the metadata and try to maintain constructs + # that already seem to be defined one way or the other + # on that side. This logic was formerly local to MySQL dialect, + # generalized to Oracle and others. See #276 + + # resolve final rendered name for unique constraints defined in the + # metadata. this includes truncation of long names. naming convention + # names currently should already be set as cons.name, however leave this + # to the sqla_compat to decide. + metadata_cons_names = [ + (sqla_compat._get_constraint_final_name(cons, dialect), cons) + for cons in metadata_unique_constraints + ] + + metadata_uq_names = { + name for name, cons in metadata_cons_names if name is not None + } + + unnamed_metadata_uqs = { + impl._create_metadata_constraint_sig(cons).unnamed + for name, cons in metadata_cons_names + if name is None + } + + metadata_ix_names = { + sqla_compat._get_constraint_final_name(cons, dialect) + for cons in metadata_indexes + if cons.unique + } + + # for reflection side, names are in their final database form + # already since they're from the database + conn_ix_names = {cons.name: cons for cons in conn_indexes if cons.unique} + + uqs_dupe_indexes = { + cons.name: cons + for cons in conn_unique_constraints + if cons.info["duplicates_index"] + } + + for overlap in uqs_dupe_indexes: + if overlap not in metadata_uq_names: + if ( + impl._create_reflected_constraint_sig( + uqs_dupe_indexes[overlap] + ).unnamed + not in unnamed_metadata_uqs + ): + conn_unique_constraints.discard(uqs_dupe_indexes[overlap]) + elif overlap not in metadata_ix_names: + conn_indexes.discard(conn_ix_names[overlap]) + + +_IndexColumnSortingOps: Mapping[str, Any] = util.immutabledict( + { + "asc": expression.asc, + "desc": expression.desc, + "nulls_first": expression.nullsfirst, + "nulls_last": expression.nullslast, + "nullsfirst": expression.nullsfirst, # 1_3 name + "nullslast": expression.nullslast, # 1_3 name + } +) + + +def _make_index( + impl: DefaultImpl, params: ReflectedIndex, conn_table: Table +) -> Optional[Index]: + exprs: list[Union[Column[Any], TextClause]] = [] + sorting = params.get("column_sorting") + + for num, col_name in enumerate(params["column_names"]): + item: Union[Column[Any], TextClause] + if col_name is None: + assert "expressions" in params + name = params["expressions"][num] + item = text(name) + else: + name = col_name + item = conn_table.c[col_name] + if sorting and name in sorting: + for operator in sorting[name]: + if operator in _IndexColumnSortingOps: + item = _IndexColumnSortingOps[operator](item) + exprs.append(item) + ix = sa_schema.Index( + params["name"], + *exprs, + unique=params["unique"], + _table=conn_table, + **impl.adjust_reflected_dialect_options(params, "index"), + ) + if "duplicates_constraint" in params: + ix.info["duplicates_constraint"] = params["duplicates_constraint"] + return ix + + +def _make_unique_constraint( + impl: DefaultImpl, params: ReflectedUniqueConstraint, conn_table: Table +) -> UniqueConstraint: + uq = sa_schema.UniqueConstraint( + *[conn_table.c[cname] for cname in params["column_names"]], + name=params["name"], + **impl.adjust_reflected_dialect_options(params, "unique_constraint"), + ) + if "duplicates_index" in params: + uq.info["duplicates_index"] = params["duplicates_index"] + + return uq + + +def _make_foreign_key( + params: ReflectedForeignKeyConstraint, conn_table: Table +) -> ForeignKeyConstraint: + tname = params["referred_table"] + if params["referred_schema"]: + tname = "%s.%s" % (params["referred_schema"], tname) + + options = params.get("options", {}) + + const = sa_schema.ForeignKeyConstraint( + [conn_table.c[cname] for cname in params["constrained_columns"]], + ["%s.%s" % (tname, n) for n in params["referred_columns"]], + onupdate=options.get("onupdate"), + ondelete=options.get("ondelete"), + deferrable=options.get("deferrable"), + initially=options.get("initially"), + name=params["name"], + ) + + referred_schema = params["referred_schema"] + referred_table = params["referred_table"] + + remote_table_key = sqla_compat._get_table_key( + referred_table, referred_schema + ) + if remote_table_key not in conn_table.metadata: + # create a placeholder table + sa_schema.Table( + referred_table, + conn_table.metadata, + schema=( + referred_schema + if referred_schema is not None + else sa_schema.BLANK_SCHEMA + ), + *[ + sa_schema.Column(remote, conn_table.c[local].type) + for local, remote in zip( + params["constrained_columns"], params["referred_columns"] + ) + ], + info={"alembic_placeholder": True}, + ) + elif conn_table.metadata.tables[remote_table_key].info.get( + "alembic_placeholder" + ): + # table exists and is a placeholder; ensure needed columns are present + placeholder_table = conn_table.metadata.tables[remote_table_key] + for local, remote in zip( + params["constrained_columns"], params["referred_columns"] + ): + if remote not in placeholder_table.c: + placeholder_table.append_column( + sa_schema.Column(remote, conn_table.c[local].type) + ) + + # needed by 0.7 + conn_table.append_constraint(const) + return const + + +def _compare_foreign_keys( + autogen_context: AutogenContext, + modify_table_ops: ModifyTableOps, + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Table, + metadata_table: Table, +) -> PriorityDispatchResult: + # if we're doing CREATE TABLE, all FKs are created + # inline within the table def + + if conn_table is None or metadata_table is None: + return PriorityDispatchResult.CONTINUE + + inspector = autogen_context.inspector + metadata_fks = { + fk + for fk in metadata_table.constraints + if isinstance(fk, sa_schema.ForeignKeyConstraint) + } + + conn_fks_list = [ + fk + for fk in _InspectorConv(inspector).get_foreign_keys( + tname, schema=schema + ) + if autogen_context.run_name_filters( + fk["name"], + "foreign_key_constraint", + {"table_name": tname, "schema_name": schema}, + ) + ] + + conn_fks = { + _make_foreign_key(const, conn_table) for const in conn_fks_list + } + + impl = autogen_context.migration_context.impl + + # give the dialect a chance to correct the FKs to match more + # closely + autogen_context.migration_context.impl.correct_for_autogen_foreignkeys( + conn_fks, metadata_fks + ) + + metadata_fks_sig = { + impl._create_metadata_constraint_sig(fk) for fk in metadata_fks + } + + conn_fks_sig = { + impl._create_reflected_constraint_sig(fk) for fk in conn_fks + } + + # check if reflected FKs include options, indicating the backend + # can reflect FK options + if conn_fks_list and "options" in conn_fks_list[0]: + conn_fks_by_sig = {c.unnamed: c for c in conn_fks_sig} + metadata_fks_by_sig = {c.unnamed: c for c in metadata_fks_sig} + else: + # otherwise compare by sig without options added + conn_fks_by_sig = {c.unnamed_no_options: c for c in conn_fks_sig} + metadata_fks_by_sig = { + c.unnamed_no_options: c for c in metadata_fks_sig + } + + metadata_fks_by_name = { + c.name: c for c in metadata_fks_sig if c.name is not None + } + conn_fks_by_name = {c.name: c for c in conn_fks_sig if c.name is not None} + + def _add_fk(obj, compare_to): + if autogen_context.run_object_filters( + obj.const, obj.name, "foreign_key_constraint", False, compare_to + ): + modify_table_ops.ops.append( + ops.CreateForeignKeyOp.from_constraint(const.const) + ) + + log.info( + "Detected added foreign key (%s)(%s) on table %s%s", + ", ".join(obj.source_columns), + ", ".join(obj.target_columns), + "%s." % obj.source_schema if obj.source_schema else "", + obj.source_table, + ) + + def _remove_fk(obj, compare_to): + if autogen_context.run_object_filters( + obj.const, obj.name, "foreign_key_constraint", True, compare_to + ): + modify_table_ops.ops.append( + ops.DropConstraintOp.from_constraint(obj.const) + ) + log.info( + "Detected removed foreign key (%s)(%s) on table %s%s", + ", ".join(obj.source_columns), + ", ".join(obj.target_columns), + "%s." % obj.source_schema if obj.source_schema else "", + obj.source_table, + ) + + # so far it appears we don't need to do this by name at all. + # SQLite doesn't preserve constraint names anyway + + for removed_sig in set(conn_fks_by_sig).difference(metadata_fks_by_sig): + const = conn_fks_by_sig[removed_sig] + if removed_sig not in metadata_fks_by_sig: + compare_to = ( + metadata_fks_by_name[const.name].const + if const.name and const.name in metadata_fks_by_name + else None + ) + _remove_fk(const, compare_to) + + for added_sig in set(metadata_fks_by_sig).difference(conn_fks_by_sig): + const = metadata_fks_by_sig[added_sig] + if added_sig not in conn_fks_by_sig: + compare_to = ( + conn_fks_by_name[const.name].const + if const.name and const.name in conn_fks_by_name + else None + ) + _add_fk(const, compare_to) + + return PriorityDispatchResult.CONTINUE + + +def _compare_nullable( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + metadata_col_nullable = metadata_col.nullable + conn_col_nullable = conn_col.nullable + alter_column_op.existing_nullable = conn_col_nullable + + if conn_col_nullable is not metadata_col_nullable: + if ( + sqla_compat._server_default_is_computed( + metadata_col.server_default, conn_col.server_default + ) + and sqla_compat._nullability_might_be_unset(metadata_col) + or ( + sqla_compat._server_default_is_identity( + metadata_col.server_default, conn_col.server_default + ) + ) + ): + log.info( + "Ignoring nullable change on identity column '%s.%s'", + tname, + cname, + ) + else: + alter_column_op.modify_nullable = metadata_col_nullable + log.info( + "Detected %s on column '%s.%s'", + "NULL" if metadata_col_nullable else "NOT NULL", + tname, + cname, + ) + # column nullablity changed, no further nullable checks needed + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def setup(plugin: Plugin) -> None: + plugin.add_autogenerate_comparator( + _compare_indexes_and_uniques, + "table", + "indexes", + ) + plugin.add_autogenerate_comparator( + _compare_foreign_keys, + "table", + "foreignkeys", + ) + plugin.add_autogenerate_comparator( + _compare_nullable, + "column", + "nullable", + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py new file mode 100644 index 0000000..1f46aff --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/schema.py @@ -0,0 +1,62 @@ +# mypy: allow-untyped-calls + +from __future__ import annotations + +import logging +from typing import Optional +from typing import Set +from typing import TYPE_CHECKING + +from sqlalchemy import inspect + +from ...util import PriorityDispatchResult + +if TYPE_CHECKING: + from sqlalchemy.engine.reflection import Inspector + + from ...autogenerate.api import AutogenContext + from ...operations.ops import UpgradeOps + from ...runtime.plugins import Plugin + + +log = logging.getLogger(__name__) + + +def _produce_net_changes( + autogen_context: AutogenContext, upgrade_ops: UpgradeOps +) -> PriorityDispatchResult: + connection = autogen_context.connection + assert connection is not None + include_schemas = autogen_context.opts.get("include_schemas", False) + + inspector: Inspector = inspect(connection) + + default_schema = connection.dialect.default_schema_name + schemas: Set[Optional[str]] + if include_schemas: + schemas = set(inspector.get_schema_names()) + # replace default schema name with None + schemas.discard("information_schema") + # replace the "default" schema with None + schemas.discard(default_schema) + schemas.add(None) + else: + schemas = {None} + + schemas = { + s for s in schemas if autogen_context.run_name_filters(s, "schema", {}) + } + + assert autogen_context.dialect is not None + autogen_context.comparators.dispatch( + "schema", qualifier=autogen_context.dialect.name + )(autogen_context, upgrade_ops, schemas) + + return PriorityDispatchResult.CONTINUE + + +def setup(plugin: Plugin) -> None: + plugin.add_autogenerate_comparator( + _produce_net_changes, + "autogenerate", + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py new file mode 100644 index 0000000..1e09e8e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/server_defaults.py @@ -0,0 +1,344 @@ +from __future__ import annotations + +import logging +import re +from types import NoneType +from typing import Any +from typing import cast +from typing import Optional +from typing import Sequence +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import schema as sa_schema +from sqlalchemy.sql.schema import DefaultClause + +from ... import util +from ...util import DispatchPriority +from ...util import PriorityDispatchResult +from ...util import sqla_compat + +if TYPE_CHECKING: + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Column + + from ...autogenerate.api import AutogenContext + from ...operations.ops import AlterColumnOp + from ...runtime.plugins import Plugin + +log = logging.getLogger(__name__) + + +def _render_server_default_for_compare( + metadata_default: Optional[Any], autogen_context: AutogenContext +) -> Optional[str]: + if isinstance(metadata_default, sa_schema.DefaultClause): + if isinstance(metadata_default.arg, str): + metadata_default = metadata_default.arg + else: + metadata_default = str( + metadata_default.arg.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + if isinstance(metadata_default, str): + return metadata_default + else: + return None + + +def _normalize_computed_default(sqltext: str) -> str: + """we want to warn if a computed sql expression has changed. however + we don't want false positives and the warning is not that critical. + so filter out most forms of variability from the SQL text. + + """ + + return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower() + + +def _compare_computed_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: str, + cname: str, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + + metadata_default = metadata_col.server_default + conn_col_default = conn_col.server_default + if conn_col_default is None and metadata_default is None: + return PriorityDispatchResult.CONTINUE + + if sqla_compat._server_default_is_computed( + conn_col_default + ) and not sqla_compat._server_default_is_computed(metadata_default): + _warn_computed_not_supported(tname, cname) + return PriorityDispatchResult.STOP + + if not sqla_compat._server_default_is_computed(metadata_default): + return PriorityDispatchResult.CONTINUE + + rendered_metadata_default = str( + cast(sa_schema.Computed, metadata_col.server_default).sqltext.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + + # since we cannot change computed columns, we do only a crude comparison + # here where we try to eliminate syntactical differences in order to + # get a minimal comparison just to emit a warning. + + rendered_metadata_default = _normalize_computed_default( + rendered_metadata_default + ) + + if isinstance(conn_col.server_default, sa_schema.Computed): + rendered_conn_default = str( + conn_col.server_default.sqltext.compile( + dialect=autogen_context.dialect, + compile_kwargs={"literal_binds": True}, + ) + ) + rendered_conn_default = _normalize_computed_default( + rendered_conn_default + ) + else: + rendered_conn_default = "" + + if rendered_metadata_default != rendered_conn_default: + _warn_computed_not_supported(tname, cname) + + return PriorityDispatchResult.STOP + + +def _warn_computed_not_supported(tname: str, cname: str) -> None: + util.warn("Computed default on %s.%s cannot be modified" % (tname, cname)) + + +def _compare_identity_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], + skip: Sequence[str] = ( + "order", + "on_null", + "oracle_order", + "oracle_on_null", + ), +) -> PriorityDispatchResult: + + metadata_default = metadata_col.server_default + conn_col_default = conn_col.server_default + if ( + conn_col_default is None + and metadata_default is None + or not sqla_compat._server_default_is_identity( + metadata_default, conn_col_default + ) + ): + return PriorityDispatchResult.CONTINUE + + assert isinstance( + metadata_col.server_default, + (sa_schema.Identity, sa_schema.Sequence, NoneType), + ) + assert isinstance( + conn_col.server_default, + (sa_schema.Identity, sa_schema.Sequence, NoneType), + ) + + impl = autogen_context.migration_context.impl + diff, _, is_alter = impl._compare_identity_default( # type: ignore[no-untyped-call] # noqa: E501 + metadata_col.server_default, conn_col.server_default + ) + + if is_alter: + alter_column_op.modify_server_default = metadata_default + if diff: + log.info( + "Detected server default on column '%s.%s': " + "identity options attributes %s", + tname, + cname, + sorted(diff), + ) + + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def _user_compare_server_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + + metadata_default = metadata_col.server_default + conn_col_default = conn_col.server_default + if conn_col_default is None and metadata_default is None: + return PriorityDispatchResult.CONTINUE + + alter_column_op.existing_server_default = conn_col_default + + migration_context = autogen_context.migration_context + + if migration_context._user_compare_server_default is False: + return PriorityDispatchResult.STOP + + if not callable(migration_context._user_compare_server_default): + return PriorityDispatchResult.CONTINUE + + rendered_metadata_default = _render_server_default_for_compare( + metadata_default, autogen_context + ) + rendered_conn_default = ( + cast(Any, conn_col_default).arg.text if conn_col_default else None + ) + + is_diff = migration_context._user_compare_server_default( + migration_context, + conn_col, + metadata_col, + rendered_conn_default, + metadata_col.server_default, + rendered_metadata_default, + ) + if is_diff: + alter_column_op.modify_server_default = metadata_default + log.info( + "User defined function %s detected " + "server default on column '%s.%s'", + migration_context._user_compare_server_default, + tname, + cname, + ) + return PriorityDispatchResult.STOP + elif is_diff is False: + # if user compare server_default returns False and not None, + # it means "dont do any more server_default comparison" + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def _dialect_impl_compare_server_default( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + """use dialect.impl.compare_server_default. + + This would in theory not be needed. however we dont know if any + third party libraries haven't made their own alembic dialect and + implemented this method. + + """ + metadata_default = metadata_col.server_default + conn_col_default = conn_col.server_default + if conn_col_default is None and metadata_default is None: + return PriorityDispatchResult.CONTINUE + + # this is already done by _user_compare_server_default, + # but doing it here also for unit tests that want to call + # _dialect_impl_compare_server_default directly + alter_column_op.existing_server_default = conn_col_default + + if not isinstance( + metadata_default, (DefaultClause, NoneType) + ) or not isinstance(conn_col_default, (DefaultClause, NoneType)): + return PriorityDispatchResult.CONTINUE + + migration_context = autogen_context.migration_context + + rendered_metadata_default = _render_server_default_for_compare( + metadata_default, autogen_context + ) + rendered_conn_default = ( + cast(Any, conn_col_default).arg.text if conn_col_default else None + ) + + is_diff = migration_context.impl.compare_server_default( # type: ignore[no-untyped-call] # noqa: E501 + conn_col, + metadata_col, + rendered_metadata_default, + rendered_conn_default, + ) + if is_diff: + alter_column_op.modify_server_default = metadata_default + log.info( + "Dialect impl %s detected server default on column '%s.%s'", + migration_context.impl, + tname, + cname, + ) + return PriorityDispatchResult.STOP + return PriorityDispatchResult.CONTINUE + + +def _setup_autoincrement( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: quoted_name, + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + if metadata_col.table._autoincrement_column is metadata_col: + alter_column_op.kw["autoincrement"] = True + elif metadata_col.autoincrement is True: + alter_column_op.kw["autoincrement"] = True + elif metadata_col.autoincrement is False: + alter_column_op.kw["autoincrement"] = False + + return PriorityDispatchResult.CONTINUE + + +def setup(plugin: Plugin) -> None: + plugin.add_autogenerate_comparator( + _user_compare_server_default, + "column", + "server_default", + priority=DispatchPriority.FIRST, + ) + plugin.add_autogenerate_comparator( + _compare_computed_default, + "column", + "server_default", + ) + + plugin.add_autogenerate_comparator( + _compare_identity_default, + "column", + "server_default", + ) + + plugin.add_autogenerate_comparator( + _setup_autoincrement, + "column", + "server_default", + ) + plugin.add_autogenerate_comparator( + _dialect_impl_compare_server_default, + "column", + "server_default", + priority=DispatchPriority.LAST, + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py new file mode 100644 index 0000000..31eddc6 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/tables.py @@ -0,0 +1,316 @@ +# mypy: allow-untyped-calls + +from __future__ import annotations + +import contextlib +import logging +from typing import Iterator +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import event +from sqlalchemy import schema as sa_schema +from sqlalchemy.util import OrderedSet + +from .util import _InspectorConv +from ...operations import ops +from ...util import PriorityDispatchResult + +if TYPE_CHECKING: + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Table + + from ...autogenerate.api import AutogenContext + from ...operations.ops import ModifyTableOps + from ...operations.ops import UpgradeOps + from ...runtime.plugins import Plugin + + +log = logging.getLogger(__name__) + + +def _autogen_for_tables( + autogen_context: AutogenContext, + upgrade_ops: UpgradeOps, + schemas: Set[Optional[str]], +) -> PriorityDispatchResult: + inspector = autogen_context.inspector + + conn_table_names: Set[Tuple[Optional[str], str]] = set() + + version_table_schema = ( + autogen_context.migration_context.version_table_schema + ) + version_table = autogen_context.migration_context.version_table + + for schema_name in schemas: + tables = available = set(inspector.get_table_names(schema=schema_name)) + if schema_name == version_table_schema: + tables = tables.difference( + [autogen_context.migration_context.version_table] + ) + + tablenames = [ + tname + for tname in tables + if autogen_context.run_name_filters( + tname, "table", {"schema_name": schema_name} + ) + ] + + conn_table_names.update((schema_name, tname) for tname in tablenames) + + inspector = autogen_context.inspector + insp = _InspectorConv(inspector) + insp.pre_cache_tables(schema_name, tablenames, available) + + metadata_table_names = OrderedSet( + [(table.schema, table.name) for table in autogen_context.sorted_tables] + ).difference([(version_table_schema, version_table)]) + + _compare_tables( + conn_table_names, + metadata_table_names, + inspector, + upgrade_ops, + autogen_context, + ) + + return PriorityDispatchResult.CONTINUE + + +def _compare_tables( + conn_table_names: set[tuple[str | None, str]], + metadata_table_names: set[tuple[str | None, str]], + inspector: Inspector, + upgrade_ops: UpgradeOps, + autogen_context: AutogenContext, +) -> None: + default_schema = inspector.bind.dialect.default_schema_name + + # tables coming from the connection will not have "schema" + # set if it matches default_schema_name; so we need a list + # of table names from local metadata that also have "None" if schema + # == default_schema_name. Most setups will be like this anyway but + # some are not (see #170) + metadata_table_names_no_dflt_schema = OrderedSet( + [ + (schema if schema != default_schema else None, tname) + for schema, tname in metadata_table_names + ] + ) + + # to adjust for the MetaData collection storing the tables either + # as "schemaname.tablename" or just "tablename", create a new lookup + # which will match the "non-default-schema" keys to the Table object. + tname_to_table = { + no_dflt_schema: autogen_context.table_key_to_table[ + sa_schema._get_table_key(tname, schema) + ] + for no_dflt_schema, (schema, tname) in zip( + metadata_table_names_no_dflt_schema, metadata_table_names + ) + } + metadata_table_names = metadata_table_names_no_dflt_schema + + for s, tname in metadata_table_names.difference(conn_table_names): + name = "%s.%s" % (s, tname) if s else tname + metadata_table = tname_to_table[(s, tname)] + if autogen_context.run_object_filters( + metadata_table, tname, "table", False, None + ): + upgrade_ops.ops.append( + ops.CreateTableOp.from_table(metadata_table) + ) + log.info("Detected added table %r", name) + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + + autogen_context.comparators.dispatch( + "table", qualifier=autogen_context.dialect.name + )( + autogen_context, + modify_table_ops, + s, + tname, + None, + metadata_table, + ) + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + removal_metadata = sa_schema.MetaData() + for s, tname in conn_table_names.difference(metadata_table_names): + name = sa_schema._get_table_key(tname, s) + + # a name might be present already if a previous reflection pulled + # this table in via foreign key constraint + exists = name in removal_metadata.tables + t = sa_schema.Table(tname, removal_metadata, schema=s) + + if not exists: + event.listen( + t, + "column_reflect", + # fmt: off + autogen_context.migration_context.impl. + _compat_autogen_column_reflect + (inspector), + # fmt: on + ) + _InspectorConv(inspector).reflect_table(t) + if autogen_context.run_object_filters(t, tname, "table", True, None): + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + + autogen_context.comparators.dispatch( + "table", qualifier=autogen_context.dialect.name + )(autogen_context, modify_table_ops, s, tname, t, None) + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + upgrade_ops.ops.append(ops.DropTableOp.from_table(t)) + log.info("Detected removed table %r", name) + + existing_tables = conn_table_names.intersection(metadata_table_names) + + existing_metadata = sa_schema.MetaData() + conn_column_info = {} + for s, tname in existing_tables: + name = sa_schema._get_table_key(tname, s) + exists = name in existing_metadata.tables + + # a name might be present already if a previous reflection pulled + # this table in via foreign key constraint + t = sa_schema.Table(tname, existing_metadata, schema=s) + if not exists: + event.listen( + t, + "column_reflect", + # fmt: off + autogen_context.migration_context.impl. + _compat_autogen_column_reflect(inspector), + # fmt: on + ) + _InspectorConv(inspector).reflect_table(t) + + conn_column_info[(s, tname)] = t + + for s, tname in sorted(existing_tables, key=lambda x: (x[0] or "", x[1])): + s = s or None + name = "%s.%s" % (s, tname) if s else tname + metadata_table = tname_to_table[(s, tname)] + conn_table = existing_metadata.tables[name] + + if autogen_context.run_object_filters( + metadata_table, tname, "table", False, conn_table + ): + modify_table_ops = ops.ModifyTableOps(tname, [], schema=s) + with _compare_columns( + s, + tname, + conn_table, + metadata_table, + modify_table_ops, + autogen_context, + inspector, + ): + autogen_context.comparators.dispatch( + "table", qualifier=autogen_context.dialect.name + )( + autogen_context, + modify_table_ops, + s, + tname, + conn_table, + metadata_table, + ) + + if not modify_table_ops.is_empty(): + upgrade_ops.ops.append(modify_table_ops) + + +@contextlib.contextmanager +def _compare_columns( + schema: Optional[str], + tname: Union[quoted_name, str], + conn_table: Table, + metadata_table: Table, + modify_table_ops: ModifyTableOps, + autogen_context: AutogenContext, + inspector: Inspector, +) -> Iterator[None]: + name = "%s.%s" % (schema, tname) if schema else tname + metadata_col_names = OrderedSet( + c.name for c in metadata_table.c if not c.system + ) + metadata_cols_by_name = { + c.name: c for c in metadata_table.c if not c.system + } + + conn_col_names = { + c.name: c + for c in conn_table.c + if autogen_context.run_name_filters( + c.name, "column", {"table_name": tname, "schema_name": schema} + ) + } + + for cname in metadata_col_names.difference(conn_col_names): + if autogen_context.run_object_filters( + metadata_cols_by_name[cname], cname, "column", False, None + ): + modify_table_ops.ops.append( + ops.AddColumnOp.from_column_and_tablename( + schema, tname, metadata_cols_by_name[cname] + ) + ) + log.info("Detected added column '%s.%s'", name, cname) + + for colname in metadata_col_names.intersection(conn_col_names): + metadata_col = metadata_cols_by_name[colname] + conn_col = conn_table.c[colname] + if not autogen_context.run_object_filters( + metadata_col, colname, "column", False, conn_col + ): + continue + alter_column_op = ops.AlterColumnOp(tname, colname, schema=schema) + + autogen_context.comparators.dispatch( + "column", qualifier=autogen_context.dialect.name + )( + autogen_context, + alter_column_op, + schema, + tname, + colname, + conn_col, + metadata_col, + ) + + if alter_column_op.has_changes(): + modify_table_ops.ops.append(alter_column_op) + + yield + + for cname in set(conn_col_names).difference(metadata_col_names): + if autogen_context.run_object_filters( + conn_table.c[cname], cname, "column", True, None + ): + modify_table_ops.ops.append( + ops.DropColumnOp.from_column_and_tablename( + schema, tname, conn_table.c[cname] + ) + ) + log.info("Detected removed column '%s.%s'", name, cname) + + +def setup(plugin: Plugin) -> None: + + plugin.add_autogenerate_comparator( + _autogen_for_tables, + "schema", + "tables", + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py new file mode 100644 index 0000000..1d5d160 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/types.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import logging +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import types as sqltypes + +from ...util import DispatchPriority +from ...util import PriorityDispatchResult + +if TYPE_CHECKING: + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Column + + from ...autogenerate.api import AutogenContext + from ...operations.ops import AlterColumnOp + from ...runtime.plugins import Plugin + + +log = logging.getLogger(__name__) + + +def _compare_type_setup( + alter_column_op: AlterColumnOp, + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> bool: + + conn_type = conn_col.type + alter_column_op.existing_type = conn_type + metadata_type = metadata_col.type + if conn_type._type_affinity is sqltypes.NullType: + log.info( + "Couldn't determine database type for column '%s.%s'", + tname, + cname, + ) + return False + if metadata_type._type_affinity is sqltypes.NullType: + log.info( + "Column '%s.%s' has no type within the model; can't compare", + tname, + cname, + ) + return False + + return True + + +def _user_compare_type( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + + migration_context = autogen_context.migration_context + + if migration_context._user_compare_type is False: + return PriorityDispatchResult.STOP + + if not _compare_type_setup( + alter_column_op, tname, cname, conn_col, metadata_col + ): + return PriorityDispatchResult.CONTINUE + + if not callable(migration_context._user_compare_type): + return PriorityDispatchResult.CONTINUE + + is_diff = migration_context._user_compare_type( + migration_context, + conn_col, + metadata_col, + conn_col.type, + metadata_col.type, + ) + if is_diff: + alter_column_op.modify_type = metadata_col.type + log.info( + "Detected type change from %r to %r on '%s.%s'", + conn_col.type, + metadata_col.type, + tname, + cname, + ) + return PriorityDispatchResult.STOP + elif is_diff is False: + # if user compare type returns False and not None, + # it means "dont do any more type comparison" + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def _dialect_impl_compare_type( + autogen_context: AutogenContext, + alter_column_op: AlterColumnOp, + schema: Optional[str], + tname: Union[quoted_name, str], + cname: Union[quoted_name, str], + conn_col: Column[Any], + metadata_col: Column[Any], +) -> PriorityDispatchResult: + + if not _compare_type_setup( + alter_column_op, tname, cname, conn_col, metadata_col + ): + return PriorityDispatchResult.CONTINUE + + migration_context = autogen_context.migration_context + is_diff = migration_context.impl.compare_type(conn_col, metadata_col) + + if is_diff: + alter_column_op.modify_type = metadata_col.type + log.info( + "Detected type change from %r to %r on '%s.%s'", + conn_col.type, + metadata_col.type, + tname, + cname, + ) + return PriorityDispatchResult.STOP + + return PriorityDispatchResult.CONTINUE + + +def setup(plugin: Plugin) -> None: + plugin.add_autogenerate_comparator( + _user_compare_type, + "column", + "types", + priority=DispatchPriority.FIRST, + ) + plugin.add_autogenerate_comparator( + _dialect_impl_compare_type, + "column", + "types", + priority=DispatchPriority.LAST, + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py new file mode 100644 index 0000000..41829c0 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/compare/util.py @@ -0,0 +1,314 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics +from __future__ import annotations + +from typing import Any +from typing import cast +from typing import Collection +from typing import TYPE_CHECKING + +from sqlalchemy.sql.elements import conv +from typing_extensions import Self + +from ...util import sqla_compat + +if TYPE_CHECKING: + from sqlalchemy import Table + from sqlalchemy.engine import Inspector + from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint + from sqlalchemy.engine.interfaces import ReflectedIndex + from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint + from sqlalchemy.engine.reflection import _ReflectionInfo + +_INSP_KEYS = ( + "columns", + "pk_constraint", + "foreign_keys", + "indexes", + "unique_constraints", + "table_comment", + "check_constraints", + "table_options", +) +_CONSTRAINT_INSP_KEYS = ( + "pk_constraint", + "foreign_keys", + "indexes", + "unique_constraints", + "check_constraints", +) + + +class _InspectorConv: + __slots__ = ("inspector",) + + def __new__(cls, inspector: Inspector) -> Self: + obj: Any + if sqla_compat.sqla_2: + obj = object.__new__(_SQLA2InspectorConv) + _SQLA2InspectorConv.__init__(obj, inspector) + else: + obj = object.__new__(_LegacyInspectorConv) + _LegacyInspectorConv.__init__(obj, inspector) + return cast(Self, obj) + + def __init__(self, inspector: Inspector): + self.inspector = inspector + + def pre_cache_tables( + self, + schema: str | None, + tablenames: list[str], + all_available_tablenames: Collection[str], + ) -> None: + pass + + def get_unique_constraints( + self, tname: str, schema: str | None + ) -> list[ReflectedUniqueConstraint]: + raise NotImplementedError() + + def get_indexes( + self, tname: str, schema: str | None + ) -> list[ReflectedIndex]: + raise NotImplementedError() + + def get_foreign_keys( + self, tname: str, schema: str | None + ) -> list[ReflectedForeignKeyConstraint]: + raise NotImplementedError() + + def reflect_table(self, table: Table) -> None: + raise NotImplementedError() + + +class _LegacyInspectorConv(_InspectorConv): + + def _apply_reflectinfo_conv(self, consts): + if not consts: + return consts + for const in consts: + if const["name"] is not None and not isinstance( + const["name"], conv + ): + const["name"] = conv(const["name"]) + return consts + + def _apply_constraint_conv(self, consts): + if not consts: + return consts + for const in consts: + if const.name is not None and not isinstance(const.name, conv): + const.name = conv(const.name) + return consts + + def get_indexes( + self, tname: str, schema: str | None + ) -> list[ReflectedIndex]: + return self._apply_reflectinfo_conv( + self.inspector.get_indexes(tname, schema=schema) + ) + + def get_unique_constraints( + self, tname: str, schema: str | None + ) -> list[ReflectedUniqueConstraint]: + return self._apply_reflectinfo_conv( + self.inspector.get_unique_constraints(tname, schema=schema) + ) + + def get_foreign_keys( + self, tname: str, schema: str | None + ) -> list[ReflectedForeignKeyConstraint]: + return self._apply_reflectinfo_conv( + self.inspector.get_foreign_keys(tname, schema=schema) + ) + + def reflect_table(self, table: Table) -> None: + self.inspector.reflect_table(table, include_columns=None) + + self._apply_constraint_conv(table.constraints) + self._apply_constraint_conv(table.indexes) + + +class _SQLA2InspectorConv(_InspectorConv): + + def _pre_cache( + self, + schema: str | None, + tablenames: list[str], + all_available_tablenames: Collection[str], + info_key: str, + inspector_method: Any, + ) -> None: + + if info_key in self.inspector.info_cache: + return + + # heuristic vendored from SQLAlchemy 2.0 + # if more than 50% of the tables in the db are in filter_names load all + # the tables, since it's most likely faster to avoid a filter on that + # many tables. also if a dialect doesnt have a "multi" method then + # return the filter names + if tablenames and all_available_tablenames and len(tablenames) > 100: + fraction = len(tablenames) / len(all_available_tablenames) + else: + fraction = None + + if ( + fraction is None + or fraction <= 0.5 + or not self.inspector.dialect._overrides_default( + inspector_method.__name__ + ) + ): + optimized_filter_names = tablenames + else: + optimized_filter_names = None + + try: + elements = inspector_method( + schema=schema, filter_names=optimized_filter_names + ) + except NotImplementedError: + self.inspector.info_cache[info_key] = NotImplementedError + else: + self.inspector.info_cache[info_key] = elements + + def _return_from_cache( + self, + tname: str, + schema: str | None, + info_key: str, + inspector_method: Any, + apply_constraint_conv: bool = False, + optional=True, + ) -> Any: + not_in_cache = object() + + if info_key in self.inspector.info_cache: + cache = self.inspector.info_cache[info_key] + if cache is NotImplementedError: + if optional: + return {} + else: + # maintain NotImplementedError as alembic compare + # uses these to determine classes of construct that it + # should not compare to DB elements + raise NotImplementedError() + + individual = cache.get((schema, tname), not_in_cache) + + if individual is not not_in_cache: + if apply_constraint_conv and individual is not None: + return self._apply_reflectinfo_conv(individual) + else: + return individual + + try: + data = inspector_method(tname, schema=schema) + except NotImplementedError: + if optional: + return {} + else: + raise + + if apply_constraint_conv: + return self._apply_reflectinfo_conv(data) + else: + return data + + def get_unique_constraints( + self, tname: str, schema: str | None + ) -> list[ReflectedUniqueConstraint]: + return self._return_from_cache( + tname, + schema, + "alembic_unique_constraints", + self.inspector.get_unique_constraints, + apply_constraint_conv=True, + optional=False, + ) + + def get_indexes( + self, tname: str, schema: str | None + ) -> list[ReflectedIndex]: + return self._return_from_cache( + tname, + schema, + "alembic_indexes", + self.inspector.get_indexes, + apply_constraint_conv=True, + optional=False, + ) + + def get_foreign_keys( + self, tname: str, schema: str | None + ) -> list[ReflectedForeignKeyConstraint]: + return self._return_from_cache( + tname, + schema, + "alembic_foreign_keys", + self.inspector.get_foreign_keys, + apply_constraint_conv=True, + ) + + def _apply_reflectinfo_conv(self, consts): + if not consts: + return consts + for const in consts if not isinstance(consts, dict) else [consts]: + if const["name"] is not None and not isinstance( + const["name"], conv + ): + const["name"] = conv(const["name"]) + return consts + + def pre_cache_tables( + self, + schema: str | None, + tablenames: list[str], + all_available_tablenames: Collection[str], + ) -> None: + for key in _INSP_KEYS: + keyname = f"alembic_{key}" + meth = getattr(self.inspector, f"get_multi_{key}") + + self._pre_cache( + schema, + tablenames, + all_available_tablenames, + keyname, + meth, + ) + + def _make_reflection_info( + self, tname: str, schema: str | None + ) -> _ReflectionInfo: + from sqlalchemy.engine.reflection import _ReflectionInfo + + table_key = (schema, tname) + + return _ReflectionInfo( + unreflectable={}, + **{ + key: { + table_key: self._return_from_cache( + tname, + schema, + f"alembic_{key}", + getattr(self.inspector, f"get_{key}"), + apply_constraint_conv=(key in _CONSTRAINT_INSP_KEYS), + ) + } + for key in _INSP_KEYS + }, + ) + + def reflect_table(self, table: Table) -> None: + ri = self._make_reflection_info(table.name, table.schema) + + self.inspector.reflect_table( + table, + include_columns=None, + resolve_fks=False, + _reflect_info=ri, + ) diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/render.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/render.py new file mode 100644 index 0000000..7f32838 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/render.py @@ -0,0 +1,1172 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from io import StringIO +import re +from typing import Any +from typing import cast +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from mako.pygen import PythonPrinter +from sqlalchemy import schema as sa_schema +from sqlalchemy import sql +from sqlalchemy import types as sqltypes +from sqlalchemy.sql.base import _DialectArgView +from sqlalchemy.sql.elements import conv +from sqlalchemy.sql.elements import Label +from sqlalchemy.sql.elements import quoted_name + +from .. import util +from ..operations import ops +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy import Computed + from sqlalchemy import Identity + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import FetchedValue + from sqlalchemy.sql.schema import ForeignKey + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.sqltypes import ARRAY + from sqlalchemy.sql.type_api import TypeEngine + + from alembic.autogenerate.api import AutogenContext + from alembic.config import Config + from alembic.operations.ops import MigrationScript + from alembic.operations.ops import ModifyTableOps + + +MAX_PYTHON_ARGS = 255 + + +def _render_gen_name( + autogen_context: AutogenContext, + name: sqla_compat._ConstraintName, +) -> Optional[Union[quoted_name, str, _f_name]]: + if isinstance(name, conv): + return _f_name(_alembic_autogenerate_prefix(autogen_context), name) + else: + return sqla_compat.constraint_name_or_none(name) + + +def _indent(text: str) -> str: + text = re.compile(r"^", re.M).sub(" ", text).strip() + text = re.compile(r" +$", re.M).sub("", text) + return text + + +def _render_python_into_templatevars( + autogen_context: AutogenContext, + migration_script: MigrationScript, + template_args: Dict[str, Union[str, Config]], +) -> None: + imports = autogen_context.imports + + for upgrade_ops, downgrade_ops in zip( + migration_script.upgrade_ops_list, migration_script.downgrade_ops_list + ): + template_args[upgrade_ops.upgrade_token] = _indent( + _render_cmd_body(upgrade_ops, autogen_context) + ) + template_args[downgrade_ops.downgrade_token] = _indent( + _render_cmd_body(downgrade_ops, autogen_context) + ) + template_args["imports"] = "\n".join(sorted(imports)) + + +default_renderers = renderers = util.Dispatcher() + + +def _render_cmd_body( + op_container: ops.OpContainer, + autogen_context: AutogenContext, +) -> str: + buf = StringIO() + printer = PythonPrinter(buf) + + printer.writeline( + "# ### commands auto generated by Alembic - please adjust! ###" + ) + + has_lines = False + for op in op_container.ops: + lines = render_op(autogen_context, op) + has_lines = has_lines or bool(lines) + + for line in lines: + printer.writeline(line) + + if not has_lines: + printer.writeline("pass") + + printer.writeline("# ### end Alembic commands ###") + + return buf.getvalue() + + +def render_op( + autogen_context: AutogenContext, op: ops.MigrateOperation +) -> List[str]: + renderer = renderers.dispatch(op) + lines = util.to_list(renderer(autogen_context, op)) + return lines + + +def render_op_text( + autogen_context: AutogenContext, op: ops.MigrateOperation +) -> str: + return "\n".join(render_op(autogen_context, op)) + + +@renderers.dispatch_for(ops.ModifyTableOps) +def _render_modify_table( + autogen_context: AutogenContext, op: ModifyTableOps +) -> List[str]: + opts = autogen_context.opts + render_as_batch = opts.get("render_as_batch", False) + + if op.ops: + lines = [] + if render_as_batch: + with autogen_context._within_batch(): + lines.append( + "with op.batch_alter_table(%r, schema=%r) as batch_op:" + % (op.table_name, op.schema) + ) + for t_op in op.ops: + t_lines = render_op(autogen_context, t_op) + lines.extend(t_lines) + lines.append("") + else: + for t_op in op.ops: + t_lines = render_op(autogen_context, t_op) + lines.extend(t_lines) + + return lines + else: + return [] + + +@renderers.dispatch_for(ops.CreateTableCommentOp) +def _render_create_table_comment( + autogen_context: AutogenContext, op: ops.CreateTableCommentOp +) -> str: + if autogen_context._has_batch: + templ = ( + "{prefix}create_table_comment(\n" + "{indent}{comment},\n" + "{indent}existing_comment={existing}\n" + ")" + ) + else: + templ = ( + "{prefix}create_table_comment(\n" + "{indent}'{tname}',\n" + "{indent}{comment},\n" + "{indent}existing_comment={existing},\n" + "{indent}schema={schema}\n" + ")" + ) + return templ.format( + prefix=_alembic_autogenerate_prefix(autogen_context), + tname=op.table_name, + comment="%r" % op.comment if op.comment is not None else None, + existing=( + "%r" % op.existing_comment + if op.existing_comment is not None + else None + ), + schema="'%s'" % op.schema if op.schema is not None else None, + indent=" ", + ) + + +@renderers.dispatch_for(ops.DropTableCommentOp) +def _render_drop_table_comment( + autogen_context: AutogenContext, op: ops.DropTableCommentOp +) -> str: + if autogen_context._has_batch: + templ = ( + "{prefix}drop_table_comment(\n" + "{indent}existing_comment={existing}\n" + ")" + ) + else: + templ = ( + "{prefix}drop_table_comment(\n" + "{indent}'{tname}',\n" + "{indent}existing_comment={existing},\n" + "{indent}schema={schema}\n" + ")" + ) + return templ.format( + prefix=_alembic_autogenerate_prefix(autogen_context), + tname=op.table_name, + existing=( + "%r" % op.existing_comment + if op.existing_comment is not None + else None + ), + schema="'%s'" % op.schema if op.schema is not None else None, + indent=" ", + ) + + +@renderers.dispatch_for(ops.CreateTableOp) +def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str: + table = op.to_table() + + args = [ + col + for col in [ + _render_column(col, autogen_context) for col in table.columns + ] + if col + ] + sorted( + [ + rcons + for rcons in [ + _render_constraint( + cons, autogen_context, op._namespace_metadata + ) + for cons in table.constraints + ] + if rcons is not None + ] + ) + + if len(args) > MAX_PYTHON_ARGS: + args_str = "*[" + ",\n".join(args) + "]" + else: + args_str = ",\n".join(args) + + text = "%(prefix)screate_table(%(tablename)r,\n%(args)s" % { + "tablename": _ident(op.table_name), + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": args_str, + } + if op.schema: + text += ",\nschema=%r" % _ident(op.schema) + + comment = table.comment + if comment: + text += ",\ncomment=%r" % _ident(comment) + + info = table.info + if info: + text += f",\ninfo={info!r}" + + for k in sorted(op.kw): + text += ",\n%s=%r" % (k.replace(" ", "_"), op.kw[k]) + + if table._prefixes: + prefixes = ", ".join("'%s'" % p for p in table._prefixes) + text += ",\nprefixes=[%s]" % prefixes + + if op.if_not_exists is not None: + text += ",\nif_not_exists=%r" % bool(op.if_not_exists) + + text += "\n)" + return text + + +@renderers.dispatch_for(ops.DropTableOp) +def _drop_table(autogen_context: AutogenContext, op: ops.DropTableOp) -> str: + text = "%(prefix)sdrop_table(%(tname)r" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": _ident(op.table_name), + } + if op.schema: + text += ", schema=%r" % _ident(op.schema) + + if op.if_exists is not None: + text += ", if_exists=%r" % bool(op.if_exists) + + text += ")" + return text + + +def _render_dialect_kwargs_items( + autogen_context: AutogenContext, dialect_kwargs: _DialectArgView +) -> list[str]: + return [ + f"{key}={_render_potential_expr(val, autogen_context)}" + for key, val in dialect_kwargs.items() + ] + + +@renderers.dispatch_for(ops.CreateIndexOp) +def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str: + index = op.to_index() + + has_batch = autogen_context._has_batch + + if has_batch: + tmpl = ( + "%(prefix)screate_index(%(name)r, [%(columns)s], " + "unique=%(unique)r%(kwargs)s)" + ) + else: + tmpl = ( + "%(prefix)screate_index(%(name)r, %(table)r, [%(columns)s], " + "unique=%(unique)r%(schema)s%(kwargs)s)" + ) + + assert index.table is not None + + opts = _render_dialect_kwargs_items(autogen_context, index.dialect_kwargs) + if op.if_not_exists is not None: + opts.append("if_not_exists=%r" % bool(op.if_not_exists)) + text = tmpl % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "name": _render_gen_name(autogen_context, index.name), + "table": _ident(index.table.name), + "columns": ", ".join( + _get_index_rendered_expressions(index, autogen_context) + ), + "unique": index.unique or False, + "schema": ( + (", schema=%r" % _ident(index.table.schema)) + if index.table.schema + else "" + ), + "kwargs": ", " + ", ".join(opts) if opts else "", + } + return text + + +@renderers.dispatch_for(ops.DropIndexOp) +def _drop_index(autogen_context: AutogenContext, op: ops.DropIndexOp) -> str: + index = op.to_index() + + has_batch = autogen_context._has_batch + + if has_batch: + tmpl = "%(prefix)sdrop_index(%(name)r%(kwargs)s)" + else: + tmpl = ( + "%(prefix)sdrop_index(%(name)r, " + "table_name=%(table_name)r%(schema)s%(kwargs)s)" + ) + opts = _render_dialect_kwargs_items(autogen_context, index.dialect_kwargs) + if op.if_exists is not None: + opts.append("if_exists=%r" % bool(op.if_exists)) + text = tmpl % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "name": _render_gen_name(autogen_context, op.index_name), + "table_name": _ident(op.table_name), + "schema": ((", schema=%r" % _ident(op.schema)) if op.schema else ""), + "kwargs": ", " + ", ".join(opts) if opts else "", + } + return text + + +@renderers.dispatch_for(ops.CreateUniqueConstraintOp) +def _add_unique_constraint( + autogen_context: AutogenContext, op: ops.CreateUniqueConstraintOp +) -> List[str]: + return [_uq_constraint(op.to_constraint(), autogen_context, True)] + + +@renderers.dispatch_for(ops.CreateForeignKeyOp) +def _add_fk_constraint( + autogen_context: AutogenContext, op: ops.CreateForeignKeyOp +) -> str: + constraint = op.to_constraint() + args = [repr(_render_gen_name(autogen_context, op.constraint_name))] + if not autogen_context._has_batch: + args.append(repr(_ident(op.source_table))) + + args.extend( + [ + repr(_ident(op.referent_table)), + repr([_ident(col) for col in op.local_cols]), + repr([_ident(col) for col in op.remote_cols]), + ] + ) + kwargs = [ + "referent_schema", + "onupdate", + "ondelete", + "initially", + "deferrable", + "use_alter", + "match", + ] + if not autogen_context._has_batch: + kwargs.insert(0, "source_schema") + + for k in kwargs: + if k in op.kw: + value = op.kw[k] + if value is not None: + args.append("%s=%r" % (k, value)) + + dialect_kwargs = _render_dialect_kwargs_items( + autogen_context, constraint.dialect_kwargs + ) + + return "%(prefix)screate_foreign_key(%(args)s%(dialect_kwargs)s)" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + "dialect_kwargs": ( + ", " + ", ".join(dialect_kwargs) if dialect_kwargs else "" + ), + } + + +@renderers.dispatch_for(ops.CreatePrimaryKeyOp) +def _add_pk_constraint(constraint, autogen_context): + raise NotImplementedError() + + +@renderers.dispatch_for(ops.CreateCheckConstraintOp) +def _add_check_constraint(constraint, autogen_context): + raise NotImplementedError() + + +@renderers.dispatch_for(ops.DropConstraintOp) +def _drop_constraint( + autogen_context: AutogenContext, op: ops.DropConstraintOp +) -> str: + prefix = _alembic_autogenerate_prefix(autogen_context) + name = _render_gen_name(autogen_context, op.constraint_name) + schema = _ident(op.schema) if op.schema else None + type_ = _ident(op.constraint_type) if op.constraint_type else None + if_exists = op.if_exists + params_strs = [] + params_strs.append(repr(name)) + if not autogen_context._has_batch: + params_strs.append(repr(_ident(op.table_name))) + if schema is not None: + params_strs.append(f"schema={schema!r}") + if type_ is not None: + params_strs.append(f"type_={type_!r}") + if if_exists is not None: + params_strs.append(f"if_exists={if_exists}") + + return f"{prefix}drop_constraint({', '.join(params_strs)})" + + +@renderers.dispatch_for(ops.AddColumnOp) +def _add_column(autogen_context: AutogenContext, op: ops.AddColumnOp) -> str: + schema, tname, column, if_not_exists = ( + op.schema, + op.table_name, + op.column, + op.if_not_exists, + ) + if autogen_context._has_batch: + template = "%(prefix)sadd_column(%(column)s)" + else: + template = "%(prefix)sadd_column(%(tname)r, %(column)s" + if schema: + template += ", schema=%(schema)r" + if if_not_exists is not None: + template += ", if_not_exists=%(if_not_exists)r" + template += ")" + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": tname, + "column": _render_column(column, autogen_context), + "schema": schema, + "if_not_exists": if_not_exists, + } + return text + + +@renderers.dispatch_for(ops.DropColumnOp) +def _drop_column(autogen_context: AutogenContext, op: ops.DropColumnOp) -> str: + schema, tname, column_name, if_exists = ( + op.schema, + op.table_name, + op.column_name, + op.if_exists, + ) + + if autogen_context._has_batch: + template = "%(prefix)sdrop_column(%(cname)r)" + else: + template = "%(prefix)sdrop_column(%(tname)r, %(cname)r" + if schema: + template += ", schema=%(schema)r" + if if_exists is not None: + template += ", if_exists=%(if_exists)r" + template += ")" + + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": _ident(tname), + "cname": _ident(column_name), + "schema": _ident(schema), + "if_exists": if_exists, + } + return text + + +@renderers.dispatch_for(ops.AlterColumnOp) +def _alter_column( + autogen_context: AutogenContext, op: ops.AlterColumnOp +) -> str: + tname = op.table_name + cname = op.column_name + server_default = op.modify_server_default + type_ = op.modify_type + nullable = op.modify_nullable + comment = op.modify_comment + newname = op.modify_name + autoincrement = op.kw.get("autoincrement", None) + existing_type = op.existing_type + existing_nullable = op.existing_nullable + existing_comment = op.existing_comment + existing_server_default = op.existing_server_default + schema = op.schema + + indent = " " * 11 + + if autogen_context._has_batch: + template = "%(prefix)salter_column(%(cname)r" + else: + template = "%(prefix)salter_column(%(tname)r, %(cname)r" + + text = template % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "tname": tname, + "cname": cname, + } + if existing_type is not None: + text += ",\n%sexisting_type=%s" % ( + indent, + _repr_type(existing_type, autogen_context), + ) + if server_default is not False: + rendered = _render_server_default(server_default, autogen_context) + text += ",\n%sserver_default=%s" % (indent, rendered) + + if newname is not None: + text += ",\n%snew_column_name=%r" % (indent, newname) + if type_ is not None: + text += ",\n%stype_=%s" % (indent, _repr_type(type_, autogen_context)) + if nullable is not None: + text += ",\n%snullable=%r" % (indent, nullable) + if comment is not False: + text += ",\n%scomment=%r" % (indent, comment) + if existing_comment is not None: + text += ",\n%sexisting_comment=%r" % (indent, existing_comment) + if nullable is None and existing_nullable is not None: + text += ",\n%sexisting_nullable=%r" % (indent, existing_nullable) + if autoincrement is not None: + text += ",\n%sautoincrement=%r" % (indent, autoincrement) + if server_default is False and existing_server_default: + rendered = _render_server_default( + existing_server_default, autogen_context + ) + text += ",\n%sexisting_server_default=%s" % (indent, rendered) + if schema and not autogen_context._has_batch: + text += ",\n%sschema=%r" % (indent, schema) + text += ")" + return text + + +class _f_name: + def __init__(self, prefix: str, name: conv) -> None: + self.prefix = prefix + self.name = name + + def __repr__(self) -> str: + return "%sf(%r)" % (self.prefix, _ident(self.name)) + + +def _ident(name: Optional[Union[quoted_name, str]]) -> Optional[str]: + """produce a __repr__() object for a string identifier that may + use quoted_name() in SQLAlchemy 0.9 and greater. + + The issue worked around here is that quoted_name() doesn't have + very good repr() behavior by itself when unicode is involved. + + """ + if name is None: + return name + elif isinstance(name, quoted_name): + return str(name) + elif isinstance(name, str): + return name + + +def _render_potential_expr( + value: Any, + autogen_context: AutogenContext, + *, + wrap_in_element: bool = True, + is_server_default: bool = False, + is_index: bool = False, +) -> str: + if isinstance(value, sql.ClauseElement): + sql_text = autogen_context.migration_context.impl.render_ddl_sql_expr( + value, is_server_default=is_server_default, is_index=is_index + ) + if wrap_in_element: + prefix = _sqlalchemy_autogenerate_prefix(autogen_context) + element = "literal_column" if is_index else "text" + value_str = f"{prefix}{element}({sql_text!r})" + if ( + is_index + and isinstance(value, Label) + and type(value.name) is str + ): + return value_str + f".label({value.name!r})" + else: + return value_str + else: + return repr(sql_text) + else: + return repr(value) + + +def _get_index_rendered_expressions( + idx: Index, autogen_context: AutogenContext +) -> List[str]: + return [ + ( + repr(_ident(getattr(exp, "name", None))) + if isinstance(exp, sa_schema.Column) + else _render_potential_expr(exp, autogen_context, is_index=True) + ) + for exp in idx.expressions + ] + + +def _uq_constraint( + constraint: UniqueConstraint, + autogen_context: AutogenContext, + alter: bool, +) -> str: + opts: List[Tuple[str, Any]] = [] + + has_batch = autogen_context._has_batch + + if constraint.deferrable: + opts.append(("deferrable", constraint.deferrable)) + if constraint.initially: + opts.append(("initially", constraint.initially)) + if not has_batch and alter and constraint.table.schema: + opts.append(("schema", _ident(constraint.table.schema))) + if not alter and constraint.name: + opts.append( + ("name", _render_gen_name(autogen_context, constraint.name)) + ) + dialect_options = _render_dialect_kwargs_items( + autogen_context, constraint.dialect_kwargs + ) + + if alter: + args = [repr(_render_gen_name(autogen_context, constraint.name))] + if not has_batch: + args += [repr(_ident(constraint.table.name))] + args.append(repr([_ident(col.name) for col in constraint.columns])) + args.extend(["%s=%r" % (k, v) for k, v in opts]) + args.extend(dialect_options) + return "%(prefix)screate_unique_constraint(%(args)s)" % { + "prefix": _alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + else: + args = [repr(_ident(col.name)) for col in constraint.columns] + args.extend(["%s=%r" % (k, v) for k, v in opts]) + args.extend(dialect_options) + return "%(prefix)sUniqueConstraint(%(args)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + + +def _user_autogenerate_prefix(autogen_context, target): + prefix = autogen_context.opts["user_module_prefix"] + if prefix is None: + return "%s." % target.__module__ + else: + return prefix + + +def _sqlalchemy_autogenerate_prefix(autogen_context: AutogenContext) -> str: + return autogen_context.opts["sqlalchemy_module_prefix"] or "" + + +def _alembic_autogenerate_prefix(autogen_context: AutogenContext) -> str: + if autogen_context._has_batch: + return "batch_op." + else: + return autogen_context.opts["alembic_module_prefix"] or "" + + +def _user_defined_render( + type_: str, object_: Any, autogen_context: AutogenContext +) -> Union[str, Literal[False]]: + if "render_item" in autogen_context.opts: + render = autogen_context.opts["render_item"] + if render: + rendered = render(type_, object_, autogen_context) + if rendered is not False: + return rendered + return False + + +def _render_column( + column: Column[Any], autogen_context: AutogenContext +) -> str: + rendered = _user_defined_render("column", column, autogen_context) + if rendered is not False: + return rendered + + args: List[str] = [] + opts: List[Tuple[str, Any]] = [] + + if column.server_default: + rendered = _render_server_default( # type:ignore[assignment] + column.server_default, autogen_context + ) + if rendered: + if _should_render_server_default_positionally( + column.server_default + ): + args.append(rendered) + else: + opts.append(("server_default", rendered)) + + if ( + column.autoincrement is not None + and column.autoincrement != sqla_compat.AUTOINCREMENT_DEFAULT + ): + opts.append(("autoincrement", column.autoincrement)) + + if column.nullable is not None: + opts.append(("nullable", column.nullable)) + + if column.system: + opts.append(("system", column.system)) + + comment = column.comment + if comment: + opts.append(("comment", "%r" % comment)) + + # TODO: for non-ascii colname, assign a "key" + return "%(prefix)sColumn(%(name)r, %(type)s, %(args)s%(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "name": _ident(column.name), + "type": _repr_type(column.type, autogen_context), + "args": ", ".join([str(arg) for arg in args]) + ", " if args else "", + "kwargs": ( + ", ".join( + ["%s=%s" % (kwname, val) for kwname, val in opts] + + [ + "%s=%s" + % (key, _render_potential_expr(val, autogen_context)) + for key, val in column.kwargs.items() + ] + ) + ), + } + + +def _should_render_server_default_positionally(server_default: Any) -> bool: + return sqla_compat._server_default_is_computed( + server_default + ) or sqla_compat._server_default_is_identity(server_default) + + +def _render_server_default( + default: Optional[ + Union[FetchedValue, str, TextClause, ColumnElement[Any]] + ], + autogen_context: AutogenContext, + repr_: bool = True, +) -> Optional[str]: + rendered = _user_defined_render("server_default", default, autogen_context) + if rendered is not False: + return rendered + + if sqla_compat._server_default_is_computed(default): + return _render_computed(cast("Computed", default), autogen_context) + elif sqla_compat._server_default_is_identity(default): + return _render_identity(cast("Identity", default), autogen_context) + elif isinstance(default, sa_schema.DefaultClause): + if isinstance(default.arg, str): + default = default.arg + else: + return _render_potential_expr( + default.arg, autogen_context, is_server_default=True + ) + elif isinstance(default, sa_schema.FetchedValue): + return _render_fetched_value(autogen_context) + + if isinstance(default, str) and repr_: + default = repr(re.sub(r"^'|'$", "", default)) + + return cast(str, default) + + +def _render_computed( + computed: Computed, autogen_context: AutogenContext +) -> str: + text = _render_potential_expr( + computed.sqltext, autogen_context, wrap_in_element=False + ) + + kwargs = {} + if computed.persisted is not None: + kwargs["persisted"] = computed.persisted + return "%(prefix)sComputed(%(text)s, %(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "text": text, + "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())), + } + + +def _render_identity( + identity: Identity, autogen_context: AutogenContext +) -> str: + kwargs = sqla_compat._get_identity_options_dict( + identity, dialect_kwargs=True + ) + + return "%(prefix)sIdentity(%(kwargs)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "kwargs": (", ".join("%s=%s" % pair for pair in kwargs.items())), + } + + +def _render_fetched_value(autogen_context: AutogenContext) -> str: + return "%(prefix)sFetchedValue()" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + } + + +def _repr_type( + type_: TypeEngine, + autogen_context: AutogenContext, + _skip_variants: bool = False, +) -> str: + rendered = _user_defined_render("type", type_, autogen_context) + if rendered is not False: + return rendered + + if hasattr(autogen_context.migration_context, "impl"): + impl_rt = autogen_context.migration_context.impl.render_type( + type_, autogen_context + ) + else: + impl_rt = None + + mod = type(type_).__module__ + imports = autogen_context.imports + + if not _skip_variants and sqla_compat._type_has_variants(type_): + return _render_Variant_type(type_, autogen_context) + elif mod.startswith("sqlalchemy.dialects"): + match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod) + assert match is not None + dname = match.group(1) + if imports is not None: + imports.add("from sqlalchemy.dialects import %s" % dname) + if impl_rt: + return impl_rt + else: + return "%s.%r" % (dname, type_) + elif impl_rt: + return impl_rt + elif mod.startswith("sqlalchemy."): + if "_render_%s_type" % type_.__visit_name__ in globals(): + fn = globals()["_render_%s_type" % type_.__visit_name__] + return fn(type_, autogen_context) + else: + prefix = _sqlalchemy_autogenerate_prefix(autogen_context) + return "%s%r" % (prefix, type_) + else: + prefix = _user_autogenerate_prefix(autogen_context, type_) + return "%s%r" % (prefix, type_) + + +def _render_ARRAY_type(type_: ARRAY, autogen_context: AutogenContext) -> str: + return cast( + str, + _render_type_w_subtype( + type_, autogen_context, "item_type", r"(.+?\()" + ), + ) + + +def _render_Variant_type( + type_: TypeEngine, autogen_context: AutogenContext +) -> str: + base_type, variant_mapping = sqla_compat._get_variant_mapping(type_) + base = _repr_type(base_type, autogen_context, _skip_variants=True) + assert base is not None and base is not False # type: ignore[comparison-overlap] # noqa:E501 + for dialect in sorted(variant_mapping): + typ = variant_mapping[dialect] + base += ".with_variant(%s, %r)" % ( + _repr_type(typ, autogen_context, _skip_variants=True), + dialect, + ) + return base + + +def _render_type_w_subtype( + type_: TypeEngine, + autogen_context: AutogenContext, + attrname: str, + regexp: str, + prefix: Optional[str] = None, +) -> Union[Optional[str], Literal[False]]: + outer_repr = repr(type_) + inner_type = getattr(type_, attrname, None) + if inner_type is None: + return False + + inner_repr = repr(inner_type) + + inner_repr = re.sub(r"([\(\)])", r"\\\1", inner_repr) + sub_type = _repr_type(getattr(type_, attrname), autogen_context) + outer_type = re.sub(regexp + inner_repr, r"\1%s" % sub_type, outer_repr) + + if prefix: + return "%s%s" % (prefix, outer_type) + + mod = type(type_).__module__ + if mod.startswith("sqlalchemy.dialects"): + match = re.match(r"sqlalchemy\.dialects\.(\w+)", mod) + assert match is not None + dname = match.group(1) + return "%s.%s" % (dname, outer_type) + elif mod.startswith("sqlalchemy"): + prefix = _sqlalchemy_autogenerate_prefix(autogen_context) + return "%s%s" % (prefix, outer_type) + else: + return None + + +_constraint_renderers = util.Dispatcher() + + +def _render_constraint( + constraint: Constraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + try: + renderer = _constraint_renderers.dispatch(constraint) + except ValueError: + util.warn("No renderer is established for object %r" % constraint) + return "[Unknown Python object %r]" % constraint + else: + return renderer(constraint, autogen_context, namespace_metadata) + + +@_constraint_renderers.dispatch_for(sa_schema.PrimaryKeyConstraint) +def _render_primary_key( + constraint: PrimaryKeyConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + rendered = _user_defined_render("primary_key", constraint, autogen_context) + if rendered is not False: + return rendered + + if not constraint.columns: + return None + + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + return "%(prefix)sPrimaryKeyConstraint(%(args)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "args": ", ".join( + [repr(c.name) for c in constraint.columns] + + ["%s=%s" % (kwname, val) for kwname, val in opts] + ), + } + + +def _fk_colspec( + fk: ForeignKey, + metadata_schema: Optional[str], + namespace_metadata: Optional[MetaData], +) -> str: + """Implement a 'safe' version of ForeignKey._get_colspec() that + won't fail if the remote table can't be resolved. + + """ + colspec = fk._get_colspec() + tokens = colspec.split(".") + tname, colname = tokens[-2:] + + if metadata_schema is not None and len(tokens) == 2: + table_fullname = "%s.%s" % (metadata_schema, tname) + else: + table_fullname = ".".join(tokens[0:-1]) + + if ( + not fk.link_to_name + and fk.parent is not None + and fk.parent.table is not None + ): + # try to resolve the remote table in order to adjust for column.key. + # the FK constraint needs to be rendered in terms of the column + # name. + + if ( + namespace_metadata is not None + and table_fullname in namespace_metadata.tables + ): + col = namespace_metadata.tables[table_fullname].c.get(colname) + if col is not None: + colname = _ident(col.name) # type: ignore[assignment] + + colspec = "%s.%s" % (table_fullname, colname) + + return colspec + + +def _populate_render_fk_opts( + constraint: ForeignKeyConstraint, opts: List[Tuple[str, str]] +) -> None: + if constraint.onupdate: + opts.append(("onupdate", repr(constraint.onupdate))) + if constraint.ondelete: + opts.append(("ondelete", repr(constraint.ondelete))) + if constraint.initially: + opts.append(("initially", repr(constraint.initially))) + if constraint.deferrable: + opts.append(("deferrable", repr(constraint.deferrable))) + if constraint.use_alter: + opts.append(("use_alter", repr(constraint.use_alter))) + if constraint.match: + opts.append(("match", repr(constraint.match))) + + +@_constraint_renderers.dispatch_for(sa_schema.ForeignKeyConstraint) +def _render_foreign_key( + constraint: ForeignKeyConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + rendered = _user_defined_render("foreign_key", constraint, autogen_context) + if rendered is not False: + return rendered + + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + + _populate_render_fk_opts(constraint, opts) + + apply_metadata_schema = ( + namespace_metadata.schema if namespace_metadata is not None else None + ) + return ( + "%(prefix)sForeignKeyConstraint([%(cols)s], " + "[%(refcols)s], %(args)s)" + % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "cols": ", ".join( + repr(_ident(f.parent.name)) for f in constraint.elements + ), + "refcols": ", ".join( + repr(_fk_colspec(f, apply_metadata_schema, namespace_metadata)) + for f in constraint.elements + ), + "args": ", ".join( + ["%s=%s" % (kwname, val) for kwname, val in opts] + ), + } + ) + + +@_constraint_renderers.dispatch_for(sa_schema.UniqueConstraint) +def _render_unique_constraint( + constraint: UniqueConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> str: + rendered = _user_defined_render("unique", constraint, autogen_context) + if rendered is not False: + return rendered + + return _uq_constraint(constraint, autogen_context, False) + + +@_constraint_renderers.dispatch_for(sa_schema.CheckConstraint) +def _render_check_constraint( + constraint: CheckConstraint, + autogen_context: AutogenContext, + namespace_metadata: Optional[MetaData], +) -> Optional[str]: + rendered = _user_defined_render("check", constraint, autogen_context) + if rendered is not False: + return rendered + + # detect the constraint being part of + # a parent type which is probably in the Table already. + # ideally SQLAlchemy would give us more of a first class + # way to detect this. + if ( + constraint._create_rule + and hasattr(constraint._create_rule, "target") + and isinstance( + constraint._create_rule.target, + sqltypes.TypeEngine, + ) + ): + return None + opts = [] + if constraint.name: + opts.append( + ("name", repr(_render_gen_name(autogen_context, constraint.name))) + ) + return "%(prefix)sCheckConstraint(%(sqltext)s%(opts)s)" % { + "prefix": _sqlalchemy_autogenerate_prefix(autogen_context), + "opts": ( + ", " + (", ".join("%s=%s" % (k, v) for k, v in opts)) + if opts + else "" + ), + "sqltext": _render_potential_expr( + constraint.sqltext, autogen_context, wrap_in_element=False + ), + } + + +@renderers.dispatch_for(ops.ExecuteSQLOp) +def _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str: + if not isinstance(op.sqltext, str): + raise NotImplementedError( + "Autogenerate rendering of SQL Expression language constructs " + "not supported here; please use a plain SQL string" + ) + return "{prefix}execute({sqltext!r})".format( + prefix=_alembic_autogenerate_prefix(autogen_context), + sqltext=op.sqltext, + ) + + +renderers = default_renderers.branch() diff --git a/venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py b/venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py new file mode 100644 index 0000000..1d44b5c --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/autogenerate/rewriter.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Iterator +from typing import List +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +from .. import util +from ..operations import ops + +if TYPE_CHECKING: + from ..operations.ops import AddColumnOp + from ..operations.ops import AlterColumnOp + from ..operations.ops import CreateTableOp + from ..operations.ops import DowngradeOps + from ..operations.ops import MigrateOperation + from ..operations.ops import MigrationScript + from ..operations.ops import ModifyTableOps + from ..operations.ops import OpContainer + from ..operations.ops import UpgradeOps + from ..runtime.migration import MigrationContext + from ..script.revision import _GetRevArg + +ProcessRevisionDirectiveFn = Callable[ + ["MigrationContext", "_GetRevArg", List["MigrationScript"]], None +] + + +class Rewriter: + """A helper object that allows easy 'rewriting' of ops streams. + + The :class:`.Rewriter` object is intended to be passed along + to the + :paramref:`.EnvironmentContext.configure.process_revision_directives` + parameter in an ``env.py`` script. Once constructed, any number + of "rewrites" functions can be associated with it, which will be given + the opportunity to modify the structure without having to have explicit + knowledge of the overall structure. + + The function is passed the :class:`.MigrationContext` object and + ``revision`` tuple that are passed to the :paramref:`.Environment + Context.configure.process_revision_directives` function normally, + and the third argument is an individual directive of the type + noted in the decorator. The function has the choice of returning + a single op directive, which normally can be the directive that + was actually passed, or a new directive to replace it, or a list + of zero or more directives to replace it. + + .. seealso:: + + :ref:`autogen_rewriter` - usage example + + """ + + _traverse = util.Dispatcher() + + _chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = () + + def __init__(self) -> None: + self.dispatch = util.Dispatcher() + + def chain( + self, + other: Union[ + ProcessRevisionDirectiveFn, + Rewriter, + ], + ) -> Rewriter: + """Produce a "chain" of this :class:`.Rewriter` to another. + + This allows two or more rewriters to operate serially on a stream, + e.g.:: + + writer1 = autogenerate.Rewriter() + writer2 = autogenerate.Rewriter() + + + @writer1.rewrites(ops.AddColumnOp) + def add_column_nullable(context, revision, op): + op.column.nullable = True + return op + + + @writer2.rewrites(ops.AddColumnOp) + def add_column_idx(context, revision, op): + idx_op = ops.CreateIndexOp( + "ixc", op.table_name, [op.column.name] + ) + return [op, idx_op] + + writer = writer1.chain(writer2) + + :param other: a :class:`.Rewriter` instance + :return: a new :class:`.Rewriter` that will run the operations + of this writer, then the "other" writer, in succession. + + """ + wr = self.__class__.__new__(self.__class__) + wr.__dict__.update(self.__dict__) + wr._chained += (other,) + return wr + + def rewrites( + self, + operator: Union[ + Type[AddColumnOp], + Type[MigrateOperation], + Type[AlterColumnOp], + Type[CreateTableOp], + Type[ModifyTableOps], + ], + ) -> Callable[..., Any]: + """Register a function as rewriter for a given type. + + The function should receive three arguments, which are + the :class:`.MigrationContext`, a ``revision`` tuple, and + an op directive of the type indicated. E.g.:: + + @writer1.rewrites(ops.AddColumnOp) + def add_column_nullable(context, revision, op): + op.column.nullable = True + return op + + """ + return self.dispatch.dispatch_for(operator) + + def _rewrite( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> Iterator[MigrateOperation]: + try: + _rewriter = self.dispatch.dispatch(directive) + except ValueError: + _rewriter = None + yield directive + else: + if self in directive._mutations: + yield directive + else: + for r_directive in util.to_list( + _rewriter(context, revision, directive), [] + ): + r_directive._mutations = r_directive._mutations.union( + [self] + ) + yield r_directive + + def __call__( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: List[MigrationScript], + ) -> None: + self.process_revision_directives(context, revision, directives) + for process_revision_directives in self._chained: + process_revision_directives(context, revision, directives) + + @_traverse.dispatch_for(ops.MigrationScript) + def _traverse_script( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrationScript, + ) -> None: + upgrade_ops_list: List[UpgradeOps] = [] + for upgrade_ops in directive.upgrade_ops_list: + ret = self._traverse_for(context, revision, upgrade_ops) + if len(ret) != 1: + raise ValueError( + "Can only return single object for UpgradeOps traverse" + ) + upgrade_ops_list.append(ret[0]) + + directive.upgrade_ops = upgrade_ops_list + + downgrade_ops_list: List[DowngradeOps] = [] + for downgrade_ops in directive.downgrade_ops_list: + ret = self._traverse_for(context, revision, downgrade_ops) + if len(ret) != 1: + raise ValueError( + "Can only return single object for DowngradeOps traverse" + ) + downgrade_ops_list.append(ret[0]) + directive.downgrade_ops = downgrade_ops_list + + @_traverse.dispatch_for(ops.OpContainer) + def _traverse_op_container( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: OpContainer, + ) -> None: + self._traverse_list(context, revision, directive.ops) + + @_traverse.dispatch_for(ops.MigrateOperation) + def _traverse_any_directive( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> None: + pass + + def _traverse_for( + self, + context: MigrationContext, + revision: _GetRevArg, + directive: MigrateOperation, + ) -> Any: + directives = list(self._rewrite(context, revision, directive)) + for directive in directives: + traverser = self._traverse.dispatch(directive) + traverser(self, context, revision, directive) + return directives + + def _traverse_list( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: Any, + ) -> None: + dest = [] + for directive in directives: + dest.extend(self._traverse_for(context, revision, directive)) + + directives[:] = dest + + def process_revision_directives( + self, + context: MigrationContext, + revision: _GetRevArg, + directives: List[MigrationScript], + ) -> None: + self._traverse_list(context, revision, directives) diff --git a/venv/lib/python3.12/site-packages/alembic/command.py b/venv/lib/python3.12/site-packages/alembic/command.py new file mode 100644 index 0000000..4897c0d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/command.py @@ -0,0 +1,848 @@ +# mypy: allow-untyped-defs, allow-untyped-calls + +from __future__ import annotations + +import os +import pathlib +from typing import List +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from . import autogenerate as autogen +from . import util +from .runtime.environment import EnvironmentContext +from .script import ScriptDirectory +from .util import compat + +if TYPE_CHECKING: + from alembic.config import Config + from alembic.script.base import Script + from alembic.script.revision import _RevIdType + from .runtime.environment import ProcessRevisionDirectiveFn + + +def list_templates(config: Config) -> None: + """List available templates. + + :param config: a :class:`.Config` object. + + """ + + config.print_stdout("Available templates:\n") + for tempname in config._get_template_path().iterdir(): + with (tempname / "README").open() as readme: + synopsis = next(readme).rstrip() + config.print_stdout("%s - %s", tempname.name, synopsis) + + config.print_stdout("\nTemplates are used via the 'init' command, e.g.:") + config.print_stdout("\n alembic init --template generic ./scripts") + + +def init( + config: Config, + directory: str, + template: str = "generic", + package: bool = False, +) -> None: + """Initialize a new scripts directory. + + :param config: a :class:`.Config` object. + + :param directory: string path of the target directory. + + :param template: string name of the migration environment template to + use. + + :param package: when True, write ``__init__.py`` files into the + environment location as well as the versions/ location. + + """ + + directory_path = pathlib.Path(directory) + if directory_path.exists() and list(directory_path.iterdir()): + raise util.CommandError( + "Directory %s already exists and is not empty" % directory_path + ) + + template_path = config._get_template_path() / template + + if not template_path.exists(): + raise util.CommandError(f"No such template {template_path}") + + # left as os.access() to suit unit test mocking + if not os.access(directory_path, os.F_OK): + with util.status( + f"Creating directory {directory_path.absolute()}", + **config.messaging_opts, + ): + os.makedirs(directory_path) + + versions = directory_path / "versions" + with util.status( + f"Creating directory {versions.absolute()}", + **config.messaging_opts, + ): + os.makedirs(versions) + + if not directory_path.is_absolute(): + # for non-absolute path, state config file in .ini / pyproject + # as relative to the %(here)s token, which is where the config + # file itself would be + + if config._config_file_path is not None: + rel_dir = compat.path_relative_to( + directory_path.absolute(), + config._config_file_path.absolute().parent, + walk_up=True, + ) + ini_script_location_directory = ("%(here)s" / rel_dir).as_posix() + if config._toml_file_path is not None: + rel_dir = compat.path_relative_to( + directory_path.absolute(), + config._toml_file_path.absolute().parent, + walk_up=True, + ) + toml_script_location_directory = ("%(here)s" / rel_dir).as_posix() + + else: + ini_script_location_directory = directory_path.as_posix() + toml_script_location_directory = directory_path.as_posix() + + script = ScriptDirectory(directory_path) + + has_toml = False + + config_file: pathlib.Path | None = None + + for file_path in template_path.iterdir(): + file_ = file_path.name + if file_ == "alembic.ini.mako": + assert config.config_file_name is not None + config_file = pathlib.Path(config.config_file_name).absolute() + if config_file.exists(): + util.msg( + f"File {config_file} already exists, skipping", + **config.messaging_opts, + ) + else: + script._generate_template( + file_path, + config_file, + script_location=ini_script_location_directory, + ) + elif file_ == "pyproject.toml.mako": + has_toml = True + assert config._toml_file_path is not None + toml_path = config._toml_file_path.absolute() + + if toml_path.exists(): + # left as open() to suit unit test mocking + with open(toml_path, "rb") as f: + toml_data = compat.tomllib.load(f) + if "tool" in toml_data and "alembic" in toml_data["tool"]: + + util.msg( + f"File {toml_path} already exists " + "and already has a [tool.alembic] section, " + "skipping", + ) + continue + script._append_template( + file_path, + toml_path, + script_location=toml_script_location_directory, + ) + else: + script._generate_template( + file_path, + toml_path, + script_location=toml_script_location_directory, + ) + + elif file_path.is_file(): + output_file = directory_path / file_ + script._copy_file(file_path, output_file) + + if package: + for path in [ + directory_path.absolute() / "__init__.py", + versions.absolute() / "__init__.py", + ]: + with util.status(f"Adding {path!s}", **config.messaging_opts): + # left as open() to suit unit test mocking + with open(path, "w"): + pass + + assert config_file is not None + + if has_toml: + util.msg( + f"Please edit configuration settings in {toml_path} and " + "configuration/connection/logging " + f"settings in {config_file} before proceeding.", + **config.messaging_opts, + ) + else: + util.msg( + "Please edit configuration/connection/logging " + f"settings in {config_file} before proceeding.", + **config.messaging_opts, + ) + + +def revision( + config: Config, + message: Optional[str] = None, + autogenerate: bool = False, + sql: bool = False, + head: str = "head", + splice: bool = False, + branch_label: Optional[_RevIdType] = None, + version_path: Union[str, os.PathLike[str], None] = None, + rev_id: Optional[str] = None, + depends_on: Optional[str] = None, + process_revision_directives: Optional[ProcessRevisionDirectiveFn] = None, +) -> Union[Optional[Script], List[Optional[Script]]]: + """Create a new revision file. + + :param config: a :class:`.Config` object. + + :param message: string message to apply to the revision; this is the + ``-m`` option to ``alembic revision``. + + :param autogenerate: whether or not to autogenerate the script from + the database; this is the ``--autogenerate`` option to + ``alembic revision``. + + :param sql: whether to dump the script out as a SQL string; when specified, + the script is dumped to stdout. This is the ``--sql`` option to + ``alembic revision``. + + :param head: head revision to build the new revision upon as a parent; + this is the ``--head`` option to ``alembic revision``. + + :param splice: whether or not the new revision should be made into a + new head of its own; is required when the given ``head`` is not itself + a head. This is the ``--splice`` option to ``alembic revision``. + + :param branch_label: string label to apply to the branch; this is the + ``--branch-label`` option to ``alembic revision``. + + :param version_path: string symbol identifying a specific version path + from the configuration; this is the ``--version-path`` option to + ``alembic revision``. + + :param rev_id: optional revision identifier to use instead of having + one generated; this is the ``--rev-id`` option to ``alembic revision``. + + :param depends_on: optional list of "depends on" identifiers; this is the + ``--depends-on`` option to ``alembic revision``. + + :param process_revision_directives: this is a callable that takes the + same form as the callable described at + :paramref:`.EnvironmentContext.configure.process_revision_directives`; + will be applied to the structure generated by the revision process + where it can be altered programmatically. Note that unlike all + the other parameters, this option is only available via programmatic + use of :func:`.command.revision`. + + """ + + script_directory = ScriptDirectory.from_config(config) + + command_args = dict( + message=message, + autogenerate=autogenerate, + sql=sql, + head=head, + splice=splice, + branch_label=branch_label, + version_path=version_path, + rev_id=rev_id, + depends_on=depends_on, + ) + revision_context = autogen.RevisionContext( + config, + script_directory, + command_args, + process_revision_directives=process_revision_directives, + ) + + environment = util.asbool( + config.get_alembic_option("revision_environment") + ) + + if autogenerate: + environment = True + + if sql: + raise util.CommandError( + "Using --sql with --autogenerate does not make any sense" + ) + + def retrieve_migrations(rev, context): + revision_context.run_autogenerate(rev, context) + return [] + + elif environment: + + def retrieve_migrations(rev, context): + revision_context.run_no_autogenerate(rev, context) + return [] + + elif sql: + raise util.CommandError( + "Using --sql with the revision command when " + "revision_environment is not configured does not make any sense" + ) + + if environment: + with EnvironmentContext( + config, + script_directory, + fn=retrieve_migrations, + as_sql=sql, + template_args=revision_context.template_args, + revision_context=revision_context, + ): + script_directory.run_env() + + # the revision_context now has MigrationScript structure(s) present. + # these could theoretically be further processed / rewritten *here*, + # in addition to the hooks present within each run_migrations() call, + # or at the end of env.py run_migrations_online(). + + scripts = [script for script in revision_context.generate_scripts()] + if len(scripts) == 1: + return scripts[0] + else: + return scripts + + +def check(config: "Config") -> None: + """Check if revision command with autogenerate has pending upgrade ops. + + :param config: a :class:`.Config` object. + + .. versionadded:: 1.9.0 + + """ + + script_directory = ScriptDirectory.from_config(config) + + command_args = dict( + message=None, + autogenerate=True, + sql=False, + head="head", + splice=False, + branch_label=None, + version_path=None, + rev_id=None, + depends_on=None, + ) + revision_context = autogen.RevisionContext( + config, + script_directory, + command_args, + ) + + def retrieve_migrations(rev, context): + revision_context.run_autogenerate(rev, context) + return [] + + with EnvironmentContext( + config, + script_directory, + fn=retrieve_migrations, + as_sql=False, + template_args=revision_context.template_args, + revision_context=revision_context, + ): + script_directory.run_env() + + # the revision_context now has MigrationScript structure(s) present. + + migration_script = revision_context.generated_revisions[-1] + diffs = [] + for upgrade_ops in migration_script.upgrade_ops_list: + diffs.extend(upgrade_ops.as_diffs()) + + if diffs: + raise util.AutogenerateDiffsDetected( + f"New upgrade operations detected: {diffs}", + revision_context=revision_context, + diffs=diffs, + ) + else: + config.print_stdout("No new upgrade operations detected.") + + +def merge( + config: Config, + revisions: _RevIdType, + message: Optional[str] = None, + branch_label: Optional[_RevIdType] = None, + rev_id: Optional[str] = None, +) -> Optional[Script]: + """Merge two revisions together. Creates a new migration file. + + :param config: a :class:`.Config` instance + + :param revisions: The revisions to merge. + + :param message: string message to apply to the revision. + + :param branch_label: string label name to apply to the new revision. + + :param rev_id: hardcoded revision identifier instead of generating a new + one. + + .. seealso:: + + :ref:`branches` + + """ + + script = ScriptDirectory.from_config(config) + template_args = { + "config": config # Let templates use config for + # e.g. multiple databases + } + + environment = util.asbool( + config.get_alembic_option("revision_environment") + ) + + if environment: + + def nothing(rev, context): + return [] + + with EnvironmentContext( + config, + script, + fn=nothing, + as_sql=False, + template_args=template_args, + ): + script.run_env() + + return script.generate_revision( + rev_id or util.rev_id(), + message, + refresh=True, + head=revisions, + branch_labels=branch_label, + **template_args, # type:ignore[arg-type] + ) + + +def upgrade( + config: Config, + revision: str, + sql: bool = False, + tag: Optional[str] = None, +) -> None: + """Upgrade to a later version. + + :param config: a :class:`.Config` instance. + + :param revision: string revision target or range for --sql mode. May be + ``"heads"`` to target the most recent revision(s). + + :param sql: if True, use ``--sql`` mode. + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` + method. + + """ + + script = ScriptDirectory.from_config(config) + + starting_rev = None + if ":" in revision: + if not sql: + raise util.CommandError("Range revision not allowed") + starting_rev, revision = revision.split(":", 2) + + def upgrade(rev, context): + return script._upgrade_revs(revision, rev) + + with EnvironmentContext( + config, + script, + fn=upgrade, + as_sql=sql, + starting_rev=starting_rev, + destination_rev=revision, + tag=tag, + ): + script.run_env() + + +def downgrade( + config: Config, + revision: str, + sql: bool = False, + tag: Optional[str] = None, +) -> None: + """Revert to a previous version. + + :param config: a :class:`.Config` instance. + + :param revision: string revision target or range for --sql mode. May + be ``"base"`` to target the first revision. + + :param sql: if True, use ``--sql`` mode. + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument` + method. + + """ + + script = ScriptDirectory.from_config(config) + starting_rev = None + if ":" in revision: + if not sql: + raise util.CommandError("Range revision not allowed") + starting_rev, revision = revision.split(":", 2) + elif sql: + raise util.CommandError( + "downgrade with --sql requires :" + ) + + def downgrade(rev, context): + return script._downgrade_revs(revision, rev) + + with EnvironmentContext( + config, + script, + fn=downgrade, + as_sql=sql, + starting_rev=starting_rev, + destination_rev=revision, + tag=tag, + ): + script.run_env() + + +def show(config: Config, rev: str) -> None: + """Show the revision(s) denoted by the given symbol. + + :param config: a :class:`.Config` instance. + + :param rev: string revision target. May be ``"current"`` to show the + revision(s) currently applied in the database. + + """ + + script = ScriptDirectory.from_config(config) + + if rev == "current": + + def show_current(rev, context): + for sc in script.get_revisions(rev): + config.print_stdout(sc.log_entry) + return [] + + with EnvironmentContext(config, script, fn=show_current): + script.run_env() + else: + for sc in script.get_revisions(rev): + config.print_stdout(sc.log_entry) + + +def history( + config: Config, + rev_range: Optional[str] = None, + verbose: bool = False, + indicate_current: bool = False, +) -> None: + """List changeset scripts in chronological order. + + :param config: a :class:`.Config` instance. + + :param rev_range: string revision range. + + :param verbose: output in verbose mode. + + :param indicate_current: indicate current revision. + + """ + base: Optional[str] + head: Optional[str] + script = ScriptDirectory.from_config(config) + if rev_range is not None: + if ":" not in rev_range: + raise util.CommandError( + "History range requires [start]:[end], " "[start]:, or :[end]" + ) + base, head = rev_range.strip().split(":") + else: + base = head = None + + environment = ( + util.asbool(config.get_alembic_option("revision_environment")) + or indicate_current + ) + + def _display_history(config, script, base, head, currents=()): + for sc in script.walk_revisions( + base=base or "base", head=head or "heads" + ): + if indicate_current: + sc._db_current_indicator = sc.revision in currents + + config.print_stdout( + sc.cmd_format( + verbose=verbose, + include_branches=True, + include_doc=True, + include_parents=True, + ) + ) + + def _display_history_w_current(config, script, base, head): + def _display_current_history(rev, context): + if head == "current": + _display_history(config, script, base, rev, rev) + elif base == "current": + _display_history(config, script, rev, head, rev) + else: + _display_history(config, script, base, head, rev) + return [] + + with EnvironmentContext(config, script, fn=_display_current_history): + script.run_env() + + if base == "current" or head == "current" or environment: + _display_history_w_current(config, script, base, head) + else: + _display_history(config, script, base, head) + + +def heads( + config: Config, verbose: bool = False, resolve_dependencies: bool = False +) -> None: + """Show current available heads in the script directory. + + :param config: a :class:`.Config` instance. + + :param verbose: output in verbose mode. + + :param resolve_dependencies: treat dependency version as down revisions. + + """ + + script = ScriptDirectory.from_config(config) + if resolve_dependencies: + heads = script.get_revisions("heads") + else: + heads = script.get_revisions(script.get_heads()) + + for rev in heads: + config.print_stdout( + rev.cmd_format( + verbose, include_branches=True, tree_indicators=False + ) + ) + + +def branches(config: Config, verbose: bool = False) -> None: + """Show current branch points. + + :param config: a :class:`.Config` instance. + + :param verbose: output in verbose mode. + + """ + script = ScriptDirectory.from_config(config) + for sc in script.walk_revisions(): + if sc.is_branch_point: + config.print_stdout( + "%s\n%s\n", + sc.cmd_format(verbose, include_branches=True), + "\n".join( + "%s -> %s" + % ( + " " * len(str(sc.revision)), + rev_obj.cmd_format( + False, include_branches=True, include_doc=verbose + ), + ) + for rev_obj in ( + script.get_revision(rev) for rev in sc.nextrev + ) + ), + ) + + +def current( + config: Config, check_heads: bool = False, verbose: bool = False +) -> None: + """Display the current revision for a database. + + :param config: a :class:`.Config` instance. + + :param check_heads: Check if all head revisions are applied to the + database. Raises :class:`.DatabaseNotAtHead` if this is not the case. + + .. versionadded:: 1.17.1 + + :param verbose: output in verbose mode. + + """ + + script = ScriptDirectory.from_config(config) + + def display_version(rev, context): + if verbose: + config.print_stdout( + "Current revision(s) for %s:", + util.obfuscate_url_pw(context.connection.engine.url), + ) + if check_heads and ( + set(context.get_current_heads()) != set(script.get_heads()) + ): + raise util.DatabaseNotAtHead( + "Database is not on all head revisions" + ) + for rev in script.get_all_current(rev): + config.print_stdout(rev.cmd_format(verbose)) + + return [] + + with EnvironmentContext( + config, script, fn=display_version, dont_mutate=True + ): + script.run_env() + + +def stamp( + config: Config, + revision: _RevIdType, + sql: bool = False, + tag: Optional[str] = None, + purge: bool = False, +) -> None: + """'stamp' the revision table with the given revision; don't + run any migrations. + + :param config: a :class:`.Config` instance. + + :param revision: target revision or list of revisions. May be a list + to indicate stamping of multiple branch heads; may be ``"base"`` + to remove all revisions from the table or ``"heads"`` to stamp the + most recent revision(s). + + .. note:: this parameter is called "revisions" in the command line + interface. + + :param sql: use ``--sql`` mode + + :param tag: an arbitrary "tag" that can be intercepted by custom + ``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument` + method. + + :param purge: delete all entries in the version table before stamping. + + """ + + script = ScriptDirectory.from_config(config) + + if sql: + destination_revs = [] + starting_rev = None + for _revision in util.to_list(revision): + if ":" in _revision: + srev, _revision = _revision.split(":", 2) + + if starting_rev != srev: + if starting_rev is None: + starting_rev = srev + else: + raise util.CommandError( + "Stamp operation with --sql only supports a " + "single starting revision at a time" + ) + destination_revs.append(_revision) + else: + destination_revs = util.to_list(revision) + + def do_stamp(rev, context): + return script._stamp_revs(util.to_tuple(destination_revs), rev) + + with EnvironmentContext( + config, + script, + fn=do_stamp, + as_sql=sql, + starting_rev=starting_rev if sql else None, + destination_rev=util.to_tuple(destination_revs), + tag=tag, + purge=purge, + ): + script.run_env() + + +def edit(config: Config, rev: str) -> None: + """Edit revision script(s) using $EDITOR. + + :param config: a :class:`.Config` instance. + + :param rev: target revision. + + """ + + script = ScriptDirectory.from_config(config) + + if rev == "current": + + def edit_current(rev, context): + if not rev: + raise util.CommandError("No current revisions") + for sc in script.get_revisions(rev): + util.open_in_editor(sc.path) + return [] + + with EnvironmentContext(config, script, fn=edit_current): + script.run_env() + else: + revs = script.get_revisions(rev) + if not revs: + raise util.CommandError( + "No revision files indicated by symbol '%s'" % rev + ) + for sc in revs: + assert sc + util.open_in_editor(sc.path) + + +def ensure_version(config: Config, sql: bool = False) -> None: + """Create the alembic version table if it doesn't exist already . + + :param config: a :class:`.Config` instance. + + :param sql: use ``--sql`` mode. + + .. versionadded:: 1.7.6 + + """ + + script = ScriptDirectory.from_config(config) + + def do_ensure_version(rev, context): + context._ensure_version_table() + return [] + + with EnvironmentContext( + config, + script, + fn=do_ensure_version, + as_sql=sql, + ): + script.run_env() diff --git a/venv/lib/python3.12/site-packages/alembic/config.py b/venv/lib/python3.12/site-packages/alembic/config.py new file mode 100644 index 0000000..121a445 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/config.py @@ -0,0 +1,1051 @@ +from __future__ import annotations + +from argparse import ArgumentParser +from argparse import Namespace +from configparser import ConfigParser +import inspect +import logging +import os +from pathlib import Path +import re +import sys +from typing import Any +from typing import cast +from typing import Dict +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Protocol +from typing import Sequence +from typing import TextIO +from typing import Union + +from typing_extensions import TypedDict + +from . import __version__ +from . import command +from . import util +from .util import compat +from .util.pyfiles import _preserving_path_as_str + + +log = logging.getLogger(__name__) + + +class Config: + r"""Represent an Alembic configuration. + + Within an ``env.py`` script, this is available + via the :attr:`.EnvironmentContext.config` attribute, + which in turn is available at ``alembic.context``:: + + from alembic import context + + some_param = context.config.get_main_option("my option") + + When invoking Alembic programmatically, a new + :class:`.Config` can be created by passing + the name of an .ini file to the constructor:: + + from alembic.config import Config + alembic_cfg = Config("/path/to/yourapp/alembic.ini") + + With a :class:`.Config` object, you can then + run Alembic commands programmatically using the directives + in :mod:`alembic.command`. + + The :class:`.Config` object can also be constructed without + a filename. Values can be set programmatically, and + new sections will be created as needed:: + + from alembic.config import Config + alembic_cfg = Config() + alembic_cfg.set_main_option("script_location", "myapp:migrations") + alembic_cfg.set_main_option("sqlalchemy.url", "postgresql://foo/bar") + alembic_cfg.set_section_option("mysection", "foo", "bar") + + .. warning:: + + When using programmatic configuration, make sure the + ``env.py`` file in use is compatible with the target configuration; + including that the call to Python ``logging.fileConfig()`` is + omitted if the programmatic configuration doesn't actually include + logging directives. + + For passing non-string values to environments, such as connections and + engines, use the :attr:`.Config.attributes` dictionary:: + + with engine.begin() as connection: + alembic_cfg.attributes['connection'] = connection + command.upgrade(alembic_cfg, "head") + + :param file\_: name of the .ini file to open if an ``alembic.ini`` is + to be used. This should refer to the ``alembic.ini`` file, either as + a filename or a full path to the file. This filename if passed must refer + to an **ini file in ConfigParser format** only. + + :param toml\_file: name of the pyproject.toml file to open if a + ``pyproject.toml`` file is to be used. This should refer to the + ``pyproject.toml`` file, either as a filename or a full path to the file. + This file must be in toml format. Both :paramref:`.Config.file\_` and + :paramref:`.Config.toml\_file` may be passed simultaneously, or + exclusively. + + .. versionadded:: 1.16.0 + + :param ini_section: name of the main Alembic section within the + .ini file + :param output_buffer: optional file-like input buffer which + will be passed to the :class:`.MigrationContext` - used to redirect + the output of "offline generation" when using Alembic programmatically. + :param stdout: buffer where the "print" output of commands will be sent. + Defaults to ``sys.stdout``. + + :param config_args: A dictionary of keys and values that will be used + for substitution in the alembic config file, as well as the pyproject.toml + file, depending on which / both are used. The dictionary as given is + **copied** to two new, independent dictionaries, stored locally under the + attributes ``.config_args`` and ``.toml_args``. Both of these + dictionaries will also be populated with the replacement variable + ``%(here)s``, which refers to the location of the .ini and/or .toml file + as appropriate. + + :param attributes: optional dictionary of arbitrary Python keys/values, + which will be populated into the :attr:`.Config.attributes` dictionary. + + .. seealso:: + + :ref:`connection_sharing` + + """ + + def __init__( + self, + file_: Union[str, os.PathLike[str], None] = None, + toml_file: Union[str, os.PathLike[str], None] = None, + ini_section: str = "alembic", + output_buffer: Optional[TextIO] = None, + stdout: TextIO = sys.stdout, + cmd_opts: Optional[Namespace] = None, + config_args: Mapping[str, Any] = util.immutabledict(), + attributes: Optional[Dict[str, Any]] = None, + ) -> None: + """Construct a new :class:`.Config`""" + self.config_file_name = ( + _preserving_path_as_str(file_) if file_ else None + ) + self.toml_file_name = ( + _preserving_path_as_str(toml_file) if toml_file else None + ) + self.config_ini_section = ini_section + self.output_buffer = output_buffer + self.stdout = stdout + self.cmd_opts = cmd_opts + self.config_args = dict(config_args) + self.toml_args = dict(config_args) + if attributes: + self.attributes.update(attributes) + + cmd_opts: Optional[Namespace] = None + """The command-line options passed to the ``alembic`` script. + + Within an ``env.py`` script this can be accessed via the + :attr:`.EnvironmentContext.config` attribute. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` + + """ + + config_file_name: Optional[str] = None + """Filesystem path to the .ini file in use.""" + + toml_file_name: Optional[str] = None + """Filesystem path to the pyproject.toml file in use. + + .. versionadded:: 1.16.0 + + """ + + @property + def _config_file_path(self) -> Optional[Path]: + if self.config_file_name is None: + return None + return Path(self.config_file_name) + + @property + def _toml_file_path(self) -> Optional[Path]: + if self.toml_file_name is None: + return None + return Path(self.toml_file_name) + + config_ini_section: str = None # type:ignore[assignment] + """Name of the config file section to read basic configuration + from. Defaults to ``alembic``, that is the ``[alembic]`` section + of the .ini file. This value is modified using the ``-n/--name`` + option to the Alembic runner. + + """ + + @util.memoized_property + def attributes(self) -> Dict[str, Any]: + """A Python dictionary for storage of additional state. + + + This is a utility dictionary which can include not just strings but + engines, connections, schema objects, or anything else. + Use this to pass objects into an env.py script, such as passing + a :class:`sqlalchemy.engine.base.Connection` when calling + commands from :mod:`alembic.command` programmatically. + + .. seealso:: + + :ref:`connection_sharing` + + :paramref:`.Config.attributes` + + """ + return {} + + def print_stdout(self, text: str, *arg: Any) -> None: + """Render a message to standard out. + + When :meth:`.Config.print_stdout` is called with additional args + those arguments will formatted against the provided text, + otherwise we simply output the provided text verbatim. + + This is a no-op when the``quiet`` messaging option is enabled. + + e.g.:: + + >>> config.print_stdout('Some text %s', 'arg') + Some Text arg + + """ + + if arg: + output = str(text) % arg + else: + output = str(text) + + util.write_outstream(self.stdout, output, "\n", **self.messaging_opts) + + @util.memoized_property + def file_config(self) -> ConfigParser: + """Return the underlying ``ConfigParser`` object. + + Dir*-ect access to the .ini file is available here, + though the :meth:`.Config.get_section` and + :meth:`.Config.get_main_option` + methods provide a possibly simpler interface. + + """ + + if self._config_file_path: + here = self._config_file_path.absolute().parent + else: + here = Path() + self.config_args["here"] = here.as_posix() + file_config = ConfigParser(self.config_args) + + verbose = getattr(self.cmd_opts, "verbose", False) + if self._config_file_path: + compat.read_config_parser(file_config, [self._config_file_path]) + if verbose: + log.info( + "Loading config from file: %s", self._config_file_path + ) + else: + file_config.add_section(self.config_ini_section) + if verbose: + log.info( + "No config file provided; using in-memory default config" + ) + return file_config + + @util.memoized_property + def toml_alembic_config(self) -> Mapping[str, Any]: + """Return a dictionary of the [tool.alembic] section from + pyproject.toml""" + + if self._toml_file_path and self._toml_file_path.exists(): + + here = self._toml_file_path.absolute().parent + self.toml_args["here"] = here.as_posix() + + with open(self._toml_file_path, "rb") as f: + toml_data = compat.tomllib.load(f) + data = toml_data.get("tool", {}).get("alembic", {}) + if not isinstance(data, dict): + raise util.CommandError("Incorrect TOML format") + return data + + else: + return {} + + def get_template_directory(self) -> str: + """Return the directory where Alembic setup templates are found. + + This method is used by the alembic ``init`` and ``list_templates`` + commands. + + """ + import alembic + + package_dir = Path(alembic.__file__).absolute().parent + return str(package_dir / "templates") + + def _get_template_path(self) -> Path: + """Return the directory where Alembic setup templates are found. + + This method is used by the alembic ``init`` and ``list_templates`` + commands. + + .. versionadded:: 1.16.0 + + """ + return Path(self.get_template_directory()) + + @overload + def get_section( + self, name: str, default: None = ... + ) -> Optional[Dict[str, str]]: ... + + # "default" here could also be a TypeVar + # _MT = TypeVar("_MT", bound=Mapping[str, str]), + # however mypy wasn't handling that correctly (pyright was) + @overload + def get_section( + self, name: str, default: Dict[str, str] + ) -> Dict[str, str]: ... + + @overload + def get_section( + self, name: str, default: Mapping[str, str] + ) -> Union[Dict[str, str], Mapping[str, str]]: ... + + def get_section( + self, name: str, default: Optional[Mapping[str, str]] = None + ) -> Optional[Mapping[str, str]]: + """Return all the configuration options from a given .ini file section + as a dictionary. + + If the given section does not exist, the value of ``default`` + is returned, which is expected to be a dictionary or other mapping. + + """ + if not self.file_config.has_section(name): + return default + + return dict(self.file_config.items(name)) + + def set_main_option(self, name: str, value: str) -> None: + """Set an option programmatically within the 'main' section. + + This overrides whatever was in the .ini file. + + :param name: name of the value + + :param value: the value. Note that this value is passed to + ``ConfigParser.set``, which supports variable interpolation using + pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of + an interpolation symbol must therefore be escaped, e.g. ``%%``. + The given value may refer to another value already in the file + using the interpolation format. + + """ + self.set_section_option(self.config_ini_section, name, value) + + def remove_main_option(self, name: str) -> None: + self.file_config.remove_option(self.config_ini_section, name) + + def set_section_option(self, section: str, name: str, value: str) -> None: + """Set an option programmatically within the given section. + + The section is created if it doesn't exist already. + The value here will override whatever was in the .ini + file. + + Does **NOT** consume from the pyproject.toml file. + + .. seealso:: + + :meth:`.Config.get_alembic_option` - includes pyproject support + + :param section: name of the section + + :param name: name of the value + + :param value: the value. Note that this value is passed to + ``ConfigParser.set``, which supports variable interpolation using + pyformat (e.g. ``%(some_value)s``). A raw percent sign not part of + an interpolation symbol must therefore be escaped, e.g. ``%%``. + The given value may refer to another value already in the file + using the interpolation format. + + """ + + if not self.file_config.has_section(section): + self.file_config.add_section(section) + self.file_config.set(section, name, value) + + def get_section_option( + self, section: str, name: str, default: Optional[str] = None + ) -> Optional[str]: + """Return an option from the given section of the .ini file.""" + if not self.file_config.has_section(section): + raise util.CommandError( + "No config file %r found, or file has no " + "'[%s]' section" % (self.config_file_name, section) + ) + if self.file_config.has_option(section, name): + return self.file_config.get(section, name) + else: + return default + + @overload + def get_main_option(self, name: str, default: str) -> str: ... + + @overload + def get_main_option( + self, name: str, default: Optional[str] = None + ) -> Optional[str]: ... + + def get_main_option( + self, name: str, default: Optional[str] = None + ) -> Optional[str]: + """Return an option from the 'main' section of the .ini file. + + This defaults to being a key from the ``[alembic]`` + section, unless the ``-n/--name`` flag were used to + indicate a different section. + + Does **NOT** consume from the pyproject.toml file. + + .. seealso:: + + :meth:`.Config.get_alembic_option` - includes pyproject support + + """ + return self.get_section_option(self.config_ini_section, name, default) + + @overload + def get_alembic_option(self, name: str, default: str) -> str: ... + + @overload + def get_alembic_option( + self, name: str, default: Optional[str] = None + ) -> Optional[str]: ... + + def get_alembic_option( + self, name: str, default: Optional[str] = None + ) -> Union[ + None, str, list[str], dict[str, str], list[dict[str, str]], int + ]: + """Return an option from the "[alembic]" or "[tool.alembic]" section + of the configparser-parsed .ini file (e.g. ``alembic.ini``) or + toml-parsed ``pyproject.toml`` file. + + The value returned is expected to be None, string, list of strings, + or dictionary of strings. Within each type of string value, the + ``%(here)s`` token is substituted out with the absolute path of the + ``pyproject.toml`` file, as are other tokens which are extracted from + the :paramref:`.Config.config_args` dictionary. + + Searches always prioritize the configparser namespace first, before + searching in the toml namespace. + + If Alembic was run using the ``-n/--name`` flag to indicate an + alternate main section name, this is taken into account **only** for + the configparser-parsed .ini file. The section name in toml is always + ``[tool.alembic]``. + + + .. versionadded:: 1.16.0 + + """ + + if self.file_config.has_option(self.config_ini_section, name): + return self.file_config.get(self.config_ini_section, name) + else: + return self._get_toml_config_value(name, default=default) + + def get_alembic_boolean_option(self, name: str) -> bool: + if self.file_config.has_option(self.config_ini_section, name): + return ( + self.file_config.get(self.config_ini_section, name) == "true" + ) + else: + value = self.toml_alembic_config.get(name, False) + if not isinstance(value, bool): + raise util.CommandError( + f"boolean value expected for TOML parameter {name!r}" + ) + return value + + def _get_toml_config_value( + self, name: str, default: Optional[Any] = None + ) -> Union[ + None, str, list[str], dict[str, str], list[dict[str, str]], int + ]: + USE_DEFAULT = object() + value: Union[None, str, list[str], dict[str, str], int] = ( + self.toml_alembic_config.get(name, USE_DEFAULT) + ) + if value is USE_DEFAULT: + return default + if value is not None: + if isinstance(value, str): + value = value % (self.toml_args) + elif isinstance(value, list): + if value and isinstance(value[0], dict): + value = [ + {k: v % (self.toml_args) for k, v in dv.items()} + for dv in value + ] + else: + value = cast( + "list[str]", [v % (self.toml_args) for v in value] + ) + elif isinstance(value, dict): + value = cast( + "dict[str, str]", + {k: v % (self.toml_args) for k, v in value.items()}, + ) + elif isinstance(value, int): + return value + else: + raise util.CommandError( + f"unsupported TOML value type for key: {name!r}" + ) + return value + + @util.memoized_property + def messaging_opts(self) -> MessagingOptions: + """The messaging options.""" + return cast( + MessagingOptions, + util.immutabledict( + {"quiet": getattr(self.cmd_opts, "quiet", False)} + ), + ) + + def _get_file_separator_char(self, *names: str) -> Optional[str]: + for name in names: + separator = self.get_main_option(name) + if separator is not None: + break + else: + return None + + split_on_path = { + "space": " ", + "newline": "\n", + "os": os.pathsep, + ":": ":", + ";": ";", + } + + try: + sep = split_on_path[separator] + except KeyError as ke: + raise ValueError( + "'%s' is not a valid value for %s; " + "expected 'space', 'newline', 'os', ':', ';'" + % (separator, name) + ) from ke + else: + if name == "version_path_separator": + util.warn_deprecated( + "The version_path_separator configuration parameter " + "is deprecated; please use path_separator" + ) + return sep + + def get_version_locations_list(self) -> Optional[list[str]]: + + version_locations_str = self.file_config.get( + self.config_ini_section, "version_locations", fallback=None + ) + + if version_locations_str: + split_char = self._get_file_separator_char( + "path_separator", "version_path_separator" + ) + + if split_char is None: + + # legacy behaviour for backwards compatibility + util.warn_deprecated( + "No path_separator found in configuration; " + "falling back to legacy splitting on spaces/commas " + "for version_locations. Consider adding " + "path_separator=os to Alembic config." + ) + + _split_on_space_comma = re.compile(r", *|(?: +)") + return _split_on_space_comma.split(version_locations_str) + else: + return [ + x.strip() + for x in version_locations_str.split(split_char) + if x + ] + else: + return cast( + "list[str]", + self._get_toml_config_value("version_locations", None), + ) + + def get_prepend_sys_paths_list(self) -> Optional[list[str]]: + prepend_sys_path_str = self.file_config.get( + self.config_ini_section, "prepend_sys_path", fallback=None + ) + + if prepend_sys_path_str: + split_char = self._get_file_separator_char("path_separator") + + if split_char is None: + + # legacy behaviour for backwards compatibility + util.warn_deprecated( + "No path_separator found in configuration; " + "falling back to legacy splitting on spaces, commas, " + "and colons for prepend_sys_path. Consider adding " + "path_separator=os to Alembic config." + ) + + _split_on_space_comma_colon = re.compile(r", *|(?: +)|\:") + return _split_on_space_comma_colon.split(prepend_sys_path_str) + else: + return [ + x.strip() + for x in prepend_sys_path_str.split(split_char) + if x + ] + else: + return cast( + "list[str]", + self._get_toml_config_value("prepend_sys_path", None), + ) + + def get_hooks_list(self) -> list[PostWriteHookConfig]: + + hooks: list[PostWriteHookConfig] = [] + + if not self.file_config.has_section("post_write_hooks"): + toml_hook_config = cast( + "list[dict[str, str]]", + self._get_toml_config_value("post_write_hooks", []), + ) + for cfg in toml_hook_config: + opts = dict(cfg) + opts["_hook_name"] = opts.pop("name") + hooks.append(opts) + + else: + _split_on_space_comma = re.compile(r", *|(?: +)") + ini_hook_config = self.get_section("post_write_hooks", {}) + names = _split_on_space_comma.split( + ini_hook_config.get("hooks", "") + ) + + for name in names: + if not name: + continue + opts = { + key[len(name) + 1 :]: ini_hook_config[key] + for key in ini_hook_config + if key.startswith(name + ".") + } + + opts["_hook_name"] = name + hooks.append(opts) + + return hooks + + +PostWriteHookConfig = Mapping[str, str] + + +class MessagingOptions(TypedDict, total=False): + quiet: bool + + +class CommandFunction(Protocol): + """A function that may be registered in the CLI as an alembic command. + It must be a named function and it must accept a :class:`.Config` object + as the first argument. + + .. versionadded:: 1.15.3 + + """ + + __name__: str + + def __call__(self, config: Config, *args: Any, **kwargs: Any) -> Any: ... + + +class CommandLine: + """Provides the command line interface to Alembic.""" + + def __init__(self, prog: Optional[str] = None) -> None: + self._generate_args(prog) + + _KWARGS_OPTS = { + "template": ( + "-t", + "--template", + dict( + default="generic", + type=str, + help="Setup template for use with 'init'", + ), + ), + "message": ( + "-m", + "--message", + dict(type=str, help="Message string to use with 'revision'"), + ), + "sql": ( + "--sql", + dict( + action="store_true", + help="Don't emit SQL to database - dump to " + "standard output/file instead. See docs on " + "offline mode.", + ), + ), + "tag": ( + "--tag", + dict( + type=str, + help="Arbitrary 'tag' name - can be used by " + "custom env.py scripts.", + ), + ), + "head": ( + "--head", + dict( + type=str, + help="Specify head revision or @head " + "to base new revision on.", + ), + ), + "splice": ( + "--splice", + dict( + action="store_true", + help="Allow a non-head revision as the 'head' to splice onto", + ), + ), + "depends_on": ( + "--depends-on", + dict( + action="append", + help="Specify one or more revision identifiers " + "which this revision should depend on.", + ), + ), + "rev_id": ( + "--rev-id", + dict( + type=str, + help="Specify a hardcoded revision id instead of " + "generating one", + ), + ), + "version_path": ( + "--version-path", + dict( + type=str, + help="Specify specific path from config for version file", + ), + ), + "branch_label": ( + "--branch-label", + dict( + type=str, + help="Specify a branch label to apply to the new revision", + ), + ), + "verbose": ( + "-v", + "--verbose", + dict(action="store_true", help="Use more verbose output"), + ), + "resolve_dependencies": ( + "--resolve-dependencies", + dict( + action="store_true", + help="Treat dependency versions as down revisions", + ), + ), + "autogenerate": ( + "--autogenerate", + dict( + action="store_true", + help="Populate revision script with candidate " + "migration operations, based on comparison " + "of database to model.", + ), + ), + "rev_range": ( + "-r", + "--rev-range", + dict( + action="store", + help="Specify a revision range; format is [start]:[end]", + ), + ), + "indicate_current": ( + "-i", + "--indicate-current", + dict( + action="store_true", + help="Indicate the current revision", + ), + ), + "purge": ( + "--purge", + dict( + action="store_true", + help="Unconditionally erase the version table before stamping", + ), + ), + "package": ( + "--package", + dict( + action="store_true", + help="Write empty __init__.py files to the " + "environment and version locations", + ), + ), + "check_heads": ( + "-c", + "--check-heads", + dict( + action="store_true", + help=( + "Check if all head revisions are applied to the database. " + "Exit with an error code if this is not the case." + ), + ), + ), + } + _POSITIONAL_OPTS = { + "directory": dict(help="location of scripts directory"), + "revision": dict( + help="revision identifier", + ), + "revisions": dict( + nargs="+", + help="one or more revisions, or 'heads' for all heads", + ), + } + _POSITIONAL_TRANSLATIONS: dict[Any, dict[str, str]] = { + command.stamp: {"revision": "revisions"} + } + + def _generate_args(self, prog: Optional[str]) -> None: + parser = ArgumentParser(prog=prog) + + parser.add_argument( + "--version", action="version", version="%%(prog)s %s" % __version__ + ) + parser.add_argument( + "-c", + "--config", + action="append", + help="Alternate config file; defaults to value of " + 'ALEMBIC_CONFIG environment variable, or "alembic.ini". ' + "May also refer to pyproject.toml file. May be specified twice " + "to reference both files separately", + ) + parser.add_argument( + "-n", + "--name", + type=str, + default="alembic", + help="Name of section in .ini file to use for Alembic config " + "(only applies to configparser config, not toml)", + ) + parser.add_argument( + "-x", + action="append", + help="Additional arguments consumed by " + "custom env.py scripts, e.g. -x " + "setting1=somesetting -x setting2=somesetting", + ) + parser.add_argument( + "--raiseerr", + action="store_true", + help="Raise a full stack trace on error", + ) + parser.add_argument( + "-q", + "--quiet", + action="store_true", + help="Do not log to std output.", + ) + + self.subparsers = parser.add_subparsers() + alembic_commands = ( + cast(CommandFunction, fn) + for fn in (getattr(command, name) for name in dir(command)) + if ( + inspect.isfunction(fn) + and fn.__name__[0] != "_" + and fn.__module__ == "alembic.command" + ) + ) + + for fn in alembic_commands: + self.register_command(fn) + + self.parser = parser + + def register_command(self, fn: CommandFunction) -> None: + """Registers a function as a CLI subcommand. The subcommand name + matches the function name, the arguments are extracted from the + signature and the help text is read from the docstring. + + .. versionadded:: 1.15.3 + + .. seealso:: + + :ref:`custom_commandline` + """ + + positional, kwarg, help_text = self._inspect_function(fn) + + subparser = self.subparsers.add_parser(fn.__name__, help=help_text) + subparser.set_defaults(cmd=(fn, positional, kwarg)) + + for arg in kwarg: + if arg in self._KWARGS_OPTS: + kwarg_opt = self._KWARGS_OPTS[arg] + args, opts = kwarg_opt[0:-1], kwarg_opt[-1] + subparser.add_argument(*args, **opts) # type:ignore + + for arg in positional: + opts = self._POSITIONAL_OPTS.get(arg, {}) + subparser.add_argument(arg, **opts) # type:ignore + + def _inspect_function(self, fn: CommandFunction) -> tuple[Any, Any, str]: + spec = compat.inspect_getfullargspec(fn) + if spec[3] is not None: + positional = spec[0][1 : -len(spec[3])] + kwarg = spec[0][-len(spec[3]) :] + else: + positional = spec[0][1:] + kwarg = [] + + if fn in self._POSITIONAL_TRANSLATIONS: + positional = [ + self._POSITIONAL_TRANSLATIONS[fn].get(name, name) + for name in positional + ] + + # parse first line(s) of helptext without a line break + help_ = fn.__doc__ + if help_: + help_lines = [] + for line in help_.split("\n"): + if not line.strip(): + break + else: + help_lines.append(line.strip()) + else: + help_lines = [] + + help_text = " ".join(help_lines) + + return positional, kwarg, help_text + + def run_cmd(self, config: Config, options: Namespace) -> None: + fn, positional, kwarg = options.cmd + + try: + fn( + config, + *[getattr(options, k, None) for k in positional], + **{k: getattr(options, k, None) for k in kwarg}, + ) + except util.CommandError as e: + if options.raiseerr: + raise + else: + util.err(str(e), **config.messaging_opts) + + def _inis_from_config(self, options: Namespace) -> tuple[str, str]: + names = options.config + + alembic_config_env = os.environ.get("ALEMBIC_CONFIG") + if ( + alembic_config_env + and os.path.basename(alembic_config_env) == "pyproject.toml" + ): + default_pyproject_toml = alembic_config_env + default_alembic_config = "alembic.ini" + elif alembic_config_env: + default_pyproject_toml = "pyproject.toml" + default_alembic_config = alembic_config_env + else: + default_alembic_config = "alembic.ini" + default_pyproject_toml = "pyproject.toml" + + if not names: + return default_pyproject_toml, default_alembic_config + + toml = ini = None + + for name in names: + if os.path.basename(name) == "pyproject.toml": + if toml is not None: + raise util.CommandError( + "pyproject.toml indicated more than once" + ) + toml = name + else: + if ini is not None: + raise util.CommandError( + "only one ini file may be indicated" + ) + ini = name + + return toml if toml else default_pyproject_toml, ( + ini if ini else default_alembic_config + ) + + def main(self, argv: Optional[Sequence[str]] = None) -> None: + """Executes the command line with the provided arguments.""" + options = self.parser.parse_args(argv) + if not hasattr(options, "cmd"): + # see http://bugs.python.org/issue9253, argparse + # behavior changed incompatibly in py3.3 + self.parser.error("too few arguments") + else: + toml, ini = self._inis_from_config(options) + cfg = Config( + file_=ini, + toml_file=toml, + ini_section=options.name, + cmd_opts=options, + ) + self.run_cmd(cfg, options) + + +def main( + argv: Optional[Sequence[str]] = None, + prog: Optional[str] = None, + **kwargs: Any, +) -> None: + """The console runner function for Alembic.""" + + CommandLine(prog=prog).main(argv=argv) + + +if __name__ == "__main__": + main() diff --git a/venv/lib/python3.12/site-packages/alembic/context.py b/venv/lib/python3.12/site-packages/alembic/context.py new file mode 100644 index 0000000..758fca8 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/context.py @@ -0,0 +1,5 @@ +from .runtime.environment import EnvironmentContext + +# create proxy functions for +# each method on the EnvironmentContext class. +EnvironmentContext.create_module_class_proxy(globals(), locals()) diff --git a/venv/lib/python3.12/site-packages/alembic/context.pyi b/venv/lib/python3.12/site-packages/alembic/context.pyi new file mode 100644 index 0000000..6045d8b --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/context.pyi @@ -0,0 +1,876 @@ +# ### this file stubs are generated by tools/write_pyi.py - do not edit ### +# ### imports are manually managed +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Collection +from typing import Dict +from typing import Iterable +from typing import List +from typing import Literal +from typing import Mapping +from typing import MutableMapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import TextIO +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from typing_extensions import ContextManager + +if TYPE_CHECKING: + from sqlalchemy.engine.base import Connection + from sqlalchemy.engine.url import URL + from sqlalchemy.sql import Executable + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import FetchedValue + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.type_api import TypeEngine + + from .autogenerate.api import AutogenContext + from .config import Config + from .operations.ops import MigrationScript + from .runtime.migration import _ProxyTransaction + from .runtime.migration import MigrationContext + from .runtime.migration import MigrationInfo + from .script import ScriptDirectory + +### end imports ### + +def begin_transaction() -> ( + Union[_ProxyTransaction, ContextManager[None, Optional[bool]]] +): + """Return a context manager that will + enclose an operation within a "transaction", + as defined by the environment's offline + and transactional DDL settings. + + e.g.:: + + with context.begin_transaction(): + context.run_migrations() + + :meth:`.begin_transaction` is intended to + "do the right thing" regardless of + calling context: + + * If :meth:`.is_transactional_ddl` is ``False``, + returns a "do nothing" context manager + which otherwise produces no transactional + state or directives. + * If :meth:`.is_offline_mode` is ``True``, + returns a context manager that will + invoke the :meth:`.DefaultImpl.emit_begin` + and :meth:`.DefaultImpl.emit_commit` + methods, which will produce the string + directives ``BEGIN`` and ``COMMIT`` on + the output stream, as rendered by the + target backend (e.g. SQL Server would + emit ``BEGIN TRANSACTION``). + * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin` + on the current online connection, which + returns a :class:`sqlalchemy.engine.Transaction` + object. This object demarcates a real + transaction and is itself a context manager, + which will roll back if an exception + is raised. + + Note that a custom ``env.py`` script which + has more specific transactional needs can of course + manipulate the :class:`~sqlalchemy.engine.Connection` + directly to produce transactional state in "online" + mode. + + """ + +config: Config + +def configure( + connection: Optional[Connection] = None, + url: Union[str, URL, None] = None, + dialect_name: Optional[str] = None, + dialect_opts: Optional[Dict[str, Any]] = None, + transactional_ddl: Optional[bool] = None, + transaction_per_migration: bool = False, + output_buffer: Optional[TextIO] = None, + starting_rev: Optional[str] = None, + tag: Optional[str] = None, + template_args: Optional[Dict[str, Any]] = None, + render_as_batch: bool = False, + target_metadata: Union[MetaData, Sequence[MetaData], None] = None, + include_name: Optional[ + Callable[ + [ + Optional[str], + Literal[ + "schema", + "table", + "column", + "index", + "unique_constraint", + "foreign_key_constraint", + ], + MutableMapping[ + Literal[ + "schema_name", + "table_name", + "schema_qualified_table_name", + ], + Optional[str], + ], + ], + bool, + ] + ] = None, + include_object: Optional[ + Callable[ + [ + SchemaItem, + Optional[str], + Literal[ + "schema", + "table", + "column", + "index", + "unique_constraint", + "foreign_key_constraint", + ], + bool, + Optional[SchemaItem], + ], + bool, + ] + ] = None, + include_schemas: bool = False, + process_revision_directives: Optional[ + Callable[ + [ + MigrationContext, + Union[str, Iterable[Optional[str]], Iterable[str]], + List[MigrationScript], + ], + None, + ] + ] = None, + compare_type: Union[ + bool, + Callable[ + [ + MigrationContext, + Column[Any], + Column[Any], + TypeEngine[Any], + TypeEngine[Any], + ], + Optional[bool], + ], + ] = True, + compare_server_default: Union[ + bool, + Callable[ + [ + MigrationContext, + Column[Any], + Column[Any], + Optional[str], + Optional[FetchedValue], + Optional[str], + ], + Optional[bool], + ], + ] = False, + render_item: Optional[ + Callable[[str, Any, AutogenContext], Union[str, Literal[False]]] + ] = None, + literal_binds: bool = False, + upgrade_token: str = "upgrades", + downgrade_token: str = "downgrades", + alembic_module_prefix: str = "op.", + sqlalchemy_module_prefix: str = "sa.", + user_module_prefix: Optional[str] = None, + on_version_apply: Optional[ + Callable[ + [ + MigrationContext, + MigrationInfo, + Collection[Any], + Mapping[str, Any], + ], + None, + ] + ] = None, + autogenerate_plugins: Optional[Sequence[str]] = None, + **kw: Any, +) -> None: + """Configure a :class:`.MigrationContext` within this + :class:`.EnvironmentContext` which will provide database + connectivity and other configuration to a series of + migration scripts. + + Many methods on :class:`.EnvironmentContext` require that + this method has been called in order to function, as they + ultimately need to have database access or at least access + to the dialect in use. Those which do are documented as such. + + The important thing needed by :meth:`.configure` is a + means to determine what kind of database dialect is in use. + An actual connection to that database is needed only if + the :class:`.MigrationContext` is to be used in + "online" mode. + + If the :meth:`.is_offline_mode` function returns ``True``, + then no connection is needed here. Otherwise, the + ``connection`` parameter should be present as an + instance of :class:`sqlalchemy.engine.Connection`. + + This function is typically called from the ``env.py`` + script within a migration environment. It can be called + multiple times for an invocation. The most recent + :class:`~sqlalchemy.engine.Connection` + for which it was called is the one that will be operated upon + by the next call to :meth:`.run_migrations`. + + General parameters: + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use + for SQL execution in "online" mode. When present, is also + used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. + The type of dialect to be used will be derived from this if + ``connection`` and ``url`` are not passed. + :param dialect_opts: dictionary of options to be passed to dialect + constructor. + :param transactional_ddl: Force the usage of "transactional" + DDL on or off; + this otherwise defaults to whether or not the dialect in + use supports it. + :param transaction_per_migration: if True, nest each migration script + in a transaction rather than the full series of migrations to + run. + :param output_buffer: a file-like object that will be used + for textual output + when the ``--sql`` option is used to generate SQL scripts. + Defaults to + ``sys.stdout`` if not passed here and also not present on + the :class:`.Config` + object. The value here overrides that of the :class:`.Config` + object. + :param output_encoding: when using ``--sql`` to generate SQL + scripts, apply this encoding to the string output. + :param literal_binds: when using ``--sql`` to generate SQL + scripts, pass through the ``literal_binds`` flag to the compiler + so that any literal values that would ordinarily be bound + parameters are converted to plain strings. + + .. warning:: Dialects can typically only handle simple datatypes + like strings and numbers for auto-literal generation. Datatypes + like dates, intervals, and others may still require manual + formatting, typically using :meth:`.Operations.inline_literal`. + + .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy + versions prior to 0.8 where this feature is not supported. + + .. seealso:: + + :meth:`.Operations.inline_literal` + + :param starting_rev: Override the "starting revision" argument + when using ``--sql`` mode. + :param tag: a string tag for usage by custom ``env.py`` scripts. + Set via the ``--tag`` option, can be overridden here. + :param template_args: dictionary of template arguments which + will be added to the template argument environment when + running the "revision" command. Note that the script environment + is only run within the "revision" command if the --autogenerate + option is used, or if the option "revision_environment=true" + is present in the alembic.ini file. + + :param version_table: The name of the Alembic version table. + The default is ``'alembic_version'``. + :param version_table_schema: Optional schema to place version + table within. + :param version_table_pk: boolean, whether the Alembic version table + should use a primary key constraint for the "value" column; this + only takes effect when the table is first created. + Defaults to True; setting to False should not be necessary and is + here for backwards compatibility reasons. + :param on_version_apply: a callable or collection of callables to be + run for each migration step. + The callables will be run in the order they are given, once for + each migration step, after the respective operation has been + applied but before its transaction is finalized. + Each callable accepts no positional arguments and the following + keyword arguments: + + * ``ctx``: the :class:`.MigrationContext` running the migration, + * ``step``: a :class:`.MigrationInfo` representing the + step currently being applied, + * ``heads``: a collection of version strings representing the + current heads, + * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`. + + Parameters specific to the autogenerate feature, when + ``alembic revision`` is run with the ``--autogenerate`` feature: + + :param target_metadata: a :class:`sqlalchemy.schema.MetaData` + object, or a sequence of :class:`~sqlalchemy.schema.MetaData` + objects, that will be consulted during autogeneration. + The tables present in each :class:`~sqlalchemy.schema.MetaData` + will be compared against + what is locally available on the target + :class:`~sqlalchemy.engine.Connection` + to produce candidate upgrade/downgrade operations. + :param compare_type: Indicates type comparison behavior during + an autogenerate + operation. Defaults to ``True`` turning on type comparison, which + has good accuracy on most backends. See :ref:`compare_types` + for an example as well as information on other type + comparison options. Set to ``False`` which disables type + comparison. A callable can also be passed to provide custom type + comparison, see :ref:`compare_types` for additional details. + + .. versionchanged:: 1.12.0 The default value of + :paramref:`.EnvironmentContext.configure.compare_type` has been + changed to ``True``. + + .. seealso:: + + :ref:`compare_types` + + :paramref:`.EnvironmentContext.configure.compare_server_default` + + :param compare_server_default: Indicates server default comparison + behavior during + an autogenerate operation. Defaults to ``False`` which disables + server default + comparison. Set to ``True`` to turn on server default comparison, + which has + varied accuracy depending on backend. + + To customize server default comparison behavior, a callable may + be specified + which can filter server default comparisons during an + autogenerate operation. + defaults during an autogenerate operation. The format of this + callable is:: + + def my_compare_server_default(context, inspected_column, + metadata_column, inspected_default, metadata_default, + rendered_metadata_default): + # return True if the defaults are different, + # False if not, or None to allow the default implementation + # to compare these defaults + return None + + context.configure( + # ... + compare_server_default = my_compare_server_default + ) + + ``inspected_column`` is a dictionary structure as returned by + :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas + ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from + the local model environment. + + A return value of ``None`` indicates to allow default server default + comparison + to proceed. Note that some backends such as Postgresql actually + execute + the two defaults on the database side to compare for equivalence. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.compare_type` + + :param include_name: A callable function which is given + the chance to return ``True`` or ``False`` for any database reflected + object based on its name, including database schema names when + the :paramref:`.EnvironmentContext.configure.include_schemas` flag + is set to ``True``. + + The function accepts the following positional arguments: + + * ``name``: the name of the object, such as schema name or table name. + Will be ``None`` when indicating the default schema name of the + database connection. + * ``type``: a string describing the type of object; currently + ``"schema"``, ``"table"``, ``"column"``, ``"index"``, + ``"unique_constraint"``, or ``"foreign_key_constraint"`` + * ``parent_names``: a dictionary of "parent" object names, that are + relative to the name being given. Keys in this dictionary may + include: ``"schema_name"``, ``"table_name"`` or + ``"schema_qualified_table_name"``. + + E.g.:: + + def include_name(name, type_, parent_names): + if type_ == "schema": + return name in ["schema_one", "schema_two"] + else: + return True + + context.configure( + # ... + include_schemas = True, + include_name = include_name + ) + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_object` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + + :param include_object: A callable function which is given + the chance to return ``True`` or ``False`` for any object, + indicating if the given object should be considered in the + autogenerate sweep. + + The function accepts the following positional arguments: + + * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such + as a :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.Column`, + :class:`~sqlalchemy.schema.Index` + :class:`~sqlalchemy.schema.UniqueConstraint`, + or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object + * ``name``: the name of the object. This is typically available + via ``object.name``. + * ``type``: a string describing the type of object; currently + ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``, + or ``"foreign_key_constraint"`` + * ``reflected``: ``True`` if the given object was produced based on + table reflection, ``False`` if it's from a local :class:`.MetaData` + object. + * ``compare_to``: the object being compared against, if available, + else ``None``. + + E.g.:: + + def include_object(object, name, type_, reflected, compare_to): + if (type_ == "column" and + not reflected and + object.info.get("skip_autogenerate", False)): + return False + else: + return True + + context.configure( + # ... + include_object = include_object + ) + + For the use case of omitting specific schemas from a target database + when :paramref:`.EnvironmentContext.configure.include_schemas` is + set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema` + attribute can be checked for each :class:`~sqlalchemy.schema.Table` + object passed to the hook, however it is much more efficient + to filter on schemas before reflection of objects takes place + using the :paramref:`.EnvironmentContext.configure.include_name` + hook. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + :param render_as_batch: if True, commands which alter elements + within a table will be placed under a ``with batch_alter_table():`` + directive, so that batch migrations will take place. + + .. seealso:: + + :ref:`batch_migrations` + + :param include_schemas: If True, autogenerate will scan across + all schemas located by the SQLAlchemy + :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names` + method, and include all differences in tables found across all + those schemas. When using this option, you may want to also + use the :paramref:`.EnvironmentContext.configure.include_name` + parameter to specify a callable which + can filter the tables/schemas that get included. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_object` + + :param render_item: Callable that can be used to override how + any schema item, i.e. column, constraint, type, + etc., is rendered for autogenerate. The callable receives a + string describing the type of object, the object, and + the autogen context. If it returns False, the + default rendering method will be used. If it returns None, + the item will not be rendered in the context of a Table + construct, that is, can be used to skip columns or constraints + within op.create_table():: + + def my_render_column(type_, col, autogen_context): + if type_ == "column" and isinstance(col, MySpecialCol): + return repr(col) + else: + return False + + context.configure( + # ... + render_item = my_render_column + ) + + Available values for the type string include: ``"column"``, + ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``, + ``"type"``, ``"server_default"``. + + .. seealso:: + + :ref:`autogen_render_types` + + :param upgrade_token: When autogenerate completes, the text of the + candidate upgrade operations will be present in this template + variable when ``script.py.mako`` is rendered. Defaults to + ``upgrades``. + :param downgrade_token: When autogenerate completes, the text of the + candidate downgrade operations will be present in this + template variable when ``script.py.mako`` is rendered. Defaults to + ``downgrades``. + + :param alembic_module_prefix: When autogenerate refers to Alembic + :mod:`alembic.operations` constructs, this prefix will be used + (i.e. ``op.create_table``) Defaults to "``op.``". + Can be ``None`` to indicate no prefix. + + :param sqlalchemy_module_prefix: When autogenerate refers to + SQLAlchemy + :class:`~sqlalchemy.schema.Column` or type classes, this prefix + will be used + (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``". + Can be ``None`` to indicate no prefix. + Note that when dialect-specific types are rendered, autogenerate + will render them using the dialect module name, i.e. ``mssql.BIT()``, + ``postgresql.UUID()``. + + :param user_module_prefix: When autogenerate refers to a SQLAlchemy + type (e.g. :class:`.TypeEngine`) where the module name is not + under the ``sqlalchemy`` namespace, this prefix will be used + within autogenerate. If left at its default of + ``None``, the ``__module__`` attribute of the type is used to + render the import module. It's a good practice to set this + and to have all custom types be available from a fixed module space, + in order to future-proof migration files against reorganizations + in modules. + + .. seealso:: + + :ref:`autogen_module_prefix` + + :param process_revision_directives: a callable function that will + be passed a structure representing the end result of an autogenerate + or plain "revision" operation, which can be manipulated to affect + how the ``alembic revision`` command ultimately outputs new + revision scripts. The structure of the callable is:: + + def process_revision_directives(context, revision, directives): + pass + + The ``directives`` parameter is a Python list containing + a single :class:`.MigrationScript` directive, which represents + the revision file to be generated. This list as well as its + contents may be freely modified to produce any set of commands. + The section :ref:`customizing_revision` shows an example of + doing this. The ``context`` parameter is the + :class:`.MigrationContext` in use, + and ``revision`` is a tuple of revision identifiers representing the + current revision of the database. + + The callable is invoked at all times when the ``--autogenerate`` + option is passed to ``alembic revision``. If ``--autogenerate`` + is not passed, the callable is invoked only if the + ``revision_environment`` variable is set to True in the Alembic + configuration, in which case the given ``directives`` collection + will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps` + collections for ``.upgrade_ops`` and ``.downgrade_ops``. The + ``--autogenerate`` option itself can be inferred by inspecting + ``context.config.cmd_opts.autogenerate``. + + The callable function may optionally be an instance of + a :class:`.Rewriter` object. This is a helper object that + assists in the production of autogenerate-stream rewriter functions. + + .. seealso:: + + :ref:`customizing_revision` + + :ref:`autogen_rewriter` + + :paramref:`.command.revision.process_revision_directives` + + :param autogenerate_plugins: A list of string names of "plugins" that + should participate in this autogenerate run. Defaults to the list + ``["alembic.autogenerate.*"]``, which indicates that Alembic's default + autogeneration plugins will be used. + + See the section :ref:`plugins_autogenerate` for complete background + on how to use this parameter. + + .. versionadded:: 1.18.0 Added a new plugin system for autogenerate + compare directives. + + .. seealso:: + + :ref:`plugins_autogenerate` - background on enabling/disabling + autogenerate plugins + + :ref:`alembic.plugins.toplevel` - Introduction and documentation + to the plugin system + + Parameters specific to individual backends: + + :param mssql_batch_separator: The "batch separator" which will + be placed between each statement when generating offline SQL Server + migrations. Defaults to ``GO``. Note this is in addition to the + customary semicolon ``;`` at the end of each statement; SQL Server + considers the "batch separator" to denote the end of an + individual statement execution, and cannot group certain + dependent operations in one step. + :param oracle_batch_separator: The "batch separator" which will + be placed between each statement when generating offline + Oracle migrations. Defaults to ``/``. Oracle doesn't add a + semicolon between statements like most other backends. + + """ + +def execute( + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, +) -> None: + """Execute the given SQL using the current change context. + + The behavior of :meth:`.execute` is the same + as that of :meth:`.Operations.execute`. Please see that + function's documentation for full detail including + caveats and limitations. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + +def get_bind() -> Connection: + """Return the current 'bind'. + + In "online" mode, this is the + :class:`sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + +def get_context() -> MigrationContext: + """Return the current :class:`.MigrationContext` object. + + If :meth:`.EnvironmentContext.configure` has not been + called yet, raises an exception. + + """ + +def get_head_revision() -> Union[str, Tuple[str, ...], None]: + """Return the hex identifier of the 'head' script revision. + + If the script directory has multiple heads, this + method raises a :class:`.CommandError`; + :meth:`.EnvironmentContext.get_head_revisions` should be preferred. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: :meth:`.EnvironmentContext.get_head_revisions` + + """ + +def get_head_revisions() -> Union[str, Tuple[str, ...], None]: + """Return the hex identifier of the 'heads' script revision(s). + + This returns a tuple containing the version number of all + heads in the script directory. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_revision_argument() -> Union[str, Tuple[str, ...], None]: + """Get the 'destination' revision argument. + + This is typically the argument passed to the + ``upgrade`` or ``downgrade`` command. + + If it was specified as ``head``, the actual + version number is returned; if specified + as ``base``, ``None`` is returned. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_starting_revision_argument() -> Union[str, Tuple[str, ...], None]: + """Return the 'starting revision' argument, + if the revision was passed using ``start:end``. + + This is only meaningful in "offline" mode. + Returns ``None`` if no value is available + or was configured. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def get_tag_argument() -> Optional[str]: + """Return the value passed for the ``--tag`` argument, if any. + + The ``--tag`` argument is not used directly by Alembic, + but is available for custom ``env.py`` configurations that + wish to use it; particularly for offline generation scripts + that wish to generate tagged filenames. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` - a newer and more + open ended system of extending ``env.py`` scripts via the command + line. + + """ + +@overload +def get_x_argument(as_dictionary: Literal[False]) -> List[str]: ... +@overload +def get_x_argument(as_dictionary: Literal[True]) -> Dict[str, str]: ... +@overload +def get_x_argument( + as_dictionary: bool = ..., +) -> Union[List[str], Dict[str, str]]: + """Return the value(s) passed for the ``-x`` argument, if any. + + The ``-x`` argument is an open ended flag that allows any user-defined + value or values to be passed on the command line, then available + here for consumption by a custom ``env.py`` script. + + The return value is a list, returned directly from the ``argparse`` + structure. If ``as_dictionary=True`` is passed, the ``x`` arguments + are parsed using ``key=value`` format into a dictionary that is + then returned. If there is no ``=`` in the argument, value is an empty + string. + + .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when + arguments are passed without the ``=`` symbol. + + For example, to support passing a database URL on the command line, + the standard ``env.py`` script can be modified like this:: + + cmd_line_url = context.get_x_argument( + as_dictionary=True).get('dbname') + if cmd_line_url: + engine = create_engine(cmd_line_url) + else: + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + This then takes effect by running the ``alembic`` script as:: + + alembic -x dbname=postgresql://user:pass@host/dbname upgrade head + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_tag_argument` + + :attr:`.Config.cmd_opts` + + """ + +def is_offline_mode() -> bool: + """Return True if the current migrations environment + is running in "offline mode". + + This is ``True`` or ``False`` depending + on the ``--sql`` flag passed. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + +def is_transactional_ddl() -> bool: + """Return True if the context is configured to expect a + transactional DDL capable backend. + + This defaults to the type of database in use, and + can be overridden by the ``transactional_ddl`` argument + to :meth:`.configure` + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + +def run_migrations(**kw: Any) -> None: + """Run migrations as determined by the current command line + configuration + as well as versioning information present (or not) in the current + database connection (if one is present). + + The function accepts optional ``**kw`` arguments. If these are + passed, they are sent directly to the ``upgrade()`` and + ``downgrade()`` + functions within each target revision file. By modifying the + ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()`` + functions accept arguments, parameters can be passed here so that + contextual information, usually information to identify a particular + database in use, can be passed from a custom ``env.py`` script + to the migration functions. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + +script: ScriptDirectory + +def static_output(text: str) -> None: + """Emit text directly to the "offline" SQL stream. + + Typically this is for emitting comments that + start with --. The statement is not treated + as a SQL execution, no ; or batch separator + is added, etc. + + """ diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/__init__.py b/venv/lib/python3.12/site-packages/alembic/ddl/__init__.py new file mode 100644 index 0000000..f2f72b3 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/__init__.py @@ -0,0 +1,6 @@ +from . import mssql +from . import mysql +from . import oracle +from . import postgresql +from . import sqlite +from .impl import DefaultImpl as DefaultImpl diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21ad7f1e5936dfa4b26c1bbb8a6808b147dc5724 GIT binary patch literal 410 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 xf0EzEADHjD6tB%wJYDdGN6bU zyJR8~JBB7MbYgc%$J2;9ZE!nnoSAmgzxw<~i*Rrcy4WYC-m>#9QP}F(I1P7@JnMRj=RB$oXCf{ zao*4Kgd4&JzX7Q+Y#cZFO^jv&ZuXlQZU%1gTNrKuZuMIkZVe0LHouMG0&u(E&Tt!W zhu_I?d)PJZ_PZJG2$zg|{2qooftUJA8SV;~jhFk&8SVyN;jdtL3GhmPCBr@8s_|-n zHSkigEL<~Q>#t>aIq*7v9m6Yt*Zb=kUJ1Ox-@x!H;En!9o|7zc8^&3U{x$iVkjIHN zz?=Qe46hA)$6Nd@46g&;>ThLueR$n?o4*ZsgV-2uA7AfZkG8B*wX{L1l*%PjE!aE8 zpufYw4RT`B6;5oHO5ZoCeE0hp)eCBi_73a_}CTG`)UT~-_A;I(MyU)#2q=gkFngXncOGDojJKX zSZy)2TzotycQ?!J(R1^@G9>QFDfu`n zxw){UctZS2PN_YtRPTyXPl|hUN_{2oJ5P!Gu9*CLb7_Q&C&m4s@5`l&!(tz3`}1fc z;sMb5#HYoBNDt)Zv39;H_Je*9Ek2W#rr zrz6Ot!qSCNB-SI5m~=*xDJynyLQ;STqtWq+P?+JCgVFHhI90Is_a7SwOXE@`hBuc= z&k`IjPex;s7>ESNB`Q54U5FhF2PYMJa~_gnqc2I~SA*e6={E+o`_+L_*0vHDi^{+w zN=y!h)Yb!Ip@^vGqX8i>b}pbyNYrI_AfyDSuYuFi$%qI_Wgr$j9hO$MBOJs?CebZw z;)sZ@hhmIHM8^n;?!dUTSK@+w1T7Cl&V(Y;Z^*`2327`i8IBzpp9qf<@AXIXLj2Nt zP;PJ%=jTNZ9-sFcA#al|JK$!~2h%1Mjg=`)uioJz~S+rnh_miWf@yo z^^oF~rI#l|vLtqSp*7y1cWg2o_Ofnxqo>bGqcQDEL!;Wb*f2?0c;}bAY>dxK!UF2@_Lq0>wu1bS?n6jw7@)=VmMGB9}>rhK>15oHA7}vvzo|mHIQcv)- zI2jI3g{FG?CFNW!I?*#Eo%cQ|N2zB@&qVOzxD-%cicW;aE(R1yekC+2^}HfQUg-&k zPWMb)jJ*_%Z0+vd(xZf8(x!>v=(*q-N$CkfV^4=hdqgqZb2_L<-4hp~Oo31&6bl5V z%Cn;A)`?rFb7cURxwM_DZ=W%}QIf1`m@&QSSOS%kTLF9)HjQ84PPV)<8gvjs^nq z27L84CwEW}NZn+w4myD-n*gqGiw3hF>;msM4RbzI%5hSnzB9!d z)tX~%63#T~F(7aAH@Fxh6S!)c=H4_zpZICxkNEdV=kV~Veraz^zYOqmjNecwvobj$ z$&4{6cSJg`IRW~(&n&m2fs{KM7IVn*4iH)Olr0b#4F?q^5K!=4Qbg}%m9NC;sfwII zWHe(Ba+GF(PcL)JPR{1OYK;puH=FKQ=7gGi!j5E9*No{Sp7S1qIPrIN|-?UL3t=;cmLJ_Y>@*{M?y^%SCS`?ldmMY ziWDk~PS?{%DGxJ9b#56I(7OI)l;<5#3Y16qEXp%&daW{M)tft;+qOoWHtB&w1a2IT zjTD1D(`LMBp+1zUV>ZFwNWHK;qv)S3ED!+*=LGGh| zv=QhhFhJlCfk6UHb!@=M0O-jxf*fIFjDV^sF947cRAh{x;?@{5f=$W#^)se>LJhRb z(>Pbsv25gAl?kUe?)2Vjyyxs$D6LFeIh*UAPzPme-AaXi0+4HT`-n(@n5%>VRHJKYPj+ufc5P1X?@xAir%TJty$kNr zv>7Q_R8^a{5(WcoO4|stb2VLQ2VqXGp)>6w%+0m#O_vbn;p#hB8|gAK)N;@kIRMpA z>GOGp3U^BnzqA>!xS^^BDH=3i?#%J!#*|yN#34-y^V~UEhlASAxbsXPa259QCM*{2 z+%RScanq*v`0wy==SJ8{jyqSUQe%#-2)e#wf^EkKRM9i3pTKL%aPGXSvt*5=uXzev zo!{Mz&i)Y!D_#K7SxZ)DZ+zZ}eiYJ%7N`$ABbyP;S!v4$kR=}kP}d%Tl|p}-N<2w`d8(pPFV7Av zBk?g*QOIH+De;dh8xxhjc%^T)eZCT2=S}-+!e{+xpnk0328O8&0SSM(vhUSo->b>K zH@RgG$-Y;UJ?t1Ss_d!$7a{v0ObEqg|9RT56|oo#!d95@bHtp_CQa&UMw$`STk--t zQku!?CWz3vnh4p)+0?Oy2%n)|5*Wu{t_Yho5jJZgY))?5K_YC{MA%FsY*s}`^*>h; zY71^{a{5A~csthiA0v!O4m>fwaQ)QV*N^kMhS|U14?Gktme{0y#O&Y<3gj3gElXHflouB@6&*`P!rwwAWdi_NrRR}W`GzxD zQ3Ee)Qx(BpG)gK^1pULOWY*Cp>g^g(z2QhUcG9A-F4?k0T_m<{qs5`>k$on%HgxAGA$9Me zLlwaD*a||OJOx;)^4C}h}X$N8Ix{fe6*SL|@OnXRYOF>)Y092i&FBaEX+R`c1*<$+o z2tBp;%h3r|QOX8QRJAZK;Loa;f>Nsm z{Mr6zbm}&$D5R>SQ#p}Ep-$Dd!h^Y04`#sTLarBM9f;DmnUWtNjCJL+D9OuIn!qgn zlmP%$NwEHJ>qt9n=5s7aOLcy2rxE8IuFZA@7u z^*k_hVkk37>PI_lH#$}JN(e_Xm}h8Bs5Sm|boig}r|brR!P+@n#kIpX&m@{Q$D20a z+3=I@54t}R`aTwFZ+6@oOtkHex9$FL@Ml9m9l9qRO`DBQOPVv9EzCIb*lk05jNShT z0`&HoWldav^Rtm z1p;xZu1yVv)iV*=&n+U1N>vzY$$-8cEzh9z0{)bz0ao)^71y4n$)u2S=#vY!rDfu65s_&2p^1b}&*X2Taa9|^lY z7TEg_13w7d6Sl9$`OG~2ANar@LiLOLqu1&o6BoL~9%d4&$RukLE4-JR+*SX;p}wb& zjle>toXnePgjMWCW*b74ngsJtf{R#AMV>R$c687lTPocHWXZL0BS)S^*UcLJ2 zTuEmdr@D>znz!9~_QUf(d-bQU&NrWkJ5QuJgUgFeRC00xoSc&@^QO&tw3S9iOBrTl zT|jb0T`W2ldHrnZb)F*F7 z4h{?roa`GJ=*MNNXZoHS_Vx{Td1Fy(U`&pV(=9ChJRNoE>YEo=r|7~IZktR*W!xp} zzHN}nGo+kYkkBo%0a=d9MYx}tO4QEi@xa@ti#xoW(g+0X7dVGIVXuqZ>(U0^Hoz|# zjSeudXngzsV%DFi%<9Ur3ZuroqtMtz#oKSYH?H$KGYuhhMDz1%u`)hO9pL2QMdiLtGvlTzCyIptZ;QP%#uiAC1 z@A~A;r{2DBw`$kCYxm_tNk{4B6mtXM zJC!eZiXBGlyas0ta!rip753ofqEVN&$WMU=*%+rY?u97B1vIv3mj4VnSS;JdCL^OU z+y+om)`_4TgF>eS+~ow0OKI<-5aS;Beu?nI1lkBtRzA~Z`WORU#A_He|zk57hvUg6{ z`^$`MR@wpaobj195pU3H6lvjeHdiuQW|lX@+EKnieh+zUKyZnfVJc)!nm);IQV9Y( zX=wF8aIMn9IV%#5hPb04;b@IJT5oNecXZ5|I#%|Utc&%Q&Kp^8(M;hJ%k&nTBPKm} zn*WhO-!{o;fnnNk+3*DSIu>&*T9W@9#c{>goj;Z0c46%qDWjmi_)OFW=6De%C_mDeZAw#Cb~eJr?VlCEg22v zmdi(pL)CMR);UuvVA4@FXR2Ds92z!b&M_d56lKP_p^)|zb!8dV*Tswd9nDRrToiPJ z8*I9hq407P2L_5ylRCxic~but2r_}AG~#9;m${^)JmIL1JL(e-U)Lk9_?)dXcCkp8Ek#-Cc9uK${xp^yJhLfXNEr*v7Y`mbwHM5yK=fQTO$qWjd#}$xd0?*I@h+D65 zivm~MdewPLo)dg(&^>Zb=v-~%jGQuKw-?AtcI&;*GP^`&w$}Z2MgChZVSPZPdL zR01zj?<#?iCN$h@?!D9TVgGz{U)<3*XX;Z~n>)jjIz7B&9()3+9U0sS9_Q2kiRf(M;~p! zN1MXxeH88QnpK?=9cLR?j)q;dbY2mlVRRf`@bq)AQhh&-(SR8}QlLLnaa0+m6 zVAwll@OC`peQbYi6y}$fBeHHRLw)Hcab03_MDL-(uE0m?^I)U)4F?4 zI64{(D|@>$+1JR$w4m8fe@(}L4>&`mCF$EbBP4_xx~N~55ZbUmT50(mbD@7+-^BYo zshXULo_=8z8VilG-&Ca>IS1h>V_1r$oLG2Ep)(OW8dp-LFz#&2AL4^4tGZTH*k&(o zGt!l9HQalTvbCsM{Vj|d9J1-?`;>|M;rdUX6rWR#D9$0HVC$b9t)xsb{H`ix7(17; z;^dwJl$4DS^^5DMJu_=hp(y1=Z}w4t8}J%6Oh6=NMF2jJaxodb7;9c=2j+`otgK;6wT9Mjxw+ME=e*W7Ud7w;b6z_spUXUR27!m}>! zSvT)&o8{-7zK_eRZ(0&H8{;(_=gYfhpNg0F%(;4IP68yI?zfI#JwE4br+S7PuIsLZ zr#7H$qDq7icOCT$o|=TGCGKfSc)H`B z?%9ipo_+D2eII%DC$l*l;+_q&wjaB1yYG2u0Cn!GPvD%jt~F8H8L#b3)NYQ~ZoX6d zlco=v=47xnQ~TPKV6Nw6V7#U=ek>iKRot>V;?zt(Lkwvku%!tGbXfxHP7Lz zhnJX&?f6P!$C3DsBg>>)Ah|4;2No@y;35tguSwS(aiM<3wBRVYe3VUe*q@#zIl!hk zRo)aBs~2N@!1Vmk8u>4;k$-%R{NXk7pIIaSg-7L^)bX9p%SSZBMlI!2E$Z56H&NdO zm@3Vzth%*@mC6|^f%7jGanmf3Q?HF)I|*zgz)D(jILnM}m3#`|<)fMUnhe;Nm}Drx zl^gQ&wVtqi);`rzZT?7JN!8&MFV2FxC-RCz_}LF<8)gGz9W&C8A~Zk2zRsKlySEUg zmRNJ+87z{)vP}%qFV4m$@oO~v;!H+>#mRpGkg_1o$5xO`4qv8dOr{`6rdTXx$1hic zVfu~XMYjFLRzR|y@()mcbvC~nKB`+^EyxbaEzQZ*+#p*rjidk>~mU7C3(0uQ<h5z*_qmSyT-SZB zjUm1Fxt{x6H~xRZJ;vx8?sJ>&bK8Dt^n7Akcc1=mzHi!k-?ZfuQ_DhS-OW9-<+Eo$ z-1Kk8`0itK^~d6s$LF}RWs93{yjHi&Az5w}_)XV3mpLTMRTX?M;YgOd&3wzX=a)Go z%N{f6^@R5l-mK!y%N`r>qsttUg$0!Om!aqzW-BUx^6^V_f4>08^ETkZTh`Yw{? zI%-EPzv$r%_L+_Id{x?D;Vns5P1;ClTMtVY%)$dR-j|G=p_-MzY>EoLdA{O-!J%eU zrU_eYpp4FWzVZRJL^ael)f(ONd~MobrBBqSjg)TK!cz7Lyi*125d&MS$hKt8v;<7; zton%}8Jp($8rGGht1fLsmy+vyS;{`4brzk_J3D7@p69*VxLeh6ckg;YC^ebOh^T`kOVGk#Ghl1b%3up% zThPW}D`0!j&R`p0N6^7wJ78zf$zTUySJ1^^C*YD`34>jL-9a~lO8|R<9tOJsmj+82 z>;dczdKp|A^G*4Keg=C1mj%lh>;qgLEN8GEa7D0!!DWCegOv;}2V51bVsHiE>R>g4 zD*@L8YXDdA)v?;Cx?mlHYXH{=>ls`dtD9;FHZZsja3B}}T+cTkPGhiI`-=xGmN-)gA0+ za690hU=M>k0QUxa8QclDFWAT6u2}!nw%|4fcgF^%wg?qTpYzF+V(@Oj$AibwFWr&eq=n-mJvleCOkVEoxTLnJ{q2i@aN`cSt9G%S;{jUqlB+S zzBm_|9;eIBM_F(HtMY9GRjR=2sGeR72A_rS^%) zwZt!|=P9>PcrGz>DZ-kI+@Wzbvrs%r&GQV+OwERcXnbb+9yt+_<)IpPf%!o|)d!H?X}wj;Y@}8y>$B zz7&b~V~$Tb97fs6Ql&Thlt!F}cYG(D8{AVT=c~BWd%JhVu=47f zX>D%ZdvMW`byO~&NIM#{-F&W^0Re6i#S(s84mX5kp#q5-%-7y^{6 zWVq%$&71S`9HN59#)RVtepW*0?L1=l1c3X-Xp}>2YNJo>q0rO}Iwb>U)E3Pf)?HqvGP`$ zQ9Q^R-i}`m_RATx^Dg`Jrbybr%U%5N5DnD@i4G~(r7;L9{Q`}lIc9NVXVvP&*Z^HR#6Flh6VVE!au z@ikMh9OaMXL-|vDC44K8tATQ6|ME~dU-gbNANx0f_!s%=ubG3DYVI}f7@waHf10mF z>?#(!?wtb6BcJTNn%X(CIQm|bG6)AimL;O&*t#@b(-Vl#jb9Fg<17{0 z6Of3A_IQBx2pZWwa{R>Txsh|P21Z6-Iq~wj_CP!lnWa2gnu~L>E9evKa|xxNL<=v` zAj8je`$fy>XV08?d1MH5+=-!)Ge=J+{nAbY5|{Rqv7SI*U*DMMqTMzWpP!zYo}Y>f zXMn(xE`=m!5k(J+5*99nK*|VVkc4{CG$Zg4LA1~FY}}}A2|)L z$aS%ICt(fpjK8|x-6f{nVXd?D3nDI-^Yr*&x%YqR;RyCZPRAI#r+iBARH>u{I z_m7t)e9Es$zXIjoyy1PhMZcq)=jORimk03w|ZjJ-sBiFT|CnTQw7QXJ86DUuL^sE6o6%GtR@=;9nr zXVEwnnVJ!uNJ3O#y6bV>K44* z+45Sv9ND_gOkHofu6K3j?%aCaiACdmYxPE57kg)`DUK~$+d)68vX0u6qa)ioxOn`& zvuVTO&N%AsIqI??+V+2I|HJa?OnG~{ygln5d1BC4cypY-)SEMK{suVCdPew+tJ4jY zBA89p4ggvrdO%;wJIl{M^u=;(iA! zuUJ^o6M~M{f!D<;Gg-QH1PQ@EOFe(g@1^ovm_0(lrFbE9`rb(NUWB#2$>C%IfneJ_lH-AU}fknZ4O3oM+ z6V7GU@L!^+7b4Si#*75Qfqm@UweLb7JJMbV%v_|?Q{d|5D7K@k(O3){G2tE_=!Ld; zJ6pSOfY`Co&I!`4Mw|ap>EIN=**6YUfsGFg8F|a5ROc>71)lAGw0kmELL4N*d|(DC z1c`)*#simUX08OVZ^}D&AUuuD9K4BXRQS22Bi*K!><_y#v#*K1e_mq zX&guy^a(l$K=0+57^85;89PWWE1&)J+|-2&nojELQ~EE=^AUdEzQ91=z)p$op>jlr zQZc0bMF0ao49R=jXe<*KNEJrSqvlxnNsGjCx`BDs?F+O@hh z{0_2+e+~}L)@@vgFYBy&==6W{)I+Ct>EQRoD*zrT#AB;ke_Zvm^K&G*>!bhF2)C@BbZsVL|AXqi`Rjrx`$eO13~|g-9H$MN`r+qyJ>|D_SoSw+d820S>N~7g{`|GzmPO z5Q~~@(+=|U&rP$nKU>m`lOM;gWtL*n!))$aGU`7JB1De5>d|R|1px`gUjD!}E z4U+T1%&dF{A+|2g0}?V}qplV!*M%}dZ4igySIT7uBwidyh9L=sh^Mg{l}5oJtxdZ$ z3bcqs6U#eJJ%xCTq!RrY##sq(h0ttyBm&xzC>G=2A&)0SgnUKf6OF4o8T<%l-A_C4 z^C*W`IAH?8Ks-eMy{BW&rL4oU%$J*XX#q zVIr8Y3ai9_hiY!xYE_y`+4uxUezRTBy)QTBIuSOM&!$KC*9(Rl`UN9Wsaq_srefp} zZS3&wcY>%j_V9Y%5Y=JJS88XP)&F5 zZj4ydgV>lP%)ja3Z9I-TlZ5a$JyG3)>HE6h(_P1LXR>BfAb#damj$y@yUDt(@pv<@ zk{Xi@Tf-H?2c-nIh8B{I^F%l{ZH;x^vS5*kQoZ0sTP>39mg~+vvEik_YhsE~r4UfMEa}L}xSk zV8+f{=ZNu7moDI7gsxo05jxJO|7SW|(nw1^xm`N$0S_};DLe(*1>h8}5~8dS{7$gV zgrSN=bSffY2ft{Kg7g5@3s#A660Kw8GCPKeHe%AUgBq)bP=R344xWj8X(Ael+b(!0 zRWF5EllA_|@VLLiE!q9D&^1h(+S(g?eL0S!n8NW9{rMe>St zS|uE2Q&}~_Ey~-WX`g78+DRu@rK3cv4D+~P!+ADNhj8jIdL*=G0y82qo-Ea37v}W< zQR7Fk#6U@uaL)3j==Xj19jy;5YnNYr+r2oFb+~Ws|K0srcXi6$_Fmh=@`hC4Vta6b+U_uP#ibY(qN?+j*YdeSvLpeDLYo|?J(rnUYLccunT-me?EZQrPCShD}z zUHh=2ai#NvvHKPKQ=a{)iv8J&j-~lbMMs+cPOtfYT>GQiRK@8JtMO~`WY+1)IP25S z`iCCh(#bbn8Bc54)4Ia14zGK5WITJ*p1tdy{aJ5$#@m_pc0M-fO1qvK3?O8zoWF7d z2iU{64=*0aNjBwcPC1+5{=^-~R`f0TLcUw!BXNm(*< z(o=Q`BJptC?o+{uluin4;?t2q^hB=V(ikK>U6L{% zm+C{w#R~OjVG%U{<&+9pE|#la1*MJC#!U}x9rPb(Io*>F+4iiua zJgyH^q=i$S;VV5D1?{#;`Qyo-H;Zwwq+7lP%YtLUx!_u`V^g9fhDSl1$lJ)9=5)f> zu|>J_r_M=r+mqsx3!a%%8F8UBp>DXvHpc6o=>wYF2nB6=3UVvg4u`P)7vf77o~cOv&TJuGjNk~cUFG9M$~D~ywA9Zeo9nComCE4~b*W6TsW z%CYZ(3I^F4I5UrZ^%t~2uxHw_t&LxaMP84@I!$cd3g1PDutLteOC&*!QRM_p6o^>EE{yYNBNUA@S&2EE*Tk|R>`MemDP`yXZ7RyFt?ss7tSK&{{CeUGwHfu;VFc@VqI~5xixZgB&XMv_H5MD{?bARb35nsryLE~5O1Bj zdFml{UQct{)139!XZ_f(YO^(s*{1$%&rr5)ceZuc(^4B9=^aQ}$vLZ6%F>RuY^iVY z*i#R7vy6K%?HQjN;*seXaU02p-DhARO1GkM2OKUTwd(x$Q)CgW}ge7CPu5n4f zH25w1M-?@hiq3RJC;X&}{lw!}s??OT*uf{ZaLcM%}UZup(~Mjb!#IM zfzD2hO!zzic6`K~*!<+r9L26u7}_ylD=;mXKhV7|Zz)=&0|v72Pmn1{iu~%f9p&X^ z+G$~{M)(n;h-PWINUP${;UoN*oV#$g+);@3Jewq0UNF^F_Q{uMqW2*+Cc0lHM`(_Z z-W<((s|smtOj`K7^TvrFBqXVnmG=w_*l)xHBEz|l6$$bxeK z{_FY<{YImKXLG;(bNChXoBcSL84?DPd4jCtgug^Ysn`AmL4+-~aQhs@2vKlDg|@>q z0dG=|wIVrs%&Y0Kvp3J8$Cigz2k*X$bxR#(lsl^60)}=S)-C@L0J=q2?7reFrSLB@ z)fUta5Kbnjli5>j+Tp0E|FpYBcH^P44V2tKaiMGv^%c*qkAWT@?}_Bjitcnp_v(ps#h$g7;XH8e&DprF zV>;=up$-m;QHOjKKYr7dP`uNnBZ-VSShtl(3qa`y;jd6;!P!J~%d=UDlB_8*)s?{a zsITg!zOp*$wDS9d8DB@**Rfib_U&40g7d&CpRnXbir7-48R~o)KYr6jlLQ|YKMgb< z?QA2YSq{H~#qW4ILU$tPm;j|wA~s5Jk0CP^DNHN@^HTGh;e+tH{1 zJ-9^UKF}+8k}YkSz?H%!H_s)UEW#IDD7v(Zqo_$)G)|j4_PSrMZn|Ozs^i>P8^>LT zfJ8CGrKx$bqn0X=ke?)PbQp;MbK=uX+w?fO>qhiUkweUN(}GFx zftOc`+=}oh^ITn&r<<(ZlresyC$PhS$7z0E{&C$K-UTDvGL%t-f0^fg>bN)BjbL89 zXBZuZxQiX~+T&LyqCz|YNms~303$=pLg@5}Rz4bsQ1$p_(IxTvWt=TZMdT5)V~E}V zuvI2H!xLbm$uUI>xZ+@R#6>yI2>oQNII%Q7H#<86As>Y;5f|Mu^(FHu;*7!<{qjyB z9TF5C2Kah(!ibAyat6PU7(p)@)7tT2~@z z-}Y4j&VB0+HHYT3wRuYpElLjMHFw5tkA2MPY}*%4Xf`0C?pIsaE4Dpw4&)371wrpB zy>5Q{0?d-beJiE5@#|M6NAhY9addJyR|5>V78S3P#B$3}m%TFBM+Y87s<7`bk zQMdcfo(+^Xb$jY#&S2ZII1GuEJJYwP7he$F<$rJ>OGlfow5Mxz@PTLhV+*2u!fy95 z`uYEFm<>$A`)D?%V+JqLAulA_aW^j`8*S9mBC<=1h+<(s}3?pp`c8QPe(Hg3tG>6bLe zKVb^|0}wpILp1e0xz~P1UAL7sRMs{_2}Rd73;Ap6xh=F#;i`RLEi&baYsqMx(*S-` zT1VNqly{!vtR?T?D*YE!_ex1{+xB|1)afmW5l0%+#rDL{u#IYx!&uPhey`Qx>6ArRNx z%op6T>xFu4N?uP3D$l|%nqH+>*&V%nWua7y-on~~9(3RjK?g#RcTzo)=i{*3^CDFE z`*M^?cRsEHsbNKEXx@xty>U_%{8iH6;3b2{xm%`N=5a%m8`qD0hT~pCuU$8RPBm%T zZ%(LJ7RB!N)!yh&kl+@3ip3*-S#kP$!9@K#S%k%(7ebuYEtucbzwwHZ8#i1xUuQA{ zdBpn02SgDs=!L7eQ{Ps+cTx6l*tbjol9nkRLgH*35C21tW8sC*8%L?d^B6as+#^>( zq>LtAq+l9|TqB*t2rp2rlXc7wIstUGPO{TN6v9GE97xH$jaYbn3oq#kzeM;3V9^*u zX2K$Q|1LRWm8(E0KqO&Fh~|K^)vxLCj>GIhbhE?jo0#h<9&DQ zLvPLU*>!JE%GvW1SLr)lE9X`l-~HSN{Qc@32ujuNU-uqJIS(io@f9^6b6%Hs$pAX} zj{UYhr`MO&E}gh@`u6EJNB^d*=Js#B`FZwwY;u>CgR$P(^T)RLZ0|e%uFO$W{n*bn zwyx+_268^GuKuZ&>)4)cY5o3hXWCDs+fRU{*wOUV!Zr3}0*BIpLkL>=;+pBlmLFN} z>V9PZvHM4Ea2gt$z=>#Uf9$r@wLdXXrWTa+OG9a?_pt$0d)xuJfS>qqFHw2t)a_Ht zbKhNfYvJuLWg2#-8+NWY41Rcg>C^-N$VNjmP8(&XbjyZpW$kzEZ`o7LM^|?J;J~{F zzIQ0oyeHkfXT5n}ruk@^{!V^41m{8J2(I{%23#%C6=v zLG44~@j$-FBrIy`P~<`za#SXz4yg4EYHj7s3sxw?9$BzXsu(z5Azqc6V%Xhw-M(O- zB+lZdN9jXI8x`S!EZ9CN4PA1-@d8p7!Qn;w$NEXRDI*O|#}~ozu|QlLn9XzkwX3i7 zhQ_)VUhC-VKKxo|()tBJU9WYvFf=C+XC`Sm|Amgup0Vy{x`Vi9Jr6Hgj|hAPl68V& z5EEmK#~C6LO(Y&Ah&e76?uFN0g^o5}6W> zBskUSkdAmn&WUy~zDctfcvdtgX0G5GS#*$KQb_(`2PTY6Gg&2we^3PBO-g8_NTLBZ zR-yseH12>Wu`uOF;$)UdxkQr@(8k0+36TUyA<0-YiUadDj@=6gaigs2&TF?{%arw} z%lb2AyVGU6@0$Om<1ZZRWydpRr_yDo*2_*W+Mm{Pbq(J=_139W`@XxU)@xr_G;cIE zFFLYiRg3nGGN4AIt!webLwD(|Yd5bgJ61k>-`$<9Yl8R@EJ7;d6S!w=3=ewQX>%Qmj+wfQ4 zdGYp(8Gl>a-?ozYLGslSg8iWH1N@1CLhqMpahM@Frcy)zomb} z4~j>B-FRF7Mw5|fGXro|(TgBpO>N;ad}r&(U3+XTAQ6kwIq41L?K{_Z$Zxq_=V4Nanz3`oL($cXn;ps_C!&eJcY?pSkn7+n@XP ztM~nVDc{*eYua}-FG%l%>5tQhI-9^S~1k&{8x4pz<<+0u6m!17@W%XcmusvAjPdO zwwjf>syP`I)v#0IG2Bp%8C(Q2V73?Wy|P=vCYloT#cqa_5;XC2pA=86iT0#SJ%9%_ zDdMZthIIys`A#L)Xcc6*}h8EMQFghN-gN3PYkEtMn12~ILp{+3d8CZ_c>_JgrU|A zg$^QKNO4Ts^EEQweSwppUm#Z66Syktl~EeILN#d8GJHjPW2E5$$!*FHn5{1`!nBFz z0~eu}6M(LJBya)c;LaZ;%mqn5p0Y#!N_8XwIa0ZU^2j{PiL^*a4qQ{AD3s7i(G7|v zYJFs_dK^EaiJkxt<+Z>>EPM&Sn5f;w2U=So$IuTh8PGZ zLec`Pe{llLZ%OD|AUOcRfuFEt6bTDJ#imQb%-rl|6b?f?G75JPB36DF300H&aUv`l zgvcdPKNZFQsUoP^t6*Lt0%goxpiH(ke1s4)%%lM^KlO7i&yqu;l-8Zi-;bxd_h-6~ zrMr)5z^g~kN{!sy2YV~rM^2=x2u+StQglzTNjUQ{{1s|>9;N} zzfAkVO8D*btH=KMvg+-Qo+!69T8;yt>hS?BSwz>K7i-(-t$+k?0Oj; zy7|FDPxfW(HZ8#?M}_9Tx}f6{5l36Xre||3*fTOD|3uQplQrEi(J}q88jikvLN&(# zX*4L;C9X!G`$mg{=yYb7f&I9e*Q%4&bi6Nc4q~TR7myq6qsXe|1E_#u8HdNmXXdb6 zlsutpC(JNfB*Lp}gehpq^oUfV8gyZQ6$kgmg*X&9S4zJg|kg`^0tLrn> zJJZ!W*Fx*nCl=k%zRI}U)9!Xq8h88eyH9~$VcODdX?I(yefQml`|c6uCr=>lz|qdi zCJ-H;F@cN=2EwqiQkcZxl3FedQnO&`juWeJCWF}sAO)2w!$FShW{FTMl!NkAoE)~f zH_DYZ7YgDho3!-WbK+1B5fhj7kf$rvJDhe8r>w)$3eh$Yk)#IFQh|P`q{pCb7&DwO z9hzhI%Hr}b##slCu10O46b>w<2nWSQj~XaP-`vE8acI|oxyUs7NUj}b;}Fdn--p~t>M|;}QzIrXwKb*#&WB6f5PuA18 z0us7&bq~nz&XSa~Zh7LKvy~)kUy$Y6w1|i3fo)KN}s#sR*Q^<8OqSIR0W zOcfl`s12BKs=Bon*U1v3(8!yf4ozGEo>P$*eidFiHWWi!q>ByfKVZ4VKOJppB7cu+ zFQYBe`I1od<`tl=HmUIc{HSbFLZ0NXJ`{%V78LSfn+X&`dtrCB2SC>oeGC;8(x6v& z@-Qd`SXdyHae3U32TA9$Tok)f0e+6mJ}^Idw3(?5H5-`?s#!6zSxso7LnA*z6N0J+ zGd{3e$nuF!qcY$?;9wBA6lOFJ&umj^GLcopCA28KL>ugvFovpL2<1_T28FYTREYD6 zPtPBs+l3p*OzLl%^V(-~f4?L5Hz5+zfF&yg`!GW;!^p^q>DQye%rq&q3`r)2`XDwZ zBVaTyenIUgbhDI$rja8c(ln;JScl;(6_}e1K>NQ}H8j?XOksb6ESwO5sL4UB63P3s6H_>( z(L5!kTTHAA7ml5HVPy2e1sH!2SinVSMgg&_rqk3eB>N{|X`{2rPy~xRJ_o`8vzyi~ zCK}BsfMn}Q(@8MY0)jX$nSCLP6cZD4+sI5IFfA^1pHPc>JqlAY&mZKA5g11eY3xX8 zO`eD*)}m2};Ka~r!tar@d4%i{^h1c{Qz)>N_y0?bN}N=>7+SZvB$GbNyI0O-S`Vc0 zS99Qj_2AY?{NH-W{&wq-4V^u6X5`%em&VL2HEC3MjZn?z(Q>d+!M24#E4(b$1pJT1 z$7|0DFCDwnH3uJ94@u*NB~|bgAf5O0qq3b6Wou#wIhZmM-FcM8W_jW6!3V0KY6JfNLCw)$!`FE6}hQZ%A-^Z zMop>ejl(rv9Ma*M5LKC0Mw(p2QypTF;{-ylnA z^=WJUaw5~PH{G!JzI7k;HeIC~PTxmP&k_tY-E*FKrzO>}ZM}Nn&(5XxonGH{CS83d z=dutLZBeLb?SfVjuz-dXte>J>d}WqhHx!(Y&=Z>saFK=_- zfvIYyZk4o1XYzwd2hm>g4;)!ySV5v$`mOM7O7c^K#PUVAN)AFihCHB)3(8nZBu@$H z$L7;ZS$nf}gj8-NGy9l0N(WMh{C)8@&aLrxPo>>w|EdK}%6fL|UaD?g z`;fyOh{ZgYud5Z|!Sy+matuY)s#Ie}ObKW+{|v*R;USZ8B6TA}ko;(P$eQh}*mYBW zi;FWuNcl~qXl!-!lVX!-DCqq=-#rv0pp^BveN;4EMgV3CU2x(&$$`TbynMFm8)Xn-8P7{+j z9Gf16c)^-CChrW%mt~2oGhm$qoe?K~9>)t}R4Yef%vBf)qAM)$FWJEuh8O4U(w-Q= z;YJp`mXIA{7Z^um2NU3g%QHOA;AEH&#*mOmDmwqI3reJ9nS4{Wc1cx$Xpt~;4PW5E z$uM6PI_k3m1Fdu)fU}lto|4@PLS`+{M&aMH6f#qT^e^KADYl8WBcc{rkBDt<|{OM6v_ zVAB*~7ttiJmjQ5*`#5*O4i$+P>cMUbG-BhA`%xGUX?_kidJPb4l4PQ?&g$i};I@_9 z7wwNBK2e@^y2%j4QW6$>nab48v}fnq7uG$;7ftNBb%R96RJ1X1#UGkxc!zbp5te-N2fEEs_~LnI1fu z+65z0rIdtZ419g zac)r@$;gH1D?&Mm{$k{VhI@;?5ncH>^wUcDu*EO2dkF*3c*@vSKE)_&d`k}$o?@OG zt)Gy>4P*`%jNnzI4epwhnR#G10P+hJM8|KFoD8^$*98qOe8x;~lw!2&H>+S_^%6diDANjDuyH6De}G2O5(XEBxy>z*2D%Prwb8#A7cw5Ma$_rTNpxC-U}>SGfZ zII3fZ=&$>awL-)f4NxAMrfi|M0bn=Q=-kZ@4~r`bUCvIbFybM9qe`|^34NR|AQf&X zdAvkV9vw=S(M97Gbo8dfT$rB(nWs6OZOSxRaG3kF*ssypkS+kQAsCJU*iRvY{w=%K zgmw46lyx5?6J#-aftt%yqM!NC%HOa%KcsT^$0X$;Vq>sRF+3|#BM~cXjM>^xdQ?y8 z({E$!V-3Ox^?+F-v_<161j=yj&Mt9Tq5lIiO_rVm@3i1MRft1p1?h&D;s}2Xr_(OP zRo?`lXD+o+Z0=*i9EGxalHRdE!zr-Tq_%-P>25^WtNKz*Y>3y%V-GoBB8TEisaTnM z3I0GT|26`jm4BAL(|7)a`hhrnd4-CeP?Tosc9u!)fnm*QMN`M zW10oeO1Fft>Lkn6JhWfY8|LPW;!$+1h62N`f<%4(V^M#CMfk-X(~&;*V5l(+pu_U83oOzeLPu zjv;nUexAOP#;W+Nu~>C99|uHx|6DEO$0~GJh4MXa-&C6LPpGbRu9kZ1Uld7WW=At= zW*2LL9KMZ`77MouDGFkvoCcJvkOq?VsA zvRX5XHmyw*%$NggQV@qgnjWkOTJH$|3SLQvBAU_{8WMho;;HC6S-XiSk#omVnLCj|1hm?RV7nXpwv(AIUDaz|ba!$h$ zE%;33%s4)gNTi$exgvG}eU1X%p^)Z-vxvl`oavs3eNhpde)?*q#E}v%lQ+A;zD5w} zdrtT=9MKAP5abgQ^TJojuaW#*(Wxov3#H8P_8kgXBInP@`Df((D{}s4a{dE3_sIE( zoL`XhpUL?fay};Kr{w${IVNg#p~R`Mhy3uVU`{wj&ImbY$oUL8pC#ukIj@p4M$QFt zBIL}G!&p=5=~MVN`TZd||Ck)2|Ct;lqfFSLP&i9BoAVspW|Q5GYnaW`n6YXAdA0)VG0uH)>s0t0p zdf%b!!K2xh!Ik5gmcew(;CjoRoTJ;cd&Ae6GvST8)$Yz&2!dA6{+x{&gaNvXEiib1>W0 zm+jk5A*I=#ZP|fck2~W!Q^|(EB4;81A>~y$=wbl0bH2u$gMm&Q&vPyYdO3G(&c{GM z=V{56F|eE~tgc5-%*BmG2{b&WY&i|LeZ!`+cH5r9nH7%B#0&Qwdma<=6AuJ4Tu+0^&jRVaYk$swHw(l&cT878NiqHDedsuQvrzp0IAAfopv!qJ zCf7!Jcg}=2s#MXKvk+wE+Axa=!U}86IS9fmE_vbt^s(27`BlOl8q(zqPLmy9^^x9# z`=s}&Gwi)#b3QR4{7J2j=D7(ac~KJHR1$iWAZ60KJ}$ACoKL)FI3BC%&=bGabU5cX zpyriLIRm|S4YBuzt@()wzdyEDV?_2?O_kaD=A41vgXjUgH*DokFmRQW{H!ip4LM&1 z^lz7d4O=Vaa8{SI8%!@L5dcULUed`CUP6RUQx6bh&V)B=UEPqg5QJJ+Kd}+?Te@n* zsO!y{@J5V={+xv%DF&dA_qa`cxh8{Ye>Sj_%HN+ofKkMI!&dPW)z~lLXu`~prLYl? zg>7Qv@K7I|K=w}N%T=rgwe)?FKL8z%9n?7mnxcB|rj6weBEYtWrJmT73(UwgB5jo>Lls$hqhJp^Kx7=JAV&)Zml}d&gARC+Hy}emhV!q zadn>(L%l@-o3Z9J9wxR>34ks9@uiUhLYMcl$T_p~K5#QQ~Bw#Q> zz7E94V!nV@2~+4Nd^eO~1bldMJR!-WOQ2JhH9aC@ z2Vyf25{@70)5O0<*nlYT{~H1yPF&EovSVD{(idnYsU9ds+zuA_g39X zV!f(o@zkbY?yBYBy1P9afR&hS>A`yKdI1PsnEX%lyr>{{{>R!#OE19 znkMRv>JWYMQ;|7Sgxpx~2>%wzMAIBGwZs}V3f&MFmM>c1N8=@2p(0aopv}qT)tDB2 z6kmPS$hXZfgBqlV6~Z@|X`nw_&&yf^SR;fBm)V{!PKj*PV`ZLL~vTemhsAkJjQ+(0x--#ts;LwEU7GS$4}zI$iNx-;e8iR)*Z z|Dn^j^kS-UU~T)lbC7`JGSoJopqK=mOcx7kt+<9;tNBbC46fZr<*!96EKPo z&av1*A?EJ{KS>|-2EYD~J)Y@|pu{e@<7 zS}0~|KKH)JG};gmS}Bm&(PHJ6z&=R3$DuaYPQkTXgTW)f2L$n9?zqrF*>sZAMNao~ zQpmRfSZqQMMJdr_3Q*$k^ifDfVMrhJI@m?D&`+Udn?j{IAZ$aJFhCCDxDeMvtlAuv zX^60IeeR_2%>`jNBcbqYRO0mz{U&;}*K2(gM%*9KlmBF5A2XgIG13{2{hwmai8i2} z)0ivxIx8c`zDcw35RwY}$vHv}Bg}}Hl&C$nI07sw%G{>*v+x-2kPe@OVx$N+!R z*SKJ5SEu{Gtemd-Z@H5Hm9stK${%qxkGKlOt$xJSF&Lf*s$@X{06(W_2x@r5bu$2P zEn@zhqIn;2dmeH79&x@$T>VcCo=3*|N5;CJ8+#rZJ0BT$Ju;qpWZci(;YY^7N3#Ep zN5*ZBjQbuLyO}4VZU4Eko1+jB-0kqql9mzMwyLgvfm(6ZZi7R z-~Zfu0T2Rh+4VMGdQG46zvukVp9B4k%jMwkTI3FxUdv8n!an3fLC5G1vy! z9=0>so^T|cVS&Mpge&O|yBX{R>ZI$RCdEqW6EWKFn+!Ct_D zaDc&8fNR6G4E6!83)eBY8gPBMp22>=4dDg`*8pw|H!?T?xGCJk;99`V;bsQc0d5Jm zFt{FYYq*uc4S<8;AcGqLZwPN-a8sf!*&c2O+$^>vI+C5?P6oFoI+I=DE(Qk^-N~MC zkAaKXr01}v8?Y{+a0qpr*p}!`ZVYc^a690>a36y^5}T6!;eG~pCI*t5!XNO6ZeVR-{r&4vo?*zHfwN3#G#V*FEHHtb#O<; z9VNIgGTerBaL2@*CAh;3w`m>RadB4(?mmXQVIAE4;_edMkc-hT>MGBJaK%I7^CffHzhDf#^efuckU?=qXJ%$o(NuhPMxMaC zX?SJ<89y4IN>P3|E>mthI!7qc1YpO+=G4qFZ~LPb9QCX!VnrIqH4W zvr;me(#DI-sI6+WQaL7_!(_xl=T%w-n~sj{)C<)voyTI-&7?M-Q=8hs;y zpHIbUWu5X{i73WS&PCax6BJLd6}29YV;$7>KXQC@^e{e|>LfIC9XWpdTcriU$h;)a zO2=Zdx&~%_`OSq{FhE^?^x&Y+ADcN9pJ9s-InF**o2j-m{>ZV|bRssDieYzV;(f#G;Dq6cGDePCFNAvUslANuf$)$elQD)#Pw*B17bVv0>- z4f?NM;@&ZY{l~dbvtnm^OT?m>lQQUIQZep7s#sqgJ~nb-_?Tjw(4de-u_fa(=c0-E z7@+7oD&&}%EtC?hmz$>+rV=s5JQbZf9h)M~(;qVc562l+X|;dXaFt6HYA>16+>fAA z(uO#fHvXaEU&6FVb7wRqqjy008EfgA3X2Bd+MX`gRZp6W6na|VLdFTDhJA75WHdE( zIwHsBqEa+9D}~I8S)QMZNs28JiOs-)3bB&=>^P8Iv1ac4V;V3oEu2QPY%p2q)yMyZ0+ANI3R;< zH_b(-&PGqg$v8-ZR^Ojwy(5q&bDq|Y2B7>-If#DuT8FW?#g!VS~VGijwSw{t!WjFxhYMv z6U-Gh_jmA8wz+0~b59x4#@{#mu?3rKqD?-}SKef)8|9kS$$HlksS)LxFeojrjQx>D zULYm@ICsM^5i(1=(M&N%Vo(-}B`Qa-QquEO=8{QQO+{wsQ*-mFkVSfdO6}Nv2&DO` zl)__kWoej7EfSM|(muS%G)-(UwpfDQ%t`x^EP6^7oyIyum{d(GUKO}fd!>1KAyYMU zecSD-p__x*s-Y!I&gpq?&u{Hn-nP;>aJz9JCwNzcmaNdSoLXt!nr+>BSJ;;G)GWCq z+P*c6MKlIG&9M3+fUCqf1;#>w_An+X-7&44n6bE0j6D{s_Kod7I1!vYHau~Bcx3Y6 z(TR{zItWC?2hEX;&WI7{46r}r8UtG^nZ^vVHBcO}B$(%93}S!LUtEe}>C3>Cn~+@K zayDVb){wO|WE%T#`d79b$l|Z@z&+c+r#}=Qseb4W0Wcy%u0EF_1AYV|)Q!sZ9KUy`cQaW1*Q10AGA)5d9I8f%lbrfq45X#2pN zc4}HxokP7ghnh5hu{u?-iC7PvY7c13|IDtB2YxbN^skF){+XkQNgNS!PAr~2a(ol) z$Kdec$+2U>k)wxSIWiHPg??4Ng5cZn)al^FD~AsUXQkl8(Mc7SVsFjIrC2a^IuNRVpGtnYU{m0DB1ah7>p+5*+Lb% z*EnP^jaC@GSPCsLPaZ)qH5-hcn~jUXI83PcR6G@gu7D9Q!($1~P8YvN^)O^=K;POw zJ}D7POW#KFq*h*#`!%y4_PK8T%8(_;2Fw8q-nqSQMxF>CAmmKZkudO}6~QXffE=sTx**^Cs%O?<5{&+%2hyn}PMW?a43=Q9H@=4yME4&-fIbKBC9oWJhU*h7~u<7&At zc;7pC>0sVysv3A~GP~S)H&@rZB;0ofR-A)b=U`4~$TV$zY%&UtzjxKGxVB|oFs!sP zw*#|j3N9VawQpJ4m+`b_gC*>(AZVp56Fj&eNRngzkH5u2{3) z4sE2y4VZJ@&Q*0j<}e&STkvL_jrWDBOZ(|7yba4Q-L%}AgeG!r_?$ChWlWsVq-yq3 zDNmA^>4@jBThI`OGbkv3&gfdDq(X!rs7vX0AWI?HjnE2)O1{Gd;~@^(!!*jBnBX`V zpL}J!N~Y&IgKYR=(>nMSOwts-Mt#zJO3>j-9;!b3qD24U%RCuz1-Y)bhCZm);k048 z;42kdLhhO-6qoAV#Al`w^J44s4O}VUmhxz6 z`7h8${s9uOsEw;{`A~RYSgGsD*7aO(z14B2?uFZRV;>JLxxlyYefQFLGoG%DtqW!4ojpRJO<<1W`zQ6IqP492I?E#>~j^vclw?9l!@ zy$9}j59ZC(3=Cj*z2~^(_?V2WMvOie z>yM931`m#oO-vq~{7&TH#PP9Xlfh0ocywa1VZEyH@sL$DrpdultYRE4FWjdWNEqk58*{$E)xIlzE55<3Z*ax;Le}@f$L2e}@kb^@)qcZc zlL?a9#<_eXDO<0ZucflW;C1P?0A-$ z(eleWE%TL~McB%kd_HeZnvJUY0EQ?VEzgt zSV~5K<;*<1O5afM0_YpizlKx%f>c4iU$TMI{5q$>9yi4eXXc z0v`wN)E>Ai9Mr;Jjmw9wTe7y%o98mN(T{z1Y@@0gD3CY4fMAe@VRa4wb7E^t+|5#B z2KDs7G$|n`7$yA0XKBIf1jq~&)7c#yhk&hVV$u2f8$EA?f^W3F(S1UJ>8}{2m|~LW zPwGn93-JaY2a!xop(~zbEEN@_DT@8mxrtIQLEXnB7}tdw&HGRq3RT5!w7PP|9w5&E zlD5pDSPT};@X8pUV_9Sh{(u@_D|Tj_oA3AZ{@ilIlJmA)GiEyWVjDy5jL->NK<3@fG*0P{xb#>1o9Z(E$M>DmJ)!%rR1V zm?WOc!%Uk5<7$1>jS1$f1~{kC^Ib`s9gfrp;z%dah;Rt>x_*w5P-op}hD zO8FzgCf0YflD_&#YXD|6Rn&Wh2$AdqMk)JiEk$F|-++ZxxfheJJJ#*Rw6+(}1wWy9 zG^2*j8tB+mwX8j=I)E)NBj_3vLnhUV=ci^7zSEhUCt+qS9y(_+b zS>L`pzR@LX&J!Rze))yFo{c%7`n{Jfy|nDP?!PPaYc-k1zPp}HT2-blbbZ@hVKeHM z+^SV$r1*PkUYb$(iCG*g^3ov;D-pj-zlUTopdTr}UV2)2B2*_uQ4NWilBAP_{Q)Hu zW_jW+G&wmtn>fJ&G>cmch3pvm^`{Rn`-0lx>FeVNBgIafI3ZDRXGOPHInmhXdW!S^C3YLwOAZ@)-n5nv@i>4m!1eUBnUoiHgJ6*b=J?OCs!vbA;)g*Kzn?`mwZ6 z`o#VLp5|PM{#N#%Q+&Et;QLN z=wuvGs&JS*BS)vyD+Upqj;XQuW{jk`SQt@Wn3LS zFTg(k^e&h4)n9lCL8%O1lM6IlIQr1vy5irI^>4Zvx)siBIgr_WDC<9T;YhBjd!=b_ zwrOvkvkn_9k2q_yCBxT$?&Hml&jsEbSQQNBLx#^SCiC{ZpX=zMD3LGM(w6J$%>_Gh z{ex6cmFwI5XhY0kc0cqZDntN^1NC_;107uThP;!3g0bF_cQMe%d7ARo4D@r}j(iOR z16);0zLtS?TwPzjo`DUq%{ykLs z*^u81udB~%?#_4E&9%88jz=he?gf@VwAcQl8ShNSuzi`rz7!tKyfdu!p=j;>gVJ+$ z9mcH#BhK6T1ruzuH_>E#s#KRyZ_GA%j`apRgd?4*KJY=f2*^Qu!^wC=8y$WJJpBk- zs*ir|#3Q1^&Tp)+@(3tKS9MpH6rc1nN(vF>GNVpCEp94!6GdO5aBhtIW}F;EC(L>a zG%oQ!bmVGVkO{e(2KE+cTH=4?WQ#a~fHWJHaWsv+SgVSc5}D8!EJ#R|^O;A&1Y$u( z+%QW&rJ4UeC4YcKam8m&$0QsvvP%Xki%wJRC?zA5ys0O+Jjcnj&fqS8{e>fW-eGQj z2nsYK2if>Sc`HFMC#&;zf>in;NC5dfb^%g5raq_ef8i-mMGuO;m@3v##CL%{K1Cv0 ze+m@Qj}o$Oo|PhW8ZfDA&9<1Xx?@w!GAVENnUjWaWDFh)suZShMMC zcH36A+tM{ODp7ES{gnQgD$4eYmPDdt7mY_;R@g7Ixj9!;mp4(~4$(mQ&M}rlBs@0b zUELqn2QA>5n+fEwI>~SQw*auT6@zyuEif6-w5M{Q45bLfl|R$`8`x0Qg*b0P(9c39 ze=&4`E#-^Bg|tbsr*U;@5#5OV2WATG9mk<$5e}IrqPV2Wi7{Ly!ExA&>~x6fzu>v3 zG<7;E^$czw3OR7~1g74a`n|z#y>fJNY-0GxSkLPu5Ke@G)6#4**x5rmFC;tO2LI6U z8GJrmZDGbD&VqtC_0r*?d95^D*rb05BQcoedJLJIW5>C4kY%)FNo%HG50s}55 zx(%UU^oDn>2M67W=;>x2NstAjX=YRjO+Jegr;r?^AOan!K|auZuwZv^W9c`Bo*4S2 z?_>3FX@{qeDpI;4ly;>!O1{FZ(qox4Y@lweiWLhdb!P&j=?6ZjRj=x>L9BlditY)64Re#3QHW5X=C(L$KsI zhuM<%aa*^S7cwSU!y0rt&sc+e|8oYw=X2j{0au zdiTFx-je=3=t1Q!zQ|Y7pHk_6Kmz4b#uSW&EYhD5#-CF%OvzfL;#Db)XPU}*X8LR? z+x#VZI6-U^1Hin>md+1<b{@Tw^+&FP-|1XaI#nH<BnAEK8bgoh{n#KH*04pzYm3uH{SxGl z4L?E1=!S9Pi6IC}&WA~;4i+t}9#D>oh4VXdxo()sc0ne45z!v?OwM~Qj+=v#l13k+ z`Yl@L9RMzHk8v*MzkFc%)Jn_dY|G}G-GAQyC;j(q!}oDVuZ&4)_=nTjbPNg}raUgluI*rn$H9etV^LFxxtK&$jixty-nRt=X2X_iWqX8n5`;Zu{DnEE(G%)FhFf8Juk6 zLR;6Ae8DdMA*S%pu#3jx$>^8w;!n(KxZf2d9vB@PAAaTVWN^_O96x$2h%oS4DIk$) zj^j}Emje<`fjKp}_4NMu*X)np;;+!+w`qI!0l@aS*V~@v<ZUT6|*a?h7tD`hwl+g|=?Q)=2vm1#@u|6f174QQHdK zQmyWW+P5a)r2mSJmi5iJ$atk;x#!ycm5!lo$Ivb7FWi6OzGvINX8G^gc4_Z_6!~f7 zo^AUVZAL+!{WEcb;j40j^dGS#Pg6aMz!k+33u-(~@l|Umo_PuX9o_wbzQSKw8hx02 zKY7>I_e?9ih>^ZAa>tC-hp|ir_Y>y{r04NK!sEI-4h$vIS<>GifxWhtL=`s(Gr~MC z^>*IVYv)@-pA1#HjlTXJE#44-FCx!+m1uWoJ9gh1y=xmTw-ic)VMLOcx?yz^JyYmI zGNuhPHBe=!D1R#1Ty#M}W>>pJL!~2|%Km!At2TU*JkL~8wc2!aX;E3-R=T*AHtR=1 z>$E9c>?(dS4kxv3{|apiM^E}i43Glssqc`-O|lGzAAZe3hrr?-JZ#*CSqOF?IUd2U zQKqCgJ9mP7`|f$jH`Q;}-xvkxFRG4bZ#Nv$TbQ#_ttpg<=5CYNQ!MI597VvLGRQd9 zt5*E#NpmEj{_-rseo!QxMK978>dIEU$iu<`T9=BMz1^^=c3;7{z;BJ%V2FOQAM4@Q zZtVU#qwJ@gj_MNIm!?D>zw{8p-oj$9{w{zE+#@&F+@_Pwb5>|x5qh&i@AW-*gdMD%TGpP$4{XNn2)&icR$bPu4vJK27WV(=8|+W%bLQ8~ zH*e^CGe3uG6tQoXb)>ua6zM*ar>Ha#Q3upLADvKt3FQ9APv+9Po% z-J|4xQ1S^S3MKy&3EXt%l@_Fv^nj}V7D?HGi{dXt3n@&h@9?L<{Cn)MOv6AFS-y%W z`nqDq%9EI}>SD%-q4lQ_0-467){7BdT!e}$ZKbuoxMR%?!HR)%v?LRt>_KS-GaaR6 zc=Y;_CKiu#DVJWoo}KiKj#m2Xod4@@D=KVh9A5!f{C^=+yoDd3=oCUSKn<-$31U|8 zLxPBODtH=e{XV^ye*p3-(HVIHEWBp({NQ^Rzn8JL<($41XM5J!o^cM`uMXwBxV=+* z)qTZ{Y{lD_^|mpfJ&Ru)pu%&-v+A_?-FeP}E3ID6j|wl;+rgwA*zeEiE8nDqFQzEwXQ>|%MC+qUBL)$5 zsb$60b3AkFVjG*fBczB+S`X7JPe~E;0A1Udp2yuCT-%UHT1&r)M6tj#!>h0he8Au<}NqLEn0g?%8~SGIYK0@yaOmomx_wz8Zx!8SJ3 zjtL=WO^{QQR)+D&|23Ozyo}{W9~K$~HwY;2*uru)3f^gyAj8P}s!>hLgZ!aTpEo0i z`zCnuR)TOL*^{>ugkJ(55_qyQD3qqHI<67bwZ| z@Q~x&c*+9I_(^;m7aIv^#?faSM*T41P$OH zj2k)bIwx@wpWx>BFwYZgNEpHf+>Ht2oGEN#I1^xV*vw!vU`yD-U<+Vt*vepQLYT9K zZ44Fw+rth9+Y;iOGwfur9k46xVz2|SJM0E5O3s95t|nZ=U>9I-*vnuyU|-nBU=Lt_ z*w5e^z=3dp!Ct_%;aUd!0M~`<80-gJAFgL`Aki?_7;XeyE7c{M=9V>Udk=4CeGgT(vvK8r(ug$lrm5IVcn722_E_qj(r^X!2%{c;EcGsFTLtyb@~DqO z(o@p*H%;MPtVi3KL;7n~+#Wm1ZfQrwC{HaJLVJEg?Lxfjh$fTs>1aAWpG-~R**Kb9 z!c91~K&WT}uygw5uTMnA4or+4J~*`>2=j|c)V7|8O3V;wx#O7kj zG{Lsn#pDd@nI7gBVsbP+FQnA zK-e|JRCJE0UHa2vBB8g}>!-m8>bnPL4!l3J^Pj!nhna~N7Gb?(r|IXJhF z_>Dp1&$O71)B8J8uO_1CXl@}&e7GX)1urAzpU@Bu=n3p)1M?P$n3}I8ud`2u1m+z8MGMzR^X328eFl$&w zMc5))CE=7IY$ZXIyj8@}cZr+jCEJ_Muprqb1O9LPM;OOD;-f8p8;s;g9t|)2ivv@nch}aOBw3{^^OArkCqyPDkbF48CkI6-x(uzxB%bR|X;{ z`a{cJ2v{PTj#c1hOy!y7EPx#x$3HlNFY-P9I+roZZ>32C*p=baByH?EQ`t_(eW2H- z{@^ifsQ*n@v5mYn!=KTY&PtLrTWASWO%^I@U6rjgc-C-^Pc?4f#Z#m@<1Iqvdx@7o zT3<9Us=r)cgfjdG`kcJRr|XK?%jOLCC&L_<;b-}bA!C&IcMKWRkNAJX<2xFrLI$-4 zy0}r9RRO4YQi@#&IaPZ+6^AlKlQS_HUx8CS&&QKdd1>Opf*ebwV8vATi<=O_+`JT@ zjmKn}v`Mu}vD8dtenGWF*gr!q`atuts#z=_> zL@W}+kRgj|O2rbhs)?TDt>{S}pzaK3&#SgbWF`?!r6Q3OebgXb7r%IQ==A(tY$$qC zT1-TjwAd?L#S;r-6m&%-QIvSZXK=t8+3wGXy4wm?jJ^EXhw{^yQY4jto|L zkzF83T|w~=7rFIXPW0aqYp?ki{q@(6 zUOW0>DA)HjrSEG$dpY;)QRUgA+0c~YpStI6xY79|TQ0aq3GTV=e)^uTDd+1{e4Q)P ztG>QVv)R|VCzNj6Nh z4rq&2M7Dukb;d5lQ)zrRX42KV(&MI# zB?28YQ&b5ODN;6ODEgrCWGKo?cBW(^K%-w7Q^r#`t;Ierd!SxW z-wc#%7V4F;WULt>V<&p3nlB?B%Fk~!A9<5A8Zsc*$>I+dN zdD>lkqVX`28EwsIi;)2tlje*acYBhT&}X?{$+*bNPj0OpO1w$IO*)13#vW^+WRq`j@SI z!zC_cnF?9u=RhuxQbE%oKTpLt6?>@|Loq!GGA8lXWb8btLAepxIOb)^6GXnB3R*Wp zcDWHZwP}-yB?l-jL8Rm9rK*+W4=tu+l1z3&6{F10(>yV?lUg%d6Nx4g^XFqyr06C_ z6Ge|qHN#%UX4D#Hg~m=tlc!>u!>6N;l8s*4fMV{W?iht?B{S({0!Akp;)dh8_~)+I7V&% z7V90X=fgoxtjjsJD~|1Xu`%1c<{gn(|dWpA_nuG#=NJg zV0JiN1q;`>C12Z=uWQM-_UE_k$hQsTd-tyUsLs#%0`~(1)pMdJ>u9=>&b1v-+79Fc zjoCo=<%yd|KY9-11?}j@S@YJR%ZKiG+VlR_yuU5q)SmC`&32BwJ^oQYs+&8rO+)KO zuA#jkaKUZawrB9rGPu^>h04xQwqxY&u8#(Yur^pl%Ljn?~rld#z;$ zQTN~KQaYY`+nRm$=);}OHQvvSt>AGV&p88Gyq(*hb)#&~x_k5P+8Z&&-LWQm{)M@F zj&n7y>{Oh+ScJ-~tT=A|{>PJ;LV5`kUc$(^Lh`ftzB83Jn>3f%_5}bNZTnYs0A;ak zx696w%r9~2l8blQ2zwqN8&+u}-sh)67I_dOYx?gVI~|)jTi#^IO;p7+)h%Inf}Kuj z|6$V9$gH8tf=@35q1R>oWjrl6lx2NWFaI6YDe`ql-0d7Zh1fga$hGWHT6WxajQp(i z7v|f0rt+fqp5VM}RfM(`^QS_$Hi^Xr5{(go;5a<<4_*KOKN|a*Mg9UWZ@~h^pCO7( z*ONW#MeGM;1dB2V9WBD2l)s7QAtSbTY;PBxosB2Sz6o&N2$NEUarpVuacn=0Y+5v{ zK}IA+d}2N{)hG2{9xPACS21`B^+Jxe!c=P#v(7TnK3iwjQRVVWd1>sACtTW zp8i)wKUKC?66W*_w0-mSfsQEpW3O4hv>)SF>c^U~$Okjl6u$)MOx@;jIZhIab|+(f zay`vEB;j%W*oyt&D^>5QOku2eyJ}w^L5MGzCtWJOLO)NQC%qqCrb^Nk5~r4pJ;Obd z%jVzj$@Fxp+!uV$a!c=EUntl+pA0T8NU$q|eS1T_d!K%#_mxm+Z|AgJkLi`^eU{yz ztgKr$ys{0O91|HO`4u8NfkL$&Or~R}u<>*($niPsaU*AAOU$8D4RTC1rWQ}Cmg8xe zZBY>!n>nqT$^DX5^C{Z8h6K&ld$3_fR0)APJ$9zL=VIw7c#IYi4cd+no?EF#cOccq zQdPN|Vx8v^YH>cPy*)PUDYg6r!W6A)FsbZfc2%t=Vz9HDCQN#coE@rFgn>o)N_}@-rc~`+$N!&jI*3{=uwo&qE_ubEznS~a?YNDok+GFQhbN9;-O!PU3pKNX4-R}9g1hiUtaj>>pyv2@l4)pYk&9L z+viqJ-!f!Jj;^*%WyR*aZ{XIp?9Peo$mGY-?6w1n??6^O@R_qm**fu1btvZ%J!*Q=! zV7y989D33(nZM#eO_e1N?S6uGWUNZARb+xT>^^wZ0 zc<#z9#;s!R!0SQqDrAIeTvhLvcunzFTK?&{P4XtL`q&xsIU4&Xc%#2DaaR-*SMlXc z#W%)VZPCA6wJ)Ic;f0z1lG(n}X+l^)^YjFm#8_H2B@q5mEsM$ctBWxeu^LK0q~v}0 zz^Vn2o%wUHol!|rMWha5@l#1MtErGpGnRF5jFS4ItmPpV<|Y>Ia8xdOj&)?^*_A!hxa*$ zv+=4i@2kDO``Ydssdq2Dec=;dPu?HQ`Fj+9&rRcPKkRAK^~2W=uNZ$UyeHiDhd%Q+ zAyM8!d-}bKK zZO6*kj}N?e;CB7MXZ0-w8`_aYtz!e9|HRjgZrAI;PQeXsv9I>mXX|@ceIdBfwgHX% z3zsk4c(>UjDKpl5;Vd8M0-P(9xHu#z3Z>oqxm&s#U8PwJZDI5HeDHWkw>W3cbICK zGnNKW!OWR-NPvl{;?KV^TpeG{kQEKG?)&>-ma6o4(Ta$l5GlQRb<=i{zw(XhHD5k5 zOiUVMS237x@K@eoqtkwgT~{uXk(e>?`2U*md<7ITA}55r0i39rv?{zXaeVrPgJaVn zK@CO_1%?+Fkz=z7=p$UC@-8}(il0)wi%AN)Ns;6{k_jv+s=#8d@I5oe?d z)0{MWRyEBee+#pt+eb2wUuJeHNXN ztcp8x;sHfG@QW>qc%)zi$tAc7xWQGZ>p+{g&$=b|}6b@TpdP`|oo#wtkoiU)RcvLTQJ*uYILM@pWGshn=Yj zTz6h`!okrq8Jc-p^@nbTKk+}MBeVX{TCne`>ATJ~kDut3YV@lmdPS%Io^!nega6}w zA71Gn?+f5DMSAkDI>vUJ{-R^-SyRX`If-!W9w6l)6%8j~G2<^>#XSGFsH+n!Ui`(55|SnQzx8O+d23KlbAM|x znn=Y?oH)T&%rBg!mKgU03y^%_FX1R%t);S0XmRR)N6b7=#rLTgqvCBU#;LeL1q;A3 zXtIRc59o`|QnwnD7_bUg2dEJnrZhM5zXeV;P0b^tZjr-8&`!lh0{I8Pp?;LA7SST3)KYRjxiptjJ?t?mpF$uUEoRazEyu#j^2;}R}3e5$xahqkGGX^Xdk7BFSFp}MQC|h?pYw|-kI(qXBtp!`1*}mpNSQ>YV z0|yIMf&|W2$Lc`n_7)t36FIx{b0_@Vy<79! zcIJ0Ko9`Q351r%9t~GyM!At;1YO$+gpq=xz7aR-}4Gs2!lYw5&-CXc7(9d}~3jqe! za_}1J7+BBMZ!I)1u+gx^UT9)q5CQB$I|H|H_1%RI26l4oLxnB|c5~icg&qd>a!tJj z##f<_-mRZuw{mvZ=K~B}Fbd|LHFrb7j5}Bcjg=sQNNfc8ZJ0E7N5PCc2pW#?1uKC> z@xTV?e(fl4KFr^rG<28;{_YUpWbS943j2yP_==^AJ~J-<0Y#-Yp8UXpAEW9x8kOVG z@#opO2P+~3@u+liDrAy>2zvQlDpsg?55@bOHpnxCxLGVtVu%#2lO!`uZyeTWsCok< zWPot-4=AQwc?bmuNeq>TH8fZBQ!8bICG3YPc&6 zFEe9g=H54NmI2~i{`XY;2NWq1E!~39XJOxFmY65o4GRK=MjpsvGq&;X;R0n6i~iG&tT3yvKao!UP!boAKx!O53{W5OO_$ zo|PHql6q2HDRmAlNj>YIsGjw~OQtJMrlnWJ;;p5Rbhoki=$ze7dI63Bl+kDGO#R79qN3_sEjufpp(Ske&0k6&wVKv@Sada?uj*Cdk8iC-_1QfnHjFeFXVw z&83bDwPZNz0IhQcKtx$y377bgL8j#46cTuw7QH_MTI-E1c|^rX zuhJXSDugA^Bxn?@vYRO0N3kK%OKS{djekJOR~eaLOH>y_s=xymd zs?2=a4arIDRJF4g(tIqHOh@$o>A;-Mj{MhP{k!zqbWD$BY#pt6VGJ=^hXv=X%oa8a z8(y2FkiGW**!0?1h`Fj!5UyGXwwM$9J}>bVkqqjiWDYTaeifH@P-A(o7FT$qNvg;W zF<$J&*5!M#pJkZi@AE$}ALk^Kb`P1SmR-ToBhwQv6b;}Pd~n%yd}2B{J~27^;*sfK zS87=tf8p5IiwK;uaiec-$HlahQgjvWLM=W1ClnjMi@$Ux23z~$F&O!0p!*?xi+KR- zTiE8@ct;N9TY3`d`1k+ICD4kKsc)g9V!);*$zy3slh?ESNXo9{tRuPV6)PUjUWT z{m=E8(->X5*oef>rVD~lCCCFTIdz*ZV7;4Acm$RNf7A7$@dJIe)f^=*l;3d3W#Id$BdVK5%Uy-`JjO98wyGD3W{Z+Of}_R-3(G zb&&Kpsu=|O224`$%aMN>Nhy0%WImpt_GCWFsb3~n|0EIwE=ct^T^r=eN*f=NE6w)` zGP#of3z~$)$4Zdq{`?$7zl-ePTsw}|6=mnYqL-hO?4$wwHnPLa&yOSTMLrdF{tl94 z6G@W9q9o~>1|B$$z9ZZ9iA<6(iwwQ7sJ6tMsoXp2%8<7_@ z$q0f=re%1>c*5@?aPZWhF}&dg)>5$`VLw^9Xz282c?5J;a9Cxrs+yTHm*uXS)%rl^ zy2@AOhT)Qbb`H9=hsh!C$yisM9xJMi!tW2%cnq% zCGaEM9%~M91|F7<1rl_hTnkPy8K&YB5<%LSA|~wRY&AKdUJ>eZLboDx=Y&B;7`(X% zLHo?xc-gXosBRykQ=;YK_`1LefxIAQJ?)Cne#w-#25!8lShuYFQMP9k_^Zy_!JW5G zf9zTf9#cHW6yew<(`Qx}t633p!JSHQXD)b12_DLezVE(tJ#sCw8t8`O({5kq3^i2P z?3YaNWkt_LE&8#+4I;T^R__@DS-tAAiX=@%B;v`3ttx7($W%pb75g@8EXs`>$QWe9 zA@19d_-d(=OzF_{@yhJ0v7-Mf7O~X(57BFt)wSB%Dvy)aRa;-H9T&1d_CN1)%21=I6((jokqEQM`2?VVFWWLJXmB$P5PZ6Kw+`Ld|Fv(ux`ADIX) z*94jGI(BRdsjN}_@+r71>L~iTUvPPHYCedZGO|rG!JbXDJs3p~77PTj8;ODi(rg^e z-aKx|E&miRT5$^1KdGhD565tdyLjGI^?zMEyR;GOL4+UK`3kzgIsC9E9oclyRDOxZ z_-`oahd&WL#HkAStq+|Wl35O)eiX<~h2v)+^vglD2J@x;Qn1K|{8#7#AxOqDGi8(; zQ0>~@jOIOM0)kCEZ5P$r;-+fj@mcwAQTcz!n32N_W9H)EPqkfZyLjl1wI(lk?}^@= z*rSL&cQybWirB$Ubv`s(1)Kk?6N-j%bE^3nCm&ig38?=u$tRiU=y3Qx-&I6WfZ3 z(ZUbc0Tt`n4*Lo9EFe*>-leTPtk>tL>l;)smz)JZM(J@26^{*rRPxbAl-0)BP1&B= zMWon}-;v3l%P|zH1pzP|u#-tVWO6`cS`%b)VpKcU(5R-!TFjV?wifAfm8%8;vY)E4 z-_|W}=)S&jeFvbGStLm*F0ysz+ESe~_Zc~LGj!4Ntsts(a z)qXn6Qg4s6)N)@7aqUxepgBu-XqgBmJWK+Zm{7Qa?UUKgMKDQ@T8i#D`B!*^W^$=!tknf0&-0%P9N+OP&UTlpy~{P-<$`y)`d@P`ce&oXT*qCm z6MyUxn6|rIGb2C=e9v7X^WEiI?{Wip{H4)z*VOuJ)84zLZFfyO@0y11nzsMi)ceqq zGV%Ra9S=FWKTOzozWHkCA&1*T!OpkcKn-pW8yop;1mpJbX`Z)b>lu*^{*A$YY4AqR z%AQqz;4|KKsqgBk8%wKvh~R-6bt|D&erqLu-C*P`4~(3lj|!-x4hICx9m*;+8tz3*vuUo#8$&3LA!+Ze@q^QKlVy%pW574EDR?s_Xg>svOp za_g<=POWfft#H>{0V;F`cu(F1mT+HlAq|N;q8ot&$deHWi&tB)5rmxnmV$#Ik#lV? zI0|7@bCO!WT DRY<^% literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/oracle.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5ead14f1c54739032237244035215f9b18eed18 GIT binary patch literal 8541 zcmc&ZTWlQHbu+uOv%`J!wcUd&ny4o3vOYeiZ zGnB+NRZ)SEP(fQUMGG@Xf+!CpR-7U(P@oDBp#5%tc10Px7>I@2ARh+$u`+EmaDRHv zotfR`Lo|vO)ku5qnS0K;_slu>JnrScc)czLp8LH&OeR_w=J)twJq`n4H-2GbnD-c& zk=YcJW<{1o*p{-1Ht6jsdzuqDI>rI)5FHeD0L+U#h53{-ErYQ?=bRSrR4*=p*wNEjZxYZm%Q%-&>=o`HUH#;a>pI+ z0|UW`os`!94BChss-f+ow1H>P_Q~NI+HOkgdj{p zp2=jjxR%Uj)Ny#X4`vpi6DHG*sK2V4flcsz~tKJ#rpl`>~A z@l&wk16FUC&XROolM-}2DPwRoo1>~iuEFARW%7CxLB+AC-PRyX2}?GQ${<=&TQHWc zO6HzaaQ-P%5VwgLP0yu1!F>w6LL!@KOhs@8|W*}<( zjD=SC89Qyy0a_H~IV+-uf@3iC4zyMdqiH|%?iR>9u2FEW3@G0Y;HWEO2dXa5qy@FFt{BU_(Y z=hhJhLO0L~+sCc2w8%)6nHHFcZCno;nvt%=wZx31DsyoX*Rmwy&>iahoI-R#l9HLE zCP}&{k zV|7bJt{{kN683HYGm|g_Kf+AKzJCobTkPww?0c16wEdL*TORCeM?2r-cD6I= zg>l@UTOQI5<8@mG;dN)~56m3~yNLt$V8$1!}d9i{WhQ z5HzUr`r!BWd~e_CLcadR_m4lSfAPbkh58ql`I6gz@AP+1uO2V9#~!uEN}fQ`(^2qr ztZK#3u|nwBW6$xDzj@h9PQg40GXeo zGd1b{w4%l3xE8ktT8Wa86{5&e1!E>Ey{&?(PGjO!l%DPhx+U}=W}G+y&)$^la{Fs` z$<8FFVIp+}nkA;}V>~^1cmI=veZS!!@TI`M)%pB^GvJdEU*6O6nZQI28HoM&PvrZC zEJRsgJO}dbBToPwd=NDGz5?hz;5z_(eJ>aY$~SBwfUj?uEk;HQkt#H2v?-~j+&4>Gg0SQx`cW~)v~gK@6y;>c8ZYI1P=;^5E}q>b?8 z$l!4Jjmgoekq0bwCg`a5;B;X_p)Ms2;rWVkHJQmZ*C??%{00!KkZ(Y7CBbvgb=Q?| zjC~~Z|6j?i^FtFCE{sl*F<66Kz}0Hxb5lXUq#$N4xK%)7TtpsUt%NHB5Lu~!5gW!B zZJuOwZakY&A{;q`>tH$|lhDAVX0`W?+G6GHNC1-;RzQ%vT#}T|LcT%RBT3(!kEcwG zTax4?B#i{-xO$DmGip3xRF4wm5F;Wspq%lHEJ1CUPG&Ggl0C4t?!`({QWrAW%tBh# zo0DcOCdD<4s8aH3CQB5WeW?A&S5fM5Y`#X>&GeX1QYlG_utpd~?R~|FpdCnxO&9#s z%h3Gs4pVAqTRLBA*_Y>#MOxvtN=m+eN>=5N_<6p302wQlf z)m_!XSm)KmjFOJ)uDC2)F^f0q><4z;sX$!-b#kTTB{YAj=&BH&3||UdOF}UrRVN_R zfJUvWnCzCA&seBSBo9}g^vCYd;cN&chQf8!6!n)%E0OyKfe6^%LU=+`{a>u zwCu2Z_%dU6@P?n&>Id^A)#MKW`2W(RRRp5m!7}tl0G6+L2q)V>%Z`h1-Zy&4DFi>#W{o!+8n?(T!W0nB zAn_D5yQ#ftes|)q(qO{POLeyuIc>_DDEZwZnF;qsLRN{~Unlpmp1p z2(*J&SURuhJLiYQJU)0~Bs?@RcIm=+ILC#jCc@i%g4Dw|QSIx_jB*|G&6ld45)t70 zGx({mLbIFd+fdQPo`FISq|29{2=x7z(odwv!in9qUfJva3=2F5tv@6F+-?US@PbJ6 zp+2ySJ*qyiHCp9d!y{)0FO5xwd(@oAf@$1(wuk6WvtlI;u>1~&YTD!@7V$g~--Vxw z{<52`Hb*LmNP#`~Zr#0=_xF|UjJNf1d;fG`oG&vrZ@641UUsb>*mL9C=>C;&WttDh&0q>5uPLtAd-+cdgt4 z?sqdhdNB+G^fK=z*$_!qpCFxER6%lj~oQ9!_FfPLMCgXr3rQoW8#8c9>cxqlD z*MWy%1*7v9;V_lCs`JLxTy;t)RAi)5u)!Kaw_{O>oLf&uoA;L~3AqWBt4NKZ98Nw0 z<37tS4Snnj7JXd>UsusLQ1A_u_ICYv;`t7REHyn09 zzjO|2Kac<3sk^62jcvuooB7TOYC9weue33?R|K1KKY6_{~*l}+L} zjLk#%sbOeP^2U7dY`H(LJNB*f3nu2>I&yDQz5&sJ8&m)8Vh=b6&V->yR zT!cHE9ON;efD_9ihTuw8198n~XKw3GpvoVyDm?G-A0R_tl8gl?AB zl@qBE2jTcE1GXk0ad@d~-Od^ZA@Xe`!RB}HQ*m?b<`7#_8yCN8&6$?e$}^-2&JaqXUSj1qaWqm zL-bEZ-+;exys%840%Kg3 z1m?A53KzgTMYsjjNGno=YF0O|V$^7s{>3IrWk4nAEj3h125!5QXoCK2inJrTxdshj z)VK^FdjV%$XG|lM!UiifLa@Q&jb>OH{Ed)GqZ8f#hY@=U8@$Z^LU`G@L3)M!6}++->@^b5T$t9fGp9~gKKP8+2%x+_Oc!O-T~TE7I;UNZc6dA30eA9X4Y7kgR<-~ zSh|jHV+sH7SPrkYtg)SCn;QjdE8DR@aFX^^FnCA7Ky-kf?nS{Y7N5z2J+eSgS)eyr z0D9VqEQeNZtg#>|)mV$6vF;PwG`62oY&5X!%NqjAo@Jj3z@2x6?t_qhWt*36DtSB0 TcI=~PKE)naI`kAVbaVVS@#s4g literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/postgresql.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f874fea4e850a9aff7e0f907727766ba07c0d33f GIT binary patch literal 33745 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[N0G zl?(`w1?<6sVin9)VFUPlbdv3vfL9a5MX4M4<|^2v6}>henZGs{m0qK=rs%drH*#r~ zKB3z>-N-jgRv!MGATs_+U(xM(~X2mi461-P7vXw8oJ>;5KhA3A1GJx zWu6L!!@=O^yu<`k#BeXN`B6GXdC=`F{zv~Cs9Jyb0he{RTz?5B$)uqgF2@;ff6CjR zwYrk-j-;g%!sbrXBTiRsy8iMLi_O^oxXxtU`PjA7Xv;6~Tt_$T2o>&ZOIvn(Pgdy2 zcK2odyC|$G+qWaTbMK?h7mT(IFT9iSM0ibo-b@hI$G7F}1UX=N$X5{L=A2D=4?$ke z)se3zsD`U*$=4E8hYb<=dV(6by1slPK~1_gTfUi~Hm<2B-%d~mw{0L#rRKMDPItbG zpl;4q`5Pag#}eOZ-2Hf*uQHzE^M^UBXIV!Z_L82iw52<1ajUQWX^X$`dh*=}cMfTjVwm&iAJ>Ot4+OpM+c|AS-L+rU>bv!ZRJ?~|a4RU1vL9NJoIkIn1D{@Q9 z-%_@0`jLoWY4{W_Mdsq4>_AZIZuZih$^?r#wkw+dz!A{19m@t1N2MPj*%ZxT>P5n* zofKl4xtKz1n5AJ;c5T`sVk|npub80lLiHgTcv^=|yG+@|%4*T{iP-Y1xVD|(f(6+c ztJz!-DwBL+TZe9(ZB+1?nR!T7P{E>R=AobjrSAYQXOBhjE^qUa+2=BXk%Fl!%pItTke_iWdMl3V;o{9jv8cEw!>^WV0po}gJBP{&+LCuW+`PJGNV__h zjSnmpv@M}-)w}xrlx64orF)j052~9p)xK1QR+nf7-yEqJ4mAHNh@E0Kj88@ld&-i zex#@8cAxy*1!~6ohPC3iMm!&JxXHA~GC7X4+8fr%y|YwX0tIkd>NG-2=?$sqQr!T5 z!f*1Tpgc6_1x7`M@PER8N=8K;4CcbwvGL)T$At3}0@Tx1WGc&H*9mIJEmt`ox~lA~ zr8x{56ixm)n5AVGFDB7#R-@dq`U^dYCOx+6M%sgxt)hn9ht$vyu+`an^Tg`qOv{c` z%Z@u;|J?uQ{$E;#A6V*FyVs6qItEi6gLg;oTZZ#Sy~C8}^hOhF(M|103KJW5{|A8o zQthZ}rqt^2QbGCh#rn>nz7?M;J{dA74SJ`t7^^~36$~JnF><<3&FA1Uik`z0c1N~= zA2-uMFG43)m|r=EgKPjXk3fuvvYuoz0~;9~D6lxjrCmhyL>3spvJPA&cb>98iX;(f zACMZU@8*H{O?~IR`MuSvnbuvY)?L4}?9N)MK6+`*vflM){h#-zz5COa{n_pPKcD-P zxnOs^VW ze5$ob7_cc&8RwuB7`hIOOuYQ^*!X#2Vq6%RIQ`1Yi6-&eYt2J4b=@|=JG zcKXi}R5KhVgb9zSW<);Bvy+_+WS3|+QO-g3O_deULKm}`srGQ}VO|W$bByU6Giv8d z@*zQtx0JA&He}8w8E_%uFA`33ll6j}p5$V-b%`c9(5XtP-c6LMR9eCjZCn=-E16>1 z+JpTVe|?1f>^!uiBx#3Po3mDBEX^rPbH=hOW!Xim4ihUApM3AL;K#vqvmg6Qu+z=L zReJMw&gjUyIIJoUt_(i#bYvago0l^+-Km;x5VrqwKdms)UX<#twRi8-W_FIIc8;dK zV_8=%(mHCMa3&&dlNS3TGGL^<2lf4jU|*ja*x(_j!m{f+?NigmD`byvwqmTo+OBs3 zORr*KxH-$R!7IpBBY)L)tfplpXbb>NcRI}+QI19p)nRb~Lt+W1Ccv3dGc}wUtJS&} zT_WKGcnay@2`(Dm>IOl=B@8Zbrr8=5xA>HFq6sX(Ozc|zst;R%Z9%KhDnZ#5Yg7Y7 zD+IpMvj?!hSFQC7UwV2=NzSL?>PAzzO_>NPM$b;15l)U`n4diVTHG)^Ix4`YD13GD zS~OmDZtT2pd~AH|?C|-qQA`qu8b1B%@N4Jdwxh2JqhrU0Upaj~ZYDHD%1*G(=T9NG zP<3Yg$W_ZDa7H;G4UB+o>^IPn9l3hO7vO0XEX1oj_D#`Pv`g`)?}2@a|SZ9Z}lFRMu?Tr?GUjA67zodIgCjk*GIt% zEY2Uk_x^iHbIXRsanqTyv?^}QP<1ob?v%AVY286lrj<&>0I+0S{*=qVe)Z4Z`~1DM z>qyFSL`edfx2E~_gg2!UP1f~hh3=m_|HOIg#GMOR%&B)nu z;csFgzT<3&oHF5_Crk=*TVeH)wF?{))y7o(BVo_<=gdm<_*6j)MEih(bp;PvR6#_051crK!P`NlP>o;-G()XpnI9Db*mQ3&6uBeE8R?^^~{G zlT-i)L)q&vnlrMUwbFG8bp<2R3^o!MQZOw+-3fxzNfkBYJVCF}jgm>9ic+jG~vyj6`)Ywhk?fJ zg+^)KDsj@Zf|lc-yolE@u}DLsK-=M(A-g`a$!5D{)PctLJgN` zODHB&Hks*ZI>Vt3#%bLx-G>eXEE1UBFrgQHg`sQknv<_W6{v;AlnI*RwKNojYD~}w z)!0V$rS1#T&2XKQH-Rd3!$wfJX*I|;0mWo2TlIy6z}5k5rV1l(SS-bmAAhUNYwRbG-K9(gH$et8-mo#@}Yqw=;`%<-i*%~2J z)03*{$-0{|?zWV>?P;xpRw*4;+uuJmAt$yZ{jZ!y>s_1lZ{kdBWc-Yc42V_;D8JsP z9S`M!c!z6WfgfpEH%dnmD7BOeD!CzF4LgUnfcGV@3RM^(5NqWNxWBV523!qF7j z(bULDbfe=Zw1hJ5&)GJ0H489Ms&`P`X=&882}r zDdV;9E+SH9qYVe7FwWbLfLz5ncF4{Ia~g}vdjN^k%N)9fAjY*B6K5ld7`F5}4Wcsy zQR7Nf+gw%Q+(30CDKkK>YEC_Eltv8mf3V4i`vl>wmgC{AM$@0XZp4P1fxPOsP77Eo ziIfhyuICTBO^83HA@|>-lFR~0JFtY5T}ayd;4~w9EhZh^52{--)qSbzzGVO5boG&C z8^&SA(UfvDt)3@M@y_{w{`#N4j^mHgj^Sngfm6sJZJ5$k`s%U*|HCp%J*ro~;;FA9~92HC>J+`*z%er*a zu3tKK=W)o~9vIuLwv44QWocZ6b!;2NKy@db0biqDq$xv!=AY1-+ige|P)aED3w|<-Y)|5Z3Y@fob2jL#VA@fkNH7Q*``*auS4PK5g&20` z5ACh6$^20erWUa?9->d7*9o0^y~N|n?-#H^FEltP2Z4^oo<0m zr&BmJ1ILa`iXx^1I_Noktxy&*WE{mY18GZuKNxKuiIJz+u)K*w*y-ODq>m*5q6i>g*8DN4ojjf5mVu|q6->}!Hq>~ zgm<@r;%6daTc6OjeS4d4Ys&66=5$5~hqU6EKz2yM61E)@x}rF2Q(%d@$`kmCA2^PF zkiF?>V@=V+E-GA&NN-Wk3Q-t~1ZbCZ#0m4ei%b{3THcK_CQG*?g<15?HwD=fRhWs9 z$J{|-W?I16bJ#hAc@_?j=r5^AGYdEuB_#F`n?uZlwCW{?lnlJ(%pu<+`Mv9n`^`l|b{3IWOo6osTg(5gHr z1X^YH3n!=H1sQ{bBhHi|Wq%BZSJPR#5vh=gFg|g4pZpK*r zhBhq7W+gR{zo@JZC&y2o$3ZuzUlWFyf$(s#9{*7V%M-_-p!#qKa)8`}U{>;eERZX< zLCwsUPzp}-G0n`s6L~9ywXO@`dO4en3!V~{%CrKaenEu8 zQo!>J6#j3q;`|$;)G<&1X}b;ZevSvEImtV#m8 zW0;p13DNP!Ldq>OpzOLO`+^ymmMPVsNoA*}Ge|6(S`5+@Wz08_SRjZA>|0 zrpOc=ol31mY!Q*{H#iWQ4zZO*JBdtH(iOT917#G-Y|o6x$()rn3f^3;7 z;JIOKcw)pe-^Y-8jh!3bmb?*9RHmUhZzjmXRoCUM1lc(F5ZDRAsT}Tn1woaZt^TnS zkWvtZ;fHysHeSlEJ&yv^>&m2Bs=2}1ZSN3#}$BBCXXnk+@92mo0NB&IGFM- z>j*K#%NJOB=>(&Ms@7Ea-SaT-C>UVxdiJ{}My4BJ+k{7Bo@2cXjnjrQBsPcAe56`V zYJ(8uwNg=){!i+7(k7X0#L>DcWm*SPtpn-Sop+A>qT{}OB59b=o)y{05l8$sn;osm z!`h&H5^Y@&b&EXq4Pl9;=(SEb@J}!OaEbO1AHxD^FG|7hZZI()q8NUiLsaZeQWPlQ z;g^8HzCY)th1Mv}s1Fs6InG&RBUMO@(f}plLSBsDrdqFN=EKNTtC6lD|KCs}kxL>P z$-%j5GR_{_>tU(7Ik@^B%=<3W^%EdNi|giM%G{K7)-F4LWp7!HuOGQPmbM>F8ji{> zh^z`1eJ5+dqX5`8*FsbgSc0wa{VHvO5er*Aa2TA9?0!8=v(ngip=wzRh3yw{dlB~2 z7Ajc}!!~lzib_e}M}13oMPaXJF_n4*8`~-$gzWAD9`5&6-7?wXYt!N5ROZpW} zhaPGBib=~6q12H_JLMNjC*f371>!kiY8b+vTSm#g8|{>8&Sho5^jv;Dw+wyJc>-l< zq|_=Sp^u)$nO|p+Lc=hvWvY_~pVN+p;+d{mX1m|L1kHNtqKA-!h8u>o^Eve~mehwP z?y_rz*UnEh_LcU;GQZ3%o2Knzp5RsrhZ(`y4Ce#ls2u&LMl2CE;w(@-@Nv2x z%Ak3s?0SC}jK~^hcF|B=Qqmg9+PM|$LG4@YJCs2d;<9VSf-j^+EoSdVEDC7P|Db&{djaVUbBk-*;7Ba$;Cma59ZcU!t^Ux;{brQvh*`Y!~Z{ zKI0ET@h8hQ&KOmei56h{!mqs*e&S7Dj`|;vPM+io8_SA_WFV9NXJGiOI2{;2ivz*yQ~evvI8@J*oK)#2aD@$bk|5Xl2M7`0>3k{$fluggxfIkyA&N8E*&9&0UK?>hQQ9v2#K6Mq~rKbp=hWcRDiO7e4P;-?sjpKjDA6^Imml z+WW$Ncm|Ro`|qE2p_spa(!o{lkJ?f7AGaKBw)|^d)zJ=HyuY(sp2dC9=NeSHJG-f& zeNmru9o-7S!KBRm{}o>2oy9D+C}+i_zAEhT#AZ(US7hX2%m{!fRE5<-d1>T2YAI8O zNp@n9d)}qe6P<-j0$3u!o1s+KP`dq4(tG&6)sG@)GlXpH_ z`gmzQkm)~^>OYj}A5Ha-rd!97-edPI$G^6_ZU$}#e;icMvlX6?zALZKtOxJT{#E2( zMAFscNyh~9-uw3!C}WTMI7`K^9IlUQGF9EFs_ymHyFGt3@Gl0^Rj1O9m)J(=Wy2#n zEgHo9(fLod)9*E{+cMqzQ{DSB-G@@$htdtS^aFt~<#-WZH*yoDyxY^>?)%uq;XM4H zrYTc%AXRhV?m)Wc6z!-#dF$k=_{S5EjQXQIevJd$>EXU?wLQXbWZ;;|c5(vR_n>C) zok+Uo*k=5}5_DPggkWE|Xhj`;$v^qu0hH=A39h)yRuveeeOQ&$VDb{S(=X}faef*d z2U`@jA5n=FkUX>>47*X}KEF9btL5xH`dh~NQ zL8T0NOt;ehedYKst-;E2m42V{Z=hUAg7N8dM%nR|)ZLr~n{Khnfg{7{t1v$0;rV-% zhHj^5cuI(3x|_v)v-6Xt`{wO9iWHpop1pCS_Tb8ijoRJICpMh5jM#=!HA8oU>6(}E z^A5L9-8uzAYb+rgxYm;f*q3tlWgS)cZI}m5+t(JqjQ{14r2nOK)9I&X15uJksSfqUfu`LtC@YMJpeM|hd=f%sr1*OhS@yL}K14y}K76FNwdWxl5O6+4r z%&SyCI?m*-**3N}|b17?m9d43}`?pS;nLZDbIr zDvZF|2x_NW2i+J^G0J5&@-7PNrrR{#dT_(<-^#y8!0Z5QRLgeGKEmiH3}>NPFKrQX0w7clWw%hI@td5dT(Kt`SkDtSuye84U(_H(vpt4HP zD9m+!3+9z5eorgJcDsISuCw@oU5V{$B};<#u$O~ z7u&P-Un5XDMmNTa_Rvt?M>q2LXa2o8H-4=H2mE5Kz0jpO=df7vGq@8Ax!M8}nH`G@ znv4VCtUikqwSXY)s%{Y`x5q{me2X z9~yF4`U@cAkBidj_BfB9=YM12`1XIxRsJuW^&waDkgHYy8Xt1_t9!@^xc@EJ{E%yU z$hAJ?Iv;W^59t-L-4D5)54oX-+^&b5?;*G2A?JU{?E%i;DmXwxl8%R5J4@2V?rrSu zdB|;JdE&L7Wrb|JAP^a<9vWKy)*w7I^gc8UJ~RwJGz>j7?0;xD@X)ZA{q25e@Uz%m ze{1M|Xy|!p*!j@V|In~!!&H0ya>i7fGS#kH(xz?K^-oQbiMQQ+{V7M!r?VAizH#;7 zQx1nxOJ8@7bv*-u+Tc@s;6C#CT-j7K$8O#fm(7{;R> zR!r*}(jb#o-kYsx%j@asKlqrQggd}uuwR$cwO((?@GU96CD}Td<_~81_Uo+~zCFda zXAP~%ZAYOH>2xr{>WmrQo8rA0zAweYvuHTYAAPDjrsF%;^ci7yO4$8`qc_Tagnwd2 zDguY{HVVz3HE=phhObKTRcdkj)BJ(FPS2m=v(@;42L||#$^iQ?3ZXm(Wi04oltpuj zZ_e;NDZVG!JCx=R<#l#`gwMLrm;`JfFCzh{bN#lwnSmD0WPNNUkn%e$CwbCj;Oq01 yL%fY8#*?MR6G?I2keMJ#`NT@l5BM6~bvkT|(Gz6hYC7^(f^5KfY6rw*-~SgdNU;b2 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/ddl/__pycache__/sqlite.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f5aa6e7f05daa987a5e1a8d5ae0b4057cfbdfd5 GIT binary patch literal 8054 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 fuk*y!$0(jO bool: + return self.status == "equal" + + @property + def is_different(self) -> bool: + return self.status == "different" + + @property + def is_skip(self) -> bool: + return self.status == "skip" + + @classmethod + def Equal(cls) -> ComparisonResult: + """the constraints are equal.""" + return cls("equal", "The two constraints are equal") + + @classmethod + def Different(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult: + """the constraints are different for the provided reason(s).""" + return cls("different", ", ".join(util.to_list(reason))) + + @classmethod + def Skip(cls, reason: Union[str, Sequence[str]]) -> ComparisonResult: + """the constraint cannot be compared for the provided reason(s). + + The message is logged, but the constraints will be otherwise + considered equal, meaning that no migration command will be + generated. + """ + return cls("skip", ", ".join(util.to_list(reason))) + + +class _constraint_sig(Generic[_C]): + const: _C + + _sig: Tuple[Any, ...] + name: Optional[sqla_compat._ConstraintNameDefined] + + impl: DefaultImpl + + _is_index: ClassVar[bool] = False + _is_fk: ClassVar[bool] = False + _is_uq: ClassVar[bool] = False + + _is_metadata: bool + + def __init_subclass__(cls) -> None: + cls._register() + + @classmethod + def _register(cls): + raise NotImplementedError() + + def __init__( + self, is_metadata: bool, impl: DefaultImpl, const: _C + ) -> None: + raise NotImplementedError() + + def compare_to_reflected( + self, other: _constraint_sig[Any] + ) -> ComparisonResult: + assert self.impl is other.impl + assert self._is_metadata + assert not other._is_metadata + + return self._compare_to_reflected(other) + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + raise NotImplementedError() + + @classmethod + def from_constraint( + cls, is_metadata: bool, impl: DefaultImpl, constraint: _C + ) -> _constraint_sig[_C]: + # these could be cached by constraint/impl, however, if the + # constraint is modified in place, then the sig is wrong. the mysql + # impl currently does this, and if we fixed that we can't be sure + # someone else might do it too, so play it safe. + sig = _clsreg[constraint.__visit_name__](is_metadata, impl, constraint) + return sig + + def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]: + return sqla_compat._get_constraint_final_name( + self.const, context.dialect + ) + + @util.memoized_property + def is_named(self): + return sqla_compat._constraint_is_named(self.const, self.impl.dialect) + + @util.memoized_property + def unnamed(self) -> Tuple[Any, ...]: + return self._sig + + @util.memoized_property + def unnamed_no_options(self) -> Tuple[Any, ...]: + raise NotImplementedError() + + @util.memoized_property + def _full_sig(self) -> Tuple[Any, ...]: + return (self.name,) + self.unnamed + + def __eq__(self, other) -> bool: + return self._full_sig == other._full_sig + + def __ne__(self, other) -> bool: + return self._full_sig != other._full_sig + + def __hash__(self) -> int: + return hash(self._full_sig) + + +class _uq_constraint_sig(_constraint_sig[UniqueConstraint]): + _is_uq = True + + @classmethod + def _register(cls) -> None: + _clsreg["unique_constraint"] = cls + + is_unique = True + + def __init__( + self, + is_metadata: bool, + impl: DefaultImpl, + const: UniqueConstraint, + ) -> None: + self.impl = impl + self.const = const + self.name = sqla_compat.constraint_name_or_none(const.name) + self._sig = tuple(sorted([col.name for col in const.columns])) + self._is_metadata = is_metadata + + @property + def column_names(self) -> Tuple[str, ...]: + return tuple([col.name for col in self.const.columns]) + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + assert self._is_metadata + metadata_obj = self + conn_obj = other + + assert is_uq_sig(conn_obj) + return self.impl.compare_unique_constraint( + metadata_obj.const, conn_obj.const + ) + + +class _ix_constraint_sig(_constraint_sig[Index]): + _is_index = True + + name: sqla_compat._ConstraintName + + @classmethod + def _register(cls) -> None: + _clsreg["index"] = cls + + def __init__( + self, is_metadata: bool, impl: DefaultImpl, const: Index + ) -> None: + self.impl = impl + self.const = const + self.name = const.name + self.is_unique = bool(const.unique) + self._is_metadata = is_metadata + + def _compare_to_reflected( + self, other: _constraint_sig[_C] + ) -> ComparisonResult: + assert self._is_metadata + metadata_obj = self + conn_obj = other + + assert is_index_sig(conn_obj) + return self.impl.compare_indexes(metadata_obj.const, conn_obj.const) + + @util.memoized_property + def has_expressions(self): + return sqla_compat.is_expression_index(self.const) + + @util.memoized_property + def column_names(self) -> Tuple[str, ...]: + return tuple([col.name for col in self.const.columns]) + + @util.memoized_property + def column_names_optional(self) -> Tuple[Optional[str], ...]: + return tuple( + [getattr(col, "name", None) for col in self.const.expressions] + ) + + @util.memoized_property + def is_named(self): + return True + + @util.memoized_property + def unnamed(self): + return (self.is_unique,) + self.column_names_optional + + +class _fk_constraint_sig(_constraint_sig[ForeignKeyConstraint]): + _is_fk = True + + @classmethod + def _register(cls) -> None: + _clsreg["foreign_key_constraint"] = cls + + def __init__( + self, + is_metadata: bool, + impl: DefaultImpl, + const: ForeignKeyConstraint, + ) -> None: + self._is_metadata = is_metadata + + self.impl = impl + self.const = const + + self.name = sqla_compat.constraint_name_or_none(const.name) + + ( + self.source_schema, + self.source_table, + self.source_columns, + self.target_schema, + self.target_table, + self.target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) = sqla_compat._fk_spec(const) + + self._sig: Tuple[Any, ...] = ( + self.source_schema, + self.source_table, + tuple(self.source_columns), + self.target_schema, + self.target_table, + tuple(self.target_columns), + ) + ( + ( + (None if onupdate.lower() == "no action" else onupdate.lower()) + if onupdate + else None + ), + ( + (None if ondelete.lower() == "no action" else ondelete.lower()) + if ondelete + else None + ), + # convert initially + deferrable into one three-state value + ( + "initially_deferrable" + if initially and initially.lower() == "deferred" + else "deferrable" if deferrable else "not deferrable" + ), + ) + + @util.memoized_property + def unnamed_no_options(self): + return ( + self.source_schema, + self.source_table, + tuple(self.source_columns), + self.target_schema, + self.target_table, + tuple(self.target_columns), + ) + + +def is_index_sig(sig: _constraint_sig) -> TypeGuard[_ix_constraint_sig]: + return sig._is_index + + +def is_uq_sig(sig: _constraint_sig) -> TypeGuard[_uq_constraint_sig]: + return sig._is_uq + + +def is_fk_sig(sig: _constraint_sig) -> TypeGuard[_fk_constraint_sig]: + return sig._is_fk diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/base.py b/venv/lib/python3.12/site-packages/alembic/ddl/base.py new file mode 100644 index 0000000..30a3a15 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/base.py @@ -0,0 +1,406 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import functools +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import exc +from sqlalchemy import Integer +from sqlalchemy import types as sqltypes +from sqlalchemy.ext.compiler import compiles +from sqlalchemy.schema import Column +from sqlalchemy.schema import DDLElement +from sqlalchemy.sql.elements import ColumnElement +from sqlalchemy.sql.elements import quoted_name +from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.schema import FetchedValue + +from ..util.sqla_compat import _columns_for_constraint # noqa +from ..util.sqla_compat import _find_columns # noqa +from ..util.sqla_compat import _fk_spec # noqa +from ..util.sqla_compat import _is_type_bound # noqa +from ..util.sqla_compat import _table_for_constraint # noqa + +if TYPE_CHECKING: + + from sqlalchemy import Computed + from sqlalchemy import Identity + from sqlalchemy.sql.compiler import Compiled + from sqlalchemy.sql.compiler import DDLCompiler + from sqlalchemy.sql.type_api import TypeEngine + + from .impl import DefaultImpl + +_ServerDefaultType = Union[FetchedValue, str, TextClause, ColumnElement[Any]] + + +class AlterTable(DDLElement): + """Represent an ALTER TABLE statement. + + Only the string name and optional schema name of the table + is required, not a full Table object. + + """ + + def __init__( + self, + table_name: str, + schema: Optional[Union[quoted_name, str]] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + + +class RenameTable(AlterTable): + def __init__( + self, + old_table_name: str, + new_table_name: Union[quoted_name, str], + schema: Optional[Union[quoted_name, str]] = None, + ) -> None: + super().__init__(old_table_name, schema=schema) + self.new_table_name = new_table_name + + +class AlterColumn(AlterTable): + def __init__( + self, + name: str, + column_name: str, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_nullable: Optional[bool] = None, + existing_server_default: Optional[_ServerDefaultType] = None, + existing_comment: Optional[str] = None, + ) -> None: + super().__init__(name, schema=schema) + self.column_name = column_name + self.existing_type = ( + sqltypes.to_instance(existing_type) + if existing_type is not None + else None + ) + self.existing_nullable = existing_nullable + self.existing_server_default = existing_server_default + self.existing_comment = existing_comment + + +class ColumnNullable(AlterColumn): + def __init__( + self, name: str, column_name: str, nullable: bool, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.nullable = nullable + + +class ColumnType(AlterColumn): + def __init__( + self, name: str, column_name: str, type_: TypeEngine, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.type_ = sqltypes.to_instance(type_) + + +class ColumnName(AlterColumn): + def __init__( + self, name: str, column_name: str, newname: str, **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.newname = newname + + +class ColumnDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: Optional[_ServerDefaultType], + **kw, + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + + +class ComputedColumnDefault(AlterColumn): + def __init__( + self, name: str, column_name: str, default: Optional[Computed], **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + + +class IdentityColumnDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: Optional[Identity], + impl: DefaultImpl, + **kw, + ) -> None: + super().__init__(name, column_name, **kw) + self.default = default + self.impl = impl + + +class AddColumn(AlterTable): + def __init__( + self, + name: str, + column: Column[Any], + schema: Optional[Union[quoted_name, str]] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + super().__init__(name, schema=schema) + self.column = column + self.if_not_exists = if_not_exists + self.inline_references = inline_references + self.inline_primary_key = inline_primary_key + + +class DropColumn(AlterTable): + def __init__( + self, + name: str, + column: Column[Any], + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + ) -> None: + super().__init__(name, schema=schema) + self.column = column + self.if_exists = if_exists + + +class ColumnComment(AlterColumn): + def __init__( + self, name: str, column_name: str, comment: Optional[str], **kw + ) -> None: + super().__init__(name, column_name, **kw) + self.comment = comment + + +@compiles(RenameTable) +def visit_rename_table( + element: RenameTable, compiler: DDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, element.schema), + ) + + +@compiles(AddColumn) +def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + add_column( + compiler, + element.column, + if_not_exists=element.if_not_exists, + inline_references=element.inline_references, + inline_primary_key=element.inline_primary_key, + **kw, + ), + ) + + +@compiles(DropColumn) +def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + drop_column( + compiler, element.column.name, if_exists=element.if_exists, **kw + ), + ) + + +@compiles(ColumnNullable) +def visit_column_nullable( + element: ColumnNullable, compiler: DDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "DROP NOT NULL" if element.nullable else "SET NOT NULL", + ) + + +@compiles(ColumnType) +def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "TYPE %s" % format_type(compiler, element.type_), + ) + + +@compiles(ColumnName) +def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str: + return "%s RENAME %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnDefault) +def visit_column_default( + element: ColumnDefault, compiler: DDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ( + "SET DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DROP DEFAULT" + ), + ) + + +@compiles(ComputedColumnDefault) +def visit_computed_column( + element: ComputedColumnDefault, compiler: DDLCompiler, **kw +): + raise exc.CompileError( + 'Adding or removing a "computed" construct, e.g. GENERATED ' + "ALWAYS AS, to or from an existing column is not supported." + ) + + +@compiles(IdentityColumnDefault) +def visit_identity_column( + element: IdentityColumnDefault, compiler: DDLCompiler, **kw +): + raise exc.CompileError( + 'Adding, removing or modifying an "identity" construct, ' + "e.g. GENERATED AS IDENTITY, to or from an existing " + "column is not supported in this dialect." + ) + + +def quote_dotted( + name: Union[quoted_name, str], quote: functools.partial +) -> Union[quoted_name, str]: + """quote the elements of a dotted name""" + + if isinstance(name, quoted_name): + return quote(name) + result = ".".join([quote(x) for x in name.split(".")]) + return result + + +def format_table_name( + compiler: Compiled, + name: Union[quoted_name, str], + schema: Optional[Union[quoted_name, str]], +) -> Union[quoted_name, str]: + quote = functools.partial(compiler.preparer.quote) + if schema: + return quote_dotted(schema, quote) + "." + quote(name) + else: + return quote(name) + + +def format_column_name( + compiler: DDLCompiler, name: Optional[Union[quoted_name, str]] +) -> Union[quoted_name, str]: + return compiler.preparer.quote(name) # type: ignore[arg-type] + + +def format_server_default( + compiler: DDLCompiler, + default: Optional[_ServerDefaultType], +) -> str: + # this can be updated to use compiler.render_default_string + # for SQLAlchemy 2.0 and above; not in 1.4 + default_str = compiler.get_column_default_string( + Column("x", Integer, server_default=default) + ) + assert default_str is not None + return default_str + + +def format_type(compiler: DDLCompiler, type_: TypeEngine) -> str: + return compiler.dialect.type_compiler.process(type_) + + +def alter_table( + compiler: DDLCompiler, + name: str, + schema: Optional[str], +) -> str: + return "ALTER TABLE %s" % format_table_name(compiler, name, schema) + + +def drop_column( + compiler: DDLCompiler, name: str, if_exists: Optional[bool] = None, **kw +) -> str: + return "DROP COLUMN %s%s" % ( + "IF EXISTS " if if_exists else "", + format_column_name(compiler, name), + ) + + +def alter_column(compiler: DDLCompiler, name: str) -> str: + return "ALTER COLUMN %s" % format_column_name(compiler, name) + + +def add_column( + compiler: DDLCompiler, + column: Column[Any], + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + **kw, +) -> str: + text = "ADD COLUMN %s%s" % ( + "IF NOT EXISTS " if if_not_exists else "", + compiler.get_column_specification(column, **kw), + ) + + if inline_primary_key and column.primary_key: + text += " PRIMARY KEY" + + # Handle inline REFERENCES if requested + # Only render inline if there's exactly one foreign key AND the + # ForeignKeyConstraint is single-column, to avoid non-deterministic + # behavior with sets and to ensure proper syntax + if ( + inline_references + and len(column.foreign_keys) == 1 + and (fk := list(column.foreign_keys)[0]) + and fk.constraint is not None + and len(fk.constraint.columns) == 1 + ): + ref_col = fk.column + ref_table = ref_col.table + + # Format with proper quoting + if ref_table.schema: + table_name = "%s.%s" % ( + compiler.preparer.quote_schema(ref_table.schema), + compiler.preparer.quote(ref_table.name), + ) + else: + table_name = compiler.preparer.quote(ref_table.name) + + text += " REFERENCES %s (%s)" % ( + table_name, + compiler.preparer.quote(ref_col.name), + ) + + const = " ".join( + compiler.process(constraint) for constraint in column.constraints + ) + if const: + text += " " + const + + return text diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/impl.py b/venv/lib/python3.12/site-packages/alembic/ddl/impl.py new file mode 100644 index 0000000..964cd1f --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/impl.py @@ -0,0 +1,921 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import logging +import re +from typing import Any +from typing import Callable +from typing import Dict +from typing import Iterable +from typing import List +from typing import Mapping +from typing import NamedTuple +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import cast +from sqlalchemy import Column +from sqlalchemy import MetaData +from sqlalchemy import PrimaryKeyConstraint +from sqlalchemy import schema +from sqlalchemy import String +from sqlalchemy import Table +from sqlalchemy import text + +from . import _autogen +from . import base +from ._autogen import _constraint_sig as _constraint_sig +from ._autogen import ComparisonResult as ComparisonResult +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + from typing import TextIO + + from sqlalchemy.engine import Connection + from sqlalchemy.engine import Dialect + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.engine.interfaces import ReflectedForeignKeyConstraint + from sqlalchemy.engine.interfaces import ReflectedIndex + from sqlalchemy.engine.interfaces import ReflectedPrimaryKeyConstraint + from sqlalchemy.engine.interfaces import ReflectedUniqueConstraint + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql import ClauseElement + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefaultType + from ..autogenerate.api import AutogenContext + from ..operations.batch import ApplyBatchImpl + from ..operations.batch import BatchOperationsImpl + + _ReflectedConstraint = ( + ReflectedForeignKeyConstraint + | ReflectedPrimaryKeyConstraint + | ReflectedIndex + | ReflectedUniqueConstraint + ) +log = logging.getLogger(__name__) + + +class ImplMeta(type): + def __init__( + cls, + classname: str, + bases: Tuple[Type[DefaultImpl]], + dict_: Dict[str, Any], + ): + newtype = type.__init__(cls, classname, bases, dict_) + if "__dialect__" in dict_: + _impls[dict_["__dialect__"]] = cls # type: ignore[assignment] + return newtype + + +_impls: Dict[str, Type[DefaultImpl]] = {} + + +class DefaultImpl(metaclass=ImplMeta): + """Provide the entrypoint for major migration operations, + including database-specific behavioral variances. + + While individual SQL/DDL constructs already provide + for database-specific implementations, variances here + allow for entirely different sequences of operations + to take place for a particular migration, such as + SQL Server's special 'IDENTITY INSERT' step for + bulk inserts. + + """ + + __dialect__ = "default" + + transactional_ddl = False + command_terminator = ";" + type_synonyms: Tuple[Set[str], ...] = ({"NUMERIC", "DECIMAL"},) + type_arg_extract: Sequence[str] = () + # These attributes are deprecated in SQLAlchemy via #10247. They need to + # be ignored to support older version that did not use dialect kwargs. + # They only apply to Oracle and are replaced by oracle_order, + # oracle_on_null + identity_attrs_ignore: Tuple[str, ...] = ("order", "on_null") + + def __init__( + self, + dialect: Dialect, + connection: Optional[Connection], + as_sql: bool, + transactional_ddl: Optional[bool], + output_buffer: Optional[TextIO], + context_opts: Dict[str, Any], + ) -> None: + self.dialect = dialect + self.connection = connection + self.as_sql = as_sql + self.literal_binds = context_opts.get("literal_binds", False) + + self.output_buffer = output_buffer + self.memo: dict = {} + self.context_opts = context_opts + if transactional_ddl is not None: + self.transactional_ddl = transactional_ddl + + if self.literal_binds: + if not self.as_sql: + raise util.CommandError( + "Can't use literal_binds setting without as_sql mode" + ) + + @classmethod + def get_by_dialect(cls, dialect: Dialect) -> Type[DefaultImpl]: + return _impls[dialect.name] + + def static_output(self, text: str) -> None: + assert self.output_buffer is not None + self.output_buffer.write(text + "\n\n") + self.output_buffer.flush() + + def version_table_impl( + self, + *, + version_table: str, + version_table_schema: Optional[str], + version_table_pk: bool, + **kw: Any, + ) -> Table: + """Generate a :class:`.Table` object which will be used as the + structure for the Alembic version table. + + Third party dialects may override this hook to provide an alternate + structure for this :class:`.Table`; requirements are only that it + be named based on the ``version_table`` parameter and contains + at least a single string-holding column named ``version_num``. + + .. versionadded:: 1.14 + + """ + vt = Table( + version_table, + MetaData(), + Column("version_num", String(32), nullable=False), + schema=version_table_schema, + ) + if version_table_pk: + vt.append_constraint( + PrimaryKeyConstraint( + "version_num", name=f"{version_table}_pkc" + ) + ) + + return vt + + def requires_recreate_in_batch( + self, batch_op: BatchOperationsImpl + ) -> bool: + """Return True if the given :class:`.BatchOperationsImpl` + would need the table to be recreated and copied in order to + proceed. + + Normally, only returns True on SQLite when operations other + than add_column are present. + + """ + return False + + def prep_table_for_batch( + self, batch_impl: ApplyBatchImpl, table: Table + ) -> None: + """perform any operations needed on a table before a new + one is created to replace it in batch mode. + + the PG dialect uses this to drop constraints on the table + before the new one uses those same names. + + """ + + @property + def bind(self) -> Optional[Connection]: + return self.connection + + def _exec( + self, + construct: Union[Executable, str], + execution_options: Optional[Mapping[str, Any]] = None, + multiparams: Optional[Sequence[Mapping[str, Any]]] = None, + params: Mapping[str, Any] = util.immutabledict(), + ) -> Optional[CursorResult]: + if isinstance(construct, str): + construct = text(construct) + if self.as_sql: + if multiparams is not None or params: + raise TypeError("SQL parameters not allowed with as_sql") + + compile_kw: dict[str, Any] + if self.literal_binds and not isinstance( + construct, schema.DDLElement + ): + compile_kw = dict(compile_kwargs={"literal_binds": True}) + else: + compile_kw = {} + + if TYPE_CHECKING: + assert isinstance(construct, ClauseElement) + compiled = construct.compile(dialect=self.dialect, **compile_kw) + self.static_output( + str(compiled).replace("\t", " ").strip() + + self.command_terminator + ) + return None + else: + conn = self.connection + assert conn is not None + if execution_options: + conn = conn.execution_options(**execution_options) + + if params and multiparams is not None: + raise TypeError( + "Can't send params and multiparams at the same time" + ) + + if multiparams: + return conn.execute(construct, multiparams) + else: + return conn.execute(construct, params) + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + self._exec(sql, execution_options) + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + existing_comment: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = None, + existing_nullable: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + **kw: Any, + ) -> None: + if autoincrement is not None or existing_autoincrement is not None: + util.warn( + "autoincrement and existing_autoincrement " + "only make sense for MySQL", + stacklevel=3, + ) + if nullable is not None: + self._exec( + base.ColumnNullable( + table_name, + column_name, + nullable, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + if server_default is not False: + kw = {} + cls_: Type[ + Union[ + base.ComputedColumnDefault, + base.IdentityColumnDefault, + base.ColumnDefault, + ] + ] + if sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + cls_ = base.ComputedColumnDefault + elif sqla_compat._server_default_is_identity( + server_default, existing_server_default + ): + cls_ = base.IdentityColumnDefault + kw["impl"] = self + else: + cls_ = base.ColumnDefault + self._exec( + cls_( + table_name, + column_name, + server_default, # type:ignore[arg-type] + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + **kw, + ) + ) + if type_ is not None: + self._exec( + base.ColumnType( + table_name, + column_name, + type_, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + + if comment is not False: + self._exec( + base.ColumnComment( + table_name, + column_name, + comment, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + ) + ) + + # do the new name last ;) + if name is not None: + self._exec( + base.ColumnName( + table_name, + column_name, + name, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + ) + ) + + def add_column( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[Union[str, quoted_name]] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + self._exec( + base.AddColumn( + table_name, + column, + schema=schema, + if_not_exists=if_not_exists, + inline_references=inline_references, + inline_primary_key=inline_primary_key, + ) + ) + + def drop_column( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw, + ) -> None: + self._exec( + base.DropColumn( + table_name, column, schema=schema, if_exists=if_exists + ) + ) + + def add_constraint(self, const: Any, **kw: Any) -> None: + if const._create_rule is None or const._create_rule(self): + if sqla_compat.sqla_2_1: + # this should be the default already + kw.setdefault("isolate_from_table", True) + self._exec(schema.AddConstraint(const, **kw)) + + def drop_constraint(self, const: Constraint, **kw: Any) -> None: + self._exec(schema.DropConstraint(const, **kw)) + + def rename_table( + self, + old_table_name: str, + new_table_name: Union[str, quoted_name], + schema: Optional[Union[str, quoted_name]] = None, + ) -> None: + self._exec( + base.RenameTable(old_table_name, new_table_name, schema=schema) + ) + + def create_table(self, table: Table, **kw: Any) -> None: + table.dispatch.before_create( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + self._exec(schema.CreateTable(table, **kw)) + table.dispatch.after_create( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + for index in table.indexes: + self._exec(schema.CreateIndex(index)) + + with_comment = ( + self.dialect.supports_comments and not self.dialect.inline_comments + ) + comment = table.comment + if comment and with_comment: + self.create_table_comment(table) + + for column in table.columns: + comment = column.comment + if comment and with_comment: + self.create_column_comment(column) + + def drop_table(self, table: Table, **kw: Any) -> None: + table.dispatch.before_drop( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + self._exec(schema.DropTable(table, **kw)) + table.dispatch.after_drop( + table, self.connection, checkfirst=False, _ddl_runner=self + ) + + def create_index(self, index: Index, **kw: Any) -> None: + self._exec(schema.CreateIndex(index, **kw)) + + def create_table_comment(self, table: Table) -> None: + self._exec(schema.SetTableComment(table)) + + def drop_table_comment(self, table: Table) -> None: + self._exec(schema.DropTableComment(table)) + + def create_column_comment(self, column: Column[Any]) -> None: + self._exec(schema.SetColumnComment(column)) + + def drop_index(self, index: Index, **kw: Any) -> None: + self._exec(schema.DropIndex(index, **kw)) + + def bulk_insert( + self, + table: Union[TableClause, Table], + rows: List[dict], + multiinsert: bool = True, + ) -> None: + if not isinstance(rows, list): + raise TypeError("List expected") + elif rows and not isinstance(rows[0], dict): + raise TypeError("List of dictionaries expected") + if self.as_sql: + for row in rows: + self._exec( + table.insert() + .inline() + .values( + **{ + k: ( + sqla_compat._literal_bindparam( + k, v, type_=table.c[k].type + ) + if not isinstance( + v, sqla_compat._literal_bindparam + ) + else v + ) + for k, v in row.items() + } + ) + ) + else: + if rows: + if multiinsert: + self._exec(table.insert().inline(), multiparams=rows) + else: + for row in rows: + self._exec(table.insert().inline().values(**row)) + + def _tokenize_column_type(self, column: Column) -> Params: + definition: str + definition = self.dialect.type_compiler.process(column.type).lower() + + # tokenize the SQLAlchemy-generated version of a type, so that + # the two can be compared. + # + # examples: + # NUMERIC(10, 5) + # TIMESTAMP WITH TIMEZONE + # INTEGER UNSIGNED + # INTEGER (10) UNSIGNED + # INTEGER(10) UNSIGNED + # varchar character set utf8 + # + + tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition) + + term_tokens: List[str] = [] + paren_term = None + + for token in tokens: + if re.match(r"^\(.*\)$", token): + paren_term = token + else: + term_tokens.append(token) + + params = Params(term_tokens[0], term_tokens[1:], [], {}) + + if paren_term: + term: str + for term in re.findall("[^(),]+", paren_term): + if "=" in term: + key, val = term.split("=") + params.kwargs[key.strip()] = val.strip() + else: + params.args.append(term.strip()) + + return params + + def _column_types_match( + self, inspector_params: Params, metadata_params: Params + ) -> bool: + if inspector_params.token0 == metadata_params.token0: + return True + + synonyms = [{t.lower() for t in batch} for batch in self.type_synonyms] + inspector_all_terms = " ".join( + [inspector_params.token0] + inspector_params.tokens + ) + metadata_all_terms = " ".join( + [metadata_params.token0] + metadata_params.tokens + ) + + for batch in synonyms: + if {inspector_all_terms, metadata_all_terms}.issubset(batch) or { + inspector_params.token0, + metadata_params.token0, + }.issubset(batch): + return True + return False + + def _column_args_match( + self, inspected_params: Params, meta_params: Params + ) -> bool: + """We want to compare column parameters. However, we only want + to compare parameters that are set. If they both have `collation`, + we want to make sure they are the same. However, if only one + specifies it, dont flag it for being less specific + """ + + if ( + len(meta_params.tokens) == len(inspected_params.tokens) + and meta_params.tokens != inspected_params.tokens + ): + return False + + if ( + len(meta_params.args) == len(inspected_params.args) + and meta_params.args != inspected_params.args + ): + return False + + insp = " ".join(inspected_params.tokens).lower() + meta = " ".join(meta_params.tokens).lower() + + for reg in self.type_arg_extract: + mi = re.search(reg, insp) + mm = re.search(reg, meta) + + if mi and mm and mi.group(1) != mm.group(1): + return False + + return True + + def compare_type( + self, inspector_column: Column[Any], metadata_column: Column + ) -> bool: + """Returns True if there ARE differences between the types of the two + columns. Takes impl.type_synonyms into account between retrospected + and metadata types + """ + inspector_params = self._tokenize_column_type(inspector_column) + metadata_params = self._tokenize_column_type(metadata_column) + + if not self._column_types_match(inspector_params, metadata_params): + return True + if not self._column_args_match(inspector_params, metadata_params): + return True + return False + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + return rendered_inspector_default != rendered_metadata_default + + def correct_for_autogen_constraints( + self, + conn_uniques: Set[UniqueConstraint], + conn_indexes: Set[Index], + metadata_unique_constraints: Set[UniqueConstraint], + metadata_indexes: Set[Index], + ) -> None: + pass + + def cast_for_batch_migrate(self, existing, existing_transfer, new_type): + if existing.type._type_affinity is not new_type._type_affinity: + existing_transfer["expr"] = cast( + existing_transfer["expr"], new_type + ) + + def render_ddl_sql_expr( + self, expr: ClauseElement, is_server_default: bool = False, **kw: Any + ) -> str: + """Render a SQL expression that is typically a server default, + index expression, etc. + + """ + + compile_kw = {"literal_binds": True, "include_table": False} + + return str( + expr.compile(dialect=self.dialect, compile_kwargs=compile_kw) + ) + + def _compat_autogen_column_reflect(self, inspector: Inspector) -> Callable: + return self.autogen_column_reflect + + def correct_for_autogen_foreignkeys( + self, + conn_fks: Set[ForeignKeyConstraint], + metadata_fks: Set[ForeignKeyConstraint], + ) -> None: + pass + + def autogen_column_reflect(self, inspector, table, column_info): + """A hook that is attached to the 'column_reflect' event for when + a Table is reflected from the database during the autogenerate + process. + + Dialects can elect to modify the information gathered here. + + """ + + def start_migrations(self) -> None: + """A hook called when :meth:`.EnvironmentContext.run_migrations` + is called. + + Implementations can set up per-migration-run state here. + + """ + + def emit_begin(self) -> None: + """Emit the string ``BEGIN``, or the backend-specific + equivalent, on the current connection context. + + This is used in offline mode and typically + via :meth:`.EnvironmentContext.begin_transaction`. + + """ + self.static_output("BEGIN" + self.command_terminator) + + def emit_commit(self) -> None: + """Emit the string ``COMMIT``, or the backend-specific + equivalent, on the current connection context. + + This is used in offline mode and typically + via :meth:`.EnvironmentContext.begin_transaction`. + + """ + self.static_output("COMMIT" + self.command_terminator) + + def render_type( + self, type_obj: TypeEngine, autogen_context: AutogenContext + ) -> Union[str, Literal[False]]: + return False + + def _compare_identity_default(self, metadata_identity, inspector_identity): + # ignored contains the attributes that were not considered + # because assumed to their default values in the db. + diff, ignored = _compare_identity_options( + metadata_identity, + inspector_identity, + schema.Identity(), + skip={"always"}, + ) + + meta_always = getattr(metadata_identity, "always", None) + inspector_always = getattr(inspector_identity, "always", None) + # None and False are the same in this comparison + if bool(meta_always) != bool(inspector_always): + diff.add("always") + + diff.difference_update(self.identity_attrs_ignore) + + # returns 3 values: + return ( + # different identity attributes + diff, + # ignored identity attributes + ignored, + # if the two identity should be considered different + bool(diff) or bool(metadata_identity) != bool(inspector_identity), + ) + + def _compare_index_unique( + self, metadata_index: Index, reflected_index: Index + ) -> Optional[str]: + conn_unique = bool(reflected_index.unique) + meta_unique = bool(metadata_index.unique) + if conn_unique != meta_unique: + return f"unique={conn_unique} to unique={meta_unique}" + else: + return None + + def _create_metadata_constraint_sig( + self, constraint: _autogen._C, **opts: Any + ) -> _constraint_sig[_autogen._C]: + return _constraint_sig.from_constraint(True, self, constraint, **opts) + + def _create_reflected_constraint_sig( + self, constraint: _autogen._C, **opts: Any + ) -> _constraint_sig[_autogen._C]: + return _constraint_sig.from_constraint(False, self, constraint, **opts) + + def compare_indexes( + self, + metadata_index: Index, + reflected_index: Index, + ) -> ComparisonResult: + """Compare two indexes by comparing the signature generated by + ``create_index_sig``. + + This method returns a ``ComparisonResult``. + """ + msg: List[str] = [] + unique_msg = self._compare_index_unique( + metadata_index, reflected_index + ) + if unique_msg: + msg.append(unique_msg) + m_sig = self._create_metadata_constraint_sig(metadata_index) + r_sig = self._create_reflected_constraint_sig(reflected_index) + + assert _autogen.is_index_sig(m_sig) + assert _autogen.is_index_sig(r_sig) + + # The assumption is that the index have no expression + for sig in m_sig, r_sig: + if sig.has_expressions: + log.warning( + "Generating approximate signature for index %s. " + "The dialect " + "implementation should either skip expression indexes " + "or provide a custom implementation.", + sig.const, + ) + + if m_sig.column_names != r_sig.column_names: + msg.append( + f"expression {r_sig.column_names} to {m_sig.column_names}" + ) + + if msg: + return ComparisonResult.Different(msg) + else: + return ComparisonResult.Equal() + + def compare_unique_constraint( + self, + metadata_constraint: UniqueConstraint, + reflected_constraint: UniqueConstraint, + ) -> ComparisonResult: + """Compare two unique constraints by comparing the two signatures. + + The arguments are two tuples that contain the unique constraint and + the signatures generated by ``create_unique_constraint_sig``. + + This method returns a ``ComparisonResult``. + """ + metadata_tup = self._create_metadata_constraint_sig( + metadata_constraint + ) + reflected_tup = self._create_reflected_constraint_sig( + reflected_constraint + ) + + meta_sig = metadata_tup.unnamed + conn_sig = reflected_tup.unnamed + if conn_sig != meta_sig: + return ComparisonResult.Different( + f"expression {conn_sig} to {meta_sig}" + ) + else: + return ComparisonResult.Equal() + + def _skip_functional_indexes(self, metadata_indexes, conn_indexes): + conn_indexes_by_name = {c.name: c for c in conn_indexes} + + for idx in list(metadata_indexes): + if idx.name in conn_indexes_by_name: + continue + iex = sqla_compat.is_expression_index(idx) + if iex: + util.warn( + "autogenerate skipping metadata-specified " + "expression-based index " + f"{idx.name!r}; dialect {self.__dialect__!r} under " + f"SQLAlchemy {sqla_compat.sqlalchemy_version} can't " + "reflect these indexes so they can't be compared" + ) + metadata_indexes.discard(idx) + + def adjust_reflected_dialect_options( + self, reflected_object: _ReflectedConstraint, kind: str + ) -> Dict[str, Any]: + return reflected_object.get("dialect_options", {}) # type: ignore[return-value] # noqa: E501 + + +class Params(NamedTuple): + token0: str + tokens: List[str] + args: List[str] + kwargs: Dict[str, str] + + +def _compare_identity_options( + metadata_io: Union[schema.Identity, schema.Sequence, None], + inspector_io: Union[schema.Identity, schema.Sequence, None], + default_io: Union[schema.Identity, schema.Sequence], + skip: Set[str], +): + # this can be used for identity or sequence compare. + # default_io is an instance of IdentityOption with all attributes to the + # default value. + meta_d = sqla_compat._get_identity_options_dict(metadata_io) + insp_d = sqla_compat._get_identity_options_dict(inspector_io) + + diff = set() + ignored_attr = set() + + def check_dicts( + meta_dict: Mapping[str, Any], + insp_dict: Mapping[str, Any], + default_dict: Mapping[str, Any], + attrs: Iterable[str], + ): + for attr in set(attrs).difference(skip): + meta_value = meta_dict.get(attr) + insp_value = insp_dict.get(attr) + if insp_value != meta_value: + default_value = default_dict.get(attr) + if meta_value == default_value: + ignored_attr.add(attr) + else: + diff.add(attr) + + check_dicts( + meta_d, + insp_d, + sqla_compat._get_identity_options_dict(default_io), + set(meta_d).union(insp_d), + ) + if sqla_compat.identity_has_dialect_kwargs: + assert hasattr(default_io, "dialect_kwargs") + # use only the dialect kwargs in inspector_io since metadata_io + # can have options for many backends + check_dicts( + getattr(metadata_io, "dialect_kwargs", {}), + getattr(inspector_io, "dialect_kwargs", {}), + default_io.dialect_kwargs, + getattr(inspector_io, "dialect_kwargs", {}), + ) + + return diff, ignored_attr diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/mssql.py b/venv/lib/python3.12/site-packages/alembic/ddl/mssql.py new file mode 100644 index 0000000..91cd9e4 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/mssql.py @@ -0,0 +1,523 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import types as sqltypes +from sqlalchemy.schema import Column +from sqlalchemy.schema import CreateIndex +from sqlalchemy.sql.base import Executable +from sqlalchemy.sql.elements import ClauseElement + +from .base import AddColumn +from .base import alter_column +from .base import alter_table +from .base import ColumnComment +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .base import format_table_name +from .base import format_type +from .base import RenameTable +from .impl import DefaultImpl +from .. import util +from ..util import sqla_compat +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.dialects.mssql.base import MSDDLCompiler + from sqlalchemy.dialects.mssql.base import MSSQLCompiler + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefaultType + from .impl import _ReflectedConstraint + + +class MSSQLImpl(DefaultImpl): + __dialect__ = "mssql" + transactional_ddl = True + batch_separator = "GO" + + type_synonyms = DefaultImpl.type_synonyms + ({"VARCHAR", "NVARCHAR"},) + identity_attrs_ignore = DefaultImpl.identity_attrs_ignore + ( + "minvalue", + "maxvalue", + "nominvalue", + "nomaxvalue", + "cycle", + "cache", + ) + + def __init__(self, *arg, **kw) -> None: + super().__init__(*arg, **kw) + self.batch_separator = self.context_opts.get( + "mssql_batch_separator", self.batch_separator + ) + + def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]: + result = super()._exec(construct, *args, **kw) + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + return result + + def emit_begin(self) -> None: + self.static_output("BEGIN TRANSACTION" + self.command_terminator) + + def emit_commit(self) -> None: + super().emit_commit() + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Union[ + _ServerDefaultType, Literal[False], None + ] = None, + existing_nullable: Optional[bool] = None, + **kw: Any, + ) -> None: + if nullable is not None: + if type_ is not None: + # the NULL/NOT NULL alter will handle + # the type alteration + existing_type = type_ + type_ = None + elif existing_type is None: + raise util.CommandError( + "MS-SQL ALTER COLUMN operations " + "with NULL or NOT NULL require the " + "existing_type or a new type_ be passed." + ) + elif existing_nullable is not None and type_ is not None: + nullable = existing_nullable + + # the NULL/NOT NULL alter will handle + # the type alteration + existing_type = type_ + type_ = None + + elif type_ is not None: + util.warn( + "MS-SQL ALTER COLUMN operations that specify type_= " + "should also specify a nullable= or " + "existing_nullable= argument to avoid implicit conversion " + "of NOT NULL columns to NULL." + ) + + used_default = False + if sqla_compat._server_default_is_identity( + server_default, existing_server_default + ) or sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + used_default = True + kw["server_default"] = server_default + kw["existing_server_default"] = existing_server_default + + # drop existing default constraints before changing type + # or default, see issue #1744 + if ( + server_default is not False + and used_default is False + and ( + existing_server_default is not False or server_default is None + ) + ): + self._exec( + _ExecDropConstraint( + table_name, + column_name, + "sys.default_constraints", + schema, + ) + ) + + # TODO: see why these two alter_columns can't be called + # at once. joining them works but some of the mssql tests + # seem to expect something different + super().alter_column( + table_name, + column_name, + nullable=nullable, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_nullable=existing_nullable, + **kw, + ) + + if server_default is not False and used_default is False: + if server_default is not None: + super().alter_column( + table_name, + column_name, + schema=schema, + server_default=server_default, + ) + + if name is not None: + super().alter_column( + table_name, column_name, schema=schema, name=name + ) + + def create_index(self, index: Index, **kw: Any) -> None: + # this likely defaults to None if not present, so get() + # should normally not return the default value. being + # defensive in any case + mssql_include = index.kwargs.get("mssql_include", None) or () + assert index.table is not None + for col in mssql_include: + if col not in index.table.c: + index.table.append_column(Column(col, sqltypes.NullType)) + self._exec(CreateIndex(index, **kw)) + + def bulk_insert( # type:ignore[override] + self, table: Union[TableClause, Table], rows: List[dict], **kw: Any + ) -> None: + if self.as_sql: + self._exec( + "SET IDENTITY_INSERT %s ON" + % self.dialect.identifier_preparer.format_table(table) + ) + super().bulk_insert(table, rows, **kw) + self._exec( + "SET IDENTITY_INSERT %s OFF" + % self.dialect.identifier_preparer.format_table(table) + ) + else: + super().bulk_insert(table, rows, **kw) + + def drop_column( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + **kw, + ) -> None: + drop_default = kw.pop("mssql_drop_default", False) + if drop_default: + self._exec( + _ExecDropConstraint( + table_name, column, "sys.default_constraints", schema + ) + ) + drop_check = kw.pop("mssql_drop_check", False) + if drop_check: + self._exec( + _ExecDropConstraint( + table_name, column, "sys.check_constraints", schema + ) + ) + drop_fks = kw.pop("mssql_drop_foreign_key", False) + if drop_fks: + self._exec(_ExecDropFKConstraint(table_name, column, schema)) + super().drop_column(table_name, column, schema=schema, **kw) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"[\(\) \"\']", "", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + # SQL Server collapses whitespace and adds arbitrary parenthesis + # within expressions. our only option is collapse all of it + + rendered_inspector_default = re.sub( + r"[\(\) \"\']", "", rendered_inspector_default + ) + + return rendered_inspector_default != rendered_metadata_default + + def _compare_identity_default(self, metadata_identity, inspector_identity): + diff, ignored, is_alter = super()._compare_identity_default( + metadata_identity, inspector_identity + ) + + if ( + metadata_identity is None + and inspector_identity is not None + and not diff + and inspector_identity.column is not None + and inspector_identity.column.primary_key + ): + # mssql reflect primary keys with autoincrement as identity + # columns. if no different attributes are present ignore them + is_alter = False + + return diff, ignored, is_alter + + def adjust_reflected_dialect_options( + self, reflected_object: _ReflectedConstraint, kind: str + ) -> Dict[str, Any]: + options: Dict[str, Any] + options = reflected_object.get("dialect_options", {}).copy() # type: ignore[attr-defined] # noqa: E501 + if not options.get("mssql_include"): + options.pop("mssql_include", None) + if not options.get("mssql_clustered"): + options.pop("mssql_clustered", None) + return options + + +class _ExecDropConstraint(Executable, ClauseElement): + inherit_cache = False + + def __init__( + self, + tname: str, + colname: Union[Column[Any], str], + type_: str, + schema: Optional[str], + ) -> None: + self.tname = tname + self.colname = colname + self.type_ = type_ + self.schema = schema + + +class _ExecDropFKConstraint(Executable, ClauseElement): + inherit_cache = False + + def __init__( + self, tname: str, colname: Column[Any], schema: Optional[str] + ) -> None: + self.tname = tname + self.colname = colname + self.schema = schema + + +@compiles(_ExecDropConstraint, "mssql") +def _exec_drop_col_constraint( + element: _ExecDropConstraint, compiler: MSSQLCompiler, **kw +) -> str: + schema, tname, colname, type_ = ( + element.schema, + element.tname, + element.colname, + element.type_, + ) + # from http://www.mssqltips.com/sqlservertip/1425/\ + # working-with-default-constraints-in-sql-server/ + return """declare @const_name varchar(256) +select @const_name = QUOTENAME([name]) from %(type)s +where parent_object_id = object_id('%(schema_dot)s%(tname)s') +and col_name(parent_object_id, parent_column_id) = '%(colname)s' +exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % { + "type": type_, + "tname": tname, + "colname": colname, + "tname_quoted": format_table_name(compiler, tname, schema), + "schema_dot": schema + "." if schema else "", + } + + +@compiles(_ExecDropFKConstraint, "mssql") +def _exec_drop_col_fk_constraint( + element: _ExecDropFKConstraint, compiler: MSSQLCompiler, **kw +) -> str: + schema, tname, colname = element.schema, element.tname, element.colname + + return """declare @const_name varchar(256) +select @const_name = QUOTENAME([name]) from +sys.foreign_keys fk join sys.foreign_key_columns fkc +on fk.object_id=fkc.constraint_object_id +where fkc.parent_object_id = object_id('%(schema_dot)s%(tname)s') +and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s' +exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % { + "tname": tname, + "colname": colname, + "tname_quoted": format_table_name(compiler, tname, schema), + "schema_dot": schema + "." if schema else "", + } + + +@compiles(AddColumn, "mssql") +def visit_add_column(element: AddColumn, compiler: MSDDLCompiler, **kw) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + mssql_add_column(compiler, element.column, **kw), + ) + + +def mssql_add_column( + compiler: MSDDLCompiler, column: Column[Any], **kw +) -> str: + return "ADD %s" % compiler.get_column_specification(column, **kw) + + +@compiles(ColumnNullable, "mssql") +def visit_column_nullable( + element: ColumnNullable, compiler: MSDDLCompiler, **kw +) -> str: + return "%s %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + format_type(compiler, element.existing_type), # type: ignore[arg-type] + "NULL" if element.nullable else "NOT NULL", + ) + + +@compiles(ColumnDefault, "mssql") +def visit_column_default( + element: ColumnDefault, compiler: MSDDLCompiler, **kw +) -> str: + # TODO: there can also be a named constraint + # with ADD CONSTRAINT here + return "%s ADD DEFAULT %s FOR %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_server_default(compiler, element.default), + format_column_name(compiler, element.column_name), + ) + + +@compiles(ColumnName, "mssql") +def visit_rename_column( + element: ColumnName, compiler: MSDDLCompiler, **kw +) -> str: + return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % ( + format_table_name(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnType, "mssql") +def visit_column_type( + element: ColumnType, compiler: MSDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + format_type(compiler, element.type_), + ) + + +@compiles(RenameTable, "mssql") +def visit_rename_table( + element: RenameTable, compiler: MSDDLCompiler, **kw +) -> str: + return "EXEC sp_rename '%s', %s" % ( + format_table_name(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +def _add_column_comment( + compiler: MSDDLCompiler, + schema: Optional[str], + tname: str, + cname: str, + comment: str, +) -> str: + schema_name = schema if schema else compiler.dialect.default_schema_name + assert schema_name + return ( + "exec sp_addextendedproperty 'MS_Description', {}, " + "'schema', {}, 'table', {}, 'column', {}".format( + compiler.sql_compiler.render_literal_value( + comment, sqltypes.NVARCHAR() + ), + compiler.preparer.quote_schema(schema_name), + compiler.preparer.quote(tname), + compiler.preparer.quote(cname), + ) + ) + + +def _update_column_comment( + compiler: MSDDLCompiler, + schema: Optional[str], + tname: str, + cname: str, + comment: str, +) -> str: + schema_name = schema if schema else compiler.dialect.default_schema_name + assert schema_name + return ( + "exec sp_updateextendedproperty 'MS_Description', {}, " + "'schema', {}, 'table', {}, 'column', {}".format( + compiler.sql_compiler.render_literal_value( + comment, sqltypes.NVARCHAR() + ), + compiler.preparer.quote_schema(schema_name), + compiler.preparer.quote(tname), + compiler.preparer.quote(cname), + ) + ) + + +def _drop_column_comment( + compiler: MSDDLCompiler, schema: Optional[str], tname: str, cname: str +) -> str: + schema_name = schema if schema else compiler.dialect.default_schema_name + assert schema_name + return ( + "exec sp_dropextendedproperty 'MS_Description', " + "'schema', {}, 'table', {}, 'column', {}".format( + compiler.preparer.quote_schema(schema_name), + compiler.preparer.quote(tname), + compiler.preparer.quote(cname), + ) + ) + + +@compiles(ColumnComment, "mssql") +def visit_column_comment( + element: ColumnComment, compiler: MSDDLCompiler, **kw: Any +) -> str: + if element.comment is not None: + if element.existing_comment is not None: + return _update_column_comment( + compiler, + element.schema, + element.table_name, + element.column_name, + element.comment, + ) + else: + return _add_column_comment( + compiler, + element.schema, + element.table_name, + element.column_name, + element.comment, + ) + else: + return _drop_column_comment( + compiler, element.schema, element.table_name, element.column_name + ) diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/mysql.py b/venv/lib/python3.12/site-packages/alembic/ddl/mysql.py new file mode 100644 index 0000000..27f808b --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/mysql.py @@ -0,0 +1,526 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import schema +from sqlalchemy import types as sqltypes +from sqlalchemy.sql import elements +from sqlalchemy.sql import functions +from sqlalchemy.sql import operators + +from .base import alter_table +from .base import AlterColumn +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .impl import DefaultImpl +from .. import util +from ..util import sqla_compat +from ..util.sqla_compat import _is_type_bound +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.dialects.mysql.base import MySQLDDLCompiler + from sqlalchemy.sql.ddl import DropConstraint + from sqlalchemy.sql.elements import ClauseElement + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefaultType + + +class MySQLImpl(DefaultImpl): + __dialect__ = "mysql" + + transactional_ddl = False + type_synonyms = DefaultImpl.type_synonyms + ( + {"BOOL", "TINYINT"}, + {"JSON", "LONGTEXT"}, + ) + type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"] + + def render_ddl_sql_expr( + self, + expr: ClauseElement, + is_server_default: bool = False, + is_index: bool = False, + **kw: Any, + ) -> str: + # apply Grouping to index expressions; + # see https://github.com/sqlalchemy/sqlalchemy/blob/ + # 36da2eaf3e23269f2cf28420ae73674beafd0661/ + # lib/sqlalchemy/dialects/mysql/base.py#L2191 + if is_index and ( + isinstance(expr, elements.BinaryExpression) + or ( + isinstance(expr, elements.UnaryExpression) + and expr.modifier not in (operators.desc_op, operators.asc_op) + ) + or isinstance(expr, functions.FunctionElement) + ): + expr = elements.Grouping(expr) + + return super().render_ddl_sql_expr( + expr, is_server_default=is_server_default, is_index=is_index, **kw + ) + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = None, + existing_nullable: Optional[bool] = None, + autoincrement: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + existing_comment: Optional[str] = None, + **kw: Any, + ) -> None: + if sqla_compat._server_default_is_identity( + server_default, existing_server_default + ) or sqla_compat._server_default_is_computed( + server_default, existing_server_default + ): + # modifying computed or identity columns is not supported + # the default will raise + super().alter_column( + table_name, + column_name, + nullable=nullable, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_nullable=existing_nullable, + server_default=server_default, + existing_server_default=existing_server_default, + **kw, + ) + if name is not None or self._is_mysql_allowed_functional_default( + type_ if type_ is not None else existing_type, server_default + ): + self._exec( + MySQLChangeColumn( + table_name, + column_name, + schema=schema, + newname=name if name is not None else column_name, + nullable=( + nullable + if nullable is not None + else ( + existing_nullable + if existing_nullable is not None + else True + ) + ), + type_=type_ if type_ is not None else existing_type, + default=( + server_default + if server_default is not False + else existing_server_default + ), + autoincrement=( + autoincrement + if autoincrement is not None + else existing_autoincrement + ), + comment=( + comment if comment is not False else existing_comment + ), + ) + ) + elif ( + nullable is not None + or type_ is not None + or autoincrement is not None + or comment is not False + ): + self._exec( + MySQLModifyColumn( + table_name, + column_name, + schema=schema, + newname=name if name is not None else column_name, + nullable=( + nullable + if nullable is not None + else ( + existing_nullable + if existing_nullable is not None + else True + ) + ), + type_=type_ if type_ is not None else existing_type, + default=( + server_default + if server_default is not False + else existing_server_default + ), + autoincrement=( + autoincrement + if autoincrement is not None + else existing_autoincrement + ), + comment=( + comment if comment is not False else existing_comment + ), + ) + ) + elif server_default is not False: + self._exec( + MySQLAlterDefault( + table_name, column_name, server_default, schema=schema + ) + ) + + def drop_constraint( + self, + const: Constraint, + **kw: Any, + ) -> None: + if isinstance(const, schema.CheckConstraint) and _is_type_bound(const): + return + + super().drop_constraint(const) + + def _is_mysql_allowed_functional_default( + self, + type_: Optional[TypeEngine], + server_default: Optional[Union[_ServerDefaultType, Literal[False]]], + ) -> bool: + return ( + type_ is not None + and type_._type_affinity is sqltypes.DateTime + and server_default is not None + ) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + # partially a workaround for SQLAlchemy issue #3023; if the + # column were created without "NOT NULL", MySQL may have added + # an implicit default of '0' which we need to skip + # TODO: this is not really covered anymore ? + if ( + metadata_column.type._type_affinity is sqltypes.Integer + and inspector_column.primary_key + and not inspector_column.autoincrement + and not rendered_metadata_default + and rendered_inspector_default == "'0'" + ): + return False + elif ( + rendered_inspector_default + and inspector_column.type._type_affinity is sqltypes.Integer + ): + rendered_inspector_default = ( + re.sub(r"^'|'$", "", rendered_inspector_default) + if rendered_inspector_default is not None + else None + ) + return rendered_inspector_default != rendered_metadata_default + elif ( + rendered_metadata_default + and metadata_column.type._type_affinity is sqltypes.String + ): + metadata_default = re.sub(r"^'|'$", "", rendered_metadata_default) + return rendered_inspector_default != f"'{metadata_default}'" + elif rendered_inspector_default and rendered_metadata_default: + # adjust for "function()" vs. "FUNCTION" as can occur particularly + # for the CURRENT_TIMESTAMP function on newer MariaDB versions + + # SQLAlchemy MySQL dialect bundles ON UPDATE into the server + # default; adjust for this possibly being present. + onupdate_ins = re.match( + r"(.*) (on update.*?)(?:\(\))?$", + rendered_inspector_default.lower(), + ) + onupdate_met = re.match( + r"(.*) (on update.*?)(?:\(\))?$", + rendered_metadata_default.lower(), + ) + + if onupdate_ins: + if not onupdate_met: + return True + elif onupdate_ins.group(2) != onupdate_met.group(2): + return True + + rendered_inspector_default = onupdate_ins.group(1) + rendered_metadata_default = onupdate_met.group(1) + + return re.sub( + r"(.*?)(?:\(\))?$", r"\1", rendered_inspector_default.lower() + ) != re.sub( + r"(.*?)(?:\(\))?$", r"\1", rendered_metadata_default.lower() + ) + else: + return rendered_inspector_default != rendered_metadata_default + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + # TODO: if SQLA 1.0, make use of "duplicates_index" + # metadata + removed = set() + for idx in list(conn_indexes): + if idx.unique: + continue + # MySQL puts implicit indexes on FK columns, even if + # composite and even if MyISAM, so can't check this too easily. + # the name of the index may be the column name or it may + # be the name of the FK constraint. + for col in idx.columns: + if idx.name == col.name: + conn_indexes.remove(idx) + removed.add(idx.name) + break + for fk in col.foreign_keys: + if fk.name == idx.name: + conn_indexes.remove(idx) + removed.add(idx.name) + break + if idx.name in removed: + break + + # then remove indexes from the "metadata_indexes" + # that we've removed from reflected, otherwise they come out + # as adds (see #202) + for idx in list(metadata_indexes): + if idx.name in removed: + metadata_indexes.remove(idx) + + def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks): + conn_fk_by_sig = { + self._create_reflected_constraint_sig(fk).unnamed_no_options: fk + for fk in conn_fks + } + metadata_fk_by_sig = { + self._create_metadata_constraint_sig(fk).unnamed_no_options: fk + for fk in metadata_fks + } + + for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig): + mdfk = metadata_fk_by_sig[sig] + cnfk = conn_fk_by_sig[sig] + # MySQL considers RESTRICT to be the default and doesn't + # report on it. if the model has explicit RESTRICT and + # the conn FK has None, set it to RESTRICT + if ( + mdfk.ondelete is not None + and mdfk.ondelete.lower() == "restrict" + and cnfk.ondelete is None + ): + cnfk.ondelete = "RESTRICT" + if ( + mdfk.onupdate is not None + and mdfk.onupdate.lower() == "restrict" + and cnfk.onupdate is None + ): + cnfk.onupdate = "RESTRICT" + + +class MariaDBImpl(MySQLImpl): + __dialect__ = "mariadb" + + +class MySQLAlterDefault(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + default: Optional[_ServerDefaultType], + schema: Optional[str] = None, + ) -> None: + super(AlterColumn, self).__init__(name, schema=schema) + self.column_name = column_name + self.default = default + + +class MySQLChangeColumn(AlterColumn): + def __init__( + self, + name: str, + column_name: str, + schema: Optional[str] = None, + newname: Optional[str] = None, + type_: Optional[TypeEngine] = None, + nullable: Optional[bool] = None, + default: Optional[Union[_ServerDefaultType, Literal[False]]] = False, + autoincrement: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + ) -> None: + super(AlterColumn, self).__init__(name, schema=schema) + self.column_name = column_name + self.nullable = nullable + self.newname = newname + self.default = default + self.autoincrement = autoincrement + self.comment = comment + if type_ is None: + raise util.CommandError( + "All MySQL CHANGE/MODIFY COLUMN operations " + "require the existing type." + ) + + self.type_ = sqltypes.to_instance(type_) + + +class MySQLModifyColumn(MySQLChangeColumn): + pass + + +@compiles(ColumnNullable, "mysql", "mariadb") +@compiles(ColumnName, "mysql", "mariadb") +@compiles(ColumnDefault, "mysql", "mariadb") +@compiles(ColumnType, "mysql", "mariadb") +def _mysql_doesnt_support_individual(element, compiler, **kw): + raise NotImplementedError( + "Individual alter column constructs not supported by MySQL" + ) + + +@compiles(MySQLAlterDefault, "mysql", "mariadb") +def _mysql_alter_default( + element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s ALTER COLUMN %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + ( + "SET DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DROP DEFAULT" + ), + ) + + +@compiles(MySQLModifyColumn, "mysql", "mariadb") +def _mysql_modify_column( + element: MySQLModifyColumn, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s MODIFY %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + _mysql_colspec( + compiler, + nullable=element.nullable, + server_default=element.default, + type_=element.type_, + autoincrement=element.autoincrement, + comment=element.comment, + ), + ) + + +@compiles(MySQLChangeColumn, "mysql", "mariadb") +def _mysql_change_column( + element: MySQLChangeColumn, compiler: MySQLDDLCompiler, **kw +) -> str: + return "%s CHANGE %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + _mysql_colspec( + compiler, + nullable=element.nullable, + server_default=element.default, + type_=element.type_, + autoincrement=element.autoincrement, + comment=element.comment, + ), + ) + + +def _mysql_colspec( + compiler: MySQLDDLCompiler, + nullable: Optional[bool], + server_default: Optional[Union[_ServerDefaultType, Literal[False]]], + type_: TypeEngine, + autoincrement: Optional[bool], + comment: Optional[Union[str, Literal[False]]], +) -> str: + spec = "%s %s" % ( + compiler.dialect.type_compiler.process(type_), + "NULL" if nullable else "NOT NULL", + ) + if autoincrement: + spec += " AUTO_INCREMENT" + if server_default is not False and server_default is not None: + spec += " DEFAULT %s" % format_server_default(compiler, server_default) + if comment: + spec += " COMMENT %s" % compiler.sql_compiler.render_literal_value( + comment, sqltypes.String() + ) + + return spec + + +@compiles(schema.DropConstraint, "mysql", "mariadb") +def _mysql_drop_constraint( + element: DropConstraint, compiler: MySQLDDLCompiler, **kw +) -> str: + """Redefine SQLAlchemy's drop constraint to + raise errors for invalid constraint type.""" + + constraint = element.element + if isinstance( + constraint, + ( + schema.ForeignKeyConstraint, + schema.PrimaryKeyConstraint, + schema.UniqueConstraint, + ), + ): + assert not kw + return compiler.visit_drop_constraint(element) + elif isinstance(constraint, schema.CheckConstraint): + # note that SQLAlchemy as of 1.2 does not yet support + # DROP CONSTRAINT for MySQL/MariaDB, so we implement fully + # here. + if compiler.dialect.is_mariadb: + return "ALTER TABLE %s DROP CONSTRAINT %s" % ( + compiler.preparer.format_table(constraint.table), + compiler.preparer.format_constraint(constraint), + ) + else: + return "ALTER TABLE %s DROP CHECK %s" % ( + compiler.preparer.format_table(constraint.table), + compiler.preparer.format_constraint(constraint), + ) + else: + raise NotImplementedError( + "No generic 'DROP CONSTRAINT' in MySQL - " + "please specify constraint type" + ) diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/oracle.py b/venv/lib/python3.12/site-packages/alembic/ddl/oracle.py new file mode 100644 index 0000000..eac9912 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/oracle.py @@ -0,0 +1,202 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Optional +from typing import TYPE_CHECKING + +from sqlalchemy.sql import sqltypes + +from .base import AddColumn +from .base import alter_table +from .base import ColumnComment +from .base import ColumnDefault +from .base import ColumnName +from .base import ColumnNullable +from .base import ColumnType +from .base import format_column_name +from .base import format_server_default +from .base import format_table_name +from .base import format_type +from .base import IdentityColumnDefault +from .base import RenameTable +from .impl import DefaultImpl +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from sqlalchemy.dialects.oracle.base import OracleDDLCompiler + from sqlalchemy.engine.cursor import CursorResult + from sqlalchemy.sql.schema import Column + + +class OracleImpl(DefaultImpl): + __dialect__ = "oracle" + transactional_ddl = False + batch_separator = "/" + command_terminator = "" + type_synonyms = DefaultImpl.type_synonyms + ( + {"VARCHAR", "VARCHAR2"}, + {"BIGINT", "INTEGER", "SMALLINT", "DECIMAL", "NUMERIC", "NUMBER"}, + {"DOUBLE", "FLOAT", "DOUBLE_PRECISION"}, + ) + identity_attrs_ignore = () + + def __init__(self, *arg, **kw) -> None: + super().__init__(*arg, **kw) + self.batch_separator = self.context_opts.get( + "oracle_batch_separator", self.batch_separator + ) + + def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]: + result = super()._exec(construct, *args, **kw) + if self.as_sql and self.batch_separator: + self.static_output(self.batch_separator) + return result + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_metadata_default + ) + + rendered_metadata_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + rendered_inspector_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = rendered_inspector_default.strip() + return rendered_inspector_default != rendered_metadata_default + + def emit_begin(self) -> None: + self._exec("SET TRANSACTION READ WRITE") + + def emit_commit(self) -> None: + self._exec("COMMIT") + + +@compiles(AddColumn, "oracle") +def visit_add_column( + element: AddColumn, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s" % ( + alter_table(compiler, element.table_name, element.schema), + add_column(compiler, element.column, **kw), + ) + + +@compiles(ColumnNullable, "oracle") +def visit_column_nullable( + element: ColumnNullable, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "NULL" if element.nullable else "NOT NULL", + ) + + +@compiles(ColumnType, "oracle") +def visit_column_type( + element: ColumnType, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "%s" % format_type(compiler, element.type_), + ) + + +@compiles(ColumnName, "oracle") +def visit_column_name( + element: ColumnName, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s RENAME COLUMN %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +@compiles(ColumnDefault, "oracle") +def visit_column_default( + element: ColumnDefault, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ( + "DEFAULT %s" % format_server_default(compiler, element.default) + if element.default is not None + else "DEFAULT NULL" + ), + ) + + +@compiles(ColumnComment, "oracle") +def visit_column_comment( + element: ColumnComment, compiler: OracleDDLCompiler, **kw +) -> str: + ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}" + + comment = compiler.sql_compiler.render_literal_value( + (element.comment if element.comment is not None else ""), + sqltypes.String(), + ) + + return ddl.format( + table_name=element.table_name, + column_name=element.column_name, + comment=comment, + ) + + +@compiles(RenameTable, "oracle") +def visit_rename_table( + element: RenameTable, compiler: OracleDDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +def alter_column(compiler: OracleDDLCompiler, name: str) -> str: + return "MODIFY %s" % format_column_name(compiler, name) + + +def add_column(compiler: OracleDDLCompiler, column: Column[Any], **kw) -> str: + return "ADD %s" % compiler.get_column_specification(column, **kw) + + +@compiles(IdentityColumnDefault, "oracle") +def visit_identity_column( + element: IdentityColumnDefault, compiler: OracleDDLCompiler, **kw +): + text = "%s %s " % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ) + if element.default is None: + # drop identity + text += "DROP IDENTITY" + return text + else: + text += compiler.visit_identity_column(element.default) + return text diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py b/venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py new file mode 100644 index 0000000..cc03f45 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/postgresql.py @@ -0,0 +1,864 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import logging +import re +from typing import Any +from typing import cast +from typing import Dict +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import Column +from sqlalchemy import Float +from sqlalchemy import Identity +from sqlalchemy import literal_column +from sqlalchemy import Numeric +from sqlalchemy import select +from sqlalchemy import text +from sqlalchemy import types as sqltypes +from sqlalchemy.dialects.postgresql import BIGINT +from sqlalchemy.dialects.postgresql import ExcludeConstraint +from sqlalchemy.dialects.postgresql import INTEGER +from sqlalchemy.schema import CreateIndex +from sqlalchemy.sql.elements import ColumnClause +from sqlalchemy.sql.elements import TextClause +from sqlalchemy.sql.functions import FunctionElement +from sqlalchemy.types import NULLTYPE + +from .base import alter_column +from .base import alter_table +from .base import AlterColumn +from .base import ColumnComment +from .base import format_column_name +from .base import format_table_name +from .base import format_type +from .base import IdentityColumnDefault +from .base import RenameTable +from .impl import ComparisonResult +from .impl import DefaultImpl +from .. import util +from ..autogenerate import render +from ..operations import ops +from ..operations import schemaobj +from ..operations.base import BatchOperations +from ..operations.base import Operations +from ..util import sqla_compat +from ..util.sqla_compat import compiles + + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy import Index + from sqlalchemy import UniqueConstraint + from sqlalchemy.dialects.postgresql.array import ARRAY + from sqlalchemy.dialects.postgresql.base import PGDDLCompiler + from sqlalchemy.dialects.postgresql.hstore import HSTORE + from sqlalchemy.dialects.postgresql.json import JSON + from sqlalchemy.dialects.postgresql.json import JSONB + from sqlalchemy.sql.elements import ClauseElement + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + + from .base import _ServerDefaultType + from .impl import _ReflectedConstraint + from ..autogenerate.api import AutogenContext + from ..autogenerate.render import _f_name + from ..runtime.migration import MigrationContext + +log = logging.getLogger(__name__) + + +class PostgresqlImpl(DefaultImpl): + __dialect__ = "postgresql" + transactional_ddl = True + type_synonyms = DefaultImpl.type_synonyms + ( + {"FLOAT", "DOUBLE PRECISION"}, + ) + + def create_index(self, index: Index, **kw: Any) -> None: + # this likely defaults to None if not present, so get() + # should normally not return the default value. being + # defensive in any case + postgresql_include = index.kwargs.get("postgresql_include", None) or () + for col in postgresql_include: + if col not in index.table.c: # type: ignore[union-attr] + index.table.append_column( # type: ignore[union-attr] + Column(col, sqltypes.NullType) + ) + self._exec(CreateIndex(index, **kw)) + + def prep_table_for_batch(self, batch_impl, table): + for constraint in table.constraints: + if ( + constraint.name is not None + and constraint.name in batch_impl.named_constraints + ): + self.drop_constraint(constraint) + + def compare_server_default( + self, + inspector_column, + metadata_column, + rendered_metadata_default, + rendered_inspector_default, + ): + + # don't do defaults for SERIAL columns + if ( + metadata_column.primary_key + and metadata_column is metadata_column.table._autoincrement_column + ): + return False + + conn_col_default = rendered_inspector_default + + if conn_col_default and re.match( + r"nextval\('(.+?)'::regclass\)", conn_col_default + ): + conn_col_default = conn_col_default.replace("::regclass", "") + + defaults_equal = conn_col_default == rendered_metadata_default + if defaults_equal: + return False + + if None in ( + conn_col_default, + rendered_metadata_default, + metadata_column.server_default, + ): + return not defaults_equal + + metadata_default = metadata_column.server_default.arg + + if isinstance(metadata_default, str): + if not isinstance(inspector_column.type, (Numeric, Float)): + metadata_default = re.sub(r"^'|'$", "", metadata_default) + metadata_default = f"'{metadata_default}'" + + metadata_default = literal_column(metadata_default) + + # run a real compare against the server + # TODO: this seems quite a bad idea for a default that's a SQL + # function! SQL functions are not deterministic! + conn = self.connection + assert conn is not None + return not conn.scalar( + select(literal_column(conn_col_default) == metadata_default) + ) + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + schema: Optional[str] = None, + autoincrement: Optional[bool] = None, + existing_type: Optional[TypeEngine] = None, + existing_server_default: Optional[ + Union[_ServerDefaultType, Literal[False]] + ] = None, + existing_nullable: Optional[bool] = None, + existing_autoincrement: Optional[bool] = None, + **kw: Any, + ) -> None: + using = kw.pop("postgresql_using", None) + + if using is not None and type_ is None: + raise util.CommandError( + "postgresql_using must be used with the type_ parameter" + ) + + if type_ is not None: + self._exec( + PostgresqlColumnType( + table_name, + column_name, + type_, + schema=schema, + using=using, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + ) + ) + + super().alter_column( + table_name, + column_name, + nullable=nullable, + server_default=server_default, + name=name, + schema=schema, + autoincrement=autoincrement, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_autoincrement=existing_autoincrement, + **kw, + ) + + def autogen_column_reflect(self, inspector, table, column_info): + if column_info.get("default") and isinstance( + column_info["type"], (INTEGER, BIGINT) + ): + seq_match = re.match( + r"nextval\('(.+?)'::regclass\)", column_info["default"] + ) + if seq_match: + info = sqla_compat._exec_on_inspector( + inspector, + text( + "select c.relname, a.attname " + "from pg_class as c join " + "pg_depend d on d.objid=c.oid and " + "d.classid='pg_class'::regclass and " + "d.refclassid='pg_class'::regclass " + "join pg_class t on t.oid=d.refobjid " + "join pg_attribute a on a.attrelid=t.oid and " + "a.attnum=d.refobjsubid " + "where c.relkind='S' and " + "c.oid=cast(:seqname as regclass)" + ), + seqname=seq_match.group(1), + ).first() + if info: + seqname, colname = info + if colname == column_info["name"]: + log.info( + "Detected sequence named '%s' as " + "owned by integer column '%s(%s)', " + "assuming SERIAL and omitting", + seqname, + table.name, + colname, + ) + # sequence, and the owner is this column, + # its a SERIAL - whack it! + del column_info["default"] + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + doubled_constraints = { + index + for index in conn_indexes + if index.info.get("duplicates_constraint") + } + + for ix in doubled_constraints: + conn_indexes.remove(ix) + + if not sqla_compat.sqla_2: + self._skip_functional_indexes(metadata_indexes, conn_indexes) + + # pg behavior regarding modifiers + # | # | compiled sql | returned sql | regexp. group is removed | + # | - | ---------------- | -----------------| ------------------------ | + # | 1 | nulls first | nulls first | - | + # | 2 | nulls last | | (? str: + expr = expr.lower().replace('"', "").replace("'", "") + if index.table is not None: + # should not be needed, since include_table=False is in compile + expr = expr.replace(f"{index.table.name.lower()}.", "") + + if "::" in expr: + # strip :: cast. types can have spaces in them + expr = re.sub(r"(::[\w ]+\w)", "", expr) + + while expr and expr[0] == "(" and expr[-1] == ")": + expr = expr[1:-1] + + # NOTE: when parsing the connection expression this cleanup could + # be skipped + for rs in self._default_modifiers_re: + if match := rs.search(expr): + start, end = match.span(1) + expr = expr[:start] + expr[end:] + break + + while expr and expr[0] == "(" and expr[-1] == ")": + expr = expr[1:-1] + + # strip casts + cast_re = re.compile(r"cast\s*\(") + if cast_re.match(expr): + expr = cast_re.sub("", expr) + # remove the as type + expr = re.sub(r"as\s+[^)]+\)", "", expr) + # remove spaces + expr = expr.replace(" ", "") + return expr + + def _dialect_options( + self, item: Union[Index, UniqueConstraint] + ) -> Tuple[Any, ...]: + # only the positive case is returned by sqlalchemy reflection so + # None and False are treated the same + if item.dialect_kwargs.get("postgresql_nulls_not_distinct"): + return ("nulls_not_distinct",) + return () + + def compare_indexes( + self, + metadata_index: Index, + reflected_index: Index, + ) -> ComparisonResult: + msg = [] + unique_msg = self._compare_index_unique( + metadata_index, reflected_index + ) + if unique_msg: + msg.append(unique_msg) + m_exprs = metadata_index.expressions + r_exprs = reflected_index.expressions + if len(m_exprs) != len(r_exprs): + msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}") + if msg: + # no point going further, return early + return ComparisonResult.Different(msg) + skip = [] + for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1): + m_compile = self._compile_element(m_e) + m_text = self._cleanup_index_expr(metadata_index, m_compile) + # print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}") + r_compile = self._compile_element(r_e) + r_text = self._cleanup_index_expr(metadata_index, r_compile) + # print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}") + if m_text == r_text: + continue # expressions these are equal + elif m_compile.strip().endswith("_ops") and ( + " " in m_compile or ")" in m_compile # is an expression + ): + skip.append( + f"expression #{pos} {m_compile!r} detected " + "as including operator clause." + ) + util.warn( + f"Expression #{pos} {m_compile!r} in index " + f"{reflected_index.name!r} detected to include " + "an operator clause. Expression compare cannot proceed. " + "Please move the operator clause to the " + "``postgresql_ops`` dict to enable proper compare " + "of the index expressions: " + "https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501 + ) + else: + msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}") + + m_options = self._dialect_options(metadata_index) + r_options = self._dialect_options(reflected_index) + if m_options != r_options: + msg.extend(f"options {r_options} to {m_options}") + + if msg: + return ComparisonResult.Different(msg) + elif skip: + # if there are other changes detected don't skip the index + return ComparisonResult.Skip(skip) + else: + return ComparisonResult.Equal() + + def compare_unique_constraint( + self, + metadata_constraint: UniqueConstraint, + reflected_constraint: UniqueConstraint, + ) -> ComparisonResult: + metadata_tup = self._create_metadata_constraint_sig( + metadata_constraint + ) + reflected_tup = self._create_reflected_constraint_sig( + reflected_constraint + ) + + meta_sig = metadata_tup.unnamed + conn_sig = reflected_tup.unnamed + if conn_sig != meta_sig: + return ComparisonResult.Different( + f"expression {conn_sig} to {meta_sig}" + ) + + metadata_do = self._dialect_options(metadata_tup.const) + conn_do = self._dialect_options(reflected_tup.const) + if metadata_do != conn_do: + return ComparisonResult.Different( + f"expression {conn_do} to {metadata_do}" + ) + + return ComparisonResult.Equal() + + def adjust_reflected_dialect_options( + self, reflected_object: _ReflectedConstraint, kind: str + ) -> Dict[str, Any]: + options: Dict[str, Any] + options = reflected_object.get("dialect_options", {}).copy() # type: ignore[attr-defined] # noqa: E501 + if not options.get("postgresql_include"): + options.pop("postgresql_include", None) + return options + + def _compile_element(self, element: Union[ClauseElement, str]) -> str: + if isinstance(element, str): + return element + return element.compile( + dialect=self.dialect, + compile_kwargs={"literal_binds": True, "include_table": False}, + ).string + + def render_ddl_sql_expr( + self, + expr: ClauseElement, + is_server_default: bool = False, + is_index: bool = False, + **kw: Any, + ) -> str: + """Render a SQL expression that is typically a server default, + index expression, etc. + + """ + + # apply self_group to index expressions; + # see https://github.com/sqlalchemy/sqlalchemy/blob/ + # 82fa95cfce070fab401d020c6e6e4a6a96cc2578/ + # lib/sqlalchemy/dialects/postgresql/base.py#L2261 + if is_index and not isinstance(expr, ColumnClause): + expr = expr.self_group() + + return super().render_ddl_sql_expr( + expr, is_server_default=is_server_default, is_index=is_index, **kw + ) + + def render_type( + self, type_: TypeEngine, autogen_context: AutogenContext + ) -> Union[str, Literal[False]]: + mod = type(type_).__module__ + if not mod.startswith("sqlalchemy.dialects.postgresql"): + return False + + if hasattr(self, "_render_%s_type" % type_.__visit_name__): + meth = getattr(self, "_render_%s_type" % type_.__visit_name__) + return meth(type_, autogen_context) + + return False + + def _render_HSTORE_type( + self, type_: HSTORE, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "text_type", r"(.+?\(.*text_type=)" + ), + ) + + def _render_ARRAY_type( + self, type_: ARRAY, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "item_type", r"(.+?\()" + ), + ) + + def _render_JSON_type( + self, type_: JSON, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)" + ), + ) + + def _render_JSONB_type( + self, type_: JSONB, autogen_context: AutogenContext + ) -> str: + return cast( + str, + render._render_type_w_subtype( + type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)" + ), + ) + + +class PostgresqlColumnType(AlterColumn): + def __init__( + self, name: str, column_name: str, type_: TypeEngine, **kw + ) -> None: + using = kw.pop("using", None) + super().__init__(name, column_name, **kw) + self.type_ = sqltypes.to_instance(type_) + self.using = using + + +@compiles(RenameTable, "postgresql") +def visit_rename_table( + element: RenameTable, compiler: PGDDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +@compiles(PostgresqlColumnType, "postgresql") +def visit_column_type( + element: PostgresqlColumnType, compiler: PGDDLCompiler, **kw +) -> str: + return "%s %s %s %s" % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + "TYPE %s" % format_type(compiler, element.type_), + "USING %s" % element.using if element.using else "", + ) + + +@compiles(ColumnComment, "postgresql") +def visit_column_comment( + element: ColumnComment, compiler: PGDDLCompiler, **kw +) -> str: + ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}" + comment = ( + compiler.sql_compiler.render_literal_value( + element.comment, sqltypes.String() + ) + if element.comment is not None + else "NULL" + ) + + return ddl.format( + table_name=format_table_name( + compiler, element.table_name, element.schema + ), + column_name=format_column_name(compiler, element.column_name), + comment=comment, + ) + + +@compiles(IdentityColumnDefault, "postgresql") +def visit_identity_column( + element: IdentityColumnDefault, compiler: PGDDLCompiler, **kw +): + text = "%s %s " % ( + alter_table(compiler, element.table_name, element.schema), + alter_column(compiler, element.column_name), + ) + if element.default is None: + # drop identity + text += "DROP IDENTITY" + return text + elif element.existing_server_default is None: + # add identity options + text += "ADD " + text += compiler.visit_identity_column(element.default) + return text + else: + # alter identity + diff, _, _ = element.impl._compare_identity_default( + element.default, element.existing_server_default + ) + identity = element.default + for attr in sorted(diff): + if attr == "always": + text += "SET GENERATED %s " % ( + "ALWAYS" if identity.always else "BY DEFAULT" + ) + else: + text += "SET %s " % compiler.get_identity_options( + Identity(**{attr: getattr(identity, attr)}) + ) + return text + + +@Operations.register_operation("create_exclude_constraint") +@BatchOperations.register_operation( + "create_exclude_constraint", "batch_create_exclude_constraint" +) +@ops.AddConstraintOp.register_add_constraint("exclude_constraint") +class CreateExcludeConstraintOp(ops.AddConstraintOp): + """Represent a create exclude constraint operation.""" + + constraint_type = "exclude" + + def __init__( + self, + constraint_name: sqla_compat._ConstraintName, + table_name: Union[str, quoted_name], + elements: Union[ + Sequence[Tuple[str, str]], + Sequence[Tuple[ColumnClause[Any], str]], + ], + where: Optional[Union[ColumnElement[bool], str]] = None, + schema: Optional[str] = None, + _orig_constraint: Optional[ExcludeConstraint] = None, + **kw, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.elements = elements + self.where = where + self.schema = schema + self._orig_constraint = _orig_constraint + self.kw = kw + + @classmethod + def from_constraint( # type:ignore[override] + cls, constraint: ExcludeConstraint + ) -> CreateExcludeConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + return cls( + constraint.name, + constraint_table.name, + [ # type: ignore + (expr, op) for expr, name, op in constraint._render_exprs + ], + where=cast("ColumnElement[bool] | None", constraint.where), + schema=constraint_table.schema, + _orig_constraint=constraint, + deferrable=constraint.deferrable, + initially=constraint.initially, + using=constraint.using, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> ExcludeConstraint: + if self._orig_constraint is not None: + return self._orig_constraint + schema_obj = schemaobj.SchemaObjects(migration_context) + t = schema_obj.table(self.table_name, schema=self.schema) + excl = ExcludeConstraint( + *self.elements, + name=self.constraint_name, + where=self.where, + **self.kw, + ) + for ( + expr, + name, + oper, + ) in excl._render_exprs: + t.append_column(Column(name, NULLTYPE)) + t.append_constraint(excl) + return excl + + @classmethod + def create_exclude_constraint( + cls, + operations: Operations, + constraint_name: str, + table_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ + op = cls(constraint_name, table_name, elements, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_exclude_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue a "create exclude constraint" instruction using the + current batch migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + .. seealso:: + + :meth:`.Operations.create_exclude_constraint` + + """ + kw["schema"] = operations.impl.schema + op = cls(constraint_name, operations.impl.table_name, elements, **kw) + return operations.invoke(op) + + +@render.renderers.dispatch_for(CreateExcludeConstraintOp) +def _add_exclude_constraint( + autogen_context: AutogenContext, op: CreateExcludeConstraintOp +) -> str: + return _exclude_constraint(op.to_constraint(), autogen_context, alter=True) + + +@render._constraint_renderers.dispatch_for(ExcludeConstraint) +def _render_inline_exclude_constraint( + constraint: ExcludeConstraint, + autogen_context: AutogenContext, + namespace_metadata: MetaData, +) -> str: + rendered = render._user_defined_render( + "exclude", constraint, autogen_context + ) + if rendered is not False: + return rendered + + return _exclude_constraint(constraint, autogen_context, False) + + +def _postgresql_autogenerate_prefix(autogen_context: AutogenContext) -> str: + imports = autogen_context.imports + if imports is not None: + imports.add("from sqlalchemy.dialects import postgresql") + return "postgresql." + + +def _exclude_constraint( + constraint: ExcludeConstraint, + autogen_context: AutogenContext, + alter: bool, +) -> str: + opts: List[Tuple[str, Union[quoted_name, str, _f_name, None]]] = [] + + has_batch = autogen_context._has_batch + + if constraint.deferrable: + opts.append(("deferrable", str(constraint.deferrable))) + if constraint.initially: + opts.append(("initially", str(constraint.initially))) + if constraint.using: + opts.append(("using", str(constraint.using))) + if not has_batch and alter and constraint.table.schema: + opts.append(("schema", render._ident(constraint.table.schema))) + if not alter and constraint.name: + opts.append( + ("name", render._render_gen_name(autogen_context, constraint.name)) + ) + + def do_expr_where_opts(): + args = [ + "(%s, %r)" + % ( + _render_potential_column( + sqltext, # type:ignore[arg-type] + autogen_context, + ), + opstring, + ) + for sqltext, name, opstring in constraint._render_exprs + ] + if constraint.where is not None: + args.append( + "where=%s" + % render._render_potential_expr( + constraint.where, autogen_context + ) + ) + args.extend(["%s=%r" % (k, v) for k, v in opts]) + return args + + if alter: + args = [ + repr(render._render_gen_name(autogen_context, constraint.name)) + ] + if not has_batch: + args += [repr(render._ident(constraint.table.name))] + args.extend(do_expr_where_opts()) + return "%(prefix)screate_exclude_constraint(%(args)s)" % { + "prefix": render._alembic_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + else: + args = do_expr_where_opts() + return "%(prefix)sExcludeConstraint(%(args)s)" % { + "prefix": _postgresql_autogenerate_prefix(autogen_context), + "args": ", ".join(args), + } + + +def _render_potential_column( + value: Union[ + ColumnClause[Any], Column[Any], TextClause, FunctionElement[Any] + ], + autogen_context: AutogenContext, +) -> str: + if isinstance(value, ColumnClause): + if value.is_literal: + # like literal_column("int8range(from, to)") in ExcludeConstraint + template = "%(prefix)sliteral_column(%(name)r)" + else: + template = "%(prefix)scolumn(%(name)r)" + + return template % { + "prefix": render._sqlalchemy_autogenerate_prefix(autogen_context), + "name": value.name, + } + else: + return render._render_potential_expr( + value, + autogen_context, + wrap_in_element=isinstance(value, (TextClause, FunctionElement)), + ) diff --git a/venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py b/venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py new file mode 100644 index 0000000..c260d53 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/ddl/sqlite.py @@ -0,0 +1,237 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +import re +from typing import Any +from typing import Dict +from typing import Optional +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import cast +from sqlalchemy import Computed +from sqlalchemy import JSON +from sqlalchemy import schema +from sqlalchemy import sql + +from .base import alter_table +from .base import ColumnName +from .base import format_column_name +from .base import format_table_name +from .base import RenameTable +from .impl import DefaultImpl +from .. import util +from ..util.sqla_compat import compiles + +if TYPE_CHECKING: + from sqlalchemy.engine.reflection import Inspector + from sqlalchemy.sql.compiler import DDLCompiler + from sqlalchemy.sql.elements import Cast + from sqlalchemy.sql.elements import ClauseElement + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + + from ..operations.batch import BatchOperationsImpl + + +class SQLiteImpl(DefaultImpl): + __dialect__ = "sqlite" + + transactional_ddl = False + """SQLite supports transactional DDL, but pysqlite does not: + see: http://bugs.python.org/issue10740 + """ + + def requires_recreate_in_batch( + self, batch_op: BatchOperationsImpl + ) -> bool: + """Return True if the given :class:`.BatchOperationsImpl` + would need the table to be recreated and copied in order to + proceed. + + Normally, only returns True on SQLite when operations other + than add_column are present. + + """ + for op in batch_op.batch: + if op[0] == "add_column": + col = op[1][1] + if isinstance( + col.server_default, schema.DefaultClause + ) and isinstance(col.server_default.arg, sql.ClauseElement): + return True + elif ( + isinstance(col.server_default, Computed) + and col.server_default.persisted + ): + return True + elif op[0] not in ("create_index", "drop_index"): + return True + else: + return False + + def add_constraint(self, const: Constraint, **kw: Any): + # attempt to distinguish between an + # auto-gen constraint and an explicit one + if const._create_rule is None: + raise NotImplementedError( + "No support for ALTER of constraints in SQLite dialect. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + elif const._create_rule(self): + util.warn( + "Skipping unsupported ALTER for " + "creation of implicit constraint. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + + def drop_constraint(self, const: Constraint, **kw: Any): + if const._create_rule is None: + raise NotImplementedError( + "No support for ALTER of constraints in SQLite dialect. " + "Please refer to the batch mode feature which allows for " + "SQLite migrations using a copy-and-move strategy." + ) + + def compare_server_default( + self, + inspector_column: Column[Any], + metadata_column: Column[Any], + rendered_metadata_default: Optional[str], + rendered_inspector_default: Optional[str], + ) -> bool: + if rendered_metadata_default is not None: + rendered_metadata_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_metadata_default + ) + + rendered_metadata_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default + ) + + if rendered_inspector_default is not None: + rendered_inspector_default = re.sub( + r"^\((.+)\)$", r"\1", rendered_inspector_default + ) + + rendered_inspector_default = re.sub( + r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default + ) + + return rendered_inspector_default != rendered_metadata_default + + def _guess_if_default_is_unparenthesized_sql_expr( + self, expr: Optional[str] + ) -> bool: + """Determine if a server default is a SQL expression or a constant. + + There are too many assertions that expect server defaults to round-trip + identically without parenthesis added so we will add parens only in + very specific cases. + + """ + if not expr: + return False + elif re.match(r"^[0-9\.]$", expr): + return False + elif re.match(r"^'.+'$", expr): + return False + elif re.match(r"^\(.+\)$", expr): + return False + else: + return True + + def autogen_column_reflect( + self, + inspector: Inspector, + table: Table, + column_info: Dict[str, Any], + ) -> None: + # SQLite expression defaults require parenthesis when sent + # as DDL + if self._guess_if_default_is_unparenthesized_sql_expr( + column_info.get("default", None) + ): + column_info["default"] = "(%s)" % (column_info["default"],) + + def render_ddl_sql_expr( + self, expr: ClauseElement, is_server_default: bool = False, **kw + ) -> str: + # SQLite expression defaults require parenthesis when sent + # as DDL + str_expr = super().render_ddl_sql_expr( + expr, is_server_default=is_server_default, **kw + ) + + if ( + is_server_default + and self._guess_if_default_is_unparenthesized_sql_expr(str_expr) + ): + str_expr = "(%s)" % (str_expr,) + return str_expr + + def cast_for_batch_migrate( + self, + existing: Column[Any], + existing_transfer: Dict[str, Union[TypeEngine, Cast]], + new_type: TypeEngine, + ) -> None: + if ( + existing.type._type_affinity is not new_type._type_affinity + and not isinstance(new_type, JSON) + ): + existing_transfer["expr"] = cast( + existing_transfer["expr"], new_type + ) + + def correct_for_autogen_constraints( + self, + conn_unique_constraints, + conn_indexes, + metadata_unique_constraints, + metadata_indexes, + ): + self._skip_functional_indexes(metadata_indexes, conn_indexes) + + +@compiles(RenameTable, "sqlite") +def visit_rename_table( + element: RenameTable, compiler: DDLCompiler, **kw +) -> str: + return "%s RENAME TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_table_name(compiler, element.new_table_name, None), + ) + + +@compiles(ColumnName, "sqlite") +def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str: + return "%s RENAME COLUMN %s TO %s" % ( + alter_table(compiler, element.table_name, element.schema), + format_column_name(compiler, element.column_name), + format_column_name(compiler, element.newname), + ) + + +# @compiles(AddColumn, 'sqlite') +# def visit_add_column(element, compiler, **kw): +# return "%s %s" % ( +# alter_table(compiler, element.table_name, element.schema), +# add_column(compiler, element.column, **kw) +# ) + + +# def add_column(compiler, column, **kw): +# text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw) +# need to modify SQLAlchemy so that the CHECK associated with a Boolean +# or Enum gets placed as part of the column constraints, not the Table +# see ticket 98 +# for const in column.constraints: +# text += compiler.process(AddConstraint(const)) +# return text diff --git a/venv/lib/python3.12/site-packages/alembic/environment.py b/venv/lib/python3.12/site-packages/alembic/environment.py new file mode 100644 index 0000000..adfc93e --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/environment.py @@ -0,0 +1 @@ +from .runtime.environment import * # noqa diff --git a/venv/lib/python3.12/site-packages/alembic/migration.py b/venv/lib/python3.12/site-packages/alembic/migration.py new file mode 100644 index 0000000..02626e2 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/migration.py @@ -0,0 +1 @@ +from .runtime.migration import * # noqa diff --git a/venv/lib/python3.12/site-packages/alembic/op.py b/venv/lib/python3.12/site-packages/alembic/op.py new file mode 100644 index 0000000..f3f5fac --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/op.py @@ -0,0 +1,5 @@ +from .operations.base import Operations + +# create proxy functions for +# each method on the Operations class. +Operations.create_module_class_proxy(globals(), locals()) diff --git a/venv/lib/python3.12/site-packages/alembic/op.pyi b/venv/lib/python3.12/site-packages/alembic/op.pyi new file mode 100644 index 0000000..7fadaf5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/op.pyi @@ -0,0 +1,1429 @@ +# ### this file stubs are generated by tools/write_pyi.py - do not edit ### +# ### imports are manually managed +from __future__ import annotations + +from contextlib import contextmanager +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Dict +from typing import Iterator +from typing import List +from typing import Literal +from typing import Mapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +if TYPE_CHECKING: + from sqlalchemy.engine import Connection + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import conv + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.expression import TableClause + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.type_api import TypeEngine + from sqlalchemy.util import immutabledict + + from .ddl.base import _ServerDefaultType + from .operations.base import BatchOperations + from .operations.ops import AddColumnOp + from .operations.ops import AddConstraintOp + from .operations.ops import AlterColumnOp + from .operations.ops import AlterTableOp + from .operations.ops import BulkInsertOp + from .operations.ops import CreateIndexOp + from .operations.ops import CreateTableCommentOp + from .operations.ops import CreateTableOp + from .operations.ops import DropColumnOp + from .operations.ops import DropConstraintOp + from .operations.ops import DropIndexOp + from .operations.ops import DropTableCommentOp + from .operations.ops import DropTableOp + from .operations.ops import ExecuteSQLOp + from .operations.ops import MigrateOperation + from .runtime.migration import MigrationContext + from .util.sqla_compat import _literal_bindparam + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=Callable[..., Any]) + +### end imports ### + +def add_column( + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, +) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. Options + also exist for control of single-column primary key and foreign key + constraints to be generated. + + .. note:: + + Not all contraint types may be indicated with this directive. + NOT NULL, FOREIGN KEY, and CHECK are honored, PRIMARY KEY + is conditionally honored, UNIQUE + is currently not. + + As of 1.18.2, the following :class:`~sqlalchemy.schema.Column` + parameters are **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + **PRIMARY KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + ``primary_key=True`` directive, indicating the column intends to be + part of a primary key constraint. However by default, the inline + "PRIMARY KEY" directive is not emitted, and it's assumed that a + separate :meth:`.Operations.create_primary_key` directive will be used + to create this constraint, which may potentially include other columns + as well as have an explicit name. To instead render an inline + "PRIMARY KEY" directive, the + :paramref:`.AddColumnOp.inline_primary_key` parameter may be indicated + at the same time as the ``primary_key`` parameter (both are needed):: + + from alembic import op + from sqlalchemy import Column, INTEGER + + op.add_column( + "organization", + Column("id", INTEGER, primary_key=True), + inline_primary_key=True + ) + + The ``primary_key=True`` parameter on + :class:`~sqlalchemy.schema.Column` also indicates behaviors such as + using the ``SERIAL`` datatype with the PostgreSQL database, which is + why two separate, independent parameters are provided to support all + combinations. + + .. versionadded:: 1.18.4 Added + :paramref:`.AddColumnOp.inline_primary_key` + to control use of the ``PRIMARY KEY`` inline directive. + + **FOREIGN KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. By default, Alembic will automatically + emit a second ALTER statement in order to add the single-column FOREIGN + KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + To render the FOREIGN KEY constraint inline within the ADD COLUMN + directive, use the ``inline_references`` parameter. This can improve + performance on large tables since the constraint is marked as valid + immediately for nullable columns:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + inline_references=True, + ) + + **Indicating server side defaults** + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds ``IF NOT EXISTS`` operator + when creating the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param inline_references: If True, renders ``FOREIGN KEY`` constraints + inline within the ``ADD COLUMN`` directive using ``REFERENCES`` + syntax, rather than as a separate ``ALTER TABLE ADD CONSTRAINT`` + statement. This is supported by PostgreSQL, Oracle, MySQL 5.7+, and + MariaDB 10.5+. + + .. versionadded:: 1.18.2 + + :param inline_primary_key: If True, renders the ``PRIMARY KEY`` phrase + inline within the ``ADD COLUMN`` directive. When not present or + False, ``PRIMARY KEY`` is not emitted; it is assumed that the + migration script will include an additional + :meth:`.Operations.create_primary_key` directive to create a full + primary key constraint. + + .. versionadded:: 1.18.4 + + """ + +def alter_column( + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Union[_ServerDefaultType, None, Literal[False]] = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, +) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ + +@contextmanager +def batch_alter_table( + table_name: str, + schema: Optional[str] = None, + recreate: Literal["auto", "always", "never"] = "auto", + partial_reordering: list[tuple[str, ...]] | None = None, + copy_from: Optional[Table] = None, + table_args: Tuple[Any, ...] = (), + table_kwargs: Mapping[str, Any] = immutabledict({}), + reflect_args: Tuple[Any, ...] = (), + reflect_kwargs: Mapping[str, Any] = immutabledict({}), + naming_convention: Optional[Dict[str, str]] = None, +) -> Iterator[BatchOperations]: + """Invoke a series of per-table migrations in batch. + + Batch mode allows a series of operations specific to a table + to be syntactically grouped together, and allows for alternate + modes of table migration, in particular the "recreate" style of + migration required by SQLite. + + "recreate" style is as follows: + + 1. A new table is created with the new specification, based on the + migration directives within the batch, using a temporary name. + + 2. the data copied from the existing table to the new table. + + 3. the existing table is dropped. + + 4. the new table is renamed to the existing table name. + + The directive by default will only use "recreate" style on the + SQLite backend, and only if directives are present which require + this form, e.g. anything other than ``add_column()``. The batch + operation on other backends will proceed using standard ALTER TABLE + operations. + + The method is used as a context manager, which returns an instance + of :class:`.BatchOperations`; this object is the same as + :class:`.Operations` except that table names and schema names + are omitted. E.g.:: + + with op.batch_alter_table("some_table") as batch_op: + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") + + The operations within the context manager are invoked at once + when the context is ended. When run against SQLite, if the + migrations include operations not supported by SQLite's ALTER TABLE, + the entire table will be copied to a new one with the new + specification, moving all data across as well. + + The copy operation by default uses reflection to retrieve the current + structure of the table, and therefore :meth:`.batch_alter_table` + in this mode requires that the migration is run in "online" mode. + The ``copy_from`` parameter may be passed which refers to an existing + :class:`.Table` object, which will bypass this reflection step. + + .. note:: The table copy operation will currently not copy + CHECK constraints, and may not copy UNIQUE constraints that are + unnamed, as is possible on SQLite. See the section + :ref:`sqlite_batch_constraints` for workarounds. + + :param table_name: name of table + :param schema: optional schema name. + :param recreate: under what circumstances the table should be + recreated. At its default of ``"auto"``, the SQLite dialect will + recreate the table if any operations other than ``add_column()``, + ``create_index()``, or ``drop_index()`` are + present. Other options include ``"always"`` and ``"never"``. + :param copy_from: optional :class:`~sqlalchemy.schema.Table` object + that will act as the structure of the table being copied. If omitted, + table reflection is used to retrieve the structure of the table. + + .. seealso:: + + :ref:`batch_offline_mode` + + :paramref:`~.Operations.batch_alter_table.reflect_args` + + :paramref:`~.Operations.batch_alter_table.reflect_kwargs` + + :param reflect_args: a sequence of additional positional arguments that + will be applied to the table structure being reflected / copied; + this may be used to pass column and constraint overrides to the + table that will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param reflect_kwargs: a dictionary of additional keyword arguments + that will be applied to the table structure being copied; this may be + used to pass additional table and reflection options to the table that + will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param table_args: a sequence of additional positional arguments that + will be applied to the new :class:`~sqlalchemy.schema.Table` when + created, in addition to those copied from the source table. + This may be used to provide additional constraints such as CHECK + constraints that may not be reflected. + :param table_kwargs: a dictionary of additional keyword arguments + that will be applied to the new :class:`~sqlalchemy.schema.Table` + when created, in addition to those copied from the source table. + This may be used to provide for additional table options that may + not be reflected. + :param naming_convention: a naming convention dictionary of the form + described at :ref:`autogen_naming_conventions` which will be applied + to the :class:`~sqlalchemy.schema.MetaData` during the reflection + process. This is typically required if one wants to drop SQLite + constraints, as these constraints will not have names when + reflected on this backend. Requires SQLAlchemy **0.9.4** or greater. + + .. seealso:: + + :ref:`dropping_sqlite_foreign_keys` + + :param partial_reordering: a list of tuples, each suggesting a desired + ordering of two or more columns in the newly created table. Requires + that :paramref:`.batch_alter_table.recreate` is set to ``"always"``. + Examples, given a table with columns "a", "b", "c", and "d": + + Specify the order of all columns:: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], + ) as batch_op: + pass + + Ensure "d" appears before "c", and "b", appears before "a":: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], + ) as batch_op: + pass + + The ordering of columns not included in the partial_reordering + set is undefined. Therefore it is best to specify the complete + ordering of all columns for best results. + + .. note:: batch mode requires SQLAlchemy 0.8 or above. + + .. seealso:: + + :ref:`batch_migrations` + + """ + +def bulk_insert( + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, +) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ + +def create_check_constraint( + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, +) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def create_exclude_constraint( + constraint_name: str, table_name: str, *elements: Any, **kw: Any +) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ + +def create_foreign_key( + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, +) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ + +def create_index( + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + +def create_primary_key( + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, +) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def create_table( + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, +) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ + +def create_table_comment( + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, +) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + +def create_unique_constraint( + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, +) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def drop_column( + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, +) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + """ + +def drop_constraint( + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, +) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the constraint + + .. versionadded:: 1.16.0 + + """ + +def drop_index( + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + +def drop_table( + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, +) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ + +def drop_table_comment( + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, +) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + +def execute( + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, +) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ + +def f(name: str) -> conv: + """Indicate a string name that has already had a naming convention + applied to it. + + This feature combines with the SQLAlchemy ``naming_convention`` feature + to disambiguate constraint names that have already had naming + conventions applied to them, versus those that have not. This is + necessary in the case that the ``"%(constraint_name)s"`` token + is used within a naming convention, so that it can be identified + that this particular name should remain fixed. + + If the :meth:`.Operations.f` is used on a constraint, the naming + convention will not take effect:: + + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) + + Above, the CHECK constraint generated will have the name + ``ck_bool_t_x`` regardless of whether or not a naming convention is + in use. + + Alternatively, if a naming convention is in use, and 'f' is not used, + names will be converted along conventions. If the ``target_metadata`` + contains the naming convention + ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the + output of the following:: + + op.add_column("t", "x", Boolean(name="x")) + + will be:: + + CONSTRAINT ck_bool_t_x CHECK (x in (1, 0))) + + The function is rendered in the output of autogenerate when + a particular constraint name is already converted. + + """ + +def get_bind() -> Connection: + """Return the current 'bind'. + + Under normal circumstances, this is the + :class:`~sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + In a SQL script context, this value is ``None``. [TODO: verify this] + + """ + +def get_context() -> MigrationContext: + """Return the :class:`.MigrationContext` object that's + currently in use. + + """ + +def implementation_for( + op_cls: Any, replace: bool = False +) -> Callable[[_C], _C]: + """Register an implementation for a given :class:`.MigrateOperation`. + + :param replace: when True, allows replacement of an already + registered implementation for the given operation class. This + enables customization of built-in operations such as + :class:`.CreateTableOp` by providing an alternate implementation + that can augment, modify, or conditionally invoke the default + behavior. + + .. versionadded:: 1.17.2 + + This is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_plugins` + + :ref:`operations_extending_builtin` + + """ + +def inline_literal( + value: Union[str, int], type_: Optional[TypeEngine[Any]] = None +) -> _literal_bindparam: + r"""Produce an 'inline literal' expression, suitable for + using in an INSERT, UPDATE, or DELETE statement. + + When using Alembic in "offline" mode, CRUD operations + aren't compatible with SQLAlchemy's default behavior surrounding + literal values, + which is that they are converted into bound values and passed + separately into the ``execute()`` method of the DBAPI cursor. + An offline SQL + script needs to have these rendered inline. While it should + always be noted that inline literal values are an **enormous** + security hole in an application that handles untrusted input, + a schema migration is not run in this context, so + literals are safe to render inline, with the caveat that + advanced types like dates may not be supported directly + by SQLAlchemy. + + See :meth:`.Operations.execute` for an example usage of + :meth:`.Operations.inline_literal`. + + The environment can also be configured to attempt to render + "literal" values inline automatically, for those simple types + that are supported by the dialect; see + :paramref:`.EnvironmentContext.configure.literal_binds` for this + more recently added feature. + + :param value: The value to render. Strings, integers, and simple + numerics should be supported. Other types like boolean, + dates, etc. may or may not be supported yet by various + backends. + :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine` + subclass stating the type of this value. In SQLAlchemy + expressions, this is usually derived automatically + from the Python type of the value itself, as well as + based on the context in which the value is used. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.literal_binds` + + """ + +@overload +def invoke(operation: CreateTableOp) -> Table: ... +@overload +def invoke( + operation: Union[ + AddConstraintOp, + DropConstraintOp, + CreateIndexOp, + DropIndexOp, + AddColumnOp, + AlterColumnOp, + AlterTableOp, + CreateTableCommentOp, + DropTableCommentOp, + DropColumnOp, + BulkInsertOp, + DropTableOp, + ExecuteSQLOp, + ], +) -> None: ... +@overload +def invoke(operation: MigrateOperation) -> Any: + """Given a :class:`.MigrateOperation`, invoke it in terms of + this :class:`.Operations` instance. + + """ + +def register_operation( + name: str, sourcename: Optional[str] = None +) -> Callable[[Type[_T]], Type[_T]]: + """Register a new operation for this class. + + This method is normally used to add new operations + to the :class:`.Operations` class, and possibly the + :class:`.BatchOperations` class as well. All Alembic migration + operations are implemented via this system, however the system + is also available as a public API to facilitate adding custom + operations. + + .. seealso:: + + :ref:`operation_plugins` + + + """ + +def rename_table( + old_table_name: str, new_table_name: str, *, schema: Optional[str] = None +) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + +def run_async( + async_function: Callable[..., Awaitable[_T]], *args: Any, **kw_args: Any +) -> _T: + """Invoke the given asynchronous callable, passing an asynchronous + :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first + argument. + + This method allows calling async functions from within the + synchronous ``upgrade()`` or ``downgrade()`` alembic migration + method. + + The async connection passed to the callable shares the same + transaction as the connection running in the migration context. + + Any additional arg or kw_arg passed to this function are passed + to the provided async function. + + .. versionadded: 1.11 + + .. note:: + + This method can be called only when alembic is called using + an async dialect. + """ diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__init__.py b/venv/lib/python3.12/site-packages/alembic/operations/__init__.py new file mode 100644 index 0000000..26197cb --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/__init__.py @@ -0,0 +1,15 @@ +from . import toimpl +from .base import AbstractOperations +from .base import BatchOperations +from .base import Operations +from .ops import MigrateOperation +from .ops import MigrationScript + + +__all__ = [ + "AbstractOperations", + "Operations", + "BatchOperations", + "MigrateOperation", + "MigrationScript", +] diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81172f70bbc7652f9fddf43f4d1e5ea3e71c6ebc GIT binary patch literal 510 zcmY+AyGjHx6o!+X>#le~EJYLq*+PTb%7S9+1+7!P){3=qJt57tD3)R{KD5czys={E|w_A3C0yS zWFz+yuVxR}PyCvF;2;TV4rG`|NmO$P949eGJ=Be!andZ}cJm!(qBbr=r$nwL(Bgh- zone_f(cCa6)WCu}f52SUpR!t0-NKRRL7Y#_>eASQC(be>v}*^(hjAvXB&Z~+BnCNd zw@PF@pwds7<>O$TsnVsVwEa()5v5E@N?*_yoTBeL(pNbrEbU%Pb}MelA-7jfX>!bO zR!@z(j_>T8-1#@}FEWlr?h1wO64-aEJLQ5mGK0<3Fv|Q`zXBf^j?jth? FksrL}i$wqc literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d93aa05841d97c472dc1807efd34a23613269023 GIT binary patch literal 81783 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/batch.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ebb90ebe0437a1605a116fb414368c1e62979cf GIT binary patch literal 31485 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/ops.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fd038fc73d30e69da2bfe1408c7e5f796c62aad0 GIT binary patch literal 113740 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 NHLUuZ5EH_i{68Ie5d{DM literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/schemaobj.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ab864ec6ad1c293dbef65618fc69bbcf1cf6c16 GIT binary patch literal 11888 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/operations/__pycache__/toimpl.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..469895d45146198226a1fccdee0e974563bd967c GIT binary patch literal 12048 zcmdTqTWlNGa=YY`T#`%iA?huYlx00AS(0T(mS3@B%XVzVmE6P^ZCtbhtzFra`N}S3 zTgIwkid@7ljO!Z|g=HW>T;P7l*T#iE`jOB6w;$T5#xe^Va9iAu{LxlZ6!)X;%vmmX zNv#y;76oos&^c%4%$ak}%$%7yv;4LJXyB`Kq=%yZ4PT7MS#50oJws6sD4yc! z43(uX(=@gn8AsN6nSr*GXELs=`?8y~T^Uc7z05+}&3iK5tnadqv{`8TFZ*dqphY)~ z_hz_k;BtVb9F)K=b{mtLwcuWinQunx39Yxhl=;VZ@cz%4CsmZop&cmhHt3kOL6Ps| zISZA@4pjQh(MHRlVy?_+|^m#10fs8Fb9rpvdp$qZTT=cc60C9Bs4=Ds|H6Wh2r226hAcRn6)j%e!ks8acJ3>9QYoGBxx;h>EnxM=BCb_ znfl=T%=?dN%`MGe6SArA(V9TNGhdLP^34_mF;z_GbJF(?y~i~-pD!#!pIiDQlbYKP zE!U;gm5eYmj~K?&1P!#8n`J1!2tS}?YSHMk)Qi?JRrsRIf>3R~{b-(gQl6>3061#{cLJPP|N6ca2H;#h4Bu8<0LOhcJS%R1hZ=zJy5a$N#0Jg+JZb~?0=(4* z?gMzc4crg#P8&D}a2tQJ61Wpypn3XpcBM&nh-uj+UIjke_?SiTgpH5vDfSw$#XUyL ztYx?CFeD+fPaRLH-g6}=2N!Mp#cAr#0lEUdN^X)vhCWO5J#HJ>C(XHxmN|~5?hP`O z%<#@7T5?QNe?`lj*@ijnwb91w$R63f;E?^YSN6#+nUx(fBRgeI4)DxZD2cts>}LHd z`WFxr06%j-g`eBtj+u0g7Vy^ff6Ci&V z^aH9`>y@cu?WivzUc+N-hof%MVgi^)*&SW%mBOUg(?9@T@6v|ad_+B_XOfKOOH1jT zR7~aO10D9c=H^8qRTSpLrHqhtXuf%~kDbqF z3+ap?O1O(e>37Rx*Ya6mEOmun%A{_mZ;zc8r0d0eVQfaYnYbwC7lrwvG*(D0XN5WG zTE388Se}yv@kV-H7`q|lZj5EpSH=p<#cTQ8fzk2(V^X>(j1*Gy*Hc#oX)KiyvRBgc zWBHo-9V_P3*+OQtu&jl_0x#u?P#R0WJ&?*21rZ>brEG5WSSCN8%1FmYw;Mcovw0pxLe}Be`8G$^>nq}BNLycYOBJG=G-4Rx?kOTS4c1{uRMLXHm@imltZ`NuYmVzTHMhQQttrLx7R+Qs^O=gH zd28(kCWz=0L{tfJ01Fh6i2g$h=7gI-5S2-1Lyu)vLoZBS@*QADRDwm>1FTT3)P z?}_NWL^LL(z|Bhk1ttNsufkHH&ikL<{ZtM1E5Uv>cvuM@F0+yQ!Mj0~jVo;YQFPPk z2)Rq=0JSBiMu(N?aOscA?LBJy0j2%GI@3~)4XCjbO6JsZztYrSdcWM3P}|0pw()f)`tOX><$LX;*p`PaWv*49Pvr&_ZUA0i;0n4G zu6y;*)M;?yAA@d%)GyPpK@u>58a4#IU2f;6c zYG_OejXm={4;`(z0HMNBT<_W*#Xq{vjIR4f%l^nJyv5d;*t$RVib<5FznJ}OR%H?j zlK{Sz+2)F?$>pxNsYqwJt*;#DE;luogDvIO-g08H+&NqhwUs-PuR9Q_lk!LJ@4dVC zO&7eqPEc%M>wYYdL4_M!>v|?^aL0Dq)lr*WjVhthwcF1_M_$9Oj#dINm58xRU9gCn zM}oy)L*oIJrbvhe?Z)S`AUNDcAiT>Co<_4q9DB{rn22^B2PoP25b!%Tp`qbd7U(q0 zrMvfK>ak-+^QIT(h((f;F7YVLDDH+r^O4g_&bY=F^K*JDoK0WV!)ZLOMd4QQu~VGD zL3jxi@op(0AK)PAM+f&LV(~NV!nnUgz4H1?r^=p?>giNGo$H?da%ZpFIihrq+;QAF zdN){RVuo(S6edDUrU{UhMEy zkMO1+-nPoHcBFm-^Z&jwgoB88A{!}^gJ=VnotA@$derc2BWFBTPD4s~tO9VXa`|HI zXpuvd&hf%6T~23!t}ZOD8nltlBx4>Y&6(zJ>1PTTiq1(tRPWb;OjkD0Hi{u1yv(9==V;>f`uX&UQU<$m5LZ)z-*%5??T&RmJCfNzQn5OM9IYw zz?SMY{Q?re;&bf#7z(24KCbFOw$o{hWMT>zTfMEuk1Fw_;P)Y{YS8o+g&A4f{Vg+6 z_J>Lrc94S%^KP5}vE#6<#Q|y9a2237p5rNPWE*I_r8;KFZ3Wm$d^0Bs7_{p?nb>!& z2?JijS2OT`>@aPKgs#r8A2ZMvvG^tYOYcBIxata812S;BXvbI6YV4pAJNV7AdSX^N zF!2itx6u+;@wpUNvNXKC)Eiy?A!la;74LTRCT34Mn)!R&6&_dJ> zmtC{3c+f>G9>Tx07Yf23h`;TWs!8YCpu!BUwLE9WwkpyeB3~kAdI&_q&IC2 z7fEmW8i>Oyq&J;*VcBQ?BI*`-WT&BXuo}uPNxB#?aLpQ0Zcdpo5A_O5jBLcjFj{tL zR2&U~BqniflBtkN^YD(Z7}-@#+Oc-2RY*?$9Cqj}b{R9t-eR}Wwo92B`7DvX7n9xM z2iuHH7&OefHgeodumWfIm>5RO;3e<8J_w4|VZry>rC1yA>3i2f26kMuORaj1(U4mG z0#d8&;*hZpvj#pvqGiRm4PCSq4Ug=z(2WCK`xT8g41?FjeOqCWJM1CZLAzbq(b&J0 z21l*g(7SLIk_O2*q$G*RB+Wx2K|vxhwb+ZG zu(VVt*gIH!v&L*s6yi`R^4 z$s`h*6!1>wqu&EC9ul#54F3|UAzZHQ6GoQYkvDR``))S`P}W3L$8NP_Lg|=TXIfu* z0$<3V$?Kl@i*V~NZ~V=!JMLF(L}hm=>@Jni~8%QU1W2Ahx1ZTl<6Tm)SQI=bF6ZdlBqd9r{&T?LV#b zpMD-Z1IeoJ?iW!AQ>u_mG2c+%1&1O1JWyckE9ENxg)ly!VWmZFzN@#MOo2 z=V|)#SdI3G!j7yhJZC2$f{G?eH@7-R7f6vf#|$)nV!VlC8y+~~HcrtnRz__NoiMo6 zPeg!Z6^vZ29>QOQUTYYsg=?x`LtFry?+_17Yh6&eTVZySK2O z8*>j(Gz$&SQ|A*#2U%}ka7SQfZnu5(#}`g!@H6;wV*lvAgeZKnlokbEPwU~Zq+f6Q zp{+H~qAn<2IF)tEhE23i}wpWW>6>@@@o(Fqm2w4l7ZT+%=$@e-aew5HH z`_?-(p^e4A!oTzjDC)0t)tunVV9Se0-+KRvjmUfJ+wE1qEYc=gq#e%XAHY2jeyrfn!ndh3 zd=(VLqH!biUs=jrpVMK)eQJ!DzbT1XV6S))8Ew7)3@n1rZ^HZ%{_0~c zUm!pJ07jYVQIFm&Xdd_l2qv+`Phino_@w!(cyoXz76IkI;oo{+)<0gTOrOH^smuuE z6NriHP}q*OkJXWL%E&q7*{I30q+1UR(7utJ`Ry?LP-XNae*K&xG0AK}bBjW8NzBb` zMGhkJY51;o2|vEg7Q`i>V}lnm@C=&UA(Gjr$XDa@n4H|6WNqpn!Lv}bE>h%RrCT>d zusMJ^2jYCL{&!-R+sMkSEZtDXLZJV}W8Jco|!0NSU24PWKJ^2v3BDK|+3WNy`&2N=G z2Gv@yQOX$IXd!*^7U81-)wnvzp>fr?IvJt;cuW!RLZbKIvB%&OJN!`NxOfk`Ak3kp zX%HNQo;3XfOVK^QrG|b>gaI9RBY8}3ymEQpxDc}Gq#XNotjbwO9g(j;efa4HJ@&@orrn6PuY#>g zqz*}qk16r74H`Oqw3|$Xb@FJ%2{oApY8(!0fffYW+bq;W!#87CC)T)3sL61stBZsd zTnwzkYfEHnON3UXwGPR`43-X)Ik3h#pnm1z-ngKy&H=6Mhr6mf#2SY~O_m6CbvU#t zp@!KfmH6ZaeW>DK=zU~%tdmm}C)8v()HwVw!GKGUb%$zvLWxgo&{sIZH|PTu2W}0efOYctmMy+RFt%** zfD%8jK|^P#IvnfdiHZ|yvPP(J`U!$j>2H|5rb2bIg9^dBmBFAw0NFD8TMvJY!^v6u E9~mw3mjD0& literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/operations/base.py b/venv/lib/python3.12/site-packages/alembic/operations/base.py new file mode 100644 index 0000000..b9e6107 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/base.py @@ -0,0 +1,2001 @@ +# mypy: allow-untyped-calls + +from __future__ import annotations + +from contextlib import contextmanager +import re +import textwrap +from typing import Any +from typing import Awaitable +from typing import Callable +from typing import Dict +from typing import Iterator +from typing import List # noqa +from typing import Mapping +from typing import NoReturn +from typing import Optional +from typing import overload +from typing import Sequence # noqa +from typing import Tuple +from typing import Type # noqa +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy.sql.elements import conv + +from . import batch +from . import schemaobj +from .. import util +from ..ddl.base import _ServerDefaultType +from ..util import sqla_compat +from ..util.compat import formatannotation_fwdref +from ..util.compat import inspect_formatargspec +from ..util.compat import inspect_getfullargspec +from ..util.sqla_compat import _literal_bindparam + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy import Table + from sqlalchemy.engine import Connection + from sqlalchemy.sql import Executable + from sqlalchemy.sql.expression import ColumnElement + from sqlalchemy.sql.expression import TableClause + from sqlalchemy.sql.expression import TextClause + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.types import TypeEngine + + from .batch import BatchOperationsImpl + from .ops import AddColumnOp + from .ops import AddConstraintOp + from .ops import AlterColumnOp + from .ops import AlterTableOp + from .ops import BulkInsertOp + from .ops import CreateIndexOp + from .ops import CreateTableCommentOp + from .ops import CreateTableOp + from .ops import DropColumnOp + from .ops import DropConstraintOp + from .ops import DropIndexOp + from .ops import DropTableCommentOp + from .ops import DropTableOp + from .ops import ExecuteSQLOp + from .ops import MigrateOperation + from ..ddl import DefaultImpl + from ..runtime.migration import MigrationContext +__all__ = ("Operations", "BatchOperations") +_T = TypeVar("_T") + +_C = TypeVar("_C", bound=Callable[..., Any]) + + +class AbstractOperations(util.ModuleClsProxy): + """Base class for Operations and BatchOperations. + + .. versionadded:: 1.11.0 + + """ + + impl: Union[DefaultImpl, BatchOperationsImpl] + _to_impl = util.Dispatcher() + + def __init__( + self, + migration_context: MigrationContext, + impl: Optional[BatchOperationsImpl] = None, + ) -> None: + """Construct a new :class:`.Operations` + + :param migration_context: a :class:`.MigrationContext` + instance. + + """ + self.migration_context = migration_context + if impl is None: + self.impl = migration_context.impl + else: + self.impl = impl + + self.schema_obj = schemaobj.SchemaObjects(migration_context) + + @classmethod + def register_operation( + cls, name: str, sourcename: Optional[str] = None + ) -> Callable[[Type[_T]], Type[_T]]: + """Register a new operation for this class. + + This method is normally used to add new operations + to the :class:`.Operations` class, and possibly the + :class:`.BatchOperations` class as well. All Alembic migration + operations are implemented via this system, however the system + is also available as a public API to facilitate adding custom + operations. + + .. seealso:: + + :ref:`operation_plugins` + + + """ + + def register(op_cls: Type[_T]) -> Type[_T]: + if sourcename is None: + fn = getattr(op_cls, name) + source_name = fn.__name__ + else: + fn = getattr(op_cls, sourcename) + source_name = fn.__name__ + + spec = inspect_getfullargspec(fn) + + name_args = spec[0] + assert name_args[0:2] == ["cls", "operations"] + + name_args[0:2] = ["self"] + + args = inspect_formatargspec( + *spec, formatannotation=formatannotation_fwdref + ) + num_defaults = len(spec[3]) if spec[3] else 0 + + defaulted_vals: Tuple[Any, ...] + + if num_defaults: + defaulted_vals = tuple(name_args[0 - num_defaults :]) + else: + defaulted_vals = () + + defaulted_vals += tuple(spec[4]) + # here, we are using formatargspec in a different way in order + # to get a string that will re-apply incoming arguments to a new + # function call + + apply_kw = inspect_formatargspec( + name_args + spec[4], + spec[1], + spec[2], + defaulted_vals, + formatvalue=lambda x: "=" + x, + formatannotation=formatannotation_fwdref, + ) + + args = re.sub( + r'[_]?ForwardRef\(([\'"].+?[\'"])\)', + lambda m: m.group(1), + args, + ) + + func_text = textwrap.dedent( + """\ + def %(name)s%(args)s: + %(doc)r + return op_cls.%(source_name)s%(apply_kw)s + """ + % { + "name": name, + "source_name": source_name, + "args": args, + "apply_kw": apply_kw, + "doc": fn.__doc__, + } + ) + + globals_ = dict(globals()) + globals_.update({"op_cls": op_cls}) + lcl: Dict[str, Any] = {} + + exec(func_text, globals_, lcl) + setattr(cls, name, lcl[name]) + fn.__func__.__doc__ = ( + "This method is proxied on " + "the :class:`.%s` class, via the :meth:`.%s.%s` method." + % (cls.__name__, cls.__name__, name) + ) + if hasattr(fn, "_legacy_translations"): + lcl[name]._legacy_translations = fn._legacy_translations + return op_cls + + return register + + @classmethod + def implementation_for( + cls, op_cls: Any, replace: bool = False + ) -> Callable[[_C], _C]: + """Register an implementation for a given :class:`.MigrateOperation`. + + :param replace: when True, allows replacement of an already + registered implementation for the given operation class. This + enables customization of built-in operations such as + :class:`.CreateTableOp` by providing an alternate implementation + that can augment, modify, or conditionally invoke the default + behavior. + + .. versionadded:: 1.17.2 + + This is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_plugins` + + :ref:`operations_extending_builtin` + + """ + + def decorate(fn: _C) -> _C: + cls._to_impl.dispatch_for(op_cls, replace=replace)(fn) + return fn + + return decorate + + @classmethod + @contextmanager + def context( + cls, migration_context: MigrationContext + ) -> Iterator[Operations]: + op = Operations(migration_context) + op._install_proxy() + yield op + op._remove_proxy() + + @contextmanager + def batch_alter_table( + self, + table_name: str, + schema: Optional[str] = None, + recreate: Literal["auto", "always", "never"] = "auto", + partial_reordering: list[tuple[str, ...]] | None = None, + copy_from: Optional[Table] = None, + table_args: Tuple[Any, ...] = (), + table_kwargs: Mapping[str, Any] = util.immutabledict(), + reflect_args: Tuple[Any, ...] = (), + reflect_kwargs: Mapping[str, Any] = util.immutabledict(), + naming_convention: Optional[Dict[str, str]] = None, + ) -> Iterator[BatchOperations]: + """Invoke a series of per-table migrations in batch. + + Batch mode allows a series of operations specific to a table + to be syntactically grouped together, and allows for alternate + modes of table migration, in particular the "recreate" style of + migration required by SQLite. + + "recreate" style is as follows: + + 1. A new table is created with the new specification, based on the + migration directives within the batch, using a temporary name. + + 2. the data copied from the existing table to the new table. + + 3. the existing table is dropped. + + 4. the new table is renamed to the existing table name. + + The directive by default will only use "recreate" style on the + SQLite backend, and only if directives are present which require + this form, e.g. anything other than ``add_column()``. The batch + operation on other backends will proceed using standard ALTER TABLE + operations. + + The method is used as a context manager, which returns an instance + of :class:`.BatchOperations`; this object is the same as + :class:`.Operations` except that table names and schema names + are omitted. E.g.:: + + with op.batch_alter_table("some_table") as batch_op: + batch_op.add_column(Column("foo", Integer)) + batch_op.drop_column("bar") + + The operations within the context manager are invoked at once + when the context is ended. When run against SQLite, if the + migrations include operations not supported by SQLite's ALTER TABLE, + the entire table will be copied to a new one with the new + specification, moving all data across as well. + + The copy operation by default uses reflection to retrieve the current + structure of the table, and therefore :meth:`.batch_alter_table` + in this mode requires that the migration is run in "online" mode. + The ``copy_from`` parameter may be passed which refers to an existing + :class:`.Table` object, which will bypass this reflection step. + + .. note:: The table copy operation will currently not copy + CHECK constraints, and may not copy UNIQUE constraints that are + unnamed, as is possible on SQLite. See the section + :ref:`sqlite_batch_constraints` for workarounds. + + :param table_name: name of table + :param schema: optional schema name. + :param recreate: under what circumstances the table should be + recreated. At its default of ``"auto"``, the SQLite dialect will + recreate the table if any operations other than ``add_column()``, + ``create_index()``, or ``drop_index()`` are + present. Other options include ``"always"`` and ``"never"``. + :param copy_from: optional :class:`~sqlalchemy.schema.Table` object + that will act as the structure of the table being copied. If omitted, + table reflection is used to retrieve the structure of the table. + + .. seealso:: + + :ref:`batch_offline_mode` + + :paramref:`~.Operations.batch_alter_table.reflect_args` + + :paramref:`~.Operations.batch_alter_table.reflect_kwargs` + + :param reflect_args: a sequence of additional positional arguments that + will be applied to the table structure being reflected / copied; + this may be used to pass column and constraint overrides to the + table that will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param reflect_kwargs: a dictionary of additional keyword arguments + that will be applied to the table structure being copied; this may be + used to pass additional table and reflection options to the table that + will be reflected, in lieu of passing the whole + :class:`~sqlalchemy.schema.Table` using + :paramref:`~.Operations.batch_alter_table.copy_from`. + :param table_args: a sequence of additional positional arguments that + will be applied to the new :class:`~sqlalchemy.schema.Table` when + created, in addition to those copied from the source table. + This may be used to provide additional constraints such as CHECK + constraints that may not be reflected. + :param table_kwargs: a dictionary of additional keyword arguments + that will be applied to the new :class:`~sqlalchemy.schema.Table` + when created, in addition to those copied from the source table. + This may be used to provide for additional table options that may + not be reflected. + :param naming_convention: a naming convention dictionary of the form + described at :ref:`autogen_naming_conventions` which will be applied + to the :class:`~sqlalchemy.schema.MetaData` during the reflection + process. This is typically required if one wants to drop SQLite + constraints, as these constraints will not have names when + reflected on this backend. Requires SQLAlchemy **0.9.4** or greater. + + .. seealso:: + + :ref:`dropping_sqlite_foreign_keys` + + :param partial_reordering: a list of tuples, each suggesting a desired + ordering of two or more columns in the newly created table. Requires + that :paramref:`.batch_alter_table.recreate` is set to ``"always"``. + Examples, given a table with columns "a", "b", "c", and "d": + + Specify the order of all columns:: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("c", "d", "a", "b")], + ) as batch_op: + pass + + Ensure "d" appears before "c", and "b", appears before "a":: + + with op.batch_alter_table( + "some_table", + recreate="always", + partial_reordering=[("d", "c"), ("b", "a")], + ) as batch_op: + pass + + The ordering of columns not included in the partial_reordering + set is undefined. Therefore it is best to specify the complete + ordering of all columns for best results. + + .. note:: batch mode requires SQLAlchemy 0.8 or above. + + .. seealso:: + + :ref:`batch_migrations` + + """ + impl = batch.BatchOperationsImpl( + self, + table_name, + schema, + recreate, + copy_from, + table_args, + table_kwargs, + reflect_args, + reflect_kwargs, + naming_convention, + partial_reordering, + ) + batch_op = BatchOperations(self.migration_context, impl=impl) + yield batch_op + impl.flush() + + def get_context(self) -> MigrationContext: + """Return the :class:`.MigrationContext` object that's + currently in use. + + """ + + return self.migration_context + + @overload + def invoke(self, operation: CreateTableOp) -> Table: ... + + @overload + def invoke( + self, + operation: Union[ + AddConstraintOp, + DropConstraintOp, + CreateIndexOp, + DropIndexOp, + AddColumnOp, + AlterColumnOp, + AlterTableOp, + CreateTableCommentOp, + DropTableCommentOp, + DropColumnOp, + BulkInsertOp, + DropTableOp, + ExecuteSQLOp, + ], + ) -> None: ... + + @overload + def invoke(self, operation: MigrateOperation) -> Any: ... + + def invoke(self, operation: MigrateOperation) -> Any: + """Given a :class:`.MigrateOperation`, invoke it in terms of + this :class:`.Operations` instance. + + """ + fn = self._to_impl.dispatch( + operation, self.migration_context.impl.__dialect__ + ) + return fn(self, operation) + + def f(self, name: str) -> conv: + """Indicate a string name that has already had a naming convention + applied to it. + + This feature combines with the SQLAlchemy ``naming_convention`` feature + to disambiguate constraint names that have already had naming + conventions applied to them, versus those that have not. This is + necessary in the case that the ``"%(constraint_name)s"`` token + is used within a naming convention, so that it can be identified + that this particular name should remain fixed. + + If the :meth:`.Operations.f` is used on a constraint, the naming + convention will not take effect:: + + op.add_column("t", "x", Boolean(name=op.f("ck_bool_t_x"))) + + Above, the CHECK constraint generated will have the name + ``ck_bool_t_x`` regardless of whether or not a naming convention is + in use. + + Alternatively, if a naming convention is in use, and 'f' is not used, + names will be converted along conventions. If the ``target_metadata`` + contains the naming convention + ``{"ck": "ck_bool_%(table_name)s_%(constraint_name)s"}``, then the + output of the following:: + + op.add_column("t", "x", Boolean(name="x")) + + will be:: + + CONSTRAINT ck_bool_t_x CHECK (x in (1, 0))) + + The function is rendered in the output of autogenerate when + a particular constraint name is already converted. + + """ + return conv(name) + + def inline_literal( + self, value: Union[str, int], type_: Optional[TypeEngine[Any]] = None + ) -> _literal_bindparam: + r"""Produce an 'inline literal' expression, suitable for + using in an INSERT, UPDATE, or DELETE statement. + + When using Alembic in "offline" mode, CRUD operations + aren't compatible with SQLAlchemy's default behavior surrounding + literal values, + which is that they are converted into bound values and passed + separately into the ``execute()`` method of the DBAPI cursor. + An offline SQL + script needs to have these rendered inline. While it should + always be noted that inline literal values are an **enormous** + security hole in an application that handles untrusted input, + a schema migration is not run in this context, so + literals are safe to render inline, with the caveat that + advanced types like dates may not be supported directly + by SQLAlchemy. + + See :meth:`.Operations.execute` for an example usage of + :meth:`.Operations.inline_literal`. + + The environment can also be configured to attempt to render + "literal" values inline automatically, for those simple types + that are supported by the dialect; see + :paramref:`.EnvironmentContext.configure.literal_binds` for this + more recently added feature. + + :param value: The value to render. Strings, integers, and simple + numerics should be supported. Other types like boolean, + dates, etc. may or may not be supported yet by various + backends. + :param type\_: optional - a :class:`sqlalchemy.types.TypeEngine` + subclass stating the type of this value. In SQLAlchemy + expressions, this is usually derived automatically + from the Python type of the value itself, as well as + based on the context in which the value is used. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.literal_binds` + + """ + return sqla_compat._literal_bindparam(None, value, type_=type_) + + def get_bind(self) -> Connection: + """Return the current 'bind'. + + Under normal circumstances, this is the + :class:`~sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + In a SQL script context, this value is ``None``. [TODO: verify this] + + """ + return self.migration_context.impl.bind # type: ignore[return-value] + + def run_async( + self, + async_function: Callable[..., Awaitable[_T]], + *args: Any, + **kw_args: Any, + ) -> _T: + """Invoke the given asynchronous callable, passing an asynchronous + :class:`~sqlalchemy.ext.asyncio.AsyncConnection` as the first + argument. + + This method allows calling async functions from within the + synchronous ``upgrade()`` or ``downgrade()`` alembic migration + method. + + The async connection passed to the callable shares the same + transaction as the connection running in the migration context. + + Any additional arg or kw_arg passed to this function are passed + to the provided async function. + + .. versionadded: 1.11 + + .. note:: + + This method can be called only when alembic is called using + an async dialect. + """ + if not sqla_compat.sqla_14_18: + raise NotImplementedError("SQLAlchemy 1.4.18+ required") + sync_conn = self.get_bind() + if sync_conn is None: + raise NotImplementedError("Cannot call run_async in SQL mode") + if not sync_conn.dialect.is_async: + raise ValueError("Cannot call run_async with a sync engine") + from sqlalchemy.ext.asyncio import AsyncConnection + from sqlalchemy.util import await_only + + async_conn = AsyncConnection._retrieve_proxy_for_target(sync_conn) + return await_only(async_function(async_conn, *args, **kw_args)) + + +class Operations(AbstractOperations): + """Define high level migration operations. + + Each operation corresponds to some schema migration operation, + executed against a particular :class:`.MigrationContext` + which in turn represents connectivity to a database, + or a file output stream. + + While :class:`.Operations` is normally configured as + part of the :meth:`.EnvironmentContext.run_migrations` + method called from an ``env.py`` script, a standalone + :class:`.Operations` instance can be + made for use cases external to regular Alembic + migrations by passing in a :class:`.MigrationContext`:: + + from alembic.migration import MigrationContext + from alembic.operations import Operations + + conn = myengine.connect() + ctx = MigrationContext.configure(conn) + op = Operations(ctx) + + op.alter_column("t", "c", nullable=True) + + Note that as of 0.8, most of the methods on this class are produced + dynamically using the :meth:`.Operations.register_operation` + method. + + """ + + if TYPE_CHECKING: + # START STUB FUNCTIONS: op_cls + # ### the following stubs are generated by tools/write_pyi.py ### + # ### do not edit ### + + def add_column( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. Options + also exist for control of single-column primary key and foreign key + constraints to be generated. + + .. note:: + + Not all contraint types may be indicated with this directive. + NOT NULL, FOREIGN KEY, and CHECK are honored, PRIMARY KEY + is conditionally honored, UNIQUE + is currently not. + + As of 1.18.2, the following :class:`~sqlalchemy.schema.Column` + parameters are **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + **PRIMARY KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + ``primary_key=True`` directive, indicating the column intends to be + part of a primary key constraint. However by default, the inline + "PRIMARY KEY" directive is not emitted, and it's assumed that a + separate :meth:`.Operations.create_primary_key` directive will be used + to create this constraint, which may potentially include other columns + as well as have an explicit name. To instead render an inline + "PRIMARY KEY" directive, the + :paramref:`.AddColumnOp.inline_primary_key` parameter may be indicated + at the same time as the ``primary_key`` parameter (both are needed):: + + from alembic import op + from sqlalchemy import Column, INTEGER + + op.add_column( + "organization", + Column("id", INTEGER, primary_key=True), + inline_primary_key=True + ) + + The ``primary_key=True`` parameter on + :class:`~sqlalchemy.schema.Column` also indicates behaviors such as + using the ``SERIAL`` datatype with the PostgreSQL database, which is + why two separate, independent parameters are provided to support all + combinations. + + .. versionadded:: 1.18.4 Added + :paramref:`.AddColumnOp.inline_primary_key` + to control use of the ``PRIMARY KEY`` inline directive. + + **FOREIGN KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. By default, Alembic will automatically + emit a second ALTER statement in order to add the single-column FOREIGN + KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + To render the FOREIGN KEY constraint inline within the ADD COLUMN + directive, use the ``inline_references`` parameter. This can improve + performance on large tables since the constraint is marked as valid + immediately for nullable columns:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + inline_references=True, + ) + + **Indicating server side defaults** + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds ``IF NOT EXISTS`` operator + when creating the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param inline_references: If True, renders ``FOREIGN KEY`` constraints + inline within the ``ADD COLUMN`` directive using ``REFERENCES`` + syntax, rather than as a separate ``ALTER TABLE ADD CONSTRAINT`` + statement. This is supported by PostgreSQL, Oracle, MySQL 5.7+, and + MariaDB 10.5+. + + .. versionadded:: 1.18.2 + + :param inline_primary_key: If True, renders the ``PRIMARY KEY`` phrase + inline within the ``ADD COLUMN`` directive. When not present or + False, ``PRIMARY KEY`` is not emitted; it is assumed that the + migration script will include an additional + :meth:`.Operations.create_primary_key` directive to create a full + primary key constraint. + + .. versionadded:: 1.18.4 + + """ # noqa: E501 + ... + + def alter_column( + self, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[ + TypeEngine[Any], Type[TypeEngine[Any]], None + ] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ # noqa: E501 + ... + + def bulk_insert( + self, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ # noqa: E501 + ... + + def create_check_constraint( + self, + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def create_exclude_constraint( + self, + constraint_name: str, + table_name: str, + *elements: Any, + **kw: Any, + ) -> Optional[Table]: + """Issue an alter to create an EXCLUDE constraint using the + current migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + e.g.:: + + from alembic import op + + op.create_exclude_constraint( + "user_excl", + "user", + ("period", "&&"), + ("group", "="), + where=("group != 'some group'"), + ) + + Note that the expressions work the same way as that of + the ``ExcludeConstraint`` object itself; if plain strings are + passed, quoting rules must be applied manually. + + :param name: Name of the constraint. + :param table_name: String name of the source table. + :param elements: exclude conditions. + :param where: SQL expression or SQL string with optional WHERE + clause. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. + + """ # noqa: E501 + ... + + def create_foreign_key( + self, + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ # noqa: E501 + ... + + def create_index( + self, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ # noqa: E501 + ... + + def create_primary_key( + self, + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, + ) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def create_table( + self, + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ # noqa: E501 + ... + + def create_table_comment( + self, + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ # noqa: E501 + ... + + def create_unique_constraint( + self, + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + def drop_column( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + """ # noqa: E501 + ... + + def drop_constraint( + self, + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + ) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the constraint + + .. versionadded:: 1.16.0 + + """ # noqa: E501 + ... + + def drop_index( + self, + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ # noqa: E501 + ... + + def drop_table( + self, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ # noqa: E501 + ... + + def drop_table_comment( + self, + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ # noqa: E501 + ... + + def execute( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ # noqa: E501 + ... + + def rename_table( + self, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ # noqa: E501 + ... + + # END STUB FUNCTIONS: op_cls + + +class BatchOperations(AbstractOperations): + """Modifies the interface :class:`.Operations` for batch mode. + + This basically omits the ``table_name`` and ``schema`` parameters + from associated methods, as these are a given when running under batch + mode. + + .. seealso:: + + :meth:`.Operations.batch_alter_table` + + Note that as of 0.8, most of the methods on this class are produced + dynamically using the :meth:`.Operations.register_operation` + method. + + """ + + impl: BatchOperationsImpl + + def _noop(self, operation: Any) -> NoReturn: + raise NotImplementedError( + "The %s method does not apply to a batch table alter operation." + % operation + ) + + if TYPE_CHECKING: + # START STUB FUNCTIONS: batch_op + # ### the following stubs are generated by tools/write_pyi.py ### + # ### do not edit ### + + def add_column( + self, + column: Column[Any], + *, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + """Issue an "add column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.add_column` + + """ # noqa: E501 + ... + + def alter_column( + self, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Union[str, Literal[False], None] = False, + server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + new_column_name: Optional[str] = None, + type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None, + existing_type: Union[ + TypeEngine[Any], Type[TypeEngine[Any]], None + ] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue an "alter column" instruction using the current + batch migration context. + + Parameters are the same as that of :meth:`.Operations.alter_column`, + as well as the following option(s): + + :param insert_before: String name of an existing column which this + column should be placed before, when creating the new table. + + :param insert_after: String name of an existing column which this + column should be placed after, when creating the new table. If + both :paramref:`.BatchOperations.alter_column.insert_before` + and :paramref:`.BatchOperations.alter_column.insert_after` are + omitted, the column is inserted after the last existing column + in the table. + + .. seealso:: + + :meth:`.Operations.alter_column` + + + """ # noqa: E501 + ... + + def create_check_constraint( + self, + constraint_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_check_constraint` + + """ # noqa: E501 + ... + + def create_exclude_constraint( + self, constraint_name: str, *elements: Any, **kw: Any + ) -> Optional[Table]: + """Issue a "create exclude constraint" instruction using the + current batch migration context. + + .. note:: This method is Postgresql specific, and additionally + requires at least SQLAlchemy 1.0. + + .. seealso:: + + :meth:`.Operations.create_exclude_constraint` + + """ # noqa: E501 + ... + + def create_foreign_key( + self, + constraint_name: Optional[str], + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + referent_schema: Optional[str] = None, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``source_schema`` + arguments from the call. + + e.g.:: + + with batch_alter_table("address") as batch_op: + batch_op.create_foreign_key( + "fk_user_address", + "user", + ["user_id"], + ["id"], + ) + + .. seealso:: + + :meth:`.Operations.create_foreign_key` + + """ # noqa: E501 + ... + + def create_index( + self, index_name: str, columns: List[str], **kw: Any + ) -> None: + """Issue a "create index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.create_index` + + """ # noqa: E501 + ... + + def create_primary_key( + self, constraint_name: Optional[str], columns: List[str] + ) -> None: + """Issue a "create primary key" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_primary_key` + + """ # noqa: E501 + ... + + def create_table_comment( + self, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table + using the current batch migration context. + + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + """ # noqa: E501 + ... + + def create_unique_constraint( + self, constraint_name: str, columns: Sequence[str], **kw: Any + ) -> Any: + """Issue a "create unique constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_unique_constraint` + + """ # noqa: E501 + ... + + def drop_column(self, column_name: str, **kw: Any) -> None: + """Issue a "drop column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_column` + + """ # noqa: E501 + ... + + def drop_constraint( + self, constraint_name: str, type_: Optional[str] = None + ) -> None: + """Issue a "drop constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.drop_constraint` + + """ # noqa: E501 + ... + + def drop_index(self, index_name: str, **kw: Any) -> None: + """Issue a "drop index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_index` + + """ # noqa: E501 + ... + + def drop_table_comment( + self, *, existing_comment: Optional[str] = None + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table using the current + batch operations context. + + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + """ # noqa: E501 + ... + + def execute( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current migration context. + + .. seealso:: + + :meth:`.Operations.execute` + + """ # noqa: E501 + ... + + # END STUB FUNCTIONS: batch_op diff --git a/venv/lib/python3.12/site-packages/alembic/operations/batch.py b/venv/lib/python3.12/site-packages/alembic/operations/batch.py new file mode 100644 index 0000000..9b48be5 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/batch.py @@ -0,0 +1,720 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import CheckConstraint +from sqlalchemy import Column +from sqlalchemy import ForeignKeyConstraint +from sqlalchemy import Index +from sqlalchemy import MetaData +from sqlalchemy import PrimaryKeyConstraint +from sqlalchemy import schema as sql_schema +from sqlalchemy import select +from sqlalchemy import Table +from sqlalchemy import types as sqltypes +from sqlalchemy.sql.schema import SchemaEventTarget +from sqlalchemy.util import OrderedDict +from sqlalchemy.util import topological + +from ..util import exc +from ..util.sqla_compat import _columns_for_constraint +from ..util.sqla_compat import _copy +from ..util.sqla_compat import _copy_expression +from ..util.sqla_compat import _ensure_scope_for_ddl +from ..util.sqla_compat import _fk_is_self_referential +from ..util.sqla_compat import _idx_table_bound_expressions +from ..util.sqla_compat import _is_type_bound +from ..util.sqla_compat import _remove_column_from_collection +from ..util.sqla_compat import _resolve_for_variant +from ..util.sqla_compat import constraint_name_defined +from ..util.sqla_compat import constraint_name_string + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.engine import Dialect + from sqlalchemy.sql.elements import ColumnClause + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.type_api import TypeEngine + + from ..ddl.base import _ServerDefaultType + from ..ddl.impl import DefaultImpl + + +class BatchOperationsImpl: + def __init__( + self, + operations, + table_name, + schema, + recreate, + copy_from, + table_args, + table_kwargs, + reflect_args, + reflect_kwargs, + naming_convention, + partial_reordering, + ): + self.operations = operations + self.table_name = table_name + self.schema = schema + if recreate not in ("auto", "always", "never"): + raise ValueError( + "recreate may be one of 'auto', 'always', or 'never'." + ) + self.recreate = recreate + self.copy_from = copy_from + self.table_args = table_args + self.table_kwargs = dict(table_kwargs) + self.reflect_args = reflect_args + self.reflect_kwargs = dict(reflect_kwargs) + self.reflect_kwargs.setdefault( + "listeners", list(self.reflect_kwargs.get("listeners", ())) + ) + self.reflect_kwargs["listeners"].append( + ("column_reflect", operations.impl.autogen_column_reflect) + ) + self.naming_convention = naming_convention + self.partial_reordering = partial_reordering + self.batch = [] + + @property + def dialect(self) -> Dialect: + return self.operations.impl.dialect + + @property + def impl(self) -> DefaultImpl: + return self.operations.impl + + def _should_recreate(self) -> bool: + if self.recreate == "auto": + return self.operations.impl.requires_recreate_in_batch(self) + elif self.recreate == "always": + return True + else: + return False + + def flush(self) -> None: + should_recreate = self._should_recreate() + + with _ensure_scope_for_ddl(self.impl.connection): + if not should_recreate: + for opname, arg, kw in self.batch: + fn = getattr(self.operations.impl, opname) + fn(*arg, **kw) + else: + if self.naming_convention: + m1 = MetaData(naming_convention=self.naming_convention) + else: + m1 = MetaData() + + if self.copy_from is not None: + existing_table = self.copy_from + reflected = False + else: + if self.operations.migration_context.as_sql: + raise exc.CommandError( + f"This operation cannot proceed in --sql mode; " + f"batch mode with dialect " + f"{self.operations.migration_context.dialect.name} " # noqa: E501 + f"requires a live database connection with which " + f'to reflect the table "{self.table_name}". ' + f"To generate a batch SQL migration script using " + "table " + '"move and copy", a complete Table object ' + f'should be passed to the "copy_from" argument ' + "of the batch_alter_table() method so that table " + "reflection can be skipped." + ) + + existing_table = Table( + self.table_name, + m1, + schema=self.schema, + autoload_with=self.operations.get_bind(), + *self.reflect_args, + **self.reflect_kwargs, + ) + reflected = True + + batch_impl = ApplyBatchImpl( + self.impl, + existing_table, + self.table_args, + self.table_kwargs, + reflected, + partial_reordering=self.partial_reordering, + ) + for opname, arg, kw in self.batch: + fn = getattr(batch_impl, opname) + fn(*arg, **kw) + + batch_impl._create(self.impl) + + def alter_column(self, *arg, **kw) -> None: + self.batch.append(("alter_column", arg, kw)) + + def add_column(self, *arg, **kw) -> None: + if ( + "insert_before" in kw or "insert_after" in kw + ) and not self._should_recreate(): + raise exc.CommandError( + "Can't specify insert_before or insert_after when using " + "ALTER; please specify recreate='always'" + ) + self.batch.append(("add_column", arg, kw)) + + def drop_column(self, *arg, **kw) -> None: + self.batch.append(("drop_column", arg, kw)) + + def add_constraint(self, const: Constraint) -> None: + self.batch.append(("add_constraint", (const,), {})) + + def drop_constraint(self, const: Constraint) -> None: + self.batch.append(("drop_constraint", (const,), {})) + + def rename_table(self, *arg, **kw): + self.batch.append(("rename_table", arg, kw)) + + def create_index(self, idx: Index, **kw: Any) -> None: + self.batch.append(("create_index", (idx,), kw)) + + def drop_index(self, idx: Index, **kw: Any) -> None: + self.batch.append(("drop_index", (idx,), kw)) + + def create_table_comment(self, table): + self.batch.append(("create_table_comment", (table,), {})) + + def drop_table_comment(self, table): + self.batch.append(("drop_table_comment", (table,), {})) + + def create_table(self, table): + raise NotImplementedError("Can't create table in batch mode") + + def drop_table(self, table): + raise NotImplementedError("Can't drop table in batch mode") + + def create_column_comment(self, column): + self.batch.append(("create_column_comment", (column,), {})) + + +class ApplyBatchImpl: + def __init__( + self, + impl: DefaultImpl, + table: Table, + table_args: tuple, + table_kwargs: Dict[str, Any], + reflected: bool, + partial_reordering: tuple = (), + ) -> None: + self.impl = impl + self.table = table # this is a Table object + self.table_args = table_args + self.table_kwargs = table_kwargs + self.temp_table_name = self._calc_temp_name(table.name) + self.new_table: Optional[Table] = None + + self.partial_reordering = partial_reordering # tuple of tuples + self.add_col_ordering: Tuple[ + Tuple[str, str], ... + ] = () # tuple of tuples + + self.column_transfers = OrderedDict( + (c.name, {"expr": c}) for c in self.table.c + ) + self.existing_ordering = list(self.column_transfers) + + self.reflected = reflected + self._grab_table_elements() + + @classmethod + def _calc_temp_name(cls, tablename: Union[quoted_name, str]) -> str: + return ("_alembic_tmp_%s" % tablename)[0:50] + + def _grab_table_elements(self) -> None: + schema = self.table.schema + self.columns: Dict[str, Column[Any]] = OrderedDict() + for c in self.table.c: + c_copy = _copy(c, schema=schema) + c_copy.unique = c_copy.index = False + # ensure that the type object was copied, + # as we may need to modify it in-place + if isinstance(c.type, SchemaEventTarget): + assert c_copy.type is not c.type + self.columns[c.name] = c_copy + self.named_constraints: Dict[str, Constraint] = {} + self.unnamed_constraints = [] + self.col_named_constraints = {} + self.indexes: Dict[str, Index] = {} + self.new_indexes: Dict[str, Index] = {} + + for const in self.table.constraints: + if _is_type_bound(const): + continue + elif ( + self.reflected + and isinstance(const, CheckConstraint) + and not const.name + ): + # TODO: we are skipping unnamed reflected CheckConstraint + # because + # we have no way to determine _is_type_bound() for these. + pass + elif constraint_name_string(const.name): + self.named_constraints[const.name] = const + else: + self.unnamed_constraints.append(const) + + if not self.reflected: + for col in self.table.c: + for const in col.constraints: + if const.name: + self.col_named_constraints[const.name] = (col, const) + + for idx in self.table.indexes: + self.indexes[idx.name] = idx # type: ignore[index] + + for k in self.table.kwargs: + self.table_kwargs.setdefault(k, self.table.kwargs[k]) + + def _adjust_self_columns_for_partial_reordering(self) -> None: + pairs = set() + + col_by_idx = list(self.columns) + + if self.partial_reordering: + for tuple_ in self.partial_reordering: + for index, elem in enumerate(tuple_): + if index > 0: + pairs.add((tuple_[index - 1], elem)) + else: + for index, elem in enumerate(self.existing_ordering): + if index > 0: + pairs.add((col_by_idx[index - 1], elem)) + + pairs.update(self.add_col_ordering) + + # this can happen if some columns were dropped and not removed + # from existing_ordering. this should be prevented already, but + # conservatively making sure this didn't happen + pairs_list = [p for p in pairs if p[0] != p[1]] + + sorted_ = list( + topological.sort(pairs_list, col_by_idx, deterministic_order=True) + ) + self.columns = OrderedDict((k, self.columns[k]) for k in sorted_) + self.column_transfers = OrderedDict( + (k, self.column_transfers[k]) for k in sorted_ + ) + + def _transfer_elements_to_new_table(self) -> None: + assert self.new_table is None, "Can only create new table once" + + m = MetaData() + schema = self.table.schema + + if self.partial_reordering or self.add_col_ordering: + self._adjust_self_columns_for_partial_reordering() + + self.new_table = new_table = Table( + self.temp_table_name, + m, + *(list(self.columns.values()) + list(self.table_args)), + schema=schema, + **self.table_kwargs, + ) + + for const in ( + list(self.named_constraints.values()) + self.unnamed_constraints + ): + const_columns = {c.key for c in _columns_for_constraint(const)} + + if not const_columns.issubset(self.column_transfers): + continue + + const_copy: Constraint + if isinstance(const, ForeignKeyConstraint): + if _fk_is_self_referential(const): + # for self-referential constraint, refer to the + # *original* table name, and not _alembic_batch_temp. + # This is consistent with how we're handling + # FK constraints from other tables; we assume SQLite + # no foreign keys just keeps the names unchanged, so + # when we rename back, they match again. + const_copy = _copy( + const, schema=schema, target_table=self.table + ) + else: + # "target_table" for ForeignKeyConstraint.copy() is + # only used if the FK is detected as being + # self-referential, which we are handling above. + const_copy = _copy(const, schema=schema) + else: + const_copy = _copy( + const, schema=schema, target_table=new_table + ) + if isinstance(const, ForeignKeyConstraint): + self._setup_referent(m, const) + new_table.append_constraint(const_copy) + + def _gather_indexes_from_both_tables(self) -> List[Index]: + assert self.new_table is not None + idx: List[Index] = [] + + for idx_existing in self.indexes.values(): + # this is a lift-and-move from Table.to_metadata + + if idx_existing._column_flag: + continue + + idx_copy = Index( + idx_existing.name, + unique=idx_existing.unique, + *[ + _copy_expression(expr, self.new_table) + for expr in _idx_table_bound_expressions(idx_existing) + ], + _table=self.new_table, + **idx_existing.kwargs, + ) + idx.append(idx_copy) + + for index in self.new_indexes.values(): + idx.append( + Index( + index.name, + unique=index.unique, + *[self.new_table.c[col] for col in index.columns.keys()], + **index.kwargs, + ) + ) + return idx + + def _setup_referent( + self, metadata: MetaData, constraint: ForeignKeyConstraint + ) -> None: + spec = constraint.elements[0]._get_colspec() + parts = spec.split(".") + tname = parts[-2] + if len(parts) == 3: + referent_schema = parts[0] + else: + referent_schema = None + + if tname != self.temp_table_name: + key = sql_schema._get_table_key(tname, referent_schema) + + def colspec(elem: Any): + return elem._get_colspec() + + if key in metadata.tables: + t = metadata.tables[key] + for elem in constraint.elements: + colname = colspec(elem).split(".")[-1] + if colname not in t.c: + t.append_column(Column(colname, sqltypes.NULLTYPE)) + else: + Table( + tname, + metadata, + *[ + Column(n, sqltypes.NULLTYPE) + for n in [ + colspec(elem).split(".")[-1] + for elem in constraint.elements + ] + ], + schema=referent_schema, + ) + + def _create(self, op_impl: DefaultImpl) -> None: + self._transfer_elements_to_new_table() + + op_impl.prep_table_for_batch(self, self.table) + assert self.new_table is not None + op_impl.create_table(self.new_table) + + try: + op_impl._exec( + self.new_table.insert() + .inline() + .from_select( + list( + k + for k, transfer in self.column_transfers.items() + if "expr" in transfer + ), + select( + *[ + transfer["expr"] + for transfer in self.column_transfers.values() + if "expr" in transfer + ] + ), + ) + ) + op_impl.drop_table(self.table) + except: + op_impl.drop_table(self.new_table) + raise + else: + op_impl.rename_table( + self.temp_table_name, self.table.name, schema=self.table.schema + ) + self.new_table.name = self.table.name + try: + for idx in self._gather_indexes_from_both_tables(): + op_impl.create_index(idx) + finally: + self.new_table.name = self.temp_table_name + + def alter_column( + self, + table_name: str, + column_name: str, + nullable: Optional[bool] = None, + server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + name: Optional[str] = None, + type_: Optional[TypeEngine] = None, + autoincrement: Optional[Union[bool, Literal["auto"]]] = None, + comment: Union[str, Literal[False]] = False, + **kw, + ) -> None: + existing = self.columns[column_name] + existing_transfer: Dict[str, Any] = self.column_transfers[column_name] + if name is not None and name != column_name: + # note that we don't change '.key' - we keep referring + # to the renamed column by its old key in _create(). neat! + existing.name = name + existing_transfer["name"] = name + + existing_type = kw.get("existing_type", None) + if existing_type: + resolved_existing_type = _resolve_for_variant( + kw["existing_type"], self.impl.dialect + ) + + # pop named constraints for Boolean/Enum for rename + if ( + isinstance(resolved_existing_type, SchemaEventTarget) + and resolved_existing_type.name # type:ignore[attr-defined] # noqa E501 + ): + self.named_constraints.pop( + resolved_existing_type.name, # type:ignore[attr-defined] # noqa E501 + None, + ) + + if type_ is not None: + type_ = sqltypes.to_instance(type_) + # old type is being discarded so turn off eventing + # rules. Alternatively we can + # erase the events set up by this type, but this is simpler. + # we also ignore the drop_constraint that will come here from + # Operations.implementation_for(alter_column) + + if isinstance(existing.type, SchemaEventTarget): + existing.type._create_events = ( # type:ignore[attr-defined] + existing.type.create_constraint # type:ignore[attr-defined] # noqa + ) = False + + self.impl.cast_for_batch_migrate( + existing, existing_transfer, type_ + ) + + existing.type = type_ + + # we *dont* however set events for the new type, because + # alter_column is invoked from + # Operations.implementation_for(alter_column) which already + # will emit an add_constraint() + + if nullable is not None: + existing.nullable = nullable + if server_default is not False: + if server_default is None: + existing.server_default = None + else: + sql_schema.DefaultClause( + server_default # type: ignore[arg-type] + )._set_parent(existing) + if autoincrement is not None: + existing.autoincrement = bool(autoincrement) + + if comment is not False: + existing.comment = comment + + def _setup_dependencies_for_add_column( + self, + colname: str, + insert_before: Optional[str], + insert_after: Optional[str], + ) -> None: + index_cols = self.existing_ordering + col_indexes = {name: i for i, name in enumerate(index_cols)} + + if not self.partial_reordering: + if insert_after: + if not insert_before: + if insert_after in col_indexes: + # insert after an existing column + idx = col_indexes[insert_after] + 1 + if idx < len(index_cols): + insert_before = index_cols[idx] + else: + # insert after a column that is also new + insert_before = dict(self.add_col_ordering)[ + insert_after + ] + if insert_before: + if not insert_after: + if insert_before in col_indexes: + # insert before an existing column + idx = col_indexes[insert_before] - 1 + if idx >= 0: + insert_after = index_cols[idx] + else: + # insert before a column that is also new + insert_after = { + b: a for a, b in self.add_col_ordering + }[insert_before] + + if insert_before: + self.add_col_ordering += ((colname, insert_before),) + if insert_after: + self.add_col_ordering += ((insert_after, colname),) + + if ( + not self.partial_reordering + and not insert_before + and not insert_after + and col_indexes + ): + self.add_col_ordering += ((index_cols[-1], colname),) + + def add_column( + self, + table_name: str, + column: Column[Any], + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw, + ) -> None: + self._setup_dependencies_for_add_column( + column.name, insert_before, insert_after + ) + # we copy the column because operations.add_column() + # gives us a Column that is part of a Table already. + self.columns[column.name] = _copy(column, schema=self.table.schema) + self.column_transfers[column.name] = {} + + def drop_column( + self, + table_name: str, + column: Union[ColumnClause[Any], Column[Any]], + **kw, + ) -> None: + if column.name in self.table.primary_key.columns: + _remove_column_from_collection( + self.table.primary_key.columns, column + ) + del self.columns[column.name] + del self.column_transfers[column.name] + self.existing_ordering.remove(column.name) + + # pop named constraints for Boolean/Enum for rename + if ( + "existing_type" in kw + and isinstance(kw["existing_type"], SchemaEventTarget) + and kw["existing_type"].name # type:ignore[attr-defined] + ): + self.named_constraints.pop( + kw["existing_type"].name, None # type:ignore[attr-defined] + ) + + def create_column_comment(self, column): + """the batch table creation function will issue create_column_comment + on the real "impl" as part of the create table process. + + That is, the Column object will have the comment on it already, + so when it is received by add_column() it will be a normal part of + the CREATE TABLE and doesn't need an extra step here. + + """ + + def create_table_comment(self, table): + """the batch table creation function will issue create_table_comment + on the real "impl" as part of the create table process. + + """ + + def drop_table_comment(self, table): + """the batch table creation function will issue drop_table_comment + on the real "impl" as part of the create table process. + + """ + + def add_constraint(self, const: Constraint) -> None: + if not constraint_name_defined(const.name): + raise ValueError("Constraint must have a name") + if isinstance(const, sql_schema.PrimaryKeyConstraint): + if self.table.primary_key in self.unnamed_constraints: + self.unnamed_constraints.remove(self.table.primary_key) + + if constraint_name_string(const.name): + self.named_constraints[const.name] = const + else: + self.unnamed_constraints.append(const) + + def drop_constraint(self, const: Constraint) -> None: + if not const.name: + raise ValueError("Constraint must have a name") + try: + if const.name in self.col_named_constraints: + col, const = self.col_named_constraints.pop(const.name) + + for col_const in list(self.columns[col.name].constraints): + if col_const.name == const.name: + self.columns[col.name].constraints.remove(col_const) + elif constraint_name_string(const.name): + const = self.named_constraints.pop(const.name) + elif const in self.unnamed_constraints: + self.unnamed_constraints.remove(const) + + except KeyError: + if _is_type_bound(const): + # type-bound constraints are only included in the new + # table via their type object in any case, so ignore the + # drop_constraint() that comes here via the + # Operations.implementation_for(alter_column) + return + raise ValueError("No such constraint: '%s'" % const.name) + else: + if isinstance(const, PrimaryKeyConstraint): + for col in const.columns: + self.columns[col.name].primary_key = False + + def create_index(self, idx: Index) -> None: + self.new_indexes[idx.name] = idx # type: ignore[index] + + def drop_index(self, idx: Index) -> None: + try: + del self.indexes[idx.name] # type: ignore[arg-type] + except KeyError: + raise ValueError("No such index: '%s'" % idx.name) + + def rename_table(self, *arg, **kw): + raise NotImplementedError("TODO") diff --git a/venv/lib/python3.12/site-packages/alembic/operations/ops.py b/venv/lib/python3.12/site-packages/alembic/operations/ops.py new file mode 100644 index 0000000..7eda50d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/ops.py @@ -0,0 +1,2918 @@ +from __future__ import annotations + +from abc import abstractmethod +import os +import pathlib +import re +from typing import Any +from typing import Callable +from typing import cast +from typing import Dict +from typing import FrozenSet +from typing import Iterator +from typing import List +from typing import MutableMapping +from typing import Optional +from typing import Sequence +from typing import Set +from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import TypeVar +from typing import Union + +from sqlalchemy.types import NULLTYPE + +from . import schemaobj +from .base import BatchOperations +from .base import Operations +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from typing import Literal + + from sqlalchemy.sql import Executable + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import conv + from sqlalchemy.sql.elements import quoted_name + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import Column + from sqlalchemy.sql.schema import Constraint + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import Index + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.selectable import TableClause + from sqlalchemy.sql.type_api import TypeEngine + + from ..autogenerate.rewriter import Rewriter + from ..ddl.base import _ServerDefaultType + from ..runtime.migration import MigrationContext + from ..script.revision import _RevIdType + +_T = TypeVar("_T", bound=Any) +_AC = TypeVar("_AC", bound="AddConstraintOp") + + +class MigrateOperation: + """base class for migration command and organization objects. + + This system is part of the operation extensibility API. + + .. seealso:: + + :ref:`operation_objects` + + :ref:`operation_plugins` + + :ref:`customizing_revision` + + """ + + @util.memoized_property + def info(self) -> Dict[Any, Any]: + """A dictionary that may be used to store arbitrary information + along with this :class:`.MigrateOperation` object. + + """ + return {} + + _mutations: FrozenSet[Rewriter] = frozenset() + + def reverse(self) -> MigrateOperation: + raise NotImplementedError + + def to_diff_tuple(self) -> Tuple[Any, ...]: + raise NotImplementedError + + +class AddConstraintOp(MigrateOperation): + """Represent an add constraint operation.""" + + add_constraint_ops = util.Dispatcher() + + @property + def constraint_type(self) -> str: + raise NotImplementedError() + + @classmethod + def register_add_constraint( + cls, type_: str + ) -> Callable[[Type[_AC]], Type[_AC]]: + def go(klass: Type[_AC]) -> Type[_AC]: + cls.add_constraint_ops.dispatch_for(type_)(klass.from_constraint) + return klass + + return go + + @classmethod + def from_constraint(cls, constraint: Constraint) -> AddConstraintOp: + return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501 + constraint + ) + + @abstractmethod + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> Constraint: + pass + + def reverse(self) -> DropConstraintOp: + return DropConstraintOp.from_constraint(self.to_constraint()) + + def to_diff_tuple(self) -> Tuple[str, Constraint]: + return ("add_constraint", self.to_constraint()) + + +@Operations.register_operation("drop_constraint") +@BatchOperations.register_operation("drop_constraint", "batch_drop_constraint") +class DropConstraintOp(MigrateOperation): + """Represent a drop constraint operation.""" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + _reverse: Optional[AddConstraintOp] = None, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.constraint_type = type_ + self.schema = schema + self.if_exists = if_exists + self._reverse = _reverse + + def reverse(self) -> AddConstraintOp: + return AddConstraintOp.from_constraint(self.to_constraint()) + + def to_diff_tuple( + self, + ) -> Tuple[str, SchemaItem]: + if self.constraint_type == "foreignkey": + return ("remove_fk", self.to_constraint()) + else: + return ("remove_constraint", self.to_constraint()) + + @classmethod + def from_constraint(cls, constraint: Constraint) -> DropConstraintOp: + types = { + "unique_constraint": "unique", + "foreign_key_constraint": "foreignkey", + "primary_key_constraint": "primary", + "check_constraint": "check", + "column_check_constraint": "check", + "table_or_column_check_constraint": "check", + } + + constraint_table = sqla_compat._table_for_constraint(constraint) + return cls( + sqla_compat.constraint_name_or_none(constraint.name), + constraint_table.name, + schema=constraint_table.schema, + type_=types.get(constraint.__visit_name__), + _reverse=AddConstraintOp.from_constraint(constraint), + ) + + def to_constraint(self) -> Constraint: + if self._reverse is not None: + constraint = self._reverse.to_constraint() + constraint.name = self.constraint_name + constraint_table = sqla_compat._table_for_constraint(constraint) + constraint_table.name = self.table_name + constraint_table.schema = self.schema + + return constraint + else: + raise ValueError( + "constraint cannot be produced; " + "original constraint is not present" + ) + + @classmethod + def drop_constraint( + cls, + operations: Operations, + constraint_name: str, + table_name: str, + type_: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + ) -> None: + r"""Drop a constraint of the given name, typically via DROP CONSTRAINT. + + :param constraint_name: name of the constraint. + :param table_name: table name. + :param type\_: optional, required on MySQL. can be + 'foreignkey', 'primary', 'unique', or 'check'. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the constraint + + .. versionadded:: 1.16.0 + + """ + + op = cls( + constraint_name, + table_name, + type_=type_, + schema=schema, + if_exists=if_exists, + ) + return operations.invoke(op) + + @classmethod + def batch_drop_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + type_: Optional[str] = None, + ) -> None: + """Issue a "drop constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.drop_constraint` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + type_=type_, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_primary_key") +@BatchOperations.register_operation( + "create_primary_key", "batch_create_primary_key" +) +@AddConstraintOp.register_add_constraint("primary_key_constraint") +class CreatePrimaryKeyOp(AddConstraintOp): + """Represent a create primary key operation.""" + + constraint_type = "primarykey" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint(cls, constraint: Constraint) -> CreatePrimaryKeyOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + pk_constraint = cast("PrimaryKeyConstraint", constraint) + return cls( + sqla_compat.constraint_name_or_none(pk_constraint.name), + constraint_table.name, + pk_constraint.columns.keys(), + schema=constraint_table.schema, + **pk_constraint.dialect_kwargs, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> PrimaryKeyConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.primary_key_constraint( + self.constraint_name, + self.table_name, + self.columns, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_primary_key( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + columns: List[str], + *, + schema: Optional[str] = None, + ) -> None: + """Issue a "create primary key" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_primary_key("pk_my_table", "my_table", ["id", "version"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.PrimaryKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the primary key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions` + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the target table. + :param columns: a list of string column names to be applied to the + primary key constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(constraint_name, table_name, columns, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_create_primary_key( + cls, + operations: BatchOperations, + constraint_name: Optional[str], + columns: List[str], + ) -> None: + """Issue a "create primary key" instruction using the + current batch migration context. + + The batch form of this call omits the ``table_name`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_primary_key` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + columns, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_unique_constraint") +@BatchOperations.register_operation( + "create_unique_constraint", "batch_create_unique_constraint" +) +@AddConstraintOp.register_add_constraint("unique_constraint") +class CreateUniqueConstraintOp(AddConstraintOp): + """Represent a create unique constraint operation.""" + + constraint_type = "unique" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint( + cls, constraint: Constraint + ) -> CreateUniqueConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + + uq_constraint = cast("UniqueConstraint", constraint) + + kw: Dict[str, Any] = {} + if uq_constraint.deferrable: + kw["deferrable"] = uq_constraint.deferrable + if uq_constraint.initially: + kw["initially"] = uq_constraint.initially + kw.update(uq_constraint.dialect_kwargs) + return cls( + sqla_compat.constraint_name_or_none(uq_constraint.name), + constraint_table.name, + [c.name for c in uq_constraint.columns], + schema=constraint_table.schema, + **kw, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> UniqueConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.unique_constraint( + self.constraint_name, + self.table_name, + self.columns, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_unique_constraint( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + columns: Sequence[str], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + op.create_unique_constraint("uq_user_name", "user", ["name"]) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.UniqueConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param name: Name of the unique constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param columns: a list of string column names in the + source table. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + + op = cls(constraint_name, table_name, columns, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_unique_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + columns: Sequence[str], + **kw: Any, + ) -> Any: + """Issue a "create unique constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_unique_constraint` + + """ + kw["schema"] = operations.impl.schema + op = cls(constraint_name, operations.impl.table_name, columns, **kw) + return operations.invoke(op) + + +@Operations.register_operation("create_foreign_key") +@BatchOperations.register_operation( + "create_foreign_key", "batch_create_foreign_key" +) +@AddConstraintOp.register_add_constraint("foreign_key_constraint") +class CreateForeignKeyOp(AddConstraintOp): + """Represent a create foreign key constraint operation.""" + + constraint_type = "foreignkey" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.source_table = source_table + self.referent_table = referent_table + self.local_cols = local_cols + self.remote_cols = remote_cols + self.kw = kw + + def to_diff_tuple(self) -> Tuple[str, ForeignKeyConstraint]: + return ("add_fk", self.to_constraint()) + + @classmethod + def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp: + fk_constraint = cast("ForeignKeyConstraint", constraint) + kw: Dict[str, Any] = {} + if fk_constraint.onupdate: + kw["onupdate"] = fk_constraint.onupdate + if fk_constraint.ondelete: + kw["ondelete"] = fk_constraint.ondelete + if fk_constraint.initially: + kw["initially"] = fk_constraint.initially + if fk_constraint.deferrable: + kw["deferrable"] = fk_constraint.deferrable + if fk_constraint.use_alter: + kw["use_alter"] = fk_constraint.use_alter + if fk_constraint.match: + kw["match"] = fk_constraint.match + + ( + source_schema, + source_table, + source_columns, + target_schema, + target_table, + target_columns, + onupdate, + ondelete, + deferrable, + initially, + ) = sqla_compat._fk_spec(fk_constraint) + + kw["source_schema"] = source_schema + kw["referent_schema"] = target_schema + kw.update(fk_constraint.dialect_kwargs) + return cls( + sqla_compat.constraint_name_or_none(fk_constraint.name), + source_table, + target_table, + source_columns, + target_columns, + **kw, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> ForeignKeyConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.foreign_key_constraint( + self.constraint_name, + self.source_table, + self.referent_table, + self.local_cols, + self.remote_cols, + **self.kw, + ) + + @classmethod + def create_foreign_key( + cls, + operations: Operations, + constraint_name: Optional[str], + source_table: str, + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current migration context. + + e.g.:: + + from alembic import op + + op.create_foreign_key( + "fk_user_address", + "address", + "user", + ["user_id"], + ["id"], + ) + + This internally generates a :class:`~sqlalchemy.schema.Table` object + containing the necessary columns, then generates a new + :class:`~sqlalchemy.schema.ForeignKeyConstraint` + object which it then associates with the + :class:`~sqlalchemy.schema.Table`. + Any event listeners associated with this action will be fired + off normally. The :class:`~sqlalchemy.schema.AddConstraint` + construct is ultimately used to generate the ALTER statement. + + :param constraint_name: Name of the foreign key constraint. The name + is necessary so that an ALTER statement can be emitted. For setups + that use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param source_table: String name of the source table. + :param referent_table: String name of the destination table. + :param local_cols: a list of string column names in the + source table. + :param remote_cols: a list of string column names in the + remote table. + :param onupdate: Optional string. If set, emit ON UPDATE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param ondelete: Optional string. If set, emit ON DELETE when + issuing DDL for this constraint. Typical values include CASCADE, + DELETE and RESTRICT. + :param deferrable: optional bool. If set, emit DEFERRABLE or NOT + DEFERRABLE when issuing DDL for this constraint. + :param source_schema: Optional schema name of the source table. + :param referent_schema: Optional schema name of the destination table. + + """ + + op = cls( + constraint_name, + source_table, + referent_table, + local_cols, + remote_cols, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + source_schema=source_schema, + referent_schema=referent_schema, + initially=initially, + match=match, + **dialect_kw, + ) + return operations.invoke(op) + + @classmethod + def batch_create_foreign_key( + cls, + operations: BatchOperations, + constraint_name: Optional[str], + referent_table: str, + local_cols: List[str], + remote_cols: List[str], + *, + referent_schema: Optional[str] = None, + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw: Any, + ) -> None: + """Issue a "create foreign key" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``source_schema`` + arguments from the call. + + e.g.:: + + with batch_alter_table("address") as batch_op: + batch_op.create_foreign_key( + "fk_user_address", + "user", + ["user_id"], + ["id"], + ) + + .. seealso:: + + :meth:`.Operations.create_foreign_key` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + referent_table, + local_cols, + remote_cols, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + source_schema=operations.impl.schema, + referent_schema=referent_schema, + initially=initially, + match=match, + **dialect_kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_check_constraint") +@BatchOperations.register_operation( + "create_check_constraint", "batch_create_check_constraint" +) +@AddConstraintOp.register_add_constraint("check_constraint") +@AddConstraintOp.register_add_constraint("table_or_column_check_constraint") +@AddConstraintOp.register_add_constraint("column_check_constraint") +class CreateCheckConstraintOp(AddConstraintOp): + """Represent a create check constraint operation.""" + + constraint_type = "check" + + def __init__( + self, + constraint_name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + condition: Union[str, TextClause, ColumnElement[Any]], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + self.constraint_name = constraint_name + self.table_name = table_name + self.condition = condition + self.schema = schema + self.kw = kw + + @classmethod + def from_constraint( + cls, constraint: Constraint + ) -> CreateCheckConstraintOp: + constraint_table = sqla_compat._table_for_constraint(constraint) + + ck_constraint = cast("CheckConstraint", constraint) + return cls( + sqla_compat.constraint_name_or_none(ck_constraint.name), + constraint_table.name, + cast("ColumnElement[Any]", ck_constraint.sqltext), + schema=constraint_table.schema, + **ck_constraint.dialect_kwargs, + ) + + def to_constraint( + self, migration_context: Optional[MigrationContext] = None + ) -> CheckConstraint: + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.check_constraint( + self.constraint_name, + self.table_name, + self.condition, + schema=self.schema, + **self.kw, + ) + + @classmethod + def create_check_constraint( + cls, + operations: Operations, + constraint_name: Optional[str], + table_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current migration context. + + e.g.:: + + from alembic import op + from sqlalchemy.sql import column, func + + op.create_check_constraint( + "ck_user_name_len", + "user", + func.len(column("name")) > 5, + ) + + CHECK constraints are usually against a SQL expression, so ad-hoc + table metadata is usually needed. The function will convert the given + arguments into a :class:`sqlalchemy.schema.CheckConstraint` bound + to an anonymous table in order to emit the CREATE statement. + + :param name: Name of the check constraint. The name is necessary + so that an ALTER statement can be emitted. For setups that + use an automated naming scheme such as that described at + :ref:`sqla:constraint_naming_conventions`, + ``name`` here can be ``None``, as the event listener will + apply the name to the constraint object when it is associated + with the table. + :param table_name: String name of the source table. + :param condition: SQL expression that's the condition of the + constraint. Can be a string or SQLAlchemy expression language + structure. + :param deferrable: optional bool. If set, emit DEFERRABLE or + NOT DEFERRABLE when issuing DDL for this constraint. + :param initially: optional string. If set, emit INITIALLY + when issuing DDL for this constraint. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(constraint_name, table_name, condition, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_create_check_constraint( + cls, + operations: BatchOperations, + constraint_name: str, + condition: Union[str, ColumnElement[bool], TextClause], + **kw: Any, + ) -> None: + """Issue a "create check constraint" instruction using the + current batch migration context. + + The batch form of this call omits the ``source`` and ``schema`` + arguments from the call. + + .. seealso:: + + :meth:`.Operations.create_check_constraint` + + """ + op = cls( + constraint_name, + operations.impl.table_name, + condition, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_index") +@BatchOperations.register_operation("create_index", "batch_create_index") +class CreateIndexOp(MigrateOperation): + """Represent a create index operation.""" + + def __init__( + self, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + self.index_name = index_name + self.table_name = table_name + self.columns = columns + self.schema = schema + self.unique = unique + self.if_not_exists = if_not_exists + self.kw = kw + + def reverse(self) -> DropIndexOp: + return DropIndexOp.from_index(self.to_index()) + + def to_diff_tuple(self) -> Tuple[str, Index]: + return ("add_index", self.to_index()) + + @classmethod + def from_index(cls, index: Index) -> CreateIndexOp: + assert index.table is not None + return cls( + index.name, + index.table.name, + index.expressions, + schema=index.table.schema, + unique=index.unique, + **index.kwargs, + ) + + def to_index( + self, migration_context: Optional[MigrationContext] = None + ) -> Index: + schema_obj = schemaobj.SchemaObjects(migration_context) + + idx = schema_obj.index( + self.index_name, + self.table_name, + self.columns, + schema=self.schema, + unique=self.unique, + **self.kw, + ) + return idx + + @classmethod + def create_index( + cls, + operations: Operations, + index_name: Optional[str], + table_name: str, + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + *, + schema: Optional[str] = None, + unique: bool = False, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "create index" instruction using the current + migration context. + + e.g.:: + + from alembic import op + + op.create_index("ik_test", "t1", ["foo", "bar"]) + + Functional indexes can be produced by using the + :func:`sqlalchemy.sql.expression.text` construct:: + + from alembic import op + from sqlalchemy import text + + op.create_index("ik_test", "t1", [text("lower(foo)")]) + + :param index_name: name of the index. + :param table_name: name of the owning table. + :param columns: a list consisting of string column names and/or + :func:`~sqlalchemy.sql.expression.text` constructs. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param unique: If True, create a unique index. + + :param quote: Force quoting of this column's name on or off, + corresponding to ``True`` or ``False``. When left at its default + of ``None``, the column identifier will be quoted according to + whether the name is case sensitive (identifiers with at least one + upper case character are treated as case sensitive), or if it's a + reserved word. This flag is only needed to force quoting of a + reserved word which is not known by the SQLAlchemy dialect. + + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + op = cls( + index_name, + table_name, + columns, + schema=schema, + unique=unique, + if_not_exists=if_not_exists, + **kw, + ) + return operations.invoke(op) + + @classmethod + def batch_create_index( + cls, + operations: BatchOperations, + index_name: str, + columns: List[str], + **kw: Any, + ) -> None: + """Issue a "create index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.create_index` + + """ + + op = cls( + index_name, + operations.impl.table_name, + columns, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("drop_index") +@BatchOperations.register_operation("drop_index", "batch_drop_index") +class DropIndexOp(MigrateOperation): + """Represent a drop index operation.""" + + def __init__( + self, + index_name: Union[quoted_name, str, conv], + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + _reverse: Optional[CreateIndexOp] = None, + **kw: Any, + ) -> None: + self.index_name = index_name + self.table_name = table_name + self.schema = schema + self.if_exists = if_exists + self._reverse = _reverse + self.kw = kw + + def to_diff_tuple(self) -> Tuple[str, Index]: + return ("remove_index", self.to_index()) + + def reverse(self) -> CreateIndexOp: + return CreateIndexOp.from_index(self.to_index()) + + @classmethod + def from_index(cls, index: Index) -> DropIndexOp: + assert index.table is not None + return cls( + index.name, # type: ignore[arg-type] + table_name=index.table.name, + schema=index.table.schema, + _reverse=CreateIndexOp.from_index(index), + unique=index.unique, + **index.kwargs, + ) + + def to_index( + self, migration_context: Optional[MigrationContext] = None + ) -> Index: + schema_obj = schemaobj.SchemaObjects(migration_context) + + # need a dummy column name here since SQLAlchemy + # 0.7.6 and further raises on Index with no columns + return schema_obj.index( + self.index_name, + self.table_name, + self._reverse.columns if self._reverse else ["x"], + schema=self.schema, + **self.kw, + ) + + @classmethod + def drop_index( + cls, + operations: Operations, + index_name: str, + table_name: Optional[str] = None, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop index" instruction using the current + migration context. + + e.g.:: + + drop_index("accounts") + + :param index_name: name of the index. + :param table_name: name of the owning table. Some + backends such as Microsoft SQL Server require this. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + :param if_exists: If True, adds IF EXISTS operator when + dropping the index. + + .. versionadded:: 1.12.0 + + :param \**kw: Additional keyword arguments not mentioned above are + dialect specific, and passed in the form + ``_``. + See the documentation regarding an individual dialect at + :ref:`dialect_toplevel` for detail on documented arguments. + + """ + op = cls( + index_name, + table_name=table_name, + schema=schema, + if_exists=if_exists, + **kw, + ) + return operations.invoke(op) + + @classmethod + def batch_drop_index( + cls, operations: BatchOperations, index_name: str, **kw: Any + ) -> None: + """Issue a "drop index" instruction using the + current batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_index` + + """ + + op = cls( + index_name, + table_name=operations.impl.table_name, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("create_table") +class CreateTableOp(MigrateOperation): + """Represent a create table operation.""" + + def __init__( + self, + table_name: str, + columns: Sequence[SchemaItem], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + _namespace_metadata: Optional[MetaData] = None, + _constraints_included: bool = False, + **kw: Any, + ) -> None: + self.table_name = table_name + self.columns = columns + self.schema = schema + self.if_not_exists = if_not_exists + self.info = kw.pop("info", {}) + self.comment = kw.pop("comment", None) + self.prefixes = kw.pop("prefixes", None) + self.kw = kw + self._namespace_metadata = _namespace_metadata + self._constraints_included = _constraints_included + + def reverse(self) -> DropTableOp: + return DropTableOp.from_table( + self.to_table(), _namespace_metadata=self._namespace_metadata + ) + + def to_diff_tuple(self) -> Tuple[str, Table]: + return ("add_table", self.to_table()) + + @classmethod + def from_table( + cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None + ) -> CreateTableOp: + if _namespace_metadata is None: + _namespace_metadata = table.metadata + + return cls( + table.name, + list(table.c) + list(table.constraints), + schema=table.schema, + _namespace_metadata=_namespace_metadata, + # given a Table() object, this Table will contain full Index() + # and UniqueConstraint objects already constructed in response to + # each unique=True / index=True flag on a Column. Carry this + # state along so that when we re-convert back into a Table, we + # skip unique=True/index=True so that these constraints are + # not doubled up. see #844 #848 + _constraints_included=True, + comment=table.comment, + info=dict(table.info), + prefixes=list(table._prefixes), + **table.kwargs, + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table( + self.table_name, + *self.columns, + schema=self.schema, + prefixes=list(self.prefixes) if self.prefixes else [], + comment=self.comment, + info=self.info.copy() if self.info else {}, + _constraints_included=self._constraints_included, + **self.kw, + ) + + @classmethod + def create_table( + cls, + operations: Operations, + table_name: str, + *columns: SchemaItem, + if_not_exists: Optional[bool] = None, + **kw: Any, + ) -> Table: + r"""Issue a "create table" instruction using the current migration + context. + + This directive receives an argument list similar to that of the + traditional :class:`sqlalchemy.schema.Table` construct, but without the + metadata:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + Note that :meth:`.create_table` accepts + :class:`~sqlalchemy.schema.Column` + constructs directly from the SQLAlchemy library. In particular, + default values to be created on the database side are + specified using the ``server_default`` parameter, and not + ``default`` which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the "timestamp" column + op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + The function also returns a newly created + :class:`~sqlalchemy.schema.Table` object, corresponding to the table + specification given, which is suitable for + immediate SQL operations, in particular + :meth:`.Operations.bulk_insert`:: + + from sqlalchemy import INTEGER, VARCHAR, NVARCHAR, Column + from alembic import op + + account_table = op.create_table( + "account", + Column("id", INTEGER, primary_key=True), + Column("name", VARCHAR(50), nullable=False), + Column("description", NVARCHAR(200)), + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + op.bulk_insert( + account_table, + [ + {"name": "A1", "description": "account 1"}, + {"name": "A2", "description": "account 2"}, + ], + ) + + :param table_name: Name of the table + :param \*columns: collection of :class:`~sqlalchemy.schema.Column` + objects within + the table, as well as optional :class:`~sqlalchemy.schema.Constraint` + objects + and :class:`~.sqlalchemy.schema.Index` objects. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds IF NOT EXISTS operator when + creating the new table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + :return: the :class:`~sqlalchemy.schema.Table` object corresponding + to the parameters given. + + """ + op = cls(table_name, columns, if_not_exists=if_not_exists, **kw) + return operations.invoke(op) + + +@Operations.register_operation("drop_table") +class DropTableOp(MigrateOperation): + """Represent a drop table operation.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + table_kw: Optional[MutableMapping[Any, Any]] = None, + _reverse: Optional[CreateTableOp] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + self.if_exists = if_exists + self.table_kw = table_kw or {} + self.comment = self.table_kw.pop("comment", None) + self.info = self.table_kw.pop("info", None) + self.prefixes = self.table_kw.pop("prefixes", None) + self._reverse = _reverse + + def to_diff_tuple(self) -> Tuple[str, Table]: + return ("remove_table", self.to_table()) + + def reverse(self) -> CreateTableOp: + return CreateTableOp.from_table(self.to_table()) + + @classmethod + def from_table( + cls, table: Table, *, _namespace_metadata: Optional[MetaData] = None + ) -> DropTableOp: + return cls( + table.name, + schema=table.schema, + table_kw={ + "comment": table.comment, + "info": dict(table.info), + "prefixes": list(table._prefixes), + **table.kwargs, + }, + _reverse=CreateTableOp.from_table( + table, _namespace_metadata=_namespace_metadata + ), + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + if self._reverse: + cols_and_constraints = self._reverse.columns + else: + cols_and_constraints = [] + + schema_obj = schemaobj.SchemaObjects(migration_context) + t = schema_obj.table( + self.table_name, + *cols_and_constraints, + comment=self.comment, + info=self.info.copy() if self.info else {}, + prefixes=list(self.prefixes) if self.prefixes else [], + schema=self.schema, + _constraints_included=( + self._reverse._constraints_included if self._reverse else False + ), + **self.table_kw, + ) + return t + + @classmethod + def drop_table( + cls, + operations: Operations, + table_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + **kw: Any, + ) -> None: + r"""Issue a "drop table" instruction using the current + migration context. + + + e.g.:: + + drop_table("accounts") + + :param table_name: Name of the table + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the table. + + .. versionadded:: 1.13.3 + :param \**kw: Other keyword arguments are passed to the underlying + :class:`sqlalchemy.schema.Table` object created for the command. + + """ + op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw) + operations.invoke(op) + + +class AlterTableOp(MigrateOperation): + """Represent an alter table operation.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.schema = schema + + +@Operations.register_operation("rename_table") +class RenameTableOp(AlterTableOp): + """Represent a rename table operation.""" + + def __init__( + self, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + super().__init__(old_table_name, schema=schema) + self.new_table_name = new_table_name + + @classmethod + def rename_table( + cls, + operations: Operations, + old_table_name: str, + new_table_name: str, + *, + schema: Optional[str] = None, + ) -> None: + """Emit an ALTER TABLE to rename a table. + + :param old_table_name: old name. + :param new_table_name: new name. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + + """ + op = cls(old_table_name, new_table_name, schema=schema) + return operations.invoke(op) + + +@Operations.register_operation("create_table_comment") +@BatchOperations.register_operation( + "create_table_comment", "batch_create_table_comment" +) +class CreateTableCommentOp(AlterTableOp): + """Represent a COMMENT ON `table` operation.""" + + def __init__( + self, + table_name: str, + comment: Optional[str], + *, + schema: Optional[str] = None, + existing_comment: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.comment = comment + self.existing_comment = existing_comment + self.schema = schema + + @classmethod + def create_table_comment( + cls, + operations: Operations, + table_name: str, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table. + + :param table_name: string name of the target table. + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + .. seealso:: + + :meth:`.Operations.drop_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + + op = cls( + table_name, + comment, + existing_comment=existing_comment, + schema=schema, + ) + return operations.invoke(op) + + @classmethod + def batch_create_table_comment( + cls, + operations: BatchOperations, + comment: Optional[str], + *, + existing_comment: Optional[str] = None, + ) -> None: + """Emit a COMMENT ON operation to set the comment for a table + using the current batch migration context. + + :param comment: string value of the comment being registered against + the specified table. + :param existing_comment: String value of a comment + already registered on the specified table, used within autogenerate + so that the operation is reversible, but not required for direct + use. + + """ + + op = cls( + operations.impl.table_name, + comment, + existing_comment=existing_comment, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]: + """Reverses the COMMENT ON operation against a table.""" + if self.existing_comment is None: + return DropTableCommentOp( + self.table_name, + existing_comment=self.comment, + schema=self.schema, + ) + else: + return CreateTableCommentOp( + self.table_name, + self.existing_comment, + existing_comment=self.comment, + schema=self.schema, + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table( + self.table_name, schema=self.schema, comment=self.comment + ) + + def to_diff_tuple(self) -> Tuple[Any, ...]: + return ("add_table_comment", self.to_table(), self.existing_comment) + + +@Operations.register_operation("drop_table_comment") +@BatchOperations.register_operation( + "drop_table_comment", "batch_drop_table_comment" +) +class DropTableCommentOp(AlterTableOp): + """Represent an operation to remove the comment from a table.""" + + def __init__( + self, + table_name: str, + *, + schema: Optional[str] = None, + existing_comment: Optional[str] = None, + ) -> None: + self.table_name = table_name + self.existing_comment = existing_comment + self.schema = schema + + @classmethod + def drop_table_comment( + cls, + operations: Operations, + table_name: str, + *, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table. + + :param table_name: string name of the target table. + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + .. seealso:: + + :meth:`.Operations.create_table_comment` + + :paramref:`.Operations.alter_column.comment` + + """ + + op = cls(table_name, existing_comment=existing_comment, schema=schema) + return operations.invoke(op) + + @classmethod + def batch_drop_table_comment( + cls, + operations: BatchOperations, + *, + existing_comment: Optional[str] = None, + ) -> None: + """Issue a "drop table comment" operation to + remove an existing comment set on a table using the current + batch operations context. + + :param existing_comment: An optional string value of a comment already + registered on the specified table. + + """ + + op = cls( + operations.impl.table_name, + existing_comment=existing_comment, + schema=operations.impl.schema, + ) + return operations.invoke(op) + + def reverse(self) -> CreateTableCommentOp: + """Reverses the COMMENT ON operation against a table.""" + return CreateTableCommentOp( + self.table_name, self.existing_comment, schema=self.schema + ) + + def to_table( + self, migration_context: Optional[MigrationContext] = None + ) -> Table: + schema_obj = schemaobj.SchemaObjects(migration_context) + + return schema_obj.table(self.table_name, schema=self.schema) + + def to_diff_tuple(self) -> Tuple[Any, ...]: + return ("remove_table_comment", self.to_table()) + + +@Operations.register_operation("alter_column") +@BatchOperations.register_operation("alter_column", "batch_alter_column") +class AlterColumnOp(AlterTableOp): + """Represent an alter column operation.""" + + def __init__( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + existing_type: Optional[Any] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + modify_nullable: Optional[bool] = None, + modify_comment: Optional[Union[str, Literal[False]]] = False, + modify_server_default: Any = False, + modify_name: Optional[str] = None, + modify_type: Optional[Any] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column_name = column_name + self.existing_type = existing_type + self.existing_server_default = existing_server_default + self.existing_nullable = existing_nullable + self.existing_comment = existing_comment + self.modify_nullable = modify_nullable + self.modify_comment = modify_comment + self.modify_server_default = modify_server_default + self.modify_name = modify_name + self.modify_type = modify_type + self.kw = kw + + def to_diff_tuple(self) -> Any: + col_diff = [] + schema, tname, cname = self.schema, self.table_name, self.column_name + + if self.modify_type is not None: + col_diff.append( + ( + "modify_type", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_server_default": ( + self.existing_server_default + ), + "existing_comment": self.existing_comment, + }, + self.existing_type, + self.modify_type, + ) + ) + + if self.modify_nullable is not None: + col_diff.append( + ( + "modify_nullable", + schema, + tname, + cname, + { + "existing_type": self.existing_type, + "existing_server_default": ( + self.existing_server_default + ), + "existing_comment": self.existing_comment, + }, + self.existing_nullable, + self.modify_nullable, + ) + ) + + if self.modify_server_default is not False: + col_diff.append( + ( + "modify_default", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_type": self.existing_type, + "existing_comment": self.existing_comment, + }, + self.existing_server_default, + self.modify_server_default, + ) + ) + + if self.modify_comment is not False: + col_diff.append( + ( + "modify_comment", + schema, + tname, + cname, + { + "existing_nullable": self.existing_nullable, + "existing_type": self.existing_type, + "existing_server_default": ( + self.existing_server_default + ), + }, + self.existing_comment, + self.modify_comment, + ) + ) + + return col_diff + + def has_changes(self) -> bool: + hc1 = ( + self.modify_nullable is not None + or self.modify_server_default is not False + or self.modify_type is not None + or self.modify_comment is not False + ) + if hc1: + return True + for kw in self.kw: + if kw.startswith("modify_"): + return True + else: + return False + + def reverse(self) -> AlterColumnOp: + kw = self.kw.copy() + kw["existing_type"] = self.existing_type + kw["existing_nullable"] = self.existing_nullable + kw["existing_server_default"] = self.existing_server_default + kw["existing_comment"] = self.existing_comment + if self.modify_type is not None: + kw["modify_type"] = self.modify_type + if self.modify_nullable is not None: + kw["modify_nullable"] = self.modify_nullable + if self.modify_server_default is not False: + kw["modify_server_default"] = self.modify_server_default + if self.modify_comment is not False: + kw["modify_comment"] = self.modify_comment + + # TODO: make this a little simpler + all_keys = { + m.group(1) + for m in [re.match(r"^(?:existing_|modify_)(.+)$", k) for k in kw] + if m + } + + for k in all_keys: + if "modify_%s" % k in kw: + swap = kw["existing_%s" % k] + kw["existing_%s" % k] = kw["modify_%s" % k] + kw["modify_%s" % k] = swap + + return self.__class__( + self.table_name, self.column_name, schema=self.schema, **kw + ) + + @classmethod + def alter_column( + cls, + operations: Operations, + table_name: str, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + new_column_name: Optional[str] = None, + type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None, + existing_type: Optional[ + Union[TypeEngine[Any], Type[TypeEngine[Any]]] + ] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + r"""Issue an "alter column" instruction using the + current migration context. + + Generally, only that aspect of the column which + is being changed, i.e. name, type, nullability, + default, needs to be specified. Multiple changes + can also be specified at once and the backend should + "do the right thing", emitting each change either + separately or together as the backend allows. + + MySQL has special requirements here, since MySQL + cannot ALTER a column without a full specification. + When producing MySQL-compatible migration files, + it is recommended that the ``existing_type``, + ``existing_server_default``, and ``existing_nullable`` + parameters be present, if not being altered. + + Type changes which are against the SQLAlchemy + "schema" types :class:`~sqlalchemy.types.Boolean` + and :class:`~sqlalchemy.types.Enum` may also + add or drop constraints which accompany those + types on backends that don't support them natively. + The ``existing_type`` argument is + used in this case to identify and remove a previous + constraint that was bound to the type object. + + :param table_name: string name of the target table. + :param column_name: string name of the target column, + as it exists before the operation begins. + :param nullable: Optional; specify ``True`` or ``False`` + to alter the column's nullability. + :param server_default: Optional; specify a string + SQL expression, :func:`~sqlalchemy.sql.expression.text`, + or :class:`~sqlalchemy.schema.DefaultClause` to indicate + an alteration to the column's default value. + Set to ``None`` to have the default removed. + :param comment: optional string text of a new comment to add to the + column. + :param new_column_name: Optional; specify a string name here to + indicate the new name within a column rename operation. + :param type\_: Optional; a :class:`~sqlalchemy.types.TypeEngine` + type object to specify a change to the column's type. + For SQLAlchemy types that also indicate a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, :class:`~sqlalchemy.types.Enum`), + the constraint is also generated. + :param autoincrement: set the ``AUTO_INCREMENT`` flag of the column; + currently understood by the MySQL dialect. + :param existing_type: Optional; a + :class:`~sqlalchemy.types.TypeEngine` + type object to specify the previous type. This + is required for all MySQL column alter operations that + don't otherwise specify a new type, as well as for + when nullability is being changed on a SQL Server + column. It is also used if the type is a so-called + SQLAlchemy "schema" type which may define a constraint (i.e. + :class:`~sqlalchemy.types.Boolean`, + :class:`~sqlalchemy.types.Enum`), + so that the constraint can be dropped. + :param existing_server_default: Optional; The existing + default value of the column. Required on MySQL if + an existing default is not being changed; else MySQL + removes the default. + :param existing_nullable: Optional; the existing nullability + of the column. Required on MySQL if the existing nullability + is not being changed; else MySQL sets this to NULL. + :param existing_autoincrement: Optional; the existing autoincrement + of the column. Used for MySQL's system of altering a column + that specifies ``AUTO_INCREMENT``. + :param existing_comment: string text of the existing comment on the + column to be maintained. Required on MySQL if the existing comment + on the column is not being changed. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param postgresql_using: String argument which will indicate a + SQL expression to render within the Postgresql-specific USING clause + within ALTER COLUMN. This string is taken directly as raw SQL which + must explicitly include any necessary quoting or escaping of tokens + within the expression. + + """ + + alt = cls( + table_name, + column_name, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + modify_name=new_column_name, + modify_type=type_, + modify_server_default=server_default, + modify_nullable=nullable, + modify_comment=comment, + **kw, + ) + + return operations.invoke(alt) + + @classmethod + def batch_alter_column( + cls, + operations: BatchOperations, + column_name: str, + *, + nullable: Optional[bool] = None, + comment: Optional[Union[str, Literal[False]]] = False, + server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + new_column_name: Optional[str] = None, + type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None, + existing_type: Optional[ + Union[TypeEngine[Any], Type[TypeEngine[Any]]] + ] = None, + existing_server_default: Union[ + _ServerDefaultType, None, Literal[False] + ] = False, + existing_nullable: Optional[bool] = None, + existing_comment: Optional[str] = None, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue an "alter column" instruction using the current + batch migration context. + + Parameters are the same as that of :meth:`.Operations.alter_column`, + as well as the following option(s): + + :param insert_before: String name of an existing column which this + column should be placed before, when creating the new table. + + :param insert_after: String name of an existing column which this + column should be placed after, when creating the new table. If + both :paramref:`.BatchOperations.alter_column.insert_before` + and :paramref:`.BatchOperations.alter_column.insert_after` are + omitted, the column is inserted after the last existing column + in the table. + + .. seealso:: + + :meth:`.Operations.alter_column` + + + """ + alt = cls( + operations.impl.table_name, + column_name, + schema=operations.impl.schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + existing_comment=existing_comment, + modify_name=new_column_name, + modify_type=type_, + modify_server_default=server_default, + modify_nullable=nullable, + modify_comment=comment, + insert_before=insert_before, + insert_after=insert_after, + **kw, + ) + + return operations.invoke(alt) + + +@Operations.register_operation("add_column") +@BatchOperations.register_operation("add_column", "batch_add_column") +class AddColumnOp(AlterTableOp): + """Represent an add column operation.""" + + def __init__( + self, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column = column + self.if_not_exists = if_not_exists + self.inline_references = inline_references + self.inline_primary_key = inline_primary_key + self.kw = kw + + def reverse(self) -> DropColumnOp: + op = DropColumnOp.from_column_and_tablename( + self.schema, self.table_name, self.column + ) + op.if_exists = self.if_not_exists + return op + + def to_diff_tuple( + self, + ) -> Tuple[str, Optional[str], str, Column[Any]]: + return ("add_column", self.schema, self.table_name, self.column) + + def to_column(self) -> Column[Any]: + return self.column + + @classmethod + def from_column(cls, col: Column[Any]) -> AddColumnOp: + return cls(col.table.name, col, schema=col.table.schema) + + @classmethod + def from_column_and_tablename( + cls, + schema: Optional[str], + tname: str, + col: Column[Any], + ) -> AddColumnOp: + return cls(tname, col, schema=schema) + + @classmethod + def add_column( + cls, + operations: Operations, + table_name: str, + column: Column[Any], + *, + schema: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + """Issue an "add column" instruction using the current + migration context. + + e.g.:: + + from alembic import op + from sqlalchemy import Column, String + + op.add_column("organization", Column("name", String())) + + The :meth:`.Operations.add_column` method typically corresponds + to the SQL command "ALTER TABLE... ADD COLUMN". Within the scope + of this command, the column's name, datatype, nullability, + and optional server-generated defaults may be indicated. Options + also exist for control of single-column primary key and foreign key + constraints to be generated. + + .. note:: + + Not all contraint types may be indicated with this directive. + NOT NULL, FOREIGN KEY, and CHECK are honored, PRIMARY KEY + is conditionally honored, UNIQUE + is currently not. + + As of 1.18.2, the following :class:`~sqlalchemy.schema.Column` + parameters are **ignored**: + + * :paramref:`~sqlalchemy.schema.Column.unique` - use the + :meth:`.Operations.create_unique_constraint` method + * :paramref:`~sqlalchemy.schema.Column.index` - use the + :meth:`.Operations.create_index` method + + **PRIMARY KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + ``primary_key=True`` directive, indicating the column intends to be + part of a primary key constraint. However by default, the inline + "PRIMARY KEY" directive is not emitted, and it's assumed that a + separate :meth:`.Operations.create_primary_key` directive will be used + to create this constraint, which may potentially include other columns + as well as have an explicit name. To instead render an inline + "PRIMARY KEY" directive, the + :paramref:`.AddColumnOp.inline_primary_key` parameter may be indicated + at the same time as the ``primary_key`` parameter (both are needed):: + + from alembic import op + from sqlalchemy import Column, INTEGER + + op.add_column( + "organization", + Column("id", INTEGER, primary_key=True), + inline_primary_key=True + ) + + The ``primary_key=True`` parameter on + :class:`~sqlalchemy.schema.Column` also indicates behaviors such as + using the ``SERIAL`` datatype with the PostgreSQL database, which is + why two separate, independent parameters are provided to support all + combinations. + + .. versionadded:: 1.18.4 Added + :paramref:`.AddColumnOp.inline_primary_key` + to control use of the ``PRIMARY KEY`` inline directive. + + **FOREIGN KEY support** + + The provided :class:`~sqlalchemy.schema.Column` object may include a + :class:`~sqlalchemy.schema.ForeignKey` constraint directive, + referencing a remote table name. By default, Alembic will automatically + emit a second ALTER statement in order to add the single-column FOREIGN + KEY constraint separately:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + ) + + To render the FOREIGN KEY constraint inline within the ADD COLUMN + directive, use the ``inline_references`` parameter. This can improve + performance on large tables since the constraint is marked as valid + immediately for nullable columns:: + + from alembic import op + from sqlalchemy import Column, INTEGER, ForeignKey + + op.add_column( + "organization", + Column("account_id", INTEGER, ForeignKey("accounts.id")), + inline_references=True, + ) + + **Indicating server side defaults** + + The column argument passed to :meth:`.Operations.add_column` is a + :class:`~sqlalchemy.schema.Column` construct, used in the same way it's + used in SQLAlchemy. In particular, values or functions to be indicated + as producing the column's default value on the database side are + specified using the ``server_default`` parameter, and not ``default`` + which only specifies Python-side defaults:: + + from alembic import op + from sqlalchemy import Column, TIMESTAMP, func + + # specify "DEFAULT NOW" along with the column add + op.add_column( + "account", + Column("timestamp", TIMESTAMP, server_default=func.now()), + ) + + :param table_name: String name of the parent table. + :param column: a :class:`sqlalchemy.schema.Column` object + representing the new column. + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_not_exists: If True, adds ``IF NOT EXISTS`` operator + when creating the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param inline_references: If True, renders ``FOREIGN KEY`` constraints + inline within the ``ADD COLUMN`` directive using ``REFERENCES`` + syntax, rather than as a separate ``ALTER TABLE ADD CONSTRAINT`` + statement. This is supported by PostgreSQL, Oracle, MySQL 5.7+, and + MariaDB 10.5+. + + .. versionadded:: 1.18.2 + + :param inline_primary_key: If True, renders the ``PRIMARY KEY`` phrase + inline within the ``ADD COLUMN`` directive. When not present or + False, ``PRIMARY KEY`` is not emitted; it is assumed that the + migration script will include an additional + :meth:`.Operations.create_primary_key` directive to create a full + primary key constraint. + + .. versionadded:: 1.18.4 + + """ + + op = cls( + table_name, + column, + schema=schema, + if_not_exists=if_not_exists, + inline_references=inline_references, + inline_primary_key=inline_primary_key, + ) + return operations.invoke(op) + + @classmethod + def batch_add_column( + cls, + operations: BatchOperations, + column: Column[Any], + *, + insert_before: Optional[str] = None, + insert_after: Optional[str] = None, + if_not_exists: Optional[bool] = None, + inline_references: Optional[bool] = None, + inline_primary_key: Optional[bool] = None, + ) -> None: + """Issue an "add column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.add_column` + + """ + + kw = {} + if insert_before: + kw["insert_before"] = insert_before + if insert_after: + kw["insert_after"] = insert_after + + op = cls( + operations.impl.table_name, + column, + schema=operations.impl.schema, + if_not_exists=if_not_exists, + inline_references=inline_references, + inline_primary_key=inline_primary_key, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("drop_column") +@BatchOperations.register_operation("drop_column", "batch_drop_column") +class DropColumnOp(AlterTableOp): + """Represent a drop column operation.""" + + def __init__( + self, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + if_exists: Optional[bool] = None, + _reverse: Optional[AddColumnOp] = None, + **kw: Any, + ) -> None: + super().__init__(table_name, schema=schema) + self.column_name = column_name + self.kw = kw + self.if_exists = if_exists + self._reverse = _reverse + + def to_diff_tuple( + self, + ) -> Tuple[str, Optional[str], str, Column[Any]]: + return ( + "remove_column", + self.schema, + self.table_name, + self.to_column(), + ) + + def reverse(self) -> AddColumnOp: + if self._reverse is None: + raise ValueError( + "operation is not reversible; " + "original column is not present" + ) + + op = AddColumnOp.from_column_and_tablename( + self.schema, self.table_name, self._reverse.column + ) + op.if_not_exists = self.if_exists + return op + + @classmethod + def from_column_and_tablename( + cls, + schema: Optional[str], + tname: str, + col: Column[Any], + ) -> DropColumnOp: + return cls( + tname, + col.name, + schema=schema, + _reverse=AddColumnOp.from_column_and_tablename(schema, tname, col), + ) + + def to_column( + self, migration_context: Optional[MigrationContext] = None + ) -> Column[Any]: + if self._reverse is not None: + return self._reverse.column + schema_obj = schemaobj.SchemaObjects(migration_context) + return schema_obj.column(self.column_name, NULLTYPE) + + @classmethod + def drop_column( + cls, + operations: Operations, + table_name: str, + column_name: str, + *, + schema: Optional[str] = None, + **kw: Any, + ) -> None: + """Issue a "drop column" instruction using the current + migration context. + + e.g.:: + + drop_column("organization", "account_id") + + :param table_name: name of table + :param column_name: name of column + :param schema: Optional schema name to operate within. To control + quoting of the schema outside of the default behavior, use + the SQLAlchemy construct + :class:`~sqlalchemy.sql.elements.quoted_name`. + :param if_exists: If True, adds IF EXISTS operator when + dropping the new column for compatible dialects + + .. versionadded:: 1.16.0 + + :param mssql_drop_check: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the CHECK constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.check_constraints, + then exec's a separate DROP CONSTRAINT for that constraint. + :param mssql_drop_default: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop the DEFAULT constraint on the column using a + SQL-script-compatible + block that selects into a @variable from sys.default_constraints, + then exec's a separate DROP CONSTRAINT for that default. + :param mssql_drop_foreign_key: Optional boolean. When ``True``, on + Microsoft SQL Server only, first + drop a single FOREIGN KEY constraint on the column using a + SQL-script-compatible + block that selects into a @variable from + sys.foreign_keys/sys.foreign_key_columns, + then exec's a separate DROP CONSTRAINT for that default. Only + works if the column has exactly one FK constraint which refers to + it, at the moment. + """ + + op = cls(table_name, column_name, schema=schema, **kw) + return operations.invoke(op) + + @classmethod + def batch_drop_column( + cls, operations: BatchOperations, column_name: str, **kw: Any + ) -> None: + """Issue a "drop column" instruction using the current + batch migration context. + + .. seealso:: + + :meth:`.Operations.drop_column` + + """ + op = cls( + operations.impl.table_name, + column_name, + schema=operations.impl.schema, + **kw, + ) + return operations.invoke(op) + + +@Operations.register_operation("bulk_insert") +class BulkInsertOp(MigrateOperation): + """Represent a bulk insert operation.""" + + def __init__( + self, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + self.table = table + self.rows = rows + self.multiinsert = multiinsert + + @classmethod + def bulk_insert( + cls, + operations: Operations, + table: Union[Table, TableClause], + rows: List[Dict[str, Any]], + *, + multiinsert: bool = True, + ) -> None: + """Issue a "bulk insert" operation using the current + migration context. + + This provides a means of representing an INSERT of multiple rows + which works equally well in the context of executing on a live + connection as well as that of generating a SQL script. In the + case of a SQL script, the values are rendered inline into the + statement. + + e.g.:: + + from alembic import op + from datetime import date + from sqlalchemy.sql import table, column + from sqlalchemy import String, Integer, Date + + # Create an ad-hoc table to use for the insert statement. + accounts_table = table( + "account", + column("id", Integer), + column("name", String), + column("create_date", Date), + ) + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": date(2010, 10, 5), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": date(2007, 5, 27), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": date(2008, 8, 15), + }, + ], + ) + + When using --sql mode, some datatypes may not render inline + automatically, such as dates and other special types. When this + issue is present, :meth:`.Operations.inline_literal` may be used:: + + op.bulk_insert( + accounts_table, + [ + { + "id": 1, + "name": "John Smith", + "create_date": op.inline_literal("2010-10-05"), + }, + { + "id": 2, + "name": "Ed Williams", + "create_date": op.inline_literal("2007-05-27"), + }, + { + "id": 3, + "name": "Wendy Jones", + "create_date": op.inline_literal("2008-08-15"), + }, + ], + multiinsert=False, + ) + + When using :meth:`.Operations.inline_literal` in conjunction with + :meth:`.Operations.bulk_insert`, in order for the statement to work + in "online" (e.g. non --sql) mode, the + :paramref:`~.Operations.bulk_insert.multiinsert` + flag should be set to ``False``, which will have the effect of + individual INSERT statements being emitted to the database, each + with a distinct VALUES clause, so that the "inline" values can + still be rendered, rather than attempting to pass the values + as bound parameters. + + :param table: a table object which represents the target of the INSERT. + + :param rows: a list of dictionaries indicating rows. + + :param multiinsert: when at its default of True and --sql mode is not + enabled, the INSERT statement will be executed using + "executemany()" style, where all elements in the list of + dictionaries are passed as bound parameters in a single + list. Setting this to False results in individual INSERT + statements being emitted per parameter set, and is needed + in those cases where non-literal values are present in the + parameter sets. + + """ + + op = cls(table, rows, multiinsert=multiinsert) + operations.invoke(op) + + +@Operations.register_operation("execute") +@BatchOperations.register_operation("execute", "batch_execute") +class ExecuteSQLOp(MigrateOperation): + """Represent an execute SQL operation.""" + + def __init__( + self, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + self.sqltext = sqltext + self.execution_options = execution_options + + @classmethod + def execute( + cls, + operations: Operations, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + r"""Execute the given SQL using the current migration context. + + The given SQL can be a plain string, e.g.:: + + op.execute("INSERT INTO table (foo) VALUES ('some value')") + + Or it can be any kind of Core SQL Expression construct, such as + below where we use an update construct:: + + from sqlalchemy.sql import table, column + from sqlalchemy import String + from alembic import op + + account = table("account", column("name", String)) + op.execute( + account.update() + .where(account.c.name == op.inline_literal("account 1")) + .values({"name": op.inline_literal("account 2")}) + ) + + Above, we made use of the SQLAlchemy + :func:`sqlalchemy.sql.expression.table` and + :func:`sqlalchemy.sql.expression.column` constructs to make a brief, + ad-hoc table construct just for our UPDATE statement. A full + :class:`~sqlalchemy.schema.Table` construct of course works perfectly + fine as well, though note it's a recommended practice to at least + ensure the definition of a table is self-contained within the migration + script, rather than imported from a module that may break compatibility + with older migrations. + + In a SQL script context, the statement is emitted directly to the + output stream. There is *no* return result, however, as this + function is oriented towards generating a change script + that can run in "offline" mode. Additionally, parameterized + statements are discouraged here, as they *will not work* in offline + mode. Above, we use :meth:`.inline_literal` where parameters are + to be used. + + For full interaction with a connected database where parameters can + also be used normally, use the "bind" available from the context:: + + from alembic import op + + connection = op.get_bind() + + connection.execute( + account.update() + .where(account.c.name == "account 1") + .values({"name": "account 2"}) + ) + + Additionally, when passing the statement as a plain string, it is first + coerced into a :func:`sqlalchemy.sql.expression.text` construct + before being passed along. In the less likely case that the + literal SQL string contains a colon, it must be escaped with a + backslash, as:: + + op.execute(r"INSERT INTO table (foo) VALUES ('\:colon_value')") + + + :param sqltext: Any legal SQLAlchemy expression, including: + + * a string + * a :func:`sqlalchemy.sql.expression.text` construct. + * a :func:`sqlalchemy.sql.expression.insert` construct. + * a :func:`sqlalchemy.sql.expression.update` construct. + * a :func:`sqlalchemy.sql.expression.delete` construct. + * Any "executable" described in SQLAlchemy Core documentation, + noting that no result set is returned. + + .. note:: when passing a plain string, the statement is coerced into + a :func:`sqlalchemy.sql.expression.text` construct. This construct + considers symbols with colons, e.g. ``:foo`` to be bound parameters. + To avoid this, ensure that colon symbols are escaped, e.g. + ``\:foo``. + + :param execution_options: Optional dictionary of + execution options, will be passed to + :meth:`sqlalchemy.engine.Connection.execution_options`. + """ + op = cls(sqltext, execution_options=execution_options) + return operations.invoke(op) + + @classmethod + def batch_execute( + cls, + operations: Operations, + sqltext: Union[Executable, str], + *, + execution_options: Optional[dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current migration context. + + .. seealso:: + + :meth:`.Operations.execute` + + """ + return cls.execute( + operations, sqltext, execution_options=execution_options + ) + + def to_diff_tuple(self) -> Tuple[str, Union[Executable, str]]: + return ("execute", self.sqltext) + + +class OpContainer(MigrateOperation): + """Represent a sequence of operations operation.""" + + def __init__(self, ops: Sequence[MigrateOperation] = ()) -> None: + self.ops = list(ops) + + def is_empty(self) -> bool: + return not self.ops + + def as_diffs(self) -> Any: + return list(OpContainer._ops_as_diffs(self)) + + @classmethod + def _ops_as_diffs( + cls, migrations: OpContainer + ) -> Iterator[Tuple[Any, ...]]: + for op in migrations.ops: + if hasattr(op, "ops"): + yield from cls._ops_as_diffs(cast("OpContainer", op)) + else: + yield op.to_diff_tuple() + + +class ModifyTableOps(OpContainer): + """Contains a sequence of operations that all apply to a single Table.""" + + def __init__( + self, + table_name: str, + ops: Sequence[MigrateOperation], + *, + schema: Optional[str] = None, + ) -> None: + super().__init__(ops) + self.table_name = table_name + self.schema = schema + + def reverse(self) -> ModifyTableOps: + return ModifyTableOps( + self.table_name, + ops=list(reversed([op.reverse() for op in self.ops])), + schema=self.schema, + ) + + +class UpgradeOps(OpContainer): + """contains a sequence of operations that would apply to the + 'upgrade' stream of a script. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + def __init__( + self, + ops: Sequence[MigrateOperation] = (), + upgrade_token: str = "upgrades", + ) -> None: + super().__init__(ops=ops) + self.upgrade_token = upgrade_token + + def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps: + downgrade_ops.ops[:] = list( + reversed([op.reverse() for op in self.ops]) + ) + return downgrade_ops + + def reverse(self) -> DowngradeOps: + return self.reverse_into(DowngradeOps(ops=[])) + + +class DowngradeOps(OpContainer): + """contains a sequence of operations that would apply to the + 'downgrade' stream of a script. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + def __init__( + self, + ops: Sequence[MigrateOperation] = (), + downgrade_token: str = "downgrades", + ) -> None: + super().__init__(ops=ops) + self.downgrade_token = downgrade_token + + def reverse(self) -> UpgradeOps: + return UpgradeOps( + ops=list(reversed([op.reverse() for op in self.ops])) + ) + + +class MigrationScript(MigrateOperation): + """represents a migration script. + + E.g. when autogenerate encounters this object, this corresponds to the + production of an actual script file. + + A normal :class:`.MigrationScript` object would contain a single + :class:`.UpgradeOps` and a single :class:`.DowngradeOps` directive. + These are accessible via the ``.upgrade_ops`` and ``.downgrade_ops`` + attributes. + + In the case of an autogenerate operation that runs multiple times, + such as the multiple database example in the "multidb" template, + the ``.upgrade_ops`` and ``.downgrade_ops`` attributes are disabled, + and instead these objects should be accessed via the ``.upgrade_ops_list`` + and ``.downgrade_ops_list`` list-based attributes. These latter + attributes are always available at the very least as single-element lists. + + .. seealso:: + + :ref:`customizing_revision` + + """ + + _needs_render: Optional[bool] + _upgrade_ops: List[UpgradeOps] + _downgrade_ops: List[DowngradeOps] + + def __init__( + self, + rev_id: Optional[str], + upgrade_ops: UpgradeOps, + downgrade_ops: DowngradeOps, + *, + message: Optional[str] = None, + imports: Set[str] = set(), + head: Optional[str] = None, + splice: Optional[bool] = None, + branch_label: Optional[_RevIdType] = None, + version_path: Union[str, os.PathLike[str], None] = None, + depends_on: Optional[_RevIdType] = None, + ) -> None: + self.rev_id = rev_id + self.message = message + self.imports = imports + self.head = head + self.splice = splice + self.branch_label = branch_label + self.version_path = ( + pathlib.Path(version_path).as_posix() if version_path else None + ) + self.depends_on = depends_on + self.upgrade_ops = upgrade_ops + self.downgrade_ops = downgrade_ops + + @property + def upgrade_ops(self) -> Optional[UpgradeOps]: + """An instance of :class:`.UpgradeOps`. + + .. seealso:: + + :attr:`.MigrationScript.upgrade_ops_list` + """ + if len(self._upgrade_ops) > 1: + raise ValueError( + "This MigrationScript instance has a multiple-entry " + "list for UpgradeOps; please use the " + "upgrade_ops_list attribute." + ) + elif not self._upgrade_ops: + return None + else: + return self._upgrade_ops[0] + + @upgrade_ops.setter + def upgrade_ops( + self, upgrade_ops: Union[UpgradeOps, List[UpgradeOps]] + ) -> None: + self._upgrade_ops = util.to_list(upgrade_ops) + for elem in self._upgrade_ops: + assert isinstance(elem, UpgradeOps) + + @property + def downgrade_ops(self) -> Optional[DowngradeOps]: + """An instance of :class:`.DowngradeOps`. + + .. seealso:: + + :attr:`.MigrationScript.downgrade_ops_list` + """ + if len(self._downgrade_ops) > 1: + raise ValueError( + "This MigrationScript instance has a multiple-entry " + "list for DowngradeOps; please use the " + "downgrade_ops_list attribute." + ) + elif not self._downgrade_ops: + return None + else: + return self._downgrade_ops[0] + + @downgrade_ops.setter + def downgrade_ops( + self, downgrade_ops: Union[DowngradeOps, List[DowngradeOps]] + ) -> None: + self._downgrade_ops = util.to_list(downgrade_ops) + for elem in self._downgrade_ops: + assert isinstance(elem, DowngradeOps) + + @property + def upgrade_ops_list(self) -> List[UpgradeOps]: + """A list of :class:`.UpgradeOps` instances. + + This is used in place of the :attr:`.MigrationScript.upgrade_ops` + attribute when dealing with a revision operation that does + multiple autogenerate passes. + + """ + return self._upgrade_ops + + @property + def downgrade_ops_list(self) -> List[DowngradeOps]: + """A list of :class:`.DowngradeOps` instances. + + This is used in place of the :attr:`.MigrationScript.downgrade_ops` + attribute when dealing with a revision operation that does + multiple autogenerate passes. + + """ + return self._downgrade_ops diff --git a/venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py b/venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py new file mode 100644 index 0000000..59c1002 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/schemaobj.py @@ -0,0 +1,290 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Sequence +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import schema as sa_schema +from sqlalchemy.sql.schema import Column +from sqlalchemy.sql.schema import Constraint +from sqlalchemy.sql.schema import Index +from sqlalchemy.types import Integer +from sqlalchemy.types import NULLTYPE + +from .. import util +from ..util import sqla_compat + +if TYPE_CHECKING: + from sqlalchemy.sql.elements import ColumnElement + from sqlalchemy.sql.elements import TextClause + from sqlalchemy.sql.schema import CheckConstraint + from sqlalchemy.sql.schema import ForeignKey + from sqlalchemy.sql.schema import ForeignKeyConstraint + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import PrimaryKeyConstraint + from sqlalchemy.sql.schema import Table + from sqlalchemy.sql.schema import UniqueConstraint + from sqlalchemy.sql.type_api import TypeEngine + + from ..runtime.migration import MigrationContext + + +class SchemaObjects: + def __init__( + self, migration_context: Optional[MigrationContext] = None + ) -> None: + self.migration_context = migration_context + + def primary_key_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + cols: Sequence[str], + schema: Optional[str] = None, + **dialect_kw, + ) -> PrimaryKeyConstraint: + m = self.metadata() + columns = [sa_schema.Column(n, NULLTYPE) for n in cols] + t = sa_schema.Table(table_name, m, *columns, schema=schema) + # SQLAlchemy primary key constraint name arg is wrongly typed on + # the SQLAlchemy side through 2.0.5 at least + p = sa_schema.PrimaryKeyConstraint( + *[t.c[n] for n in cols], name=name, **dialect_kw # type: ignore + ) + return p + + def foreign_key_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + referent: str, + local_cols: List[str], + remote_cols: List[str], + onupdate: Optional[str] = None, + ondelete: Optional[str] = None, + deferrable: Optional[bool] = None, + source_schema: Optional[str] = None, + referent_schema: Optional[str] = None, + initially: Optional[str] = None, + match: Optional[str] = None, + **dialect_kw, + ) -> ForeignKeyConstraint: + m = self.metadata() + if source == referent and source_schema == referent_schema: + t1_cols = local_cols + remote_cols + else: + t1_cols = local_cols + sa_schema.Table( + referent, + m, + *[sa_schema.Column(n, NULLTYPE) for n in remote_cols], + schema=referent_schema, + ) + + t1 = sa_schema.Table( + source, + m, + *[ + sa_schema.Column(n, NULLTYPE) + for n in util.unique_list(t1_cols) + ], + schema=source_schema, + ) + + tname = ( + "%s.%s" % (referent_schema, referent) + if referent_schema + else referent + ) + + dialect_kw["match"] = match + + f = sa_schema.ForeignKeyConstraint( + local_cols, + ["%s.%s" % (tname, n) for n in remote_cols], + name=name, + onupdate=onupdate, + ondelete=ondelete, + deferrable=deferrable, + initially=initially, + **dialect_kw, + ) + t1.append_constraint(f) + + return f + + def unique_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + local_cols: Sequence[str], + schema: Optional[str] = None, + **kw, + ) -> UniqueConstraint: + t = sa_schema.Table( + source, + self.metadata(), + *[sa_schema.Column(n, NULLTYPE) for n in local_cols], + schema=schema, + ) + kw["name"] = name + uq = sa_schema.UniqueConstraint(*[t.c[n] for n in local_cols], **kw) + # TODO: need event tests to ensure the event + # is fired off here + t.append_constraint(uq) + return uq + + def check_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + source: str, + condition: Union[str, TextClause, ColumnElement[Any]], + schema: Optional[str] = None, + **kw, + ) -> Union[CheckConstraint]: + t = sa_schema.Table( + source, + self.metadata(), + sa_schema.Column("x", Integer), + schema=schema, + ) + ck = sa_schema.CheckConstraint(condition, name=name, **kw) + t.append_constraint(ck) + return ck + + def generic_constraint( + self, + name: Optional[sqla_compat._ConstraintNameDefined], + table_name: str, + type_: Optional[str], + schema: Optional[str] = None, + **kw, + ) -> Any: + t = self.table(table_name, schema=schema) + types: Dict[Optional[str], Any] = { + "foreignkey": lambda name: sa_schema.ForeignKeyConstraint( + [], [], name=name + ), + "primary": sa_schema.PrimaryKeyConstraint, + "unique": sa_schema.UniqueConstraint, + "check": lambda name: sa_schema.CheckConstraint("", name=name), + None: sa_schema.Constraint, + } + try: + const = types[type_] + except KeyError as ke: + raise TypeError( + "'type' can be one of %s" + % ", ".join(sorted(repr(x) for x in types)) + ) from ke + else: + const = const(name=name) + t.append_constraint(const) + return const + + def metadata(self) -> MetaData: + kw = {} + if ( + self.migration_context is not None + and "target_metadata" in self.migration_context.opts + ): + mt = self.migration_context.opts["target_metadata"] + if hasattr(mt, "naming_convention"): + kw["naming_convention"] = mt.naming_convention + return sa_schema.MetaData(**kw) + + def table(self, name: str, *columns, **kw) -> Table: + m = self.metadata() + + cols = [ + sqla_compat._copy(c) if c.table is not None else c + for c in columns + if isinstance(c, Column) + ] + # these flags have already added their UniqueConstraint / + # Index objects to the table, so flip them off here. + # SQLAlchemy tometadata() avoids this instead by preserving the + # flags and skipping the constraints that have _type_bound on them, + # but for a migration we'd rather list out the constraints + # explicitly. + _constraints_included = kw.pop("_constraints_included", False) + if _constraints_included: + for c in cols: + c.unique = c.index = False + + t = sa_schema.Table(name, m, *cols, **kw) + + constraints = [ + ( + sqla_compat._copy(elem, target_table=t) + if getattr(elem, "parent", None) is not t + and getattr(elem, "parent", None) is not None + else elem + ) + for elem in columns + if isinstance(elem, (Constraint, Index)) + ] + + for const in constraints: + t.append_constraint(const) + + for f in t.foreign_keys: + self._ensure_table_for_fk(m, f) + return t + + def column(self, name: str, type_: TypeEngine, **kw) -> Column: + return sa_schema.Column(name, type_, **kw) + + def index( + self, + name: Optional[str], + tablename: Optional[str], + columns: Sequence[Union[str, TextClause, ColumnElement[Any]]], + schema: Optional[str] = None, + **kw, + ) -> Index: + t = sa_schema.Table( + tablename or "no_table", + self.metadata(), + schema=schema, + ) + kw["_table"] = t + idx = sa_schema.Index( + name, + *[util.sqla_compat._textual_index_column(t, n) for n in columns], + **kw, + ) + return idx + + def _parse_table_key(self, table_key: str) -> Tuple[Optional[str], str]: + if "." in table_key: + tokens = table_key.split(".") + sname: Optional[str] = ".".join(tokens[0:-1]) + tname = tokens[-1] + else: + tname = table_key + sname = None + return (sname, tname) + + def _ensure_table_for_fk(self, metadata: MetaData, fk: ForeignKey) -> None: + """create a placeholder Table object for the referent of a + ForeignKey. + + """ + if isinstance(fk._colspec, str): + table_key, cname = fk._colspec.rsplit(".", 1) + sname, tname = self._parse_table_key(table_key) + if table_key not in metadata.tables: + rel_t = sa_schema.Table(tname, metadata, schema=sname) + else: + rel_t = metadata.tables[table_key] + if cname not in rel_t.c: + rel_t.append_column(sa_schema.Column(cname, NULLTYPE)) diff --git a/venv/lib/python3.12/site-packages/alembic/operations/toimpl.py b/venv/lib/python3.12/site-packages/alembic/operations/toimpl.py new file mode 100644 index 0000000..85b9d8a --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/operations/toimpl.py @@ -0,0 +1,261 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from typing import TYPE_CHECKING + +from sqlalchemy import schema as sa_schema + +from . import ops +from .base import Operations +from ..util.sqla_compat import _copy +from ..util.sqla_compat import sqla_2 + +if TYPE_CHECKING: + from sqlalchemy.sql.schema import Table + + +@Operations.implementation_for(ops.AlterColumnOp) +def alter_column( + operations: "Operations", operation: "ops.AlterColumnOp" +) -> None: + compiler = operations.impl.dialect.statement_compiler( + operations.impl.dialect, None + ) + + existing_type = operation.existing_type + existing_nullable = operation.existing_nullable + existing_server_default = operation.existing_server_default + type_ = operation.modify_type + column_name = operation.column_name + table_name = operation.table_name + schema = operation.schema + server_default = operation.modify_server_default + new_column_name = operation.modify_name + nullable = operation.modify_nullable + comment = operation.modify_comment + existing_comment = operation.existing_comment + + def _count_constraint(constraint): + return not isinstance(constraint, sa_schema.PrimaryKeyConstraint) and ( + not constraint._create_rule or constraint._create_rule(compiler) + ) + + if existing_type and type_: + t = operations.schema_obj.table( + table_name, + sa_schema.Column(column_name, existing_type), + schema=schema, + ) + for constraint in t.constraints: + if _count_constraint(constraint): + operations.impl.drop_constraint(constraint) + + # some weird pyright quirk here, these have Literal[False] + # in their types, not sure why pyright thinks they could be True + assert existing_server_default is not True # type: ignore[comparison-overlap] # noqa: E501 + assert comment is not True # type: ignore[comparison-overlap] + + operations.impl.alter_column( + table_name, + column_name, + nullable=nullable, + server_default=server_default, + name=new_column_name, + type_=type_, + schema=schema, + existing_type=existing_type, + existing_server_default=existing_server_default, + existing_nullable=existing_nullable, + comment=comment, + existing_comment=existing_comment, + **operation.kw, + ) + + if type_: + t = operations.schema_obj.table( + table_name, + operations.schema_obj.column(column_name, type_), + schema=schema, + ) + for constraint in t.constraints: + if _count_constraint(constraint): + operations.impl.add_constraint(constraint) + + +@Operations.implementation_for(ops.DropTableOp) +def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None: + kw = {} + if operation.if_exists is not None: + kw["if_exists"] = operation.if_exists + operations.impl.drop_table( + operation.to_table(operations.migration_context), **kw + ) + + +@Operations.implementation_for(ops.DropColumnOp) +def drop_column( + operations: "Operations", operation: "ops.DropColumnOp" +) -> None: + column = operation.to_column(operations.migration_context) + operations.impl.drop_column( + operation.table_name, + column, + schema=operation.schema, + if_exists=operation.if_exists, + **operation.kw, + ) + + +@Operations.implementation_for(ops.CreateIndexOp) +def create_index( + operations: "Operations", operation: "ops.CreateIndexOp" +) -> None: + idx = operation.to_index(operations.migration_context) + kw = {} + if operation.if_not_exists is not None: + kw["if_not_exists"] = operation.if_not_exists + operations.impl.create_index(idx, **kw) + + +@Operations.implementation_for(ops.DropIndexOp) +def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None: + kw = {} + if operation.if_exists is not None: + kw["if_exists"] = operation.if_exists + + operations.impl.drop_index( + operation.to_index(operations.migration_context), + **kw, + ) + + +@Operations.implementation_for(ops.CreateTableOp) +def create_table( + operations: "Operations", operation: "ops.CreateTableOp" +) -> "Table": + kw = {} + if operation.if_not_exists is not None: + kw["if_not_exists"] = operation.if_not_exists + table = operation.to_table(operations.migration_context) + operations.impl.create_table(table, **kw) + return table + + +@Operations.implementation_for(ops.RenameTableOp) +def rename_table( + operations: "Operations", operation: "ops.RenameTableOp" +) -> None: + operations.impl.rename_table( + operation.table_name, operation.new_table_name, schema=operation.schema + ) + + +@Operations.implementation_for(ops.CreateTableCommentOp) +def create_table_comment( + operations: "Operations", operation: "ops.CreateTableCommentOp" +) -> None: + table = operation.to_table(operations.migration_context) + operations.impl.create_table_comment(table) + + +@Operations.implementation_for(ops.DropTableCommentOp) +def drop_table_comment( + operations: "Operations", operation: "ops.DropTableCommentOp" +) -> None: + table = operation.to_table(operations.migration_context) + operations.impl.drop_table_comment(table) + + +@Operations.implementation_for(ops.AddColumnOp) +def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None: + table_name = operation.table_name + column = operation.column + schema = operation.schema + kw = operation.kw + inline_references = operation.inline_references + inline_primary_key = operation.inline_primary_key + + if column.table is not None: + column = _copy(column) + + t = operations.schema_obj.table(table_name, column, schema=schema) + operations.impl.add_column( + table_name, + column, + schema=schema, + if_not_exists=operation.if_not_exists, + inline_references=inline_references, + inline_primary_key=inline_primary_key, + **kw, + ) + + for constraint in t.constraints: + if not isinstance(constraint, sa_schema.PrimaryKeyConstraint): + # Skip ForeignKeyConstraint if it was rendered inline + # This only happens when inline_references=True AND there's exactly + # one FK AND the constraint is single-column + if ( + inline_references + and isinstance(constraint, sa_schema.ForeignKeyConstraint) + and len(column.foreign_keys) == 1 + and len(constraint.columns) == 1 + ): + continue + operations.impl.add_constraint(constraint) + for index in t.indexes: + operations.impl.create_index(index) + + with_comment = ( + operations.impl.dialect.supports_comments + and not operations.impl.dialect.inline_comments + ) + comment = column.comment + if comment and with_comment: + operations.impl.create_column_comment(column) + + +@Operations.implementation_for(ops.AddConstraintOp) +def create_constraint( + operations: "Operations", operation: "ops.AddConstraintOp" +) -> None: + operations.impl.add_constraint( + operation.to_constraint(operations.migration_context) + ) + + +@Operations.implementation_for(ops.DropConstraintOp) +def drop_constraint( + operations: "Operations", operation: "ops.DropConstraintOp" +) -> None: + kw = {} + if operation.if_exists is not None: + if not sqla_2: + raise NotImplementedError("SQLAlchemy 2.0 required") + kw["if_exists"] = operation.if_exists + operations.impl.drop_constraint( + operations.schema_obj.generic_constraint( + operation.constraint_name, + operation.table_name, + operation.constraint_type, + schema=operation.schema, + ), + **kw, + ) + + +@Operations.implementation_for(ops.BulkInsertOp) +def bulk_insert( + operations: "Operations", operation: "ops.BulkInsertOp" +) -> None: + operations.impl.bulk_insert( # type: ignore[union-attr] + operation.table, operation.rows, multiinsert=operation.multiinsert + ) + + +@Operations.implementation_for(ops.ExecuteSQLOp) +def execute_sql( + operations: "Operations", operation: "ops.ExecuteSQLOp" +) -> None: + operations.migration_context.impl.execute( + operation.sqltext, execution_options=operation.execution_options + ) diff --git a/venv/lib/python3.12/site-packages/alembic/py.typed b/venv/lib/python3.12/site-packages/alembic/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/__init__.py b/venv/lib/python3.12/site-packages/alembic/runtime/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e80e0bc964b240f42e559c6df45cc6c16251743 GIT binary patch literal 217 zcmZ8bF%H5o3~VS!2&sQyLkwU6D`H|s{2)q;p{7j}C21jigoS~Xx9|*JfY_KwS57+n z&N}J*Op-wqa39^M-LH212YXs~Laq!hGwxvCb^*&Hl6oM@31;(!)Ji87@OOkFt& gHJnpLj`OMZ-~LjnP>L(l3y-is+lqJ07&CEDKR<#!aR2}S literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/environment.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4579d455a60cc607db07cebfc7576ab01e99fa28 GIT binary patch literal 44704 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 literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/runtime/__pycache__/migration.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f95b42547888f057d935152e87a76d0933a91a09 GIT binary patch literal 57839 zcmeIbd3apcbtm|00}7}DssI%Bt+1o8a$mq*1UHEUZHsbb$ZW8PDv*G%=~aOw%Ah5? zm2SXNBGOKW*g9#QvJKlfY(v;SZ0E3j(lPA7cl%`Ca2|dgllfE5VJCmf zL%3kLfW!F+7Y-M4*om-f*u~)jgx$k#4i_R^G+e}C7sAEE#T<4c>>2iOxCmkIu$RNd z2$u|(aM*)z>2N8By$F{LmvOiRVc)Qi!=;nuQx(G%94fve*mm^#= zT+88#$-1ff;d%~NPBu(64mToP6{?=}Pc;oUakvKI=HX@z*G@K1wG6j#xNfp_s%^N< zB8AK3A;6^`<+cyEmI z2)BeCJwhxZl2mQyoJLZ2yY$U%Hht*ZByHaw{tjv@Q&df z9PUDR=kQL8WR=2>iKsdW^xyEVw=CkX;oVm0h!pC6Sqk;Mg}Fh`@E(rqMO>d2x0mDk z5w}5$+sAPm5w}T;+s|>E5w}H)JHT;U5w|UT;4Pa{%M%>C9kDwMu?Oevft^31Bmqmx z6`Y=)i3X$NGt&|LSv)#39SvWKP6elf=fW~2n4X)QRAPtlx9!06Jbs)9gOiiNvy)+j z?W4g+6u*TBXC^1Zqf{2f4vvpftn*kjEGsERI66ZG+mDZPF6YTv$`PDIobBl_WpSLE zo8^*=PJQ7shei$_J#_G?V?#&i%QMrW^y0~JDt2;YbY^mHit5XYgsE~09hyEjK20sN z&B;`3>4}-q=SAexQ8^e5pQA1m9~6B(A^Q3w8XL+J3Q->WTy&g%U6B_igQ#U{Hb`X` z9GV`T357$)PU4UJ!SSF{r|p@~9Y+j6Fg>k-z;#LvPDg?&M2e{yX$cAsT?&uRMF~1T zqUnMXN)Jt67?)?Jr^3@wrGbdeLvU>T9EF}9mB(kPDIO6T9G6kyj7(kfjC?M9VLSp5 zos^#lMhFB>H3ngq8a@%69i<7NKFWl#tRF_qH7P6&TSC&XHRKFgUbYY0!uC)>$ojJV zEhRlrR$<77FM0U2<2OI#3OVrW47o#j_$`>v4-}d%v1PrK zD_yWGNFz!Gk~A;fw$OAdnHoQb5krqK20S$b)|4GXJtl8J;s_Pu_kS|qe|}~v+#fs} znwt#9#$)}1;mGsRnc4oK@I^mbF#!-o`e%dlQ{j=w`I*`AvH6imSiUem8t%Ukp1#mO zIexZ(c0PK3W_okq#!dYZ%&y+q;OO&M1(ALX^3>V!(SCVuIy#POv^Mq4&ZnFsBjeNK z(UFl@O;&&V)Nebet+aW(B&{@UTC~6CY*@!%(x`$YC@fK!0n&%SHEBVbpxBIO!U#g; z+tZTVk6^&cFiE*bFv-<=o$`&$VqC_=N(Wl zLB+0p&;lb`u6YF(c9~$Fa>~F-XEBfr8hIPyZd(~5DN9tDJq#6j0H4f;P%U5|zDHf=w27Q__a)qYx90AI-}%Bl_mP!N+ZG3t?&gHM`8{|4nnQAL zWJnI7S%h{|-V-WXR`#e=o*a(O$)5=%*7?P;rUJ5(q zLHx)x8)TYfB1C(V2vM0LS0SO@=^(KANUCsTWNHR@pTh2ukr(EIlj4iK8R=6_AhFr7 z9Gz!`z{^~w`7Ixy7XeP*PcK4U0+`emnO-~z3A4T|ty?XQ&1-p5UhzW;t=kY<<4~R* zf4cc)ari#UvpKelvZ#bn{I5OAG_c3I1?tw0Ku7D$=@h03tYI)?3GNBWnL zFa7V#ze45Ml^rV99L@{phbm}))?%EfuTta!A)JRg3PRPPeC*tXp_-5rzphYas5Vr9 zFYZuXs1Pwlq56;uzr~@3kQ=`q#24Y$o8Iogm4?egO(CzL4L-y) zhe`}F<@44+i+lv|3Vs!H;B(N_eal%{h=VpB#<=z2&mPte<~OX`-2yP<4lD5 zqci@XU)z=oX<%k`koI_xft2PqGK7?=u2&jU=^L}=f+a8xD1&iE^8`k{ z$Y2x=Q>dmsg>E}@W?(=J28~XOpZj`oJbHe78epeBC_UhYP#OUZ1r*~`vomtkuN0;i zqz$^-IR73sk+I300g>e}#%(SFs*Z+rKAR_2!s81j9H_aL2_s-_onMm{;(9a8#1O5F|^C~!+16n=M3!m@QicVo1gJ)+hU^K3liD{i7lC@B!RgU(`dWU>5YuFv zo{^`5lNx5jkgKO$4Wh>N-e+i(GU9qu^x>PC1)TH`1WsaK=n$Qm?PD|z>_B{i&gQ9k zK5jSn_?sg*GKy&YMQjc_hwnKh&xI9aF*K@h^>_<+;%%ez>W>i%%F%8-uP)f|7tyQy z@L5(ns4dtgh$PK;LYB9b^vJ1@29lH`tu(7Ei-V-fRzeVXN&rir%jbBjFsz@H2Hx<*UQ<}zaaCiwv4i7u0P+gr-oL%SN)??_9B zn2cwF&NLa%LjFRo9{cnW`%bxff<-M&Y7tR7kMT1>WO>FDs>sm_I;Ukk6HjJD;7@f# zErcih+27^z$JAe`ju_U-qgz^)qoqA2wdRMa^_I;`p&H{~f!ZN(n)zsv$_qwDKp-4W zl^FPKU`d04oT@fNjbINQ(YD)^7rab`HXI?MXkMH@2>>bU*mTMMN+}K!HPPv&UEr&;lLvSipq5es1P1F|(j|Jx@qp2dbD&F=|9)&7bTafal zeIJPs(G*O%vCHGEGJTGSx|BCP&Fu35oBSM_E1#wpF>Pbsg9`b9&3e+`7U_$X9|=!0 zT>?#gvXjq9MSY zDpZbssX{}z3cO$NZulHHX{=JF9Vpc>qMVwPyp-C|uT-%OGiN0$!HQ}63Y0e>eA!VCxJd#kPz_5ju5RyV=EJ0Ot}WdY&nL( zOBGX_&(4piK(*75rb^T20LMACS!A1>IhV4JPmj%{z^UWIHn>@|<^u)H$dX5?`RD0% zmR>=6ad)XpdIKW`_>-t-egr>;*f_E3EFxOvt-p-Vk?(@qdP!OO>jl@0iDuWa1<+g0DKdZ%-F<3arGTB)qPcIoP+o6cKp%at1z^H%EouOGc}G~Tvj zxo&6NS-sNL_jc#)&J}mZt@3r7wY!kY>;U_xyPx9Yn-9G!FLyo3u`8Ro#_IQ7CHEVe z?|ZAR*Co8|x9rKzeTmL}?-nk1K7}G0ix&r9J&6*0RoC`i-4`$Gi@W>qUg_DmIQX7B zu-ec`2|n=FCcOg*@4!0)%ihClHmRuOo;$GS#_wxKmyTXJ^~w;qaRo*9oV6&cvYtA# zT3&ta*{jc9KX-F(xx5!Kg{!W*n|5&c?l*ONbB>W!)uZ{xSh-mH0}2JNPA_wdarHE*WwHmo8|=Np~3 z_P(=axp_ZDHLmn*c>Cz>qbu&#n_*<>WoQM^zP?RwKYRPx6?bjCz7HSz3fClS0e`Kz zej49*@3(w!$M5V|aW~%_#P{9$_ojI34%FGd`R((!&)@m-a^GR9sqVh7^4iI(CzHNS z3E!qW+n0U2R@`M^V!n2A=_LI;we-}AyPmn44GDJx0_#Qj<*4!1g0)hqy8iWo8wJU# zEs3fvD>WS}Osk3 zzwpKvl5IN@Z99@}2NG=u?m3?TC-t>mU)y!x?V+)Wm-pOr_uhB9D9ehw3H`%Z@4UJ* zS=M*AtPf*Z;{U7s{DQ)@La8F~S1yDEcbc$``p{0#XN(1$4RFTDf5|#7fyT+>-@FBj zyfun1;#sgnwKImgN6A%Djjo^2Xm&M+1g(SxOAPVq&dDMdV+)j|m+jM&3s$)&s!`Vy z8r`jzrqk4yomOeVIib0B!~vPikLvnGt-mck~I6lR}c63 z{mgV!n6ZNA$3;*tiP4~P8A4&~gHy=J+$ADQ{9vvr+X$75gn}tW*_B>ELQlp%hLKwBzYS(TI8 z8K7|sUm|Y0;E7?_G3_)yVfJDnHaqjWdi>`w-feQs(G~$WFxIOts2**i8poZglEW{| zK{F%N$JBkGFjWY?ol>Aohjf|Fm?`%`2xBo4hh%w1W+Ex&q(+TkTvE=d;PYV)xrioH z39^Dimk6kUJ_}0%1u5I;WJC~GbVyIRjg1kM7tvdKJdG#QVX1u7GXjRO3Eh<{08|AZ zI#!cKeQDqR6eUOg1TT3%N5;;ie0xR@uH;FdeZzmJ&De}%N6?`k-ntreravI?y2R{ z<8k-#6;DOn(;Rm;<9*-Lcr*W2&7D*6T|>*BlX2%su~BOqFFH$`Yzr|1hY@-Tsv9gy zHl{!VderR?Iu4SV?uMB*gWQ3%CY!fho1+XOnlm1?UW+un5o7}iv*C!t~oj zQFeO0R1gwI7NTi;BNtICoue`ytyYt76B^4*&!pCR8SFl@@-ExJ($O|^JzvQB9c|lZ zCXmjyG#v{AThp{2GiIjNHv6VKRo1Q|5h8U}FD@cftN>$eZUk z>;H>}{#&FMZ9g##H1bl1>Ca@oYRo~sTosNMX&xl1+-Kg)N6s9L^_g9&ajwo=<}EG6 za{B*A$8;4;JaTj{g2DOo9G#2OMd_053(`f)zp!1j1nj}%IGr3K)gO{eH3#RSGhlo` zf2rAjb`tuOJxs!?bfY4ajm*umwD`a?r%oO`dE&&eQyLvH9+{bBVV8e047q*8Ku0j? zPV#n$9euttLc4cTg*KY4t26@I#5L5VfJ7ZD zLUMGFk0WStkWNS>4Wzb{HZw~%NmQw8ifBp`XZ6m}ps*eX#Gs0VUY9T$bqdbVs6p*X z9!Gbm`C5ihq!h0W0cLP`Is#P_RH3MaXfO%Up;%VWyJ!a~9ieAI zrDleiUQ#BE4GXld4|+24G&+_!FAp%Y1Z@pRA~;y~_`ABO2U;79BL@z|XqZGHJo$Mw zpmACQXeQ1@$z%QX3qjxhpK!jQvb<{Dszu}GJwoKYDnX{CWPq|GT6ry689 z)WPW7wAfU|!Q2nkeCT*#tLz;cpVZMQLB!a*^UyvcW`oQ;3+S6F>urj+Qi6|0B`AAj zp^kKhQib{oB%2A1A{gNb9dJW<1UZeqgp48^IRfI6#V{U;c>>jbE$6_;5PK|5_<^Si>k8z|Qwn*(=$+p2n+u(BBVH}WG!S&v` zv@_{yzw2thwdL(y-v$%j``WRkV@Y>Y!rk@->7o$`Wlr8WnXKEFsN1((cVO|vN<;JO zGdE@yPp)~vOiy|@CcGQp?TLFgE_+X|*{nq@W4RuBef-Awy@pNTBtY!r`K~9q;ZS13 zq2&!nmY!U#u3vm|&012h`%l~@tEh0-*LRUH=*6oq#w$0)y_@h}DXWi{1y+0wNncyS z*LKg>K|&PIRgb7}rFA3m@;7}XS;4KylLn4i;@+cI?7!l<+4^SSjljL;ZPcOprTL_% z>#nEkR`l&JefvvPM8Va9q_;KUZGF$%{(eb!R=usAZ=QVPWU_T#i&I_qXuNVW9)u7kz+O;Ko38nZxy-Kmb&WjlmKnie{jVA6uWkGA7n z`CoASe}WJ%=ov__=`5B;Cz51D+jR6zJL|xhaqu8Yn#wd$DW>B>2-+1gv|z&^@WQgb z#Szg1Jp=4&&4z8ZnU7^)Q$s$19Uioo)}jdZTr{5e?}vnXZzgF)BtNJdql$r$>zI-8 z%5Fy*kfeE;VJ;5!{MbOBdq8ttG~ zq9j#l**$uxpkDagIs)(qiCWx~@waJlCf%SK)UI%g7_7!E-wa%8`_yiv!&z0%rBuop zC+A@zt2!1i=^`~{x@g_uX~P7tm@Z~h7i@_+83$l!s*^o~;UJQ;8R;E>Bu(X47^wII z{^sLip){whlnjbm%Zl?5Bt=+7_<_Vs>w}{GUSh4Vlv`jgYd;5$z|B#M5T`2a)eBN0#(&_iS?SJAe z*GTuagtzTIZ^z%T?d3ln*kcK}<#B2nb;@us4AY;i+r|e%ItvPQz~+%ia4bBcR#NGb1qu@$I&mDVm!^s5xH9#M0mHv)5Y*{?{z6V-o zN{*89Yt>h)lO;WOOL~4#))aR)UD>Og|L#{df8%o8+qzZ*J+x%grbN@GcUt33d#@C~ zU)FTLw*8xX;=az++Q!%S-Pjke-4^$4!~27(mSol8MAczZ;lpVWYGleQ-}AM7 zR4CQ;-a55hyD9G5v;q~n=f3vb2Sv5(R$EagEI5nn)+qF0q2%*_V=tOQ2STHA-hF>} z(m#~&4=D#g6yC)8aP=SMNxrt5rqY~N%7Ztq0sW3zxP(=a4g_<+(S=0zc0;0BMm);AeJ;2l>^_VGWQ;&mJ zr)O6>P1`jzOB9t1+G*tAc$#VuZAdwBG_%|l>#p_K)tDRwPSX z5~VFLSTETKedXllLy66YKD1d%4qMi3wzA?i$yQVhy;NWI^^&VQ7ESbBh-<^QX_>hGYWwp@*Q8e?X`|Cg;7;q)Px1h|wv2ckN1 z{iN{oH4EhqUZTK!By7hT=FNwYvaI?DFknb!YstXs1`Ki7eD@&Sgfq2<-w7`<$`YcU zpr7Qf2B`$QF|d(M2NH1 zS3M<`K#4d)%_0MvbP0Xg{OHWcB&K`HO}nEyJaF@*_%KL0k$^{pw_?~z;k2z%pCe3r zvZ8Ft!HH7&isFJyNK&4|D`go?xs*vVGCem1TepoAUCi`}W;g}otT8!4z{Z*sc1qvG zA122t<2KH)S*JZ5ei~^HZ^faCj7U~+&n_G~{H?Dq+*r6ZwA{GsuO0U0($%WkmCBl%eTm8qkQBiX=&7quC4D^!U(coc_|^wcdRNMfSN4w0rPZP zZ6<9srBh7hS&z~ZReMbCsOUevRr63=-U!!P7qJ6>0q`KMi_o_V*oIC)9$v_vOvc7# zcxTKPe2a1zp^}aWtV6-i8^KwFY+QxHIWL(!Auy2tCx9-M7lI?kDEWY*b;IV2Mnsa* zN>djZugX|^7E)1bO-tt|Eb=uJ{E~FPs47|1mniCki1o$WFD@7D!z?V=Z^;rQV~L$u zI+1ky6K?-a-!8=(_^%w{p>lKR>3 zi&&F6i&&!AGal8%TlE*{5JN~fIaQ&m6p#prk)}YF&*7u6^VJw|3cC9azQkIxM=cBC zcG8gTMrwA-mVr5HlAetT&qh)q0MqFDsbqbBqQ3u*FS+r_#KtFqE(l4$YEUp)GytJA zT09KcKS1EIOcr>KFhdxT(gj<@i*Ju)OzNcj0z7o6i|H~1#-_-D{K28m^bAU;p_mSz z9d^@&?a&wMtL3yE2dqKDb5BR%v4=>J4046f&p}OnRHIJ}B!`iI0(K|4n~H+h0ooQP zT8;Gq>OwGYs1D(uO5H*=(vUsLkb$}oNX*TG^vEs=JOEvRbKZZEsHZ9R4ridrqcg;v zpp;NDQ!Da!hC#D}(4CtlF;Qsd;&cG?FNjP~UaE5w5jzyflWD6K^u;>}rHYS&&OHGp5iyEjTkj$(>8V+wFCwX8(eV)w7LZ-c zA~(|mUf~4*Rym6QRhgK5@;|5YJXGb#lwP2na`FyGB)rO%b#N`QimWxn@qM&xl%sxW z`ca=YU}(&|v6W3-+Y|2gd+yHrHJ!J7x8{~>wk~>Be62~}hJp&&%H)^{Er7M=xC5&k#v%@_g-QDWv}UW(d!kfHhXUnX&i7EdYUsXK^1NuPs>1npU3 zivc@GQQ?PiC$Q#OoDI)o3m^djac>G4+v1g*i*3R}K85m0t_U$a3ke3m#2zsCGvqI4 zglHV{dfLBW?hV~z(mU3w4T0g@X`VdsQkS+Q%y=1X13-6pLJ-dsu1e{33}6VTEB(zM30|w`H7od5LO&xX$ zhvzZDKxZk2))BA?0StA9>64sTh+2aTD^?vTVoZV_#Jmy>(hOA8CTW203>YLrs^m-I zc&bF8iU<7Y1xv)H$V{Vu(pr`HX75nZXDxaI$a{=7i1i+H7qPz^jRjevg*-?OO# zR-_*xr#x=0YH~32uPMhE<*)&9rR*X2M#d?h30mv(7ey_Rki45#d=*QFR>7R|btQaV%f6n)L#s6nuLo`f zUhlopd#mUUsNp?}PbrGtZ_Iu7IY{!8-mZkV>z=pg0|UX#Ot9tzOu#*Tac3Xi@F0}* zbtHTpw;Z>k%f2n^QcXbxNNC91N;(oH9e1Mfl8)t)-5*NTMHR$yfO_`L7n2>k?{@4) zSD=ny)Kfrd*G^nLk@R&Yd?3@`ZoJ)i&$siww~W2>)ZL7tJxU>z^w`y7NniI}U-t?; z4(OBuRiJDm+L(|RvWb^h!^Y-P29V<;Oe_h}xulUf0lplAXC)}Mi z8~VDoL*Y$ce=*s(L{pDK$k4rm?*_g(Heq-C>i64KIFL@j2bXNU?vZ^&4$f^Z9>8llu((47? zwr^XyZKI}K4`QhNB&Rd-Q?FBk%xS=VKhhy~{6fAk$Z`}i1!&dHjbbN*>8kK&O{j(S z+YE^H6gCj(mWJiYdBTgDO$$)-==tFE_$W4EYzd@2ry+T5Ctx89kUsvY`T~rKdB5jo z;LAmsO#DFWF|DNK6$s8ReBeQ3gN;MJ2yF}AL8u4Fs;UX5(PE(J6kQgBF`h0$G4yQ+ zqWcI<4+lKEg-2EXzYQ9b@+*i>XZjP%Ggg%~H59SJFVj4rqZ{Tyje_uA^DKF;+u#!G zJ!ik*Y8huX&0mbSWqk+%GKOF!ddtgBh@rJrW5&sX!ksqvkyUy0F^77ObtrA&(X@g? zNy|D-rGI9D-G$Bc=wzIJYO$If0P&AmV^4k$->@T+*AOv>QZ6C9ByUCVW53(@=MZDY(#&!K`0G@26H=q*kMb-99gxjspk&zGb5~0vz^~SD~G6PloOd7XHKw|oz7OlQ1AJfQNy|?vrvPTx^S@$q8*+x z&l>oEAZ-oH?;xq+gl0zBU5_Bnap1WahQK|jAKj=vQ66(YiBwdME`|;WzmXix+koz=P%}E%GS+%VC*U8ERISbE&ueG~<{;vb zM(3So8nF;5a|vW8+;A~so2Kp=%$|9mX*OuYcMzVV;1!{nFu5WfhQ`x$XcC7Zt^!P5 z95}SW6f$cZAR{=Yijxz@oR#cgWD4p~9tZYU%usN&nfFhQ!_?6*q>;I^bOr{5)N_qt zW`cs|?RGn+y74*}J2g61p0(mB`ZsB96(a+1X1nC|vB1FD@Q8(U^bre5CMUJ{p81%e z(5IOJA3Lrnqwq{qFOE3KCIV!APa1@f`ePcZJBr~Tt4{*#a-^6B**`nGOySxcGBVKh zm=!U~`)FCLG7AzS^}i%o5cUEr3PFw~T`hNAEubcnW!;Ig?qu1fMA@dedlM*JhL6yl zE)+hWh$+Y5|HOAvxnu1)bVHdIr`Z8iteCE8dkzS@$A4gY{xrAslR7PZwokg#e6@?X zh9!SUJOQs5=Kq^&te_56YbEfFZK4ABXghsc{tCW-Qlx!H)u*|=u@WtAP+W>3S|k5^ zD)p;WYLy{{+G@(L^hGa}yF$}Kc*s(&qbWW{>;pLEdyX!tIUTSZ#(g$)!h@Q|%*-Tr zEVfrPIO~$39zS457D6Y4HwiAI2hQh(vH5dU!s*keK|FB1AD~zz{E6ojDK!Zgz?kYZ zYW+O5?T7T@)W`JH919`C_vm9My}peX&TCQGkk&5$F?}K)t{#hANkqgdL>B~UM<@eY zN(5!1UQA&~;4+S@-7uWoKAM7<$Q-6YR>Co_)_nj6v-@Qf1h6e7rhqgMI@I@TeW?&H?pVv`pi`=BT`S;Vp#+6Y7YE%`MG*&!rON&_4+p)Hr%n_F-=&2$56dWa z9TyLktXVn1v_;DEJ+wY&aqOmO-x>$k7OYZx_nPgj#nDZT;lOIak#z^ftRJ;iJL=ZA zbvp__8n)y)_FLAyc1Pj5yV$XD-GA8P*s%T?Yo4QKtwP$qYc0RXv0=5SWzB(Kbfazy z$E-OC4h0m~kmopLS*d|{QwppWRIWQH0Q+pZD5=!ZNDae9NeHcVa$LTnisL@CA|wWj z#)*hC?XilGWqk_*hKrK&#dUH|IU!v_NS6`PC4~9&c>x!*Ptp!f zAh`tDMRNmW7x^y}_26yLvDaAt_^vdBt|L^eQd0Rq%!CExKSDO(Is=y}^ zT0u1JW0mOwM(tj}e}ft#q+{2RlI_Fqy&-dY=w%8CC`xMkgs$t?O{ zwzpkSY>#v7eAK**N7t%lQ3b8ZjK@rHXDa+QJ%*Tx^&X|aGGQXR)2*2^o!CO_2JoOq z{iRb0^jSW!lgc-)K)i!*@I*~aw3H1}VwnevPI>abqnFTP0kMpW!cuvx1R0U(WC29S z%&~lht!rcpz?rtlq3V~#8Zw7D{rex%XwRTjQiQ00qk@SW6A4de+}RoTbgnqvbbsJ$ z#Y@FkF2bwwD*kpaxs%T7gtI#7Y)rt>GWcW1myQcR3rTlp!ri&-?n=5hCfpnE*LB_6 zcIQZ?K4&}= z-i!$RIbmWdugh#Rryd#UsFP0pW!bV>3y3nxcp_6dT3eNo5PzCUX654x)~_5}u$oC{ zL)Pa(cgs7YjcQpFel?{3;!YJ(I5%r=vz|%~nb$Oy=BfFsmU%2c`Y&J~k{=?gu*Nuf zQaPlmOyV?utdPz?;h3aT&^#3qDpn*T{t_E*;Ot1hyZmh=-vjq4P%0KQt{$8svlx># zE&n!ZPdV6VT7H{aLYRP8wov~VrSBE06*27dro$tse-!0M>GDt!GO+5OLk1P&e@vNe zI6|die4quP0hb#sq<_ChXyI>A7SMt~0%Fl$@3;qdE5)U+O)gC)iyITgjmhG+L~&a# zG_WDz-T?mgHTP9_(o4pQNiXTJCB2&y-p%*Rnr^n=0^4`fa@l4w{n!Ep&!XaWo5NGM zXd{Oz$WB8w!LHfp8I#)@zO&d=G)lJ#6e~z4w#uZz``aEDt>wqsw!z^Iv{Pg))3TR2n0o* zKLMf4=cnH3G7H4tW~yMirARKt12cU)Q58GM%Hfb=d=HX0IZjSg?KNNqCF( zXQvO@7+nrTThwn8t!haB4UwyOnvG^KYuD4MA@jP%?8fmiAWVmXFxQb^V9j6nNwFrY zt7%vh{PP^x(%iDtdMbrIk%grGNiR!kQaE_YpBwdcF1at{Ytm zPix%S8uzrOem@cVevv0_3Yr?nIaV9FcwD4$fCN8W;5CNu=37TQ8FJ1!8a z(xp? znGtl{LLbPsDC2o_A7tt|VT+9C(Yd(i==bIP$MoEeJ3Z2Sp0D)Wi5kqtQqz(A3430j z(Emsx*E7VcRela|eW;jGZ?Zh3dEh72Rm8NGXH%!0-h& zm7!mk(ka$A&%+-G$S}}h6$U9nYZUw>^$~m}Qss%&XD}S};z{8=4=5t?n_`PpZ+*Xc z>doOdhVS9-cM>9yP1oWe-CzX&jq!@kTky-+&uTWpRtADp_{~TbwI_<&Z${sYy%D=t z1P6)PIeTt}?zuOct3qqA4rh&05q)1$1g~OSSFLlPK!Fl81d)_))z1`S-j%P?{*}T_xv|+~mePz3;jA zbE5-^qQI@zx4Uk4-7DG(@d;H;sy}dAIdB{IYi(1irg~AER`u4Os%p5HOjd75RFfRI z=DRh^)w`FTTs(ATvj$|`DR#$p&%H$lR{1U4d+r`S1d(w9(_zfKCVo{WkKZoSu~YsF zdNDtqWCzH_(}iQ9_LBA8&T}-@AS45yQ>-PQ#w6+SKTWm{6f}weoEdz4X5Y)S(s*_9 z5)$fU`zek%wPIfWE`{jzzX2sh2)Vy3ty`@Q|GLu&MzgQnQM}$)?I`{TJm_ALk5Z4~ zf9=7%rN^>fh_2`i;Ae2R!Z17tgzX{QZhVJ=GllI+*nx1K63$21X$Ti6;X=f_L|DC{ zKJzzJ!S~xc!g--ey5asUZi^%h=Od;Hx7+J6PL9F-_F9?(#8juSNK08)aE0e7n6an(M+w84hrAUdy;X?>(E1JU@OLr^i)47vpI4Nvg(mD-lsN`t9EgKS zLi!jlX_*%>3X#3m#`7~X&nxX?sTkd%gHr;l;-;JkLsC99D}R6iA}bxu92gi|{&R{1 z{xwEMQYEBOnieH@Q$jZoF5jySqn+7NjbgT5E$jaoflRRh-#?5gDjvQATG@vo zQpi{1%~%5qcB8SV@p=l-4JMpQx!gQHWFw*r`L#Ar=kpEC+TV|9ha;mMTB`;3Za+$w ze}i7sv4B%1cTjRQy=v%1>szMTFW2#(^s2`T2#NUS8Oc*_!+^lDDs40(nf6hPatzi{ zG~+eN$v6fjGKMN38TU%{0lFWsdZnfnH}`o~s{C|;-~Gmx*Dv3=eCvzvZo1bfE)c|( zeMwj2U0h70+FRdwA|-FTX3_YkG3g{@x?C59-i} zTr6}qc~!Cm2~>2ic>iJe9}5p)wF)nBP z9vbA>_5hgKkEQtbiAw8ml_fQ$)<hZOFNnhusEfT?l)(CV`gzM>26NA!GMKL?aHow$z3NCyU2OA zDkb{+fz`GwSOv$(#nYz*72_I)S0<>cpmT-`6j+%o9bUS9Im0Vgy4TWx))sj4E?fVOi_a_7m!&Rjr4elfvF`ytl^GZn3w3Sm?R3s13UjzH6Dzc!xuLn0^!O7Vf6~*N z@bumaCHoE}`VK66o`^f2$X=H}Pp}>T)L?`2HL@Po(3(aP>2On^l`1kiAc>eRT$#l5 znTX2(ugE_)0L*CsSYN^eRsLlE!9@STTxz60Pk@a+0$^!-HfEXvGR<*Q7VK2eMf|(9 z>9_X`p*@~;-8A9M*0S70(5p*Fb7{3lzV#UR9ZK{cTK3@9g2R8m6E0+gUwQ{j@U)n7 zKOF$u2#T4@)|*jTB-ZfA#zemT7?kT5eV& zxMb;&HBEHqG2lI(=s&*fIT3fBVDJvXzpPjygqWE|V2wd0mN-QC<&#>2hGmAL;b~q& z@+^Ey0MI;-vfpOeRnF_*tRRlgTake^5L` zIwP!Y%)4Sxll!M^x8t8d;)=Agi2o? zRQgDOy=;dLoW)TNVYH|09}iJ@8^Yzy%l4)8p>d+VCji9dW&RmZZu9R@o1 zqgX0yShm-%*bl7P%N$QxRx5D#1_h{q>a~0hI;FD8wE_+nN|l?|TpV;u)mzqzI9N=Y zaUKqKDe7)biV9r5qX~L^q!vf|c0w%F%(a}-1qDWwhibxL*$;?yEy zkLa;S^jH!GApsoSI*#eFLn8J=#12^mcnPSZ0Jio3!1P$we;@C39!wZK7~-LXEC?|t zCS*s*!Et#AKsa53@_QxYmL#N>yaMh;ANO}&39!gqJwbr3i@QCMD@d`oVwoQHs`33U(3_=k~j-#e9 z7b;U&K!U9$#l7E9j*jflCJ0P~|t{=bEey4TWvpw$I{!?^7PEb>KV^rk5 zc%?VBoahziSEV&p-z=0tH%atw&6?Y`?AZ}_?s&wc5b(zF6|KsAh#MjFSS?~Z$sBz; zaceC$5U$)x9E3Q$u?GUw2?CLSfY6|HSU^-Rp<<^#K}E;%}uqg10&XPl9aYB5$Qn z+vt@6LHYkcdV@6w15Pu>{&xhmJxGBAd7cD+xV#6HU9{bMHxu8%Q(_ z+;c-0+fJUaG;K~)u00xziRqaO#`1IKJPn3DEmwZbpp1NvgdJf2xcnv@O&#{oKs24M z@WYbN92d9RI{g=;1>M3!Gz5q7idlR7uw&mre{16=AE7_>v7)ib956CZfNjLch`|_% z2Th6oQyQ{okP*lj7oY9;+KyGaA>bPY_cbBZsbt&!MBD!Nya&?a_9oi)zUSRXN+Qm$ zJJ$*vK1Khnm^?7`ptj22&-bksl%$;9ATkO|SDf9ej-rR4x^SO66CDU)qLaab&9P&pwtdY; zzZfWPx1)h#n9Ub_j*o>Hq?oDzZD!NDmSZRg(pR z76b7R=dTXs8DfQqDBlnR5s{M4NGb_2k&#q#hiZv98ZJ^>7^(|_92zbT)w9UTqs2Fn z;A+^5^bl7Km*5vds^L=ndhuHpY9xWwun#Tvhm3-#a*i7gr9{K_1PP(MsX3sxCYwX9H~y=Yv%J! zYlt)&O-M$3vGLYuABq(jyVMygf%{2~6gS~nV=;h?i2iGW zj2{jAiUe7{aM#(q>~1Dq=aa9TTyxlH_p_;7YGYGr67t0RWdw{4Vizp1(o!B(KjspS zJJ6#s)l~xsj1yYPNh+(z1)3kPZ~3+2tHrp4viFVNgtz}8F2vkI zo0aFP=jPUA%jN|Bc{hWOP=SaQtUVNFApYGC6+HO7>0}Z`6dCiT^#9XJ#KU!=PMTVRt4%zx@L$3ABbXarw={L(iW>JO`2NOQ~QCD$G>@@k5njDXX zLrgVm@`gy(^e;|7WX5XKfRX+&pWxCX-~y6kA7AG{E}N5OyAox);_hAWcq5$2kk^}d zSx;P19!WciY9sm&2V&K#MZ{wP##CFZlm;tG_fLF$X^X&?dl zH54lLgkh7;rmEQya|8-8LAcyDs;2h3Fw6Mkg;DD;h3IRKNIlL%tZwmH7>B5iO6I6Z-t4o~Kl@OG4;x;9yN|EA2GhN=$ zOskuKET2J+48k-IQ5j3eOhvbwri?i9 zf;r7n+`AtCN?8?M_qJzgPtw(ta5dd3Pq_Lh(uhqKo$q@p5d(FDI)AcuYod1Ro$zw) z?#1IPl`ZkImbkkmd#-7O>%T<%(pH#7T{%CwxSZyW{3XmB6WYwo4k1TMSH`5npSr%k zQF98H5!ptq=jk${V$#cp*^kK~oz56$odcx6!d8^mQ^ z!esN#v1RXGc))KcUK#?&x}o`IYqEK3qIv6bJ+28sOi{^|hMSg~&GC}9WhiR9Yp!pC zCqC9Iu1~n@uLntIr9i)CqZ+k|Yc?9o4pyaKvr&MK^8w(ufX{#0RcJPk&uOcf*Jdoo zilcU9JULSSG;7mjof+1qW?z)HHnFZVA9dB4;-{>&$>g9(DZm{LU=JH(k7t2B965UY z)4@jbMxBngPQ!ME@s$Zo794C6=>|44`TBL+tBrQ))nUeH4&bz(8{CxZ!hmHsWP{z4 z0~p6PbXXq8+J;&x;TU;RMI3^!Gev(gGWi$um3BlU8j)w{`z*a)z$@i4?y9tHK;VQ& zXtj?aMXWgug_!Qf&jC&m;*H^Yl8R~r3OV)m>D#B1y$2J$2je}1%N2(fT|f9aU=m;T zua(gy8hwfSzUAuvWc5Izdf=U%%hiXL%MQoghap66?M$|8NwjSF*pW|2sd0r27Ct>L zdzg=Gz<}(G)K`9O;OfB5+Ph^vYc379A;F3lx3J=O@z{qFem?TzZox;l>~9`+06qQ9 zdIgfR$I##FDm&z~e}C73`a?x|X;%Vi6vHWu5;Fq;@XW_RALd|giEB?Gj3wTA-@A;;y=0nsC?AZ^?Cd`)bf}dNwts zQSOgA_CJDhN6RuFjSjb%(&3u(6_jd)Us0?|B3?B{iOj^S8<%T z*}&5#)^DKHTwJD~6nMG})@J&Z%3Ve-ZQ8@B-0`TCY0sH}O9li9*p6eU$y6V1FcJrBJ+xA4; z_II4iZ3mW{o_P1Ocbg8wUToFl%Z9c#Rj>aHp^dHc(;rAB*<%<1Z6X_CEXM$>th1N^ zZ#HD^!(VCpI_!t=1Gy)k9;D?rFK4!-#bwv zNP#fW8||SmrmSV1_`SI%_Jo#P52);Wi{Q0o$|v%(L6&Wx{5t^IdWN{7E#Ya4JKKKt zEtOwFlQP-BGFd}r3~jbG^uIzo<*!ioEdW58NI=sQ`5zd5X{Q?#r1|NWP>z%)yceW& z>f@q@_(VdRFCNyzIegn8Xm~$$q={a5#it71bttX0K`k=T_o zv2FzUO|-R4l%Y)Ii%dJMOt?;_)yqqRufoVwXJLIAdi@13M`S+&FT*8&fn#8$u4Bzc zzngc8-y=`)?`lEOx&!eKE7&h4R7e!XTZ^MfQ#EN)R88!TUL_78R^?rTHgfN3LDfSz z!c@N@qze`EKODQYQu}evBaXg_a@NzJ(+|jz$SLrBRDX(9%;diy5 zW!-__^-UEHsQqpzR6gN%wIHzW!0&ohiKBkK9oIapSMbZ@*tyosnlctgyVj+4rAuaU z`HDsjIqei~DTV$A##DP0TEenUvDSYv@&jSZT+Y{?A; z8*<~B2c;RZxps!~UzSt~Vi@|sP`fdz$rK6=VZ<6$Wn5aUQJdI}+|{Hqqm@_0F-8sI zVpwaF@{F!bBa~>oYTKc7L&`KIP`V*yn$l1ME7O$WZ>Z7?`|t||n&EPk+?b{~Q-Q;g zKUJWa+Ygx=8ovvW8X0hA^epb2H{3M~-4+df>qy-+9}c$ zJbM`@QVx9>ID7g#=);`kT;@BxNvxtpvxReM69o-a9&@@NP4}iyO{@Q+-6pX{-J3k2 zd-Dp-6y9lgli`WOTl6V>NP|7?PBE_zd!dW-KEa%b2@njdb~C>0?tCcOS?xwRBe`Wy zcJ4~xpLf?o8$PUh;clck;cdPdO15uL;GcKj;cXVq4|GZlt=F=xo z+2xrEJ1&*hNSjw^rWoZK1ai^BI^D}FCzCoNfEedlx*p13P@iet3;mqfQY@}a^`E98 zHq@C#*wUcHDs-S5IkEqlnt>IhG~?3JrA1FRY5S9aL;kv18;Asn>Aq0<(t)As6+EQY z5D%Z#O>jBE&jKv+ub4H6D4|$`IZ)K*{Fs_UYP zH=A%pecag+_p}hV4{*QUv~)S{RM>a`miGMIdn;c@b!sn7XEYzx486=gr`dqQ(|Wn% z>W)_~!?miXDei2Fdzz@17|!dKr7y*uHQ9Jx>*a5u<7s&QQ;%udJOI=)pPzIdn4bg1 z93i#|!%bCt(Kqv7N4;rCn0Xim=w&l7oCG~ORYA_edM%lAM8ulGRGB$&zkUA3`I|4? zcs_2hVKPo}+GBaqOd{}j01NSf3NrJBrHC3#c~e3hLE;B_4$})@Vm8ntz{GOxdVMJw zxwnd>p@`r_cpn5$Aqk9q*J`fTTt9IAY`mmN(XV}S>B%ed(h#ZI9$h+m<+DpqJxai! zF%bk*&`*?}`6!|l(}^fxpqq^3fD{X3RA?HZq638ClQnthpcre^p_0ydRp3%daM^}t z191{uULzp9uyjHN!snKb!wLt`s=8Wrz3KWBR~nX$vh?iLNE3%A3l*AcKZYJL#>~9K z%3ckk@E|%MXxps5u?ml2PcYNpX`-$X!L95GLaX+!XRAtW#GPBS+pl4h|MSu9e>PY9 zFIgh3xWSng4L|6m2_2{osHqN)rXc`0WtL?^=Y6`eRJi5|4ib}oe}onahiNP_W(=MM zYnDP~!#Yg_)j_a6iPAtLk*506Nz%pMz?VceB}$s^mK^@p)56u=|2UFt+m~qD_pW2P z?Jy~4Z}@<8KCT_Tdh{FMwX#pV@?~$oK|$MyjI{CDgxVgDj8xcQ2Onfj^ii5rH|Uks zQCG%-F_i`NQBTQ9X*$9!p(`_;!oVg?M*A~q?>9}`ORS$*lfJ-nNLV&zwwO1A>o341 ziK&XYhS)w}2%94|h!#wd8e;9pmhp^Pr%6lJ*65LvFbS{ z?&*m;d+@%W#-yzUYgS{(R%b9|g=LSMSNA7vn%@G0kA#CWxWk&o(9AK##Yf!l-=evu zvuznq{1Clh21Be|D@kQ0p9mWLCCYzENtuUByij?Oz$&=4u^Lgi%FgZaYusAF+KufI z2@I@Ux{%4h%2j!?T1LY+x=qTQDa_1Verfqm%l|}gn1P{Np4mjc%}QRP#FeJ3+#S?Q zwm2kig@LK7pR$>$%lurOMavvpX6n*5Ei;cdgQ-h%DwB)*3MHo3zr|XL>_XrdC9q}t zH?I{HIRdMmEp$au04M8W7`RZ#NlqJZMNt6LsiJ1hMIpCT*~96PliV~=Tmv-uUOKo~ z57!Utc8kOJFb^C~_S5ho>=J@Gms+4bP zJ{$50pAA;9Wrdw2LTEABE?OP6T3qeA(*^^|Lb%ObDeqsi;diy*@VW!PYd($y511pt z1IF*V&+gc^?q;J$vWv_E$69H2k;cG9h&O+RW?(Q?I5HyqyTKBRmUgO8x!`DW{H(Z} z3(Fa|c;PPTb5K#1iRw$`(f#lEgEn}X@TN>G9htA^YG1b@&cu(%SzFQiH@5J&y7QYw2})EZNwd} z@N*e~(n&Zb1n(4sw#0Se1vtzMunz4SUluivE?VRN?Y6^3ZMqW04V5)-nL+b7Q5!v1lu{0nB7d-4*EPv&cEG<8j+8#*Fe73OvhBB8hAbaS{PXc)`vyz%hZ0^Njaf!)mclE0J^~NiQuxte{sjyEEcz>nei$`)oNfLbf0o*1FFD;3d8`@RBSsos z&&aG*a@M(!ihXe%aJM;pwOk!lmk%avDw|K)d`fa(@*!XB7|O~VRFMi-mCYxCNpi=h zyw@`$$;MbxeT1Zd+mZ^{dALG$JODL?D;YJprZ6=ZcE)I+HdtNWoIPKBW! zkb_zz7fnT3J)}i*O{peU4?{hcig82k)3ENpD4C{0^H7am`u4?<^w8;%p|fW$ zocanyfL5Vssv5kThgG8>nVHFpRMn|!PD5MVt-E~+h1S;1t?w&FQ8P1+)a+H$)(_zG z7EwrwlS#@Wb17a9$sSm#Actih-rgXQBeGZVWt$oq*mc==+Y=;P{gb1z|C&D)P=bxU za+4gmCb;{T3MoN3CI=f@!qY;c+45b~iW6#H+6}51R*Vc)#}rW#4TY+TA?oAecrl-0 z8!eiXk||155er34Q)E%ii;@Af1;!y63W-oIO{#{7|MRM;!ro7drY;t!KBdZvIIdH% zpi>hSB5JB(vKgeJsZS_*g-WKvcx3ckL87Xm=j*fgij(?PWlEtSh63W5#R6mJPA_Jp zyhw{6Qhu5-(SeSNSJb@BCYjLmF-a4tG6B=VOkg@F{Z&QN9LbYO5!f5hA<42L_xFph zCtrU%dDLE4#xYZ-3@d_Rz=gkzr$tb{ur#&<7%vScoeFTeTCXkbow5a?b9ZY1L%AJz#jpPA1AsN5nZwy{zs*_!dVZG^l_rn!Wt6sJl~pHjg7 zL0k4>E+jm3I}oy6O>lH4)D4_Tq;2rDeD;u1)LEtd*@INg@R%jfeI&YTar@79{bbil zq>Hvg;|0qvE8`NN80akc-J+zasHchBJnC&Sq$fO}7Zmp+7!_t}YsqYUS}(YnMfP{KLMh(jMJxKoG}486pD zs*LxKIyR8DVE`~LKap-^I?6CbhU-KHgkv~@%#@F%TtP!T%p(jTq!kTE6L9xT>T<)( z{eWFkP8`qo0~jh>iHYM~36hM+1zBj$DC(4A)TRc&ohas%JV2#UP%@y43=YSonQ+A+ zLN{#0m;x&j3kn6K#iK3d>x&TK2o*IMrZ9^X_EPN6Xp&*{k0!Ui#74y_Jl!v^%$`q4 zn;@EE+N=C}S?Vh~q$DSjz2fL-C%XtbM@QL5M%RkDy!(X=p`Lj4^a`AB*q46sy#k^+ zfV)u~JL0s8_D(pW%HmZOj!nKU1KU3)KikHaXQ>%}zc>t@#h3*8mf=DJa}sNPN%2Ey zhJ`pM1_VX}s!s!8>5ejbMY#QJ9ZtjUyUY+_;V!(w_!M-*oS=&FT@6{8dYzMNFZGc} zYW6S*YSt8*6o>7DYoLW}km}|2xu>~b9Qjzufa3$E?Y6N)z+k4qv|JNS)@3?ba}Cfd zP#7QEFQ{?SsSHPVoa(vyuzqmED43n=Nx9~iJbC8*4=&-d#aEJ?I{_38A~de}m4nWY z44?V%JPfPJkl?KVSjz;}iv`*N$VB(T1K|{WZDK`a#{o=(HWNOEtCnXxZ#8YSeJkdE zJ7&k&Fop)+!iXKBgT=3bSFr(W8m`AurvuRc2hCC7XZ#jCW=VCJ^9Dn6!djqvCD6SV zIJOcvR*AG%cI>b0*i&iksDdB(#ySC-YKVlR{}uKIn;5od8eup9yx^N#zXNy9pN8Nb zs@+kpGcm)fWJcJc-COnylgopI%=pT_t-xKbK{?%9=C^W$OuRZ+Df?uP%(s(!&iwA1 zBMZkGsoeImQ1+qovv5KnCa!{&Y|RyNTL^L20kVEd$oE5L)a{!KjdQYaEl}o%Yg8_O z$}O~&g<*2tpYcqPjOTI}tkVy(`gg(bY|Q0Rf6s@qc%N=6E_GVi)4S>&bz#YXA6R&nwvUss?Dzf(#vpOCGI+s}#cvFE5Do99uOmdtE!2aBBg--U+WTJB+`JRk~jg^YaYByJF;^z2+;Ed_lIBM zx-V?-)zCG015e2%sQfv1mT>6slgqHu8F1yZwVkT3YTmPz7w4|)$J{LrEPJ0orpaIN zSGl_!z>ek3VZ3SkalGY89<>DJhLIchQqTjFogZz4y{7Zar$-MFAt^S++i{n4L@{=oTEr(a) zgTFCwBmTJi5Lem<@q~$P21Hq$03NxXB3Kw6e}{M=W=jQvS7s$vbvg2ge^X!8CKW^aTcdoF*Tpjin3CRar~$U zXt4ZFkJZ9-rO0Vifns39XxOqrj08HXv9Re3E1Ji^6;=l-R%F@-@)(F#a5p&RcWrmr_^ciziO{n%ioxvlCWor(YEJsnMz zU3->JeKCCZ{95neO7Gx5`2H#UkKqTs=YA+uv3u^lDuLHiAK4Ba)mNTi;4s-}2F+)wbce=)Z>B9>!bO;$188u1fsy(wl$%&fRyuxV+jkw0wAY zxp`!r_p~&D4AG`4Pa?a1^K2K0V_=N?i%8c<*!PR00z5a|r?sTO+dzd$fvvE?#DmOA zxrTHuGC3oNYGOFv;1Z31Q{LZScad;7`fY%=ERIe(adgr-%%YmZRE}s{ zVhG9I;`HREA@75eB^?&dnc6r(u3}fd8>O@_9mg~Bzf~wr~73d`oxr=&a#j*>d?xrI? zF4p z%!g&?rt~X{+}mIvl3A1I72-D-3tKrv#b8LF;|zeHo%3T{*{uLzr@~DG`9DY+ItGDh z7&~d_o+(y)^L2kC9;PdSpKG>9-b#fArCV zKqvFKA)>&H7uv5+YVe@9sTv{e zU6t;)*188)x(A-dpy_es^{N-Ud~gR;{a6c-_WdsT_MO!zwl$HKt}4=3n@Ol?eH+%^ z=ZJ4x)wAE*RB7+6^7tM+&EAiL(Pv)xUguqk>NzBS=4tbGvBsxZvzLJexWS#os1S^g zTVI9>I+9ym67}F|hLb%rkO_tW2At3tzRb(~l|!&szOqLaN)XP>cov!u!_yg8KuCDeo8D{)E@^8U;-?t%z#W zZZk@EK)2_-;2@sYC{}9m9^4cHhq{6wj4>o-hzS!e7RE$4@2V`}l z+peePQ;YncrDsn*4n=2&D%*BGBR=miH|MQ{n{Nz#f1nbH-FSQcz4@U^G`<$?Sc!Jr zkM6_9w-(>J`R+yJaiHUd=7ucS$NEnC#`$e zT6_9bBJXaKt6#YGX;D$3~v|$;O6L#nmguLoG;HXfFd38J1 zK`gON8n=XEJX*)f7l8r$emhQe=)kDdkl|6~IgWcoI)6p>{*pBPk^~=--H*s?k4XC? zBL0fBJR)6>NcX?-&5wj5k7L_rBY>6uw%Lg_-_8}^&c(p0Z{IBcKfV(luInkm=lW?O U${ko21r9c{iE#cu2{0-C7nE=D!2kdN literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/environment.py b/venv/lib/python3.12/site-packages/alembic/runtime/environment.py new file mode 100644 index 0000000..5817e2d --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/runtime/environment.py @@ -0,0 +1,1074 @@ +from __future__ import annotations + +from typing import Any +from typing import Callable +from typing import Collection +from typing import Dict +from typing import List +from typing import Mapping +from typing import MutableMapping +from typing import Optional +from typing import overload +from typing import Sequence +from typing import TextIO +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy.sql.schema import Column +from sqlalchemy.sql.schema import FetchedValue +from typing_extensions import ContextManager +from typing_extensions import Literal + +from .migration import _ProxyTransaction +from .migration import MigrationContext +from .. import util +from ..operations import Operations +from ..script.revision import _GetRevArg + +if TYPE_CHECKING: + from sqlalchemy.engine import URL + from sqlalchemy.engine.base import Connection + from sqlalchemy.sql import Executable + from sqlalchemy.sql.schema import MetaData + from sqlalchemy.sql.schema import SchemaItem + from sqlalchemy.sql.type_api import TypeEngine + + from .migration import MigrationInfo + from ..autogenerate.api import AutogenContext + from ..config import Config + from ..ddl import DefaultImpl + from ..operations.ops import MigrationScript + from ..script.base import ScriptDirectory + +_RevNumber = Optional[Union[str, Tuple[str, ...]]] + +ProcessRevisionDirectiveFn = Callable[ + [MigrationContext, _GetRevArg, List["MigrationScript"]], None +] + +RenderItemFn = Callable[ + [str, Any, "AutogenContext"], Union[str, Literal[False]] +] + +NameFilterType = Literal[ + "schema", + "table", + "column", + "index", + "unique_constraint", + "foreign_key_constraint", +] +NameFilterParentNames = MutableMapping[ + Literal["schema_name", "table_name", "schema_qualified_table_name"], + Optional[str], +] +IncludeNameFn = Callable[ + [Optional[str], NameFilterType, NameFilterParentNames], bool +] + +IncludeObjectFn = Callable[ + [ + "SchemaItem", + Optional[str], + NameFilterType, + bool, + Optional["SchemaItem"], + ], + bool, +] + +OnVersionApplyFn = Callable[ + [MigrationContext, "MigrationInfo", Collection[Any], Mapping[str, Any]], + None, +] + +CompareServerDefault = Callable[ + [ + MigrationContext, + "Column[Any]", + "Column[Any]", + Optional[str], + Optional[FetchedValue], + Optional[str], + ], + Optional[bool], +] + +CompareType = Callable[ + [ + MigrationContext, + "Column[Any]", + "Column[Any]", + "TypeEngine[Any]", + "TypeEngine[Any]", + ], + Optional[bool], +] + + +class EnvironmentContext(util.ModuleClsProxy): + """A configurational facade made available in an ``env.py`` script. + + The :class:`.EnvironmentContext` acts as a *facade* to the more + nuts-and-bolts objects of :class:`.MigrationContext` as well as certain + aspects of :class:`.Config`, + within the context of the ``env.py`` script that is invoked by + most Alembic commands. + + :class:`.EnvironmentContext` is normally instantiated + when a command in :mod:`alembic.command` is run. It then makes + itself available in the ``alembic.context`` module for the scope + of the command. From within an ``env.py`` script, the current + :class:`.EnvironmentContext` is available by importing this module. + + :class:`.EnvironmentContext` also supports programmatic usage. + At this level, it acts as a Python context manager, that is, is + intended to be used using the + ``with:`` statement. A typical use of :class:`.EnvironmentContext`:: + + from alembic.config import Config + from alembic.script import ScriptDirectory + + config = Config() + config.set_main_option("script_location", "myapp:migrations") + script = ScriptDirectory.from_config(config) + + + def my_function(rev, context): + '''do something with revision "rev", which + will be the current database revision, + and "context", which is the MigrationContext + that the env.py will create''' + + + with EnvironmentContext( + config, + script, + fn=my_function, + as_sql=False, + starting_rev="base", + destination_rev="head", + tag="sometag", + ): + script.run_env() + + The above script will invoke the ``env.py`` script + within the migration environment. If and when ``env.py`` + calls :meth:`.MigrationContext.run_migrations`, the + ``my_function()`` function above will be called + by the :class:`.MigrationContext`, given the context + itself as well as the current revision in the database. + + .. note:: + + For most API usages other than full blown + invocation of migration scripts, the :class:`.MigrationContext` + and :class:`.ScriptDirectory` objects can be created and + used directly. The :class:`.EnvironmentContext` object + is *only* needed when you need to actually invoke the + ``env.py`` module present in the migration environment. + + """ + + _migration_context: Optional[MigrationContext] = None + + config: Config = None # type:ignore[assignment] + """An instance of :class:`.Config` representing the + configuration file contents as well as other variables + set programmatically within it.""" + + script: ScriptDirectory = None # type:ignore[assignment] + """An instance of :class:`.ScriptDirectory` which provides + programmatic access to version files within the ``versions/`` + directory. + + """ + + def __init__( + self, config: Config, script: ScriptDirectory, **kw: Any + ) -> None: + r"""Construct a new :class:`.EnvironmentContext`. + + :param config: a :class:`.Config` instance. + :param script: a :class:`.ScriptDirectory` instance. + :param \**kw: keyword options that will be ultimately + passed along to the :class:`.MigrationContext` when + :meth:`.EnvironmentContext.configure` is called. + + """ + self.config = config + self.script = script + self.context_opts = kw + + def __enter__(self) -> EnvironmentContext: + """Establish a context which provides a + :class:`.EnvironmentContext` object to + env.py scripts. + + The :class:`.EnvironmentContext` will + be made available as ``from alembic import context``. + + """ + self._install_proxy() + return self + + def __exit__(self, *arg: Any, **kw: Any) -> None: + self._remove_proxy() + + def is_offline_mode(self) -> bool: + """Return True if the current migrations environment + is running in "offline mode". + + This is ``True`` or ``False`` depending + on the ``--sql`` flag passed. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.context_opts.get("as_sql", False) # type: ignore[no-any-return] # noqa: E501 + + def is_transactional_ddl(self) -> bool: + """Return True if the context is configured to expect a + transactional DDL capable backend. + + This defaults to the type of database in use, and + can be overridden by the ``transactional_ddl`` argument + to :meth:`.configure` + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + return self.get_context().impl.transactional_ddl + + def requires_connection(self) -> bool: + return not self.is_offline_mode() + + def get_head_revision(self) -> _RevNumber: + """Return the hex identifier of the 'head' script revision. + + If the script directory has multiple heads, this + method raises a :class:`.CommandError`; + :meth:`.EnvironmentContext.get_head_revisions` should be preferred. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: :meth:`.EnvironmentContext.get_head_revisions` + + """ + return self.script.as_revision_number("head") + + def get_head_revisions(self) -> _RevNumber: + """Return the hex identifier of the 'heads' script revision(s). + + This returns a tuple containing the version number of all + heads in the script directory. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.script.as_revision_number("heads") + + def get_starting_revision_argument(self) -> _RevNumber: + """Return the 'starting revision' argument, + if the revision was passed using ``start:end``. + + This is only meaningful in "offline" mode. + Returns ``None`` if no value is available + or was configured. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + if self._migration_context is not None: + return self.script.as_revision_number( + self.get_context()._start_from_rev + ) + elif "starting_rev" in self.context_opts: + return self.script.as_revision_number( + self.context_opts["starting_rev"] + ) + else: + # this should raise only in the case that a command + # is being run where the "starting rev" is never applicable; + # this is to catch scripts which rely upon this in + # non-sql mode or similar + raise util.CommandError( + "No starting revision argument is available." + ) + + def get_revision_argument(self) -> _RevNumber: + """Get the 'destination' revision argument. + + This is typically the argument passed to the + ``upgrade`` or ``downgrade`` command. + + If it was specified as ``head``, the actual + version number is returned; if specified + as ``base``, ``None`` is returned. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + """ + return self.script.as_revision_number( + self.context_opts["destination_rev"] + ) + + def get_tag_argument(self) -> Optional[str]: + """Return the value passed for the ``--tag`` argument, if any. + + The ``--tag`` argument is not used directly by Alembic, + but is available for custom ``env.py`` configurations that + wish to use it; particularly for offline generation scripts + that wish to generate tagged filenames. + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_x_argument` - a newer and more + open ended system of extending ``env.py`` scripts via the command + line. + + """ + return self.context_opts.get("tag", None) + + @overload + def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]: ... + + @overload + def get_x_argument( + self, as_dictionary: Literal[True] + ) -> Dict[str, str]: ... + + @overload + def get_x_argument( + self, as_dictionary: bool = ... + ) -> Union[List[str], Dict[str, str]]: ... + + def get_x_argument( + self, as_dictionary: bool = False + ) -> Union[List[str], Dict[str, str]]: + """Return the value(s) passed for the ``-x`` argument, if any. + + The ``-x`` argument is an open ended flag that allows any user-defined + value or values to be passed on the command line, then available + here for consumption by a custom ``env.py`` script. + + The return value is a list, returned directly from the ``argparse`` + structure. If ``as_dictionary=True`` is passed, the ``x`` arguments + are parsed using ``key=value`` format into a dictionary that is + then returned. If there is no ``=`` in the argument, value is an empty + string. + + .. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when + arguments are passed without the ``=`` symbol. + + For example, to support passing a database URL on the command line, + the standard ``env.py`` script can be modified like this:: + + cmd_line_url = context.get_x_argument( + as_dictionary=True).get('dbname') + if cmd_line_url: + engine = create_engine(cmd_line_url) + else: + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + This then takes effect by running the ``alembic`` script as:: + + alembic -x dbname=postgresql://user:pass@host/dbname upgrade head + + This function does not require that the :class:`.MigrationContext` + has been configured. + + .. seealso:: + + :meth:`.EnvironmentContext.get_tag_argument` + + :attr:`.Config.cmd_opts` + + """ + if self.config.cmd_opts is not None: + value = self.config.cmd_opts.x or [] + else: + value = [] + if as_dictionary: + dict_value = {} + for arg in value: + x_key, _, x_value = arg.partition("=") + dict_value[x_key] = x_value + value = dict_value + + return value + + def configure( + self, + connection: Optional[Connection] = None, + url: Optional[Union[str, URL]] = None, + dialect_name: Optional[str] = None, + dialect_opts: Optional[Dict[str, Any]] = None, + transactional_ddl: Optional[bool] = None, + transaction_per_migration: bool = False, + output_buffer: Optional[TextIO] = None, + starting_rev: Optional[str] = None, + tag: Optional[str] = None, + template_args: Optional[Dict[str, Any]] = None, + render_as_batch: bool = False, + target_metadata: Union[MetaData, Sequence[MetaData], None] = None, + include_name: Optional[IncludeNameFn] = None, + include_object: Optional[IncludeObjectFn] = None, + include_schemas: bool = False, + process_revision_directives: Optional[ + ProcessRevisionDirectiveFn + ] = None, + compare_type: Union[bool, CompareType] = True, + compare_server_default: Union[bool, CompareServerDefault] = False, + render_item: Optional[RenderItemFn] = None, + literal_binds: bool = False, + upgrade_token: str = "upgrades", + downgrade_token: str = "downgrades", + alembic_module_prefix: str = "op.", + sqlalchemy_module_prefix: str = "sa.", + user_module_prefix: Optional[str] = None, + on_version_apply: Optional[OnVersionApplyFn] = None, + autogenerate_plugins: Sequence[str] | None = None, + **kw: Any, + ) -> None: + """Configure a :class:`.MigrationContext` within this + :class:`.EnvironmentContext` which will provide database + connectivity and other configuration to a series of + migration scripts. + + Many methods on :class:`.EnvironmentContext` require that + this method has been called in order to function, as they + ultimately need to have database access or at least access + to the dialect in use. Those which do are documented as such. + + The important thing needed by :meth:`.configure` is a + means to determine what kind of database dialect is in use. + An actual connection to that database is needed only if + the :class:`.MigrationContext` is to be used in + "online" mode. + + If the :meth:`.is_offline_mode` function returns ``True``, + then no connection is needed here. Otherwise, the + ``connection`` parameter should be present as an + instance of :class:`sqlalchemy.engine.Connection`. + + This function is typically called from the ``env.py`` + script within a migration environment. It can be called + multiple times for an invocation. The most recent + :class:`~sqlalchemy.engine.Connection` + for which it was called is the one that will be operated upon + by the next call to :meth:`.run_migrations`. + + General parameters: + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use + for SQL execution in "online" mode. When present, is also + used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. + The type of dialect to be used will be derived from this if + ``connection`` and ``url`` are not passed. + :param dialect_opts: dictionary of options to be passed to dialect + constructor. + :param transactional_ddl: Force the usage of "transactional" + DDL on or off; + this otherwise defaults to whether or not the dialect in + use supports it. + :param transaction_per_migration: if True, nest each migration script + in a transaction rather than the full series of migrations to + run. + :param output_buffer: a file-like object that will be used + for textual output + when the ``--sql`` option is used to generate SQL scripts. + Defaults to + ``sys.stdout`` if not passed here and also not present on + the :class:`.Config` + object. The value here overrides that of the :class:`.Config` + object. + :param output_encoding: when using ``--sql`` to generate SQL + scripts, apply this encoding to the string output. + :param literal_binds: when using ``--sql`` to generate SQL + scripts, pass through the ``literal_binds`` flag to the compiler + so that any literal values that would ordinarily be bound + parameters are converted to plain strings. + + .. warning:: Dialects can typically only handle simple datatypes + like strings and numbers for auto-literal generation. Datatypes + like dates, intervals, and others may still require manual + formatting, typically using :meth:`.Operations.inline_literal`. + + .. note:: the ``literal_binds`` flag is ignored on SQLAlchemy + versions prior to 0.8 where this feature is not supported. + + .. seealso:: + + :meth:`.Operations.inline_literal` + + :param starting_rev: Override the "starting revision" argument + when using ``--sql`` mode. + :param tag: a string tag for usage by custom ``env.py`` scripts. + Set via the ``--tag`` option, can be overridden here. + :param template_args: dictionary of template arguments which + will be added to the template argument environment when + running the "revision" command. Note that the script environment + is only run within the "revision" command if the --autogenerate + option is used, or if the option "revision_environment=true" + is present in the alembic.ini file. + + :param version_table: The name of the Alembic version table. + The default is ``'alembic_version'``. + :param version_table_schema: Optional schema to place version + table within. + :param version_table_pk: boolean, whether the Alembic version table + should use a primary key constraint for the "value" column; this + only takes effect when the table is first created. + Defaults to True; setting to False should not be necessary and is + here for backwards compatibility reasons. + :param on_version_apply: a callable or collection of callables to be + run for each migration step. + The callables will be run in the order they are given, once for + each migration step, after the respective operation has been + applied but before its transaction is finalized. + Each callable accepts no positional arguments and the following + keyword arguments: + + * ``ctx``: the :class:`.MigrationContext` running the migration, + * ``step``: a :class:`.MigrationInfo` representing the + step currently being applied, + * ``heads``: a collection of version strings representing the + current heads, + * ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`. + + Parameters specific to the autogenerate feature, when + ``alembic revision`` is run with the ``--autogenerate`` feature: + + :param target_metadata: a :class:`sqlalchemy.schema.MetaData` + object, or a sequence of :class:`~sqlalchemy.schema.MetaData` + objects, that will be consulted during autogeneration. + The tables present in each :class:`~sqlalchemy.schema.MetaData` + will be compared against + what is locally available on the target + :class:`~sqlalchemy.engine.Connection` + to produce candidate upgrade/downgrade operations. + :param compare_type: Indicates type comparison behavior during + an autogenerate + operation. Defaults to ``True`` turning on type comparison, which + has good accuracy on most backends. See :ref:`compare_types` + for an example as well as information on other type + comparison options. Set to ``False`` which disables type + comparison. A callable can also be passed to provide custom type + comparison, see :ref:`compare_types` for additional details. + + .. versionchanged:: 1.12.0 The default value of + :paramref:`.EnvironmentContext.configure.compare_type` has been + changed to ``True``. + + .. seealso:: + + :ref:`compare_types` + + :paramref:`.EnvironmentContext.configure.compare_server_default` + + :param compare_server_default: Indicates server default comparison + behavior during + an autogenerate operation. Defaults to ``False`` which disables + server default + comparison. Set to ``True`` to turn on server default comparison, + which has + varied accuracy depending on backend. + + To customize server default comparison behavior, a callable may + be specified + which can filter server default comparisons during an + autogenerate operation. + defaults during an autogenerate operation. The format of this + callable is:: + + def my_compare_server_default(context, inspected_column, + metadata_column, inspected_default, metadata_default, + rendered_metadata_default): + # return True if the defaults are different, + # False if not, or None to allow the default implementation + # to compare these defaults + return None + + context.configure( + # ... + compare_server_default = my_compare_server_default + ) + + ``inspected_column`` is a dictionary structure as returned by + :meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas + ``metadata_column`` is a :class:`sqlalchemy.schema.Column` from + the local model environment. + + A return value of ``None`` indicates to allow default server default + comparison + to proceed. Note that some backends such as Postgresql actually + execute + the two defaults on the database side to compare for equivalence. + + .. seealso:: + + :paramref:`.EnvironmentContext.configure.compare_type` + + :param include_name: A callable function which is given + the chance to return ``True`` or ``False`` for any database reflected + object based on its name, including database schema names when + the :paramref:`.EnvironmentContext.configure.include_schemas` flag + is set to ``True``. + + The function accepts the following positional arguments: + + * ``name``: the name of the object, such as schema name or table name. + Will be ``None`` when indicating the default schema name of the + database connection. + * ``type``: a string describing the type of object; currently + ``"schema"``, ``"table"``, ``"column"``, ``"index"``, + ``"unique_constraint"``, or ``"foreign_key_constraint"`` + * ``parent_names``: a dictionary of "parent" object names, that are + relative to the name being given. Keys in this dictionary may + include: ``"schema_name"``, ``"table_name"`` or + ``"schema_qualified_table_name"``. + + E.g.:: + + def include_name(name, type_, parent_names): + if type_ == "schema": + return name in ["schema_one", "schema_two"] + else: + return True + + context.configure( + # ... + include_schemas = True, + include_name = include_name + ) + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_object` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + + :param include_object: A callable function which is given + the chance to return ``True`` or ``False`` for any object, + indicating if the given object should be considered in the + autogenerate sweep. + + The function accepts the following positional arguments: + + * ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such + as a :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.Column`, + :class:`~sqlalchemy.schema.Index` + :class:`~sqlalchemy.schema.UniqueConstraint`, + or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object + * ``name``: the name of the object. This is typically available + via ``object.name``. + * ``type``: a string describing the type of object; currently + ``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``, + or ``"foreign_key_constraint"`` + * ``reflected``: ``True`` if the given object was produced based on + table reflection, ``False`` if it's from a local :class:`.MetaData` + object. + * ``compare_to``: the object being compared against, if available, + else ``None``. + + E.g.:: + + def include_object(object, name, type_, reflected, compare_to): + if (type_ == "column" and + not reflected and + object.info.get("skip_autogenerate", False)): + return False + else: + return True + + context.configure( + # ... + include_object = include_object + ) + + For the use case of omitting specific schemas from a target database + when :paramref:`.EnvironmentContext.configure.include_schemas` is + set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema` + attribute can be checked for each :class:`~sqlalchemy.schema.Table` + object passed to the hook, however it is much more efficient + to filter on schemas before reflection of objects takes place + using the :paramref:`.EnvironmentContext.configure.include_name` + hook. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_schemas` + + :param render_as_batch: if True, commands which alter elements + within a table will be placed under a ``with batch_alter_table():`` + directive, so that batch migrations will take place. + + .. seealso:: + + :ref:`batch_migrations` + + :param include_schemas: If True, autogenerate will scan across + all schemas located by the SQLAlchemy + :meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names` + method, and include all differences in tables found across all + those schemas. When using this option, you may want to also + use the :paramref:`.EnvironmentContext.configure.include_name` + parameter to specify a callable which + can filter the tables/schemas that get included. + + .. seealso:: + + :ref:`autogenerate_include_hooks` + + :paramref:`.EnvironmentContext.configure.include_name` + + :paramref:`.EnvironmentContext.configure.include_object` + + :param render_item: Callable that can be used to override how + any schema item, i.e. column, constraint, type, + etc., is rendered for autogenerate. The callable receives a + string describing the type of object, the object, and + the autogen context. If it returns False, the + default rendering method will be used. If it returns None, + the item will not be rendered in the context of a Table + construct, that is, can be used to skip columns or constraints + within op.create_table():: + + def my_render_column(type_, col, autogen_context): + if type_ == "column" and isinstance(col, MySpecialCol): + return repr(col) + else: + return False + + context.configure( + # ... + render_item = my_render_column + ) + + Available values for the type string include: ``"column"``, + ``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``, + ``"type"``, ``"server_default"``. + + .. seealso:: + + :ref:`autogen_render_types` + + :param upgrade_token: When autogenerate completes, the text of the + candidate upgrade operations will be present in this template + variable when ``script.py.mako`` is rendered. Defaults to + ``upgrades``. + :param downgrade_token: When autogenerate completes, the text of the + candidate downgrade operations will be present in this + template variable when ``script.py.mako`` is rendered. Defaults to + ``downgrades``. + + :param alembic_module_prefix: When autogenerate refers to Alembic + :mod:`alembic.operations` constructs, this prefix will be used + (i.e. ``op.create_table``) Defaults to "``op.``". + Can be ``None`` to indicate no prefix. + + :param sqlalchemy_module_prefix: When autogenerate refers to + SQLAlchemy + :class:`~sqlalchemy.schema.Column` or type classes, this prefix + will be used + (i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``". + Can be ``None`` to indicate no prefix. + Note that when dialect-specific types are rendered, autogenerate + will render them using the dialect module name, i.e. ``mssql.BIT()``, + ``postgresql.UUID()``. + + :param user_module_prefix: When autogenerate refers to a SQLAlchemy + type (e.g. :class:`.TypeEngine`) where the module name is not + under the ``sqlalchemy`` namespace, this prefix will be used + within autogenerate. If left at its default of + ``None``, the ``__module__`` attribute of the type is used to + render the import module. It's a good practice to set this + and to have all custom types be available from a fixed module space, + in order to future-proof migration files against reorganizations + in modules. + + .. seealso:: + + :ref:`autogen_module_prefix` + + :param process_revision_directives: a callable function that will + be passed a structure representing the end result of an autogenerate + or plain "revision" operation, which can be manipulated to affect + how the ``alembic revision`` command ultimately outputs new + revision scripts. The structure of the callable is:: + + def process_revision_directives(context, revision, directives): + pass + + The ``directives`` parameter is a Python list containing + a single :class:`.MigrationScript` directive, which represents + the revision file to be generated. This list as well as its + contents may be freely modified to produce any set of commands. + The section :ref:`customizing_revision` shows an example of + doing this. The ``context`` parameter is the + :class:`.MigrationContext` in use, + and ``revision`` is a tuple of revision identifiers representing the + current revision of the database. + + The callable is invoked at all times when the ``--autogenerate`` + option is passed to ``alembic revision``. If ``--autogenerate`` + is not passed, the callable is invoked only if the + ``revision_environment`` variable is set to True in the Alembic + configuration, in which case the given ``directives`` collection + will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps` + collections for ``.upgrade_ops`` and ``.downgrade_ops``. The + ``--autogenerate`` option itself can be inferred by inspecting + ``context.config.cmd_opts.autogenerate``. + + The callable function may optionally be an instance of + a :class:`.Rewriter` object. This is a helper object that + assists in the production of autogenerate-stream rewriter functions. + + .. seealso:: + + :ref:`customizing_revision` + + :ref:`autogen_rewriter` + + :paramref:`.command.revision.process_revision_directives` + + :param autogenerate_plugins: A list of string names of "plugins" that + should participate in this autogenerate run. Defaults to the list + ``["alembic.autogenerate.*"]``, which indicates that Alembic's default + autogeneration plugins will be used. + + See the section :ref:`plugins_autogenerate` for complete background + on how to use this parameter. + + .. versionadded:: 1.18.0 Added a new plugin system for autogenerate + compare directives. + + .. seealso:: + + :ref:`plugins_autogenerate` - background on enabling/disabling + autogenerate plugins + + :ref:`alembic.plugins.toplevel` - Introduction and documentation + to the plugin system + + Parameters specific to individual backends: + + :param mssql_batch_separator: The "batch separator" which will + be placed between each statement when generating offline SQL Server + migrations. Defaults to ``GO``. Note this is in addition to the + customary semicolon ``;`` at the end of each statement; SQL Server + considers the "batch separator" to denote the end of an + individual statement execution, and cannot group certain + dependent operations in one step. + :param oracle_batch_separator: The "batch separator" which will + be placed between each statement when generating offline + Oracle migrations. Defaults to ``/``. Oracle doesn't add a + semicolon between statements like most other backends. + + """ + opts = self.context_opts + if transactional_ddl is not None: + opts["transactional_ddl"] = transactional_ddl + if output_buffer is not None: + opts["output_buffer"] = output_buffer + elif self.config.output_buffer is not None: + opts["output_buffer"] = self.config.output_buffer + if starting_rev: + opts["starting_rev"] = starting_rev + if tag: + opts["tag"] = tag + if template_args and "template_args" in opts: + opts["template_args"].update(template_args) + opts["transaction_per_migration"] = transaction_per_migration + opts["target_metadata"] = target_metadata + opts["include_name"] = include_name + opts["include_object"] = include_object + opts["include_schemas"] = include_schemas + opts["render_as_batch"] = render_as_batch + opts["upgrade_token"] = upgrade_token + opts["downgrade_token"] = downgrade_token + opts["sqlalchemy_module_prefix"] = sqlalchemy_module_prefix + opts["alembic_module_prefix"] = alembic_module_prefix + opts["user_module_prefix"] = user_module_prefix + opts["literal_binds"] = literal_binds + opts["process_revision_directives"] = process_revision_directives + opts["on_version_apply"] = util.to_tuple(on_version_apply, default=()) + + if autogenerate_plugins is not None: + opts["autogenerate_plugins"] = autogenerate_plugins + + if render_item is not None: + opts["render_item"] = render_item + opts["compare_type"] = compare_type + if compare_server_default is not None: + opts["compare_server_default"] = compare_server_default + opts["script"] = self.script + + opts.update(kw) + + self._migration_context = MigrationContext.configure( + connection=connection, + url=url, + dialect_name=dialect_name, + environment_context=self, + dialect_opts=dialect_opts, + opts=opts, + ) + + def run_migrations(self, **kw: Any) -> None: + """Run migrations as determined by the current command line + configuration + as well as versioning information present (or not) in the current + database connection (if one is present). + + The function accepts optional ``**kw`` arguments. If these are + passed, they are sent directly to the ``upgrade()`` and + ``downgrade()`` + functions within each target revision file. By modifying the + ``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()`` + functions accept arguments, parameters can be passed here so that + contextual information, usually information to identify a particular + database in use, can be passed from a custom ``env.py`` script + to the migration functions. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + assert self._migration_context is not None + with Operations.context(self._migration_context): + self.get_context().run_migrations(**kw) + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, + ) -> None: + """Execute the given SQL using the current change context. + + The behavior of :meth:`.execute` is the same + as that of :meth:`.Operations.execute`. Please see that + function's documentation for full detail including + caveats and limitations. + + This function requires that a :class:`.MigrationContext` has + first been made available via :meth:`.configure`. + + """ + self.get_context().execute(sql, execution_options=execution_options) + + def static_output(self, text: str) -> None: + """Emit text directly to the "offline" SQL stream. + + Typically this is for emitting comments that + start with --. The statement is not treated + as a SQL execution, no ; or batch separator + is added, etc. + + """ + self.get_context().impl.static_output(text) + + def begin_transaction( + self, + ) -> Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]: + """Return a context manager that will + enclose an operation within a "transaction", + as defined by the environment's offline + and transactional DDL settings. + + e.g.:: + + with context.begin_transaction(): + context.run_migrations() + + :meth:`.begin_transaction` is intended to + "do the right thing" regardless of + calling context: + + * If :meth:`.is_transactional_ddl` is ``False``, + returns a "do nothing" context manager + which otherwise produces no transactional + state or directives. + * If :meth:`.is_offline_mode` is ``True``, + returns a context manager that will + invoke the :meth:`.DefaultImpl.emit_begin` + and :meth:`.DefaultImpl.emit_commit` + methods, which will produce the string + directives ``BEGIN`` and ``COMMIT`` on + the output stream, as rendered by the + target backend (e.g. SQL Server would + emit ``BEGIN TRANSACTION``). + * Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin` + on the current online connection, which + returns a :class:`sqlalchemy.engine.Transaction` + object. This object demarcates a real + transaction and is itself a context manager, + which will roll back if an exception + is raised. + + Note that a custom ``env.py`` script which + has more specific transactional needs can of course + manipulate the :class:`~sqlalchemy.engine.Connection` + directly to produce transactional state in "online" + mode. + + """ + + return self.get_context().begin_transaction() + + def get_context(self) -> MigrationContext: + """Return the current :class:`.MigrationContext` object. + + If :meth:`.EnvironmentContext.configure` has not been + called yet, raises an exception. + + """ + + if self._migration_context is None: + raise Exception("No context has been configured yet.") + return self._migration_context + + def get_bind(self) -> Connection: + """Return the current 'bind'. + + In "online" mode, this is the + :class:`sqlalchemy.engine.Connection` currently being used + to emit SQL to the database. + + This function requires that a :class:`.MigrationContext` + has first been made available via :meth:`.configure`. + + """ + return self.get_context().bind # type: ignore[return-value] + + def get_impl(self) -> DefaultImpl: + return self.get_context().impl diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/migration.py b/venv/lib/python3.12/site-packages/alembic/runtime/migration.py new file mode 100644 index 0000000..3fccf22 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/runtime/migration.py @@ -0,0 +1,1346 @@ +# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls +# mypy: no-warn-return-any, allow-any-generics + +from __future__ import annotations + +from contextlib import contextmanager +from contextlib import nullcontext +import logging +import sys +from typing import Any +from typing import Callable +from typing import cast +from typing import Collection +from typing import Dict +from typing import Iterable +from typing import Iterator +from typing import List +from typing import Optional +from typing import Set +from typing import Tuple +from typing import TYPE_CHECKING +from typing import Union + +from sqlalchemy import literal_column +from sqlalchemy import select +from sqlalchemy.engine import Engine +from sqlalchemy.engine import url as sqla_url +from sqlalchemy.engine.strategies import MockEngineStrategy +from typing_extensions import ContextManager + +from .. import ddl +from .. import util +from ..util import sqla_compat +from ..util.compat import EncodedIO + +if TYPE_CHECKING: + from sqlalchemy.engine import Dialect + from sqlalchemy.engine import URL + from sqlalchemy.engine.base import Connection + from sqlalchemy.engine.base import Transaction + from sqlalchemy.engine.mock import MockConnection + from sqlalchemy.sql import Executable + + from .environment import EnvironmentContext + from ..config import Config + from ..script.base import Script + from ..script.base import ScriptDirectory + from ..script.revision import _RevisionOrBase + from ..script.revision import Revision + from ..script.revision import RevisionMap + +log = logging.getLogger(__name__) + + +class _ProxyTransaction: + def __init__(self, migration_context: MigrationContext) -> None: + self.migration_context = migration_context + + @property + def _proxied_transaction(self) -> Optional[Transaction]: + return self.migration_context._transaction + + def rollback(self) -> None: + t = self._proxied_transaction + assert t is not None + t.rollback() + self.migration_context._transaction = None + + def commit(self) -> None: + t = self._proxied_transaction + assert t is not None + t.commit() + self.migration_context._transaction = None + + def __enter__(self) -> _ProxyTransaction: + return self + + def __exit__(self, type_: Any, value: Any, traceback: Any) -> None: + if self._proxied_transaction is not None: + self._proxied_transaction.__exit__(type_, value, traceback) + self.migration_context._transaction = None + + +class MigrationContext: + """Represent the database state made available to a migration + script. + + :class:`.MigrationContext` is the front end to an actual + database connection, or alternatively a string output + stream given a particular database dialect, + from an Alembic perspective. + + When inside the ``env.py`` script, the :class:`.MigrationContext` + is available via the + :meth:`.EnvironmentContext.get_context` method, + which is available at ``alembic.context``:: + + # from within env.py script + from alembic import context + + migration_context = context.get_context() + + For usage outside of an ``env.py`` script, such as for + utility routines that want to check the current version + in the database, the :meth:`.MigrationContext.configure` + method to create new :class:`.MigrationContext` objects. + For example, to get at the current revision in the + database using :meth:`.MigrationContext.get_current_revision`:: + + # in any application, outside of an env.py script + from alembic.migration import MigrationContext + from sqlalchemy import create_engine + + engine = create_engine("postgresql://mydatabase") + conn = engine.connect() + + context = MigrationContext.configure(conn) + current_rev = context.get_current_revision() + + The above context can also be used to produce + Alembic migration operations with an :class:`.Operations` + instance:: + + # in any application, outside of the normal Alembic environment + from alembic.operations import Operations + + op = Operations(context) + op.alter_column("mytable", "somecolumn", nullable=True) + + """ + + def __init__( + self, + dialect: Dialect, + connection: Optional[Connection], + opts: Dict[str, Any], + environment_context: Optional[EnvironmentContext] = None, + ) -> None: + self.environment_context = environment_context + self.opts = opts + self.dialect = dialect + self.script: Optional[ScriptDirectory] = opts.get("script") + as_sql: bool = opts.get("as_sql", False) + transactional_ddl = opts.get("transactional_ddl") + self._transaction_per_migration = opts.get( + "transaction_per_migration", False + ) + self.on_version_apply_callbacks = opts.get("on_version_apply", ()) + self._transaction: Optional[Transaction] = None + + if as_sql: + self.connection = cast( + Optional["Connection"], self._stdout_connection(connection) + ) + assert self.connection is not None + self._in_external_transaction = False + else: + self.connection = connection + self._in_external_transaction = ( + sqla_compat._get_connection_in_transaction(connection) + ) + + self._migrations_fn: Optional[ + Callable[..., Iterable[RevisionStep]] + ] = opts.get("fn") + self.as_sql = as_sql + + self.purge = opts.get("purge", False) + + if "output_encoding" in opts: + self.output_buffer = EncodedIO( + opts.get("output_buffer") + or sys.stdout, # type:ignore[arg-type] + opts["output_encoding"], + ) + else: + self.output_buffer = opts.get( + "output_buffer", sys.stdout + ) # type:ignore[assignment] # noqa: E501 + + self.transactional_ddl = transactional_ddl + + self._user_compare_type = opts.get("compare_type", True) + self._user_compare_server_default = opts.get( + "compare_server_default", False + ) + self.version_table = version_table = opts.get( + "version_table", "alembic_version" + ) + self.version_table_schema = version_table_schema = opts.get( + "version_table_schema", None + ) + + self._start_from_rev: Optional[str] = opts.get("starting_rev") + self.impl = ddl.DefaultImpl.get_by_dialect(dialect)( + dialect, + self.connection, + self.as_sql, + transactional_ddl, + self.output_buffer, + opts, + ) + + self._version = self.impl.version_table_impl( + version_table=version_table, + version_table_schema=version_table_schema, + version_table_pk=opts.get("version_table_pk", True), + ) + + log.info("Context impl %s.", self.impl.__class__.__name__) + if self.as_sql: + log.info("Generating static SQL") + log.info( + "Will assume %s DDL.", + ( + "transactional" + if self.impl.transactional_ddl + else "non-transactional" + ), + ) + + @classmethod + def configure( + cls, + connection: Optional[Connection] = None, + url: Optional[Union[str, URL]] = None, + dialect_name: Optional[str] = None, + dialect: Optional[Dialect] = None, + environment_context: Optional[EnvironmentContext] = None, + dialect_opts: Optional[Dict[str, str]] = None, + opts: Optional[Any] = None, + ) -> MigrationContext: + """Create a new :class:`.MigrationContext`. + + This is a factory method usually called + by :meth:`.EnvironmentContext.configure`. + + :param connection: a :class:`~sqlalchemy.engine.Connection` + to use for SQL execution in "online" mode. When present, + is also used to determine the type of dialect in use. + :param url: a string database url, or a + :class:`sqlalchemy.engine.url.URL` object. + The type of dialect to be used will be derived from this if + ``connection`` is not passed. + :param dialect_name: string name of a dialect, such as + "postgresql", "mssql", etc. The type of dialect to be used will be + derived from this if ``connection`` and ``url`` are not passed. + :param opts: dictionary of options. Most other options + accepted by :meth:`.EnvironmentContext.configure` are passed via + this dictionary. + + """ + if opts is None: + opts = {} + if dialect_opts is None: + dialect_opts = {} + + if connection: + if isinstance(connection, Engine): + raise util.CommandError( + "'connection' argument to configure() is expected " + "to be a sqlalchemy.engine.Connection instance, " + "got %r" % connection, + ) + + dialect = connection.dialect + elif url: + url_obj = sqla_url.make_url(url) + dialect = url_obj.get_dialect()(**dialect_opts) + elif dialect_name: + url_obj = sqla_url.make_url("%s://" % dialect_name) + dialect = url_obj.get_dialect()(**dialect_opts) + elif not dialect: + raise Exception("Connection, url, or dialect_name is required.") + assert dialect is not None + return MigrationContext(dialect, connection, opts, environment_context) + + @contextmanager + def autocommit_block(self) -> Iterator[None]: + """Enter an "autocommit" block, for databases that support AUTOCOMMIT + isolation levels. + + This special directive is intended to support the occasional database + DDL or system operation that specifically has to be run outside of + any kind of transaction block. The PostgreSQL database platform + is the most common target for this style of operation, as many + of its DDL operations must be run outside of transaction blocks, even + though the database overall supports transactional DDL. + + The method is used as a context manager within a migration script, by + calling on :meth:`.Operations.get_context` to retrieve the + :class:`.MigrationContext`, then invoking + :meth:`.MigrationContext.autocommit_block` using the ``with:`` + statement:: + + def upgrade(): + with op.get_context().autocommit_block(): + op.execute("ALTER TYPE mood ADD VALUE 'soso'") + + Above, a PostgreSQL "ALTER TYPE..ADD VALUE" directive is emitted, + which must be run outside of a transaction block at the database level. + The :meth:`.MigrationContext.autocommit_block` method makes use of the + SQLAlchemy ``AUTOCOMMIT`` isolation level setting, which against the + psycogp2 DBAPI corresponds to the ``connection.autocommit`` setting, + to ensure that the database driver is not inside of a DBAPI level + transaction block. + + .. warning:: + + As is necessary, **the database transaction preceding the block is + unconditionally committed**. This means that the run of migrations + preceding the operation will be committed, before the overall + migration operation is complete. + + It is recommended that when an application includes migrations with + "autocommit" blocks, that + :paramref:`.EnvironmentContext.transaction_per_migration` be used + so that the calling environment is tuned to expect short per-file + migrations whether or not one of them has an autocommit block. + + + """ + _in_connection_transaction = self._in_connection_transaction() + + if self.impl.transactional_ddl and self.as_sql: + self.impl.emit_commit() + + elif _in_connection_transaction: + assert self._transaction is not None + + self._transaction.commit() + self._transaction = None + + if not self.as_sql: + assert self.connection is not None + current_level = self.connection.get_isolation_level() + base_connection = self.connection + + # in 1.3 and 1.4 non-future mode, the connection gets switched + # out. we can use the base connection with the new mode + # except that it will not know it's in "autocommit" and will + # emit deprecation warnings when an autocommit action takes + # place. + self.connection = self.impl.connection = ( + base_connection.execution_options(isolation_level="AUTOCOMMIT") + ) + + # sqlalchemy future mode will "autobegin" in any case, so take + # control of that "transaction" here + fake_trans: Optional[Transaction] = self.connection.begin() + else: + fake_trans = None + try: + yield + finally: + if not self.as_sql: + assert self.connection is not None + if fake_trans is not None: + fake_trans.commit() + self.connection.execution_options( + isolation_level=current_level + ) + self.connection = self.impl.connection = base_connection + + if self.impl.transactional_ddl and self.as_sql: + self.impl.emit_begin() + + elif _in_connection_transaction: + assert self.connection is not None + self._transaction = self.connection.begin() + + def begin_transaction( + self, _per_migration: bool = False + ) -> Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]: + """Begin a logical transaction for migration operations. + + This method is used within an ``env.py`` script to demarcate where + the outer "transaction" for a series of migrations begins. Example:: + + def run_migrations_online(): + connectable = create_engine(...) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + Above, :meth:`.MigrationContext.begin_transaction` is used to demarcate + where the outer logical transaction occurs around the + :meth:`.MigrationContext.run_migrations` operation. + + A "Logical" transaction means that the operation may or may not + correspond to a real database transaction. If the target database + supports transactional DDL (or + :paramref:`.EnvironmentContext.configure.transactional_ddl` is true), + the :paramref:`.EnvironmentContext.configure.transaction_per_migration` + flag is not set, and the migration is against a real database + connection (as opposed to using "offline" ``--sql`` mode), a real + transaction will be started. If ``--sql`` mode is in effect, the + operation would instead correspond to a string such as "BEGIN" being + emitted to the string output. + + The returned object is a Python context manager that should only be + used in the context of a ``with:`` statement as indicated above. + The object has no other guaranteed API features present. + + .. seealso:: + + :meth:`.MigrationContext.autocommit_block` + + """ + + if self._in_external_transaction: + return nullcontext() + + if self.impl.transactional_ddl: + transaction_now = _per_migration == self._transaction_per_migration + else: + transaction_now = _per_migration is True + + if not transaction_now: + return nullcontext() + + elif not self.impl.transactional_ddl: + assert _per_migration + + if self.as_sql: + return nullcontext() + else: + # track our own notion of a "transaction block", which must be + # committed when complete. Don't rely upon whether or not the + # SQLAlchemy connection reports as "in transaction"; this + # because SQLAlchemy future connection features autobegin + # behavior, so it may already be in a transaction from our + # emitting of queries like "has_version_table", etc. While we + # could track these operations as well, that leaves open the + # possibility of new operations or other things happening in + # the user environment that still may be triggering + # "autobegin". + + in_transaction = self._transaction is not None + + if in_transaction: + return nullcontext() + else: + assert self.connection is not None + self._transaction = ( + sqla_compat._safe_begin_connection_transaction( + self.connection + ) + ) + return _ProxyTransaction(self) + elif self.as_sql: + + @contextmanager + def begin_commit(): + self.impl.emit_begin() + yield + self.impl.emit_commit() + + return begin_commit() + else: + assert self.connection is not None + self._transaction = sqla_compat._safe_begin_connection_transaction( + self.connection + ) + return _ProxyTransaction(self) + + def get_current_revision(self) -> Optional[str]: + """Return the current revision, usually that which is present + in the ``alembic_version`` table in the database. + + This method intends to be used only for a migration stream that + does not contain unmerged branches in the target database; + if there are multiple branches present, an exception is raised. + The :meth:`.MigrationContext.get_current_heads` should be preferred + over this method going forward in order to be compatible with + branch migration support. + + If this :class:`.MigrationContext` was configured in "offline" + mode, that is with ``as_sql=True``, the ``starting_rev`` + parameter is returned instead, if any. + + """ + heads = self.get_current_heads() + if len(heads) == 0: + return None + elif len(heads) > 1: + raise util.CommandError( + "Version table '%s' has more than one head present; " + "please use get_current_heads()" % self.version_table + ) + else: + return heads[0] + + def get_current_heads(self) -> Tuple[str, ...]: + """Return a tuple of the current 'head versions' that are represented + in the target database. + + For a migration stream without branches, this will be a single + value, synonymous with that of + :meth:`.MigrationContext.get_current_revision`. However when multiple + unmerged branches exist within the target database, the returned tuple + will contain a value for each head. + + If this :class:`.MigrationContext` was configured in "offline" + mode, that is with ``as_sql=True``, the ``starting_rev`` + parameter is returned in a one-length tuple. + + If no version table is present, or if there are no revisions + present, an empty tuple is returned. + + """ + if self.as_sql: + start_from_rev: Any = self._start_from_rev + if start_from_rev == "base": + start_from_rev = None + elif start_from_rev is not None and self.script: + start_from_rev = [ + self.script.get_revision(sfr).revision + for sfr in util.to_list(start_from_rev) + if sfr not in (None, "base") + ] + return util.to_tuple(start_from_rev, default=()) + else: + if self._start_from_rev: + raise util.CommandError( + "Can't specify current_rev to context " + "when using a database connection" + ) + if not self._has_version_table(): + return () + assert self.connection is not None + return tuple( + row[0] + for row in self.connection.execute( + select(self._version.c.version_num) + ) + ) + + def _ensure_version_table(self, purge: bool = False) -> None: + with sqla_compat._ensure_scope_for_ddl(self.connection): + assert self.connection is not None + self._version.create(self.connection, checkfirst=True) + if purge: + assert self.connection is not None + self.connection.execute(self._version.delete()) + + def _has_version_table(self) -> bool: + assert self.connection is not None + return sqla_compat._connectable_has_table( + self.connection, self.version_table, self.version_table_schema + ) + + def stamp(self, script_directory: ScriptDirectory, revision: str) -> None: + """Stamp the version table with a specific revision. + + This method calculates those branches to which the given revision + can apply, and updates those branches as though they were migrated + towards that revision (either up or down). If no current branches + include the revision, it is added as a new branch head. + + """ + heads = self.get_current_heads() + if not self.as_sql and not heads: + self._ensure_version_table() + head_maintainer = HeadMaintainer(self, heads) + for step in script_directory._stamp_revs(revision, heads): + head_maintainer.update_to_step(step) + + def run_migrations(self, **kw: Any) -> None: + r"""Run the migration scripts established for this + :class:`.MigrationContext`, if any. + + The commands in :mod:`alembic.command` will set up a function + that is ultimately passed to the :class:`.MigrationContext` + as the ``fn`` argument. This function represents the "work" + that will be done when :meth:`.MigrationContext.run_migrations` + is called, typically from within the ``env.py`` script of the + migration environment. The "work function" then provides an iterable + of version callables and other version information which + in the case of the ``upgrade`` or ``downgrade`` commands are the + list of version scripts to invoke. Other commands yield nothing, + in the case that a command wants to run some other operation + against the database such as the ``current`` or ``stamp`` commands. + + :param \**kw: keyword arguments here will be passed to each + migration callable, that is the ``upgrade()`` or ``downgrade()`` + method within revision scripts. + + """ + self.impl.start_migrations() + + heads: Tuple[str, ...] + if self.purge: + if self.as_sql: + raise util.CommandError("Can't use --purge with --sql mode") + self._ensure_version_table(purge=True) + heads = () + else: + heads = self.get_current_heads() + + dont_mutate = self.opts.get("dont_mutate", False) + + if not self.as_sql and not heads and not dont_mutate: + self._ensure_version_table() + + head_maintainer = HeadMaintainer(self, heads) + + assert self._migrations_fn is not None + for step in self._migrations_fn(heads, self): + with self.begin_transaction(_per_migration=True): + if self.as_sql and not head_maintainer.heads: + # for offline mode, include a CREATE TABLE from + # the base + assert self.connection is not None + self._version.create(self.connection) + log.info("Running %s", step) + if self.as_sql: + self.impl.static_output( + "-- Running %s" % (step.short_log,) + ) + step.migration_fn(**kw) + + # previously, we wouldn't stamp per migration + # if we were in a transaction, however given the more + # complex model that involves any number of inserts + # and row-targeted updates and deletes, it's simpler for now + # just to run the operations on every version + head_maintainer.update_to_step(step) + for callback in self.on_version_apply_callbacks: + callback( + ctx=self, + step=step.info, + heads=set(head_maintainer.heads), + run_args=kw, + ) + + if self.as_sql and not head_maintainer.heads: + assert self.connection is not None + self._version.drop(self.connection) + + def _in_connection_transaction(self) -> bool: + try: + meth = self.connection.in_transaction # type:ignore[union-attr] + except AttributeError: + return False + else: + return meth() + + def execute( + self, + sql: Union[Executable, str], + execution_options: Optional[Dict[str, Any]] = None, + ) -> None: + """Execute a SQL construct or string statement. + + The underlying execution mechanics are used, that is + if this is "offline mode" the SQL is written to the + output buffer, otherwise the SQL is emitted on + the current SQLAlchemy connection. + + """ + self.impl._exec(sql, execution_options) + + def _stdout_connection( + self, connection: Optional[Connection] + ) -> MockConnection: + def dump(construct, *multiparams, **params): + self.impl._exec(construct) + + return MockEngineStrategy.MockConnection(self.dialect, dump) + + @property + def bind(self) -> Optional[Connection]: + """Return the current "bind". + + In online mode, this is an instance of + :class:`sqlalchemy.engine.Connection`, and is suitable + for ad-hoc execution of any kind of usage described + in SQLAlchemy Core documentation as well as + for usage with the :meth:`sqlalchemy.schema.Table.create` + and :meth:`sqlalchemy.schema.MetaData.create_all` methods + of :class:`~sqlalchemy.schema.Table`, + :class:`~sqlalchemy.schema.MetaData`. + + Note that when "standard output" mode is enabled, + this bind will be a "mock" connection handler that cannot + return results and is only appropriate for a very limited + subset of commands. + + """ + return self.connection + + @property + def config(self) -> Optional[Config]: + """Return the :class:`.Config` used by the current environment, + if any.""" + + if self.environment_context: + return self.environment_context.config + else: + return None + + +class HeadMaintainer: + def __init__(self, context: MigrationContext, heads: Any) -> None: + self.context = context + self.heads = set(heads) + + def _insert_version(self, version: str) -> None: + assert version not in self.heads + self.heads.add(version) + + self.context.impl._exec( + self.context._version.insert().values( + version_num=literal_column("'%s'" % version) + ) + ) + + def _delete_version(self, version: str) -> None: + self.heads.remove(version) + + ret = self.context.impl._exec( + self.context._version.delete().where( + self.context._version.c.version_num + == literal_column("'%s'" % version) + ) + ) + + if ( + not self.context.as_sql + and self.context.dialect.supports_sane_rowcount + and ret is not None + and ret.rowcount != 1 + ): + raise util.CommandError( + "Online migration expected to match one " + "row when deleting '%s' in '%s'; " + "%d found" + % (version, self.context.version_table, ret.rowcount) + ) + + def _update_version(self, from_: str, to_: str) -> None: + assert to_ not in self.heads + self.heads.remove(from_) + self.heads.add(to_) + + ret = self.context.impl._exec( + self.context._version.update() + .values(version_num=literal_column("'%s'" % to_)) + .where( + self.context._version.c.version_num + == literal_column("'%s'" % from_) + ) + ) + + if ( + not self.context.as_sql + and self.context.dialect.supports_sane_rowcount + and ret is not None + and ret.rowcount != 1 + ): + raise util.CommandError( + "Online migration expected to match one " + "row when updating '%s' to '%s' in '%s'; " + "%d found" + % (from_, to_, self.context.version_table, ret.rowcount) + ) + + def update_to_step(self, step: Union[RevisionStep, StampStep]) -> None: + if step.should_delete_branch(self.heads): + vers = step.delete_version_num + log.debug("branch delete %s", vers) + self._delete_version(vers) + elif step.should_create_branch(self.heads): + vers = step.insert_version_num + log.debug("new branch insert %s", vers) + self._insert_version(vers) + elif step.should_merge_branches(self.heads): + # delete revs, update from rev, update to rev + ( + delete_revs, + update_from_rev, + update_to_rev, + ) = step.merge_branch_idents(self.heads) + log.debug( + "merge, delete %s, update %s to %s", + delete_revs, + update_from_rev, + update_to_rev, + ) + for delrev in delete_revs: + self._delete_version(delrev) + self._update_version(update_from_rev, update_to_rev) + elif step.should_unmerge_branches(self.heads): + ( + update_from_rev, + update_to_rev, + insert_revs, + ) = step.unmerge_branch_idents(self.heads) + log.debug( + "unmerge, insert %s, update %s to %s", + insert_revs, + update_from_rev, + update_to_rev, + ) + for insrev in insert_revs: + self._insert_version(insrev) + self._update_version(update_from_rev, update_to_rev) + else: + from_, to_ = step.update_version_num(self.heads) + log.debug("update %s to %s", from_, to_) + self._update_version(from_, to_) + + +class MigrationInfo: + """Exposes information about a migration step to a callback listener. + + The :class:`.MigrationInfo` object is available exclusively for the + benefit of the :paramref:`.EnvironmentContext.on_version_apply` + callback hook. + + """ + + is_upgrade: bool + """True/False: indicates whether this operation ascends or descends the + version tree.""" + + is_stamp: bool + """True/False: indicates whether this operation is a stamp (i.e. whether + it results in any actual database operations).""" + + up_revision_id: Optional[str] + """Version string corresponding to :attr:`.Revision.revision`. + + In the case of a stamp operation, it is advised to use the + :attr:`.MigrationInfo.up_revision_ids` tuple as a stamp operation can + make a single movement from one or more branches down to a single + branchpoint, in which case there will be multiple "up" revisions. + + .. seealso:: + + :attr:`.MigrationInfo.up_revision_ids` + + """ + + up_revision_ids: Tuple[str, ...] + """Tuple of version strings corresponding to :attr:`.Revision.revision`. + + In the majority of cases, this tuple will be a single value, synonymous + with the scalar value of :attr:`.MigrationInfo.up_revision_id`. + It can be multiple revision identifiers only in the case of an + ``alembic stamp`` operation which is moving downwards from multiple + branches down to their common branch point. + + """ + + down_revision_ids: Tuple[str, ...] + """Tuple of strings representing the base revisions of this migration step. + + If empty, this represents a root revision; otherwise, the first item + corresponds to :attr:`.Revision.down_revision`, and the rest are inferred + from dependencies. + """ + + revision_map: RevisionMap + """The revision map inside of which this operation occurs.""" + + def __init__( + self, + revision_map: RevisionMap, + is_upgrade: bool, + is_stamp: bool, + up_revisions: Union[str, Tuple[str, ...]], + down_revisions: Union[str, Tuple[str, ...]], + ) -> None: + self.revision_map = revision_map + self.is_upgrade = is_upgrade + self.is_stamp = is_stamp + self.up_revision_ids = util.to_tuple(up_revisions, default=()) + if self.up_revision_ids: + self.up_revision_id = self.up_revision_ids[0] + else: + # this should never be the case with + # "upgrade", "downgrade", or "stamp" as we are always + # measuring movement in terms of at least one upgrade version + self.up_revision_id = None + self.down_revision_ids = util.to_tuple(down_revisions, default=()) + + @property + def is_migration(self) -> bool: + """True/False: indicates whether this operation is a migration. + + At present this is true if and only the migration is not a stamp. + If other operation types are added in the future, both this attribute + and :attr:`~.MigrationInfo.is_stamp` will be false. + """ + return not self.is_stamp + + @property + def source_revision_ids(self) -> Tuple[str, ...]: + """Active revisions before this migration step is applied.""" + return ( + self.down_revision_ids if self.is_upgrade else self.up_revision_ids + ) + + @property + def destination_revision_ids(self) -> Tuple[str, ...]: + """Active revisions after this migration step is applied.""" + return ( + self.up_revision_ids if self.is_upgrade else self.down_revision_ids + ) + + @property + def up_revision(self) -> Optional[Revision]: + """Get :attr:`~.MigrationInfo.up_revision_id` as + a :class:`.Revision`. + + """ + return self.revision_map.get_revision(self.up_revision_id) + + @property + def up_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~.MigrationInfo.up_revision_ids` as a + :class:`.Revision`.""" + return self.revision_map.get_revisions(self.up_revision_ids) + + @property + def down_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~.MigrationInfo.down_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.down_revision_ids) + + @property + def source_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~MigrationInfo.source_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.source_revision_ids) + + @property + def destination_revisions(self) -> Tuple[Optional[_RevisionOrBase], ...]: + """Get :attr:`~MigrationInfo.destination_revision_ids` as a tuple of + :class:`Revisions <.Revision>`.""" + return self.revision_map.get_revisions(self.destination_revision_ids) + + +class MigrationStep: + from_revisions_no_deps: Tuple[str, ...] + to_revisions_no_deps: Tuple[str, ...] + is_upgrade: bool + migration_fn: Any + + if TYPE_CHECKING: + + @property + def doc(self) -> Optional[str]: ... + + @property + def name(self) -> str: + return self.migration_fn.__name__ + + @classmethod + def upgrade_from_script( + cls, revision_map: RevisionMap, script: Script + ) -> RevisionStep: + return RevisionStep(revision_map, script, True) + + @classmethod + def downgrade_from_script( + cls, revision_map: RevisionMap, script: Script + ) -> RevisionStep: + return RevisionStep(revision_map, script, False) + + @property + def is_downgrade(self) -> bool: + return not self.is_upgrade + + @property + def short_log(self) -> str: + return "%s %s -> %s" % ( + self.name, + util.format_as_comma(self.from_revisions_no_deps), + util.format_as_comma(self.to_revisions_no_deps), + ) + + def __str__(self): + if self.doc: + return "%s %s -> %s, %s" % ( + self.name, + util.format_as_comma(self.from_revisions_no_deps), + util.format_as_comma(self.to_revisions_no_deps), + self.doc, + ) + else: + return self.short_log + + +class RevisionStep(MigrationStep): + def __init__( + self, revision_map: RevisionMap, revision: Script, is_upgrade: bool + ) -> None: + self.revision_map = revision_map + self.revision = revision + self.is_upgrade = is_upgrade + if is_upgrade: + self.migration_fn = revision.module.upgrade + else: + self.migration_fn = revision.module.downgrade + + def __repr__(self): + return "RevisionStep(%r, is_upgrade=%r)" % ( + self.revision.revision, + self.is_upgrade, + ) + + def __eq__(self, other: object) -> bool: + return ( + isinstance(other, RevisionStep) + and other.revision == self.revision + and self.is_upgrade == other.is_upgrade + ) + + @property + def doc(self) -> Optional[str]: + return self.revision.doc + + @property + def from_revisions(self) -> Tuple[str, ...]: + if self.is_upgrade: + return self.revision._normalized_down_revisions + else: + return (self.revision.revision,) + + @property + def from_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + if self.is_upgrade: + return self.revision._versioned_down_revisions + else: + return (self.revision.revision,) + + @property + def to_revisions(self) -> Tuple[str, ...]: + if self.is_upgrade: + return (self.revision.revision,) + else: + return self.revision._normalized_down_revisions + + @property + def to_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + if self.is_upgrade: + return (self.revision.revision,) + else: + return self.revision._versioned_down_revisions + + @property + def _has_scalar_down_revision(self) -> bool: + return len(self.revision._normalized_down_revisions) == 1 + + def should_delete_branch(self, heads: Set[str]) -> bool: + """A delete is when we are a. in a downgrade and b. + we are going to the "base" or we are going to a version that + is implied as a dependency on another version that is remaining. + + """ + if not self.is_downgrade: + return False + + if self.revision.revision not in heads: + return False + + downrevs = self.revision._normalized_down_revisions + + if not downrevs: + # is a base + return True + else: + # determine what the ultimate "to_revisions" for an + # unmerge would be. If there are none, then we're a delete. + to_revisions = self._unmerge_to_revisions(heads) + return not to_revisions + + def merge_branch_idents( + self, heads: Set[str] + ) -> Tuple[List[str], str, str]: + other_heads = set(heads).difference(self.from_revisions) + + if other_heads: + ancestors = { + r.revision + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(other_heads), check=False + ) + } + from_revisions = list( + set(self.from_revisions).difference(ancestors) + ) + else: + from_revisions = list(self.from_revisions) + + return ( + # delete revs, update from rev, update to rev + list(from_revisions[0:-1]), + from_revisions[-1], + self.to_revisions[0], + ) + + def _unmerge_to_revisions(self, heads: Set[str]) -> Tuple[str, ...]: + other_heads = set(heads).difference([self.revision.revision]) + if other_heads: + ancestors = { + r.revision + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(other_heads), check=False + ) + } + return tuple(set(self.to_revisions).difference(ancestors)) + else: + # for each revision we plan to return, compute its ancestors + # (excluding self), and remove those from the final output since + # they are already accounted for. + ancestors = { + r.revision + for to_revision in self.to_revisions + for r in self.revision_map._get_ancestor_nodes( + self.revision_map.get_revisions(to_revision), check=False + ) + if r.revision != to_revision + } + return tuple(set(self.to_revisions).difference(ancestors)) + + def unmerge_branch_idents( + self, heads: Set[str] + ) -> Tuple[str, str, Tuple[str, ...]]: + to_revisions = self._unmerge_to_revisions(heads) + + return ( + # update from rev, update to rev, insert revs + self.from_revisions[0], + to_revisions[-1], + to_revisions[0:-1], + ) + + def should_create_branch(self, heads: Set[str]) -> bool: + if not self.is_upgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if not downrevs: + # is a base + return True + else: + # none of our downrevs are present, so... + # we have to insert our version. This is true whether + # or not there is only one downrev, or multiple (in the latter + # case, we're a merge point.) + if not heads.intersection(downrevs): + return True + else: + return False + + def should_merge_branches(self, heads: Set[str]) -> bool: + if not self.is_upgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if len(downrevs) > 1 and len(heads.intersection(downrevs)) > 1: + return True + + return False + + def should_unmerge_branches(self, heads: Set[str]) -> bool: + if not self.is_downgrade: + return False + + downrevs = self.revision._normalized_down_revisions + + if self.revision.revision in heads and len(downrevs) > 1: + return True + + return False + + def update_version_num(self, heads: Set[str]) -> Tuple[str, str]: + if not self._has_scalar_down_revision: + downrev = heads.intersection( + self.revision._normalized_down_revisions + ) + assert ( + len(downrev) == 1 + ), "Can't do an UPDATE because downrevision is ambiguous" + down_revision = list(downrev)[0] + else: + down_revision = self.revision._normalized_down_revisions[0] + + if self.is_upgrade: + return down_revision, self.revision.revision + else: + return self.revision.revision, down_revision + + @property + def delete_version_num(self) -> str: + return self.revision.revision + + @property + def insert_version_num(self) -> str: + return self.revision.revision + + @property + def info(self) -> MigrationInfo: + return MigrationInfo( + revision_map=self.revision_map, + up_revisions=self.revision.revision, + down_revisions=self.revision._normalized_down_revisions, + is_upgrade=self.is_upgrade, + is_stamp=False, + ) + + +class StampStep(MigrationStep): + def __init__( + self, + from_: Optional[Union[str, Collection[str]]], + to_: Optional[Union[str, Collection[str]]], + is_upgrade: bool, + branch_move: bool, + revision_map: Optional[RevisionMap] = None, + ) -> None: + self.from_: Tuple[str, ...] = util.to_tuple(from_, default=()) + self.to_: Tuple[str, ...] = util.to_tuple(to_, default=()) + self.is_upgrade = is_upgrade + self.branch_move = branch_move + self.migration_fn = self.stamp_revision + self.revision_map = revision_map + + doc: Optional[str] = None + + def stamp_revision(self, **kw: Any) -> None: + return None + + def __eq__(self, other): + return ( + isinstance(other, StampStep) + and other.from_revisions == self.from_revisions + and other.to_revisions == self.to_revisions + and other.branch_move == self.branch_move + and self.is_upgrade == other.is_upgrade + ) + + @property + def from_revisions(self): + return self.from_ + + @property + def to_revisions(self) -> Tuple[str, ...]: + return self.to_ + + @property + def from_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + return self.from_ + + @property + def to_revisions_no_deps( # type:ignore[override] + self, + ) -> Tuple[str, ...]: + return self.to_ + + @property + def delete_version_num(self) -> str: + assert len(self.from_) == 1 + return self.from_[0] + + @property + def insert_version_num(self) -> str: + assert len(self.to_) == 1 + return self.to_[0] + + def update_version_num(self, heads: Set[str]) -> Tuple[str, str]: + assert len(self.from_) == 1 + assert len(self.to_) == 1 + return self.from_[0], self.to_[0] + + def merge_branch_idents( + self, heads: Union[Set[str], List[str]] + ) -> Union[Tuple[List[Any], str, str], Tuple[List[str], str, str]]: + return ( + # delete revs, update from rev, update to rev + list(self.from_[0:-1]), + self.from_[-1], + self.to_[0], + ) + + def unmerge_branch_idents( + self, heads: Set[str] + ) -> Tuple[str, str, List[str]]: + return ( + # update from rev, update to rev, insert revs + self.from_[0], + self.to_[-1], + list(self.to_[0:-1]), + ) + + def should_delete_branch(self, heads: Set[str]) -> bool: + # TODO: we probably need to look for self.to_ inside of heads, + # in a similar manner as should_create_branch, however we have + # no tests for this yet (stamp downgrades w/ branches) + return self.is_downgrade and self.branch_move + + def should_create_branch(self, heads: Set[str]) -> Union[Set[str], bool]: + return ( + self.is_upgrade + and (self.branch_move or set(self.from_).difference(heads)) + and set(self.to_).difference(heads) + ) + + def should_merge_branches(self, heads: Set[str]) -> bool: + return len(self.from_) > 1 + + def should_unmerge_branches(self, heads: Set[str]) -> bool: + return len(self.to_) > 1 + + @property + def info(self) -> MigrationInfo: + up, down = ( + (self.to_, self.from_) + if self.is_upgrade + else (self.from_, self.to_) + ) + assert self.revision_map is not None + return MigrationInfo( + revision_map=self.revision_map, + up_revisions=up, + down_revisions=down, + is_upgrade=self.is_upgrade, + is_stamp=True, + ) diff --git a/venv/lib/python3.12/site-packages/alembic/runtime/plugins.py b/venv/lib/python3.12/site-packages/alembic/runtime/plugins.py new file mode 100644 index 0000000..be1d590 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/runtime/plugins.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +from importlib import metadata +import logging +import re +from types import ModuleType +from typing import Callable +from typing import Pattern +from typing import TYPE_CHECKING + +from .. import util +from ..util import DispatchPriority +from ..util import PriorityDispatcher + +if TYPE_CHECKING: + from ..util import PriorityDispatchResult + +_all_plugins = {} + + +log = logging.getLogger(__name__) + + +class Plugin: + """Describe a series of functions that are pulled in as a plugin. + + This is initially to provide for portable lists of autogenerate + comparison functions, however the setup for a plugin can run any + other kinds of global registration as well. + + .. versionadded:: 1.18.0 + + """ + + def __init__(self, name: str): + self.name = name + log.info("setup plugin %s", name) + if name in _all_plugins: + raise ValueError(f"A plugin named {name} is already registered") + _all_plugins[name] = self + self.autogenerate_comparators = PriorityDispatcher() + + def remove(self) -> None: + """remove this plugin""" + + del _all_plugins[self.name] + + def add_autogenerate_comparator( + self, + fn: Callable[..., PriorityDispatchResult], + compare_target: str, + compare_element: str | None = None, + *, + qualifier: str = "default", + priority: DispatchPriority = DispatchPriority.MEDIUM, + ) -> None: + """Register an autogenerate comparison function. + + See the section :ref:`plugins_registering_autogenerate` for detailed + examples on how to use this method. + + :param fn: The comparison function to register. The function receives + arguments specific to the type of comparison being performed and + should return a :class:`.PriorityDispatchResult` value. + + :param compare_target: The type of comparison being performed + (e.g., ``"table"``, ``"column"``, ``"type"``). + + :param compare_element: Optional sub-element being compared within + the target type. + + :param qualifier: Database dialect qualifier. Use ``"default"`` for + all dialects, or specify a dialect name like ``"postgresql"`` to + register a dialect-specific handler. Defaults to ``"default"``. + + :param priority: Execution priority for this comparison function. + Functions are executed in priority order from + :attr:`.DispatchPriority.FIRST` to :attr:`.DispatchPriority.LAST`. + Defaults to :attr:`.DispatchPriority.MEDIUM`. + + """ + self.autogenerate_comparators.dispatch_for( + compare_target, + subgroup=compare_element, + priority=priority, + qualifier=qualifier, + )(fn) + + @classmethod + def populate_autogenerate_priority_dispatch( + cls, comparators: PriorityDispatcher, include_plugins: list[str] + ) -> None: + """Populate all current autogenerate comparison functions into + a given PriorityDispatcher.""" + + exclude: set[Pattern[str]] = set() + include: dict[str, Pattern[str]] = {} + + matched_expressions: set[str] = set() + + for name in include_plugins: + if name.startswith("~"): + exclude.add(_make_re(name[1:])) + else: + include[name] = _make_re(name) + + for plugin in _all_plugins.values(): + if any(excl.match(plugin.name) for excl in exclude): + continue + + include_matches = [ + incl for incl in include if include[incl].match(plugin.name) + ] + if not include_matches: + continue + else: + matched_expressions.update(include_matches) + + log.info("setting up autogenerate plugin %s", plugin.name) + comparators.populate_with(plugin.autogenerate_comparators) + + never_matched = set(include).difference(matched_expressions) + if never_matched: + raise util.CommandError( + f"Did not locate plugins: {', '.join(never_matched)}" + ) + + @classmethod + def setup_plugin_from_module(cls, module: ModuleType, name: str) -> None: + """Call the ``setup()`` function of a plugin module, identified by + passing the module object itself. + + E.g.:: + + from alembic.runtime.plugins import Plugin + import myproject.alembic_plugin + + # Register the plugin manually + Plugin.setup_plugin_from_module( + myproject.alembic_plugin, + "myproject.custom_operations" + ) + + This will generate a new :class:`.Plugin` object with the given + name, which will register itself in the global list of plugins. + Then the module's ``setup()`` function is invoked, passing that + :class:`.Plugin` object. + + This exact process is invoked automatically at import time for any + plugin module that is published via the ``alembic.plugins`` entrypoint. + + """ + module.setup(Plugin(name)) + + +def _make_re(name: str) -> Pattern[str]: + tokens = name.split(".") + + reg = r"" + for token in tokens: + if token == "*": + reg += r"\..+?" + elif token.isidentifier(): + reg += r"\." + token + else: + raise ValueError(f"Invalid plugin expression {name!r}") + + # omit leading r'\.' + return re.compile(f"^{reg[2:]}$") + + +def _setup() -> None: + # setup third party plugins + for entrypoint in metadata.entry_points(group="alembic.plugins"): + for mod in entrypoint.load(): + Plugin.setup_plugin_from_module(mod, entrypoint.name) + + +_setup() diff --git a/venv/lib/python3.12/site-packages/alembic/script/__init__.py b/venv/lib/python3.12/site-packages/alembic/script/__init__.py new file mode 100644 index 0000000..d78f3f1 --- /dev/null +++ b/venv/lib/python3.12/site-packages/alembic/script/__init__.py @@ -0,0 +1,4 @@ +from .base import Script +from .base import ScriptDirectory + +__all__ = ["ScriptDirectory", "Script"] diff --git a/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/script/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2aea0fa76a7d2b386d348718cf718d9c057d437 GIT binary patch literal 330 zcmXw#u};G<5Qgm}v{g~Xgv7uA5>l0cTEL2!I#)<+Su7_pjj0n`wn^n~JOvC4tUL<~ zbEHa0Ol(NqI&leb!`*jxzwYdN8;ynt>}C8ca)_U8@^8T(Y)$|kQI2vfQHc|bTMmGO zBmfTcXdWb?--&x)7^E?7dN-L7+5zqT(o`6pIc2UI)wo6i|k`tEZ_mZu|icGm(IHk#qFORN`x`UyXXy!{!ZK1Td^%MryBIBgyRZXNw ziT18g)y3rOoLJ%bsb<-N&ABB^@-h_}vE5?{rJ@p!(uwxl!DXpnC%-c7ecE5yeQ@6) X#(0em*68?S_wcEBAC4dTW&oWZCA3@z literal 0 HcmV?d00001 diff --git a/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc b/venv/lib/python3.12/site-packages/alembic/script/__pycache__/base.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee7159cc7d7e650e88247ec78ab4948fba159597 GIT binary patch literal 42668 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