This commit is contained in:
@@ -1,47 +1,45 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: PyJWT
|
||||
Version: 2.8.0
|
||||
Version: 2.10.1
|
||||
Summary: JSON Web Token implementation in Python
|
||||
Home-page: https://github.com/jpadilla/pyjwt
|
||||
Author: Jose Padilla
|
||||
Author-email: hello@jpadilla.com
|
||||
Author-email: Jose Padilla <hello@jpadilla.com>
|
||||
License: MIT
|
||||
Project-URL: Homepage, https://github.com/jpadilla/pyjwt
|
||||
Keywords: json,jwt,security,signing,token,web
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Natural Language :: English
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
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: Topic :: Utilities
|
||||
Requires-Python: >=3.7
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
License-File: AUTHORS.rst
|
||||
Requires-Dist: typing-extensions ; python_version <= "3.7"
|
||||
Provides-Extra: crypto
|
||||
Requires-Dist: cryptography (>=3.4.0) ; extra == 'crypto'
|
||||
Requires-Dist: cryptography>=3.4.0; extra == "crypto"
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: sphinx (<5.0.0,>=4.5.0) ; extra == 'dev'
|
||||
Requires-Dist: sphinx-rtd-theme ; extra == 'dev'
|
||||
Requires-Dist: zope.interface ; extra == 'dev'
|
||||
Requires-Dist: cryptography (>=3.4.0) ; extra == 'dev'
|
||||
Requires-Dist: pytest (<7.0.0,>=6.0.0) ; extra == 'dev'
|
||||
Requires-Dist: coverage[toml] (==5.0.4) ; extra == 'dev'
|
||||
Requires-Dist: pre-commit ; extra == 'dev'
|
||||
Requires-Dist: coverage[toml]==5.0.4; extra == "dev"
|
||||
Requires-Dist: cryptography>=3.4.0; extra == "dev"
|
||||
Requires-Dist: pre-commit; extra == "dev"
|
||||
Requires-Dist: pytest<7.0.0,>=6.0.0; extra == "dev"
|
||||
Requires-Dist: sphinx; extra == "dev"
|
||||
Requires-Dist: sphinx-rtd-theme; extra == "dev"
|
||||
Requires-Dist: zope.interface; extra == "dev"
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: sphinx (<5.0.0,>=4.5.0) ; extra == 'docs'
|
||||
Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
|
||||
Requires-Dist: zope.interface ; extra == 'docs'
|
||||
Requires-Dist: sphinx; extra == "docs"
|
||||
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
||||
Requires-Dist: zope.interface; extra == "docs"
|
||||
Provides-Extra: tests
|
||||
Requires-Dist: pytest (<7.0.0,>=6.0.0) ; extra == 'tests'
|
||||
Requires-Dist: coverage[toml] (==5.0.4) ; extra == 'tests'
|
||||
Requires-Dist: coverage[toml]==5.0.4; extra == "tests"
|
||||
Requires-Dist: pytest<7.0.0,>=6.0.0; extra == "tests"
|
||||
|
||||
PyJWT
|
||||
=====
|
||||
@@ -63,11 +61,12 @@ A Python implementation of `RFC 7519 <https://tools.ietf.org/html/rfc7519>`_. Or
|
||||
Sponsor
|
||||
-------
|
||||
|
||||
+--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/developers <https://auth0.com/developers?utm_source=GHsponsor&utm_medium=GHsponsor&utm_campaign=pyjwt&utm_content=auth>`_. |
|
||||
+--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
.. |auth0-logo| image:: https://github.com/user-attachments/assets/ee98379e-ee76-4bcb-943a-e25c4ea6d174
|
||||
:width: 160px
|
||||
|
||||
.. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png
|
||||
+--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/signup <https://auth0.com/signup?utm_source=external_sites&utm_medium=pyjwt&utm_campaign=devn_signup>`_. |
|
||||
+--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Installing
|
||||
----------
|
||||
@@ -0,0 +1,33 @@
|
||||
PyJWT-2.10.1.dist-info/AUTHORS.rst,sha256=klzkNGECnu2_VY7At89_xLBF3vUSDruXk3xwgUBxzwc,322
|
||||
PyJWT-2.10.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
PyJWT-2.10.1.dist-info/LICENSE,sha256=eXp6ICMdTEM-nxkR2xcx0GtYKLmPSZgZoDT3wPVvXOU,1085
|
||||
PyJWT-2.10.1.dist-info/METADATA,sha256=EkewF6D6KU8SGaaQzVYfxUUU1P_gs_dp1pYTkoYvAx8,3990
|
||||
PyJWT-2.10.1.dist-info/RECORD,,
|
||||
PyJWT-2.10.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
PyJWT-2.10.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
||||
PyJWT-2.10.1.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4
|
||||
jwt/__init__.py,sha256=VB2vFKuboTjcDGeZ8r-UqK_dz3NsQSQEqySSICby8Xg,1711
|
||||
jwt/__pycache__/__init__.cpython-312.pyc,,
|
||||
jwt/__pycache__/algorithms.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jwk.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jws.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jwt.cpython-312.pyc,,
|
||||
jwt/__pycache__/exceptions.cpython-312.pyc,,
|
||||
jwt/__pycache__/help.cpython-312.pyc,,
|
||||
jwt/__pycache__/jwk_set_cache.cpython-312.pyc,,
|
||||
jwt/__pycache__/jwks_client.cpython-312.pyc,,
|
||||
jwt/__pycache__/types.cpython-312.pyc,,
|
||||
jwt/__pycache__/utils.cpython-312.pyc,,
|
||||
jwt/__pycache__/warnings.cpython-312.pyc,,
|
||||
jwt/algorithms.py,sha256=cKr-XEioe0mBtqJMCaHEswqVOA1Z8Purt5Sb3Bi-5BE,30409
|
||||
jwt/api_jwk.py,sha256=6F1r7rmm8V5qEnBKA_xMjS9R7VoANe1_BL1oD2FrAjE,4451
|
||||
jwt/api_jws.py,sha256=aM8vzqQf6mRrAw7bRy-Moj_pjWsKSVQyYK896AfMjJU,11762
|
||||
jwt/api_jwt.py,sha256=OGT4hok1l5A6FH_KdcrU5g6u6EQ8B7em0r9kGM9SYgA,14512
|
||||
jwt/exceptions.py,sha256=bUIOJ-v9tjopTLS-FYOTc3kFx5WP5IZt7ksN_HE1G9Q,1211
|
||||
jwt/help.py,sha256=vFdNzjQoAch04XCMYpCkyB2blaqHAGAqQrtf9nSPkdk,1808
|
||||
jwt/jwk_set_cache.py,sha256=hBKmN-giU7-G37L_XKgc_OZu2ah4wdbj1ZNG_GkoSE8,959
|
||||
jwt/jwks_client.py,sha256=p9b-IbQqo2tEge9Zit3oSPBFNePqwho96VLbnUrHUWs,4259
|
||||
jwt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
jwt/types.py,sha256=VnhGv_VFu5a7_mrPoSCB7HaNLrJdhM8Sq1sSfEg0gLU,99
|
||||
jwt/utils.py,sha256=hxOjvDBheBYhz-RIPiEz7Q88dSUSTMzEdKE_Ww2VdJw,3640
|
||||
jwt/warnings.py,sha256=50XWOnyNsIaqzUJTk6XHNiIDykiL763GYA92MjTKmok,59
|
||||
@@ -1,5 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.40.0)
|
||||
Generator: setuptools (75.6.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
PyJWT-2.8.0.dist-info/AUTHORS.rst,sha256=klzkNGECnu2_VY7At89_xLBF3vUSDruXk3xwgUBxzwc,322
|
||||
PyJWT-2.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
PyJWT-2.8.0.dist-info/LICENSE,sha256=eXp6ICMdTEM-nxkR2xcx0GtYKLmPSZgZoDT3wPVvXOU,1085
|
||||
PyJWT-2.8.0.dist-info/METADATA,sha256=pV2XZjvithGcVesLHWAv0J4T5t8Qc66fip2sbxwoz1o,4160
|
||||
PyJWT-2.8.0.dist-info/RECORD,,
|
||||
PyJWT-2.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
PyJWT-2.8.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
||||
PyJWT-2.8.0.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4
|
||||
jwt/__init__.py,sha256=mV9lg6n4-0xiqCKaE1eEPC9a4j6sEkEYQcKghULE7kU,1670
|
||||
jwt/__pycache__/__init__.cpython-312.pyc,,
|
||||
jwt/__pycache__/algorithms.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jwk.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jws.cpython-312.pyc,,
|
||||
jwt/__pycache__/api_jwt.cpython-312.pyc,,
|
||||
jwt/__pycache__/exceptions.cpython-312.pyc,,
|
||||
jwt/__pycache__/help.cpython-312.pyc,,
|
||||
jwt/__pycache__/jwk_set_cache.cpython-312.pyc,,
|
||||
jwt/__pycache__/jwks_client.cpython-312.pyc,,
|
||||
jwt/__pycache__/types.cpython-312.pyc,,
|
||||
jwt/__pycache__/utils.cpython-312.pyc,,
|
||||
jwt/__pycache__/warnings.cpython-312.pyc,,
|
||||
jwt/algorithms.py,sha256=RDsv5Lm3bzwsiWT3TynT7JR41R6H6s_fWUGOIqd9x_I,29800
|
||||
jwt/api_jwk.py,sha256=HPxVqgBZm7RTaEXydciNBCuYNKDYOC_prTdaN9toGbo,4196
|
||||
jwt/api_jws.py,sha256=da17RrDe0PDccTbx3rx2lLezEG_c_YGw_vVHa335IOk,11099
|
||||
jwt/api_jwt.py,sha256=yF9DwF1kt3PA5n_TiU0OmHd0LtPHfe4JCE1XOfKPjw0,12638
|
||||
jwt/exceptions.py,sha256=KDC3M7cTrpR4OQXVURlVMThem0pfANSgBxRz-ttivmo,1046
|
||||
jwt/help.py,sha256=Jrp84fG43sCwmSIaDtY08I6ZR2VE7NhrTff89tYSE40,1749
|
||||
jwt/jwk_set_cache.py,sha256=hBKmN-giU7-G37L_XKgc_OZu2ah4wdbj1ZNG_GkoSE8,959
|
||||
jwt/jwks_client.py,sha256=9W8JVyGByQgoLbBN1u5iY1_jlgfnnukeOBTpqaM_9SE,4222
|
||||
jwt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
jwt/types.py,sha256=VnhGv_VFu5a7_mrPoSCB7HaNLrJdhM8Sq1sSfEg0gLU,99
|
||||
jwt/utils.py,sha256=PAI05_8MHQCxWQTDlwN0hTtTIT2DTTZ28mm1x6-26UY,3903
|
||||
jwt/warnings.py,sha256=50XWOnyNsIaqzUJTk6XHNiIDykiL763GYA92MjTKmok,59
|
||||
@@ -1,530 +0,0 @@
|
||||
SQLAlchemy-2.0.23.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
SQLAlchemy-2.0.23.dist-info/LICENSE,sha256=2lSTeluT1aC-5eJXO8vhkzf93qCSeV_mFXLrv3tNdIU,1100
|
||||
SQLAlchemy-2.0.23.dist-info/METADATA,sha256=znDChLueFNPCOPuNix-FfY7FG6aQOCM-lQwwN-cPLQs,9551
|
||||
SQLAlchemy-2.0.23.dist-info/RECORD,,
|
||||
SQLAlchemy-2.0.23.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
SQLAlchemy-2.0.23.dist-info/WHEEL,sha256=JmQLNqDEfvnYMfsIaVeSP3fmUcYDwmF12m3QYW0c7QQ,152
|
||||
SQLAlchemy-2.0.23.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
|
||||
sqlalchemy/__init__.py,sha256=DjKCAltzrHGfaVdXVeFJpBmTaX6JmyloHANzewBUWo4,12708
|
||||
sqlalchemy/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/events.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/exc.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/inspection.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/log.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/schema.cpython-312.pyc,,
|
||||
sqlalchemy/__pycache__/types.cpython-312.pyc,,
|
||||
sqlalchemy/connectors/__init__.py,sha256=uKUYWQoXyleIyjWBuh7gzgnazJokx3DaasKJbFOfQGA,476
|
||||
sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/aioodbc.cpython-312.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/asyncio.cpython-312.pyc,,
|
||||
sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc,,
|
||||
sqlalchemy/connectors/aioodbc.py,sha256=QiafuN9bx_wcIs8tByLftTmGAegXPoFPwUaxCDU_ZQA,5737
|
||||
sqlalchemy/connectors/asyncio.py,sha256=ZZmJSFT50u-GEjZzytQOdB_tkBFxi3XPWRrNhs_nASc,6139
|
||||
sqlalchemy/connectors/pyodbc.py,sha256=NskMydn26ZkHL8aQ1V3L4WIAWin3zwJ5VEnlHvAD1DE,8453
|
||||
sqlalchemy/cyextension/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/cyextension/collections.cpython-312-x86_64-linux-gnu.so,sha256=qPSMnyXVSLYHMr_ot_ZK7yEYadhTuT8ryb6eTMFFWrM,1947440
|
||||
sqlalchemy/cyextension/collections.pyx,sha256=KDI5QTOyYz9gDl-3d7MbGMA0Kc-wxpJqnLmCaUmQy2U,12323
|
||||
sqlalchemy/cyextension/immutabledict.cpython-312-x86_64-linux-gnu.so,sha256=J9m0gK6R8PGR36jxAKx415VxX0-0fqvbQAP9-DDU1qA,811232
|
||||
sqlalchemy/cyextension/immutabledict.pxd,sha256=oc8BbnQwDg7pWAdThB-fzu8s9_ViOe1Ds-8T0r0POjI,41
|
||||
sqlalchemy/cyextension/immutabledict.pyx,sha256=aQJPZKjcqbO8jHDqpC9F-v-ew2qAjUscc5CntaheZUk,3285
|
||||
sqlalchemy/cyextension/processors.cpython-312-x86_64-linux-gnu.so,sha256=WOLcEWRNXn4UtJGhzF5B1h7JpPPfn-ziQMT0lkhobQE,533968
|
||||
sqlalchemy/cyextension/processors.pyx,sha256=0swFIBdR19x1kPRe-dijBaLW898AhH6QJizbv4ho9pk,1545
|
||||
sqlalchemy/cyextension/resultproxy.cpython-312-x86_64-linux-gnu.so,sha256=bte73oURZXuV7YvkjyGo-OjRCnSgYukqDp5KM9-Z8xY,626112
|
||||
sqlalchemy/cyextension/resultproxy.pyx,sha256=cDtMjLTdC47g7cME369NSOCck3JwG2jwZ6j25no3_gw,2477
|
||||
sqlalchemy/cyextension/util.cpython-312-x86_64-linux-gnu.so,sha256=8yMbb069NQN1b6yAsCBCMpbX94sH4iLs61vPNxd0bOg,958760
|
||||
sqlalchemy/cyextension/util.pyx,sha256=lv03p63oVn23jLhMI4_RYGewUnJfh-4FkrNMEFL7A3Y,2289
|
||||
sqlalchemy/dialects/__init__.py,sha256=hLsgIEomunlp4mNLnvjCQTLOnBVva8N7IT2-RYrN2_4,1770
|
||||
sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/_typing.py,sha256=P2ML2o4b_bWAAy3zbdoUjx3vXsMNwpiOblef8ThCxlM,648
|
||||
sqlalchemy/dialects/mssql/__init__.py,sha256=CYbbydyMSLjUq8vY1siNStd4lvjVXod8ddeDS6ELHLk,1871
|
||||
sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/json.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mssql/aioodbc.py,sha256=ncj3yyfvW91o3g19GB5s1I0oaZKUO_P-R2nwnLF0t9E,2013
|
||||
sqlalchemy/dialects/mssql/base.py,sha256=l9vX6fK6DJEYA00N9uDnvSbqfgvxXfYUn2C4AF5T920,133649
|
||||
sqlalchemy/dialects/mssql/information_schema.py,sha256=ll0zAupJ4cPvhi9v5hTi7PQLU1lae4o6eQ5Vg7gykXQ,8074
|
||||
sqlalchemy/dialects/mssql/json.py,sha256=B0m6H08CKuk-yomDHcCwfQbVuVN2WLufuVueA_qb1NQ,4573
|
||||
sqlalchemy/dialects/mssql/provision.py,sha256=x7XRSQDxz4jz2uIpqwhuIXpL9bic0Vw7Mhy39HOkyqY,5013
|
||||
sqlalchemy/dialects/mssql/pymssql.py,sha256=BfJp9t-IQabqWXySJBmP9pwNTWnJqbjA2jJM9M4XeWc,4029
|
||||
sqlalchemy/dialects/mssql/pyodbc.py,sha256=qwZ8ByOTZ1WObjxeOravoJBSBX-s4RJ_PZ5VJ_Ch5Ws,27048
|
||||
sqlalchemy/dialects/mysql/__init__.py,sha256=btLABiNnmbWt9ziW-XgVWEB1qHWQcSFz7zxZNw4m_LY,2144
|
||||
sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/mysql/aiomysql.py,sha256=Zb-_F9Pzl0t-fT1bZwbNNne6jjCUqBXxeizbhMFPqls,9750
|
||||
sqlalchemy/dialects/mysql/asyncmy.py,sha256=zqupDz7AJihjv3E8w_4XAtq95d8stdrETNx60MLNVr0,9819
|
||||
sqlalchemy/dialects/mysql/base.py,sha256=q-DzkR_txwDTeWTEByzHAoIArYU3Bb5HT2Bnmuw7WIM,120688
|
||||
sqlalchemy/dialects/mysql/cymysql.py,sha256=5CQVJAlqQ3pT4IDGSQJH2hCzj-EWjUitA21MLqJwEEs,2291
|
||||
sqlalchemy/dialects/mysql/dml.py,sha256=qw0ZweHbMsbNyVSfC17HqylCnf7XAuIjtgofiWABT8k,7636
|
||||
sqlalchemy/dialects/mysql/enumerated.py,sha256=1L2J2wT6nQEmRS4z-jzZpoi44IqIaHgBRZZB9m55czo,8439
|
||||
sqlalchemy/dialects/mysql/expression.py,sha256=WW5G2XPwqJfXjuzHBt4BRP0pCLcPJkPD1mvZX1g0JL0,4066
|
||||
sqlalchemy/dialects/mysql/json.py,sha256=JlSFBAHhJ9JmV-3azH80xkLgeh7g6A6DVyNVCNZiKPU,2260
|
||||
sqlalchemy/dialects/mysql/mariadb.py,sha256=Sugyngvo6j6SfFFuJ23rYeFWEPdZ9Ji9guElsk_1WSQ,844
|
||||
sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=F1VPosecC1hDZqjzZI29j4GUduyU4ewPwb-ekBQva5w,8725
|
||||
sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=5glmkPhD_KP-Mci8ZXBr4yzqH1MDfzCJ9F_kZNyXcGo,5666
|
||||
sqlalchemy/dialects/mysql/mysqldb.py,sha256=R5BDiXiHX5oFuAOzyxZ6TYUTGzly-dulMeQLkeia6kk,9649
|
||||
sqlalchemy/dialects/mysql/provision.py,sha256=uPT6-BIoP_12XLmWAza1TDFNhOVVJ3rmQoMH7nvh-Vg,3226
|
||||
sqlalchemy/dialects/mysql/pymysql.py,sha256=d2-00IPoyEDkR9REQTE-DGEQrGshUq_0G5liZ5FiSEM,4032
|
||||
sqlalchemy/dialects/mysql/pyodbc.py,sha256=mkOvumrxpmAi6noZlkaTVKz2F7G5vLh2vx0cZSn9VTA,4288
|
||||
sqlalchemy/dialects/mysql/reflection.py,sha256=ak6E-eCP9346ixnILYNJcrRYblWbIT0sjXf4EqmfBsY,22556
|
||||
sqlalchemy/dialects/mysql/reserved_words.py,sha256=DsPHsW3vwOrvU7bv3Nbfact2Z_jyZ9xUTT-mdeQvqxo,9145
|
||||
sqlalchemy/dialects/mysql/types.py,sha256=i8DpRkOL1QhPErZ25AmCQOuFLciWhdjNL3I0CeHEhdY,24258
|
||||
sqlalchemy/dialects/oracle/__init__.py,sha256=pjk1aWi9XFCAHWNSJzSzmoIcL32-AkU_1J9IV4PtwpA,1318
|
||||
sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/__pycache__/types.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/oracle/base.py,sha256=u55_R9NrCRijud7ioHMxT-r0MSW0gMFjOwbrDdPgFsc,118036
|
||||
sqlalchemy/dialects/oracle/cx_oracle.py,sha256=L0GvcB6xb0-zyv5dx3bpQCeptp0KSqH6g9FUQ4y-d-g,55108
|
||||
sqlalchemy/dialects/oracle/dictionary.py,sha256=iUoyFEFM8z0sfVWR2n_nnre14kaQkV_syKO0R5Dos4M,19487
|
||||
sqlalchemy/dialects/oracle/oracledb.py,sha256=_-fUQ94xai80B7v9WLVGoGDIv8u54nVspBdyGEyI76g,3457
|
||||
sqlalchemy/dialects/oracle/provision.py,sha256=5cvIc3yTWxz4AIRYxcesbRJ1Ft-zT9GauQ911yPnN2o,8055
|
||||
sqlalchemy/dialects/oracle/types.py,sha256=TeOhUW5W9qZC8SaJ-9b3u6OvOPOarNq4MmCQ7l3wWX0,8204
|
||||
sqlalchemy/dialects/postgresql/__init__.py,sha256=bZEPsLbRtB7s6TMQAHCIzKBgkxUa3eDXvCkeARua37E,3734
|
||||
sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=U3aWzbKD3VOj6Z6r-4IsIQmtjGGIB4RDZH6NXfd8Xz0,5655
|
||||
sqlalchemy/dialects/postgresql/array.py,sha256=tLyU9GDAeIypNhjTuFQUYbaTeijVM1VVJS6UdzzXXn4,13682
|
||||
sqlalchemy/dialects/postgresql/asyncpg.py,sha256=XNaoOZ5Da4-jUTaES1zEOTEW3WG8UKyVCoIS3LsFhzE,39967
|
||||
sqlalchemy/dialects/postgresql/base.py,sha256=DGhaquFJWDQL7wIvQ2EE57LxD7zGR06BKQxvNZHFLgY,175634
|
||||
sqlalchemy/dialects/postgresql/dml.py,sha256=_He69efdpDA5gGmBsE7Lo4ViSi3QnR38BiFmrR1tw6k,11203
|
||||
sqlalchemy/dialects/postgresql/ext.py,sha256=oPP22Pq-n2lMmQ8ahifYmsmzRhSiSv1RV-xrTT0gycw,16253
|
||||
sqlalchemy/dialects/postgresql/hstore.py,sha256=q5x0npbAMI8cdRFGTMwLoWFj9P1G9DUkw5OEUCfTXpI,11532
|
||||
sqlalchemy/dialects/postgresql/json.py,sha256=panGtnEbcirQDy4yR2huWydFqa_Kmv8xhpLyf-SSRWE,11203
|
||||
sqlalchemy/dialects/postgresql/named_types.py,sha256=zNoHsP3nVq5xxA7SOQ6LLDwYZEHFciZ-nDjw_I9f_G0,17092
|
||||
sqlalchemy/dialects/postgresql/operators.py,sha256=MB40xq1124OnhUzkvtbnTmxEiey0VxMOYyznF96wwhI,2799
|
||||
sqlalchemy/dialects/postgresql/pg8000.py,sha256=w6pJ3LaIKWmnwvB0Pr1aTJX5OKNtG5RNClVfkE019vU,18620
|
||||
sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=0lLnIgxfCrqkx_LNijMxo0trNLsodcd8KwretZIj4uM,8875
|
||||
sqlalchemy/dialects/postgresql/provision.py,sha256=oxyAzs8_PhuK0ChivXC3l2Nldih3_HKffvGsZqD8XWI,5509
|
||||
sqlalchemy/dialects/postgresql/psycopg.py,sha256=YMubzQHMYN1By8QJScIPb_PwNiACv6srddQ6nX6WltQ,22238
|
||||
sqlalchemy/dialects/postgresql/psycopg2.py,sha256=3Xci4bTA2BvhrZAQa727uFWdaXEZmvfD-Z-upE3NyQE,31592
|
||||
sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=2EOuDwBetfvelcPoTzSwOHe6X8lTwaYH7znNzXJt9eM,1739
|
||||
sqlalchemy/dialects/postgresql/ranges.py,sha256=yHB1BRlUreQPZB3VEn0KMMLf02zjf5jjYdmg4N4S2Sw,30220
|
||||
sqlalchemy/dialects/postgresql/types.py,sha256=l24rs8_nK4vqLyQC0aUkf4S7ecw6T_7Pgq50Icc5CBs,7292
|
||||
sqlalchemy/dialects/sqlite/__init__.py,sha256=wnZ9vtfm0QXmth1jiGiubFgRiKxIoQoNthb1bp4FhCs,1173
|
||||
sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/json.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-312.pyc,,
|
||||
sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=GZJioZLot0D5CQ6ovPQoqv2iV8FAFm3G75lEFCzopoE,12296
|
||||
sqlalchemy/dialects/sqlite/base.py,sha256=YYEB5BeuemLC3FAR7EB8vA0zoUOwHTKoF_srvnAStps,96785
|
||||
sqlalchemy/dialects/sqlite/dml.py,sha256=PYESBj8Ip7bGs_Fi7QjbWLXLnU9a-SbP96JZiUoZNHg,8434
|
||||
sqlalchemy/dialects/sqlite/json.py,sha256=XFPwSdNx0DxDfxDZn7rmGGqsAgL4vpJbjjGaA73WruQ,2533
|
||||
sqlalchemy/dialects/sqlite/provision.py,sha256=O4JDoybdb2RBblXErEVPE2P_5xHab927BQItJa203zU,5383
|
||||
sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=_JuOCoic--ehAGkCgnwUUKKTs6xYoBGag4Y_WkQUDwU,5347
|
||||
sqlalchemy/dialects/sqlite/pysqlite.py,sha256=xBg6DKqvml5cCGxVSAQxR1dcMvso8q4uyXs2m4WLzz0,27891
|
||||
sqlalchemy/dialects/type_migration_guidelines.txt,sha256=-uHNdmYFGB7bzUNT6i8M5nb4j6j9YUKAtW4lcBZqsMg,8239
|
||||
sqlalchemy/engine/__init__.py,sha256=fJCAl5P7JH9iwjuWo72_3LOIzWWhTnvXqzpAmm_T0fY,2818
|
||||
sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_row.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/_py_util.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/create.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/default.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/events.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/mock.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/processors.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/result.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/row.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/url.cpython-312.pyc,,
|
||||
sqlalchemy/engine/__pycache__/util.cpython-312.pyc,,
|
||||
sqlalchemy/engine/_py_processors.py,sha256=RSVKm9YppSBDSCEi8xvbZdRCP9EsCYfbyEg9iDCMCiI,3744
|
||||
sqlalchemy/engine/_py_row.py,sha256=Zdta0JGa7V2aV04L7nzXUEp-H1gpresKyBlneQu60pk,3549
|
||||
sqlalchemy/engine/_py_util.py,sha256=5m3MZbEqnUwP5kK_ghisFpzcXgBwSxTSkBEFB6afiD8,2245
|
||||
sqlalchemy/engine/base.py,sha256=RbIfWZ1Otyb4VzMYjDpK5BiDIE8QZwa4vQgRX0yCa28,122246
|
||||
sqlalchemy/engine/characteristics.py,sha256=YvMgrUVAt3wsSiQ0K8l44yBjFlMK3MGajxhg50t5yFM,2344
|
||||
sqlalchemy/engine/create.py,sha256=8372TLpy4FOAIZ9WmuNkx1v9DPgwpoCAH9P7LNXZCwY,32629
|
||||
sqlalchemy/engine/cursor.py,sha256=6e1Tp63r0Kt-P4pEaYR7wUew2aClTdKAEI-FoAAxJxE,74405
|
||||
sqlalchemy/engine/default.py,sha256=bi--ytxYJ0EtsCudl38owGtytnwTHX-PjlsYTFe8LpA,84065
|
||||
sqlalchemy/engine/events.py,sha256=PQyc_sbmqks6pqyN7xitO658KdKzzJWfW1TKYwEd5vo,37392
|
||||
sqlalchemy/engine/interfaces.py,sha256=pAFYR15f1Z_-qdzTYI4mAm8IYbD6maLBKbG3pBaJ8Us,112824
|
||||
sqlalchemy/engine/mock.py,sha256=ki4ud7YrUrzP2katdkxlJGFUKB2kS7cZZAHK5xWsNF8,4179
|
||||
sqlalchemy/engine/processors.py,sha256=ENN6XwndxJPW-aXPu_3NzAZsy5SvNznHoa1Qn29ERAw,2383
|
||||
sqlalchemy/engine/reflection.py,sha256=2aakNheQJNMUXZbhY8s1NtqGoGWTxM2THkJlMMfiX_s,75125
|
||||
sqlalchemy/engine/result.py,sha256=shRAsboHPTvKR38ryGgC4KLcUeVTbABSlWzAfOUKVZs,77841
|
||||
sqlalchemy/engine/row.py,sha256=doiXKaUI6s6OkfqPIwNyTPLllxJfR8HYgEI8ve9VYe0,11955
|
||||
sqlalchemy/engine/strategies.py,sha256=HjCj_FHQOgkkhhtnVmcOEuHI_cftNo3P0hN5zkhZvDc,442
|
||||
sqlalchemy/engine/url.py,sha256=_WNE7ia0JIPRc1PLY_jSA3F7bB5kp1gzuzkc5eoKviA,30694
|
||||
sqlalchemy/engine/util.py,sha256=3-ENI9S-3KLWr0GW27uWQfsvCJwMBGTKbykkKPUgiAE,5667
|
||||
sqlalchemy/event/__init__.py,sha256=CSBMp0yu5joTC6tWvx40B4p87N7oGKxC-ZLx2ULKUnQ,997
|
||||
sqlalchemy/event/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/event/__pycache__/api.cpython-312.pyc,,
|
||||
sqlalchemy/event/__pycache__/attr.cpython-312.pyc,,
|
||||
sqlalchemy/event/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/event/__pycache__/legacy.cpython-312.pyc,,
|
||||
sqlalchemy/event/__pycache__/registry.cpython-312.pyc,,
|
||||
sqlalchemy/event/api.py,sha256=nQAvPK1jrLpmu8aKCUtc-vYWcIuG-1FgAtp3GRkfIiI,8227
|
||||
sqlalchemy/event/attr.py,sha256=NMe_sPQTju2PE-f68C8TcKJGW-Gxyi1CLXumAmE368Y,20438
|
||||
sqlalchemy/event/base.py,sha256=Cr_PNJlCYJSU3rtT8DkplyjBRb-E2Wa3OAeK9woFJkk,14980
|
||||
sqlalchemy/event/legacy.py,sha256=OpPqE64xk1OYjLW1scvc6iijhoa5GZJt5f7-beWhgOc,8211
|
||||
sqlalchemy/event/registry.py,sha256=Zig9q2Galo8kO2aqr7a2rNAhmIkdJ-ntHSEcM5MfSgw,10833
|
||||
sqlalchemy/events.py,sha256=pRcPKKsPQHGPH_pvTtKRmzuEIy-QHCtkUiZl4MUbxKs,536
|
||||
sqlalchemy/exc.py,sha256=4SMKOJtz7_SWt5vskCSeXSi4ZlFyL4jh53Q8sk4-ODQ,24011
|
||||
sqlalchemy/ext/__init__.py,sha256=w4h7EpXjKPr0LD4yHa0pDCfrvleU3rrX7mgyb8RuDYQ,322
|
||||
sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/associationproxy.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/automap.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/baked.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/horizontal_shard.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/hybrid.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/indexable.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/instrumentation.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/mutable.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/orderinglist.cpython-312.pyc,,
|
||||
sqlalchemy/ext/__pycache__/serializer.cpython-312.pyc,,
|
||||
sqlalchemy/ext/associationproxy.py,sha256=5voNXWIJYGt6c8mwuSA6alm3SmEHOZ-CVK8ikgfzk8s,65960
|
||||
sqlalchemy/ext/asyncio/__init__.py,sha256=iG_0TmBO1pCB316WS-p17AImwqRtUoaKo7UphYZ7bYw,1317
|
||||
sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/engine.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/exc.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/result.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/__pycache__/session.cpython-312.pyc,,
|
||||
sqlalchemy/ext/asyncio/base.py,sha256=PXF4YqfRi2-mADAtaL2_-Uv7CzoBVojPbzyA5phJ9To,8959
|
||||
sqlalchemy/ext/asyncio/engine.py,sha256=h4pe3ixuX6YfI97B5QWo2V4_CCCnOvM_EHPZhX19Mgc,47796
|
||||
sqlalchemy/ext/asyncio/exc.py,sha256=1hCdOKzvSryc_YE4jgj0l9JASOmZXutdzShEYPiLbGI,639
|
||||
sqlalchemy/ext/asyncio/result.py,sha256=zETerVB53gql1DL6tkO_JiqeU-m1OM-8kX0ULxmoL_I,30554
|
||||
sqlalchemy/ext/asyncio/scoping.py,sha256=cBNluB7n_lwdAAo6pySbvNRqPN7UBzwQHZ6XhRDyWgA,52685
|
||||
sqlalchemy/ext/asyncio/session.py,sha256=yWwhI5i_yVWjykxmxkcP3-xmw3UpoGYNhHZL8sYXQMA,62998
|
||||
sqlalchemy/ext/automap.py,sha256=7p13-VpN0MOM525r7pmEnftedya9l5G-Ei_cFXZfpTc,61431
|
||||
sqlalchemy/ext/baked.py,sha256=R8ZAxiVN6eH50AJu0O3TtFXNE1tnRkMlSj3AvkcWFhY,17818
|
||||
sqlalchemy/ext/compiler.py,sha256=h7eR0NcPJ4F_k8YGRP3R9YX75Y9pgiVxoCjRyvceF7g,20391
|
||||
sqlalchemy/ext/declarative/__init__.py,sha256=VJu8S1efxil20W48fJlpDn6gHorOudn5p3-lF72WcJ8,1818
|
||||
sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc,,
|
||||
sqlalchemy/ext/declarative/extensions.py,sha256=vwZjudPFA_mao1U04-RZCaU_tvPMBgQa5OTmSI7K7SU,19547
|
||||
sqlalchemy/ext/horizontal_shard.py,sha256=eh14W8QWHYH22PL1l5qF_ad9Fyh1WAFjKi_vNfsme94,16766
|
||||
sqlalchemy/ext/hybrid.py,sha256=98D72WBmlileYBtEKMSNF9l-bwRavThSV8-LyB2gjo0,52499
|
||||
sqlalchemy/ext/indexable.py,sha256=RkG9BKwil-TqDjVBM14ML9c-geUrHxtRKpYkSJEwGHA,11028
|
||||
sqlalchemy/ext/instrumentation.py,sha256=rjjSbTGilYeGLdyEWV932TfTaGxiVP44_RajinANk54,15723
|
||||
sqlalchemy/ext/mutable.py,sha256=d3Pp8PcAVN4pHN9rhc1ReXBWe0Q70Q5S1klFoYGyDPA,37393
|
||||
sqlalchemy/ext/mypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/ext/mypy/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/apply.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/infer.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/names.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/plugin.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/__pycache__/util.cpython-312.pyc,,
|
||||
sqlalchemy/ext/mypy/apply.py,sha256=uUES4grydYtKykLKlxzJeBXeGe8kfWou9_rzEyEkfp0,10503
|
||||
sqlalchemy/ext/mypy/decl_class.py,sha256=Ls2Efh4kEhle6Z4VMz0GRBgGQTYs2fHr5b4DfuDj44c,17377
|
||||
sqlalchemy/ext/mypy/infer.py,sha256=si720RW6iGxMRZNP5tcaIxA1_ehFp215TzxVXaLjglU,19364
|
||||
sqlalchemy/ext/mypy/names.py,sha256=tch4f5fDmdv4AWWFzXgGZdCpxmae59XRPT02KyMvrEI,10625
|
||||
sqlalchemy/ext/mypy/plugin.py,sha256=fLXDukvZqbJ0JJCOoyZAuOniYZ_F1YT-l9gKppu8SEs,9750
|
||||
sqlalchemy/ext/mypy/util.py,sha256=TlEQq4bcs8ARLL3PoFS8Qw6oYFeMqcGnWTeJ7NsPPFk,9408
|
||||
sqlalchemy/ext/orderinglist.py,sha256=8Vcg7UUkLg-QbYAbLVDSqu-5REkR6L-FLLhCYsHYxCQ,14384
|
||||
sqlalchemy/ext/serializer.py,sha256=ox6dbMOBmFR0H2RQFt17mcYBOGKgn1cNVFfqY8-jpgQ,6178
|
||||
sqlalchemy/future/__init__.py,sha256=79DZx3v7TQZpkS_qThlmuCOm1a9UK2ObNZhyMmjfNB0,516
|
||||
sqlalchemy/future/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/future/__pycache__/engine.cpython-312.pyc,,
|
||||
sqlalchemy/future/engine.py,sha256=6uOpOedIqiT1-3qJSJIlv9_raMJU8NTkhQwN_Ngg8kI,499
|
||||
sqlalchemy/inspection.py,sha256=i3aR-IV101YU8D9TA8Pxb2wi08QZuJ34sMy6L5M__rY,5145
|
||||
sqlalchemy/log.py,sha256=aSlZ8DFHkOuI-AMmaOUUYtS9zGPadi_7tAo98QpUOiY,8634
|
||||
sqlalchemy/orm/__init__.py,sha256=cBn0aPWyDFY4ya-cHRshQBcuThk1smTUCTrlp6LHdlE,8463
|
||||
sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/collections.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/context.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/events.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/exc.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/identity.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/loading.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/properties.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/query.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/session.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/sync.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/util.cpython-312.pyc,,
|
||||
sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc,,
|
||||
sqlalchemy/orm/_orm_constructors.py,sha256=_7_GY6qw2sA-GG_WXLz1GOO-0qC-SCBeA43GhVuS2Qw,99803
|
||||
sqlalchemy/orm/_typing.py,sha256=oRUJVAGpU3_DhSkIb1anXgneweVIARjB51HlPhMNfcM,5015
|
||||
sqlalchemy/orm/attributes.py,sha256=NFhYheqqu2VcXmKTdcvQKiRR_6qo0rHLK7nda7rpviA,92578
|
||||
sqlalchemy/orm/base.py,sha256=iZXsygk4fn8wd7wx1iXn_PfnGDY7d41YRfS0mC_q5vE,27700
|
||||
sqlalchemy/orm/bulk_persistence.py,sha256=S9VK5a6GSqnw3z7O5UG5OOnc9WxzmS_ooDkA5JmCIsY,69878
|
||||
sqlalchemy/orm/clsregistry.py,sha256=4J-kKshmLOEyx3VBqREm2k_XY0cer4zwUoHJT3n5Xmw,17949
|
||||
sqlalchemy/orm/collections.py,sha256=0AZFr9us9MiHo_Xcyi7DUsN02jSBERUOd-jIK8qQ1DA,52159
|
||||
sqlalchemy/orm/context.py,sha256=VyJl1ZJ5OnJUACKlM-bPLyyoqu4tyaKKdxeC-QF4EuU,111698
|
||||
sqlalchemy/orm/decl_api.py,sha256=a2Cyvjh6j5BlXJQ2i0jpQx7xkeI_6xo5MMxr0d2ndQY,63589
|
||||
sqlalchemy/orm/decl_base.py,sha256=g9xW9G-n9iStMI0i3i-9Rt4LDRW8--3iCCRPlWF6Cko,81660
|
||||
sqlalchemy/orm/dependency.py,sha256=g3R_1H_OGzagXFeen3Irm3c1lO3yeXGdGa0muUZgZAk,47583
|
||||
sqlalchemy/orm/descriptor_props.py,sha256=SdrfVu05zhWLGe_DnBlgbU6e5sWkkfBTirH9Nrr1MLk,37176
|
||||
sqlalchemy/orm/dynamic.py,sha256=pYlMIrpp80Ex4KByqdyhx0x0kIrl_cIADwkeVxvYu4s,9798
|
||||
sqlalchemy/orm/evaluator.py,sha256=jPjVrP7XbVOG6aXTCBREq0rF3oNHLqB4XAT-gt_cpaA,11925
|
||||
sqlalchemy/orm/events.py,sha256=fGnUHwDTV9FTiifB2mmIJispwPbIT4mZongRJD7uiw4,127258
|
||||
sqlalchemy/orm/exc.py,sha256=A3wvZVs5sC5XCef4LoTUBG-UfhmliFpU9rYMdS2t_To,7356
|
||||
sqlalchemy/orm/identity.py,sha256=gRiuQSrurHGEAJXH9QGYioXL49Im5EGcYQ-IKUEpHmQ,9249
|
||||
sqlalchemy/orm/instrumentation.py,sha256=o1mTv5gCgl9d-SRvEXXjl8rzl8uBasRL3bpDgWg9P58,24337
|
||||
sqlalchemy/orm/interfaces.py,sha256=RW7bBXGWtZHY2wXFOSqtvYm6UDl7yHZUyRX_6Yd3GfQ,48395
|
||||
sqlalchemy/orm/loading.py,sha256=F1ZEHTPBglmznST2nGj_0ARccoFgTyaOOwjcqpYeuvM,57366
|
||||
sqlalchemy/orm/mapped_collection.py,sha256=ZgYHaF37yo6-gZ7Da1Gg25rMgG2GynAy-RJoDhljV5g,19698
|
||||
sqlalchemy/orm/mapper.py,sha256=kyq4pBkTvvEqlW4H4XK_ktP1sOiALNAycgvF5f-xtqw,170969
|
||||
sqlalchemy/orm/path_registry.py,sha256=olyutgn0uNB7Wi32YNQx9ZHV6sUgV3TbyGplfSxfZ6g,25938
|
||||
sqlalchemy/orm/persistence.py,sha256=qr1jUgo-NZ0tLa5eIis2271QDt4KNJwYlYU_9CaKNhQ,60545
|
||||
sqlalchemy/orm/properties.py,sha256=dt1Gy06pbRY6zgm4QGR9nU6z2WCyoTZWBJYKpUhLq_c,29095
|
||||
sqlalchemy/orm/query.py,sha256=VBSD0k15xU_XykggvLGAwGdwNglBAoBKbOk8qAoMKdI,117714
|
||||
sqlalchemy/orm/relationships.py,sha256=wrHyICb8A5qPoyxf-nITQVJ13kCNr2MedDqEY8QMSt8,127816
|
||||
sqlalchemy/orm/scoping.py,sha256=75iPEWDFhPcIXgl8EUd_sPTCL6punfegEaTRE5mP3e8,78835
|
||||
sqlalchemy/orm/session.py,sha256=TeBcZNdY4HWQFdXNCIqbsQTtkvfJkBweMzvA9p3BiPA,193279
|
||||
sqlalchemy/orm/state.py,sha256=EaWkVNWHaDeJ_FZGXHakSamUk51BXmtMWLGdFhlJmh8,37536
|
||||
sqlalchemy/orm/state_changes.py,sha256=pqkjSDOR6H5BufMKdzFUIatDp3DY90SovOJiJ1k6Ayw,6815
|
||||
sqlalchemy/orm/strategies.py,sha256=V0o-1kB1IVTxhOGqGtRyjddZqAbPdsl_h-k0N3MKCGo,114052
|
||||
sqlalchemy/orm/strategy_options.py,sha256=EmgH28uMQhwwBCDVcXmywLk_Q8AbpnK02seMsMV4nmc,84102
|
||||
sqlalchemy/orm/sync.py,sha256=5Nt_OqP4IfhAtHwFRar4dw-YjLENRLvp4d3jDC4wpnw,5749
|
||||
sqlalchemy/orm/unitofwork.py,sha256=Wk5YZocBbxe4m1wU2aFQ7gY1Cp5CROi13kDEM1iOSz4,27033
|
||||
sqlalchemy/orm/util.py,sha256=7hCRYbQjqhWJTkrPf_NXY9zF_18VWTpyguu-nfYfc6c,80340
|
||||
sqlalchemy/orm/writeonly.py,sha256=WCPXCAwHqVCfhVWXQEFCP3OocIiHgqNJ5KnuJwSgGq4,22329
|
||||
sqlalchemy/pool/__init__.py,sha256=CIv4b6ctueY7w3sML_LxyLKAdl59esYOhz3O7W5w7WE,1815
|
||||
sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/pool/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/pool/__pycache__/events.cpython-312.pyc,,
|
||||
sqlalchemy/pool/__pycache__/impl.cpython-312.pyc,,
|
||||
sqlalchemy/pool/base.py,sha256=wuwKIak5d_4-TqKI2RFN8OYMEyOvV0djnoSVR8gbxAQ,52249
|
||||
sqlalchemy/pool/events.py,sha256=IcWfORKbHM69Z9FdPJlXI7-NIhQrR9O_lg59tiUdTRU,13148
|
||||
sqlalchemy/pool/impl.py,sha256=vU0n82a7uxdE34p3hU7cvUDA5QDy9MkIv1COT4kYFP8,17724
|
||||
sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/schema.py,sha256=mt74CGCBtfv_qI1_6zzNFMexYGyWDj2Jkh-XdH4kEWI,3194
|
||||
sqlalchemy/sql/__init__.py,sha256=jAQx9rwhyPhoSjntM1BZSElJiMRmLowGThJVDGvExSU,5820
|
||||
sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_py_util.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/crud.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/dml.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/elements.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/events.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/expression.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/functions.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/naming.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/operators.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/roles.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/schema.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/util.cpython-312.pyc,,
|
||||
sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc,,
|
||||
sqlalchemy/sql/_dml_constructors.py,sha256=hoNyINY3FNi1ZQajR6lbcRN7oYsNghM1wuzzVWxIv3c,3867
|
||||
sqlalchemy/sql/_elements_constructors.py,sha256=-qksx59Gqhmzxo1xByPtZZboNvL8uYcCN14pjHYHxL8,62914
|
||||
sqlalchemy/sql/_orm_types.py,sha256=_vR3_HQYgZR_of6_ZpTQByie2gaVScxQjVAVWAP3Ztg,620
|
||||
sqlalchemy/sql/_py_util.py,sha256=iiwgX3dQhOjdB5-10jtgHPIdibUqGk49bC1qdZMBpYI,2173
|
||||
sqlalchemy/sql/_selectable_constructors.py,sha256=RDqgejqiUuU12Be1jBpMIx_YdJho8fhKfnMoJLPFTFE,18812
|
||||
sqlalchemy/sql/_typing.py,sha256=C8kNZQ3TIpM-Q12Of3tTaESB1UxIfRME_lXouqgwMT8,12252
|
||||
sqlalchemy/sql/annotation.py,sha256=pTNidcQatCar6H1I9YAoPP1e6sOewaJ15B7_-7ykZOE,18271
|
||||
sqlalchemy/sql/base.py,sha256=dVvZoPoa3pb6iuwTU4QoCvVWQPyHZthaekl5J2zV_SU,73928
|
||||
sqlalchemy/sql/cache_key.py,sha256=Dl163qHjTkMCa5LTipZud8X3w0d8DvdIvGvv4AqriHE,32823
|
||||
sqlalchemy/sql/coercions.py,sha256=ju8xEi7b9G_GzxaQ6Nwu0cFIWFZ--ottIVfdiuhHY7Y,40553
|
||||
sqlalchemy/sql/compiler.py,sha256=9Wx423H72Yq7NHR8cmMAH6GpMCJmghs1L85YJqs_Lng,268763
|
||||
sqlalchemy/sql/crud.py,sha256=nyAPlmvuyWxMqSBdWPffC5P3CGXTQKK0bJoDbNgB3iQ,56457
|
||||
sqlalchemy/sql/ddl.py,sha256=XuUhulJLvvPjU4nYD6N42QLg8rEgquD6Jwn_yIHZejk,45542
|
||||
sqlalchemy/sql/default_comparator.py,sha256=SE0OaK1BlY0RinQ21ZXJOUGkO00oGv6GMMmAH-4iNTQ,16663
|
||||
sqlalchemy/sql/dml.py,sha256=eftbzdFJgMk7NV0BHKfK4dQ2R7XsyyJn6fCgYFJ0KNQ,65728
|
||||
sqlalchemy/sql/elements.py,sha256=dsNa2K57RygsGoaWuTMPp2QQ6SU3uZXSMW6CLGBbcIY,171208
|
||||
sqlalchemy/sql/events.py,sha256=xe3vJ6pQJau3dJWBAY0zU7Lz52UKuMrpLycriLm3AWA,18301
|
||||
sqlalchemy/sql/expression.py,sha256=baMnCH04jeE8E3tA2TovXlsREocA2j3fdHKnzOB8H4U,7586
|
||||
sqlalchemy/sql/functions.py,sha256=AcI_KstJxeLw6rEXx6QnIgR2rq4Ru6RXMbq4EIIUURA,55319
|
||||
sqlalchemy/sql/lambdas.py,sha256=EfDdUBi5cSmkjz8pQCSRo858UWQCFNZxXkM-1qS0CgU,49281
|
||||
sqlalchemy/sql/naming.py,sha256=l8udFP2wvXLgehIB0uF2KXwpkXSVSREDk6fLCH9F-XY,6865
|
||||
sqlalchemy/sql/operators.py,sha256=BYATjkBQLJAmwHAlGUSV-dv9RLtGw_ziAvFbKDrN4YU,76107
|
||||
sqlalchemy/sql/roles.py,sha256=71zm_xpRkUdnu-WzG6lxQVnFHwvUjf6X6e3kRIkbzAs,7686
|
||||
sqlalchemy/sql/schema.py,sha256=TOBTbcRY6ehosJEcpYn2NX0_UGZP9lfFs-o8lJVc5tI,228104
|
||||
sqlalchemy/sql/selectable.py,sha256=9dO2yhN83zjna7nPjOE1hcvGyJGjc_lj5SAz7SP5CBQ,233041
|
||||
sqlalchemy/sql/sqltypes.py,sha256=_0FpFLH0AFueb3TIB5Vcx9nXWDNj31XFQTP0u8OXnSo,126540
|
||||
sqlalchemy/sql/traversals.py,sha256=7b98JSeLxqecmGHhhLXT_2M4QMke6W-xCci5RXndhxI,33521
|
||||
sqlalchemy/sql/type_api.py,sha256=D9Kq-ppwZvlNmxaHqvVmM8IVg4n6_erzJpVioye9WKE,83823
|
||||
sqlalchemy/sql/util.py,sha256=lBEAf_-eRepTErOBCp1PbEMZDYdJqAiK1GemQtgojYo,48175
|
||||
sqlalchemy/sql/visitors.py,sha256=KD1qOYm6RdftCufVGB8q6jFTIZIQKS3zPCg78cVV0mQ,36427
|
||||
sqlalchemy/testing/__init__.py,sha256=9M2SMxBBLJ8xLUWXNCWDzkcvOqFznWcJzrSd712vATU,3126
|
||||
sqlalchemy/testing/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertions.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/assertsql.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/asyncio.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/config.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/engines.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/entities.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/exclusions.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/pickleable.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/profiling.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/provision.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/requirements.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/schema.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/util.cpython-312.pyc,,
|
||||
sqlalchemy/testing/__pycache__/warnings.cpython-312.pyc,,
|
||||
sqlalchemy/testing/assertions.py,sha256=lNNZ-gfF4TDRXmB7hZDdch7JYZRb_qWGeqWDFKtopx0,31439
|
||||
sqlalchemy/testing/assertsql.py,sha256=EIVk3i5qjiSI63c1ikTPoGhulZl88SSeOS2VNo1LJvM,16817
|
||||
sqlalchemy/testing/asyncio.py,sha256=cAw68tzu3h5wjdIKfOqhFATcbMb38XeK0ThjIalUHuQ,3728
|
||||
sqlalchemy/testing/config.py,sha256=MZOWz7wqzc1pbwHWSAR0RJkt2C-SD6ox-nYY7VHdi_U,12030
|
||||
sqlalchemy/testing/engines.py,sha256=w5-0FbanItRsOt6x4n7wM_OnToCzJnrvZZ2hk5Yzng8,13355
|
||||
sqlalchemy/testing/entities.py,sha256=rysywsnjXHlIIC-uv0L7-fLmTAuNpHJvcSd1HeAdY5M,3354
|
||||
sqlalchemy/testing/exclusions.py,sha256=uoYLEwyNOK1eR8rpfOZ2Q3dxgY0akM-RtsIFML-FPrY,12444
|
||||
sqlalchemy/testing/fixtures/__init__.py,sha256=9snVns5A7g28LqC6gqQuO4xRBoJzdnf068GQ6Cae75I,1198
|
||||
sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/base.cpython-312.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-312.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/orm.cpython-312.pyc,,
|
||||
sqlalchemy/testing/fixtures/__pycache__/sql.cpython-312.pyc,,
|
||||
sqlalchemy/testing/fixtures/base.py,sha256=OayRr25soCqj1_yc665D5XbWWzFCm7Xl9Txtps953p4,12256
|
||||
sqlalchemy/testing/fixtures/mypy.py,sha256=7fWVZzYzNjqmLIoFa-MmXSGDPS3eZYFXlH-WxaxBDDY,11845
|
||||
sqlalchemy/testing/fixtures/orm.py,sha256=x27qjpK54JETATcYuiphtW-HXRy8ej8h3aCDkeQXPfY,6095
|
||||
sqlalchemy/testing/fixtures/sql.py,sha256=Q7Qq0n4qTT681nWt5DqjThopgjv5BB2KmSmrmAxUqHM,15704
|
||||
sqlalchemy/testing/pickleable.py,sha256=B9dXGF7E2PywB67SngHPjSMIBDTFhyAV4rkDUcyMulk,2833
|
||||
sqlalchemy/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
sqlalchemy/testing/plugin/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-312.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-312.pyc,,
|
||||
sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-312.pyc,,
|
||||
sqlalchemy/testing/plugin/bootstrap.py,sha256=GrBB27KbswjE3Tt-zJlj6uSqGh9N-_CXkonnJSSBz84,1437
|
||||
sqlalchemy/testing/plugin/plugin_base.py,sha256=4SizjghFdDddt5o5gQ16Nw0bJHrtuBa4smxJcea-ti8,21573
|
||||
sqlalchemy/testing/plugin/pytestplugin.py,sha256=yh4PP406O0TwPMDzpJHpcNdU2WHXCLYI10F3oOLePjE,27295
|
||||
sqlalchemy/testing/profiling.py,sha256=HPjYvRLT1nD90FCZ7AA8j9ygkMtf1SGA47Xze2QPueo,10148
|
||||
sqlalchemy/testing/provision.py,sha256=w4F_ceGHPpWHUeh6cVcE5ktCC-ISrGc2yOSnXauOd5U,14200
|
||||
sqlalchemy/testing/requirements.py,sha256=gkviA8f5p4qdoDwAK791I4oGvnEqlm0ZZwJZpJzobFY,51393
|
||||
sqlalchemy/testing/schema.py,sha256=OSfMoIJ7ORbevGkeJdrKcTrQ0s7wXebuCU08mC1Y9jA,6513
|
||||
sqlalchemy/testing/suite/__init__.py,sha256=_firVc2uS3TMZ3vH2baQzNb17ubM78RHtb9kniSybmk,476
|
||||
sqlalchemy/testing/suite/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_cte.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_insert.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_results.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_select.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_types.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-312.pyc,,
|
||||
sqlalchemy/testing/suite/test_cte.py,sha256=O5idVeBnHm9zdiG3tuCBUn4hYU_TA63-6LNnRygr8g0,6205
|
||||
sqlalchemy/testing/suite/test_ddl.py,sha256=xWimTjggpTe3S1Xfmt_IPofTXkUUcKuVSVCIfIyGMbA,11785
|
||||
sqlalchemy/testing/suite/test_deprecations.py,sha256=XI8ZU1NxC-6uvPDImaaq9O7Ov6MF5gmy-yk3TfesLAo,5082
|
||||
sqlalchemy/testing/suite/test_dialect.py,sha256=HUpHZb7pnHbsoRpDLONpsCO_oWhBgjglU9pBO-EOUw4,22673
|
||||
sqlalchemy/testing/suite/test_insert.py,sha256=Wm_pW0qqUNV1Fs7mXoxtmaTHMQGmaVDgDsYgZs1jlxM,18308
|
||||
sqlalchemy/testing/suite/test_reflection.py,sha256=Nd4Ao_J3Sr-VeAeWbUe3gs6STPvik9DC37WkyJc-PVg,106205
|
||||
sqlalchemy/testing/suite/test_results.py,sha256=Hd6R4jhBNNQSp0xGa8wwTgpw-XUrCEZ3dWXpoZ4_DKs,15687
|
||||
sqlalchemy/testing/suite/test_rowcount.py,sha256=zhKVv0ibFSQmnE5luLwgHAn840zOJ6HxtkR3oL995cs,7652
|
||||
sqlalchemy/testing/suite/test_select.py,sha256=QHsBX16EZpxlEZZLM0pMNcwayPU0dig39McKwiiith0,58325
|
||||
sqlalchemy/testing/suite/test_sequence.py,sha256=c80CBWrU930GPnPfr9TCRbTTuITR7BpIactncLIj2XU,9672
|
||||
sqlalchemy/testing/suite/test_types.py,sha256=QjV48MqR7dB8UVzt56UL2z7Nt28-IhywX3DKuQeLYsY,65429
|
||||
sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=7obItCpFt4qlWaDqe25HWgQT6FoUhgz1W7_Xycfz9Xk,5887
|
||||
sqlalchemy/testing/suite/test_update_delete.py,sha256=1hT0BTxB4SNipd6hnVlMnq25dLtQQoXov7z7UR0Sgi8,3658
|
||||
sqlalchemy/testing/util.py,sha256=Wsu4GZgCW6wX9mmxfiffhDz1cZm3778OB3LtiWNgb3Y,14080
|
||||
sqlalchemy/testing/warnings.py,sha256=pmfT33PF1q1PI7DdHOsup3LxHq1AC4-aYl1oL8HmrYo,1546
|
||||
sqlalchemy/types.py,sha256=DgBpPaT-vtsn6_glx5wocrIhR2A1vy56SQNRY3NiPUw,3168
|
||||
sqlalchemy/util/__init__.py,sha256=Bh0SkfkeCsz6-rbDmC41lAWOuCvKCiXVZthN2cWJEXk,8245
|
||||
sqlalchemy/util/__pycache__/__init__.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/_collections.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/_py_collections.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/compat.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/queue.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/tool_support.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/topological.cpython-312.pyc,,
|
||||
sqlalchemy/util/__pycache__/typing.cpython-312.pyc,,
|
||||
sqlalchemy/util/_collections.py,sha256=FYqVQg3CaqiEd21OFN1pNCfFbQ8gvlchW_TMtihSFNE,20169
|
||||
sqlalchemy/util/_concurrency_py3k.py,sha256=31vs1oXaLzeTRgmOXRrWToRQskWmJk-CBs3-JxSTcck,8223
|
||||
sqlalchemy/util/_has_cy.py,sha256=XMkeqCDGmhkd0uuzpCdyELz7gOjHxyFQ1AIlc5NneoY,1229
|
||||
sqlalchemy/util/_py_collections.py,sha256=cYjsYLCLBy5jdGBJATLJCmtfzr_AaJ-HKTUN8OdAzxY,16630
|
||||
sqlalchemy/util/compat.py,sha256=FkeHnW9asJYJvNmxVltee8jQNwQSdVRdKJlVRRInJI4,9388
|
||||
sqlalchemy/util/concurrency.py,sha256=ZxcQYOKy-GBsQkPmCrBO5MzMpqW3JZme2Hiyqpbt9uc,2284
|
||||
sqlalchemy/util/deprecations.py,sha256=pr9DSAf1ECqDk7X7F6TNc1jrhOeFihL33uEb5Wt2_T0,11971
|
||||
sqlalchemy/util/langhelpers.py,sha256=CQQP2Q9c68nL5mcWL-Q38-INrtoDHDnBmq7QhnWyEDM,64980
|
||||
sqlalchemy/util/preloaded.py,sha256=KKNLJEqChDW1TNUsM_TzKu7JYEA3kkuh2N-quM_2_Y4,5905
|
||||
sqlalchemy/util/queue.py,sha256=ITejs6KS4Hz_ojrss2oFeUO9MoIeR3qWmZQ8J7yyrNU,10205
|
||||
sqlalchemy/util/tool_support.py,sha256=epm8MzDZpVmhE6LIjrjJrP8BUf12Wab2m28A9lGq95s,5969
|
||||
sqlalchemy/util/topological.py,sha256=hjJWL3C_B7Rpv9s7jj7wcTckcZUSkxc6xRDhiN1xyec,3458
|
||||
sqlalchemy/util/typing.py,sha256=ESYm4oQtt-SarN04YTXCgovXT8tFupMiPmuGCDCMEIc,15831
|
||||
@@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.1
|
||||
Metadata-Version: 2.3
|
||||
Name: aiofiles
|
||||
Version: 23.2.1
|
||||
Version: 24.1.0
|
||||
Summary: File support for asyncio.
|
||||
Project-URL: Changelog, https://github.com/Tinche/aiofiles#history
|
||||
Project-URL: Bug Tracker, https://github.com/Tinche/aiofiles/issues
|
||||
@@ -13,15 +13,15 @@ Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Framework :: AsyncIO
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
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 :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.7
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# aiofiles: file support for asyncio
|
||||
@@ -135,6 +135,8 @@ several useful `os` functions that deal with files:
|
||||
- `listdir`
|
||||
- `scandir`
|
||||
- `access`
|
||||
- `getcwd`
|
||||
- `path.abspath`
|
||||
- `path.exists`
|
||||
- `path.isfile`
|
||||
- `path.isdir`
|
||||
@@ -176,25 +178,50 @@ as desired. The return type also needs to be registered with the
|
||||
|
||||
```python
|
||||
aiofiles.threadpool.wrap.register(mock.MagicMock)(
|
||||
lambda *args, **kwargs: threadpool.AsyncBufferedIOBase(*args, **kwargs))
|
||||
lambda *args, **kwargs: aiofiles.threadpool.AsyncBufferedIOBase(*args, **kwargs)
|
||||
)
|
||||
|
||||
async def test_stuff():
|
||||
data = 'data'
|
||||
mock_file = mock.MagicMock()
|
||||
write_data = 'data'
|
||||
read_file_chunks = [
|
||||
b'file chunks 1',
|
||||
b'file chunks 2',
|
||||
b'file chunks 3',
|
||||
b'',
|
||||
]
|
||||
file_chunks_iter = iter(read_file_chunks)
|
||||
|
||||
with mock.patch('aiofiles.threadpool.sync_open', return_value=mock_file) as mock_open:
|
||||
mock_file_stream = mock.MagicMock(
|
||||
read=lambda *args, **kwargs: next(file_chunks_iter)
|
||||
)
|
||||
|
||||
with mock.patch('aiofiles.threadpool.sync_open', return_value=mock_file_stream) as mock_open:
|
||||
async with aiofiles.open('filename', 'w') as f:
|
||||
await f.write(data)
|
||||
await f.write(write_data)
|
||||
assert f.read() == b'file chunks 1'
|
||||
|
||||
mock_file.write.assert_called_once_with(data)
|
||||
mock_file_stream.write.assert_called_once_with(write_data)
|
||||
```
|
||||
|
||||
### History
|
||||
|
||||
#### 24.1.0 (2024-06-24)
|
||||
|
||||
- Import `os.link` conditionally to fix importing on android.
|
||||
[#175](https://github.com/Tinche/aiofiles/issues/175)
|
||||
- Remove spurious items from `aiofiles.os.__all__` when running on Windows.
|
||||
- Switch to more modern async idioms: Remove types.coroutine and make AiofilesContextManager an awaitable instead a coroutine.
|
||||
- Add `aiofiles.os.path.abspath` and `aiofiles.os.getcwd`.
|
||||
[#174](https://github.com/Tinche/aiofiles/issues/181)
|
||||
- _aiofiles_ is now tested on Python 3.13 too.
|
||||
[#184](https://github.com/Tinche/aiofiles/pull/184)
|
||||
- Dropped Python 3.7 support. If you require it, use version 23.2.1.
|
||||
|
||||
#### 23.2.1 (2023-08-09)
|
||||
|
||||
- Import `os.statvfs` conditionally to fix importing on non-UNIX systems.
|
||||
[#171](https://github.com/Tinche/aiofiles/issues/171) [#172](https://github.com/Tinche/aiofiles/pull/172)
|
||||
- aiofiles is now also tested on Windows.
|
||||
|
||||
#### 23.2.0 (2023-08-09)
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
aiofiles-23.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
aiofiles-23.2.1.dist-info/METADATA,sha256=cot28p_PNjdl_MK--l9Qu2e6QOv9OxdHrKbjLmYf9Uw,9673
|
||||
aiofiles-23.2.1.dist-info/RECORD,,
|
||||
aiofiles-23.2.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
aiofiles-23.2.1.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
|
||||
aiofiles-23.2.1.dist-info/licenses/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
|
||||
aiofiles-23.2.1.dist-info/licenses/NOTICE,sha256=EExY0dRQvWR0wJ2LZLwBgnM6YKw9jCU-M0zegpRSD_E,55
|
||||
aiofiles/__init__.py,sha256=1iAMJQyJtX3LGIS0AoFTJeO1aJ_RK2jpBSBhg0VoIrE,344
|
||||
aiofiles/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/base.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/os.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/ospath.cpython-312.pyc,,
|
||||
aiofiles/base.py,sha256=rZwA151Ji8XlBkzvDmcF1CgDTY2iKNuJMfvNlM0s0E0,2684
|
||||
aiofiles/os.py,sha256=zuFGaIyGCGUuFb7trFFEm6SLdCRqTFsSV0mY6SO8z3M,970
|
||||
aiofiles/ospath.py,sha256=zqG2VFzRb6yYiIOWipqsdgvZmoMTFvZmBdkxkAl1FT4,764
|
||||
aiofiles/tempfile/__init__.py,sha256=hFSNTOjOUv371Ozdfy6FIxeln46Nm3xOVh4ZR3Q94V0,10244
|
||||
aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc,,
|
||||
aiofiles/tempfile/temptypes.py,sha256=ddEvNjMLVlr7WUILCe6ypTqw77yREeIonTk16Uw_NVs,2093
|
||||
aiofiles/threadpool/__init__.py,sha256=c_aexl1t193iKdPZaolPEEbHDrQ0RrsH_HTAToMPQBo,3171
|
||||
aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/binary.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/text.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/utils.cpython-312.pyc,,
|
||||
aiofiles/threadpool/binary.py,sha256=hp-km9VCRu0MLz_wAEUfbCz7OL7xtn9iGAawabpnp5U,2315
|
||||
aiofiles/threadpool/text.py,sha256=fNmpw2PEkj0BZSldipJXAgZqVGLxALcfOMiuDQ54Eas,1223
|
||||
aiofiles/threadpool/utils.py,sha256=B59dSZwO_WZs2dFFycKeA91iD2Xq2nNw1EFF8YMBI5k,1868
|
||||
aiofiles-24.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
aiofiles-24.1.0.dist-info/METADATA,sha256=CvUJx21XclgI1Lp5Bt_4AyJesRYg0xCSx4exJZVmaSA,10708
|
||||
aiofiles-24.1.0.dist-info/RECORD,,
|
||||
aiofiles-24.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
aiofiles-24.1.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
||||
aiofiles-24.1.0.dist-info/licenses/LICENSE,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
|
||||
aiofiles-24.1.0.dist-info/licenses/NOTICE,sha256=EExY0dRQvWR0wJ2LZLwBgnM6YKw9jCU-M0zegpRSD_E,55
|
||||
aiofiles/__init__.py,sha256=1iAMJQyJtX3LGIS0AoFTJeO1aJ_RK2jpBSBhg0VoIrE,344
|
||||
aiofiles/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/base.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/os.cpython-312.pyc,,
|
||||
aiofiles/__pycache__/ospath.cpython-312.pyc,,
|
||||
aiofiles/base.py,sha256=zo0FgkCqZ5aosjvxqIvDf2t-RFg1Lc6X8P6rZ56p6fQ,1784
|
||||
aiofiles/os.py,sha256=0DrsG-eH4h7xRzglv9pIWsQuzqe7ZhVYw5FQS18fIys,1153
|
||||
aiofiles/ospath.py,sha256=WaYelz_k6ykAFRLStr4bqYIfCVQ-5GGzIqIizykbY2Q,794
|
||||
aiofiles/tempfile/__init__.py,sha256=hFSNTOjOUv371Ozdfy6FIxeln46Nm3xOVh4ZR3Q94V0,10244
|
||||
aiofiles/tempfile/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/tempfile/__pycache__/temptypes.cpython-312.pyc,,
|
||||
aiofiles/tempfile/temptypes.py,sha256=ddEvNjMLVlr7WUILCe6ypTqw77yREeIonTk16Uw_NVs,2093
|
||||
aiofiles/threadpool/__init__.py,sha256=kt0hwwx3bLiYtnA1SORhW8mJ6z4W9Xr7MbY80UIJJrI,3133
|
||||
aiofiles/threadpool/__pycache__/__init__.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/binary.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/text.cpython-312.pyc,,
|
||||
aiofiles/threadpool/__pycache__/utils.cpython-312.pyc,,
|
||||
aiofiles/threadpool/binary.py,sha256=hp-km9VCRu0MLz_wAEUfbCz7OL7xtn9iGAawabpnp5U,2315
|
||||
aiofiles/threadpool/text.py,sha256=fNmpw2PEkj0BZSldipJXAgZqVGLxALcfOMiuDQ54Eas,1223
|
||||
aiofiles/threadpool/utils.py,sha256=B59dSZwO_WZs2dFFycKeA91iD2Xq2nNw1EFF8YMBI5k,1868
|
||||
@@ -1,4 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.18.0
|
||||
Generator: hatchling 1.25.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Various base classes."""
|
||||
from types import coroutine
|
||||
from collections.abc import Coroutine
|
||||
from collections.abc import Awaitable
|
||||
from contextlib import AbstractAsyncContextManager
|
||||
from asyncio import get_running_loop
|
||||
|
||||
|
||||
@@ -45,66 +45,22 @@ class AsyncIndirectBase(AsyncBase):
|
||||
pass # discard writes
|
||||
|
||||
|
||||
class _ContextManager(Coroutine):
|
||||
class AiofilesContextManager(Awaitable, AbstractAsyncContextManager):
|
||||
"""An adjusted async context manager for aiofiles."""
|
||||
|
||||
__slots__ = ("_coro", "_obj")
|
||||
|
||||
def __init__(self, coro):
|
||||
self._coro = coro
|
||||
self._obj = None
|
||||
|
||||
def send(self, value):
|
||||
return self._coro.send(value)
|
||||
|
||||
def throw(self, typ, val=None, tb=None):
|
||||
if val is None:
|
||||
return self._coro.throw(typ)
|
||||
elif tb is None:
|
||||
return self._coro.throw(typ, val)
|
||||
else:
|
||||
return self._coro.throw(typ, val, tb)
|
||||
|
||||
def close(self):
|
||||
return self._coro.close()
|
||||
|
||||
@property
|
||||
def gi_frame(self):
|
||||
return self._coro.gi_frame
|
||||
|
||||
@property
|
||||
def gi_running(self):
|
||||
return self._coro.gi_running
|
||||
|
||||
@property
|
||||
def gi_code(self):
|
||||
return self._coro.gi_code
|
||||
|
||||
def __next__(self):
|
||||
return self.send(None)
|
||||
|
||||
@coroutine
|
||||
def __iter__(self):
|
||||
resp = yield from self._coro
|
||||
return resp
|
||||
|
||||
def __await__(self):
|
||||
resp = yield from self._coro
|
||||
return resp
|
||||
|
||||
async def __anext__(self):
|
||||
resp = await self._coro
|
||||
return resp
|
||||
|
||||
async def __aenter__(self):
|
||||
self._obj = await self._coro
|
||||
if self._obj is None:
|
||||
self._obj = yield from self._coro.__await__()
|
||||
return self._obj
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
self._obj.close()
|
||||
self._obj = None
|
||||
|
||||
|
||||
class AiofilesContextManager(_ContextManager):
|
||||
"""An adjusted async context manager for aiofiles."""
|
||||
async def __aenter__(self):
|
||||
return await self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
await get_running_loop().run_in_executor(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Async executor versions of file functions from the os module."""
|
||||
|
||||
import os
|
||||
|
||||
from . import ospath as path
|
||||
@@ -7,7 +8,6 @@ from .ospath import wrap
|
||||
__all__ = [
|
||||
"path",
|
||||
"stat",
|
||||
"statvfs",
|
||||
"rename",
|
||||
"renames",
|
||||
"replace",
|
||||
@@ -17,15 +17,20 @@ __all__ = [
|
||||
"makedirs",
|
||||
"rmdir",
|
||||
"removedirs",
|
||||
"link",
|
||||
"symlink",
|
||||
"readlink",
|
||||
"listdir",
|
||||
"scandir",
|
||||
"access",
|
||||
"sendfile",
|
||||
"wrap",
|
||||
"getcwd",
|
||||
]
|
||||
if hasattr(os, "link"):
|
||||
__all__ += ["link"]
|
||||
if hasattr(os, "sendfile"):
|
||||
__all__ += ["sendfile"]
|
||||
if hasattr(os, "statvfs"):
|
||||
__all__ += ["statvfs"]
|
||||
|
||||
|
||||
stat = wrap(os.stat)
|
||||
@@ -38,13 +43,15 @@ mkdir = wrap(os.mkdir)
|
||||
makedirs = wrap(os.makedirs)
|
||||
rmdir = wrap(os.rmdir)
|
||||
removedirs = wrap(os.removedirs)
|
||||
link = wrap(os.link)
|
||||
symlink = wrap(os.symlink)
|
||||
readlink = wrap(os.readlink)
|
||||
listdir = wrap(os.listdir)
|
||||
scandir = wrap(os.scandir)
|
||||
access = wrap(os.access)
|
||||
getcwd = wrap(os.getcwd)
|
||||
|
||||
if hasattr(os, "link"):
|
||||
link = wrap(os.link)
|
||||
if hasattr(os, "sendfile"):
|
||||
sendfile = wrap(os.sendfile)
|
||||
if hasattr(os, "statvfs"):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Async executor versions of file functions from the os.path module."""
|
||||
|
||||
import asyncio
|
||||
from functools import partial, wraps
|
||||
from os import path
|
||||
@@ -26,3 +27,4 @@ getatime = wrap(path.getatime)
|
||||
getctime = wrap(path.getctime)
|
||||
samefile = wrap(path.samefile)
|
||||
sameopenfile = wrap(path.sameopenfile)
|
||||
abspath = wrap(path.abspath)
|
||||
|
||||
@@ -10,7 +10,6 @@ from io import (
|
||||
FileIO,
|
||||
TextIOBase,
|
||||
)
|
||||
from types import coroutine
|
||||
|
||||
from ..base import AiofilesContextManager
|
||||
from .binary import (
|
||||
@@ -63,8 +62,7 @@ def open(
|
||||
)
|
||||
|
||||
|
||||
@coroutine
|
||||
def _open(
|
||||
async def _open(
|
||||
file,
|
||||
mode="r",
|
||||
buffering=-1,
|
||||
@@ -91,7 +89,7 @@ def _open(
|
||||
closefd=closefd,
|
||||
opener=opener,
|
||||
)
|
||||
f = yield from loop.run_in_executor(executor, cb)
|
||||
f = await loop.run_in_executor(executor, cb)
|
||||
|
||||
return wrap(f, loop=loop, executor=executor)
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
../../../bin/alembic,sha256=kheZTewTBSd6rruOpyoj8QhFdGKiaj38MUFgBD5whig,238
|
||||
alembic-1.12.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
alembic-1.12.1.dist-info/LICENSE,sha256=soUmiob0QW6vTQWyrjiAwVb3xZqPk1pAK8BW6vszrwg,1058
|
||||
alembic-1.12.1.dist-info/METADATA,sha256=D9-LeKL0unLPg2JKmlFMB5NAxt9N9y-8oVEGOUHbQnU,7306
|
||||
alembic-1.12.1.dist-info/RECORD,,
|
||||
alembic-1.12.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
alembic-1.12.1.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
||||
alembic-1.12.1.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48
|
||||
alembic-1.12.1.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8
|
||||
alembic/__init__.py,sha256=gczqgDgBRw3aV70aNeH6WGu0WdASQf_YiChV12qCRRI,75
|
||||
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=4IHgWH89pForRq-yCDZhGjjVtsfGX5ECWNPuUs8nGUk,351
|
||||
alembic/autogenerate/__pycache__/__init__.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/api.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/compare.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/render.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc,,
|
||||
alembic/autogenerate/api.py,sha256=MNn0Xtmj44aMFjfiR0LMkbxOynHyiyaRBnrj5EkImm4,21967
|
||||
alembic/autogenerate/compare.py,sha256=gSCjxrkQAl0rJD6o9Ln8wNxGVNU6FrWzKZYVkH5Tmac,47042
|
||||
alembic/autogenerate/render.py,sha256=Fik2aPZEIxOlTCrBd0UiPxnX5SFG__CvfXqMWoJr6lw,34475
|
||||
alembic/autogenerate/rewriter.py,sha256=Osba8GFVeqiX1ypGJW7Axt0ui2EROlaFtVZdMFbhzZ0,7384
|
||||
alembic/command.py,sha256=ze4pYvKpB-FtF8rduY6F6n3XHqeA-15iXaaEDeNHVzI,21588
|
||||
alembic/config.py,sha256=68e1nmYU5Nfh0bNRqRWUygSilDl1p0G_U1zZ8ifgmD8,21931
|
||||
alembic/context.py,sha256=hK1AJOQXJ29Bhn276GYcosxeG7pC5aZRT5E8c4bMJ4Q,195
|
||||
alembic/context.pyi,sha256=FLsT0be_vO_ozlC05EJkWR5olDPoTVq-7tgtoM5wSAw,31463
|
||||
alembic/ddl/__init__.py,sha256=xXr1W6PePe0gCLwR42ude0E6iru9miUFc1fCeQN4YP8,137
|
||||
alembic/ddl/__pycache__/__init__.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/base.py,sha256=cCY3NldMRggrKd9bZ0mFRBE9GNDaAy0UJcM3ey4Utgw,9638
|
||||
alembic/ddl/impl.py,sha256=Z3GpNM2KwBpfl1UCam1YsYbSd0mQzRigOKQhUCLIPgE,25564
|
||||
alembic/ddl/mssql.py,sha256=0k26xnUSZNj3qCHEMzRFbaWgUzKcV07I3_-Ns47VhO0,14105
|
||||
alembic/ddl/mysql.py,sha256=ff8OE0zQ8YYjAgltBbtjQkDR-g9z65DNeFjEMm4sX6c,16675
|
||||
alembic/ddl/oracle.py,sha256=E0VaZaUM_5mwqNiJVA3zOAK-cuHVVIv_-NmUbH1JuGQ,6097
|
||||
alembic/ddl/postgresql.py,sha256=aO8pcVN5ycw1wG2m1RRt8dQUD1KgRa6T4rSzg9FPCkU,26457
|
||||
alembic/ddl/sqlite.py,sha256=9q7NAxyeFwn9kWwQSc9RLeMFSos8waM7x9lnXdByh44,7613
|
||||
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=ldQBwAfzm_-ZsC3nizMuGoD34hjMKb4V_-Q1rR8q8LI,48591
|
||||
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=2so4KisDNuOLw0CRiZqorIHrhuenpVoFbn3B0sNvDic,72471
|
||||
alembic/operations/batch.py,sha256=uMvGJDlcTs0GSHasg4Gsdv1YcXeLOK_1lkRl3jk1ezY,26954
|
||||
alembic/operations/ops.py,sha256=aP9Uz36k98O_Y-njKIAifyvyhi0g2zU6_igKMos91_s,93539
|
||||
alembic/operations/schemaobj.py,sha256=-tWad8pgWUNWucbpTnPuFK_EEl913C0RADJhlBnrjhc,9393
|
||||
alembic/operations/toimpl.py,sha256=K8nUmojtL94tyLSWdDD-e94IbghZ19k55iBIMvzMm5E,6993
|
||||
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/environment.py,sha256=qaerrw5jB7zYliNnCvIziaju4-tvQ451MuGW8PHnfvw,41019
|
||||
alembic/runtime/migration.py,sha256=5UtTI_T0JtYzt6ZpeUhannMZOvXWiEymKFOpeCefaPY,49407
|
||||
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=90SpT8wyTMTUuS0Svsy5YIoqJSrR-6CtYSzStmRvFT0,37174
|
||||
alembic/script/revision.py,sha256=DE0nwvDOzdFo843brvnhs1DfP0jRC5EVQHrNihC7PUQ,61471
|
||||
alembic/script/write_hooks.py,sha256=Nqj4zz3sm97kAPOpK1m-i2znJchiybO_TWT50oljlJw,4917
|
||||
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=k3IyGDG15Rp1JDweC0TiDauaKYNvj3clrGfhw6oV6MI,3505
|
||||
alembic/templates/async/env.py,sha256=zbOCf3Y7w2lg92hxSwmG1MM_7y56i_oRH4AKp0pQBYo,2389
|
||||
alembic/templates/async/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
||||
alembic/templates/generic/README,sha256=MVlc9TYmr57RbhXET6QxgyCcwWP7w-vLkEsirENqiIQ,38
|
||||
alembic/templates/generic/__pycache__/env.cpython-312.pyc,,
|
||||
alembic/templates/generic/alembic.ini.mako,sha256=gZWFmH2A9sP0i7cxEDhJFkjGtTKUXaVna8QAbIaRqxk,3614
|
||||
alembic/templates/generic/env.py,sha256=TLRWOVW3Xpt_Tpf8JFzlnoPn_qoUu8UV77Y4o9XD6yI,2103
|
||||
alembic/templates/generic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
||||
alembic/templates/multidb/README,sha256=dWLDhnBgphA4Nzb7sNlMfCS3_06YqVbHhz-9O5JNqyI,606
|
||||
alembic/templates/multidb/__pycache__/env.cpython-312.pyc,,
|
||||
alembic/templates/multidb/alembic.ini.mako,sha256=j_Y0yuZVoHy7sTPgSPd8DmbT2ItvAdWs7trYZSOmFnw,3708
|
||||
alembic/templates/multidb/env.py,sha256=6zNjnW8mXGUk7erTsAvrfhvqoczJ-gagjVq1Ypg2YIQ,4230
|
||||
alembic/templates/multidb/script.py.mako,sha256=N06nMtNSwHkgl0EBXDyMt8njp9tlOesR583gfq21nbY,1090
|
||||
alembic/testing/__init__.py,sha256=kOxOh5nwmui9d-_CCq9WA4Udwy7ITjm453w74CTLZDo,1159
|
||||
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=1CbJk8c8-WO9eJ0XJ0jJvMsNRLUrXV41NOeIJUAlOBk,5015
|
||||
alembic/testing/env.py,sha256=zJacVb_z6uLs2U1TtkmnFH9P3_F-3IfYbVv4UEPOvfo,10754
|
||||
alembic/testing/fixtures.py,sha256=NyP4wE_dFN9ZzSGiBagRu1cdzkka03nwJYJYHYrrkSY,9112
|
||||
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=WByOiJxn2crazIXPq6-0cfqV95cfd9vP_ZQ1Cf2l8hY,4841
|
||||
alembic/testing/schemacompare.py,sha256=7_4_0Y4UvuMiZ66pz1RC_P8Z1kYOP-R4Y5qUcNmcMKA,4535
|
||||
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=cDq1pmzHe15S6dZPGNC6sqFaCQ3hLT_oPV2IDigUGQ0,9880
|
||||
alembic/testing/suite/test_autogen_comments.py,sha256=aEGqKUDw4kHjnDk298aoGcQvXJWmZXcIX_2FxH4cJK8,6283
|
||||
alembic/testing/suite/test_autogen_computed.py,sha256=qJeBpc8urnwTFvbwWrSTIbHVkRUuCXP-dKaNbUK2U2U,6077
|
||||
alembic/testing/suite/test_autogen_diffs.py,sha256=T4SR1n_kmcOKYhR4W1-dA0e5sddJ69DSVL2HW96kAkE,8394
|
||||
alembic/testing/suite/test_autogen_fks.py,sha256=AqFmb26Buex167HYa9dZWOk8x-JlB1OK3bwcvvjDFaU,32927
|
||||
alembic/testing/suite/test_autogen_identity.py,sha256=kcuqngG7qXAKPJDX4U8sRzPKHEJECHuZ0DtuaS6tVkk,5824
|
||||
alembic/testing/suite/test_environment.py,sha256=w9F0xnLEbALeR8k6_-Tz6JHvy91IqiTSypNasVzXfZQ,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=RxA7x_8GseANgw07Us8JN_1iGbANxaw6_VitX2ZGQH4,1078
|
||||
alembic/util/__init__.py,sha256=cPF_jjFx7YRBByHHDqW3wxCIHsqnGfncEr_i238aduY,1202
|
||||
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=WN8jPPFB9ri_uuEM1HEaN1ak3RJc_H3x8NqvtFkoXuM,2279
|
||||
alembic/util/editor.py,sha256=JIz6_BdgV8_oKtnheR6DZoB7qnrHrlRgWjx09AsTsUw,2546
|
||||
alembic/util/exc.py,sha256=KQTru4zcgAmN4IxLMwLFS56XToUewaXB7oOLcPNjPwg,98
|
||||
alembic/util/langhelpers.py,sha256=ZFGyGygHRbztOeajpajppyhd-Gp4PB5slMuvCFVrnmg,8591
|
||||
alembic/util/messaging.py,sha256=B6T-loMhIOY3OTbG47Ywp1Df9LZn18PgjwpwLrD1VNg,3042
|
||||
alembic/util/pyfiles.py,sha256=95J01FChN0j2uP3p72mjaOQvh5wC6XbdGtTDK8oEzsQ,3373
|
||||
alembic/util/sqla_compat.py,sha256=94MHlkj43y-QQySz5dCUiJUNOPr3BF9TQ_BrP6ey-8w,18906
|
||||
@@ -1,11 +1,10 @@
|
||||
Metadata-Version: 2.1
|
||||
Metadata-Version: 2.4
|
||||
Name: alembic
|
||||
Version: 1.12.1
|
||||
Version: 1.16.5
|
||||
Summary: A database migration tool for SQLAlchemy.
|
||||
Home-page: https://alembic.sqlalchemy.org
|
||||
Author: Mike Bayer
|
||||
Author-email: mike_mp@zzzcomputing.com
|
||||
License: MIT
|
||||
Author-email: Mike Bayer <mike_mp@zzzcomputing.com>
|
||||
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/
|
||||
@@ -13,27 +12,27 @@ Project-URL: Issue Tracker, https://github.com/sqlalchemy/alembic/issues/
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Environment :: Console
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
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 :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Database :: Front-Ends
|
||||
Requires-Python: >=3.7
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
Requires-Dist: SQLAlchemy >=1.3.0
|
||||
Requires-Dist: SQLAlchemy>=1.4.0
|
||||
Requires-Dist: Mako
|
||||
Requires-Dist: typing-extensions >=4
|
||||
Requires-Dist: importlib-metadata ; python_version < "3.9"
|
||||
Requires-Dist: importlib-resources ; python_version < "3.9"
|
||||
Requires-Dist: typing-extensions>=4.12
|
||||
Requires-Dist: tomli; python_version < "3.11"
|
||||
Provides-Extra: tz
|
||||
Requires-Dist: python-dateutil ; extra == 'tz'
|
||||
Requires-Dist: tzdata; extra == "tz"
|
||||
Dynamic: license-file
|
||||
|
||||
Alembic is a database migrations tool written by the author
|
||||
of `SQLAlchemy <http://www.sqlalchemy.org>`_. A migrations tool
|
||||
@@ -0,0 +1,163 @@
|
||||
../../../bin/alembic,sha256=_J6yD4KtWGrilKk3GrsJKTd-33Dqp4ejOp_LNh0fQNs,234
|
||||
alembic-1.16.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
alembic-1.16.5.dist-info/METADATA,sha256=_hKTp0jnKI77a2esxmoCXgv5t2U8hDZS7yZDRkDBl0k,7265
|
||||
alembic-1.16.5.dist-info/RECORD,,
|
||||
alembic-1.16.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
alembic-1.16.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
||||
alembic-1.16.5.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48
|
||||
alembic-1.16.5.dist-info/licenses/LICENSE,sha256=NeqcNBmyYfrxvkSMT0fZJVKBv2s2tf_qVQUiJ9S6VN4,1059
|
||||
alembic-1.16.5.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8
|
||||
alembic/__init__.py,sha256=H_hItDeyDOrQAHc1AFoYXIRN3O3FSxw4zSNiVzz2JlM,63
|
||||
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__/compare.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/render.cpython-312.pyc,,
|
||||
alembic/autogenerate/__pycache__/rewriter.cpython-312.pyc,,
|
||||
alembic/autogenerate/api.py,sha256=L4qkapSJO1Ypymx8HsjLl0vFFt202agwMYsQbIe6ZtI,22219
|
||||
alembic/autogenerate/compare.py,sha256=LRTxNijEBvcTauuUXuJjC6Sg_gUn33FCYBTF0neZFwE,45979
|
||||
alembic/autogenerate/render.py,sha256=ceQL8nk8m2kBtQq5gtxtDLR9iR0Sck8xG_61Oez-Sqs,37270
|
||||
alembic/autogenerate/rewriter.py,sha256=NIASSS-KaNKPmbm1k4pE45aawwjSh1Acf6eZrOwnUGM,7814
|
||||
alembic/command.py,sha256=pZPQUGSxCjFu7qy0HMe02HJmByM0LOqoiK2AXKfRO3A,24855
|
||||
alembic/config.py,sha256=nfwN_OOFPpee-OY4o10DANh7VG_E4O7bdW00Wx8NNKY,34237
|
||||
alembic/context.py,sha256=hK1AJOQXJ29Bhn276GYcosxeG7pC5aZRT5E8c4bMJ4Q,195
|
||||
alembic/context.pyi,sha256=fdeFNTRc0bUgi7n2eZWVFh6NG-TzIv_0gAcapbfHnKY,31773
|
||||
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=A1f89-rCZvqw-hgWmBbIszRqx94lL6gKLFXE9kHettA,10478
|
||||
alembic/ddl/impl.py,sha256=UL8-iza7CJk_T73lr5fjDLdhxEL56uD-AEjtmESAbLk,30439
|
||||
alembic/ddl/mssql.py,sha256=NzORSIDHUll_g6iH4IyMTXZU1qjKzXrpespKrjWnfLY,14216
|
||||
alembic/ddl/mysql.py,sha256=LSfwiABdT54sKY_uQ-h6RvjbGiG-1vCSDkO3ECeq3qM,18383
|
||||
alembic/ddl/oracle.py,sha256=669YlkcZihlXFbnXhH2krdrvDry8q5pcUGfoqkg_R6Y,6243
|
||||
alembic/ddl/postgresql.py,sha256=S7uye2NDSHLwV3w8SJ2Q9DLbcvQIxQfJ3EEK6JqyNag,29950
|
||||
alembic/ddl/sqlite.py,sha256=u5tJgRUiY6bzVltl_NWlI6cy23v8XNagk_9gPI6Lnns,8006
|
||||
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=PQ4mKNp7EXrjVdIWQRoGiBSVke4PPxTc9I6qF8ZGGZE,50711
|
||||
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=npw1iFboTlEsaQS0b7mb2SEHsRDV4GLQqnjhcfma6Nk,75157
|
||||
alembic/operations/batch.py,sha256=1UmCFcsFWObinQWFRWoGZkjynl54HKpldbPs67aR4wg,26923
|
||||
alembic/operations/ops.py,sha256=ftsFgcZIctxRDiuGgkQsaFHsMlRP7cLq7Dj_seKVBnQ,96276
|
||||
alembic/operations/schemaobj.py,sha256=Wp-bBe4a8lXPTvIHJttBY0ejtpVR5Jvtb2kI-U2PztQ,9468
|
||||
alembic/operations/toimpl.py,sha256=rgufuSUNwpgrOYzzY3Q3ELW1rQv2fQbQVokXgnIYIrs,7503
|
||||
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/environment.py,sha256=L6bDW1dvw8L4zwxlTG8KnT0xcCgLXxUfdRpzqlJoFjo,41479
|
||||
alembic/runtime/migration.py,sha256=lu9_z_qyWmNzSM52_FgdXP_G52PTmTTeOeMBQAkQTFg,49997
|
||||
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=4jINClsNNwQIvnf4Kwp9JPAMrANLXdLItylXmcMqAkI,36896
|
||||
alembic/script/revision.py,sha256=BQcJoMCIXtSJRLCvdasgLOtCx9O7A8wsSym1FsqLW4s,62307
|
||||
alembic/script/write_hooks.py,sha256=uQWAtguSCrxU_k9d87NX19y6EzyjJRRQ5HS9cyPnK9o,5092
|
||||
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=Bgi4WkaHYsT7xvsX-4WOGkcXKFroNoQLaUvZA23ZwGs,4864
|
||||
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=LCpLL02bi9Qr3KRTEj9NbQqAu0ckUmYBwPtrMtQkv-Y,4864
|
||||
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=rIp1LTdE1xcoFT2G7X72KshzYjUTRrHTvnkvFL___-8,5190
|
||||
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=Gf16ZR9OMG9zDlFO5PVQlfiL1DTKwSA--sTNzK7Lba0,2852
|
||||
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=Gf16ZR9OMG9zDlFO5PVQlfiL1DTKwSA--sTNzK7Lba0,2852
|
||||
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=qcqf3tRAUe-A12NzuK_yxlksuX9OZKRC5E8pKIdBnPg,5302
|
||||
alembic/testing/env.py,sha256=pka7fjwOC8hYL6X0XE4oPkJpy_1WX01bL7iP7gpO_4I,11551
|
||||
alembic/testing/fixtures.py,sha256=fOzsRF8SW6CWpAH0sZpUHcgsJjun9EHnp4k2S3Lq5eU,9920
|
||||
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=gNnnvgPCuiqKeHmiNymdQuYIjQ0BrxiPxu_in4eHEsc,4180
|
||||
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=Drrz_FKb9KDjq8hkwxtPkJVY1sCY7Biw-Muzb8kANp8,13480
|
||||
alembic/testing/suite/test_autogen_comments.py,sha256=aEGqKUDw4kHjnDk298aoGcQvXJWmZXcIX_2FxH4cJK8,6283
|
||||
alembic/testing/suite/test_autogen_computed.py,sha256=-5wran56qXo3afAbSk8cuSDDpbQweyJ61RF-GaVuZbA,4126
|
||||
alembic/testing/suite/test_autogen_diffs.py,sha256=T4SR1n_kmcOKYhR4W1-dA0e5sddJ69DSVL2HW96kAkE,8394
|
||||
alembic/testing/suite/test_autogen_fks.py,sha256=AqFmb26Buex167HYa9dZWOk8x-JlB1OK3bwcvvjDFaU,32927
|
||||
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=_Zj_xp6ssKLyoLHUFzmKhnc8mhwXW8D8h7qyX-wO56M,1519
|
||||
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=Vt5xCn5Y675jI4seKNBV4IVnCl9V4wyH3OBI2w7U0EY,4248
|
||||
alembic/util/editor.py,sha256=JIz6_BdgV8_oKtnheR6DZoB7qnrHrlRgWjx09AsTsUw,2546
|
||||
alembic/util/exc.py,sha256=ZBlTQ8g-Jkb1iYFhFHs9djilRz0SSQ0Foc5SSoENs5o,564
|
||||
alembic/util/langhelpers.py,sha256=LpOcovnhMnP45kTt8zNJ4BHpyQrlF40OL6yDXjqKtsE,10026
|
||||
alembic/util/messaging.py,sha256=3bEBoDy4EAXETXAvArlYjeMITXDTgPTu6ZoE3ytnzSw,3294
|
||||
alembic/util/pyfiles.py,sha256=kOBjZEytRkBKsQl0LAj2sbKJMQazjwQ_5UeMKSIvVFo,4730
|
||||
alembic/util/sqla_compat.py,sha256=9OYPTf-GCultAIuv1PoiaqYXAApZQxUOqjrOaeJDAik,14790
|
||||
@@ -1,5 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.41.3)
|
||||
Generator: setuptools (80.9.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2009-2023 Michael Bayer.
|
||||
Copyright 2009-2025 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
|
||||
@@ -16,4 +16,4 @@ 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.
|
||||
SOFTWARE.
|
||||
@@ -1,6 +1,4 @@
|
||||
import sys
|
||||
|
||||
from . import context
|
||||
from . import op
|
||||
|
||||
__version__ = "1.12.1"
|
||||
__version__ = "1.16.5"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from .api import _render_migration_diffs
|
||||
from .api import compare_metadata
|
||||
from .api import produce_migrations
|
||||
from .api import render_python_code
|
||||
from .api import RevisionContext
|
||||
from .compare import _produce_net_changes
|
||||
from .compare import comparators
|
||||
from .render import render_op_text
|
||||
from .render import renderers
|
||||
from .rewriter import Rewriter
|
||||
from .api import _render_migration_diffs as _render_migration_diffs
|
||||
from .api import compare_metadata as compare_metadata
|
||||
from .api import produce_migrations as produce_migrations
|
||||
from .api import render_python_code as render_python_code
|
||||
from .api import RevisionContext as RevisionContext
|
||||
from .compare import _produce_net_changes as _produce_net_changes
|
||||
from .compare import comparators as comparators
|
||||
from .render import render_op_text as render_op_text
|
||||
from .render import renderers as renderers
|
||||
from .rewriter import Rewriter as Rewriter
|
||||
|
||||
@@ -17,6 +17,7 @@ from . import compare
|
||||
from . import render
|
||||
from .. import util
|
||||
from ..operations import ops
|
||||
from ..util import sqla_compat
|
||||
|
||||
"""Provide the 'autogenerate' feature which can produce migration operations
|
||||
automatically."""
|
||||
@@ -27,6 +28,7 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.engine import Inspector
|
||||
from sqlalchemy.sql.schema import MetaData
|
||||
from sqlalchemy.sql.schema import SchemaItem
|
||||
from sqlalchemy.sql.schema import Table
|
||||
|
||||
from ..config import Config
|
||||
from ..operations.ops import DowngradeOps
|
||||
@@ -164,6 +166,7 @@ def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any:
|
||||
"""
|
||||
|
||||
migration_script = produce_migrations(context, metadata)
|
||||
assert migration_script.upgrade_ops is not None
|
||||
return migration_script.upgrade_ops.as_diffs()
|
||||
|
||||
|
||||
@@ -274,7 +277,7 @@ class AutogenContext:
|
||||
"""Maintains configuration and state that's specific to an
|
||||
autogenerate operation."""
|
||||
|
||||
metadata: Optional[MetaData] = None
|
||||
metadata: Union[MetaData, Sequence[MetaData], None] = None
|
||||
"""The :class:`~sqlalchemy.schema.MetaData` object
|
||||
representing the destination.
|
||||
|
||||
@@ -329,8 +332,8 @@ class AutogenContext:
|
||||
def __init__(
|
||||
self,
|
||||
migration_context: MigrationContext,
|
||||
metadata: Optional[MetaData] = None,
|
||||
opts: Optional[dict] = None,
|
||||
metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
||||
opts: Optional[Dict[str, Any]] = None,
|
||||
autogenerate: bool = True,
|
||||
) -> None:
|
||||
if (
|
||||
@@ -440,7 +443,7 @@ class AutogenContext:
|
||||
def run_object_filters(
|
||||
self,
|
||||
object_: SchemaItem,
|
||||
name: Optional[str],
|
||||
name: sqla_compat._ConstraintName,
|
||||
type_: NameFilterType,
|
||||
reflected: bool,
|
||||
compare_to: Optional[SchemaItem],
|
||||
@@ -464,7 +467,7 @@ class AutogenContext:
|
||||
run_filters = run_object_filters
|
||||
|
||||
@util.memoized_property
|
||||
def sorted_tables(self):
|
||||
def sorted_tables(self) -> List[Table]:
|
||||
"""Return an aggregate of the :attr:`.MetaData.sorted_tables`
|
||||
collection(s).
|
||||
|
||||
@@ -480,7 +483,7 @@ class AutogenContext:
|
||||
return result
|
||||
|
||||
@util.memoized_property
|
||||
def table_key_to_table(self):
|
||||
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
|
||||
@@ -491,7 +494,7 @@ class AutogenContext:
|
||||
objects contain the same table key, an exception is raised.
|
||||
|
||||
"""
|
||||
result = {}
|
||||
result: Dict[str, Table] = {}
|
||||
for m in util.to_list(self.metadata):
|
||||
intersect = set(result).intersection(set(m.tables))
|
||||
if intersect:
|
||||
@@ -593,9 +596,9 @@ class RevisionContext:
|
||||
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.downgrade_ops_list[-1].downgrade_token = (
|
||||
downgrade_token
|
||||
)
|
||||
migration_script._needs_render = True
|
||||
else:
|
||||
migration_script._upgrade_ops.append(
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
@@ -7,12 +10,12 @@ from typing import Any
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import event
|
||||
@@ -21,10 +24,15 @@ from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.sql import expression
|
||||
from sqlalchemy.sql.elements import conv
|
||||
from sqlalchemy.sql.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.sql.schema import Index
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
from sqlalchemy.util import OrderedSet
|
||||
|
||||
from alembic.ddl.base import _fk_spec
|
||||
from .. import util
|
||||
from ..ddl._autogen import is_index_sig
|
||||
from ..ddl._autogen import is_uq_sig
|
||||
from ..operations import ops
|
||||
from ..util import sqla_compat
|
||||
|
||||
@@ -35,10 +43,7 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.sql.schema import Index
|
||||
from sqlalchemy.sql.schema import Table
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
|
||||
from alembic.autogenerate.api import AutogenContext
|
||||
from alembic.ddl.impl import DefaultImpl
|
||||
@@ -46,6 +51,8 @@ if TYPE_CHECKING:
|
||||
from alembic.operations.ops import MigrationScript
|
||||
from alembic.operations.ops import ModifyTableOps
|
||||
from alembic.operations.ops import UpgradeOps
|
||||
from ..ddl._autogen import _constraint_sig
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -210,7 +217,7 @@ def _compare_tables(
|
||||
(inspector),
|
||||
# fmt: on
|
||||
)
|
||||
sqla_compat._reflect_table(inspector, t)
|
||||
_InspectorConv(inspector).reflect_table(t, include_columns=None)
|
||||
if autogen_context.run_object_filters(t, tname, "table", True, None):
|
||||
modify_table_ops = ops.ModifyTableOps(tname, [], schema=s)
|
||||
|
||||
@@ -240,7 +247,8 @@ def _compare_tables(
|
||||
_compat_autogen_column_reflect(inspector),
|
||||
# fmt: on
|
||||
)
|
||||
sqla_compat._reflect_table(inspector, t)
|
||||
_InspectorConv(inspector).reflect_table(t, include_columns=None)
|
||||
|
||||
conn_column_info[(s, tname)] = t
|
||||
|
||||
for s, tname in sorted(existing_tables, key=lambda x: (x[0] or "", x[1])):
|
||||
@@ -429,102 +437,56 @@ def _compare_columns(
|
||||
log.info("Detected removed column '%s.%s'", name, cname)
|
||||
|
||||
|
||||
class _constraint_sig:
|
||||
const: Union[UniqueConstraint, ForeignKeyConstraint, Index]
|
||||
_C = TypeVar("_C", bound=Union[UniqueConstraint, ForeignKeyConstraint, Index])
|
||||
|
||||
def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
|
||||
return sqla_compat._get_constraint_final_name(
|
||||
self.const, context.dialect
|
||||
|
||||
class _InspectorConv:
|
||||
__slots__ = ("inspector",)
|
||||
|
||||
def __init__(self, inspector):
|
||||
self.inspector = inspector
|
||||
|
||||
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, *args, **kw):
|
||||
return self._apply_reflectinfo_conv(
|
||||
self.inspector.get_indexes(*args, **kw)
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.const == other.const
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.const != other.const
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.const)
|
||||
|
||||
|
||||
class _uq_constraint_sig(_constraint_sig):
|
||||
is_index = False
|
||||
is_unique = True
|
||||
|
||||
def __init__(self, const: UniqueConstraint, impl: DefaultImpl) -> None:
|
||||
self.const = const
|
||||
self.name = const.name
|
||||
self.sig = ("UNIQUE_CONSTRAINT",) + impl.create_unique_constraint_sig(
|
||||
const
|
||||
def get_unique_constraints(self, *args, **kw):
|
||||
return self._apply_reflectinfo_conv(
|
||||
self.inspector.get_unique_constraints(*args, **kw)
|
||||
)
|
||||
|
||||
@property
|
||||
def column_names(self) -> List[str]:
|
||||
return [col.name for col in self.const.columns]
|
||||
|
||||
|
||||
class _ix_constraint_sig(_constraint_sig):
|
||||
is_index = True
|
||||
|
||||
def __init__(self, const: Index, impl: DefaultImpl) -> None:
|
||||
self.const = const
|
||||
self.name = const.name
|
||||
self.sig = ("INDEX",) + impl.create_index_sig(const)
|
||||
self.is_unique = bool(const.unique)
|
||||
|
||||
def md_name_to_sql_name(self, context: AutogenContext) -> Optional[str]:
|
||||
return sqla_compat._get_constraint_final_name(
|
||||
self.const, context.dialect
|
||||
def get_foreign_keys(self, *args, **kw):
|
||||
return self._apply_reflectinfo_conv(
|
||||
self.inspector.get_foreign_keys(*args, **kw)
|
||||
)
|
||||
|
||||
@property
|
||||
def column_names(self) -> Union[List[quoted_name], List[None]]:
|
||||
return sqla_compat._get_index_column_names(self.const)
|
||||
def reflect_table(self, table, *, include_columns):
|
||||
self.inspector.reflect_table(table, include_columns=include_columns)
|
||||
|
||||
|
||||
class _fk_constraint_sig(_constraint_sig):
|
||||
def __init__(
|
||||
self, const: ForeignKeyConstraint, include_options: bool = False
|
||||
) -> None:
|
||||
self.const = const
|
||||
self.name = const.name
|
||||
|
||||
(
|
||||
self.source_schema,
|
||||
self.source_table,
|
||||
self.source_columns,
|
||||
self.target_schema,
|
||||
self.target_table,
|
||||
self.target_columns,
|
||||
onupdate,
|
||||
ondelete,
|
||||
deferrable,
|
||||
initially,
|
||||
) = _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),
|
||||
)
|
||||
if include_options:
|
||||
self.sig += (
|
||||
(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",
|
||||
)
|
||||
# I had a cool version of this using _ReflectInfo, however that doesn't
|
||||
# work in 1.4 and it's not public API in 2.x. Then this is just a two
|
||||
# liner. So there's no competition...
|
||||
self._apply_constraint_conv(table.constraints)
|
||||
self._apply_constraint_conv(table.indexes)
|
||||
|
||||
|
||||
@comparators.dispatch_for("table")
|
||||
@@ -561,34 +523,34 @@ def _compare_indexes_and_uniques(
|
||||
|
||||
if conn_table is not None:
|
||||
# 1b. ... and from connection, if the table exists
|
||||
if hasattr(inspector, "get_unique_constraints"):
|
||||
try:
|
||||
conn_uniques = inspector.get_unique_constraints( # type:ignore[assignment] # noqa
|
||||
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 = [ # type:ignore[assignment]
|
||||
uq
|
||||
for uq in conn_uniques
|
||||
if autogen_context.run_name_filters(
|
||||
uq["name"],
|
||||
"unique_constraint",
|
||||
{"table_name": tname, "schema_name": schema},
|
||||
)
|
||||
]
|
||||
for uq in conn_uniques:
|
||||
if uq.get("duplicates_index"):
|
||||
unique_constraints_duplicate_unique_indexes = True
|
||||
try:
|
||||
conn_indexes = inspector.get_indexes( # type:ignore[assignment]
|
||||
conn_uniques = _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 = [ # type:ignore[assignment]
|
||||
uq
|
||||
for uq in conn_uniques
|
||||
if autogen_context.run_name_filters(
|
||||
uq["name"],
|
||||
"unique_constraint",
|
||||
{"table_name": tname, "schema_name": schema},
|
||||
)
|
||||
]
|
||||
for uq in conn_uniques:
|
||||
if uq.get("duplicates_index"):
|
||||
unique_constraints_duplicate_unique_indexes = True
|
||||
try:
|
||||
conn_indexes = _InspectorConv(inspector).get_indexes(
|
||||
tname, schema=schema
|
||||
)
|
||||
except NotImplementedError:
|
||||
@@ -639,7 +601,7 @@ def _compare_indexes_and_uniques(
|
||||
# 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
|
||||
autogen_context.migration_context.impl.correct_for_autogen_constraints(
|
||||
impl.correct_for_autogen_constraints(
|
||||
conn_uniques, # type: ignore[arg-type]
|
||||
conn_indexes, # type: ignore[arg-type]
|
||||
metadata_unique_constraints,
|
||||
@@ -651,31 +613,31 @@ def _compare_indexes_and_uniques(
|
||||
# Index and UniqueConstraint so we can easily work with them
|
||||
# interchangeably
|
||||
metadata_unique_constraints_sig = {
|
||||
_uq_constraint_sig(uq, impl) for uq in metadata_unique_constraints
|
||||
impl._create_metadata_constraint_sig(uq)
|
||||
for uq in metadata_unique_constraints
|
||||
}
|
||||
|
||||
metadata_indexes_sig = {
|
||||
_ix_constraint_sig(ix, impl) for ix in metadata_indexes
|
||||
impl._create_metadata_constraint_sig(ix) for ix in metadata_indexes
|
||||
}
|
||||
|
||||
conn_unique_constraints = {
|
||||
_uq_constraint_sig(uq, impl) for uq in conn_uniques
|
||||
impl._create_reflected_constraint_sig(uq) for uq in conn_uniques
|
||||
}
|
||||
|
||||
conn_indexes_sig = {_ix_constraint_sig(ix, impl) for ix in conn_indexes}
|
||||
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 # type:ignore[arg-type]
|
||||
)
|
||||
if isinstance(c, _ix_constraint_sig)
|
||||
or sqla_compat._constraint_is_named(c.const, autogen_context.dialect)
|
||||
for c in metadata_unique_constraints_sig.union(metadata_indexes_sig)
|
||||
if c.is_named
|
||||
}
|
||||
|
||||
conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _uq_constraint_sig]
|
||||
conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _ix_constraint_sig]
|
||||
conn_uniques_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
|
||||
conn_indexes_by_name: Dict[sqla_compat._ConstraintName, _constraint_sig]
|
||||
|
||||
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}
|
||||
@@ -694,13 +656,12 @@ def _compare_indexes_and_uniques(
|
||||
|
||||
# 6. index things by "column signature", to help with unnamed unique
|
||||
# constraints.
|
||||
conn_uniques_by_sig = {uq.sig: uq for uq in conn_unique_constraints}
|
||||
conn_uniques_by_sig = {uq.unnamed: uq for uq in conn_unique_constraints}
|
||||
metadata_uniques_by_sig = {
|
||||
uq.sig: uq for uq in metadata_unique_constraints_sig
|
||||
uq.unnamed: uq for uq in metadata_unique_constraints_sig
|
||||
}
|
||||
metadata_indexes_by_sig = {ix.sig: ix for ix in metadata_indexes_sig}
|
||||
unnamed_metadata_uniques = {
|
||||
uq.sig: uq
|
||||
uq.unnamed: uq
|
||||
for uq in metadata_unique_constraints_sig
|
||||
if not sqla_compat._constraint_is_named(
|
||||
uq.const, autogen_context.dialect
|
||||
@@ -715,18 +676,18 @@ def _compare_indexes_and_uniques(
|
||||
# 4. The backend may double up indexes as unique constraints and
|
||||
# vice versa (e.g. MySQL, Postgresql)
|
||||
|
||||
def obj_added(obj):
|
||||
if obj.is_index:
|
||||
def obj_added(obj: _constraint_sig):
|
||||
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 '%s' on %s",
|
||||
"Detected added index %r on '%s'",
|
||||
obj.name,
|
||||
", ".join(["'%s'" % obj.column_names]),
|
||||
obj.column_names,
|
||||
)
|
||||
else:
|
||||
elif is_uq_sig(obj):
|
||||
if not supports_unique_constraints:
|
||||
# can't report unique indexes as added if we don't
|
||||
# detect them
|
||||
@@ -741,13 +702,15 @@ def _compare_indexes_and_uniques(
|
||||
ops.AddConstraintOp.from_constraint(obj.const)
|
||||
)
|
||||
log.info(
|
||||
"Detected added unique constraint '%s' on %s",
|
||||
"Detected added unique constraint %r on '%s'",
|
||||
obj.name,
|
||||
", ".join(["'%s'" % obj.column_names]),
|
||||
obj.column_names,
|
||||
)
|
||||
else:
|
||||
assert False
|
||||
|
||||
def obj_removed(obj):
|
||||
if obj.is_index:
|
||||
def obj_removed(obj: _constraint_sig):
|
||||
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
|
||||
@@ -758,10 +721,8 @@ def _compare_indexes_and_uniques(
|
||||
obj.const, obj.name, "index", True, None
|
||||
):
|
||||
modify_ops.ops.append(ops.DropIndexOp.from_index(obj.const))
|
||||
log.info(
|
||||
"Detected removed index '%s' on '%s'", obj.name, tname
|
||||
)
|
||||
else:
|
||||
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
|
||||
@@ -773,33 +734,40 @@ def _compare_indexes_and_uniques(
|
||||
ops.DropConstraintOp.from_constraint(obj.const)
|
||||
)
|
||||
log.info(
|
||||
"Detected removed unique constraint '%s' on '%s'",
|
||||
"Detected removed unique constraint %r on %r",
|
||||
obj.name,
|
||||
tname,
|
||||
)
|
||||
else:
|
||||
assert False
|
||||
|
||||
def obj_changed(
|
||||
old: _constraint_sig,
|
||||
new: _constraint_sig,
|
||||
msg: str,
|
||||
):
|
||||
if is_index_sig(old):
|
||||
assert is_index_sig(new)
|
||||
|
||||
def obj_changed(old, new, msg):
|
||||
if old.is_index:
|
||||
if autogen_context.run_object_filters(
|
||||
new.const, new.name, "index", False, old.const
|
||||
):
|
||||
log.info(
|
||||
"Detected changed index '%s' on '%s':%s",
|
||||
old.name,
|
||||
tname,
|
||||
", ".join(msg),
|
||||
"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))
|
||||
else:
|
||||
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 '%s' on '%s':%s",
|
||||
"Detected changed unique constraint %r on %r: %s",
|
||||
old.name,
|
||||
tname,
|
||||
", ".join(msg),
|
||||
msg,
|
||||
)
|
||||
modify_ops.ops.append(
|
||||
ops.DropConstraintOp.from_constraint(old.const)
|
||||
@@ -807,18 +775,24 @@ def _compare_indexes_and_uniques(
|
||||
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: Union[_ix_constraint_sig, _uq_constraint_sig] = conn_names[
|
||||
removed_name
|
||||
]
|
||||
if not conn_obj.is_index and conn_obj.sig in unnamed_metadata_uniques:
|
||||
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 (
|
||||
conn_idx.sig not in metadata_indexes_by_sig
|
||||
and conn_uq.sig not in metadata_uniques_by_sig
|
||||
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)
|
||||
@@ -830,30 +804,36 @@ def _compare_indexes_and_uniques(
|
||||
|
||||
if existing_name in doubled_constraints:
|
||||
conn_uq, conn_idx = doubled_constraints[existing_name]
|
||||
if metadata_obj.is_index:
|
||||
if is_index_sig(metadata_obj):
|
||||
conn_obj = conn_idx
|
||||
else:
|
||||
conn_obj = conn_uq
|
||||
else:
|
||||
conn_obj = conn_names[existing_name]
|
||||
|
||||
if conn_obj.is_index != metadata_obj.is_index:
|
||||
if type(conn_obj) != type(metadata_obj):
|
||||
obj_removed(conn_obj)
|
||||
obj_added(metadata_obj)
|
||||
else:
|
||||
msg = []
|
||||
if conn_obj.is_unique != metadata_obj.is_unique:
|
||||
msg.append(
|
||||
" unique=%r to unique=%r"
|
||||
% (conn_obj.is_unique, metadata_obj.is_unique)
|
||||
)
|
||||
if conn_obj.sig != metadata_obj.sig:
|
||||
msg.append(
|
||||
" expression %r to %r" % (conn_obj.sig, metadata_obj.sig)
|
||||
)
|
||||
comparison = metadata_obj.compare_to_reflected(conn_obj)
|
||||
|
||||
if msg:
|
||||
obj_changed(conn_obj, metadata_obj, msg)
|
||||
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]
|
||||
@@ -893,7 +873,7 @@ def _correct_for_uq_duplicates_uix(
|
||||
}
|
||||
|
||||
unnamed_metadata_uqs = {
|
||||
_uq_constraint_sig(cons, impl).sig
|
||||
impl._create_metadata_constraint_sig(cons).unnamed
|
||||
for name, cons in metadata_cons_names
|
||||
if name is None
|
||||
}
|
||||
@@ -917,7 +897,9 @@ def _correct_for_uq_duplicates_uix(
|
||||
for overlap in uqs_dupe_indexes:
|
||||
if overlap not in metadata_uq_names:
|
||||
if (
|
||||
_uq_constraint_sig(uqs_dupe_indexes[overlap], impl).sig
|
||||
impl._create_reflected_constraint_sig(
|
||||
uqs_dupe_indexes[overlap]
|
||||
).unnamed
|
||||
not in unnamed_metadata_uqs
|
||||
):
|
||||
conn_unique_constraints.discard(uqs_dupe_indexes[overlap])
|
||||
@@ -1053,7 +1035,7 @@ def _normalize_computed_default(sqltext: str) -> str:
|
||||
|
||||
"""
|
||||
|
||||
return re.sub(r"[ \(\)'\"`\[\]]", "", sqltext).lower()
|
||||
return re.sub(r"[ \(\)'\"`\[\]\t\r\n]", "", sqltext).lower()
|
||||
|
||||
|
||||
def _compare_computed_default(
|
||||
@@ -1137,27 +1119,15 @@ def _compare_server_default(
|
||||
return False
|
||||
|
||||
if sqla_compat._server_default_is_computed(metadata_default):
|
||||
# return False in case of a computed column as the server
|
||||
# default. Note that DDL for adding or removing "GENERATED AS" from
|
||||
# an existing column is not currently known for any backend.
|
||||
# Once SQLAlchemy can reflect "GENERATED" as the "computed" element,
|
||||
# we would also want to ignore and/or warn for changes vs. the
|
||||
# metadata (or support backend specific DDL if applicable).
|
||||
if not sqla_compat.has_computed_reflection:
|
||||
return False
|
||||
|
||||
else:
|
||||
return (
|
||||
_compare_computed_default( # type:ignore[func-returns-value]
|
||||
autogen_context,
|
||||
alter_column_op,
|
||||
schema,
|
||||
tname,
|
||||
cname,
|
||||
conn_col,
|
||||
metadata_col,
|
||||
)
|
||||
)
|
||||
return _compare_computed_default( # type:ignore[func-returns-value]
|
||||
autogen_context,
|
||||
alter_column_op,
|
||||
schema,
|
||||
tname,
|
||||
cname,
|
||||
conn_col,
|
||||
metadata_col,
|
||||
)
|
||||
if sqla_compat._server_default_is_computed(conn_col_default):
|
||||
_warn_computed_not_supported(tname, cname)
|
||||
return False
|
||||
@@ -1243,8 +1213,8 @@ def _compare_foreign_keys(
|
||||
modify_table_ops: ModifyTableOps,
|
||||
schema: Optional[str],
|
||||
tname: Union[quoted_name, str],
|
||||
conn_table: Optional[Table],
|
||||
metadata_table: Optional[Table],
|
||||
conn_table: Table,
|
||||
metadata_table: Table,
|
||||
) -> None:
|
||||
# if we're doing CREATE TABLE, all FKs are created
|
||||
# inline within the table def
|
||||
@@ -1260,7 +1230,9 @@ def _compare_foreign_keys(
|
||||
|
||||
conn_fks_list = [
|
||||
fk
|
||||
for fk in inspector.get_foreign_keys(tname, schema=schema)
|
||||
for fk in _InspectorConv(inspector).get_foreign_keys(
|
||||
tname, schema=schema
|
||||
)
|
||||
if autogen_context.run_name_filters(
|
||||
fk["name"],
|
||||
"foreign_key_constraint",
|
||||
@@ -1268,15 +1240,12 @@ def _compare_foreign_keys(
|
||||
)
|
||||
]
|
||||
|
||||
backend_reflects_fk_options = bool(
|
||||
conn_fks_list and "options" in conn_fks_list[0]
|
||||
)
|
||||
|
||||
conn_fks = {
|
||||
_make_foreign_key(const, conn_table) # type: ignore[arg-type]
|
||||
for const in conn_fks_list
|
||||
_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(
|
||||
@@ -1284,17 +1253,24 @@ def _compare_foreign_keys(
|
||||
)
|
||||
|
||||
metadata_fks_sig = {
|
||||
_fk_constraint_sig(fk, include_options=backend_reflects_fk_options)
|
||||
for fk in metadata_fks
|
||||
impl._create_metadata_constraint_sig(fk) for fk in metadata_fks
|
||||
}
|
||||
|
||||
conn_fks_sig = {
|
||||
_fk_constraint_sig(fk, include_options=backend_reflects_fk_options)
|
||||
for fk in conn_fks
|
||||
impl._create_reflected_constraint_sig(fk) for fk in conn_fks
|
||||
}
|
||||
|
||||
conn_fks_by_sig = {c.sig: c for c in conn_fks_sig}
|
||||
metadata_fks_by_sig = {c.sig: c for c in metadata_fks_sig}
|
||||
# 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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 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
|
||||
@@ -15,7 +18,9 @@ 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
|
||||
@@ -25,7 +30,8 @@ from ..util import sqla_compat
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from sqlalchemy.sql.base import DialectKWArgs
|
||||
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
|
||||
@@ -45,8 +51,6 @@ if TYPE_CHECKING:
|
||||
from alembic.config import Config
|
||||
from alembic.operations.ops import MigrationScript
|
||||
from alembic.operations.ops import ModifyTableOps
|
||||
from alembic.util.sqla_compat import Computed
|
||||
from alembic.util.sqla_compat import Identity
|
||||
|
||||
|
||||
MAX_PYTHON_ARGS = 255
|
||||
@@ -164,21 +168,31 @@ def _render_modify_table(
|
||||
def _render_create_table_comment(
|
||||
autogen_context: AutogenContext, op: ops.CreateTableCommentOp
|
||||
) -> str:
|
||||
templ = (
|
||||
"{prefix}create_table_comment(\n"
|
||||
"{indent}'{tname}',\n"
|
||||
"{indent}{comment},\n"
|
||||
"{indent}existing_comment={existing},\n"
|
||||
"{indent}schema={schema}\n"
|
||||
")"
|
||||
)
|
||||
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,
|
||||
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=" ",
|
||||
)
|
||||
@@ -188,19 +202,28 @@ def _render_create_table_comment(
|
||||
def _render_drop_table_comment(
|
||||
autogen_context: AutogenContext, op: ops.DropTableCommentOp
|
||||
) -> str:
|
||||
templ = (
|
||||
"{prefix}drop_table_comment(\n"
|
||||
"{indent}'{tname}',\n"
|
||||
"{indent}existing_comment={existing},\n"
|
||||
"{indent}schema={schema}\n"
|
||||
")"
|
||||
)
|
||||
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,
|
||||
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=" ",
|
||||
)
|
||||
@@ -257,6 +280,9 @@ def _add_table(autogen_context: AutogenContext, op: ops.CreateTableOp) -> str:
|
||||
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
|
||||
|
||||
@@ -269,16 +295,20 @@ def _drop_table(autogen_context: AutogenContext, op: ops.DropTableOp) -> str:
|
||||
}
|
||||
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, item: DialectKWArgs
|
||||
autogen_context: AutogenContext, dialect_kwargs: _DialectArgView
|
||||
) -> list[str]:
|
||||
return [
|
||||
f"{key}={_render_potential_expr(val, autogen_context)}"
|
||||
for key, val in item.dialect_kwargs.items()
|
||||
for key, val in dialect_kwargs.items()
|
||||
]
|
||||
|
||||
|
||||
@@ -301,7 +331,9 @@ def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str:
|
||||
|
||||
assert index.table is not None
|
||||
|
||||
opts = _render_dialect_kwargs_items(autogen_context, index)
|
||||
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),
|
||||
@@ -310,9 +342,11 @@ def _add_index(autogen_context: AutogenContext, op: ops.CreateIndexOp) -> str:
|
||||
_get_index_rendered_expressions(index, autogen_context)
|
||||
),
|
||||
"unique": index.unique or False,
|
||||
"schema": (", schema=%r" % _ident(index.table.schema))
|
||||
if index.table.schema
|
||||
else "",
|
||||
"schema": (
|
||||
(", schema=%r" % _ident(index.table.schema))
|
||||
if index.table.schema
|
||||
else ""
|
||||
),
|
||||
"kwargs": ", " + ", ".join(opts) if opts else "",
|
||||
}
|
||||
return text
|
||||
@@ -331,7 +365,9 @@ def _drop_index(autogen_context: AutogenContext, op: ops.DropIndexOp) -> str:
|
||||
"%(prefix)sdrop_index(%(name)r, "
|
||||
"table_name=%(table_name)r%(schema)s%(kwargs)s)"
|
||||
)
|
||||
opts = _render_dialect_kwargs_items(autogen_context, index)
|
||||
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),
|
||||
@@ -353,6 +389,7 @@ def _add_unique_constraint(
|
||||
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)))
|
||||
@@ -382,9 +419,16 @@ def _add_fk_constraint(
|
||||
if value is not None:
|
||||
args.append("%s=%r" % (k, value))
|
||||
|
||||
return "%(prefix)screate_foreign_key(%(args)s)" % {
|
||||
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 ""
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -406,7 +450,7 @@ def _drop_constraint(
|
||||
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:
|
||||
@@ -415,32 +459,47 @@ def _drop_constraint(
|
||||
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 = op.schema, op.table_name, op.column
|
||||
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 = op.schema, op.table_name, op.column_name
|
||||
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)"
|
||||
@@ -448,6 +507,8 @@ def _drop_column(autogen_context: AutogenContext, op: ops.DropColumnOp) -> str:
|
||||
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 % {
|
||||
@@ -455,6 +516,7 @@ def _drop_column(autogen_context: AutogenContext, op: ops.DropColumnOp) -> str:
|
||||
"tname": _ident(tname),
|
||||
"cname": _ident(column_name),
|
||||
"schema": _ident(schema),
|
||||
"if_exists": if_exists,
|
||||
}
|
||||
return text
|
||||
|
||||
@@ -469,6 +531,7 @@ def _alter_column(
|
||||
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
|
||||
@@ -497,6 +560,8 @@ def _alter_column(
|
||||
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:
|
||||
@@ -549,23 +614,28 @@ def _render_potential_expr(
|
||||
value: Any,
|
||||
autogen_context: AutogenContext,
|
||||
*,
|
||||
wrap_in_text: bool = True,
|
||||
wrap_in_element: bool = True,
|
||||
is_server_default: bool = False,
|
||||
is_index: bool = False,
|
||||
) -> str:
|
||||
if isinstance(value, sql.ClauseElement):
|
||||
if wrap_in_text:
|
||||
template = "%(prefix)stext(%(sql)r)"
|
||||
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:
|
||||
template = "%(sql)r"
|
||||
|
||||
return template % {
|
||||
"prefix": _sqlalchemy_autogenerate_prefix(autogen_context),
|
||||
"sql": autogen_context.migration_context.impl.render_ddl_sql_expr(
|
||||
value, is_server_default=is_server_default, is_index=is_index
|
||||
),
|
||||
}
|
||||
|
||||
return repr(sql_text)
|
||||
else:
|
||||
return repr(value)
|
||||
|
||||
@@ -574,9 +644,11 @@ 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)
|
||||
(
|
||||
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
|
||||
]
|
||||
|
||||
@@ -591,16 +663,18 @@ def _uq_constraint(
|
||||
has_batch = autogen_context._has_batch
|
||||
|
||||
if constraint.deferrable:
|
||||
opts.append(("deferrable", str(constraint.deferrable)))
|
||||
opts.append(("deferrable", constraint.deferrable))
|
||||
if constraint.initially:
|
||||
opts.append(("initially", str(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_options = _render_dialect_kwargs_items(
|
||||
autogen_context, constraint.dialect_kwargs
|
||||
)
|
||||
|
||||
if alter:
|
||||
args = [repr(_render_gen_name(autogen_context, constraint.name))]
|
||||
@@ -704,7 +778,7 @@ def _render_column(
|
||||
+ [
|
||||
"%s=%s"
|
||||
% (key, _render_potential_expr(val, autogen_context))
|
||||
for key, val in sqla_compat._column_kwargs(column).items()
|
||||
for key, val in column.kwargs.items()
|
||||
]
|
||||
)
|
||||
),
|
||||
@@ -739,6 +813,8 @@ def _render_server_default(
|
||||
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))
|
||||
@@ -750,7 +826,7 @@ def _render_computed(
|
||||
computed: Computed, autogen_context: AutogenContext
|
||||
) -> str:
|
||||
text = _render_potential_expr(
|
||||
computed.sqltext, autogen_context, wrap_in_text=False
|
||||
computed.sqltext, autogen_context, wrap_in_element=False
|
||||
)
|
||||
|
||||
kwargs = {}
|
||||
@@ -776,6 +852,12 @@ def _render_identity(
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
@@ -794,7 +876,10 @@ def _repr_type(
|
||||
|
||||
mod = type(type_).__module__
|
||||
imports = autogen_context.imports
|
||||
if mod.startswith("sqlalchemy.dialects"):
|
||||
|
||||
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)
|
||||
@@ -806,8 +891,6 @@ def _repr_type(
|
||||
return "%s.%r" % (dname, type_)
|
||||
elif impl_rt:
|
||||
return impl_rt
|
||||
elif not _skip_variants and sqla_compat._type_has_variants(type_):
|
||||
return _render_Variant_type(type_, autogen_context)
|
||||
elif mod.startswith("sqlalchemy."):
|
||||
if "_render_%s_type" % type_.__visit_name__ in globals():
|
||||
fn = globals()["_render_%s_type" % type_.__visit_name__]
|
||||
@@ -834,7 +917,7 @@ def _render_Variant_type(
|
||||
) -> 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
|
||||
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)" % (
|
||||
@@ -925,13 +1008,13 @@ def _render_primary_key(
|
||||
def _fk_colspec(
|
||||
fk: ForeignKey,
|
||||
metadata_schema: Optional[str],
|
||||
namespace_metadata: MetaData,
|
||||
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() # type:ignore[attr-defined]
|
||||
colspec = fk._get_colspec()
|
||||
tokens = colspec.split(".")
|
||||
tname, colname = tokens[-2:]
|
||||
|
||||
@@ -949,7 +1032,10 @@ def _fk_colspec(
|
||||
# the FK constraint needs to be rendered in terms of the column
|
||||
# name.
|
||||
|
||||
if table_fullname in namespace_metadata.tables:
|
||||
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]
|
||||
@@ -980,7 +1066,7 @@ def _populate_render_fk_opts(
|
||||
def _render_foreign_key(
|
||||
constraint: ForeignKeyConstraint,
|
||||
autogen_context: AutogenContext,
|
||||
namespace_metadata: MetaData,
|
||||
namespace_metadata: Optional[MetaData],
|
||||
) -> Optional[str]:
|
||||
rendered = _user_defined_render("foreign_key", constraint, autogen_context)
|
||||
if rendered is not False:
|
||||
@@ -994,15 +1080,16 @@ def _render_foreign_key(
|
||||
|
||||
_populate_render_fk_opts(constraint, opts)
|
||||
|
||||
apply_metadata_schema = namespace_metadata.schema
|
||||
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(
|
||||
"%r" % _ident(cast("Column", f.parent).name)
|
||||
for f in constraint.elements
|
||||
repr(_ident(f.parent.name)) for f in constraint.elements
|
||||
),
|
||||
"refcols": ", ".join(
|
||||
repr(_fk_colspec(f, apply_metadata_schema, namespace_metadata))
|
||||
@@ -1043,12 +1130,10 @@ def _render_check_constraint(
|
||||
# ideally SQLAlchemy would give us more of a first class
|
||||
# way to detect this.
|
||||
if (
|
||||
constraint._create_rule # type:ignore[attr-defined]
|
||||
and hasattr(
|
||||
constraint._create_rule, "target" # type:ignore[attr-defined]
|
||||
)
|
||||
constraint._create_rule
|
||||
and hasattr(constraint._create_rule, "target")
|
||||
and isinstance(
|
||||
constraint._create_rule.target, # type:ignore[attr-defined]
|
||||
constraint._create_rule.target,
|
||||
sqltypes.TypeEngine,
|
||||
)
|
||||
):
|
||||
@@ -1060,11 +1145,13 @@ def _render_check_constraint(
|
||||
)
|
||||
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 "",
|
||||
"opts": (
|
||||
", " + (", ".join("%s=%s" % (k, v) for k, v in opts))
|
||||
if opts
|
||||
else ""
|
||||
),
|
||||
"sqltext": _render_potential_expr(
|
||||
constraint.sqltext, autogen_context, wrap_in_text=False
|
||||
constraint.sqltext, autogen_context, wrap_in_element=False
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1076,7 +1163,10 @@ def _execute_sql(autogen_context: AutogenContext, op: ops.ExecuteSQLOp) -> str:
|
||||
"Autogenerate rendering of SQL Expression language constructs "
|
||||
"not supported here; please use a plain SQL string"
|
||||
)
|
||||
return "op.execute(%r)" % op.sqltext
|
||||
return "{prefix}execute({sqltext!r})".format(
|
||||
prefix=_alembic_autogenerate_prefix(autogen_context),
|
||||
sqltext=op.sqltext,
|
||||
)
|
||||
|
||||
|
||||
renderers = default_renderers.branch()
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
@@ -16,12 +16,18 @@ 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 ..runtime.environment import _GetRevArg
|
||||
from ..operations.ops import UpgradeOps
|
||||
from ..runtime.migration import MigrationContext
|
||||
from ..script.revision import _GetRevArg
|
||||
|
||||
ProcessRevisionDirectiveFn = Callable[
|
||||
["MigrationContext", "_GetRevArg", List["MigrationScript"]], None
|
||||
]
|
||||
|
||||
|
||||
class Rewriter:
|
||||
@@ -52,15 +58,21 @@ class Rewriter:
|
||||
|
||||
_traverse = util.Dispatcher()
|
||||
|
||||
_chained: Optional[Rewriter] = None
|
||||
_chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = ()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.dispatch = util.Dispatcher()
|
||||
|
||||
def chain(self, other: Rewriter) -> Rewriter:
|
||||
def chain(
|
||||
self,
|
||||
other: Union[
|
||||
ProcessRevisionDirectiveFn,
|
||||
Rewriter,
|
||||
],
|
||||
) -> Rewriter:
|
||||
"""Produce a "chain" of this :class:`.Rewriter` to another.
|
||||
|
||||
This allows two rewriters to operate serially on a stream,
|
||||
This allows two or more rewriters to operate serially on a stream,
|
||||
e.g.::
|
||||
|
||||
writer1 = autogenerate.Rewriter()
|
||||
@@ -89,7 +101,7 @@ class Rewriter:
|
||||
"""
|
||||
wr = self.__class__.__new__(self.__class__)
|
||||
wr.__dict__.update(self.__dict__)
|
||||
wr._chained = other
|
||||
wr._chained += (other,)
|
||||
return wr
|
||||
|
||||
def rewrites(
|
||||
@@ -101,7 +113,7 @@ class Rewriter:
|
||||
Type[CreateTableOp],
|
||||
Type[ModifyTableOps],
|
||||
],
|
||||
) -> Callable:
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a function as rewriter for a given type.
|
||||
|
||||
The function should receive three arguments, which are
|
||||
@@ -146,8 +158,8 @@ class Rewriter:
|
||||
directives: List[MigrationScript],
|
||||
) -> None:
|
||||
self.process_revision_directives(context, revision, directives)
|
||||
if self._chained:
|
||||
self._chained(context, revision, directives)
|
||||
for process_revision_directives in self._chained:
|
||||
process_revision_directives(context, revision, directives)
|
||||
|
||||
@_traverse.dispatch_for(ops.MigrationScript)
|
||||
def _traverse_script(
|
||||
@@ -156,7 +168,7 @@ class Rewriter:
|
||||
revision: _GetRevArg,
|
||||
directive: MigrationScript,
|
||||
) -> None:
|
||||
upgrade_ops_list = []
|
||||
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:
|
||||
@@ -164,9 +176,10 @@ class Rewriter:
|
||||
"Can only return single object for UpgradeOps traverse"
|
||||
)
|
||||
upgrade_ops_list.append(ret[0])
|
||||
|
||||
directive.upgrade_ops = upgrade_ops_list
|
||||
|
||||
downgrade_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:
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# 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
|
||||
@@ -10,6 +13,7 @@ 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
|
||||
@@ -18,7 +22,7 @@ if TYPE_CHECKING:
|
||||
from .runtime.environment import ProcessRevisionDirectiveFn
|
||||
|
||||
|
||||
def list_templates(config: Config):
|
||||
def list_templates(config: Config) -> None:
|
||||
"""List available templates.
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
@@ -26,12 +30,10 @@ def list_templates(config: Config):
|
||||
"""
|
||||
|
||||
config.print_stdout("Available templates:\n")
|
||||
for tempname in os.listdir(config.get_template_directory()):
|
||||
with open(
|
||||
os.path.join(config.get_template_directory(), tempname, "README")
|
||||
) as readme:
|
||||
for tempname in config._get_template_path().iterdir():
|
||||
with (tempname / "README").open() as readme:
|
||||
synopsis = next(readme).rstrip()
|
||||
config.print_stdout("%s - %s", tempname, synopsis)
|
||||
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")
|
||||
@@ -47,7 +49,7 @@ def init(
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
|
||||
:param directory: string path of the target directory
|
||||
:param directory: string path of the target directory.
|
||||
|
||||
:param template: string name of the migration environment template to
|
||||
use.
|
||||
@@ -57,65 +59,136 @@ def init(
|
||||
|
||||
"""
|
||||
|
||||
if os.access(directory, os.F_OK) and os.listdir(directory):
|
||||
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
|
||||
"Directory %s already exists and is not empty" % directory_path
|
||||
)
|
||||
|
||||
template_dir = os.path.join(config.get_template_directory(), template)
|
||||
if not os.access(template_dir, os.F_OK):
|
||||
raise util.CommandError("No such template %r" % template)
|
||||
template_path = config._get_template_path() / template
|
||||
|
||||
if not os.access(directory, os.F_OK):
|
||||
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 {os.path.abspath(directory)!r}",
|
||||
f"Creating directory {directory_path.absolute()}",
|
||||
**config.messaging_opts,
|
||||
):
|
||||
os.makedirs(directory)
|
||||
os.makedirs(directory_path)
|
||||
|
||||
versions = os.path.join(directory, "versions")
|
||||
versions = directory_path / "versions"
|
||||
with util.status(
|
||||
f"Creating directory {os.path.abspath(versions)!r}",
|
||||
f"Creating directory {versions.absolute()}",
|
||||
**config.messaging_opts,
|
||||
):
|
||||
os.makedirs(versions)
|
||||
|
||||
script = ScriptDirectory(directory)
|
||||
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
|
||||
|
||||
config_file: str | None = None
|
||||
for file_ in os.listdir(template_dir):
|
||||
file_path = os.path.join(template_dir, file_)
|
||||
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 = os.path.abspath(config.config_file_name)
|
||||
if os.access(config_file, os.F_OK):
|
||||
config_file = pathlib.Path(config.config_file_name).absolute()
|
||||
if config_file.exists():
|
||||
util.msg(
|
||||
f"File {config_file!r} already exists, skipping",
|
||||
f"File {config_file} already exists, skipping",
|
||||
**config.messaging_opts,
|
||||
)
|
||||
else:
|
||||
script._generate_template(
|
||||
file_path, config_file, script_location=directory
|
||||
file_path,
|
||||
config_file,
|
||||
script_location=ini_script_location_directory,
|
||||
)
|
||||
elif os.path.isfile(file_path):
|
||||
output_file = os.path.join(directory, file_)
|
||||
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 [
|
||||
os.path.join(os.path.abspath(directory), "__init__.py"),
|
||||
os.path.join(os.path.abspath(versions), "__init__.py"),
|
||||
directory_path.absolute() / "__init__.py",
|
||||
versions.absolute() / "__init__.py",
|
||||
]:
|
||||
with util.status(f"Adding {path!r}", **config.messaging_opts):
|
||||
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
|
||||
util.msg(
|
||||
"Please edit configuration/connection/logging "
|
||||
f"settings in {config_file!r} before proceeding.",
|
||||
**config.messaging_opts,
|
||||
)
|
||||
|
||||
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(
|
||||
@@ -126,7 +199,7 @@ def revision(
|
||||
head: str = "head",
|
||||
splice: bool = False,
|
||||
branch_label: Optional[_RevIdType] = None,
|
||||
version_path: Optional[str] = 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,
|
||||
@@ -172,7 +245,7 @@ def revision(
|
||||
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`
|
||||
use of :func:`.command.revision`.
|
||||
|
||||
"""
|
||||
|
||||
@@ -196,7 +269,9 @@ def revision(
|
||||
process_revision_directives=process_revision_directives,
|
||||
)
|
||||
|
||||
environment = util.asbool(config.get_main_option("revision_environment"))
|
||||
environment = util.asbool(
|
||||
config.get_alembic_option("revision_environment")
|
||||
)
|
||||
|
||||
if autogenerate:
|
||||
environment = True
|
||||
@@ -290,10 +365,15 @@ def check(config: "Config") -> None:
|
||||
# the revision_context now has MigrationScript structure(s) present.
|
||||
|
||||
migration_script = revision_context.generated_revisions[-1]
|
||||
diffs = migration_script.upgrade_ops.as_diffs()
|
||||
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}"
|
||||
f"New upgrade operations detected: {diffs}",
|
||||
revision_context=revision_context,
|
||||
diffs=diffs,
|
||||
)
|
||||
else:
|
||||
config.print_stdout("No new upgrade operations detected.")
|
||||
@@ -310,9 +390,11 @@ def merge(
|
||||
|
||||
:param config: a :class:`.Config` instance
|
||||
|
||||
:param message: string message to apply to the revision
|
||||
:param revisions: The revisions to merge.
|
||||
|
||||
:param branch_label: string label name to apply to the new revision
|
||||
: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.
|
||||
@@ -329,7 +411,9 @@ def merge(
|
||||
# e.g. multiple databases
|
||||
}
|
||||
|
||||
environment = util.asbool(config.get_main_option("revision_environment"))
|
||||
environment = util.asbool(
|
||||
config.get_alembic_option("revision_environment")
|
||||
)
|
||||
|
||||
if environment:
|
||||
|
||||
@@ -365,9 +449,10 @@ def upgrade(
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: string revision target or range for --sql mode
|
||||
: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 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`
|
||||
@@ -408,9 +493,10 @@ def downgrade(
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: string revision target or range for --sql mode
|
||||
: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 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`
|
||||
@@ -444,12 +530,13 @@ def downgrade(
|
||||
script.run_env()
|
||||
|
||||
|
||||
def show(config, rev):
|
||||
def show(config: Config, rev: str) -> None:
|
||||
"""Show the revision(s) denoted by the given symbol.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: string revision target
|
||||
:param rev: string revision target. May be ``"current"`` to show the
|
||||
revision(s) currently applied in the database.
|
||||
|
||||
"""
|
||||
|
||||
@@ -479,7 +566,7 @@ def history(
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param rev_range: string revision range
|
||||
:param rev_range: string revision range.
|
||||
|
||||
:param verbose: output in verbose mode.
|
||||
|
||||
@@ -499,7 +586,7 @@ def history(
|
||||
base = head = None
|
||||
|
||||
environment = (
|
||||
util.asbool(config.get_main_option("revision_environment"))
|
||||
util.asbool(config.get_alembic_option("revision_environment"))
|
||||
or indicate_current
|
||||
)
|
||||
|
||||
@@ -538,7 +625,9 @@ def history(
|
||||
_display_history(config, script, base, head)
|
||||
|
||||
|
||||
def heads(config, verbose=False, resolve_dependencies=False):
|
||||
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.
|
||||
@@ -563,7 +652,7 @@ def heads(config, verbose=False, resolve_dependencies=False):
|
||||
)
|
||||
|
||||
|
||||
def branches(config, verbose=False):
|
||||
def branches(config: Config, verbose: bool = False) -> None:
|
||||
"""Show current branch points.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
@@ -633,7 +722,9 @@ def stamp(
|
||||
: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.
|
||||
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.
|
||||
@@ -723,7 +814,7 @@ def ensure_version(config: Config, sql: bool = False) -> None:
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param sql: use ``--sql`` mode
|
||||
:param sql: use ``--sql`` mode.
|
||||
|
||||
.. versionadded:: 1.7.6
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Collection
|
||||
from typing import ContextManager
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
@@ -14,11 +13,14 @@ 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
|
||||
@@ -39,7 +41,9 @@ if TYPE_CHECKING:
|
||||
|
||||
### end imports ###
|
||||
|
||||
def begin_transaction() -> Union[_ProxyTransaction, ContextManager[None]]:
|
||||
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
|
||||
@@ -97,7 +101,7 @@ def configure(
|
||||
tag: Optional[str] = None,
|
||||
template_args: Optional[Dict[str, Any]] = None,
|
||||
render_as_batch: bool = False,
|
||||
target_metadata: Optional[MetaData] = None,
|
||||
target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
||||
include_name: Optional[
|
||||
Callable[
|
||||
[
|
||||
@@ -159,8 +163,8 @@ def configure(
|
||||
MigrationContext,
|
||||
Column[Any],
|
||||
Column[Any],
|
||||
TypeEngine,
|
||||
TypeEngine,
|
||||
TypeEngine[Any],
|
||||
TypeEngine[Any],
|
||||
],
|
||||
Optional[bool],
|
||||
],
|
||||
@@ -635,7 +639,8 @@ def configure(
|
||||
"""
|
||||
|
||||
def execute(
|
||||
sql: Union[Executable, str], execution_options: Optional[dict] = None
|
||||
sql: Union[Executable, str],
|
||||
execution_options: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Execute the given SQL using the current change context.
|
||||
|
||||
@@ -758,7 +763,11 @@ def get_x_argument(
|
||||
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.
|
||||
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::
|
||||
@@ -800,7 +809,7 @@ def is_offline_mode() -> bool:
|
||||
|
||||
"""
|
||||
|
||||
def is_transactional_ddl():
|
||||
def is_transactional_ddl() -> bool:
|
||||
"""Return True if the context is configured to expect a
|
||||
transactional DDL capable backend.
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ from . import mysql
|
||||
from . import oracle
|
||||
from . import postgresql
|
||||
from . import sqlite
|
||||
from .impl import DefaultImpl
|
||||
from .impl import DefaultImpl as DefaultImpl
|
||||
|
||||
329
venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
Normal file
329
venv/lib/python3.12/site-packages/alembic/ddl/_autogen.py
Normal file
@@ -0,0 +1,329 @@
|
||||
# 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 ClassVar
|
||||
from typing import Dict
|
||||
from typing import Generic
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
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
|
||||
|
||||
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 typing_extensions import TypeGuard
|
||||
|
||||
from .. import util
|
||||
from ..util import sqla_compat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from alembic.autogenerate.api import AutogenContext
|
||||
from alembic.ddl.impl import DefaultImpl
|
||||
|
||||
CompareConstraintType = Union[Constraint, Index]
|
||||
|
||||
_C = TypeVar("_C", bound=CompareConstraintType)
|
||||
|
||||
_clsreg: Dict[str, Type[_constraint_sig]] = {}
|
||||
|
||||
|
||||
class ComparisonResult(NamedTuple):
|
||||
status: Literal["equal", "different", "skip"]
|
||||
message: str
|
||||
|
||||
@property
|
||||
def is_equal(self) -> 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
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
@@ -22,6 +25,8 @@ from ..util.sqla_compat import _table_for_constraint # noqa
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import Computed
|
||||
from sqlalchemy import Identity
|
||||
from sqlalchemy.sql.compiler import Compiled
|
||||
from sqlalchemy.sql.compiler import DDLCompiler
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
@@ -30,14 +35,11 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .impl import DefaultImpl
|
||||
from ..util.sqla_compat import Computed
|
||||
from ..util.sqla_compat import Identity
|
||||
|
||||
_ServerDefault = Union["TextClause", "FetchedValue", "Function[Any]", str]
|
||||
|
||||
|
||||
class AlterTable(DDLElement):
|
||||
|
||||
"""Represent an ALTER TABLE statement.
|
||||
|
||||
Only the string name and optional schema name of the table
|
||||
@@ -152,17 +154,24 @@ class AddColumn(AlterTable):
|
||||
name: str,
|
||||
column: Column[Any],
|
||||
schema: Optional[Union[quoted_name, str]] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
super().__init__(name, schema=schema)
|
||||
self.column = column
|
||||
self.if_not_exists = if_not_exists
|
||||
|
||||
|
||||
class DropColumn(AlterTable):
|
||||
def __init__(
|
||||
self, name: str, column: Column[Any], schema: Optional[str] = None
|
||||
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):
|
||||
@@ -187,7 +196,9 @@ def visit_rename_table(
|
||||
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, **kw),
|
||||
add_column(
|
||||
compiler, element.column, if_not_exists=element.if_not_exists, **kw
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -195,7 +206,9 @@ def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
|
||||
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, **kw),
|
||||
drop_column(
|
||||
compiler, element.column.name, if_exists=element.if_exists, **kw
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -235,9 +248,11 @@ def visit_column_default(
|
||||
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",
|
||||
(
|
||||
"SET DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DROP DEFAULT"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -295,9 +310,13 @@ def format_server_default(
|
||||
compiler: DDLCompiler,
|
||||
default: Optional[_ServerDefault],
|
||||
) -> str:
|
||||
return compiler.get_column_default_string(
|
||||
# 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:
|
||||
@@ -312,16 +331,29 @@ def alter_table(
|
||||
return "ALTER TABLE %s" % format_table_name(compiler, name, schema)
|
||||
|
||||
|
||||
def drop_column(compiler: DDLCompiler, name: str, **kw) -> str:
|
||||
return "DROP COLUMN %s" % format_column_name(compiler, name)
|
||||
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], **kw) -> str:
|
||||
text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
|
||||
def add_column(
|
||||
compiler: DDLCompiler,
|
||||
column: Column[Any],
|
||||
if_not_exists: Optional[bool] = None,
|
||||
**kw,
|
||||
) -> str:
|
||||
text = "ADD COLUMN %s%s" % (
|
||||
"IF NOT EXISTS " if if_not_exists else "",
|
||||
compiler.get_column_specification(column, **kw),
|
||||
)
|
||||
|
||||
const = " ".join(
|
||||
compiler.process(constraint) for constraint in column.constraints
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
@@ -8,6 +11,7 @@ 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
|
||||
@@ -17,10 +21,18 @@ 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
|
||||
|
||||
@@ -34,13 +46,10 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
from sqlalchemy.sql import ClauseElement
|
||||
from sqlalchemy.sql import Executable
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
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 Table
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
from sqlalchemy.sql.selectable import TableClause
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
@@ -50,6 +59,8 @@ if TYPE_CHECKING:
|
||||
from ..operations.batch import ApplyBatchImpl
|
||||
from ..operations.batch import BatchOperationsImpl
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImplMeta(type):
|
||||
def __init__(
|
||||
@@ -66,11 +77,8 @@ class ImplMeta(type):
|
||||
|
||||
_impls: Dict[str, Type[DefaultImpl]] = {}
|
||||
|
||||
Params = namedtuple("Params", ["token0", "tokens", "args", "kwargs"])
|
||||
|
||||
|
||||
class DefaultImpl(metaclass=ImplMeta):
|
||||
|
||||
"""Provide the entrypoint for major migration operations,
|
||||
including database-specific behavioral variances.
|
||||
|
||||
@@ -130,6 +138,40 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
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:
|
||||
@@ -161,16 +203,15 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
def _exec(
|
||||
self,
|
||||
construct: Union[Executable, str],
|
||||
execution_options: Optional[dict[str, Any]] = None,
|
||||
multiparams: Sequence[dict] = (),
|
||||
params: Dict[str, Any] = util.immutabledict(),
|
||||
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 or params:
|
||||
# TODO: coverage
|
||||
raise Exception("Execution arguments not allowed with 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(
|
||||
@@ -193,11 +234,16 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
assert conn is not None
|
||||
if execution_options:
|
||||
conn = conn.execution_options(**execution_options)
|
||||
if params:
|
||||
assert isinstance(multiparams, tuple)
|
||||
multiparams += (params,)
|
||||
|
||||
return conn.execute(construct, multiparams)
|
||||
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,
|
||||
@@ -210,8 +256,11 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Union[_ServerDefault, Literal[False]] = False,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
@@ -322,25 +371,40 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
self,
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[Union[str, quoted_name]] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
self._exec(base.AddColumn(table_name, column, schema=schema))
|
||||
self._exec(
|
||||
base.AddColumn(
|
||||
table_name,
|
||||
column,
|
||||
schema=schema,
|
||||
if_not_exists=if_not_exists,
|
||||
)
|
||||
)
|
||||
|
||||
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))
|
||||
self._exec(
|
||||
base.DropColumn(
|
||||
table_name, column, schema=schema, if_exists=if_exists
|
||||
)
|
||||
)
|
||||
|
||||
def add_constraint(self, const: Any) -> None:
|
||||
if const._create_rule is None or const._create_rule(self):
|
||||
self._exec(schema.AddConstraint(const))
|
||||
|
||||
def drop_constraint(self, const: Constraint) -> None:
|
||||
self._exec(schema.DropConstraint(const))
|
||||
def drop_constraint(self, const: Constraint, **kw: Any) -> None:
|
||||
self._exec(schema.DropConstraint(const, **kw))
|
||||
|
||||
def rename_table(
|
||||
self,
|
||||
@@ -352,11 +416,11 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
base.RenameTable(old_table_name, new_table_name, schema=schema)
|
||||
)
|
||||
|
||||
def create_table(self, table: Table) -> None:
|
||||
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))
|
||||
self._exec(schema.CreateTable(table, **kw))
|
||||
table.dispatch.after_create(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
@@ -375,11 +439,11 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
if comment and with_comment:
|
||||
self.create_column_comment(column)
|
||||
|
||||
def drop_table(self, table: Table) -> None:
|
||||
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))
|
||||
self._exec(schema.DropTable(table, **kw))
|
||||
table.dispatch.after_drop(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
@@ -393,7 +457,7 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
def drop_table_comment(self, table: Table) -> None:
|
||||
self._exec(schema.DropTableComment(table))
|
||||
|
||||
def create_column_comment(self, column: ColumnElement[Any]) -> None:
|
||||
def create_column_comment(self, column: Column[Any]) -> None:
|
||||
self._exec(schema.SetColumnComment(column))
|
||||
|
||||
def drop_index(self, index: Index, **kw: Any) -> None:
|
||||
@@ -412,15 +476,19 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
if self.as_sql:
|
||||
for row in rows:
|
||||
self._exec(
|
||||
sqla_compat._insert_inline(table).values(
|
||||
table.insert()
|
||||
.inline()
|
||||
.values(
|
||||
**{
|
||||
k: sqla_compat._literal_bindparam(
|
||||
k, v, type_=table.c[k].type
|
||||
k: (
|
||||
sqla_compat._literal_bindparam(
|
||||
k, v, type_=table.c[k].type
|
||||
)
|
||||
if not isinstance(
|
||||
v, sqla_compat._literal_bindparam
|
||||
)
|
||||
else v
|
||||
)
|
||||
if not isinstance(
|
||||
v, sqla_compat._literal_bindparam
|
||||
)
|
||||
else v
|
||||
for k, v in row.items()
|
||||
}
|
||||
)
|
||||
@@ -428,16 +496,13 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
else:
|
||||
if rows:
|
||||
if multiinsert:
|
||||
self._exec(
|
||||
sqla_compat._insert_inline(table), multiparams=rows
|
||||
)
|
||||
self._exec(table.insert().inline(), multiparams=rows)
|
||||
else:
|
||||
for row in rows:
|
||||
self._exec(
|
||||
sqla_compat._insert_inline(table).values(**row)
|
||||
)
|
||||
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
|
||||
@@ -452,9 +517,9 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
# varchar character set utf8
|
||||
#
|
||||
|
||||
tokens = re.findall(r"[\w\-_]+|\(.+?\)", definition)
|
||||
tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition)
|
||||
|
||||
term_tokens = []
|
||||
term_tokens: List[str] = []
|
||||
paren_term = None
|
||||
|
||||
for token in tokens:
|
||||
@@ -466,6 +531,7 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
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("=")
|
||||
@@ -642,7 +708,7 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
diff, ignored = _compare_identity_options(
|
||||
metadata_identity,
|
||||
inspector_identity,
|
||||
sqla_compat.Identity(),
|
||||
schema.Identity(),
|
||||
skip={"always"},
|
||||
)
|
||||
|
||||
@@ -664,15 +730,96 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
bool(diff) or bool(metadata_identity) != bool(inspector_identity),
|
||||
)
|
||||
|
||||
def create_index_sig(self, index: Index) -> Tuple[Any, ...]:
|
||||
# order of col matters in an index
|
||||
return tuple(col.name for col in index.columns)
|
||||
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_unique_constraint_sig(
|
||||
self, const: UniqueConstraint
|
||||
) -> Tuple[Any, ...]:
|
||||
# order of col does not matters in an unique constraint
|
||||
return tuple(sorted([col.name for col in const.columns]))
|
||||
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}
|
||||
@@ -697,6 +844,13 @@ class DefaultImpl(metaclass=ImplMeta):
|
||||
return reflected_object.get("dialect_options", {})
|
||||
|
||||
|
||||
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],
|
||||
@@ -735,12 +889,13 @@ def _compare_identity_options(
|
||||
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, # type: ignore[union-attr]
|
||||
default_io.dialect_kwargs,
|
||||
getattr(inspector_io, "dialect_kwargs", {}),
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -9,7 +12,6 @@ from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.schema import Column
|
||||
from sqlalchemy.schema import CreateIndex
|
||||
from sqlalchemy.sql.base import Executable
|
||||
@@ -30,6 +32,7 @@ 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
|
||||
@@ -80,10 +83,11 @@ class MSSQLImpl(DefaultImpl):
|
||||
if self.as_sql and self.batch_separator:
|
||||
self.static_output(self.batch_separator)
|
||||
|
||||
def alter_column( # type:ignore[override]
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
@@ -199,6 +203,7 @@ class MSSQLImpl(DefaultImpl):
|
||||
self,
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -8,7 +11,9 @@ from typing import Union
|
||||
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql import elements
|
||||
from sqlalchemy.sql import functions
|
||||
from sqlalchemy.sql import operators
|
||||
|
||||
from .base import alter_table
|
||||
from .base import AlterColumn
|
||||
@@ -20,16 +25,16 @@ from .base import format_column_name
|
||||
from .base import format_server_default
|
||||
from .impl import DefaultImpl
|
||||
from .. import util
|
||||
from ..autogenerate import compare
|
||||
from ..util import sqla_compat
|
||||
from ..util.sqla_compat import _is_mariadb
|
||||
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
|
||||
|
||||
@@ -46,12 +51,40 @@ class MySQLImpl(DefaultImpl):
|
||||
)
|
||||
type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"]
|
||||
|
||||
def alter_column( # type:ignore[override]
|
||||
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: Union[_ServerDefault, Literal[False]] = False,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
@@ -92,21 +125,29 @@ class MySQLImpl(DefaultImpl):
|
||||
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,
|
||||
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,
|
||||
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 (
|
||||
@@ -121,21 +162,29 @@ class MySQLImpl(DefaultImpl):
|
||||
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,
|
||||
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,
|
||||
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:
|
||||
@@ -148,6 +197,7 @@ class MySQLImpl(DefaultImpl):
|
||||
def drop_constraint(
|
||||
self,
|
||||
const: Constraint,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
|
||||
return
|
||||
@@ -157,12 +207,11 @@ class MySQLImpl(DefaultImpl):
|
||||
def _is_mysql_allowed_functional_default(
|
||||
self,
|
||||
type_: Optional[TypeEngine],
|
||||
server_default: Union[_ServerDefault, Literal[False]],
|
||||
server_default: Optional[Union[_ServerDefault, Literal[False]]],
|
||||
) -> bool:
|
||||
return (
|
||||
type_ is not None
|
||||
and type_._type_affinity # type:ignore[attr-defined]
|
||||
is sqltypes.DateTime
|
||||
and type_._type_affinity is sqltypes.DateTime
|
||||
and server_default is not None
|
||||
)
|
||||
|
||||
@@ -272,10 +321,12 @@ class MySQLImpl(DefaultImpl):
|
||||
|
||||
def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
|
||||
conn_fk_by_sig = {
|
||||
compare._fk_constraint_sig(fk).sig: fk for fk in conn_fks
|
||||
self._create_reflected_constraint_sig(fk).unnamed_no_options: fk
|
||||
for fk in conn_fks
|
||||
}
|
||||
metadata_fk_by_sig = {
|
||||
compare._fk_constraint_sig(fk).sig: fk for fk in metadata_fks
|
||||
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):
|
||||
@@ -307,7 +358,7 @@ class MySQLAlterDefault(AlterColumn):
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
default: _ServerDefault,
|
||||
default: Optional[_ServerDefault],
|
||||
schema: Optional[str] = None,
|
||||
) -> None:
|
||||
super(AlterColumn, self).__init__(name, schema=schema)
|
||||
@@ -365,9 +416,11 @@ def _mysql_alter_default(
|
||||
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",
|
||||
(
|
||||
"SET DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DROP DEFAULT"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -454,7 +507,7 @@ def _mysql_drop_constraint(
|
||||
# note that SQLAlchemy as of 1.2 does not yet support
|
||||
# DROP CONSTRAINT for MySQL/MariaDB, so we implement fully
|
||||
# here.
|
||||
if _is_mariadb(compiler.dialect):
|
||||
if compiler.dialect.is_mariadb:
|
||||
return "ALTER TABLE %s DROP CONSTRAINT %s" % (
|
||||
compiler.preparer.format_table(constraint.table),
|
||||
compiler.preparer.format_constraint(constraint),
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -5,7 +8,6 @@ from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql import sqltypes
|
||||
|
||||
from .base import AddColumn
|
||||
@@ -22,6 +24,7 @@ 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
|
||||
@@ -138,9 +141,11 @@ def visit_column_default(
|
||||
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",
|
||||
(
|
||||
"DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DEFAULT NULL"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
@@ -13,18 +16,19 @@ 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 import operators
|
||||
from sqlalchemy.sql.elements import ColumnClause
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.elements import UnaryExpression
|
||||
from sqlalchemy.sql.functions import FunctionElement
|
||||
from sqlalchemy.types import NULLTYPE
|
||||
|
||||
@@ -32,12 +36,12 @@ from .base import alter_column
|
||||
from .base import alter_table
|
||||
from .base import AlterColumn
|
||||
from .base import ColumnComment
|
||||
from .base import compiles
|
||||
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
|
||||
@@ -46,6 +50,8 @@ 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
|
||||
@@ -130,25 +136,28 @@ class PostgresqlImpl(DefaultImpl):
|
||||
metadata_default = metadata_column.server_default.arg
|
||||
|
||||
if isinstance(metadata_default, str):
|
||||
if not isinstance(inspector_column.type, Numeric):
|
||||
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
|
||||
return not self.connection.scalar(
|
||||
sqla_compat._select(
|
||||
literal_column(conn_col_default) == metadata_default
|
||||
)
|
||||
conn = self.connection
|
||||
assert conn is not None
|
||||
return not conn.scalar(
|
||||
select(literal_column(conn_col_default) == metadata_default)
|
||||
)
|
||||
|
||||
def alter_column( # type:ignore[override]
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Union[_ServerDefault, Literal[False]] = False,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
@@ -214,7 +223,8 @@ class PostgresqlImpl(DefaultImpl):
|
||||
"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.relname=:seqname"
|
||||
"where c.relkind='S' and "
|
||||
"c.oid=cast(:seqname as regclass)"
|
||||
),
|
||||
seqname=seq_match.group(1),
|
||||
).first()
|
||||
@@ -252,62 +262,60 @@ class PostgresqlImpl(DefaultImpl):
|
||||
if not sqla_compat.sqla_2:
|
||||
self._skip_functional_indexes(metadata_indexes, conn_indexes)
|
||||
|
||||
def _cleanup_index_expr(
|
||||
self, index: Index, expr: str, remove_suffix: str
|
||||
) -> str:
|
||||
# start = expr
|
||||
# pg behavior regarding modifiers
|
||||
# | # | compiled sql | returned sql | regexp. group is removed |
|
||||
# | - | ---------------- | -----------------| ------------------------ |
|
||||
# | 1 | nulls first | nulls first | - |
|
||||
# | 2 | nulls last | | (?<! desc)( nulls last)$ |
|
||||
# | 3 | asc | | ( asc)$ |
|
||||
# | 4 | asc nulls first | nulls first | ( asc) nulls first$ |
|
||||
# | 5 | asc nulls last | | ( asc nulls last)$ |
|
||||
# | 6 | desc | desc | - |
|
||||
# | 7 | desc nulls first | desc | desc( nulls first)$ |
|
||||
# | 8 | desc nulls last | desc nulls last | - |
|
||||
_default_modifiers_re = ( # order of case 2 and 5 matters
|
||||
re.compile("( asc nulls last)$"), # case 5
|
||||
re.compile("(?<! desc)( nulls last)$"), # case 2
|
||||
re.compile("( asc)$"), # case 3
|
||||
re.compile("( asc) nulls first$"), # case 4
|
||||
re.compile(" desc( nulls first)$"), # case 7
|
||||
)
|
||||
|
||||
def _cleanup_index_expr(self, index: Index, expr: str) -> 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()}.", "")
|
||||
|
||||
while expr and expr[0] == "(" and expr[-1] == ")":
|
||||
expr = expr[1:-1]
|
||||
if "::" in expr:
|
||||
# strip :: cast. types can have spaces in them
|
||||
expr = re.sub(r"(::[\w ]+\w)", "", expr)
|
||||
|
||||
if remove_suffix and expr.endswith(remove_suffix):
|
||||
expr = expr[: -len(remove_suffix)]
|
||||
while expr and expr[0] == "(" and expr[-1] == ")":
|
||||
expr = expr[1:-1]
|
||||
|
||||
# print(f"START: {start} END: {expr}")
|
||||
# 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 _default_modifiers(self, exp: ClauseElement) -> str:
|
||||
to_remove = ""
|
||||
while isinstance(exp, UnaryExpression):
|
||||
if exp.modifier is None:
|
||||
exp = exp.element
|
||||
else:
|
||||
op = exp.modifier
|
||||
if isinstance(exp.element, UnaryExpression):
|
||||
inner_op = exp.element.modifier
|
||||
else:
|
||||
inner_op = None
|
||||
if inner_op is None:
|
||||
if op == operators.asc_op:
|
||||
# default is asc
|
||||
to_remove = " asc"
|
||||
elif op == operators.nullslast_op:
|
||||
# default is nulls last
|
||||
to_remove = " nulls last"
|
||||
else:
|
||||
if (
|
||||
inner_op == operators.asc_op
|
||||
and op == operators.nullslast_op
|
||||
):
|
||||
# default is asc nulls last
|
||||
to_remove = " asc nulls last"
|
||||
elif (
|
||||
inner_op == operators.desc_op
|
||||
and op == operators.nullsfirst_op
|
||||
):
|
||||
# default for desc is nulls first
|
||||
to_remove = " nulls first"
|
||||
break
|
||||
return to_remove
|
||||
|
||||
def _dialect_sig(
|
||||
def _dialect_options(
|
||||
self, item: Union[Index, UniqueConstraint]
|
||||
) -> Tuple[Any, ...]:
|
||||
# only the positive case is returned by sqlalchemy reflection so
|
||||
@@ -316,25 +324,93 @@ class PostgresqlImpl(DefaultImpl):
|
||||
return ("nulls_not_distinct",)
|
||||
return ()
|
||||
|
||||
def create_index_sig(self, index: Index) -> Tuple[Any, ...]:
|
||||
return tuple(
|
||||
self._cleanup_index_expr(
|
||||
index,
|
||||
*(
|
||||
(e, "")
|
||||
if isinstance(e, str)
|
||||
else (self._compile_element(e), self._default_modifiers(e))
|
||||
),
|
||||
)
|
||||
for e in index.expressions
|
||||
) + self._dialect_sig(index)
|
||||
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}")
|
||||
|
||||
def create_unique_constraint_sig(
|
||||
self, const: UniqueConstraint
|
||||
) -> Tuple[Any, ...]:
|
||||
return tuple(
|
||||
sorted([col.name for col in const.columns])
|
||||
) + self._dialect_sig(const)
|
||||
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_options: Dict[str, Any], kind: str
|
||||
@@ -345,7 +421,9 @@ class PostgresqlImpl(DefaultImpl):
|
||||
options.pop("postgresql_include", None)
|
||||
return options
|
||||
|
||||
def _compile_element(self, element: ClauseElement) -> str:
|
||||
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},
|
||||
@@ -512,7 +590,7 @@ def visit_identity_column(
|
||||
)
|
||||
else:
|
||||
text += "SET %s " % compiler.get_identity_options(
|
||||
sqla_compat.Identity(**{attr: getattr(identity, attr)})
|
||||
Identity(**{attr: getattr(identity, attr)})
|
||||
)
|
||||
return text
|
||||
|
||||
@@ -556,9 +634,8 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
||||
return cls(
|
||||
constraint.name,
|
||||
constraint_table.name,
|
||||
[
|
||||
(expr, op)
|
||||
for expr, name, op in constraint._render_exprs # type:ignore[attr-defined] # noqa
|
||||
[ # type: ignore
|
||||
(expr, op) for expr, name, op in constraint._render_exprs
|
||||
],
|
||||
where=cast("ColumnElement[bool] | None", constraint.where),
|
||||
schema=constraint_table.schema,
|
||||
@@ -585,7 +662,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
||||
expr,
|
||||
name,
|
||||
oper,
|
||||
) in excl._render_exprs: # type:ignore[attr-defined]
|
||||
) in excl._render_exprs:
|
||||
t.append_column(Column(name, NULLTYPE))
|
||||
t.append_constraint(excl)
|
||||
return excl
|
||||
@@ -643,7 +720,7 @@ class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
||||
constraint_name: str,
|
||||
*elements: Any,
|
||||
**kw: Any,
|
||||
):
|
||||
) -> Optional[Table]:
|
||||
"""Issue a "create exclude constraint" instruction using the
|
||||
current batch migration context.
|
||||
|
||||
@@ -715,10 +792,13 @@ def _exclude_constraint(
|
||||
args = [
|
||||
"(%s, %r)"
|
||||
% (
|
||||
_render_potential_column(sqltext, autogen_context),
|
||||
_render_potential_column(
|
||||
sqltext, # type:ignore[arg-type]
|
||||
autogen_context,
|
||||
),
|
||||
opstring,
|
||||
)
|
||||
for sqltext, name, opstring in constraint._render_exprs # type:ignore[attr-defined] # noqa
|
||||
for sqltext, name, opstring in constraint._render_exprs
|
||||
]
|
||||
if constraint.where is not None:
|
||||
args.append(
|
||||
@@ -770,5 +850,5 @@ def _render_potential_column(
|
||||
return render._render_potential_expr(
|
||||
value,
|
||||
autogen_context,
|
||||
wrap_in_text=isinstance(value, (TextClause, FunctionElement)),
|
||||
wrap_in_element=isinstance(value, (TextClause, FunctionElement)),
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
@@ -8,16 +11,19 @@ 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 sqlalchemy.ext.compiler import compiles
|
||||
|
||||
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
|
||||
@@ -59,7 +65,7 @@ class SQLiteImpl(DefaultImpl):
|
||||
) and isinstance(col.server_default.arg, sql.ClauseElement):
|
||||
return True
|
||||
elif (
|
||||
isinstance(col.server_default, util.sqla_compat.Computed)
|
||||
isinstance(col.server_default, Computed)
|
||||
and col.server_default.persisted
|
||||
):
|
||||
return True
|
||||
@@ -71,13 +77,13 @@ class SQLiteImpl(DefaultImpl):
|
||||
def add_constraint(self, const: Constraint):
|
||||
# attempt to distinguish between an
|
||||
# auto-gen constraint and an explicit one
|
||||
if const._create_rule is None: # type:ignore[attr-defined]
|
||||
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): # type:ignore[attr-defined]
|
||||
elif const._create_rule(self):
|
||||
util.warn(
|
||||
"Skipping unsupported ALTER for "
|
||||
"creation of implicit constraint. "
|
||||
@@ -85,8 +91,8 @@ class SQLiteImpl(DefaultImpl):
|
||||
"SQLite migrations using a copy-and-move strategy."
|
||||
)
|
||||
|
||||
def drop_constraint(self, const: Constraint):
|
||||
if const._create_rule is None: # type:ignore[attr-defined]
|
||||
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 "
|
||||
@@ -177,8 +183,7 @@ class SQLiteImpl(DefaultImpl):
|
||||
new_type: TypeEngine,
|
||||
) -> None:
|
||||
if (
|
||||
existing.type._type_affinity # type:ignore[attr-defined]
|
||||
is not new_type._type_affinity # type:ignore[attr-defined]
|
||||
existing.type._type_affinity is not new_type._type_affinity
|
||||
and not isinstance(new_type, JSON)
|
||||
):
|
||||
existing_transfer["expr"] = cast(
|
||||
@@ -205,6 +210,15 @@ def visit_rename_table(
|
||||
)
|
||||
|
||||
|
||||
@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" % (
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
@@ -26,7 +27,6 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.elements import conv
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.expression import TableClause
|
||||
from sqlalchemy.sql.functions import Function
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import Computed
|
||||
from sqlalchemy.sql.schema import Identity
|
||||
@@ -35,16 +35,36 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
from sqlalchemy.util import immutabledict
|
||||
|
||||
from .operations.ops import BatchOperations
|
||||
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
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Issue an "add column" instruction using the current
|
||||
migration context.
|
||||
@@ -121,6 +141,10 @@ def add_column(
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
@@ -130,12 +154,14 @@ def alter_column(
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
comment: Union[str, Literal[False], None] = False,
|
||||
server_default: Any = False,
|
||||
server_default: Union[
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
new_column_name: Optional[str] = None,
|
||||
type_: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
|
||||
existing_type: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
|
||||
existing_server_default: Union[
|
||||
str, bool, Identity, Computed, None
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_comment: Optional[str] = None,
|
||||
@@ -230,7 +256,7 @@ def batch_alter_table(
|
||||
table_name: str,
|
||||
schema: Optional[str] = None,
|
||||
recreate: Literal["auto", "always", "never"] = "auto",
|
||||
partial_reordering: Optional[tuple] = None,
|
||||
partial_reordering: Optional[Tuple[Any, ...]] = None,
|
||||
copy_from: Optional[Table] = None,
|
||||
table_args: Tuple[Any, ...] = (),
|
||||
table_kwargs: Mapping[str, Any] = immutabledict({}),
|
||||
@@ -377,7 +403,7 @@ def batch_alter_table(
|
||||
|
||||
def bulk_insert(
|
||||
table: Union[Table, TableClause],
|
||||
rows: List[dict],
|
||||
rows: List[Dict[str, Any]],
|
||||
*,
|
||||
multiinsert: bool = True,
|
||||
) -> None:
|
||||
@@ -633,7 +659,7 @@ def create_foreign_key(
|
||||
def create_index(
|
||||
index_name: Optional[str],
|
||||
table_name: str,
|
||||
columns: Sequence[Union[str, TextClause, Function[Any]]],
|
||||
columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
unique: bool = False,
|
||||
@@ -730,7 +756,12 @@ def create_primary_key(
|
||||
|
||||
"""
|
||||
|
||||
def create_table(table_name: str, *columns: SchemaItem, **kw: Any) -> Table:
|
||||
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.
|
||||
|
||||
@@ -801,6 +832,10 @@ def create_table(table_name: str, *columns: SchemaItem, **kw: Any) -> Table:
|
||||
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.
|
||||
|
||||
@@ -900,6 +935,11 @@ def drop_column(
|
||||
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
|
||||
@@ -921,7 +961,6 @@ def drop_column(
|
||||
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(
|
||||
@@ -930,6 +969,7 @@ def drop_constraint(
|
||||
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.
|
||||
|
||||
@@ -941,6 +981,10 @@ def drop_constraint(
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
@@ -981,7 +1025,11 @@ def drop_index(
|
||||
"""
|
||||
|
||||
def drop_table(
|
||||
table_name: str, *, schema: Optional[str] = None, **kw: Any
|
||||
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.
|
||||
@@ -996,6 +1044,10 @@ def drop_table(
|
||||
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.
|
||||
|
||||
@@ -1132,7 +1184,7 @@ def f(name: str) -> conv:
|
||||
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:
|
||||
output of the following::
|
||||
|
||||
op.add_column("t", "x", Boolean(name="x"))
|
||||
|
||||
@@ -1162,7 +1214,7 @@ def get_context() -> MigrationContext:
|
||||
|
||||
"""
|
||||
|
||||
def implementation_for(op_cls: Any) -> Callable[..., Any]:
|
||||
def implementation_for(op_cls: Any) -> Callable[[_C], _C]:
|
||||
"""Register an implementation for a given :class:`.MigrateOperation`.
|
||||
|
||||
This is part of the operation extensibility API.
|
||||
@@ -1174,7 +1226,7 @@ def implementation_for(op_cls: Any) -> Callable[..., Any]:
|
||||
"""
|
||||
|
||||
def inline_literal(
|
||||
value: Union[str, int], type_: Optional[TypeEngine] = None
|
||||
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.
|
||||
@@ -1218,6 +1270,27 @@ def inline_literal(
|
||||
|
||||
"""
|
||||
|
||||
@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.
|
||||
@@ -1226,7 +1299,7 @@ def invoke(operation: MigrateOperation) -> Any:
|
||||
|
||||
def register_operation(
|
||||
name: str, sourcename: Optional[str] = None
|
||||
) -> Callable[[_T], _T]:
|
||||
) -> Callable[[Type[_T]], Type[_T]]:
|
||||
"""Register a new operation for this class.
|
||||
|
||||
This method is normally used to add new operations
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# mypy: allow-untyped-calls
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
@@ -10,7 +12,9 @@ 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
|
||||
@@ -39,7 +43,6 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.expression import ColumnElement
|
||||
from sqlalchemy.sql.expression import TableClause
|
||||
from sqlalchemy.sql.expression import TextClause
|
||||
from sqlalchemy.sql.functions import Function
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import Computed
|
||||
from sqlalchemy.sql.schema import Identity
|
||||
@@ -47,12 +50,28 @@ if TYPE_CHECKING:
|
||||
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.
|
||||
@@ -86,7 +105,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
@classmethod
|
||||
def register_operation(
|
||||
cls, name: str, sourcename: Optional[str] = None
|
||||
) -> Callable[[_T], _T]:
|
||||
) -> Callable[[Type[_T]], Type[_T]]:
|
||||
"""Register a new operation for this class.
|
||||
|
||||
This method is normally used to add new operations
|
||||
@@ -103,7 +122,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
|
||||
"""
|
||||
|
||||
def register(op_cls):
|
||||
def register(op_cls: Type[_T]) -> Type[_T]:
|
||||
if sourcename is None:
|
||||
fn = getattr(op_cls, name)
|
||||
source_name = fn.__name__
|
||||
@@ -122,8 +141,11 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
*spec, formatannotation=formatannotation_fwdref
|
||||
)
|
||||
num_defaults = len(spec[3]) if spec[3] else 0
|
||||
|
||||
defaulted_vals: Tuple[Any, ...]
|
||||
|
||||
if num_defaults:
|
||||
defaulted_vals = name_args[0 - num_defaults :]
|
||||
defaulted_vals = tuple(name_args[0 - num_defaults :])
|
||||
else:
|
||||
defaulted_vals = ()
|
||||
|
||||
@@ -164,7 +186,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
|
||||
globals_ = dict(globals())
|
||||
globals_.update({"op_cls": op_cls})
|
||||
lcl = {}
|
||||
lcl: Dict[str, Any] = {}
|
||||
|
||||
exec(func_text, globals_, lcl)
|
||||
setattr(cls, name, lcl[name])
|
||||
@@ -180,7 +202,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
return register
|
||||
|
||||
@classmethod
|
||||
def implementation_for(cls, op_cls: Any) -> Callable[..., Any]:
|
||||
def implementation_for(cls, op_cls: Any) -> Callable[[_C], _C]:
|
||||
"""Register an implementation for a given :class:`.MigrateOperation`.
|
||||
|
||||
This is part of the operation extensibility API.
|
||||
@@ -191,7 +213,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
|
||||
"""
|
||||
|
||||
def decorate(fn):
|
||||
def decorate(fn: _C) -> _C:
|
||||
cls._to_impl.dispatch_for(op_cls)(fn)
|
||||
return fn
|
||||
|
||||
@@ -213,7 +235,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
table_name: str,
|
||||
schema: Optional[str] = None,
|
||||
recreate: Literal["auto", "always", "never"] = "auto",
|
||||
partial_reordering: Optional[tuple] = None,
|
||||
partial_reordering: Optional[Tuple[Any, ...]] = None,
|
||||
copy_from: Optional[Table] = None,
|
||||
table_args: Tuple[Any, ...] = (),
|
||||
table_kwargs: Mapping[str, Any] = util.immutabledict(),
|
||||
@@ -382,6 +404,32 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
|
||||
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.
|
||||
@@ -416,7 +464,7 @@ class AbstractOperations(util.ModuleClsProxy):
|
||||
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:
|
||||
output of the following::
|
||||
|
||||
op.add_column("t", "x", Boolean(name="x"))
|
||||
|
||||
@@ -570,6 +618,7 @@ class Operations(AbstractOperations):
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Issue an "add column" instruction using the current
|
||||
migration context.
|
||||
@@ -646,6 +695,10 @@ class Operations(AbstractOperations):
|
||||
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
|
||||
|
||||
""" # noqa: E501
|
||||
...
|
||||
@@ -657,12 +710,16 @@ class Operations(AbstractOperations):
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
comment: Union[str, Literal[False], None] = False,
|
||||
server_default: Any = False,
|
||||
server_default: Union[
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
new_column_name: Optional[str] = None,
|
||||
type_: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
|
||||
existing_type: Union[
|
||||
TypeEngine[Any], Type[TypeEngine[Any]], None
|
||||
] = None,
|
||||
existing_server_default: Union[
|
||||
str, bool, Identity, Computed, None
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_comment: Optional[str] = None,
|
||||
@@ -756,7 +813,7 @@ class Operations(AbstractOperations):
|
||||
def bulk_insert(
|
||||
self,
|
||||
table: Union[Table, TableClause],
|
||||
rows: List[dict],
|
||||
rows: List[Dict[str, Any]],
|
||||
*,
|
||||
multiinsert: bool = True,
|
||||
) -> None:
|
||||
@@ -1023,7 +1080,7 @@ class Operations(AbstractOperations):
|
||||
self,
|
||||
index_name: Optional[str],
|
||||
table_name: str,
|
||||
columns: Sequence[Union[str, TextClause, Function[Any]]],
|
||||
columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
unique: bool = False,
|
||||
@@ -1124,7 +1181,11 @@ class Operations(AbstractOperations):
|
||||
...
|
||||
|
||||
def create_table(
|
||||
self, table_name: str, *columns: SchemaItem, **kw: Any
|
||||
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.
|
||||
@@ -1196,6 +1257,10 @@ class Operations(AbstractOperations):
|
||||
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.
|
||||
|
||||
@@ -1301,6 +1366,11 @@ class Operations(AbstractOperations):
|
||||
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
|
||||
@@ -1322,7 +1392,6 @@ class Operations(AbstractOperations):
|
||||
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
|
||||
...
|
||||
|
||||
@@ -1333,6 +1402,7 @@ class Operations(AbstractOperations):
|
||||
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.
|
||||
|
||||
@@ -1344,6 +1414,10 @@ class Operations(AbstractOperations):
|
||||
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
|
||||
...
|
||||
@@ -1387,7 +1461,12 @@ class Operations(AbstractOperations):
|
||||
...
|
||||
|
||||
def drop_table(
|
||||
self, table_name: str, *, schema: Optional[str] = None, **kw: Any
|
||||
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.
|
||||
@@ -1402,6 +1481,10 @@ class Operations(AbstractOperations):
|
||||
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.
|
||||
|
||||
@@ -1560,7 +1643,7 @@ class BatchOperations(AbstractOperations):
|
||||
|
||||
impl: BatchOperationsImpl
|
||||
|
||||
def _noop(self, operation):
|
||||
def _noop(self, operation: Any) -> NoReturn:
|
||||
raise NotImplementedError(
|
||||
"The %s method does not apply to a batch table alter operation."
|
||||
% operation
|
||||
@@ -1577,6 +1660,7 @@ class BatchOperations(AbstractOperations):
|
||||
*,
|
||||
insert_before: Optional[str] = None,
|
||||
insert_after: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Issue an "add column" instruction using the current
|
||||
batch migration context.
|
||||
@@ -1596,8 +1680,10 @@ class BatchOperations(AbstractOperations):
|
||||
comment: Union[str, Literal[False], None] = False,
|
||||
server_default: Any = False,
|
||||
new_column_name: Optional[str] = None,
|
||||
type_: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
existing_type: Union[TypeEngine, Type[TypeEngine], None] = None,
|
||||
type_: Union[TypeEngine[Any], Type[TypeEngine[Any]], None] = None,
|
||||
existing_type: Union[
|
||||
TypeEngine[Any], Type[TypeEngine[Any]], None
|
||||
] = None,
|
||||
existing_server_default: Union[
|
||||
str, bool, Identity, Computed, None
|
||||
] = False,
|
||||
@@ -1652,7 +1738,7 @@ class BatchOperations(AbstractOperations):
|
||||
|
||||
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.
|
||||
|
||||
@@ -1668,7 +1754,7 @@ class BatchOperations(AbstractOperations):
|
||||
|
||||
def create_foreign_key(
|
||||
self,
|
||||
constraint_name: str,
|
||||
constraint_name: Optional[str],
|
||||
referent_table: str,
|
||||
local_cols: List[str],
|
||||
remote_cols: List[str],
|
||||
@@ -1718,7 +1804,7 @@ class BatchOperations(AbstractOperations):
|
||||
...
|
||||
|
||||
def create_primary_key(
|
||||
self, constraint_name: str, columns: List[str]
|
||||
self, constraint_name: Optional[str], columns: List[str]
|
||||
) -> None:
|
||||
"""Issue a "create primary key" instruction using the
|
||||
current batch migration context.
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 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
|
||||
@@ -15,9 +18,10 @@ 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.events import SchemaEventTarget
|
||||
from sqlalchemy.sql.schema import SchemaEventTarget
|
||||
from sqlalchemy.util import OrderedDict
|
||||
from sqlalchemy.util import topological
|
||||
|
||||
@@ -28,11 +32,9 @@ 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 _insert_inline
|
||||
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 _select
|
||||
from ..util.sqla_compat import constraint_name_defined
|
||||
from ..util.sqla_compat import constraint_name_string
|
||||
|
||||
@@ -374,7 +376,7 @@ class ApplyBatchImpl:
|
||||
for idx_existing in self.indexes.values():
|
||||
# this is a lift-and-move from Table.to_metadata
|
||||
|
||||
if idx_existing._column_flag: # type: ignore
|
||||
if idx_existing._column_flag:
|
||||
continue
|
||||
|
||||
idx_copy = Index(
|
||||
@@ -403,9 +405,7 @@ class ApplyBatchImpl:
|
||||
def _setup_referent(
|
||||
self, metadata: MetaData, constraint: ForeignKeyConstraint
|
||||
) -> None:
|
||||
spec = constraint.elements[
|
||||
0
|
||||
]._get_colspec() # type:ignore[attr-defined]
|
||||
spec = constraint.elements[0]._get_colspec()
|
||||
parts = spec.split(".")
|
||||
tname = parts[-2]
|
||||
if len(parts) == 3:
|
||||
@@ -448,13 +448,15 @@ class ApplyBatchImpl:
|
||||
|
||||
try:
|
||||
op_impl._exec(
|
||||
_insert_inline(self.new_table).from_select(
|
||||
self.new_table.insert()
|
||||
.inline()
|
||||
.from_select(
|
||||
list(
|
||||
k
|
||||
for k, transfer in self.column_transfers.items()
|
||||
if "expr" in transfer
|
||||
),
|
||||
_select(
|
||||
select(
|
||||
*[
|
||||
transfer["expr"]
|
||||
for transfer in self.column_transfers.values()
|
||||
@@ -546,9 +548,7 @@ class ApplyBatchImpl:
|
||||
else:
|
||||
sql_schema.DefaultClause(
|
||||
server_default # type: ignore[arg-type]
|
||||
)._set_parent( # type:ignore[attr-defined]
|
||||
existing
|
||||
)
|
||||
)._set_parent(existing)
|
||||
if autoincrement is not None:
|
||||
existing.autoincrement = bool(autoincrement)
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
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
|
||||
@@ -15,6 +18,7 @@ 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
|
||||
@@ -33,7 +37,6 @@ if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.elements import conv
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.functions import Function
|
||||
from sqlalchemy.sql.schema import CheckConstraint
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import Computed
|
||||
@@ -53,6 +56,9 @@ if TYPE_CHECKING:
|
||||
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.
|
||||
@@ -70,7 +76,7 @@ class MigrateOperation:
|
||||
"""
|
||||
|
||||
@util.memoized_property
|
||||
def info(self):
|
||||
def info(self) -> Dict[Any, Any]:
|
||||
"""A dictionary that may be used to store arbitrary information
|
||||
along with this :class:`.MigrateOperation` object.
|
||||
|
||||
@@ -92,12 +98,14 @@ class AddConstraintOp(MigrateOperation):
|
||||
add_constraint_ops = util.Dispatcher()
|
||||
|
||||
@property
|
||||
def constraint_type(self):
|
||||
def constraint_type(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def register_add_constraint(cls, type_: str) -> Callable:
|
||||
def go(klass):
|
||||
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
|
||||
|
||||
@@ -105,7 +113,7 @@ class AddConstraintOp(MigrateOperation):
|
||||
|
||||
@classmethod
|
||||
def from_constraint(cls, constraint: Constraint) -> AddConstraintOp:
|
||||
return cls.add_constraint_ops.dispatch(constraint.__visit_name__)(
|
||||
return cls.add_constraint_ops.dispatch(constraint.__visit_name__)( # type: ignore[no-any-return] # noqa: E501
|
||||
constraint
|
||||
)
|
||||
|
||||
@@ -134,12 +142,14 @@ class DropConstraintOp(MigrateOperation):
|
||||
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:
|
||||
@@ -197,6 +207,7 @@ class DropConstraintOp(MigrateOperation):
|
||||
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.
|
||||
|
||||
@@ -208,10 +219,20 @@ class DropConstraintOp(MigrateOperation):
|
||||
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)
|
||||
op = cls(
|
||||
constraint_name,
|
||||
table_name,
|
||||
type_=type_,
|
||||
schema=schema,
|
||||
if_exists=if_exists,
|
||||
)
|
||||
return operations.invoke(op)
|
||||
|
||||
@classmethod
|
||||
@@ -342,7 +363,7 @@ class CreatePrimaryKeyOp(AddConstraintOp):
|
||||
def batch_create_primary_key(
|
||||
cls,
|
||||
operations: BatchOperations,
|
||||
constraint_name: str,
|
||||
constraint_name: Optional[str],
|
||||
columns: List[str],
|
||||
) -> None:
|
||||
"""Issue a "create primary key" instruction using the
|
||||
@@ -398,7 +419,7 @@ class CreateUniqueConstraintOp(AddConstraintOp):
|
||||
|
||||
uq_constraint = cast("UniqueConstraint", constraint)
|
||||
|
||||
kw: dict = {}
|
||||
kw: Dict[str, Any] = {}
|
||||
if uq_constraint.deferrable:
|
||||
kw["deferrable"] = uq_constraint.deferrable
|
||||
if uq_constraint.initially:
|
||||
@@ -532,7 +553,7 @@ class CreateForeignKeyOp(AddConstraintOp):
|
||||
@classmethod
|
||||
def from_constraint(cls, constraint: Constraint) -> CreateForeignKeyOp:
|
||||
fk_constraint = cast("ForeignKeyConstraint", constraint)
|
||||
kw: dict = {}
|
||||
kw: Dict[str, Any] = {}
|
||||
if fk_constraint.onupdate:
|
||||
kw["onupdate"] = fk_constraint.onupdate
|
||||
if fk_constraint.ondelete:
|
||||
@@ -674,7 +695,7 @@ class CreateForeignKeyOp(AddConstraintOp):
|
||||
def batch_create_foreign_key(
|
||||
cls,
|
||||
operations: BatchOperations,
|
||||
constraint_name: str,
|
||||
constraint_name: Optional[str],
|
||||
referent_table: str,
|
||||
local_cols: List[str],
|
||||
remote_cols: List[str],
|
||||
@@ -897,9 +918,9 @@ class CreateIndexOp(MigrateOperation):
|
||||
def from_index(cls, index: Index) -> CreateIndexOp:
|
||||
assert index.table is not None
|
||||
return cls(
|
||||
index.name, # type: ignore[arg-type]
|
||||
index.name,
|
||||
index.table.name,
|
||||
sqla_compat._get_index_expressions(index),
|
||||
index.expressions,
|
||||
schema=index.table.schema,
|
||||
unique=index.unique,
|
||||
**index.kwargs,
|
||||
@@ -926,7 +947,7 @@ class CreateIndexOp(MigrateOperation):
|
||||
operations: Operations,
|
||||
index_name: Optional[str],
|
||||
table_name: str,
|
||||
columns: Sequence[Union[str, TextClause, Function[Any]]],
|
||||
columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
unique: bool = False,
|
||||
@@ -1054,6 +1075,7 @@ class DropIndexOp(MigrateOperation):
|
||||
table_name=index.table.name,
|
||||
schema=index.table.schema,
|
||||
_reverse=CreateIndexOp.from_index(index),
|
||||
unique=index.unique,
|
||||
**index.kwargs,
|
||||
)
|
||||
|
||||
@@ -1151,6 +1173,7 @@ class CreateTableOp(MigrateOperation):
|
||||
columns: Sequence[SchemaItem],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
_namespace_metadata: Optional[MetaData] = None,
|
||||
_constraints_included: bool = False,
|
||||
**kw: Any,
|
||||
@@ -1158,6 +1181,7 @@ class CreateTableOp(MigrateOperation):
|
||||
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)
|
||||
@@ -1182,7 +1206,7 @@ class CreateTableOp(MigrateOperation):
|
||||
|
||||
return cls(
|
||||
table.name,
|
||||
list(table.c) + list(table.constraints), # type:ignore[arg-type]
|
||||
list(table.c) + list(table.constraints),
|
||||
schema=table.schema,
|
||||
_namespace_metadata=_namespace_metadata,
|
||||
# given a Table() object, this Table will contain full Index()
|
||||
@@ -1220,6 +1244,7 @@ class CreateTableOp(MigrateOperation):
|
||||
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
|
||||
@@ -1292,6 +1317,10 @@ class CreateTableOp(MigrateOperation):
|
||||
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.
|
||||
|
||||
@@ -1299,7 +1328,7 @@ class CreateTableOp(MigrateOperation):
|
||||
to the parameters given.
|
||||
|
||||
"""
|
||||
op = cls(table_name, columns, **kw)
|
||||
op = cls(table_name, columns, if_not_exists=if_not_exists, **kw)
|
||||
return operations.invoke(op)
|
||||
|
||||
|
||||
@@ -1312,11 +1341,13 @@ class DropTableOp(MigrateOperation):
|
||||
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)
|
||||
@@ -1363,9 +1394,9 @@ class DropTableOp(MigrateOperation):
|
||||
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,
|
||||
_constraints_included=(
|
||||
self._reverse._constraints_included if self._reverse else False
|
||||
),
|
||||
**self.table_kw,
|
||||
)
|
||||
return t
|
||||
@@ -1377,6 +1408,7 @@ class DropTableOp(MigrateOperation):
|
||||
table_name: str,
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_exists: Optional[bool] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
r"""Issue a "drop table" instruction using the current
|
||||
@@ -1392,11 +1424,15 @@ class DropTableOp(MigrateOperation):
|
||||
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, table_kw=kw)
|
||||
op = cls(table_name, schema=schema, if_exists=if_exists, table_kw=kw)
|
||||
operations.invoke(op)
|
||||
|
||||
|
||||
@@ -1534,7 +1570,7 @@ class CreateTableCommentOp(AlterTableOp):
|
||||
)
|
||||
return operations.invoke(op)
|
||||
|
||||
def reverse(self):
|
||||
def reverse(self) -> Union[CreateTableCommentOp, DropTableCommentOp]:
|
||||
"""Reverses the COMMENT ON operation against a table."""
|
||||
if self.existing_comment is None:
|
||||
return DropTableCommentOp(
|
||||
@@ -1550,14 +1586,16 @@ class CreateTableCommentOp(AlterTableOp):
|
||||
schema=self.schema,
|
||||
)
|
||||
|
||||
def to_table(self, migration_context=None):
|
||||
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):
|
||||
def to_diff_tuple(self) -> Tuple[Any, ...]:
|
||||
return ("add_table_comment", self.to_table(), self.existing_comment)
|
||||
|
||||
|
||||
@@ -1629,18 +1667,20 @@ class DropTableCommentOp(AlterTableOp):
|
||||
)
|
||||
return operations.invoke(op)
|
||||
|
||||
def reverse(self):
|
||||
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=None):
|
||||
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):
|
||||
def to_diff_tuple(self) -> Tuple[Any, ...]:
|
||||
return ("remove_table_comment", self.to_table())
|
||||
|
||||
|
||||
@@ -1815,12 +1855,16 @@ class AlterColumnOp(AlterTableOp):
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
comment: Optional[Union[str, Literal[False]]] = False,
|
||||
server_default: Any = False,
|
||||
server_default: Union[
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
new_column_name: Optional[str] = None,
|
||||
type_: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
|
||||
existing_type: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
|
||||
existing_server_default: Optional[
|
||||
Union[str, bool, Identity, Computed]
|
||||
type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
|
||||
existing_type: Optional[
|
||||
Union[TypeEngine[Any], Type[TypeEngine[Any]]]
|
||||
] = None,
|
||||
existing_server_default: Union[
|
||||
str, bool, Identity, Computed, TextClause, None
|
||||
] = False,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_comment: Optional[str] = None,
|
||||
@@ -1938,8 +1982,10 @@ class AlterColumnOp(AlterTableOp):
|
||||
comment: Optional[Union[str, Literal[False]]] = False,
|
||||
server_default: Any = False,
|
||||
new_column_name: Optional[str] = None,
|
||||
type_: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
|
||||
existing_type: Optional[Union[TypeEngine, Type[TypeEngine]]] = None,
|
||||
type_: Optional[Union[TypeEngine[Any], Type[TypeEngine[Any]]]] = None,
|
||||
existing_type: Optional[
|
||||
Union[TypeEngine[Any], Type[TypeEngine[Any]]]
|
||||
] = None,
|
||||
existing_server_default: Optional[
|
||||
Union[str, bool, Identity, Computed]
|
||||
] = False,
|
||||
@@ -2003,27 +2049,31 @@ class AddColumnOp(AlterTableOp):
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
super().__init__(table_name, schema=schema)
|
||||
self.column = column
|
||||
self.if_not_exists = if_not_exists
|
||||
self.kw = kw
|
||||
|
||||
def reverse(self) -> DropColumnOp:
|
||||
return DropColumnOp.from_column_and_tablename(
|
||||
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:
|
||||
def to_column(self) -> Column[Any]:
|
||||
return self.column
|
||||
|
||||
@classmethod
|
||||
def from_column(cls, col: Column) -> AddColumnOp:
|
||||
def from_column(cls, col: Column[Any]) -> AddColumnOp:
|
||||
return cls(col.table.name, col, schema=col.table.schema)
|
||||
|
||||
@classmethod
|
||||
@@ -2043,6 +2093,7 @@ class AddColumnOp(AlterTableOp):
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Issue an "add column" instruction using the current
|
||||
migration context.
|
||||
@@ -2119,10 +2170,19 @@ class AddColumnOp(AlterTableOp):
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
op = cls(table_name, column, schema=schema)
|
||||
op = cls(
|
||||
table_name,
|
||||
column,
|
||||
schema=schema,
|
||||
if_not_exists=if_not_exists,
|
||||
)
|
||||
return operations.invoke(op)
|
||||
|
||||
@classmethod
|
||||
@@ -2133,6 +2193,7 @@ class AddColumnOp(AlterTableOp):
|
||||
*,
|
||||
insert_before: Optional[str] = None,
|
||||
insert_after: Optional[str] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
"""Issue an "add column" instruction using the current
|
||||
batch migration context.
|
||||
@@ -2153,6 +2214,7 @@ class AddColumnOp(AlterTableOp):
|
||||
operations.impl.table_name,
|
||||
column,
|
||||
schema=operations.impl.schema,
|
||||
if_not_exists=if_not_exists,
|
||||
**kw,
|
||||
)
|
||||
return operations.invoke(op)
|
||||
@@ -2169,12 +2231,14 @@ class DropColumnOp(AlterTableOp):
|
||||
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(
|
||||
@@ -2194,9 +2258,11 @@ class DropColumnOp(AlterTableOp):
|
||||
"original column is not present"
|
||||
)
|
||||
|
||||
return AddColumnOp.from_column_and_tablename(
|
||||
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(
|
||||
@@ -2214,7 +2280,7 @@ class DropColumnOp(AlterTableOp):
|
||||
|
||||
def to_column(
|
||||
self, migration_context: Optional[MigrationContext] = None
|
||||
) -> Column:
|
||||
) -> Column[Any]:
|
||||
if self._reverse is not None:
|
||||
return self._reverse.column
|
||||
schema_obj = schemaobj.SchemaObjects(migration_context)
|
||||
@@ -2243,6 +2309,11 @@ class DropColumnOp(AlterTableOp):
|
||||
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
|
||||
@@ -2264,7 +2335,6 @@ class DropColumnOp(AlterTableOp):
|
||||
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)
|
||||
@@ -2298,7 +2368,7 @@ class BulkInsertOp(MigrateOperation):
|
||||
def __init__(
|
||||
self,
|
||||
table: Union[Table, TableClause],
|
||||
rows: List[dict],
|
||||
rows: List[Dict[str, Any]],
|
||||
*,
|
||||
multiinsert: bool = True,
|
||||
) -> None:
|
||||
@@ -2311,7 +2381,7 @@ class BulkInsertOp(MigrateOperation):
|
||||
cls,
|
||||
operations: Operations,
|
||||
table: Union[Table, TableClause],
|
||||
rows: List[dict],
|
||||
rows: List[Dict[str, Any]],
|
||||
*,
|
||||
multiinsert: bool = True,
|
||||
) -> None:
|
||||
@@ -2607,7 +2677,7 @@ class UpgradeOps(OpContainer):
|
||||
self.upgrade_token = upgrade_token
|
||||
|
||||
def reverse_into(self, downgrade_ops: DowngradeOps) -> DowngradeOps:
|
||||
downgrade_ops.ops[:] = list( # type:ignore[index]
|
||||
downgrade_ops.ops[:] = list(
|
||||
reversed([op.reverse() for op in self.ops])
|
||||
)
|
||||
return downgrade_ops
|
||||
@@ -2634,7 +2704,7 @@ class DowngradeOps(OpContainer):
|
||||
super().__init__(ops=ops)
|
||||
self.downgrade_token = downgrade_token
|
||||
|
||||
def reverse(self):
|
||||
def reverse(self) -> UpgradeOps:
|
||||
return UpgradeOps(
|
||||
ops=list(reversed([op.reverse() for op in self.ops]))
|
||||
)
|
||||
@@ -2665,6 +2735,8 @@ class MigrationScript(MigrateOperation):
|
||||
"""
|
||||
|
||||
_needs_render: Optional[bool]
|
||||
_upgrade_ops: List[UpgradeOps]
|
||||
_downgrade_ops: List[DowngradeOps]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -2677,7 +2749,7 @@ class MigrationScript(MigrateOperation):
|
||||
head: Optional[str] = None,
|
||||
splice: Optional[bool] = None,
|
||||
branch_label: Optional[_RevIdType] = None,
|
||||
version_path: Optional[str] = None,
|
||||
version_path: Union[str, os.PathLike[str], None] = None,
|
||||
depends_on: Optional[_RevIdType] = None,
|
||||
) -> None:
|
||||
self.rev_id = rev_id
|
||||
@@ -2686,13 +2758,15 @@ class MigrationScript(MigrateOperation):
|
||||
self.head = head
|
||||
self.splice = splice
|
||||
self.branch_label = branch_label
|
||||
self.version_path = version_path
|
||||
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):
|
||||
def upgrade_ops(self) -> Optional[UpgradeOps]:
|
||||
"""An instance of :class:`.UpgradeOps`.
|
||||
|
||||
.. seealso::
|
||||
@@ -2711,13 +2785,15 @@ class MigrationScript(MigrateOperation):
|
||||
return self._upgrade_ops[0]
|
||||
|
||||
@upgrade_ops.setter
|
||||
def upgrade_ops(self, upgrade_ops):
|
||||
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):
|
||||
def downgrade_ops(self) -> Optional[DowngradeOps]:
|
||||
"""An instance of :class:`.DowngradeOps`.
|
||||
|
||||
.. seealso::
|
||||
@@ -2736,7 +2812,9 @@ class MigrationScript(MigrateOperation):
|
||||
return self._downgrade_ops[0]
|
||||
|
||||
@downgrade_ops.setter
|
||||
def downgrade_ops(self, downgrade_ops):
|
||||
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)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 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
|
||||
@@ -220,10 +223,12 @@ class SchemaObjects:
|
||||
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
|
||||
(
|
||||
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))
|
||||
]
|
||||
@@ -274,10 +279,8 @@ class SchemaObjects:
|
||||
ForeignKey.
|
||||
|
||||
"""
|
||||
if isinstance(fk._colspec, str): # type:ignore[attr-defined]
|
||||
table_key, cname = fk._colspec.rsplit( # type:ignore[attr-defined]
|
||||
".", 1
|
||||
)
|
||||
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)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 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
|
||||
@@ -76,8 +79,11 @@ def alter_column(
|
||||
|
||||
@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)
|
||||
operation.to_table(operations.migration_context), **kw
|
||||
)
|
||||
|
||||
|
||||
@@ -87,7 +93,11 @@ def drop_column(
|
||||
) -> None:
|
||||
column = operation.to_column(operations.migration_context)
|
||||
operations.impl.drop_column(
|
||||
operation.table_name, column, schema=operation.schema, **operation.kw
|
||||
operation.table_name,
|
||||
column,
|
||||
schema=operation.schema,
|
||||
if_exists=operation.if_exists,
|
||||
**operation.kw,
|
||||
)
|
||||
|
||||
|
||||
@@ -98,9 +108,6 @@ def create_index(
|
||||
idx = operation.to_index(operations.migration_context)
|
||||
kw = {}
|
||||
if operation.if_not_exists is not None:
|
||||
if not sqla_2:
|
||||
raise NotImplementedError("SQLAlchemy 2.0+ required")
|
||||
|
||||
kw["if_not_exists"] = operation.if_not_exists
|
||||
operations.impl.create_index(idx, **kw)
|
||||
|
||||
@@ -109,9 +116,6 @@ def create_index(
|
||||
def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> 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_index(
|
||||
@@ -124,8 +128,11 @@ def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
|
||||
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)
|
||||
operations.impl.create_table(table, **kw)
|
||||
return table
|
||||
|
||||
|
||||
@@ -165,7 +172,13 @@ def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
|
||||
column = _copy(column)
|
||||
|
||||
t = operations.schema_obj.table(table_name, column, schema=schema)
|
||||
operations.impl.add_column(table_name, column, schema=schema, **kw)
|
||||
operations.impl.add_column(
|
||||
table_name,
|
||||
column,
|
||||
schema=schema,
|
||||
if_not_exists=operation.if_not_exists,
|
||||
**kw,
|
||||
)
|
||||
|
||||
for constraint in t.constraints:
|
||||
if not isinstance(constraint, sa_schema.PrimaryKeyConstraint):
|
||||
@@ -195,13 +208,19 @@ def create_constraint(
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Collection
|
||||
from typing import ContextManager
|
||||
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
|
||||
@@ -17,6 +17,7 @@ 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
|
||||
@@ -107,7 +108,6 @@ CompareType = Callable[
|
||||
|
||||
|
||||
class EnvironmentContext(util.ModuleClsProxy):
|
||||
|
||||
"""A configurational facade made available in an ``env.py`` script.
|
||||
|
||||
The :class:`.EnvironmentContext` acts as a *facade* to the more
|
||||
@@ -227,9 +227,9 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
has been configured.
|
||||
|
||||
"""
|
||||
return self.context_opts.get("as_sql", False)
|
||||
return self.context_opts.get("as_sql", False) # type: ignore[no-any-return] # noqa: E501
|
||||
|
||||
def is_transactional_ddl(self):
|
||||
def is_transactional_ddl(self) -> bool:
|
||||
"""Return True if the context is configured to expect a
|
||||
transactional DDL capable backend.
|
||||
|
||||
@@ -341,18 +341,17 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
return self.context_opts.get("tag", None)
|
||||
|
||||
@overload
|
||||
def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]:
|
||||
...
|
||||
def get_x_argument(self, as_dictionary: Literal[False]) -> List[str]: ...
|
||||
|
||||
@overload
|
||||
def get_x_argument(self, as_dictionary: Literal[True]) -> Dict[str, str]:
|
||||
...
|
||||
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]]:
|
||||
...
|
||||
) -> Union[List[str], Dict[str, str]]: ...
|
||||
|
||||
def get_x_argument(
|
||||
self, as_dictionary: bool = False
|
||||
@@ -366,7 +365,11 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
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.
|
||||
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::
|
||||
@@ -400,7 +403,12 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
else:
|
||||
value = []
|
||||
if as_dictionary:
|
||||
value = dict(arg.split("=", 1) for arg in value)
|
||||
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(
|
||||
@@ -416,7 +424,7 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
tag: Optional[str] = None,
|
||||
template_args: Optional[Dict[str, Any]] = None,
|
||||
render_as_batch: bool = False,
|
||||
target_metadata: Optional[MetaData] = None,
|
||||
target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
||||
include_name: Optional[IncludeNameFn] = None,
|
||||
include_object: Optional[IncludeObjectFn] = None,
|
||||
include_schemas: bool = False,
|
||||
@@ -940,7 +948,7 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
def execute(
|
||||
self,
|
||||
sql: Union[Executable, str],
|
||||
execution_options: Optional[dict] = None,
|
||||
execution_options: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Execute the given SQL using the current change context.
|
||||
|
||||
@@ -968,7 +976,7 @@ class EnvironmentContext(util.ModuleClsProxy):
|
||||
|
||||
def begin_transaction(
|
||||
self,
|
||||
) -> Union[_ProxyTransaction, ContextManager[None]]:
|
||||
) -> 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
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# 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
|
||||
@@ -8,7 +11,6 @@ from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Collection
|
||||
from typing import ContextManager
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
@@ -21,13 +23,11 @@ from typing import Union
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import literal_column
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import PrimaryKeyConstraint
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Table
|
||||
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
|
||||
@@ -83,7 +83,6 @@ class _ProxyTransaction:
|
||||
|
||||
|
||||
class MigrationContext:
|
||||
|
||||
"""Represent the database state made available to a migration
|
||||
script.
|
||||
|
||||
@@ -176,7 +175,11 @@ class MigrationContext:
|
||||
opts["output_encoding"],
|
||||
)
|
||||
else:
|
||||
self.output_buffer = opts.get("output_buffer", sys.stdout)
|
||||
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(
|
||||
@@ -188,18 +191,6 @@ class MigrationContext:
|
||||
self.version_table_schema = version_table_schema = opts.get(
|
||||
"version_table_schema", None
|
||||
)
|
||||
self._version = Table(
|
||||
version_table,
|
||||
MetaData(),
|
||||
Column("version_num", String(32), nullable=False),
|
||||
schema=version_table_schema,
|
||||
)
|
||||
if opts.get("version_table_pk", True):
|
||||
self._version.append_constraint(
|
||||
PrimaryKeyConstraint(
|
||||
"version_num", name="%s_pkc" % version_table
|
||||
)
|
||||
)
|
||||
|
||||
self._start_from_rev: Optional[str] = opts.get("starting_rev")
|
||||
self.impl = ddl.DefaultImpl.get_by_dialect(dialect)(
|
||||
@@ -210,14 +201,23 @@ class MigrationContext:
|
||||
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",
|
||||
(
|
||||
"transactional"
|
||||
if self.impl.transactional_ddl
|
||||
else "non-transactional"
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -342,9 +342,9 @@ class MigrationContext:
|
||||
# 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")
|
||||
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
|
||||
@@ -372,7 +372,7 @@ class MigrationContext:
|
||||
|
||||
def begin_transaction(
|
||||
self, _per_migration: bool = False
|
||||
) -> Union[_ProxyTransaction, ContextManager[None]]:
|
||||
) -> 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
|
||||
@@ -521,7 +521,7 @@ class MigrationContext:
|
||||
start_from_rev = None
|
||||
elif start_from_rev is not None and self.script:
|
||||
start_from_rev = [
|
||||
cast("Script", self.script.get_revision(sfr)).revision
|
||||
self.script.get_revision(sfr).revision
|
||||
for sfr in util.to_list(start_from_rev)
|
||||
if sfr not in (None, "base")
|
||||
]
|
||||
@@ -536,7 +536,10 @@ class MigrationContext:
|
||||
return ()
|
||||
assert self.connection is not None
|
||||
return tuple(
|
||||
row[0] for row in self.connection.execute(self._version.select())
|
||||
row[0]
|
||||
for row in self.connection.execute(
|
||||
select(self._version.c.version_num)
|
||||
)
|
||||
)
|
||||
|
||||
def _ensure_version_table(self, purge: bool = False) -> None:
|
||||
@@ -652,7 +655,7 @@ class MigrationContext:
|
||||
def execute(
|
||||
self,
|
||||
sql: Union[Executable, str],
|
||||
execution_options: Optional[dict] = None,
|
||||
execution_options: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Execute a SQL construct or string statement.
|
||||
|
||||
@@ -1000,6 +1003,11 @@ class MigrationStep:
|
||||
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__
|
||||
@@ -1048,13 +1056,9 @@ class RevisionStep(MigrationStep):
|
||||
self.revision = revision
|
||||
self.is_upgrade = is_upgrade
|
||||
if is_upgrade:
|
||||
self.migration_fn = (
|
||||
revision.module.upgrade # type:ignore[attr-defined]
|
||||
)
|
||||
self.migration_fn = revision.module.upgrade
|
||||
else:
|
||||
self.migration_fn = (
|
||||
revision.module.downgrade # type:ignore[attr-defined]
|
||||
)
|
||||
self.migration_fn = revision.module.downgrade
|
||||
|
||||
def __repr__(self):
|
||||
return "RevisionStep(%r, is_upgrade=%r)" % (
|
||||
@@ -1070,7 +1074,7 @@ class RevisionStep(MigrationStep):
|
||||
)
|
||||
|
||||
@property
|
||||
def doc(self) -> str:
|
||||
def doc(self) -> Optional[str]:
|
||||
return self.revision.doc
|
||||
|
||||
@property
|
||||
@@ -1168,7 +1172,18 @@ class RevisionStep(MigrationStep):
|
||||
}
|
||||
return tuple(set(self.to_revisions).difference(ancestors))
|
||||
else:
|
||||
return self.to_revisions
|
||||
# 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]
|
||||
@@ -1283,7 +1298,7 @@ class StampStep(MigrationStep):
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, StampStep)
|
||||
and other.from_revisions == self.revisions
|
||||
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
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from contextlib import contextmanager
|
||||
import datetime
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
@@ -11,7 +12,6 @@ from typing import Any
|
||||
from typing import cast
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
@@ -23,7 +23,9 @@ 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
|
||||
@@ -31,26 +33,28 @@ if TYPE_CHECKING:
|
||||
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 dateutil import tz
|
||||
if compat.py39:
|
||||
from zoneinfo import ZoneInfo
|
||||
from zoneinfo import ZoneInfoNotFoundError
|
||||
else:
|
||||
from backports.zoneinfo import ZoneInfo # type: ignore[import-not-found,no-redef] # noqa: E501
|
||||
from backports.zoneinfo import ZoneInfoNotFoundError # type: ignore[no-redef] # noqa: E501
|
||||
except ImportError:
|
||||
tz = None # type: ignore[assignment]
|
||||
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"
|
||||
_split_on_space_comma = re.compile(r", *|(?: +)")
|
||||
|
||||
_split_on_space_comma_colon = re.compile(r", *|(?: +)|\:")
|
||||
|
||||
|
||||
class ScriptDirectory:
|
||||
|
||||
"""Provides operations upon an Alembic script directory.
|
||||
|
||||
This object is useful to get information as to current revisions,
|
||||
@@ -72,40 +76,55 @@ class ScriptDirectory:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dir: str, # noqa
|
||||
dir: Union[str, os.PathLike[str]], # noqa: A002
|
||||
file_template: str = _default_file_template,
|
||||
truncate_slug_length: Optional[int] = 40,
|
||||
version_locations: Optional[List[str]] = None,
|
||||
version_locations: Optional[
|
||||
Sequence[Union[str, os.PathLike[str]]]
|
||||
] = None,
|
||||
sourceless: bool = False,
|
||||
output_encoding: str = "utf-8",
|
||||
timezone: Optional[str] = None,
|
||||
hook_config: Optional[Mapping[str, str]] = None,
|
||||
hooks: list[PostWriteHookConfig] = [],
|
||||
recursive_version_locations: bool = False,
|
||||
messaging_opts: MessagingOptions = cast(
|
||||
"MessagingOptions", util.EMPTY_DICT
|
||||
),
|
||||
) -> None:
|
||||
self.dir = dir
|
||||
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.version_locations = version_locations
|
||||
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.hook_config = hook_config
|
||||
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(
|
||||
"Path doesn't exist: %r. Please use "
|
||||
f"Path doesn't exist: {dir}. Please use "
|
||||
"the 'init' command to create a new "
|
||||
"scripts folder." % os.path.abspath(dir)
|
||||
"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")
|
||||
@@ -113,40 +132,31 @@ class ScriptDirectory:
|
||||
return loc[0]
|
||||
|
||||
@util.memoized_property
|
||||
def _version_locations(self):
|
||||
def _version_locations(self) -> Sequence[Path]:
|
||||
if self.version_locations:
|
||||
return [
|
||||
os.path.abspath(util.coerce_resource_to_filename(location))
|
||||
util.coerce_resource_to_filename(location).absolute()
|
||||
for location in self.version_locations
|
||||
]
|
||||
else:
|
||||
return (os.path.abspath(os.path.join(self.dir, "versions")),)
|
||||
return [Path(self.dir, "versions").absolute()]
|
||||
|
||||
def _load_revisions(self) -> Iterator[Script]:
|
||||
if self.version_locations:
|
||||
paths = [
|
||||
vers
|
||||
for vers in self._version_locations
|
||||
if os.path.exists(vers)
|
||||
]
|
||||
else:
|
||||
paths = [self.versions]
|
||||
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 = os.path.realpath(file_path)
|
||||
real_path = file_path.resolve()
|
||||
if real_path in dupes:
|
||||
util.warn(
|
||||
"File %s loaded twice! ignoring. Please ensure "
|
||||
"version_locations is unique." % real_path
|
||||
f"File {real_path} loaded twice! ignoring. "
|
||||
"Please ensure version_locations is unique."
|
||||
)
|
||||
continue
|
||||
dupes.add(real_path)
|
||||
|
||||
filename = os.path.basename(real_path)
|
||||
dir_name = os.path.dirname(real_path)
|
||||
script = Script._from_filename(self, dir_name, filename)
|
||||
script = Script._from_path(self, real_path)
|
||||
if script is None:
|
||||
continue
|
||||
yield script
|
||||
@@ -160,74 +170,36 @@ class ScriptDirectory:
|
||||
present.
|
||||
|
||||
"""
|
||||
script_location = config.get_main_option("script_location")
|
||||
script_location = config.get_alembic_option("script_location")
|
||||
if script_location is None:
|
||||
raise util.CommandError(
|
||||
"No 'script_location' key " "found in configuration."
|
||||
"No 'script_location' key found in configuration."
|
||||
)
|
||||
truncate_slug_length: Optional[int]
|
||||
tsl = config.get_main_option("truncate_slug_length")
|
||||
tsl = config.get_alembic_option("truncate_slug_length")
|
||||
if tsl is not None:
|
||||
truncate_slug_length = int(tsl)
|
||||
else:
|
||||
truncate_slug_length = None
|
||||
|
||||
version_locations_str = config.get_main_option("version_locations")
|
||||
version_locations: Optional[List[str]]
|
||||
if version_locations_str:
|
||||
version_path_separator = config.get_main_option(
|
||||
"version_path_separator"
|
||||
)
|
||||
|
||||
split_on_path = {
|
||||
None: None,
|
||||
"space": " ",
|
||||
"os": os.pathsep,
|
||||
":": ":",
|
||||
";": ";",
|
||||
}
|
||||
|
||||
try:
|
||||
split_char: Optional[str] = split_on_path[
|
||||
version_path_separator
|
||||
]
|
||||
except KeyError as ke:
|
||||
raise ValueError(
|
||||
"'%s' is not a valid value for "
|
||||
"version_path_separator; "
|
||||
"expected 'space', 'os', ':', ';'" % version_path_separator
|
||||
) from ke
|
||||
else:
|
||||
if split_char is None:
|
||||
# legacy behaviour for backwards compatibility
|
||||
version_locations = _split_on_space_comma.split(
|
||||
version_locations_str
|
||||
)
|
||||
else:
|
||||
version_locations = [
|
||||
x for x in version_locations_str.split(split_char) if x
|
||||
]
|
||||
else:
|
||||
version_locations = None
|
||||
|
||||
prepend_sys_path = config.get_main_option("prepend_sys_path")
|
||||
prepend_sys_path = config.get_prepend_sys_paths_list()
|
||||
if prepend_sys_path:
|
||||
sys.path[:0] = list(
|
||||
_split_on_space_comma_colon.split(prepend_sys_path)
|
||||
)
|
||||
sys.path[:0] = prepend_sys_path
|
||||
|
||||
rvl = config.get_main_option("recursive_version_locations") == "true"
|
||||
rvl = config.get_alembic_boolean_option("recursive_version_locations")
|
||||
return ScriptDirectory(
|
||||
util.coerce_resource_to_filename(script_location),
|
||||
file_template=config.get_main_option(
|
||||
file_template=config.get_alembic_option(
|
||||
"file_template", _default_file_template
|
||||
),
|
||||
truncate_slug_length=truncate_slug_length,
|
||||
sourceless=config.get_main_option("sourceless") == "true",
|
||||
output_encoding=config.get_main_option("output_encoding", "utf-8"),
|
||||
version_locations=version_locations,
|
||||
timezone=config.get_main_option("timezone"),
|
||||
hook_config=config.get_section("post_write_hooks", {}),
|
||||
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,
|
||||
)
|
||||
@@ -297,24 +269,22 @@ class ScriptDirectory:
|
||||
):
|
||||
yield cast(Script, rev)
|
||||
|
||||
def get_revisions(self, id_: _GetRevArg) -> Tuple[Optional[Script], ...]:
|
||||
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[Optional[Script], ...],
|
||||
Tuple[Script, ...],
|
||||
self.revision_map.get_revisions(id_),
|
||||
)
|
||||
|
||||
def get_all_current(self, id_: Tuple[str, ...]) -> Set[Optional[Script]]:
|
||||
def get_all_current(self, id_: Tuple[str, ...]) -> Set[Script]:
|
||||
with self._catch_revision_errors():
|
||||
return cast(
|
||||
Set[Optional[Script]], self.revision_map._get_all_current(id_)
|
||||
)
|
||||
return cast(Set[Script], self.revision_map._get_all_current(id_))
|
||||
|
||||
def get_revision(self, id_: str) -> Optional[Script]:
|
||||
def get_revision(self, id_: str) -> Script:
|
||||
"""Return the :class:`.Script` instance with the given rev id.
|
||||
|
||||
.. seealso::
|
||||
@@ -324,7 +294,7 @@ class ScriptDirectory:
|
||||
"""
|
||||
|
||||
with self._catch_revision_errors():
|
||||
return cast(Optional[Script], self.revision_map.get_revision(id_))
|
||||
return cast(Script, self.revision_map.get_revision(id_))
|
||||
|
||||
def as_revision_number(
|
||||
self, id_: Optional[str]
|
||||
@@ -579,24 +549,37 @@ class ScriptDirectory:
|
||||
util.load_python_file(self.dir, "env.py")
|
||||
|
||||
@property
|
||||
def env_py_location(self):
|
||||
return os.path.abspath(os.path.join(self.dir, "env.py"))
|
||||
def env_py_location(self) -> str:
|
||||
return str(Path(self.dir, "env.py"))
|
||||
|
||||
def _generate_template(self, src: str, dest: str, **kw: Any) -> None:
|
||||
def _append_template(self, src: Path, dest: Path, **kw: Any) -> None:
|
||||
with util.status(
|
||||
f"Generating {os.path.abspath(dest)}", **self.messaging_opts
|
||||
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: str, dest: str) -> None:
|
||||
def _copy_file(self, src: Path, dest: Path) -> None:
|
||||
with util.status(
|
||||
f"Generating {os.path.abspath(dest)}", **self.messaging_opts
|
||||
f"Generating {dest.absolute()}", **self.messaging_opts
|
||||
):
|
||||
shutil.copy(src, dest)
|
||||
|
||||
def _ensure_directory(self, path: str) -> None:
|
||||
path = os.path.abspath(path)
|
||||
if not os.path.exists(path):
|
||||
def _ensure_directory(self, path: Path) -> None:
|
||||
path = path.absolute()
|
||||
if not path.exists():
|
||||
with util.status(
|
||||
f"Creating directory {path}", **self.messaging_opts
|
||||
):
|
||||
@@ -604,25 +587,27 @@ class ScriptDirectory:
|
||||
|
||||
def _generate_create_date(self) -> datetime.datetime:
|
||||
if self.timezone is not None:
|
||||
if tz is None:
|
||||
if ZoneInfo is None:
|
||||
raise util.CommandError(
|
||||
"The library 'python-dateutil' is required "
|
||||
"for timezone support"
|
||||
"Python >= 3.9 is required for timezone support or "
|
||||
"the 'backports.zoneinfo' package must be installed."
|
||||
)
|
||||
# First, assume correct capitalization
|
||||
tzinfo = tz.gettz(self.timezone)
|
||||
try:
|
||||
tzinfo = ZoneInfo(self.timezone)
|
||||
except ZoneInfoNotFoundError:
|
||||
tzinfo = None
|
||||
if tzinfo is None:
|
||||
# Fall back to uppercase
|
||||
tzinfo = tz.gettz(self.timezone.upper())
|
||||
if tzinfo is None:
|
||||
raise util.CommandError(
|
||||
"Can't locate timezone: %s" % self.timezone
|
||||
)
|
||||
create_date = (
|
||||
datetime.datetime.utcnow()
|
||||
.replace(tzinfo=tz.tzutc())
|
||||
.astimezone(tzinfo)
|
||||
)
|
||||
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
|
||||
@@ -634,7 +619,8 @@ class ScriptDirectory:
|
||||
head: Optional[_RevIdType] = None,
|
||||
splice: Optional[bool] = False,
|
||||
branch_labels: Optional[_RevIdType] = None,
|
||||
version_path: Optional[str] = None,
|
||||
version_path: Union[str, os.PathLike[str], None] = None,
|
||||
file_template: Optional[str] = None,
|
||||
depends_on: Optional[_RevIdType] = None,
|
||||
**kw: Any,
|
||||
) -> Optional[Script]:
|
||||
@@ -675,7 +661,7 @@ class ScriptDirectory:
|
||||
self.revision_map.get_revisions(head),
|
||||
)
|
||||
for h in heads:
|
||||
assert h != "base"
|
||||
assert h != "base" # type: ignore[comparison-overlap]
|
||||
|
||||
if len(set(heads)) != len(heads):
|
||||
raise util.CommandError("Duplicate head revisions specified")
|
||||
@@ -687,7 +673,7 @@ class ScriptDirectory:
|
||||
for head_ in heads:
|
||||
if head_ is not None:
|
||||
assert isinstance(head_, Script)
|
||||
version_path = os.path.dirname(head_.path)
|
||||
version_path = head_._script_path.parent
|
||||
break
|
||||
else:
|
||||
raise util.CommandError(
|
||||
@@ -695,16 +681,19 @@ class ScriptDirectory:
|
||||
"please specify --version-path"
|
||||
)
|
||||
else:
|
||||
version_path = self.versions
|
||||
version_path = self._singular_version_location
|
||||
else:
|
||||
version_path = Path(version_path)
|
||||
|
||||
norm_path = os.path.normpath(os.path.abspath(version_path))
|
||||
assert isinstance(version_path, Path)
|
||||
norm_path = version_path.absolute()
|
||||
for vers_path in self._version_locations:
|
||||
if os.path.normpath(vers_path) == norm_path:
|
||||
if vers_path.absolute() == norm_path:
|
||||
break
|
||||
else:
|
||||
raise util.CommandError(
|
||||
"Path %s is not represented in current "
|
||||
"version locations" % version_path
|
||||
f"Path {version_path} is not represented in current "
|
||||
"version locations"
|
||||
)
|
||||
|
||||
if self.version_locations:
|
||||
@@ -725,9 +714,11 @@ class ScriptDirectory:
|
||||
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
|
||||
(
|
||||
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)
|
||||
@@ -737,7 +728,7 @@ class ScriptDirectory:
|
||||
resolved_depends_on = None
|
||||
|
||||
self._generate_template(
|
||||
os.path.join(self.dir, "script.py.mako"),
|
||||
Path(self.dir, "script.py.mako"),
|
||||
path,
|
||||
up_revision=str(revid),
|
||||
down_revision=revision.tuple_rev_as_scalar(
|
||||
@@ -751,7 +742,7 @@ class ScriptDirectory:
|
||||
**kw,
|
||||
)
|
||||
|
||||
post_write_hooks = self.hook_config
|
||||
post_write_hooks = self.hooks
|
||||
if post_write_hooks:
|
||||
write_hooks._run_hooks(path, post_write_hooks)
|
||||
|
||||
@@ -774,11 +765,11 @@ class ScriptDirectory:
|
||||
|
||||
def _rev_path(
|
||||
self,
|
||||
path: str,
|
||||
path: Union[str, os.PathLike[str]],
|
||||
rev_id: str,
|
||||
message: Optional[str],
|
||||
create_date: datetime.datetime,
|
||||
) -> str:
|
||||
) -> Path:
|
||||
epoch = int(create_date.timestamp())
|
||||
slug = "_".join(_slug_re.findall(message or "")).lower()
|
||||
if len(slug) > self.truncate_slug_length:
|
||||
@@ -797,11 +788,10 @@ class ScriptDirectory:
|
||||
"second": create_date.second,
|
||||
}
|
||||
)
|
||||
return os.path.join(path, filename)
|
||||
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
|
||||
@@ -809,12 +799,17 @@ class Script(revision.Revision):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, module: ModuleType, rev_id: str, path: str):
|
||||
def __init__(
|
||||
self,
|
||||
module: ModuleType,
|
||||
rev_id: str,
|
||||
path: Union[str, os.PathLike[str]],
|
||||
):
|
||||
self.module = module
|
||||
self.path = path
|
||||
self.path = _preserving_path_as_str(path)
|
||||
super().__init__(
|
||||
rev_id,
|
||||
module.down_revision, # type: ignore[attr-defined]
|
||||
module.down_revision,
|
||||
branch_labels=util.to_tuple(
|
||||
getattr(module, "branch_labels", None), default=()
|
||||
),
|
||||
@@ -829,6 +824,10 @@ class Script(revision.Revision):
|
||||
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"""
|
||||
@@ -847,9 +846,9 @@ class Script(revision.Revision):
|
||||
if doc:
|
||||
if hasattr(self.module, "_alembic_source_encoding"):
|
||||
doc = doc.decode( # type: ignore[attr-defined]
|
||||
self.module._alembic_source_encoding # type: ignore[attr-defined] # noqa
|
||||
self.module._alembic_source_encoding
|
||||
)
|
||||
return doc.strip() # type: ignore[union-attr]
|
||||
return doc.strip()
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -889,7 +888,7 @@ class Script(revision.Revision):
|
||||
)
|
||||
return entry
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return "%s -> %s%s%s%s, %s" % (
|
||||
self._format_down_revision(),
|
||||
self.revision,
|
||||
@@ -923,9 +922,11 @@ class Script(revision.Revision):
|
||||
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 "",
|
||||
(
|
||||
" (effective head)"
|
||||
if self.is_head and not self._is_real_head
|
||||
else ""
|
||||
),
|
||||
" (current)" if self._db_current_indicator else "",
|
||||
)
|
||||
if tree_indicators:
|
||||
@@ -959,36 +960,33 @@ class Script(revision.Revision):
|
||||
return util.format_as_comma(self._versioned_down_revisions)
|
||||
|
||||
@classmethod
|
||||
def _from_path(
|
||||
cls, scriptdir: ScriptDirectory, path: str
|
||||
) -> Optional[Script]:
|
||||
dir_, filename = os.path.split(path)
|
||||
return cls._from_filename(scriptdir, dir_, filename)
|
||||
|
||||
@classmethod
|
||||
def _list_py_dir(cls, scriptdir: ScriptDirectory, path: str) -> List[str]:
|
||||
def _list_py_dir(
|
||||
cls, scriptdir: ScriptDirectory, path: Path
|
||||
) -> List[Path]:
|
||||
paths = []
|
||||
for root, dirs, files in os.walk(path, topdown=True):
|
||||
if root.endswith("__pycache__"):
|
||||
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(os.path.join(root, filename))
|
||||
paths.append(root / filename)
|
||||
|
||||
if scriptdir.sourceless:
|
||||
# look for __pycache__
|
||||
py_cache_path = os.path.join(root, "__pycache__")
|
||||
if os.path.exists(py_cache_path):
|
||||
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 = {filename.split(".")[0] for filename in files}
|
||||
names = {
|
||||
Path(filename).name.split(".")[0] for filename in files
|
||||
}
|
||||
paths.extend(
|
||||
os.path.join(py_cache_path, pyc)
|
||||
for pyc in os.listdir(py_cache_path)
|
||||
if pyc.split(".")[0] not in names
|
||||
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:
|
||||
@@ -1003,9 +1001,13 @@ class Script(revision.Revision):
|
||||
return paths
|
||||
|
||||
@classmethod
|
||||
def _from_filename(
|
||||
cls, scriptdir: ScriptDirectory, dir_: str, filename: str
|
||||
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:
|
||||
@@ -1023,8 +1025,8 @@ class Script(revision.Revision):
|
||||
is_c = is_o = False
|
||||
|
||||
if is_o or is_c:
|
||||
py_exists = os.path.exists(os.path.join(dir_, py_filename))
|
||||
pyc_exists = os.path.exists(os.path.join(dir_, py_filename + "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
|
||||
@@ -1040,14 +1042,14 @@ class Script(revision.Revision):
|
||||
m = _legacy_rev.match(filename)
|
||||
if not m:
|
||||
raise util.CommandError(
|
||||
"Could not determine revision id from filename %s. "
|
||||
"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)."
|
||||
% filename
|
||||
)
|
||||
else:
|
||||
revision = m.group(1)
|
||||
else:
|
||||
revision = module.revision
|
||||
return Script(module, revision, os.path.join(dir_, filename))
|
||||
return Script(module, revision, dir_ / filename)
|
||||
|
||||
@@ -14,6 +14,7 @@ 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
|
||||
@@ -47,6 +48,17 @@ _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
|
||||
|
||||
@@ -396,7 +408,7 @@ class RevisionMap:
|
||||
for rev in self._get_ancestor_nodes(
|
||||
[revision],
|
||||
include_dependencies=False,
|
||||
map_=cast(_RevisionMapType, map_),
|
||||
map_=map_,
|
||||
):
|
||||
if rev is revision:
|
||||
continue
|
||||
@@ -707,9 +719,11 @@ class RevisionMap:
|
||||
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
|
||||
(
|
||||
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=()
|
||||
)
|
||||
@@ -791,7 +805,7 @@ class RevisionMap:
|
||||
The iterator yields :class:`.Revision` objects.
|
||||
|
||||
"""
|
||||
fn: Callable
|
||||
fn: _CollectRevisionsProtocol
|
||||
if select_for_downgrade:
|
||||
fn = self._collect_downgrade_revisions
|
||||
else:
|
||||
@@ -818,7 +832,7 @@ class RevisionMap:
|
||||
) -> Iterator[Any]:
|
||||
if omit_immediate_dependencies:
|
||||
|
||||
def fn(rev):
|
||||
def fn(rev: Revision) -> Iterable[str]:
|
||||
if rev not in targets:
|
||||
return rev._all_nextrev
|
||||
else:
|
||||
@@ -826,12 +840,12 @@ class RevisionMap:
|
||||
|
||||
elif include_dependencies:
|
||||
|
||||
def fn(rev):
|
||||
def fn(rev: Revision) -> Iterable[str]:
|
||||
return rev._all_nextrev
|
||||
|
||||
else:
|
||||
|
||||
def fn(rev):
|
||||
def fn(rev: Revision) -> Iterable[str]:
|
||||
return rev.nextrev
|
||||
|
||||
return self._iterate_related_revisions(
|
||||
@@ -847,12 +861,12 @@ class RevisionMap:
|
||||
) -> Iterator[Revision]:
|
||||
if include_dependencies:
|
||||
|
||||
def fn(rev):
|
||||
def fn(rev: Revision) -> Iterable[str]:
|
||||
return rev._normalized_down_revisions
|
||||
|
||||
else:
|
||||
|
||||
def fn(rev):
|
||||
def fn(rev: Revision) -> Iterable[str]:
|
||||
return rev._versioned_down_revisions
|
||||
|
||||
return self._iterate_related_revisions(
|
||||
@@ -861,7 +875,7 @@ class RevisionMap:
|
||||
|
||||
def _iterate_related_revisions(
|
||||
self,
|
||||
fn: Callable,
|
||||
fn: Callable[[Revision], Iterable[str]],
|
||||
targets: Collection[Optional[_RevisionOrBase]],
|
||||
map_: Optional[_RevisionMapType],
|
||||
check: bool = False,
|
||||
@@ -923,7 +937,7 @@ class RevisionMap:
|
||||
|
||||
id_to_rev = self._revision_map
|
||||
|
||||
def get_ancestors(rev_id):
|
||||
def get_ancestors(rev_id: str) -> Set[str]:
|
||||
return {
|
||||
r.revision
|
||||
for r in self._get_ancestor_nodes([id_to_rev[rev_id]])
|
||||
@@ -1003,9 +1017,9 @@ class RevisionMap:
|
||||
# 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[current_candidate_idx] = (
|
||||
get_ancestors(heads_to_add[0])
|
||||
)
|
||||
ancestors_by_idx.extend(
|
||||
get_ancestors(head) for head in heads_to_add[1:]
|
||||
)
|
||||
@@ -1041,7 +1055,7 @@ class RevisionMap:
|
||||
children: Sequence[Optional[_RevisionOrBase]]
|
||||
for _ in range(abs(steps)):
|
||||
if steps > 0:
|
||||
assert initial != "base"
|
||||
assert initial != "base" # type: ignore[comparison-overlap]
|
||||
# Walk up
|
||||
walk_up = [
|
||||
is_revision(rev)
|
||||
@@ -1055,7 +1069,7 @@ class RevisionMap:
|
||||
children = walk_up
|
||||
else:
|
||||
# Walk down
|
||||
if initial == "base":
|
||||
if initial == "base": # type: ignore[comparison-overlap]
|
||||
children = ()
|
||||
else:
|
||||
children = self.get_revisions(
|
||||
@@ -1170,9 +1184,13 @@ class RevisionMap:
|
||||
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)),
|
||||
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,
|
||||
)
|
||||
@@ -1189,7 +1207,7 @@ class RevisionMap:
|
||||
# No relative destination given, revision specified is absolute.
|
||||
branch_label, _, symbol = target.rpartition("@")
|
||||
if not branch_label:
|
||||
branch_label = None # type:ignore[assignment]
|
||||
branch_label = None
|
||||
return branch_label, self.get_revision(symbol)
|
||||
|
||||
def _parse_upgrade_target(
|
||||
@@ -1290,9 +1308,13 @@ class RevisionMap:
|
||||
)
|
||||
return (
|
||||
self._walk(
|
||||
start=self.get_revision(symbol)
|
||||
if branch_label is None
|
||||
else self.get_revision("%s@%s" % (branch_label, symbol)),
|
||||
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,
|
||||
),
|
||||
@@ -1301,11 +1323,11 @@ class RevisionMap:
|
||||
def _collect_downgrade_revisions(
|
||||
self,
|
||||
upper: _RevisionIdentifierType,
|
||||
target: _RevisionIdentifierType,
|
||||
lower: _RevisionIdentifierType,
|
||||
inclusive: bool,
|
||||
implicit_base: bool,
|
||||
assert_relative_length: bool,
|
||||
) -> Any:
|
||||
) -> 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
|
||||
@@ -1316,7 +1338,7 @@ class RevisionMap:
|
||||
|
||||
branch_label, target_revision = self._parse_downgrade_target(
|
||||
current_revisions=upper,
|
||||
target=target,
|
||||
target=lower,
|
||||
assert_relative_length=assert_relative_length,
|
||||
)
|
||||
if target_revision == "base":
|
||||
@@ -1408,7 +1430,7 @@ class RevisionMap:
|
||||
inclusive: bool,
|
||||
implicit_base: bool,
|
||||
assert_relative_length: bool,
|
||||
) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase]]]:
|
||||
) -> 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
|
||||
@@ -1500,7 +1522,7 @@ class RevisionMap:
|
||||
)
|
||||
needs.intersection_update(lower_descendents)
|
||||
|
||||
return needs, tuple(targets) # type:ignore[return-value]
|
||||
return needs, tuple(targets)
|
||||
|
||||
def _get_all_current(
|
||||
self, id_: Tuple[str, ...]
|
||||
@@ -1681,15 +1703,13 @@ class Revision:
|
||||
|
||||
|
||||
@overload
|
||||
def tuple_rev_as_scalar(rev: None) -> None:
|
||||
...
|
||||
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]]:
|
||||
...
|
||||
rev: Union[Tuple[_T, ...], List[_T]],
|
||||
) -> Union[_T, Tuple[_T, ...], List[_T]]: ...
|
||||
|
||||
|
||||
def tuple_rev_as_scalar(
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# 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
|
||||
@@ -7,13 +12,16 @@ from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
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"
|
||||
|
||||
@@ -40,16 +48,19 @@ def register(name: str) -> Callable:
|
||||
|
||||
|
||||
def _invoke(
|
||||
name: str, revision: str, options: Mapping[str, Union[str, int]]
|
||||
name: str,
|
||||
revision_path: Union[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: A :class:`.MigrationRevision` instance
|
||||
: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:
|
||||
@@ -57,36 +68,28 @@ def _invoke(
|
||||
f"No formatter with name '{name}' registered"
|
||||
) from ke
|
||||
else:
|
||||
return hook(revision, options)
|
||||
return hook(revision_path, options)
|
||||
|
||||
|
||||
def _run_hooks(path: str, hook_config: Mapping[str, str]) -> None:
|
||||
def _run_hooks(
|
||||
path: Union[str, os.PathLike[str]], hooks: list[PostWriteHookConfig]
|
||||
) -> None:
|
||||
"""Invoke hooks for a generated revision."""
|
||||
|
||||
from .base import _split_on_space_comma
|
||||
|
||||
names = _split_on_space_comma.split(hook_config.get("hooks", ""))
|
||||
|
||||
for name in names:
|
||||
if not name:
|
||||
continue
|
||||
opts = {
|
||||
key[len(name) + 1 :]: hook_config[key]
|
||||
for key in hook_config
|
||||
if key.startswith(name + ".")
|
||||
}
|
||||
opts["_hook_name"] = name
|
||||
for hook in hooks:
|
||||
name = hook["_hook_name"]
|
||||
try:
|
||||
type_ = opts["type"]
|
||||
type_ = hook["type"]
|
||||
except KeyError as ke:
|
||||
raise util.CommandError(
|
||||
f"Key {name}.type is required for post write hook {name!r}"
|
||||
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, opts)
|
||||
_invoke(type_, path, hook)
|
||||
|
||||
|
||||
def _parse_cmdline_options(cmdline_options_str: str, path: str) -> List[str]:
|
||||
@@ -110,17 +113,35 @@ def _parse_cmdline_options(cmdline_options_str: str, path: str) -> List[str]:
|
||||
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: Optional[str] = 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
|
||||
) -> None:
|
||||
try:
|
||||
entrypoint_name = options["entrypoint"]
|
||||
except KeyError as ke:
|
||||
raise util.CommandError(
|
||||
f"Key {options['_hook_name']}.entrypoint is required for post "
|
||||
f"write hook {options['_hook_name']!r}"
|
||||
) from ke
|
||||
entrypoint_name = _get_required_option(options, "entrypoint")
|
||||
for entry in compat.importlib_metadata_get("console_scripts"):
|
||||
if entry.name == entrypoint_name:
|
||||
impl: Any = entry
|
||||
@@ -129,48 +150,27 @@ def console_scripts(
|
||||
raise util.CommandError(
|
||||
f"Could not find entrypoint console_scripts.{entrypoint_name}"
|
||||
)
|
||||
cwd: Optional[str] = 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(
|
||||
[
|
||||
sys.executable,
|
||||
"-c",
|
||||
f"import {impl.module}; {impl.module}.{impl.attr}()",
|
||||
]
|
||||
+ cmdline_options_list,
|
||||
cwd=cwd,
|
||||
**kw,
|
||||
)
|
||||
command = [
|
||||
sys.executable,
|
||||
"-c",
|
||||
f"import {impl.module}; {impl.module}.{impl.attr}()",
|
||||
]
|
||||
_run_hook(path, options, ignore_output, command)
|
||||
|
||||
|
||||
@register("exec")
|
||||
def exec_(path: str, options: dict, ignore_output: bool = False) -> None:
|
||||
try:
|
||||
executable = options["executable"]
|
||||
except KeyError as ke:
|
||||
raise util.CommandError(
|
||||
f"Key {options['_hook_name']}.executable is required for post "
|
||||
f"write hook {options['_hook_name']!r}"
|
||||
) from ke
|
||||
cwd: Optional[str] = options.get("cwd", None)
|
||||
cmdline_options_str = options.get("options", "")
|
||||
cmdline_options_list = _parse_cmdline_options(cmdline_options_str, path)
|
||||
executable = _get_required_option(options, "executable")
|
||||
_run_hook(path, options, ignore_output, command=[executable])
|
||||
|
||||
kw: Dict[str, Any] = {}
|
||||
if ignore_output:
|
||||
kw["stdout"] = kw["stderr"] = subprocess.DEVNULL
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
executable,
|
||||
*cmdline_options_list,
|
||||
],
|
||||
cwd=cwd,
|
||||
**kw,
|
||||
)
|
||||
@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)
|
||||
|
||||
@@ -1,27 +1,32 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
# 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
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
# 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 python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can 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
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
@@ -34,20 +39,38 @@ prepend_sys_path = .
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to ${script_location}/versions. When using multiple version
|
||||
# to <script_location>/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 "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
|
||||
# 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
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
# 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.
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
# 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
|
||||
@@ -58,6 +81,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
||||
# 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
|
||||
|
||||
|
||||
@@ -72,13 +98,20 @@ sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# 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 = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
@@ -89,12 +122,12 @@ keys = console
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ ${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_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"}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
# 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
|
||||
@@ -11,19 +14,20 @@ script_location = ${script_location}
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
# 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 python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can 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
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
@@ -36,20 +40,37 @@ prepend_sys_path = .
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to ${script_location}/versions. When using multiple version
|
||||
# to <script_location>/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 "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
|
||||
# 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
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
# 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.
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
# 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
|
||||
@@ -60,6 +81,9 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
||||
# 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
|
||||
|
||||
|
||||
@@ -74,13 +98,20 @@ sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# 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 = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
@@ -91,12 +122,12 @@ keys = console
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
|
||||
@@ -13,14 +13,16 @@ ${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_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"}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# a multi-database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
# 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
|
||||
@@ -11,19 +14,19 @@ script_location = ${script_location}
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
# 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 python-dateutil library that can be
|
||||
# installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to dateutil.tz.gettz()
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can 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
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
@@ -36,20 +39,37 @@ prepend_sys_path = .
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to ${script_location}/versions. When using multiple version
|
||||
# to <script_location>/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 "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:${script_location}/versions
|
||||
# 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
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
# 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.
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
|
||||
# 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
|
||||
@@ -60,6 +80,13 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
|
||||
# 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]
|
||||
@@ -79,13 +106,20 @@ sqlalchemy.url = driver://user:pass@localhost/dbname2
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# 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 = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
# ruff.executable = ruff
|
||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
# Logging configuration. This is also consumed by the user-maintained
|
||||
# env.py script only.
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
@@ -96,12 +130,12 @@ keys = console
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
|
||||
@@ -16,16 +16,18 @@ ${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_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]()
|
||||
|
||||
<%
|
||||
@@ -38,10 +40,12 @@ def downgrade(engine_name: str) -> None:
|
||||
% 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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pyproject configuration, based on the generic configuration.
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,82 @@
|
||||
[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"
|
||||
|
||||
# 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 python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can 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 <script_location>/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"
|
||||
|
||||
@@ -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"}
|
||||
@@ -0,0 +1 @@
|
||||
pyproject configuration, with an async dbapi.
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,82 @@
|
||||
[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"
|
||||
|
||||
# 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 python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can 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 <script_location>/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"
|
||||
|
||||
@@ -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"}
|
||||
@@ -9,12 +9,15 @@ 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
|
||||
|
||||
@@ -8,6 +8,7 @@ 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
|
||||
@@ -17,8 +18,6 @@ from sqlalchemy.testing.assertions import is_true # noqa
|
||||
from sqlalchemy.testing.assertions import ne_ # noqa
|
||||
from sqlalchemy.util import decorator
|
||||
|
||||
from ..util import sqla_compat
|
||||
|
||||
|
||||
def _assert_proper_exception_context(exception):
|
||||
"""assert that any exception we're catching does not have a __context__
|
||||
@@ -74,7 +73,9 @@ class _ErrorContainer:
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _expect_raises(except_cls, msg=None, check_context=False):
|
||||
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]
|
||||
@@ -85,7 +86,10 @@ def _expect_raises(except_cls, msg=None, check_context=False):
|
||||
ec.error = err
|
||||
success = True
|
||||
if msg is not None:
|
||||
assert re.search(msg, str(err), re.UNICODE), f"{msg} !~ {err}"
|
||||
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"))
|
||||
@@ -98,8 +102,12 @@ 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):
|
||||
return _expect_raises(except_cls, msg=msg, 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):
|
||||
@@ -118,7 +126,7 @@ def _get_dialect(name):
|
||||
if name is None or name == "default":
|
||||
return default.DefaultDialect()
|
||||
else:
|
||||
d = sqla_compat._create_url(name).get_dialect()()
|
||||
d = URL.create(name).get_dialect()()
|
||||
|
||||
if name == "postgresql":
|
||||
d.implicit_returning = True
|
||||
@@ -159,6 +167,10 @@ def emits_python_deprecation_warning(*messages):
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import importlib.machinery
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import textwrap
|
||||
|
||||
@@ -16,7 +17,7 @@ from ..script import ScriptDirectory
|
||||
|
||||
def _get_staging_directory():
|
||||
if provision.FOLLOWER_IDENT:
|
||||
return "scratch_%s" % provision.FOLLOWER_IDENT
|
||||
return f"scratch_{provision.FOLLOWER_IDENT}"
|
||||
else:
|
||||
return "scratch"
|
||||
|
||||
@@ -24,7 +25,7 @@ def _get_staging_directory():
|
||||
def staging_env(create=True, template="generic", sourceless=False):
|
||||
cfg = _testing_config()
|
||||
if create:
|
||||
path = os.path.join(_get_staging_directory(), "scripts")
|
||||
path = _join_path(_get_staging_directory(), "scripts")
|
||||
assert not os.path.exists(path), (
|
||||
"staging directory %s already exists; poor cleanup?" % path
|
||||
)
|
||||
@@ -47,7 +48,7 @@ def staging_env(create=True, template="generic", sourceless=False):
|
||||
"pep3147_everything",
|
||||
), sourceless
|
||||
make_sourceless(
|
||||
os.path.join(path, "env.py"),
|
||||
_join_path(path, "env.py"),
|
||||
"pep3147" if "pep3147" in sourceless else "simple",
|
||||
)
|
||||
|
||||
@@ -63,14 +64,14 @@ def clear_staging_env():
|
||||
|
||||
|
||||
def script_file_fixture(txt):
|
||||
dir_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
path = os.path.join(dir_, "script.py.mako")
|
||||
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_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
dir_ = _join_path(_get_staging_directory(), "scripts")
|
||||
txt = (
|
||||
"""
|
||||
from alembic import context
|
||||
@@ -80,7 +81,7 @@ config = context.config
|
||||
+ txt
|
||||
)
|
||||
|
||||
path = os.path.join(dir_, "env.py")
|
||||
path = _join_path(dir_, "env.py")
|
||||
pyc_path = util.pyc_file_from_path(path)
|
||||
if pyc_path:
|
||||
os.unlink(pyc_path)
|
||||
@@ -90,26 +91,26 @@ config = context.config
|
||||
|
||||
|
||||
def _sqlite_file_db(tempname="foo.db", future=False, scope=None, **options):
|
||||
dir_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
dir_ = _join_path(_get_staging_directory(), "scripts")
|
||||
url = "sqlite:///%s/%s" % (dir_, tempname)
|
||||
if scope and util.sqla_14:
|
||||
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_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
url = "sqlite:///%s/foo.db" % dir_
|
||||
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 = %s
|
||||
sqlalchemy.url = %s
|
||||
sourceless = %s
|
||||
%s
|
||||
script_location = {dir_}
|
||||
sqlalchemy.url = {url}
|
||||
sourceless = {"true" if sourceless else "false"}
|
||||
{"sqlalchemy.future = true" if sqlalchemy_future else ""}
|
||||
|
||||
[loggers]
|
||||
keys = root,sqlalchemy
|
||||
@@ -118,7 +119,7 @@ keys = root,sqlalchemy
|
||||
keys = console
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
@@ -140,29 +141,25 @@ keys = generic
|
||||
format = %%(levelname)-5.5s [%%(name)s] %%(message)s
|
||||
datefmt = %%H:%%M:%%S
|
||||
"""
|
||||
% (
|
||||
dir_,
|
||||
url,
|
||||
"true" if sourceless else "false",
|
||||
"sqlalchemy.future = true" if sqlalchemy_future else "",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _multi_dir_testing_config(sourceless=False, extra_version_location=""):
|
||||
dir_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
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 = %s
|
||||
sqlalchemy.url = %s
|
||||
sqlalchemy.future = %s
|
||||
sourceless = %s
|
||||
version_locations = %%(here)s/model1/ %%(here)s/model2/ %%(here)s/model3/ %s
|
||||
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
|
||||
@@ -171,7 +168,7 @@ keys = root
|
||||
keys = console
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
@@ -188,26 +185,24 @@ keys = generic
|
||||
format = %%(levelname)-5.5s [%%(name)s] %%(message)s
|
||||
datefmt = %%H:%%M:%%S
|
||||
"""
|
||||
% (
|
||||
dir_,
|
||||
url,
|
||||
"true" if sqlalchemy_future else "false",
|
||||
"true" if sourceless else "false",
|
||||
extra_version_location,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _no_sql_testing_config(dialect="postgresql", directives=""):
|
||||
def _no_sql_pyproject_config(dialect="postgresql", directives=""):
|
||||
"""use a postgresql url with no host so that
|
||||
connections guaranteed to fail"""
|
||||
dir_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
return _write_config_file(
|
||||
"""
|
||||
dir_ = _join_path(_get_staging_directory(), "scripts")
|
||||
|
||||
return _write_toml_config(
|
||||
f"""
|
||||
[tool.alembic]
|
||||
script_location ="{dir_}"
|
||||
{textwrap.dedent(directives)}
|
||||
|
||||
""",
|
||||
f"""
|
||||
[alembic]
|
||||
script_location = %s
|
||||
sqlalchemy.url = %s://
|
||||
%s
|
||||
sqlalchemy.url = {dialect}://
|
||||
|
||||
[loggers]
|
||||
keys = root
|
||||
@@ -216,7 +211,46 @@ keys = root
|
||||
keys = console
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
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 =
|
||||
|
||||
@@ -234,10 +268,16 @@ format = %%(levelname)-5.5s [%%(name)s] %%(message)s
|
||||
datefmt = %%H:%%M:%%S
|
||||
|
||||
"""
|
||||
% (dir_, dialect, directives)
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
@@ -250,7 +290,10 @@ def _testing_config():
|
||||
|
||||
if not os.access(_get_staging_directory(), os.F_OK):
|
||||
os.mkdir(_get_staging_directory())
|
||||
return Config(os.path.join(_get_staging_directory(), "test_alembic.ini"))
|
||||
return Config(
|
||||
_join_path(_get_staging_directory(), "test_alembic.ini"),
|
||||
_join_path(_get_staging_directory(), "pyproject.toml"),
|
||||
)
|
||||
|
||||
|
||||
def write_script(
|
||||
@@ -270,9 +313,7 @@ def write_script(
|
||||
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."
|
||||
)
|
||||
raise Exception("Can't change down_revision on a refresh operation.")
|
||||
scriptdir.revision_map.add_revision(script, _replace=True)
|
||||
|
||||
if sourceless:
|
||||
@@ -312,9 +353,9 @@ def three_rev_fixture(cfg):
|
||||
write_script(
|
||||
script,
|
||||
a,
|
||||
"""\
|
||||
f"""\
|
||||
"Rev A"
|
||||
revision = '%s'
|
||||
revision = '{a}'
|
||||
down_revision = None
|
||||
|
||||
from alembic import op
|
||||
@@ -327,8 +368,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
op.execute("DROP STEP 1")
|
||||
|
||||
"""
|
||||
% a,
|
||||
""",
|
||||
)
|
||||
|
||||
script.generate_revision(b, "revision b", refresh=True, head=a)
|
||||
@@ -358,10 +398,10 @@ def downgrade():
|
||||
write_script(
|
||||
script,
|
||||
c,
|
||||
"""\
|
||||
f"""\
|
||||
"Rev C"
|
||||
revision = '%s'
|
||||
down_revision = '%s'
|
||||
revision = '{c}'
|
||||
down_revision = '{b}'
|
||||
|
||||
from alembic import op
|
||||
|
||||
@@ -373,8 +413,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
op.execute("DROP STEP 3")
|
||||
|
||||
"""
|
||||
% (c, b),
|
||||
""",
|
||||
)
|
||||
return a, b, c
|
||||
|
||||
@@ -396,10 +435,10 @@ def multi_heads_fixture(cfg, a, b, c):
|
||||
write_script(
|
||||
script,
|
||||
d,
|
||||
"""\
|
||||
f"""\
|
||||
"Rev D"
|
||||
revision = '%s'
|
||||
down_revision = '%s'
|
||||
revision = '{d}'
|
||||
down_revision = '{b}'
|
||||
|
||||
from alembic import op
|
||||
|
||||
@@ -411,8 +450,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
op.execute("DROP STEP 4")
|
||||
|
||||
"""
|
||||
% (d, b),
|
||||
""",
|
||||
)
|
||||
|
||||
script.generate_revision(
|
||||
@@ -421,10 +459,10 @@ def downgrade():
|
||||
write_script(
|
||||
script,
|
||||
e,
|
||||
"""\
|
||||
f"""\
|
||||
"Rev E"
|
||||
revision = '%s'
|
||||
down_revision = '%s'
|
||||
revision = '{e}'
|
||||
down_revision = '{d}'
|
||||
|
||||
from alembic import op
|
||||
|
||||
@@ -436,8 +474,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
op.execute("DROP STEP 5")
|
||||
|
||||
"""
|
||||
% (e, d),
|
||||
""",
|
||||
)
|
||||
|
||||
script.generate_revision(
|
||||
@@ -446,10 +483,10 @@ def downgrade():
|
||||
write_script(
|
||||
script,
|
||||
f,
|
||||
"""\
|
||||
f"""\
|
||||
"Rev F"
|
||||
revision = '%s'
|
||||
down_revision = '%s'
|
||||
revision = '{f}'
|
||||
down_revision = '{b}'
|
||||
|
||||
from alembic import op
|
||||
|
||||
@@ -461,8 +498,7 @@ def upgrade():
|
||||
def downgrade():
|
||||
op.execute("DROP STEP 6")
|
||||
|
||||
"""
|
||||
% (f, b),
|
||||
""",
|
||||
)
|
||||
|
||||
return d, e, f
|
||||
@@ -471,25 +507,25 @@ def downgrade():
|
||||
def _multidb_testing_config(engines):
|
||||
"""alembic.ini fixture to work exactly with the 'multidb' template"""
|
||||
|
||||
dir_ = os.path.join(_get_staging_directory(), "scripts")
|
||||
dir_ = _join_path(_get_staging_directory(), "scripts")
|
||||
|
||||
sqlalchemy_future = "future" in config.db.__class__.__module__
|
||||
|
||||
databases = ", ".join(engines.keys())
|
||||
engines = "\n\n".join(
|
||||
"[%s]\n" "sqlalchemy.url = %s" % (key, value.url)
|
||||
f"[{key}]\nsqlalchemy.url = {value.url}"
|
||||
for key, value in engines.items()
|
||||
)
|
||||
|
||||
return _write_config_file(
|
||||
"""
|
||||
f"""
|
||||
[alembic]
|
||||
script_location = %s
|
||||
script_location = {dir_}
|
||||
sourceless = false
|
||||
sqlalchemy.future = %s
|
||||
databases = %s
|
||||
sqlalchemy.future = {"true" if sqlalchemy_future else "false"}
|
||||
databases = {databases}
|
||||
|
||||
%s
|
||||
{engines}
|
||||
[loggers]
|
||||
keys = root
|
||||
|
||||
@@ -497,7 +533,7 @@ keys = root
|
||||
keys = console
|
||||
|
||||
[logger_root]
|
||||
level = WARN
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
@@ -514,5 +550,8 @@ keys = generic
|
||||
format = %%(levelname)-5.5s [%%(name)s] %%(message)s
|
||||
datefmt = %%H:%%M:%%S
|
||||
"""
|
||||
% (dir_, "true" if sqlalchemy_future else "false", databases, engines)
|
||||
)
|
||||
|
||||
|
||||
def _join_path(base: str, *more: str):
|
||||
return str(Path(base).joinpath(*more).as_posix())
|
||||
|
||||
@@ -3,11 +3,14 @@ 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 sqlalchemy import Column
|
||||
from sqlalchemy import create_mock_engine
|
||||
from sqlalchemy import inspect
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import String
|
||||
@@ -17,20 +20,19 @@ 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
|
||||
|
||||
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 create_mock_engine
|
||||
from ..util.sqla_compat import sqla_14
|
||||
from ..util.sqla_compat import sqla_2
|
||||
|
||||
|
||||
testing_config = configparser.ConfigParser()
|
||||
testing_config.read(["test.cfg"])
|
||||
|
||||
@@ -38,6 +40,31 @@ 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):
|
||||
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):
|
||||
@@ -49,6 +76,12 @@ class TestBase(SQLAlchemyTestBase):
|
||||
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):
|
||||
with config.db.connect() as conn:
|
||||
@@ -59,14 +92,6 @@ class TablesTest(TestBase, SQLAlchemyTablesTest):
|
||||
pass
|
||||
|
||||
|
||||
if sqla_14:
|
||||
from sqlalchemy.testing.fixtures import FutureEngineMixin
|
||||
else:
|
||||
|
||||
class FutureEngineMixin: # type:ignore[no-redef]
|
||||
__requires__ = ("sqlalchemy_14",)
|
||||
|
||||
|
||||
FutureEngineMixin.is_sqlalchemy_future = True
|
||||
|
||||
|
||||
@@ -184,12 +209,8 @@ def op_fixture(
|
||||
opts["as_sql"] = as_sql
|
||||
if literal_binds:
|
||||
opts["literal_binds"] = literal_binds
|
||||
if not sqla_14 and dialect == "mariadb":
|
||||
ctx_dialect = _get_dialect("mysql")
|
||||
ctx_dialect.server_version_info = (10, 4, 0, "MariaDB")
|
||||
|
||||
else:
|
||||
ctx_dialect = _get_dialect(dialect)
|
||||
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,
|
||||
@@ -268,9 +289,11 @@ class AlterColRoundTripFixture:
|
||||
"x",
|
||||
column.name,
|
||||
existing_type=column.type,
|
||||
existing_server_default=column.server_default
|
||||
if column.server_default is not None
|
||||
else False,
|
||||
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),
|
||||
@@ -298,9 +321,13 @@ class AlterColRoundTripFixture:
|
||||
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,
|
||||
(
|
||||
compare["server_default"].text
|
||||
if "server_default" in compare
|
||||
else (
|
||||
column.server_default.arg.text
|
||||
if column.server_default is not None
|
||||
else None
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from sqlalchemy.testing.requirements import Requirements
|
||||
|
||||
from alembic import util
|
||||
from alembic.util import sqla_compat
|
||||
from ..testing import exclusions
|
||||
|
||||
|
||||
@@ -74,13 +73,6 @@ class SuiteRequirements(Requirements):
|
||||
def reflects_fk_options(self):
|
||||
return exclusions.closed()
|
||||
|
||||
@property
|
||||
def sqlalchemy_14(self):
|
||||
return exclusions.skip_if(
|
||||
lambda config: not util.sqla_14,
|
||||
"SQLAlchemy 1.4 or greater required",
|
||||
)
|
||||
|
||||
@property
|
||||
def sqlalchemy_1x(self):
|
||||
return exclusions.skip_if(
|
||||
@@ -95,6 +87,18 @@ class SuiteRequirements(Requirements):
|
||||
"SQLAlchemy 2.x test",
|
||||
)
|
||||
|
||||
@property
|
||||
def asyncio(self):
|
||||
def go(config):
|
||||
try:
|
||||
import greenlet # noqa: F401
|
||||
except ImportError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
return exclusions.only_if(go)
|
||||
|
||||
@property
|
||||
def comments(self):
|
||||
return exclusions.only_if(
|
||||
@@ -109,26 +113,6 @@ class SuiteRequirements(Requirements):
|
||||
def computed_columns(self):
|
||||
return exclusions.closed()
|
||||
|
||||
@property
|
||||
def computed_columns_api(self):
|
||||
return exclusions.only_if(
|
||||
exclusions.BooleanPredicate(sqla_compat.has_computed)
|
||||
)
|
||||
|
||||
@property
|
||||
def computed_reflects_normally(self):
|
||||
return exclusions.only_if(
|
||||
exclusions.BooleanPredicate(sqla_compat.has_computed_reflection)
|
||||
)
|
||||
|
||||
@property
|
||||
def computed_reflects_as_server_default(self):
|
||||
return exclusions.closed()
|
||||
|
||||
@property
|
||||
def computed_doesnt_reflect_as_server_default(self):
|
||||
return exclusions.closed()
|
||||
|
||||
@property
|
||||
def autoincrement_on_composite_pk(self):
|
||||
return exclusions.closed()
|
||||
@@ -190,9 +174,3 @@ class SuiteRequirements(Requirements):
|
||||
@property
|
||||
def identity_columns_alter(self):
|
||||
return exclusions.closed()
|
||||
|
||||
@property
|
||||
def identity_columns_api(self):
|
||||
return exclusions.only_if(
|
||||
exclusions.BooleanPredicate(sqla_compat.has_identity)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from itertools import zip_longest
|
||||
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy.sql.elements import ClauseList
|
||||
|
||||
|
||||
class CompareTable:
|
||||
@@ -60,6 +61,14 @@ class CompareIndex:
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
expr = ClauseList(*self.index.expressions)
|
||||
try:
|
||||
expr_str = expr.compile().string
|
||||
except Exception:
|
||||
expr_str = str(expr)
|
||||
return f"<CompareIndex {self.index.name}({expr_str})>"
|
||||
|
||||
|
||||
class CompareCheckConstraint:
|
||||
def __init__(self, constraint):
|
||||
|
||||
@@ -14,6 +14,7 @@ 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
|
||||
@@ -149,6 +150,118 @@ class ModelOne:
|
||||
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,
|
||||
|
||||
@@ -6,9 +6,7 @@ from sqlalchemy import Table
|
||||
|
||||
from ._autogen_fixtures import AutogenFixtureTest
|
||||
from ... import testing
|
||||
from ...testing import config
|
||||
from ...testing import eq_
|
||||
from ...testing import exclusions
|
||||
from ...testing import is_
|
||||
from ...testing import is_true
|
||||
from ...testing import mock
|
||||
@@ -63,18 +61,8 @@ class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
|
||||
c = diffs[0][3]
|
||||
eq_(c.name, "foo")
|
||||
|
||||
if config.requirements.computed_reflects_normally.enabled:
|
||||
is_true(isinstance(c.computed, sa.Computed))
|
||||
else:
|
||||
is_(c.computed, None)
|
||||
|
||||
if config.requirements.computed_reflects_as_server_default.enabled:
|
||||
is_true(isinstance(c.server_default, sa.DefaultClause))
|
||||
eq_(str(c.server_default.arg.text), "5")
|
||||
elif config.requirements.computed_reflects_normally.enabled:
|
||||
is_true(isinstance(c.computed, sa.Computed))
|
||||
else:
|
||||
is_(c.computed, None)
|
||||
is_true(isinstance(c.computed, sa.Computed))
|
||||
is_true(isinstance(c.server_default, sa.Computed))
|
||||
|
||||
@testing.combinations(
|
||||
lambda: (None, sa.Computed("bar*5")),
|
||||
@@ -85,7 +73,6 @@ class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
|
||||
),
|
||||
lambda: (sa.Computed("bar*5"), sa.Computed("bar * 42")),
|
||||
)
|
||||
@config.requirements.computed_reflects_normally
|
||||
def test_cant_change_computed_warning(self, test_case):
|
||||
arg_before, arg_after = testing.resolve_lambda(test_case, **locals())
|
||||
m1 = MetaData()
|
||||
@@ -124,10 +111,7 @@ class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
|
||||
lambda: (None, None),
|
||||
lambda: (sa.Computed("5"), sa.Computed("5")),
|
||||
lambda: (sa.Computed("bar*5"), sa.Computed("bar*5")),
|
||||
(
|
||||
lambda: (sa.Computed("bar*5"), None),
|
||||
config.requirements.computed_doesnt_reflect_as_server_default,
|
||||
),
|
||||
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())
|
||||
@@ -158,46 +142,3 @@ class AutogenerateComputedTest(AutogenFixtureTest, TestBase):
|
||||
eq_(mock_warn.mock_calls, [])
|
||||
|
||||
eq_(list(diffs), [])
|
||||
|
||||
@config.requirements.computed_reflects_as_server_default
|
||||
def test_remove_computed_default_on_computed(self):
|
||||
"""Asserts the current behavior which is that on PG and Oracle,
|
||||
the GENERATED ALWAYS AS is reflected as a server default which we can't
|
||||
tell is actually "computed", so these come out as a modification to
|
||||
the server default.
|
||||
|
||||
"""
|
||||
m1 = MetaData()
|
||||
m2 = MetaData()
|
||||
|
||||
Table(
|
||||
"user",
|
||||
m1,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("bar", Integer),
|
||||
Column("foo", Integer, sa.Computed("bar + 42")),
|
||||
)
|
||||
|
||||
Table(
|
||||
"user",
|
||||
m2,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("bar", Integer),
|
||||
Column("foo", Integer),
|
||||
)
|
||||
|
||||
diffs = self._fixture(m1, m2)
|
||||
|
||||
eq_(diffs[0][0][0], "modify_default")
|
||||
eq_(diffs[0][0][2], "user")
|
||||
eq_(diffs[0][0][3], "foo")
|
||||
old = diffs[0][0][-2]
|
||||
new = diffs[0][0][-1]
|
||||
|
||||
is_(new, None)
|
||||
is_true(isinstance(old, sa.DefaultClause))
|
||||
|
||||
if exclusions.against(config, "postgresql"):
|
||||
eq_(str(old.arg.text), "(bar + 42)")
|
||||
elif exclusions.against(config, "oracle"):
|
||||
eq_(str(old.arg.text), '"BAR"+42')
|
||||
|
||||
@@ -24,9 +24,9 @@ class MigrationTransactionTest(TestBase):
|
||||
self.context = MigrationContext.configure(
|
||||
dialect=conn.dialect, opts=opts
|
||||
)
|
||||
self.context.output_buffer = (
|
||||
self.context.impl.output_buffer
|
||||
) = io.StringIO()
|
||||
self.context.output_buffer = self.context.impl.output_buffer = (
|
||||
io.StringIO()
|
||||
)
|
||||
else:
|
||||
self.context = MigrationContext.configure(
|
||||
connection=conn, opts=opts
|
||||
|
||||
@@ -10,8 +10,6 @@ import warnings
|
||||
|
||||
from sqlalchemy import exc as sa_exc
|
||||
|
||||
from ..util import sqla_14
|
||||
|
||||
|
||||
def setup_filters():
|
||||
"""Set global warning behavior for the test suite."""
|
||||
@@ -23,13 +21,6 @@ def setup_filters():
|
||||
|
||||
# some selected deprecations...
|
||||
warnings.filterwarnings("error", category=DeprecationWarning)
|
||||
if not sqla_14:
|
||||
# 1.3 uses pkg_resources in PluginLoader
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
"pkg_resources is deprecated as an API",
|
||||
DeprecationWarning,
|
||||
)
|
||||
try:
|
||||
import pytest
|
||||
except ImportError:
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
from .editor import open_in_editor
|
||||
from .exc import AutogenerateDiffsDetected
|
||||
from .exc import CommandError
|
||||
from .langhelpers import _with_legacy_names
|
||||
from .langhelpers import asbool
|
||||
from .langhelpers import dedupe_tuple
|
||||
from .langhelpers import Dispatcher
|
||||
from .langhelpers import EMPTY_DICT
|
||||
from .langhelpers import immutabledict
|
||||
from .langhelpers import memoized_property
|
||||
from .langhelpers import ModuleClsProxy
|
||||
from .langhelpers import not_none
|
||||
from .langhelpers import rev_id
|
||||
from .langhelpers import to_list
|
||||
from .langhelpers import to_tuple
|
||||
from .langhelpers import unique_list
|
||||
from .messaging import err
|
||||
from .messaging import format_as_comma
|
||||
from .messaging import msg
|
||||
from .messaging import obfuscate_url_pw
|
||||
from .messaging import status
|
||||
from .messaging import warn
|
||||
from .messaging import write_outstream
|
||||
from .pyfiles import coerce_resource_to_filename
|
||||
from .pyfiles import load_python_file
|
||||
from .pyfiles import pyc_file_from_path
|
||||
from .pyfiles import template_to_file
|
||||
from .sqla_compat import has_computed
|
||||
from .sqla_compat import sqla_13
|
||||
from .sqla_compat import sqla_14
|
||||
from .sqla_compat import sqla_2
|
||||
|
||||
|
||||
if not sqla_13:
|
||||
raise CommandError("SQLAlchemy 1.3.0 or greater is required.")
|
||||
from .editor import open_in_editor as open_in_editor
|
||||
from .exc import AutogenerateDiffsDetected as AutogenerateDiffsDetected
|
||||
from .exc import CommandError as CommandError
|
||||
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 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 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
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
# mypy: no-warn-unused-ignores
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from configparser import ConfigParser
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import typing
|
||||
from typing import Any
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy.util import inspect_getfullargspec # noqa
|
||||
from sqlalchemy.util.compat import inspect_formatargspec # noqa
|
||||
if True:
|
||||
# zimports hack for too-long names
|
||||
from sqlalchemy.util import ( # noqa: F401
|
||||
inspect_getfullargspec as inspect_getfullargspec,
|
||||
)
|
||||
from sqlalchemy.util.compat import ( # noqa: F401
|
||||
inspect_formatargspec as inspect_formatargspec,
|
||||
)
|
||||
|
||||
is_posix = os.name == "posix"
|
||||
|
||||
py314 = sys.version_info >= (3, 14)
|
||||
py313 = sys.version_info >= (3, 13)
|
||||
py312 = sys.version_info >= (3, 12)
|
||||
py311 = sys.version_info >= (3, 11)
|
||||
py310 = sys.version_info >= (3, 10)
|
||||
py39 = sys.version_info >= (3, 9)
|
||||
py38 = sys.version_info >= (3, 8)
|
||||
|
||||
|
||||
# produce a wrapper that allows encoded text to stream
|
||||
@@ -28,24 +43,82 @@ class EncodedIO(io.TextIOWrapper):
|
||||
|
||||
|
||||
if py39:
|
||||
from importlib import resources as importlib_resources
|
||||
from importlib import metadata as importlib_metadata
|
||||
from importlib.metadata import EntryPoint
|
||||
from importlib import resources as _resources
|
||||
|
||||
importlib_resources = _resources
|
||||
from importlib import metadata as _metadata
|
||||
|
||||
importlib_metadata = _metadata
|
||||
from importlib.metadata import EntryPoint as EntryPoint
|
||||
else:
|
||||
import importlib_resources # type:ignore # noqa
|
||||
import importlib_metadata # type:ignore # noqa
|
||||
from importlib_metadata import EntryPoint # type:ignore # noqa
|
||||
|
||||
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]:
|
||||
ep = importlib_metadata.entry_points()
|
||||
if hasattr(ep, "select"):
|
||||
return ep.select(group=group) # type: ignore
|
||||
return ep.select(group=group)
|
||||
else:
|
||||
return ep.get(group, ()) # type: ignore
|
||||
|
||||
|
||||
def formatannotation_fwdref(annotation, base_module=None):
|
||||
def formatannotation_fwdref(
|
||||
annotation: Any, base_module: Optional[Any] = None
|
||||
) -> str:
|
||||
"""vendored from python 3.7"""
|
||||
# copied over _formatannotation from sqlalchemy 2.0
|
||||
|
||||
@@ -66,7 +139,7 @@ def formatannotation_fwdref(annotation, base_module=None):
|
||||
def read_config_parser(
|
||||
file_config: ConfigParser,
|
||||
file_argument: Sequence[Union[str, os.PathLike[str]]],
|
||||
) -> list[str]:
|
||||
) -> List[str]:
|
||||
if py310:
|
||||
return file_config.read(file_argument, encoding="locale")
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
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):
|
||||
pass
|
||||
|
||||
|
||||
class AutogenerateDiffsDetected(CommandError):
|
||||
pass
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
revision_context: RevisionContext,
|
||||
diffs: List[Tuple[Any, ...]],
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
self.revision_context = revision_context
|
||||
self.diffs = diffs
|
||||
|
||||
@@ -5,33 +5,46 @@ from collections.abc import Iterable
|
||||
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 TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
from sqlalchemy.util import asbool # noqa
|
||||
from sqlalchemy.util import immutabledict # noqa
|
||||
from sqlalchemy.util import memoized_property # noqa
|
||||
from sqlalchemy.util import to_list # noqa
|
||||
from sqlalchemy.util import unique_list # noqa
|
||||
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")
|
||||
_T = TypeVar("_T", bound=Any)
|
||||
|
||||
_C = TypeVar("_C", bound=Callable[..., Any])
|
||||
|
||||
|
||||
class _ModuleClsMeta(type):
|
||||
def __setattr__(cls, key: str, value: Callable) -> None:
|
||||
def __setattr__(cls, key: str, value: Callable[..., Any]) -> None:
|
||||
super().__setattr__(key, value)
|
||||
cls._update_module_proxies(key) # type: ignore
|
||||
|
||||
@@ -45,9 +58,13 @@ class ModuleClsProxy(metaclass=_ModuleClsMeta):
|
||||
|
||||
"""
|
||||
|
||||
_setups: Dict[type, Tuple[set, list]] = collections.defaultdict(
|
||||
lambda: (set(), [])
|
||||
)
|
||||
_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:
|
||||
@@ -70,18 +87,33 @@ class ModuleClsProxy(metaclass=_ModuleClsMeta):
|
||||
del globals_[attr_name]
|
||||
|
||||
@classmethod
|
||||
def create_module_class_proxy(cls, globals_, locals_):
|
||||
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_, locals_, attr_names):
|
||||
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, globals_, locals_, attr_names):
|
||||
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):
|
||||
@@ -92,10 +124,15 @@ class ModuleClsProxy(metaclass=_ModuleClsMeta):
|
||||
attr_names.add(methname)
|
||||
|
||||
@classmethod
|
||||
def _create_method_proxy(cls, name, globals_, locals_):
|
||||
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, from_):
|
||||
def _name_error(name: str, from_: Exception) -> NoReturn:
|
||||
raise NameError(
|
||||
"Can't invoke function '%s', as the proxy object has "
|
||||
"not yet been "
|
||||
@@ -119,7 +156,9 @@ class ModuleClsProxy(metaclass=_ModuleClsMeta):
|
||||
translations,
|
||||
)
|
||||
|
||||
def translate(fn_name, spec, translations, args, kw):
|
||||
def translate(
|
||||
fn_name: str, spec: Any, translations: Any, args: Any, kw: Any
|
||||
) -> Any:
|
||||
return_kw = {}
|
||||
return_args = []
|
||||
|
||||
@@ -176,15 +215,15 @@ class ModuleClsProxy(metaclass=_ModuleClsMeta):
|
||||
"doc": fn.__doc__,
|
||||
}
|
||||
)
|
||||
lcl = {}
|
||||
lcl: MutableMapping[str, Any] = {}
|
||||
|
||||
exec(func_text, globals_, lcl)
|
||||
return lcl[name]
|
||||
exec(func_text, cast("Dict[str, Any]", globals_), lcl)
|
||||
return cast("Callable[..., Any]", lcl[name])
|
||||
|
||||
|
||||
def _with_legacy_names(translations):
|
||||
def decorate(fn):
|
||||
fn._legacy_translations = translations
|
||||
def _with_legacy_names(translations: Any) -> Any:
|
||||
def decorate(fn: _C) -> _C:
|
||||
fn._legacy_translations = translations # type: ignore[attr-defined]
|
||||
return fn
|
||||
|
||||
return decorate
|
||||
@@ -195,21 +234,22 @@ def rev_id() -> str:
|
||||
|
||||
|
||||
@overload
|
||||
def to_tuple(x: Any, default: tuple) -> tuple:
|
||||
...
|
||||
def to_tuple(x: Any, default: Tuple[Any, ...]) -> Tuple[Any, ...]: ...
|
||||
|
||||
|
||||
@overload
|
||||
def to_tuple(x: None, default: Optional[_T] = None) -> _T:
|
||||
...
|
||||
def to_tuple(x: None, default: Optional[_T] = ...) -> _T: ...
|
||||
|
||||
|
||||
@overload
|
||||
def to_tuple(x: Any, default: Optional[tuple] = None) -> tuple:
|
||||
...
|
||||
def to_tuple(
|
||||
x: Any, default: Optional[Tuple[Any, ...]] = None
|
||||
) -> Tuple[Any, ...]: ...
|
||||
|
||||
|
||||
def to_tuple(x, default=None):
|
||||
def to_tuple(
|
||||
x: Any, default: Optional[Tuple[Any, ...]] = None
|
||||
) -> Optional[Tuple[Any, ...]]:
|
||||
if x is None:
|
||||
return default
|
||||
elif isinstance(x, str):
|
||||
@@ -226,13 +266,13 @@ def dedupe_tuple(tup: Tuple[str, ...]) -> Tuple[str, ...]:
|
||||
|
||||
class Dispatcher:
|
||||
def __init__(self, uselist: bool = False) -> None:
|
||||
self._registry: Dict[tuple, Any] = {}
|
||||
self._registry: Dict[Tuple[Any, ...], Any] = {}
|
||||
self.uselist = uselist
|
||||
|
||||
def dispatch_for(
|
||||
self, target: Any, qualifier: str = "default"
|
||||
) -> Callable:
|
||||
def decorate(fn):
|
||||
) -> Callable[[_C], _C]:
|
||||
def decorate(fn: _C) -> _C:
|
||||
if self.uselist:
|
||||
self._registry.setdefault((target, qualifier), []).append(fn)
|
||||
else:
|
||||
@@ -244,7 +284,7 @@ class Dispatcher:
|
||||
|
||||
def dispatch(self, obj: Any, qualifier: str = "default") -> Any:
|
||||
if isinstance(obj, str):
|
||||
targets: Sequence = [obj]
|
||||
targets: Sequence[Any] = [obj]
|
||||
elif isinstance(obj, type):
|
||||
targets = obj.__mro__
|
||||
else:
|
||||
@@ -259,11 +299,13 @@ class Dispatcher:
|
||||
raise ValueError("no dispatch function for object: %s" % obj)
|
||||
|
||||
def _fn_or_list(
|
||||
self, fn_or_list: Union[List[Callable], Callable]
|
||||
) -> Callable:
|
||||
self, fn_or_list: Union[List[Callable[..., Any]], Callable[..., Any]]
|
||||
) -> Callable[..., Any]:
|
||||
if self.uselist:
|
||||
|
||||
def go(*arg, **kw):
|
||||
def go(*arg: Any, **kw: Any) -> None:
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(fn_or_list, Sequence)
|
||||
for fn in fn_or_list:
|
||||
fn(*arg, **kw)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ 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
|
||||
@@ -12,8 +13,6 @@ import warnings
|
||||
|
||||
from sqlalchemy.engine import url
|
||||
|
||||
from . import sqla_compat
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# disable "no handler found" errors
|
||||
@@ -53,7 +52,9 @@ def write_outstream(
|
||||
|
||||
|
||||
@contextmanager
|
||||
def status(status_msg: str, newline: bool = False, quiet: bool = False):
|
||||
def status(
|
||||
status_msg: str, newline: bool = False, quiet: bool = False
|
||||
) -> Iterator[None]:
|
||||
msg(status_msg + " ...", newline, flush=True, quiet=quiet)
|
||||
try:
|
||||
yield
|
||||
@@ -66,21 +67,24 @@ def status(status_msg: str, newline: bool = False, quiet: bool = False):
|
||||
write_outstream(sys.stdout, " done\n")
|
||||
|
||||
|
||||
def err(message: str, quiet: bool = False):
|
||||
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:
|
||||
u = url.make_url(input_url)
|
||||
return sqla_compat.url_render_as_string(u, hide_password=True)
|
||||
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:
|
||||
@@ -92,11 +96,17 @@ def msg(
|
||||
write_outstream(sys.stdout, "\n")
|
||||
else:
|
||||
# left indent output lines
|
||||
lines = textwrap.wrap(msg, TERMWIDTH)
|
||||
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 ""))
|
||||
write_outstream(sys.stdout, line, "\n")
|
||||
write_outstream(sys.stdout, lines[-1], ("\n" if newline else ""))
|
||||
if flush:
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
@@ -6,9 +6,13 @@ import importlib
|
||||
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
|
||||
@@ -18,9 +22,14 @@ from .exc import CommandError
|
||||
|
||||
|
||||
def template_to_file(
|
||||
template_file: str, dest: str, output_encoding: str, **kw
|
||||
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=template_file)
|
||||
template = Template(filename=_preserving_path_as_str(template_file))
|
||||
try:
|
||||
output = template.render_unicode(**kw).encode(output_encoding)
|
||||
except:
|
||||
@@ -36,11 +45,13 @@ def template_to_file(
|
||||
"template-oriented traceback." % fname
|
||||
)
|
||||
else:
|
||||
with open(dest, "wb") as f:
|
||||
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: str) -> str:
|
||||
def coerce_resource_to_filename(fname_or_resource: str) -> pathlib.Path:
|
||||
"""Interpret a filename as either a filesystem location or as a package
|
||||
resource.
|
||||
|
||||
@@ -48,8 +59,9 @@ def coerce_resource_to_filename(fname: str) -> str:
|
||||
are interpreted as resources and coerced to a file location.
|
||||
|
||||
"""
|
||||
if not os.path.isabs(fname) and ":" in fname:
|
||||
tokens = fname.split(":")
|
||||
# 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
|
||||
|
||||
@@ -59,37 +71,48 @@ def coerce_resource_to_filename(fname: str) -> str:
|
||||
ref = compat.importlib_resources.files(tokens[0])
|
||||
for tok in tokens[1:]:
|
||||
ref = ref / tok
|
||||
fname = file_manager.enter_context( # type: ignore[assignment]
|
||||
fname_or_resource = file_manager.enter_context( # type: ignore[assignment] # noqa: E501
|
||||
compat.importlib_resources.as_file(ref)
|
||||
)
|
||||
return fname
|
||||
return pathlib.Path(fname_or_resource)
|
||||
|
||||
|
||||
def pyc_file_from_path(path: str) -> Optional[str]:
|
||||
def pyc_file_from_path(
|
||||
path: Union[str, os.PathLike[str]],
|
||||
) -> Optional[pathlib.Path]:
|
||||
"""Given a python source path, locate the .pyc."""
|
||||
|
||||
candidate = importlib.util.cache_from_source(path)
|
||||
if os.path.exists(candidate):
|
||||
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
|
||||
filepath, ext = os.path.splitext(path)
|
||||
ext = pathpath.suffix
|
||||
for ext in importlib.machinery.BYTECODE_SUFFIXES:
|
||||
if os.path.exists(filepath + ext):
|
||||
return filepath + ext
|
||||
if pathpath.with_suffix(ext).exists():
|
||||
return pathpath.with_suffix(ext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def load_python_file(dir_: str, filename: str):
|
||||
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 = os.path.join(dir_, filename)
|
||||
_, ext = os.path.splitext(filename)
|
||||
path = dir_ / filename
|
||||
ext = path.suffix
|
||||
if ext == ".py":
|
||||
if os.path.exists(path):
|
||||
if path.exists():
|
||||
module = load_module_py(module_id, path)
|
||||
else:
|
||||
pyc_path = pyc_file_from_path(path)
|
||||
@@ -99,12 +122,32 @@ def load_python_file(dir_: str, filename: str):
|
||||
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: str):
|
||||
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))
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
# 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 Mapping
|
||||
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 inspect
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import sql
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.engine import url
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.schema import CheckConstraint
|
||||
from sqlalchemy.schema import Column
|
||||
from sqlalchemy.schema import ForeignKeyConstraint
|
||||
@@ -26,31 +29,33 @@ 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 quoted_name
|
||||
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.engine.reflection import Inspector
|
||||
from sqlalchemy.sql.base import ColumnCollection
|
||||
from sqlalchemy.sql.compiler import SQLCompiler
|
||||
from sqlalchemy.sql.dml import Insert
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.schema import SchemaItem
|
||||
from sqlalchemy.sql.selectable import Select
|
||||
from sqlalchemy.sql.selectable import TableClause
|
||||
|
||||
_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)
|
||||
@@ -61,90 +66,65 @@ def _safe_int(value: str) -> Union[int, str]:
|
||||
_vers = tuple(
|
||||
[_safe_int(x) for x in re.findall(r"(\d+|[abc]\d)", __version__)]
|
||||
)
|
||||
sqla_13 = _vers >= (1, 3)
|
||||
sqla_14 = _vers >= (1, 4)
|
||||
# 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,)
|
||||
sqlalchemy_version = __version__
|
||||
|
||||
try:
|
||||
from sqlalchemy.sql.naming import _NONE_NAME as _NONE_NAME
|
||||
except ImportError:
|
||||
from sqlalchemy.sql.elements import _NONE_NAME as _NONE_NAME # type: ignore # noqa: E501
|
||||
if TYPE_CHECKING:
|
||||
|
||||
def compiles(
|
||||
element: Type[ClauseElement], *dialects: str
|
||||
) -> Callable[[_CompilerProtocol], _CompilerProtocol]: ...
|
||||
|
||||
class _Unsupported:
|
||||
"Placeholder for unsupported SQLAlchemy classes"
|
||||
|
||||
|
||||
try:
|
||||
from sqlalchemy import Computed
|
||||
except ImportError:
|
||||
if not TYPE_CHECKING:
|
||||
|
||||
class Computed(_Unsupported):
|
||||
pass
|
||||
|
||||
has_computed = False
|
||||
has_computed_reflection = False
|
||||
else:
|
||||
has_computed = True
|
||||
has_computed_reflection = _vers >= (1, 3, 16)
|
||||
from sqlalchemy.ext.compiler import compiles # noqa: I100,I202
|
||||
|
||||
try:
|
||||
from sqlalchemy import Identity
|
||||
except ImportError:
|
||||
if not TYPE_CHECKING:
|
||||
|
||||
class Identity(_Unsupported):
|
||||
pass
|
||||
identity_has_dialect_kwargs = issubclass(schema.Identity, DialectKWArgs)
|
||||
|
||||
has_identity = False
|
||||
else:
|
||||
identity_has_dialect_kwargs = issubclass(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:
|
||||
as_dict = identity._as_dict() # type: ignore
|
||||
if dialect_kwargs:
|
||||
assert isinstance(identity, DialectKWArgs)
|
||||
as_dict.update(identity.dialect_kwargs)
|
||||
else:
|
||||
as_dict = {}
|
||||
if isinstance(identity, 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
|
||||
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
|
||||
|
||||
has_identity = True
|
||||
|
||||
if sqla_2:
|
||||
from sqlalchemy.sql.base import _NoneName
|
||||
@@ -153,7 +133,6 @@ else:
|
||||
|
||||
|
||||
_ConstraintName = Union[None, str, _NoneName]
|
||||
|
||||
_ConstraintNameDefined = Union[str, _NoneName]
|
||||
|
||||
|
||||
@@ -163,15 +142,11 @@ def constraint_name_defined(
|
||||
return name is _NONE_NAME or isinstance(name, (str, _NoneName))
|
||||
|
||||
|
||||
def constraint_name_string(
|
||||
name: _ConstraintName,
|
||||
) -> TypeGuard[str]:
|
||||
def constraint_name_string(name: _ConstraintName) -> TypeGuard[str]:
|
||||
return isinstance(name, str)
|
||||
|
||||
|
||||
def constraint_name_or_none(
|
||||
name: _ConstraintName,
|
||||
) -> Optional[str]:
|
||||
def constraint_name_or_none(name: _ConstraintName) -> Optional[str]:
|
||||
return name if constraint_name_string(name) else None
|
||||
|
||||
|
||||
@@ -201,17 +176,10 @@ def _ensure_scope_for_ddl(
|
||||
yield
|
||||
|
||||
|
||||
def url_render_as_string(url, hide_password=True):
|
||||
if sqla_14:
|
||||
return url.render_as_string(hide_password=hide_password)
|
||||
else:
|
||||
return url.__to_string__(hide_password=hide_password)
|
||||
|
||||
|
||||
def _safe_begin_connection_transaction(
|
||||
connection: Connection,
|
||||
) -> Transaction:
|
||||
transaction = _get_connection_transaction(connection)
|
||||
transaction = connection.get_transaction()
|
||||
if transaction:
|
||||
return transaction
|
||||
else:
|
||||
@@ -221,7 +189,7 @@ def _safe_begin_connection_transaction(
|
||||
def _safe_commit_connection_transaction(
|
||||
connection: Connection,
|
||||
) -> None:
|
||||
transaction = _get_connection_transaction(connection)
|
||||
transaction = connection.get_transaction()
|
||||
if transaction:
|
||||
transaction.commit()
|
||||
|
||||
@@ -229,7 +197,7 @@ def _safe_commit_connection_transaction(
|
||||
def _safe_rollback_connection_transaction(
|
||||
connection: Connection,
|
||||
) -> None:
|
||||
transaction = _get_connection_transaction(connection)
|
||||
transaction = connection.get_transaction()
|
||||
if transaction:
|
||||
transaction.rollback()
|
||||
|
||||
@@ -250,70 +218,34 @@ def _idx_table_bound_expressions(idx: Index) -> Iterable[ColumnElement[Any]]:
|
||||
|
||||
def _copy(schema_item: _CE, **kw) -> _CE:
|
||||
if hasattr(schema_item, "_copy"):
|
||||
return schema_item._copy(**kw) # type: ignore[union-attr]
|
||||
return schema_item._copy(**kw)
|
||||
else:
|
||||
return schema_item.copy(**kw) # type: ignore[union-attr]
|
||||
|
||||
|
||||
def _get_connection_transaction(
|
||||
connection: Connection,
|
||||
) -> Optional[Transaction]:
|
||||
if sqla_14:
|
||||
return connection.get_transaction()
|
||||
else:
|
||||
r = connection._root # type: ignore[attr-defined]
|
||||
return r._Connection__transaction
|
||||
|
||||
|
||||
def _create_url(*arg, **kw) -> url.URL:
|
||||
if hasattr(url.URL, "create"):
|
||||
return url.URL.create(*arg, **kw)
|
||||
else:
|
||||
return url.URL(*arg, **kw)
|
||||
|
||||
|
||||
def _connectable_has_table(
|
||||
connectable: Connection, tablename: str, schemaname: Union[str, None]
|
||||
) -> bool:
|
||||
if sqla_14:
|
||||
return inspect(connectable).has_table(tablename, schemaname)
|
||||
else:
|
||||
return connectable.dialect.has_table(
|
||||
connectable, tablename, schemaname
|
||||
)
|
||||
return connectable.dialect.has_table(connectable, tablename, schemaname)
|
||||
|
||||
|
||||
def _exec_on_inspector(inspector, statement, **params):
|
||||
if sqla_14:
|
||||
with inspector._operation_context() as conn:
|
||||
return conn.execute(statement, params)
|
||||
else:
|
||||
return inspector.bind.execute(statement, params)
|
||||
with inspector._operation_context() as conn:
|
||||
return conn.execute(statement, params)
|
||||
|
||||
|
||||
def _nullability_might_be_unset(metadata_column):
|
||||
if not sqla_14:
|
||||
return metadata_column.nullable
|
||||
else:
|
||||
from sqlalchemy.sql import schema
|
||||
from sqlalchemy.sql import schema
|
||||
|
||||
return (
|
||||
metadata_column._user_defined_nullable is schema.NULL_UNSPECIFIED
|
||||
)
|
||||
return metadata_column._user_defined_nullable is schema.NULL_UNSPECIFIED
|
||||
|
||||
|
||||
def _server_default_is_computed(*server_default) -> bool:
|
||||
if not has_computed:
|
||||
return False
|
||||
else:
|
||||
return any(isinstance(sd, Computed) for sd in server_default)
|
||||
return any(isinstance(sd, schema.Computed) for sd in server_default)
|
||||
|
||||
|
||||
def _server_default_is_identity(*server_default) -> bool:
|
||||
if not sqla_14:
|
||||
return False
|
||||
else:
|
||||
return any(isinstance(sd, Identity) for sd in server_default)
|
||||
return any(isinstance(sd, schema.Identity) for sd in server_default)
|
||||
|
||||
|
||||
def _table_for_constraint(constraint: Constraint) -> Table:
|
||||
@@ -334,15 +266,6 @@ def _columns_for_constraint(constraint):
|
||||
return list(constraint.columns)
|
||||
|
||||
|
||||
def _reflect_table(inspector: Inspector, table: Table) -> None:
|
||||
if sqla_14:
|
||||
return inspector.reflect_table(table, None)
|
||||
else:
|
||||
return inspector.reflecttable( # type: ignore[attr-defined]
|
||||
table, None
|
||||
)
|
||||
|
||||
|
||||
def _resolve_for_variant(type_, dialect):
|
||||
if _type_has_variants(type_):
|
||||
base_type, mapping = _get_variant_mapping(type_)
|
||||
@@ -351,7 +274,7 @@ def _resolve_for_variant(type_, dialect):
|
||||
return type_
|
||||
|
||||
|
||||
if hasattr(sqltypes.TypeEngine, "_variant_mapping"):
|
||||
if hasattr(sqltypes.TypeEngine, "_variant_mapping"): # 2.0
|
||||
|
||||
def _type_has_variants(type_):
|
||||
return bool(type_._variant_mapping)
|
||||
@@ -368,7 +291,12 @@ else:
|
||||
return type_.impl, type_.mapping
|
||||
|
||||
|
||||
def _fk_spec(constraint):
|
||||
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
|
||||
]
|
||||
@@ -397,7 +325,7 @@ def _fk_spec(constraint):
|
||||
|
||||
|
||||
def _fk_is_self_referential(constraint: ForeignKeyConstraint) -> bool:
|
||||
spec = constraint.elements[0]._get_colspec() # type: ignore[attr-defined]
|
||||
spec = constraint.elements[0]._get_colspec()
|
||||
tokens = spec.split(".")
|
||||
tokens.pop(-1) # colname
|
||||
tablekey = ".".join(tokens)
|
||||
@@ -409,13 +337,13 @@ 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 # type: ignore[attr-defined]
|
||||
return constraint._type_bound
|
||||
|
||||
|
||||
def _find_columns(clause):
|
||||
"""locate Column objects within the given expression."""
|
||||
|
||||
cols = set()
|
||||
cols: Set[ColumnElement[Any]] = set()
|
||||
traverse(clause, {}, {"column": cols.add})
|
||||
return cols
|
||||
|
||||
@@ -502,7 +430,7 @@ class _textual_index_element(sql.ColumnElement):
|
||||
self.fake_column = schema.Column(self.text.text, sqltypes.NULLTYPE)
|
||||
table.append_column(self.fake_column)
|
||||
|
||||
def get_children(self):
|
||||
def get_children(self, **kw):
|
||||
return [self.fake_column]
|
||||
|
||||
|
||||
@@ -524,116 +452,44 @@ def _render_literal_bindparam(
|
||||
return compiler.render_literal_bindparam(element, **kw)
|
||||
|
||||
|
||||
def _get_index_expressions(idx):
|
||||
return list(idx.expressions)
|
||||
|
||||
|
||||
def _get_index_column_names(idx):
|
||||
return [getattr(exp, "name", None) for exp in _get_index_expressions(idx)]
|
||||
|
||||
|
||||
def _column_kwargs(col: Column) -> Mapping:
|
||||
if sqla_13:
|
||||
return col.kwargs
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
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
|
||||
if sqla_14:
|
||||
# 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
|
||||
)
|
||||
else:
|
||||
# prior to SQLAlchemy 1.4, work around quoting logic to get at the
|
||||
# final compiled name without quotes.
|
||||
if hasattr(constraint.name, "quote"):
|
||||
# might be quoted_name, might be truncated_name, keep it the
|
||||
# same
|
||||
quoted_name_cls: type = type(constraint.name)
|
||||
else:
|
||||
quoted_name_cls = quoted_name
|
||||
|
||||
new_name = quoted_name_cls(str(constraint.name), quote=False)
|
||||
constraint = constraint.__class__(name=new_name)
|
||||
|
||||
if isinstance(constraint, schema.Index):
|
||||
# name should not be quoted.
|
||||
d = dialect.ddl_compiler(dialect, None) # type: ignore[arg-type]
|
||||
return d._prepared_index_name( # type: ignore[attr-defined]
|
||||
constraint
|
||||
)
|
||||
else:
|
||||
# name should not be quoted.
|
||||
return dialect.identifier_preparer.format_constraint(constraint)
|
||||
# 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 sqla_14:
|
||||
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
|
||||
else:
|
||||
return constraint.name is not None
|
||||
|
||||
|
||||
def _is_mariadb(mysql_dialect: Dialect) -> bool:
|
||||
if sqla_14:
|
||||
return mysql_dialect.is_mariadb # type: ignore[attr-defined]
|
||||
else:
|
||||
return bool(
|
||||
mysql_dialect.server_version_info
|
||||
and mysql_dialect._is_mariadb # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
|
||||
def _mariadb_normalized_version_info(mysql_dialect):
|
||||
return mysql_dialect._mariadb_normalized_version_info
|
||||
|
||||
|
||||
def _insert_inline(table: Union[TableClause, Table]) -> Insert:
|
||||
if sqla_14:
|
||||
return table.insert().inline()
|
||||
else:
|
||||
return table.insert(inline=True) # type: ignore[call-arg]
|
||||
|
||||
|
||||
if sqla_14:
|
||||
from sqlalchemy import create_mock_engine
|
||||
from sqlalchemy import select as _select
|
||||
else:
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
def create_mock_engine(url, executor, **kw): # type: ignore[misc]
|
||||
return create_engine(
|
||||
"postgresql://", strategy="mock", executor=executor
|
||||
)
|
||||
|
||||
def _select(*columns, **kw) -> Select: # type: ignore[no-redef]
|
||||
return sql.select(list(columns), **kw) # type: ignore[call-overload]
|
||||
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:
|
||||
expr: Any
|
||||
for expr in index.expressions:
|
||||
while isinstance(expr, UnaryExpression):
|
||||
expr = expr.element
|
||||
if not isinstance(expr, ColumnClause) or expr.is_literal:
|
||||
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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: asyncpg
|
||||
Version: 0.29.0
|
||||
Version: 0.30.0
|
||||
Summary: An asyncio PostgreSQL driver
|
||||
Author-email: MagicStack Inc <hello@magic.io>
|
||||
License: Apache License, Version 2.0
|
||||
@@ -25,14 +25,22 @@ Requires-Python: >=3.8.0
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
License-File: AUTHORS
|
||||
Requires-Dist: async-timeout >=4.0.3 ; python_version < "3.12.0"
|
||||
Requires-Dist: async-timeout>=4.0.3; python_version < "3.11.0"
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: Sphinx ~=5.3.0 ; extra == 'docs'
|
||||
Requires-Dist: sphinxcontrib-asyncio ~=0.3.0 ; extra == 'docs'
|
||||
Requires-Dist: sphinx-rtd-theme >=1.2.2 ; extra == 'docs'
|
||||
Requires-Dist: Sphinx~=8.1.3; extra == "docs"
|
||||
Requires-Dist: sphinx-rtd-theme>=1.2.2; extra == "docs"
|
||||
Provides-Extra: gssauth
|
||||
Requires-Dist: gssapi; platform_system != "Windows" and extra == "gssauth"
|
||||
Requires-Dist: sspilib; platform_system == "Windows" and extra == "gssauth"
|
||||
Provides-Extra: test
|
||||
Requires-Dist: flake8 ~=6.1 ; extra == 'test'
|
||||
Requires-Dist: uvloop >=0.15.3 ; (platform_system != "Windows" and python_version < "3.12.0") and extra == 'test'
|
||||
Requires-Dist: flake8~=6.1; extra == "test"
|
||||
Requires-Dist: flake8-pyi~=24.1.0; extra == "test"
|
||||
Requires-Dist: distro~=1.9.0; extra == "test"
|
||||
Requires-Dist: mypy~=1.8.0; extra == "test"
|
||||
Requires-Dist: uvloop>=0.15.3; (platform_system != "Windows" and python_version < "3.14.0") and extra == "test"
|
||||
Requires-Dist: gssapi; platform_system == "Linux" and extra == "test"
|
||||
Requires-Dist: k5test; platform_system == "Linux" and extra == "test"
|
||||
Requires-Dist: sspilib; platform_system == "Windows" and extra == "test"
|
||||
|
||||
asyncpg -- A fast PostgreSQL Database Client Library for Python/asyncio
|
||||
=======================================================================
|
||||
@@ -50,8 +58,9 @@ framework. You can read more about asyncpg in an introductory
|
||||
`blog post <http://magic.io/blog/asyncpg-1m-rows-from-postgres-to-python/>`_.
|
||||
|
||||
asyncpg requires Python 3.8 or later and is supported for PostgreSQL
|
||||
versions 9.5 to 16. Older PostgreSQL versions or other databases implementing
|
||||
the PostgreSQL protocol *may* work, but are not being actively tested.
|
||||
versions 9.5 to 17. Other PostgreSQL versions or other databases
|
||||
implementing the PostgreSQL protocol *may* work, but are not being
|
||||
actively tested.
|
||||
|
||||
|
||||
Documentation
|
||||
@@ -94,11 +103,18 @@ This enables asyncpg to have easy-to-use support for:
|
||||
Installation
|
||||
------------
|
||||
|
||||
asyncpg is available on PyPI and has no dependencies.
|
||||
Use pip to install::
|
||||
asyncpg is available on PyPI. When not using GSSAPI/SSPI authentication it
|
||||
has no dependencies. Use pip to install::
|
||||
|
||||
$ pip install asyncpg
|
||||
|
||||
If you need GSSAPI/SSPI authentication, use::
|
||||
|
||||
$ pip install 'asyncpg[gssauth]'
|
||||
|
||||
For more details, please `see the documentation
|
||||
<https://magicstack.github.io/asyncpg/current/installation.html>`_.
|
||||
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
@@ -117,8 +133,7 @@ Basic Usage
|
||||
)
|
||||
await conn.close()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(run())
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
License
|
||||
@@ -1,111 +1,113 @@
|
||||
asyncpg-0.29.0.dist-info/AUTHORS,sha256=gIYYcUuWiSZS93lstwQtCT56St1NtKg-fikn8ourw64,130
|
||||
asyncpg-0.29.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
asyncpg-0.29.0.dist-info/LICENSE,sha256=2SItc_2sUJkhdAdu-gT0T2-82dVhVafHCS6YdXBCpvY,11466
|
||||
asyncpg-0.29.0.dist-info/METADATA,sha256=_xxlp3Q6M3HJGWcW4cnzhtcswIBd0n7IztyBiZe4Pj0,4356
|
||||
asyncpg-0.29.0.dist-info/RECORD,,
|
||||
asyncpg-0.29.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
asyncpg-0.29.0.dist-info/WHEEL,sha256=JmQLNqDEfvnYMfsIaVeSP3fmUcYDwmF12m3QYW0c7QQ,152
|
||||
asyncpg-0.29.0.dist-info/top_level.txt,sha256=DdhVhpzCq49mykkHNag6i9zuJx05_tx4CMZymM1F8dU,8
|
||||
asyncpg/__init__.py,sha256=jOW3EoH2dDw1bsrd4qipodmPJsEN6D5genWdyqhB7e8,563
|
||||
asyncpg/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/_asyncio_compat.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/_version.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/cluster.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/compat.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connect_utils.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connection.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connresource.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/cursor.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/introspection.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/pool.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/prepared_stmt.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/serverversion.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/transaction.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/types.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/utils.cpython-312.pyc,,
|
||||
asyncpg/_asyncio_compat.py,sha256=VgUVf12ztecdiiAMjpS53R_XizOQMKXJBRK-9iCG6cI,2299
|
||||
asyncpg/_testbase/__init__.py,sha256=Sj6bhG3a8k5hqp1eFv7I6IkfulcvCXbd1y4tvfz5WQk,16066
|
||||
asyncpg/_testbase/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/_testbase/__pycache__/fuzzer.cpython-312.pyc,,
|
||||
asyncpg/_testbase/fuzzer.py,sha256=3Uxdu0YXei-7JZMCuCI3bxKMdnbuossV-KC68GG-AS4,9804
|
||||
asyncpg/_version.py,sha256=vGtvByhKF_7cyfQ46GVcrEyZ0o87ts1ofOzkmLgbmFg,576
|
||||
asyncpg/cluster.py,sha256=Bna0wFKj9tACcD4Uxjv9eeo5EwAEeJi4t5YVbN434ao,23283
|
||||
asyncpg/compat.py,sha256=mQmQgtRgu1clS-Aqiz76g1tHH9qXIRK_xJ7sokx-Y2U,1769
|
||||
asyncpg/connect_utils.py,sha256=xZE61cj1Afwm_VyKSDmWHcYwDCwIIk66OXq9MBHyH8M,34979
|
||||
asyncpg/connection.py,sha256=f30Jo8XllatqjavvlrkNCcgnIaKnNTQvf32NVJB3ExM,95227
|
||||
asyncpg/connresource.py,sha256=tBAidNpEhbDvrMOKQbwn3ZNgIVAtsVxARxTnwj5fk-Q,1384
|
||||
asyncpg/cursor.py,sha256=rKeSIJMW5mUpvsian6a1MLrLoEwbkYTZsmZtEgwFT6s,9160
|
||||
asyncpg/exceptions/__init__.py,sha256=yZXt3k0lHuF-5czqfBcsMfhxgI5fXAT31hSTn7_fiMM,28826
|
||||
asyncpg/exceptions/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/exceptions/__pycache__/_base.cpython-312.pyc,,
|
||||
asyncpg/exceptions/_base.py,sha256=u62xv69n4AHO1xr35FjdgZhYvqdeb_mkQKyp-ip_AyQ,9260
|
||||
asyncpg/introspection.py,sha256=0oyQXJF6WHpVMq7K_8VIOMVTlGde71cFCA_9NkuDgcQ,8957
|
||||
asyncpg/pgproto/__init__.pxd,sha256=uUIkKuI6IGnQ5tZXtrjOC_13qjp9MZOwewKlrxKFzPY,213
|
||||
asyncpg/pgproto/__init__.py,sha256=uUIkKuI6IGnQ5tZXtrjOC_13qjp9MZOwewKlrxKFzPY,213
|
||||
asyncpg/pgproto/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/pgproto/__pycache__/types.cpython-312.pyc,,
|
||||
asyncpg/pgproto/buffer.pxd,sha256=dVaRqkbNiT5xhQ9HTwbavJWWN3aCT1mWkecKuq-Fm9k,4382
|
||||
asyncpg/pgproto/buffer.pyx,sha256=8npNqR7ATB4iLase-V3xobD4W8L0IB_f8H1Ko4VEmgg,25310
|
||||
asyncpg/pgproto/codecs/__init__.pxd,sha256=14J1iXxgadLdTa0wjVQJuH0pooZXugSxIS8jVgSAico,6013
|
||||
asyncpg/pgproto/codecs/bits.pyx,sha256=x4MMVRLotz9R8n81E0S3lQQk23AvLlODb2pe_NGYqCI,1475
|
||||
asyncpg/pgproto/codecs/bytea.pyx,sha256=ot-oFH-hzQ89EUWneHk5QDUxl2krKkpYE_nWklVHXWU,997
|
||||
asyncpg/pgproto/codecs/context.pyx,sha256=oYurToHnpZz-Q8kPzRORFS_RyV4HH5kscNKsZYPt4FU,623
|
||||
asyncpg/pgproto/codecs/datetime.pyx,sha256=gPRHIkSy0nNVhW-rTT7WCGthrKksW68-0GyKlLzVpIc,12831
|
||||
asyncpg/pgproto/codecs/float.pyx,sha256=A6XXA2NdS82EENhADA35LInxLcJsRpXvF6JVme_6HCc,1031
|
||||
asyncpg/pgproto/codecs/geometry.pyx,sha256=DtRADwsifbzAZyACxakne2MVApcUNji8EyOgtKuoEaw,4665
|
||||
asyncpg/pgproto/codecs/hstore.pyx,sha256=sXwFn3uzypvPkYIFH0FykiW9RU8qRme2N0lg8UoB6kg,2018
|
||||
asyncpg/pgproto/codecs/int.pyx,sha256=4RuntTl_4-I7ekCSONK9y4CWFghUmaFGldXL6ruLgxM,4527
|
||||
asyncpg/pgproto/codecs/json.pyx,sha256=fs7d0sroyMM9UZW-mmGgvHtVG7MiBac7Inb_wz1mMRs,1454
|
||||
asyncpg/pgproto/codecs/jsonpath.pyx,sha256=bAXgTvPzQlkJdlHHB95CNl03J2WAd_iK3JsE1PXI2KU,833
|
||||
asyncpg/pgproto/codecs/misc.pyx,sha256=ul5HFobQ1H3shO6ThrSlkEHO1lvxOoqTnRej3UabKiQ,484
|
||||
asyncpg/pgproto/codecs/network.pyx,sha256=1oFM__xT5H3pIZrLyRqjNqrR6z1UNlqMOWGTGnsbOyw,3917
|
||||
asyncpg/pgproto/codecs/numeric.pyx,sha256=TAN5stFXzmEiyP69MDG1oXryPAFCyZmxHcqPc-vy7LM,10373
|
||||
asyncpg/pgproto/codecs/pg_snapshot.pyx,sha256=WGJ-dv7JXVufybAiuScth7KlXXLRdMqSKbtfT4kpVWI,1814
|
||||
asyncpg/pgproto/codecs/text.pyx,sha256=yHpJCRxrf2Pgmz1abYSgvFQDRcgCJN137aniygOo_ec,1516
|
||||
asyncpg/pgproto/codecs/tid.pyx,sha256=_9L8C9NSDV6Ehk48VV8xOLDNLVJz2R88EornZbHcq88,1549
|
||||
asyncpg/pgproto/codecs/uuid.pyx,sha256=XIydQCaPUlfz9MvVDOu_5BTHd1kSKmJ1r3kBpsfjfYE,855
|
||||
asyncpg/pgproto/consts.pxi,sha256=YV-GG19C1LpLtoJx-bF8Wl49wU3iZMylyQzl_ah8gFw,375
|
||||
asyncpg/pgproto/cpythonx.pxd,sha256=B9fAfasXgoWN-Z-STGCxbu0sW-QR8EblCIbxlzPo0Uc,736
|
||||
asyncpg/pgproto/debug.pxd,sha256=SuLG2tteWe3cXnS0czRTTNnnm2QGgG02icp_6G_X9Yw,263
|
||||
asyncpg/pgproto/frb.pxd,sha256=B2s2dw-SkzfKWeLEWzVLTkjjYYW53pazPcVNH3vPxAk,1212
|
||||
asyncpg/pgproto/frb.pyx,sha256=7bipWSBXebweq3JBFlCvSwa03fIZGLkKPqWbJ8VFWFI,409
|
||||
asyncpg/pgproto/hton.pxd,sha256=Swx5ry82iWYO9Ok4fRa_b7cLSrIPyxNYlyXm-ncYweo,953
|
||||
asyncpg/pgproto/pgproto.cpython-312-x86_64-linux-gnu.so,sha256=niR6XwwgUbpcrq6BrfQXz0NgIq2fn9xyMgzPq2yrACY,2849672
|
||||
asyncpg/pgproto/pgproto.pxd,sha256=QUUxWiHKdKfFxdDT0czSvOFsA4b59MJRR6WlUbJFgPg,430
|
||||
asyncpg/pgproto/pgproto.pyx,sha256=bK75qfRQlofzO8dDzJ2mHUE0wLeXSsc5SLeAGvyXSeE,1249
|
||||
asyncpg/pgproto/tohex.pxd,sha256=fQVaxBu6dBw2P_ROR8MSPVDlVep0McKi69fdQBLhifI,361
|
||||
asyncpg/pgproto/types.py,sha256=wzJgyDJ63Eu2TJym0EhhEr6-D9iIV3cdlzab11sgRS0,13014
|
||||
asyncpg/pgproto/uuid.pyx,sha256=PrQIvQKJJItsYFpwZtDCcR9Z_DIbEi_MUt6tQjnVaYI,9943
|
||||
asyncpg/pool.py,sha256=VilAdZmMrodLmu7xeYk2ExoJRFUzk4ORT4kdxMMVE64,38168
|
||||
asyncpg/prepared_stmt.py,sha256=jay1C7UISpmXmotWkUXgdRidgtSdvmaCxlGZ6xlNGEM,8992
|
||||
asyncpg/protocol/__init__.py,sha256=6mxFfJskIjmKjSxxOybsuHY68wa2BlqY3z0VWG1BT4g,304
|
||||
asyncpg/protocol/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/protocol/codecs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
asyncpg/protocol/codecs/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/protocol/codecs/array.pyx,sha256=1S_6xdgxllG8_1Lb68XdPkH1QgF63gAAmjh091Q7Dyk,29486
|
||||
asyncpg/protocol/codecs/base.pxd,sha256=NfDsh60UZX-gVThlj8rzGmLRqMbXAYqSJsAwKTcZ1Cg,6224
|
||||
asyncpg/protocol/codecs/base.pyx,sha256=V8-mmRPV3eFn2jUmdFILFNNsjUaFH8_x4S5IJ7OjtCM,33475
|
||||
asyncpg/protocol/codecs/pgproto.pyx,sha256=5PDv1JT_nXbDbHtYVrGCcZN3CxzQdgwqlXT8GpyMamk,17175
|
||||
asyncpg/protocol/codecs/range.pyx,sha256=-P-acyY2e5TlEtjqbkeH28PYk-DGLxqbmzKDFGL5BbI,6359
|
||||
asyncpg/protocol/codecs/record.pyx,sha256=l17HPv3ZeZzvDMXmh-FTdOQ0LxqaQsge_4hlmnGaf6s,2362
|
||||
asyncpg/protocol/codecs/textutils.pyx,sha256=UmTt1Zs5N2oLVDMTSlSe1zAFt5q4_4akbXZoS6HSPO8,2011
|
||||
asyncpg/protocol/consts.pxi,sha256=VT7NLBpLgPUvcUbPflrX84I79JZiFg4zFzBK28nCRZo,381
|
||||
asyncpg/protocol/coreproto.pxd,sha256=ozuSON07EOnWmJI4v3gtTjD18APpZfk1WfnoWLZ53as,6149
|
||||
asyncpg/protocol/coreproto.pyx,sha256=UprN-4_PaJFN82fCCA2tE0t_i_dShyTdtsbymOYGnfE,38015
|
||||
asyncpg/protocol/cpythonx.pxd,sha256=VX71g4PiwXWGTY-BzBPm7S-AiX5ySRrY40qAggH-BIA,613
|
||||
asyncpg/protocol/encodings.pyx,sha256=QegnSON5y-a0aQFD9zFbhAzhYTbKYj-vl3VGiyqIU3I,1644
|
||||
asyncpg/protocol/pgtypes.pxi,sha256=w8Mb6N7Z58gxPYWZkj5lwk0PRW7oBTIf9fo0MvPzm4c,6924
|
||||
asyncpg/protocol/prepared_stmt.pxd,sha256=GhHzJgQMehpWg0i3XSmbkJH6G5nnnmdNCf2EU_gXhDY,1115
|
||||
asyncpg/protocol/prepared_stmt.pyx,sha256=fbhQpVuDFEQ1GOw--sZdrD-iOkTvU5JXFOlxKpTe36c,13052
|
||||
asyncpg/protocol/protocol.cpython-312-x86_64-linux-gnu.so,sha256=92ZeyBjeYWIwDLtGqPCzTYw-94N8V-3BiTguwL6iNu4,8713328
|
||||
asyncpg/protocol/protocol.pxd,sha256=0Y1NFvnR3N0rmvBMUAocYi4U9RbAyg-6qkoqOgy53Fg,1950
|
||||
asyncpg/protocol/protocol.pyx,sha256=2EN1Aq45eR3pGQjQciafqFQzi4ilLqDLP2LpLWM3wVE,34824
|
||||
asyncpg/protocol/record/__init__.pxd,sha256=KJyCfN_ST2yyEDnUS3PfipeIEYmY8CVTeOwFPcUcVNc,495
|
||||
asyncpg/protocol/scram.pxd,sha256=t_nkicIS_4AzxyHoq-aYUNrFNv8O0W7E090HfMAIuno,1299
|
||||
asyncpg/protocol/scram.pyx,sha256=nT_Rawg6h3OrRWDBwWN7lju5_hnOmXpwWFWVrb3l_dQ,14594
|
||||
asyncpg/protocol/settings.pxd,sha256=8DTwZ5mi0aAUJRWE6SUIRDhWFGFis1mj8lcA8hNFTL0,1066
|
||||
asyncpg/protocol/settings.pyx,sha256=Z_GsQoRKzqBeztO8AJMTbv_xpT-mk8LgLfvQ2l-W7cY,3795
|
||||
asyncpg/serverversion.py,sha256=xdxEy45U9QGhpfTp3c4g6jSJ3NEb4lsDcTe3qvFNDQg,1790
|
||||
asyncpg/transaction.py,sha256=uAJok6Shx7-Kdt5l4NX-GJtLxVJSPXTOJUryGdbIVG8,8497
|
||||
asyncpg/types.py,sha256=msRSL9mXKPWjVXMi0yrk5vhVwQp9Sdwyfcp_zz8ZkNU,4653
|
||||
asyncpg/utils.py,sha256=NWmcsmYORwc4rjJvwrUqJrv1lP2Qq5c-v139LBv2ZVQ,1367
|
||||
asyncpg-0.30.0.dist-info/AUTHORS,sha256=gIYYcUuWiSZS93lstwQtCT56St1NtKg-fikn8ourw64,130
|
||||
asyncpg-0.30.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
asyncpg-0.30.0.dist-info/LICENSE,sha256=2SItc_2sUJkhdAdu-gT0T2-82dVhVafHCS6YdXBCpvY,11466
|
||||
asyncpg-0.30.0.dist-info/METADATA,sha256=60MN0tXDvcPtxahUC1vxSP8-dS5hYDtir_YIbY2NCkQ,5010
|
||||
asyncpg-0.30.0.dist-info/RECORD,,
|
||||
asyncpg-0.30.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
asyncpg-0.30.0.dist-info/WHEEL,sha256=OVgtqZzfzIXXtylXP90gxCZ6CKBCwKYyHM8PpMEjN1M,151
|
||||
asyncpg-0.30.0.dist-info/top_level.txt,sha256=DdhVhpzCq49mykkHNag6i9zuJx05_tx4CMZymM1F8dU,8
|
||||
asyncpg/__init__.py,sha256=bzD31aMekbKR9waMXuAxIYFbmrQ-S1Mttjmru_sSjo8,647
|
||||
asyncpg/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/_asyncio_compat.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/_version.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/cluster.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/compat.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connect_utils.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connection.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/connresource.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/cursor.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/introspection.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/pool.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/prepared_stmt.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/serverversion.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/transaction.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/types.cpython-312.pyc,,
|
||||
asyncpg/__pycache__/utils.cpython-312.pyc,,
|
||||
asyncpg/_asyncio_compat.py,sha256=pXF_aF4o_AqxNql0sPnuGdoe5sSSwQxHpKWF6ShZTbo,2540
|
||||
asyncpg/_testbase/__init__.py,sha256=IzMqfgI5gtOxajneoeWyoI4NtmE5sp7S5dXmU0gwwB8,16499
|
||||
asyncpg/_testbase/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/_testbase/__pycache__/fuzzer.cpython-312.pyc,,
|
||||
asyncpg/_testbase/fuzzer.py,sha256=3Uxdu0YXei-7JZMCuCI3bxKMdnbuossV-KC68GG-AS4,9804
|
||||
asyncpg/_version.py,sha256=MLgciqpbfndZJPsc0fi_WNdVVcsn3Wobpaw0WiaRvEo,641
|
||||
asyncpg/cluster.py,sha256=s_HmtiEGJqJ6GQWa6_zmfe11fZ29OpOtMT6Ufcu-g0g,24476
|
||||
asyncpg/compat.py,sha256=ebs2IeJw82rY9m0ZCmOYUqry_2nF3zqTi3tsWP5FT2o,2459
|
||||
asyncpg/connect_utils.py,sha256=vaVSrnmko33wPjw1X5wlbooF0FTeFlN5b50burZuUWc,36923
|
||||
asyncpg/connection.py,sha256=EFlI_1VIkSFzSszsUCCl0eFJITT-5McSuAVmWJyCy-Y,98545
|
||||
asyncpg/connresource.py,sha256=tBAidNpEhbDvrMOKQbwn3ZNgIVAtsVxARxTnwj5fk-Q,1384
|
||||
asyncpg/cursor.py,sha256=rKeSIJMW5mUpvsian6a1MLrLoEwbkYTZsmZtEgwFT6s,9160
|
||||
asyncpg/exceptions/__init__.py,sha256=FXUYDFQw9gxE3mVz99FmsldYxivLUMtTIhXzu5tZ7Pk,29157
|
||||
asyncpg/exceptions/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/exceptions/__pycache__/_base.cpython-312.pyc,,
|
||||
asyncpg/exceptions/_base.py,sha256=u62xv69n4AHO1xr35FjdgZhYvqdeb_mkQKyp-ip_AyQ,9260
|
||||
asyncpg/introspection.py,sha256=biiHj5yQMB8RGch2TiH2TPocN3OO6_GasyijFYxgUOM,9215
|
||||
asyncpg/pgproto/__init__.pxd,sha256=uUIkKuI6IGnQ5tZXtrjOC_13qjp9MZOwewKlrxKFzPY,213
|
||||
asyncpg/pgproto/__init__.py,sha256=uUIkKuI6IGnQ5tZXtrjOC_13qjp9MZOwewKlrxKFzPY,213
|
||||
asyncpg/pgproto/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/pgproto/__pycache__/types.cpython-312.pyc,,
|
||||
asyncpg/pgproto/buffer.pxd,sha256=dVaRqkbNiT5xhQ9HTwbavJWWN3aCT1mWkecKuq-Fm9k,4382
|
||||
asyncpg/pgproto/buffer.pyx,sha256=8npNqR7ATB4iLase-V3xobD4W8L0IB_f8H1Ko4VEmgg,25310
|
||||
asyncpg/pgproto/codecs/__init__.pxd,sha256=14J1iXxgadLdTa0wjVQJuH0pooZXugSxIS8jVgSAico,6013
|
||||
asyncpg/pgproto/codecs/bits.pyx,sha256=x4MMVRLotz9R8n81E0S3lQQk23AvLlODb2pe_NGYqCI,1475
|
||||
asyncpg/pgproto/codecs/bytea.pyx,sha256=ot-oFH-hzQ89EUWneHk5QDUxl2krKkpYE_nWklVHXWU,997
|
||||
asyncpg/pgproto/codecs/context.pyx,sha256=oYurToHnpZz-Q8kPzRORFS_RyV4HH5kscNKsZYPt4FU,623
|
||||
asyncpg/pgproto/codecs/datetime.pyx,sha256=gPRHIkSy0nNVhW-rTT7WCGthrKksW68-0GyKlLzVpIc,12831
|
||||
asyncpg/pgproto/codecs/float.pyx,sha256=A6XXA2NdS82EENhADA35LInxLcJsRpXvF6JVme_6HCc,1031
|
||||
asyncpg/pgproto/codecs/geometry.pyx,sha256=DtRADwsifbzAZyACxakne2MVApcUNji8EyOgtKuoEaw,4665
|
||||
asyncpg/pgproto/codecs/hstore.pyx,sha256=sXwFn3uzypvPkYIFH0FykiW9RU8qRme2N0lg8UoB6kg,2018
|
||||
asyncpg/pgproto/codecs/int.pyx,sha256=4RuntTl_4-I7ekCSONK9y4CWFghUmaFGldXL6ruLgxM,4527
|
||||
asyncpg/pgproto/codecs/json.pyx,sha256=fs7d0sroyMM9UZW-mmGgvHtVG7MiBac7Inb_wz1mMRs,1454
|
||||
asyncpg/pgproto/codecs/jsonpath.pyx,sha256=bAXgTvPzQlkJdlHHB95CNl03J2WAd_iK3JsE1PXI2KU,833
|
||||
asyncpg/pgproto/codecs/misc.pyx,sha256=ul5HFobQ1H3shO6ThrSlkEHO1lvxOoqTnRej3UabKiQ,484
|
||||
asyncpg/pgproto/codecs/network.pyx,sha256=1oFM__xT5H3pIZrLyRqjNqrR6z1UNlqMOWGTGnsbOyw,3917
|
||||
asyncpg/pgproto/codecs/numeric.pyx,sha256=TAN5stFXzmEiyP69MDG1oXryPAFCyZmxHcqPc-vy7LM,10373
|
||||
asyncpg/pgproto/codecs/pg_snapshot.pyx,sha256=WGJ-dv7JXVufybAiuScth7KlXXLRdMqSKbtfT4kpVWI,1814
|
||||
asyncpg/pgproto/codecs/text.pyx,sha256=yHpJCRxrf2Pgmz1abYSgvFQDRcgCJN137aniygOo_ec,1516
|
||||
asyncpg/pgproto/codecs/tid.pyx,sha256=_9L8C9NSDV6Ehk48VV8xOLDNLVJz2R88EornZbHcq88,1549
|
||||
asyncpg/pgproto/codecs/uuid.pyx,sha256=XIydQCaPUlfz9MvVDOu_5BTHd1kSKmJ1r3kBpsfjfYE,855
|
||||
asyncpg/pgproto/consts.pxi,sha256=YV-GG19C1LpLtoJx-bF8Wl49wU3iZMylyQzl_ah8gFw,375
|
||||
asyncpg/pgproto/cpythonx.pxd,sha256=B9fAfasXgoWN-Z-STGCxbu0sW-QR8EblCIbxlzPo0Uc,736
|
||||
asyncpg/pgproto/debug.pxd,sha256=SuLG2tteWe3cXnS0czRTTNnnm2QGgG02icp_6G_X9Yw,263
|
||||
asyncpg/pgproto/frb.pxd,sha256=B2s2dw-SkzfKWeLEWzVLTkjjYYW53pazPcVNH3vPxAk,1212
|
||||
asyncpg/pgproto/frb.pyx,sha256=7bipWSBXebweq3JBFlCvSwa03fIZGLkKPqWbJ8VFWFI,409
|
||||
asyncpg/pgproto/hton.pxd,sha256=Swx5ry82iWYO9Ok4fRa_b7cLSrIPyxNYlyXm-ncYweo,953
|
||||
asyncpg/pgproto/pgproto.cpython-312-x86_64-linux-gnu.so,sha256=pq0nrGmFE6y2VQgcWlKcFODTl9h9We00i1xQT55RYdE,3131904
|
||||
asyncpg/pgproto/pgproto.pxd,sha256=QUUxWiHKdKfFxdDT0czSvOFsA4b59MJRR6WlUbJFgPg,430
|
||||
asyncpg/pgproto/pgproto.pyi,sha256=W5nuATmpHFfhRF7Hnjt5Vuvr1lBJ-xkJ8nIvEYE1N1E,275
|
||||
asyncpg/pgproto/pgproto.pyx,sha256=bK75qfRQlofzO8dDzJ2mHUE0wLeXSsc5SLeAGvyXSeE,1249
|
||||
asyncpg/pgproto/tohex.pxd,sha256=fQVaxBu6dBw2P_ROR8MSPVDlVep0McKi69fdQBLhifI,361
|
||||
asyncpg/pgproto/types.py,sha256=wzJgyDJ63Eu2TJym0EhhEr6-D9iIV3cdlzab11sgRS0,13014
|
||||
asyncpg/pgproto/uuid.pyx,sha256=PrQIvQKJJItsYFpwZtDCcR9Z_DIbEi_MUt6tQjnVaYI,9943
|
||||
asyncpg/pool.py,sha256=oZh4JC01xizpa3MQSJ4mcOW71Nb_jYWluY_Dm2549fg,41296
|
||||
asyncpg/prepared_stmt.py,sha256=YfOSeQavN1c1o5SajD9ylTCLHpNV5plGBEw9ku8KyBk,9752
|
||||
asyncpg/protocol/__init__.py,sha256=c-b07Si_DGN9rqiCUAmR9RaCUCy_LiJ4lqHCb0yMBRI,340
|
||||
asyncpg/protocol/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/protocol/codecs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
asyncpg/protocol/codecs/__pycache__/__init__.cpython-312.pyc,,
|
||||
asyncpg/protocol/codecs/array.pyx,sha256=1S_6xdgxllG8_1Lb68XdPkH1QgF63gAAmjh091Q7Dyk,29486
|
||||
asyncpg/protocol/codecs/base.pxd,sha256=NfDsh60UZX-gVThlj8rzGmLRqMbXAYqSJsAwKTcZ1Cg,6224
|
||||
asyncpg/protocol/codecs/base.pyx,sha256=C1SPRtSdYbshnvZOHJVj9Gp30VSj5z6nQRBUoPgj2IU,33464
|
||||
asyncpg/protocol/codecs/pgproto.pyx,sha256=5PDv1JT_nXbDbHtYVrGCcZN3CxzQdgwqlXT8GpyMamk,17175
|
||||
asyncpg/protocol/codecs/range.pyx,sha256=-P-acyY2e5TlEtjqbkeH28PYk-DGLxqbmzKDFGL5BbI,6359
|
||||
asyncpg/protocol/codecs/record.pyx,sha256=l17HPv3ZeZzvDMXmh-FTdOQ0LxqaQsge_4hlmnGaf6s,2362
|
||||
asyncpg/protocol/codecs/textutils.pyx,sha256=UmTt1Zs5N2oLVDMTSlSe1zAFt5q4_4akbXZoS6HSPO8,2011
|
||||
asyncpg/protocol/consts.pxi,sha256=VT7NLBpLgPUvcUbPflrX84I79JZiFg4zFzBK28nCRZo,381
|
||||
asyncpg/protocol/coreproto.pxd,sha256=77yJqaBMGWHmxyihZIFfyVgfzICF9jLwKSvtuCoE8rM,6215
|
||||
asyncpg/protocol/coreproto.pyx,sha256=sMvXqxnppthc_LJYibMAJts0IfEPgYVs4nwXmY3v-IY,41037
|
||||
asyncpg/protocol/cpythonx.pxd,sha256=VX71g4PiwXWGTY-BzBPm7S-AiX5ySRrY40qAggH-BIA,613
|
||||
asyncpg/protocol/encodings.pyx,sha256=QegnSON5y-a0aQFD9zFbhAzhYTbKYj-vl3VGiyqIU3I,1644
|
||||
asyncpg/protocol/pgtypes.pxi,sha256=w8Mb6N7Z58gxPYWZkj5lwk0PRW7oBTIf9fo0MvPzm4c,6924
|
||||
asyncpg/protocol/prepared_stmt.pxd,sha256=GhHzJgQMehpWg0i3XSmbkJH6G5nnnmdNCf2EU_gXhDY,1115
|
||||
asyncpg/protocol/prepared_stmt.pyx,sha256=wfo57hwGrghO3-0o7OxABV2heL2Fb0teENUZNmMj6aI,13058
|
||||
asyncpg/protocol/protocol.cpython-312-x86_64-linux-gnu.so,sha256=mIYQ9YlP2JuiyV4448aKNqyUZgEVjBiVXlsSzKxM41k,9439904
|
||||
asyncpg/protocol/protocol.pxd,sha256=yOVFbkD7mA8VK5IGIJ4dGTyvHKWZTQOFfCFNfdeUdK8,1927
|
||||
asyncpg/protocol/protocol.pyi,sha256=Dg0-ZTvLCXc3g3aCvEHvSKVzRp63Q-9iceiqTSQMr2g,9732
|
||||
asyncpg/protocol/protocol.pyx,sha256=V99Dm45e8vgV3qSa-jmS2YypntSymrznLtyxoveU7jI,34850
|
||||
asyncpg/protocol/record/__init__.pxd,sha256=KJyCfN_ST2yyEDnUS3PfipeIEYmY8CVTeOwFPcUcVNc,495
|
||||
asyncpg/protocol/scram.pxd,sha256=t_nkicIS_4AzxyHoq-aYUNrFNv8O0W7E090HfMAIuno,1299
|
||||
asyncpg/protocol/scram.pyx,sha256=nT_Rawg6h3OrRWDBwWN7lju5_hnOmXpwWFWVrb3l_dQ,14594
|
||||
asyncpg/protocol/settings.pxd,sha256=8DTwZ5mi0aAUJRWE6SUIRDhWFGFis1mj8lcA8hNFTL0,1066
|
||||
asyncpg/protocol/settings.pyx,sha256=yICjZF5FXwfmdxQBg-1qO0XbpLvZL11-c3aMbiwM7oo,3777
|
||||
asyncpg/serverversion.py,sha256=WwlqBJkXZHvvnFluubCjPoaX_7OqjR8QgiOe90w6C9E,2133
|
||||
asyncpg/transaction.py,sha256=uAJok6Shx7-Kdt5l4NX-GJtLxVJSPXTOJUryGdbIVG8,8497
|
||||
asyncpg/types.py,sha256=2x-nAVdfk41PA83DyYcWxkUNXsiGLotGkMX0gVpuFoY,5520
|
||||
asyncpg/utils.py,sha256=Y0vATexoIHFkpWURlqnlUZUacc4F1iZJ9rWJ3654OnM,1495
|
||||
@@ -1,5 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.41.3)
|
||||
Generator: setuptools (75.2.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp312-cp312-manylinux_2_17_x86_64
|
||||
Tag: cp312-cp312-manylinux2014_x86_64
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user