Initial commit
This commit is contained in:
164
venv/Include/site/python3.11/greenlet/greenlet.h
Normal file
164
venv/Include/site/python3.11/greenlet/greenlet.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||
|
||||
/* Greenlet object interface */
|
||||
|
||||
#ifndef Py_GREENLETOBJECT_H
|
||||
#define Py_GREENLETOBJECT_H
|
||||
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is deprecated and undocumented. It does not change. */
|
||||
#define GREENLET_VERSION "1.0.0"
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
#define implementation_ptr_t void*
|
||||
#endif
|
||||
|
||||
typedef struct _greenlet {
|
||||
PyObject_HEAD
|
||||
PyObject* weakreflist;
|
||||
PyObject* dict;
|
||||
implementation_ptr_t pimpl;
|
||||
} PyGreenlet;
|
||||
|
||||
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||
|
||||
|
||||
/* C API functions */
|
||||
|
||||
/* Total number of symbols that are exported */
|
||||
#define PyGreenlet_API_pointers 12
|
||||
|
||||
#define PyGreenlet_Type_NUM 0
|
||||
#define PyExc_GreenletError_NUM 1
|
||||
#define PyExc_GreenletExit_NUM 2
|
||||
|
||||
#define PyGreenlet_New_NUM 3
|
||||
#define PyGreenlet_GetCurrent_NUM 4
|
||||
#define PyGreenlet_Throw_NUM 5
|
||||
#define PyGreenlet_Switch_NUM 6
|
||||
#define PyGreenlet_SetParent_NUM 7
|
||||
|
||||
#define PyGreenlet_MAIN_NUM 8
|
||||
#define PyGreenlet_STARTED_NUM 9
|
||||
#define PyGreenlet_ACTIVE_NUM 10
|
||||
#define PyGreenlet_GET_PARENT_NUM 11
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
/* This section is used by modules that uses the greenlet C API */
|
||||
static void** _PyGreenlet_API = NULL;
|
||||
|
||||
# define PyGreenlet_Type \
|
||||
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||
|
||||
# define PyExc_GreenletError \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||
|
||||
# define PyExc_GreenletExit \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_New(PyObject *args)
|
||||
*
|
||||
* greenlet.greenlet(run, parent=None)
|
||||
*/
|
||||
# define PyGreenlet_New \
|
||||
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetCurrent(void)
|
||||
*
|
||||
* greenlet.getcurrent()
|
||||
*/
|
||||
# define PyGreenlet_GetCurrent \
|
||||
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Throw(
|
||||
* PyGreenlet *greenlet,
|
||||
* PyObject *typ,
|
||||
* PyObject *val,
|
||||
* PyObject *tb)
|
||||
*
|
||||
* g.throw(...)
|
||||
*/
|
||||
# define PyGreenlet_Throw \
|
||||
(*(PyObject * (*)(PyGreenlet * self, \
|
||||
PyObject * typ, \
|
||||
PyObject * val, \
|
||||
PyObject * tb)) \
|
||||
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||
*
|
||||
* g.switch(*args, **kwargs)
|
||||
*/
|
||||
# define PyGreenlet_Switch \
|
||||
(*(PyObject * \
|
||||
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||
*
|
||||
* g.parent = new_parent
|
||||
*/
|
||||
# define PyGreenlet_SetParent \
|
||||
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||
*
|
||||
* return greenlet.parent;
|
||||
*
|
||||
* This could return NULL even if there is no exception active.
|
||||
* If it does not return NULL, you are responsible for decrementing the
|
||||
* reference count.
|
||||
*/
|
||||
# define PyGreenlet_GetParent \
|
||||
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||
|
||||
/*
|
||||
* deprecated, undocumented alias.
|
||||
*/
|
||||
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||
|
||||
# define PyGreenlet_MAIN \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||
|
||||
# define PyGreenlet_STARTED \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||
|
||||
# define PyGreenlet_ACTIVE \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||
|
||||
|
||||
|
||||
|
||||
/* Macro that imports greenlet and initializes C API */
|
||||
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||
keep the older definition to be sure older code that might have a copy of
|
||||
the header still works. */
|
||||
# define PyGreenlet_Import() \
|
||||
{ \
|
||||
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||
}
|
||||
|
||||
#endif /* GREENLET_MODULE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* !Py_GREENLETOBJECT_H */
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
22
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/LICENSE
Normal file
22
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2011 Matthew Frazier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
183
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/METADATA
Normal file
183
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/METADATA
Normal file
@@ -0,0 +1,183 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Flask-Login
|
||||
Version: 0.6.3
|
||||
Summary: User authentication and session management for Flask.
|
||||
Home-page: https://github.com/maxcountryman/flask-login
|
||||
Author: Matthew Frazier
|
||||
Author-email: leafstormrush@gmail.com
|
||||
Maintainer: Max Countryman
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://flask-login.readthedocs.io/
|
||||
Project-URL: Changes, https://github.com/maxcountryman/flask-login/blob/main/CHANGES.md
|
||||
Project-URL: Source Code, https://github.com/maxcountryman/flask-login
|
||||
Project-URL: Issue Tracker, https://github.com/maxcountryman/flask-login/issues
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Flask
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Requires-Python: >=3.7
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
Requires-Dist: Flask >=1.0.4
|
||||
Requires-Dist: Werkzeug >=1.0.1
|
||||
|
||||
# Flask-Login
|
||||
|
||||

|
||||
[](https://coveralls.io/github/maxcountryman/flask-login?branch=main)
|
||||
[](LICENSE)
|
||||
|
||||
Flask-Login provides user session management for Flask. It handles the common
|
||||
tasks of logging in, logging out, and remembering your users' sessions over
|
||||
extended periods of time.
|
||||
|
||||
Flask-Login is not bound to any particular database system or permissions
|
||||
model. The only requirement is that your user objects implement a few methods,
|
||||
and that you provide a callback to the extension capable of loading users from
|
||||
their ID.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the extension with pip:
|
||||
|
||||
```sh
|
||||
$ pip install flask-login
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, the Flask-Login is easy to use. Let's walk through setting up
|
||||
a basic application. Also please note that this is a very basic guide: we will
|
||||
be taking shortcuts here that you should never take in a real application.
|
||||
|
||||
To begin we'll set up a Flask app:
|
||||
|
||||
```python
|
||||
import flask
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.secret_key = 'super secret string' # Change this!
|
||||
```
|
||||
|
||||
Flask-Login works via a login manager. To kick things off, we'll set up the
|
||||
login manager by instantiating it and telling it about our Flask app:
|
||||
|
||||
```python
|
||||
import flask_login
|
||||
|
||||
login_manager = flask_login.LoginManager()
|
||||
|
||||
login_manager.init_app(app)
|
||||
```
|
||||
|
||||
To keep things simple we're going to use a dictionary to represent a database
|
||||
of users. In a real application, this would be an actual persistence layer.
|
||||
However it's important to point out this is a feature of Flask-Login: it
|
||||
doesn't care how your data is stored so long as you tell it how to retrieve it!
|
||||
|
||||
```python
|
||||
# Our mock database.
|
||||
users = {'foo@bar.tld': {'password': 'secret'}}
|
||||
```
|
||||
|
||||
We also need to tell Flask-Login how to load a user from a Flask request and
|
||||
from its session. To do this we need to define our user object, a
|
||||
`user_loader` callback, and a `request_loader` callback.
|
||||
|
||||
```python
|
||||
class User(flask_login.UserMixin):
|
||||
pass
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def user_loader(email):
|
||||
if email not in users:
|
||||
return
|
||||
|
||||
user = User()
|
||||
user.id = email
|
||||
return user
|
||||
|
||||
|
||||
@login_manager.request_loader
|
||||
def request_loader(request):
|
||||
email = request.form.get('email')
|
||||
if email not in users:
|
||||
return
|
||||
|
||||
user = User()
|
||||
user.id = email
|
||||
return user
|
||||
```
|
||||
|
||||
Now we're ready to define our views. We can start with a login view, which will
|
||||
populate the session with authentication bits. After that we can define a view
|
||||
that requires authentication.
|
||||
|
||||
```python
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if flask.request.method == 'GET':
|
||||
return '''
|
||||
<form action='login' method='POST'>
|
||||
<input type='text' name='email' id='email' placeholder='email'/>
|
||||
<input type='password' name='password' id='password' placeholder='password'/>
|
||||
<input type='submit' name='submit'/>
|
||||
</form>
|
||||
'''
|
||||
|
||||
email = flask.request.form['email']
|
||||
if email in users and flask.request.form['password'] == users[email]['password']:
|
||||
user = User()
|
||||
user.id = email
|
||||
flask_login.login_user(user)
|
||||
return flask.redirect(flask.url_for('protected'))
|
||||
|
||||
return 'Bad login'
|
||||
|
||||
|
||||
@app.route('/protected')
|
||||
@flask_login.login_required
|
||||
def protected():
|
||||
return 'Logged in as: ' + flask_login.current_user.id
|
||||
```
|
||||
|
||||
Finally we can define a view to clear the session and log users out:
|
||||
|
||||
```python
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
flask_login.logout_user()
|
||||
return 'Logged out'
|
||||
```
|
||||
|
||||
We now have a basic working application that makes use of session-based
|
||||
authentication. To round things off, we should provide a callback for login
|
||||
failures:
|
||||
|
||||
```python
|
||||
@login_manager.unauthorized_handler
|
||||
def unauthorized_handler():
|
||||
return 'Unauthorized', 401
|
||||
```
|
||||
|
||||
Documentation for Flask-Login is available on [ReadTheDocs](https://flask-login.readthedocs.io/en/latest/).
|
||||
For complete understanding of available configuration, please refer to the [source code](https://github.com/maxcountryman/flask-login).
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! If you would like to hack on Flask-Login, please
|
||||
follow these steps:
|
||||
|
||||
1. Fork this repository
|
||||
2. Make your changes
|
||||
3. Install the dev requirements with `pip install -r requirements/dev.txt`
|
||||
4. Submit a pull request after running `tox` (ensure it does not error!)
|
||||
|
||||
Please give us adequate time to review your submission. Thanks!
|
||||
23
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/RECORD
Normal file
23
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/RECORD
Normal file
@@ -0,0 +1,23 @@
|
||||
Flask_Login-0.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Flask_Login-0.6.3.dist-info/LICENSE,sha256=ep37nF2iBO0TcPO2LBPimSoS2h2nB_R-FWiX7rQ0Tls,1059
|
||||
Flask_Login-0.6.3.dist-info/METADATA,sha256=AUSHR5Po6-Cwmz1KBrAZbTzR-iVVFvtb2NQKYl7UuAU,5799
|
||||
Flask_Login-0.6.3.dist-info/RECORD,,
|
||||
Flask_Login-0.6.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
Flask_Login-0.6.3.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
||||
Flask_Login-0.6.3.dist-info/top_level.txt,sha256=OuXmIpiFnXLvW-iBbW2km7ZIy5EZvwSBnYaOC3Kt7j8,12
|
||||
flask_login/__about__.py,sha256=Kkp5e9mV9G7vK_FqZof-g9RFmyyBzq1gge5aKXgvilE,389
|
||||
flask_login/__init__.py,sha256=wYQiQCikT_Ndp3PhOD-1gRTGCrUPIE-FrjQUrT9aVAg,2681
|
||||
flask_login/__pycache__/__about__.cpython-311.pyc,,
|
||||
flask_login/__pycache__/__init__.cpython-311.pyc,,
|
||||
flask_login/__pycache__/config.cpython-311.pyc,,
|
||||
flask_login/__pycache__/login_manager.cpython-311.pyc,,
|
||||
flask_login/__pycache__/mixins.cpython-311.pyc,,
|
||||
flask_login/__pycache__/signals.cpython-311.pyc,,
|
||||
flask_login/__pycache__/test_client.cpython-311.pyc,,
|
||||
flask_login/__pycache__/utils.cpython-311.pyc,,
|
||||
flask_login/config.py,sha256=YAocv18La7YGQyNY5aT7rU1GQIZnX6pvchwqx3kA9p8,1813
|
||||
flask_login/login_manager.py,sha256=h20F_iv3mqc6rIJ4-V6_XookzOUl8Rcpasua-dCByQY,20073
|
||||
flask_login/mixins.py,sha256=gPd7otMRljxw0eUhUMbHsnEBc_jK2cYdxg5KFLuJcoI,1528
|
||||
flask_login/signals.py,sha256=xCMoFHKU1RTVt1NY-Gfl0OiVKpiyNt6YJw_PsgkjY3w,2464
|
||||
flask_login/test_client.py,sha256=6mrjiBRLGJpgvvFlLypXPTBLiMp0BAN-Ft-uogqC81g,517
|
||||
flask_login/utils.py,sha256=Y1wxjCVxpYohBaQJ0ADLypQ-VvBNycwG-gVXFF7k99I,14021
|
||||
5
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/WHEEL
Normal file
5
venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.41.3)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
flask_login
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
20
venv/Lib/site-packages/Flask_Migrate-4.1.0.dist-info/LICENSE
Normal file
20
venv/Lib/site-packages/Flask_Migrate-4.1.0.dist-info/LICENSE
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Miguel Grinberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,91 @@
|
||||
Metadata-Version: 2.2
|
||||
Name: Flask-Migrate
|
||||
Version: 4.1.0
|
||||
Summary: SQLAlchemy database migrations for Flask applications using Alembic.
|
||||
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
|
||||
License: MIT
|
||||
Project-URL: Homepage, https://github.com/miguelgrinberg/flask-migrate
|
||||
Project-URL: Bug Tracker, https://github.com/miguelgrinberg/flask-migrate/issues
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
Requires-Dist: Flask>=0.9
|
||||
Requires-Dist: Flask-SQLAlchemy>=1.0
|
||||
Requires-Dist: alembic>=1.9.0
|
||||
Provides-Extra: dev
|
||||
Requires-Dist: tox; extra == "dev"
|
||||
Requires-Dist: flake8; extra == "dev"
|
||||
Requires-Dist: pytest; extra == "dev"
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: sphinx; extra == "docs"
|
||||
|
||||
Flask-Migrate
|
||||
=============
|
||||
|
||||
[](https://github.com/miguelgrinberg/flask-migrate/actions)
|
||||
|
||||
Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic. The database operations are provided as command-line arguments under the `flask db` command.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install Flask-Migrate with `pip`:
|
||||
|
||||
pip install Flask-Migrate
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
This is an example application that handles database migrations through Flask-Migrate:
|
||||
|
||||
```python
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
migrate = Migrate(app, db)
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128))
|
||||
```
|
||||
|
||||
With the above application you can create the database or enable migrations if the database already exists with the following command:
|
||||
|
||||
$ flask db init
|
||||
|
||||
Note that the `FLASK_APP` environment variable must be set according to the Flask documentation for this command to work. This will add a `migrations` folder to your application. The contents of this folder need to be added to version control along with your other source files.
|
||||
|
||||
You can then generate an initial migration:
|
||||
|
||||
$ flask db migrate
|
||||
|
||||
The migration script needs to be reviewed and edited, as Alembic currently does not detect every change you make to your models. In particular, Alembic is currently unable to detect indexes. Once finalized, the migration script also needs to be added to version control.
|
||||
|
||||
Then you can apply the migration to the database:
|
||||
|
||||
$ flask db upgrade
|
||||
|
||||
Then each time the database models change repeat the `migrate` and `upgrade` commands.
|
||||
|
||||
To sync the database in another system just refresh the `migrations` folder from source control and run the `upgrade` command.
|
||||
|
||||
To see all the commands that are available run this command:
|
||||
|
||||
$ flask db --help
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
- [Documentation](http://flask-migrate.readthedocs.io/en/latest/)
|
||||
- [pypi](https://pypi.python.org/pypi/Flask-Migrate)
|
||||
- [Change Log](https://github.com/miguelgrinberg/Flask-Migrate/blob/master/CHANGES.md)
|
||||
31
venv/Lib/site-packages/Flask_Migrate-4.1.0.dist-info/RECORD
Normal file
31
venv/Lib/site-packages/Flask_Migrate-4.1.0.dist-info/RECORD
Normal file
@@ -0,0 +1,31 @@
|
||||
Flask_Migrate-4.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Flask_Migrate-4.1.0.dist-info/LICENSE,sha256=kfkXGlJQvKy3Y__6tAJ8ynIp1HQfeROXhL8jZU1d-DI,1082
|
||||
Flask_Migrate-4.1.0.dist-info/METADATA,sha256=jifIy8PzfDzjuCEeKLDKRJA8O56KOgLfj3s2lmzZA-8,3289
|
||||
Flask_Migrate-4.1.0.dist-info/RECORD,,
|
||||
Flask_Migrate-4.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
Flask_Migrate-4.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
||||
Flask_Migrate-4.1.0.dist-info/top_level.txt,sha256=jLoPgiMG6oR4ugNteXn3IHskVVIyIXVStZOVq-AWLdU,14
|
||||
flask_migrate/__init__.py,sha256=JMySGA55Y8Gxy3HviWu7qq5rPUNQBWc2NID2OicpDyw,10082
|
||||
flask_migrate/__pycache__/__init__.cpython-311.pyc,,
|
||||
flask_migrate/__pycache__/cli.cpython-311.pyc,,
|
||||
flask_migrate/cli.py,sha256=IxrxBSC82S5sPfWac8Qg83_FVsRvqTYtCG7HRyMW8RU,11097
|
||||
flask_migrate/templates/aioflask-multidb/README,sha256=Ek4cJqTaxneVjtkue--BXMlfpfp3MmJRjqoZvnSizww,43
|
||||
flask_migrate/templates/aioflask-multidb/__pycache__/env.cpython-311.pyc,,
|
||||
flask_migrate/templates/aioflask-multidb/alembic.ini.mako,sha256=SjYEmJKzz6K8QfuZWtLJAJWcCKOdRbfUhsVlpgv8ock,857
|
||||
flask_migrate/templates/aioflask-multidb/env.py,sha256=UcjeqkAbyUjTkuQFmCFPG7QOvqhco8-uGp8QEbto0T8,6573
|
||||
flask_migrate/templates/aioflask-multidb/script.py.mako,sha256=198VPxVEN3NZ3vHcRuCxSoI4XnOYirGWt01qkbPKoJw,1246
|
||||
flask_migrate/templates/aioflask/README,sha256=KKqWGl4YC2RqdOdq-y6quTDW0b7D_UZNHuM8glM1L-c,44
|
||||
flask_migrate/templates/aioflask/__pycache__/env.cpython-311.pyc,,
|
||||
flask_migrate/templates/aioflask/alembic.ini.mako,sha256=SjYEmJKzz6K8QfuZWtLJAJWcCKOdRbfUhsVlpgv8ock,857
|
||||
flask_migrate/templates/aioflask/env.py,sha256=m6ZtBhdpwuq89vVeLTWmNT-1NfJZqarC_hsquCdR9bw,3478
|
||||
flask_migrate/templates/aioflask/script.py.mako,sha256=8_xgA-gm_OhehnO7CiIijWgnm00ZlszEHtIHrAYFJl0,494
|
||||
flask_migrate/templates/flask-multidb/README,sha256=AfiP5foaV2odZxXxuUuSIS6YhkIpR7CsOo2mpuxwHdc,40
|
||||
flask_migrate/templates/flask-multidb/__pycache__/env.cpython-311.pyc,,
|
||||
flask_migrate/templates/flask-multidb/alembic.ini.mako,sha256=SjYEmJKzz6K8QfuZWtLJAJWcCKOdRbfUhsVlpgv8ock,857
|
||||
flask_migrate/templates/flask-multidb/env.py,sha256=F44iqsAxLTVBN_zD8CMUkdE7Aub4niHMmo5wl9mY4Uw,6190
|
||||
flask_migrate/templates/flask-multidb/script.py.mako,sha256=198VPxVEN3NZ3vHcRuCxSoI4XnOYirGWt01qkbPKoJw,1246
|
||||
flask_migrate/templates/flask/README,sha256=JL0NrjOrscPcKgRmQh1R3hlv1_rohDot0TvpmdM27Jk,41
|
||||
flask_migrate/templates/flask/__pycache__/env.cpython-311.pyc,,
|
||||
flask_migrate/templates/flask/alembic.ini.mako,sha256=SjYEmJKzz6K8QfuZWtLJAJWcCKOdRbfUhsVlpgv8ock,857
|
||||
flask_migrate/templates/flask/env.py,sha256=ibK1hsdOsOBzXNU2yQoAIza7f_EFzaVSWwON_NSpNzQ,3344
|
||||
flask_migrate/templates/flask/script.py.mako,sha256=8_xgA-gm_OhehnO7CiIijWgnm00ZlszEHtIHrAYFJl0,494
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.8.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
flask_migrate
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,28 @@
|
||||
Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
92
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA
Normal file
92
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/METADATA
Normal file
@@ -0,0 +1,92 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: MarkupSafe
|
||||
Version: 3.0.2
|
||||
Summary: Safely add untrusted strings to HTML/XML markup.
|
||||
Maintainer-email: Pallets <contact@palletsprojects.com>
|
||||
License: Copyright 2010 Pallets
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Project-URL: Donate, https://palletsprojects.com/donate
|
||||
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
||||
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
||||
Project-URL: Source, https://github.com/pallets/markupsafe/
|
||||
Project-URL: Chat, https://discord.gg/pallets
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE.txt
|
||||
|
||||
# MarkupSafe
|
||||
|
||||
MarkupSafe implements a text object that escapes characters so it is
|
||||
safe to use in HTML and XML. Characters that have special meanings are
|
||||
replaced so that they display as the actual characters. This mitigates
|
||||
injection attacks, meaning untrusted user input can safely be displayed
|
||||
on a page.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
```pycon
|
||||
>>> from markupsafe import Markup, escape
|
||||
|
||||
>>> # escape replaces special characters and wraps in Markup
|
||||
>>> escape("<script>alert(document.cookie);</script>")
|
||||
Markup('<script>alert(document.cookie);</script>')
|
||||
|
||||
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
||||
>>> Markup("<strong>Hello</strong>")
|
||||
Markup('<strong>hello</strong>')
|
||||
|
||||
>>> escape(Markup("<strong>Hello</strong>"))
|
||||
Markup('<strong>hello</strong>')
|
||||
|
||||
>>> # Markup is a str subclass
|
||||
>>> # methods and operators escape their arguments
|
||||
>>> template = Markup("Hello <em>{name}</em>")
|
||||
>>> template.format(name='"World"')
|
||||
Markup('Hello <em>"World"</em>')
|
||||
```
|
||||
|
||||
## Donate
|
||||
|
||||
The Pallets organization develops and supports MarkupSafe and other
|
||||
popular packages. In order to grow the community of contributors and
|
||||
users, and allow the maintainers to devote more time to the projects,
|
||||
[please donate today][].
|
||||
|
||||
[please donate today]: https://palletsprojects.com/donate
|
||||
14
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD
Normal file
14
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/RECORD
Normal file
@@ -0,0 +1,14 @@
|
||||
MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
|
||||
MarkupSafe-3.0.2.dist-info/METADATA,sha256=nhoabjupBG41j_JxPCJ3ylgrZ6Fx8oMCFbiLF9Kafqc,4067
|
||||
MarkupSafe-3.0.2.dist-info/RECORD,,
|
||||
MarkupSafe-3.0.2.dist-info/WHEEL,sha256=tE2EWZPEv-G0fjAlUUz7IGM64246YKD9fpv4HcsDMkk,101
|
||||
MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
||||
markupsafe/__init__.py,sha256=pREerPwvinB62tNCMOwqxBS2YHV6R52Wcq1d-rB4Z5o,13609
|
||||
markupsafe/__pycache__/__init__.cpython-311.pyc,,
|
||||
markupsafe/__pycache__/_native.cpython-311.pyc,,
|
||||
markupsafe/_native.py,sha256=2ptkJ40yCcp9kq3L1NqpgjfpZB-obniYKFFKUOkHh4Q,218
|
||||
markupsafe/_speedups.c,sha256=SglUjn40ti9YgQAO--OgkSyv9tXq9vvaHyVhQows4Ok,4353
|
||||
markupsafe/_speedups.cp311-win_amd64.pyd,sha256=-5qfBr0xMpiTRlH9hFg_7Go9PHi7z5guMzmbbmZI3Xw,13312
|
||||
markupsafe/_speedups.pyi,sha256=LSDmXYOefH4HVpAXuL8sl7AttLw0oXh1njVoVZp2wqQ,42
|
||||
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
5
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL
Normal file
5
venv/Lib/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (75.2.0)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp311-cp311-win_amd64
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
markupsafe
|
||||
Binary file not shown.
222
venv/Lib/site-packages/_distutils_hack/__init__.py
Normal file
222
venv/Lib/site-packages/_distutils_hack/__init__.py
Normal file
@@ -0,0 +1,222 @@
|
||||
# don't import any costly modules
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
||||
|
||||
|
||||
def warn_distutils_present():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
if is_pypy and sys.version_info < (3, 7):
|
||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
||||
"using distutils directly, ensure that setuptools is installed in the "
|
||||
"traditional way (e.g. not an editable install), and/or make sure "
|
||||
"that setuptools is always imported before distutils."
|
||||
)
|
||||
|
||||
|
||||
def clear_distutils():
|
||||
if 'distutils' not in sys.modules:
|
||||
return
|
||||
import warnings
|
||||
|
||||
warnings.warn("Setuptools is replacing distutils.")
|
||||
mods = [
|
||||
name
|
||||
for name in sys.modules
|
||||
if name == "distutils" or name.startswith("distutils.")
|
||||
]
|
||||
for name in mods:
|
||||
del sys.modules[name]
|
||||
|
||||
|
||||
def enabled():
|
||||
"""
|
||||
Allow selection of distutils by environment variable.
|
||||
"""
|
||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
||||
return which == 'local'
|
||||
|
||||
|
||||
def ensure_local_distutils():
|
||||
import importlib
|
||||
|
||||
clear_distutils()
|
||||
|
||||
# With the DistutilsMetaFinder in place,
|
||||
# perform an import to cause distutils to be
|
||||
# loaded from setuptools._distutils. Ref #2906.
|
||||
with shim():
|
||||
importlib.import_module('distutils')
|
||||
|
||||
# check that submodules load as expected
|
||||
core = importlib.import_module('distutils.core')
|
||||
assert '_distutils' in core.__file__, core.__file__
|
||||
assert 'setuptools._distutils.log' not in sys.modules
|
||||
|
||||
|
||||
def do_override():
|
||||
"""
|
||||
Ensure that the local copy of distutils is preferred over stdlib.
|
||||
|
||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
||||
for more motivation.
|
||||
"""
|
||||
if enabled():
|
||||
warn_distutils_present()
|
||||
ensure_local_distutils()
|
||||
|
||||
|
||||
class _TrivialRe:
|
||||
def __init__(self, *patterns):
|
||||
self._patterns = patterns
|
||||
|
||||
def match(self, string):
|
||||
return all(pat in string for pat in self._patterns)
|
||||
|
||||
|
||||
class DistutilsMetaFinder:
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
# optimization: only consider top level modules and those
|
||||
# found in the CPython test suite.
|
||||
if path is not None and not fullname.startswith('test.'):
|
||||
return
|
||||
|
||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
||||
method = getattr(self, method_name, lambda: None)
|
||||
return method()
|
||||
|
||||
def spec_for_distutils(self):
|
||||
if self.is_cpython():
|
||||
return
|
||||
|
||||
import importlib
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
mod = importlib.import_module('setuptools._distutils')
|
||||
except Exception:
|
||||
# There are a couple of cases where setuptools._distutils
|
||||
# may not be present:
|
||||
# - An older Setuptools without a local distutils is
|
||||
# taking precedence. Ref #2957.
|
||||
# - Path manipulation during sitecustomize removes
|
||||
# setuptools from the path but only after the hook
|
||||
# has been loaded. Ref #2980.
|
||||
# In either case, fall back to stdlib behavior.
|
||||
return
|
||||
|
||||
class DistutilsLoader(importlib.abc.Loader):
|
||||
def create_module(self, spec):
|
||||
mod.__name__ = 'distutils'
|
||||
return mod
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
return importlib.util.spec_from_loader(
|
||||
'distutils', DistutilsLoader(), origin=mod.__file__
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def is_cpython():
|
||||
"""
|
||||
Suppress supplying distutils for CPython (build and tests).
|
||||
Ref #2965 and #3007.
|
||||
"""
|
||||
return os.path.isfile('pybuilddir.txt')
|
||||
|
||||
def spec_for_pip(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running under pip.
|
||||
See pypa/pip#8761 for rationale.
|
||||
"""
|
||||
if self.pip_imported_during_build():
|
||||
return
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
@classmethod
|
||||
def pip_imported_during_build(cls):
|
||||
"""
|
||||
Detect if pip is being imported in a build script. Ref #2355.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
return any(
|
||||
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def frame_file_is_setup(frame):
|
||||
"""
|
||||
Return True if the indicated frame suggests a setup.py file.
|
||||
"""
|
||||
# some frames may not have __file__ (#2940)
|
||||
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
||||
|
||||
def spec_for_sensitive_tests(self):
|
||||
"""
|
||||
Ensure stdlib distutils when running select tests under CPython.
|
||||
|
||||
python/cpython#91169
|
||||
"""
|
||||
clear_distutils()
|
||||
self.spec_for_distutils = lambda: None
|
||||
|
||||
sensitive_tests = (
|
||||
[
|
||||
'test.test_distutils',
|
||||
'test.test_peg_generator',
|
||||
'test.test_importlib',
|
||||
]
|
||||
if sys.version_info < (3, 10)
|
||||
else [
|
||||
'test.test_distutils',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
for name in DistutilsMetaFinder.sensitive_tests:
|
||||
setattr(
|
||||
DistutilsMetaFinder,
|
||||
f'spec_for_{name}',
|
||||
DistutilsMetaFinder.spec_for_sensitive_tests,
|
||||
)
|
||||
|
||||
|
||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
||||
|
||||
|
||||
def add_shim():
|
||||
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
||||
|
||||
|
||||
class shim:
|
||||
def __enter__(self):
|
||||
insert_shim()
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
remove_shim()
|
||||
|
||||
|
||||
def insert_shim():
|
||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
||||
|
||||
|
||||
def remove_shim():
|
||||
try:
|
||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
||||
except ValueError:
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
1
venv/Lib/site-packages/_distutils_hack/override.py
Normal file
1
venv/Lib/site-packages/_distutils_hack/override.py
Normal file
@@ -0,0 +1 @@
|
||||
__import__('_distutils_hack').do_override()
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
140
venv/Lib/site-packages/alembic-1.16.4.dist-info/METADATA
Normal file
140
venv/Lib/site-packages/alembic-1.16.4.dist-info/METADATA
Normal file
@@ -0,0 +1,140 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: alembic
|
||||
Version: 1.16.4
|
||||
Summary: A database migration tool for SQLAlchemy.
|
||||
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/
|
||||
Project-URL: Issue Tracker, https://github.com/sqlalchemy/alembic/issues/
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.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.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
Requires-Dist: SQLAlchemy>=1.4.0
|
||||
Requires-Dist: Mako
|
||||
Requires-Dist: typing-extensions>=4.12
|
||||
Requires-Dist: tomli; python_version < "3.11"
|
||||
Provides-Extra: tz
|
||||
Requires-Dist: tzdata; extra == "tz"
|
||||
Dynamic: license-file
|
||||
|
||||
Alembic is a database migrations tool written by the author
|
||||
of `SQLAlchemy <http://www.sqlalchemy.org>`_. A migrations tool
|
||||
offers the following functionality:
|
||||
|
||||
* Can emit ALTER statements to a database in order to change
|
||||
the structure of tables and other constructs
|
||||
* Provides a system whereby "migration scripts" may be constructed;
|
||||
each script indicates a particular series of steps that can "upgrade" a
|
||||
target database to a new version, and optionally a series of steps that can
|
||||
"downgrade" similarly, doing the same steps in reverse.
|
||||
* Allows the scripts to execute in some sequential manner.
|
||||
|
||||
The goals of Alembic are:
|
||||
|
||||
* Very open ended and transparent configuration and operation. A new
|
||||
Alembic environment is generated from a set of templates which is selected
|
||||
among a set of options when setup first occurs. The templates then deposit a
|
||||
series of scripts that define fully how database connectivity is established
|
||||
and how migration scripts are invoked; the migration scripts themselves are
|
||||
generated from a template within that series of scripts. The scripts can
|
||||
then be further customized to define exactly how databases will be
|
||||
interacted with and what structure new migration files should take.
|
||||
* Full support for transactional DDL. The default scripts ensure that all
|
||||
migrations occur within a transaction - for those databases which support
|
||||
this (Postgresql, Microsoft SQL Server), migrations can be tested with no
|
||||
need to manually undo changes upon failure.
|
||||
* Minimalist script construction. Basic operations like renaming
|
||||
tables/columns, adding/removing columns, changing column attributes can be
|
||||
performed through one line commands like alter_column(), rename_table(),
|
||||
add_constraint(). There is no need to recreate full SQLAlchemy Table
|
||||
structures for simple operations like these - the functions themselves
|
||||
generate minimalist schema structures behind the scenes to achieve the given
|
||||
DDL sequence.
|
||||
* "auto generation" of migrations. While real world migrations are far more
|
||||
complex than what can be automatically determined, Alembic can still
|
||||
eliminate the initial grunt work in generating new migration directives
|
||||
from an altered schema. The ``--autogenerate`` feature will inspect the
|
||||
current status of a database using SQLAlchemy's schema inspection
|
||||
capabilities, compare it to the current state of the database model as
|
||||
specified in Python, and generate a series of "candidate" migrations,
|
||||
rendering them into a new migration script as Python directives. The
|
||||
developer then edits the new file, adding additional directives and data
|
||||
migrations as needed, to produce a finished migration. Table and column
|
||||
level changes can be detected, with constraints and indexes to follow as
|
||||
well.
|
||||
* Full support for migrations generated as SQL scripts. Those of us who
|
||||
work in corporate environments know that direct access to DDL commands on a
|
||||
production database is a rare privilege, and DBAs want textual SQL scripts.
|
||||
Alembic's usage model and commands are oriented towards being able to run a
|
||||
series of migrations into a textual output file as easily as it runs them
|
||||
directly to a database. Care must be taken in this mode to not invoke other
|
||||
operations that rely upon in-memory SELECTs of rows - Alembic tries to
|
||||
provide helper constructs like bulk_insert() to help with data-oriented
|
||||
operations that are compatible with script-based DDL.
|
||||
* Non-linear, dependency-graph versioning. Scripts are given UUID
|
||||
identifiers similarly to a DVCS, and the linkage of one script to the next
|
||||
is achieved via human-editable markers within the scripts themselves.
|
||||
The structure of a set of migration files is considered as a
|
||||
directed-acyclic graph, meaning any migration file can be dependent
|
||||
on any other arbitrary set of migration files, or none at
|
||||
all. Through this open-ended system, migration files can be organized
|
||||
into branches, multiple roots, and mergepoints, without restriction.
|
||||
Commands are provided to produce new branches, roots, and merges of
|
||||
branches automatically.
|
||||
* Provide a library of ALTER constructs that can be used by any SQLAlchemy
|
||||
application. The DDL constructs build upon SQLAlchemy's own DDLElement base
|
||||
and can be used standalone by any application or script.
|
||||
* At long last, bring SQLite and its inability to ALTER things into the fold,
|
||||
but in such a way that SQLite's very special workflow needs are accommodated
|
||||
in an explicit way that makes the most of a bad situation, through the
|
||||
concept of a "batch" migration, where multiple changes to a table can
|
||||
be batched together to form a series of instructions for a single, subsequent
|
||||
"move-and-copy" workflow. You can even use "move-and-copy" workflow for
|
||||
other databases, if you want to recreate a table in the background
|
||||
on a busy system.
|
||||
|
||||
Documentation and status of Alembic is at https://alembic.sqlalchemy.org/
|
||||
|
||||
The SQLAlchemy Project
|
||||
======================
|
||||
|
||||
Alembic is part of the `SQLAlchemy Project <https://www.sqlalchemy.org>`_ and
|
||||
adheres to the same standards and conventions as the core project.
|
||||
|
||||
Development / Bug reporting / Pull requests
|
||||
___________________________________________
|
||||
|
||||
Please refer to the
|
||||
`SQLAlchemy Community Guide <https://www.sqlalchemy.org/develop.html>`_ for
|
||||
guidelines on coding and participating in this project.
|
||||
|
||||
Code of Conduct
|
||||
_______________
|
||||
|
||||
Above all, SQLAlchemy places great emphasis on polite, thoughtful, and
|
||||
constructive communication between users and developers.
|
||||
Please see our current Code of Conduct at
|
||||
`Code of Conduct <https://www.sqlalchemy.org/codeofconduct.html>`_.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Alembic is distributed under the `MIT license
|
||||
<https://opensource.org/licenses/MIT>`_.
|
||||
162
venv/Lib/site-packages/alembic-1.16.4.dist-info/RECORD
Normal file
162
venv/Lib/site-packages/alembic-1.16.4.dist-info/RECORD
Normal file
@@ -0,0 +1,162 @@
|
||||
../../Scripts/alembic.exe,sha256=kg4OoHQLGwbKYDijEitu59R9DHwOxBU8iH-3haB5TFM,108431
|
||||
alembic-1.16.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
alembic-1.16.4.dist-info/METADATA,sha256=FOov3ImluFXi-Y-Zod8XhsXQ9xlB-lbP1V6WKMjhJ6c,7265
|
||||
alembic-1.16.4.dist-info/RECORD,,
|
||||
alembic-1.16.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
||||
alembic-1.16.4.dist-info/entry_points.txt,sha256=aykM30soxwGN0pB7etLc1q0cHJbL9dy46RnK9VX4LLw,48
|
||||
alembic-1.16.4.dist-info/licenses/LICENSE,sha256=NeqcNBmyYfrxvkSMT0fZJVKBv2s2tf_qVQUiJ9S6VN4,1059
|
||||
alembic-1.16.4.dist-info/top_level.txt,sha256=FwKWd5VsPFC8iQjpu1u9Cn-JnK3-V1RhUCmWqz1cl-s,8
|
||||
alembic/__init__.py,sha256=LBJqQA9P3cs5iDeqKVCK3DsoFQfdhd1cvtctowCUd0g,63
|
||||
alembic/__main__.py,sha256=373m7-TBh72JqrSMYviGrxCHZo-cnweM8AGF8A22PmY,78
|
||||
alembic/__pycache__/__init__.cpython-311.pyc,,
|
||||
alembic/__pycache__/__main__.cpython-311.pyc,,
|
||||
alembic/__pycache__/command.cpython-311.pyc,,
|
||||
alembic/__pycache__/config.cpython-311.pyc,,
|
||||
alembic/__pycache__/context.cpython-311.pyc,,
|
||||
alembic/__pycache__/environment.cpython-311.pyc,,
|
||||
alembic/__pycache__/migration.cpython-311.pyc,,
|
||||
alembic/__pycache__/op.cpython-311.pyc,,
|
||||
alembic/autogenerate/__init__.py,sha256=ntmUTXhjLm4_zmqIwyVaECdpPDn6_u1yM9vYk6-553E,543
|
||||
alembic/autogenerate/__pycache__/__init__.cpython-311.pyc,,
|
||||
alembic/autogenerate/__pycache__/api.cpython-311.pyc,,
|
||||
alembic/autogenerate/__pycache__/compare.cpython-311.pyc,,
|
||||
alembic/autogenerate/__pycache__/render.cpython-311.pyc,,
|
||||
alembic/autogenerate/__pycache__/rewriter.cpython-311.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=SbOhoGuXlh_vVpK3SD8LFUG_BNH5HDLv6Q9949HCiXA,34124
|
||||
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-311.pyc,,
|
||||
alembic/ddl/__pycache__/_autogen.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/base.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/impl.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/mssql.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/mysql.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/oracle.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/postgresql.cpython-311.pyc,,
|
||||
alembic/ddl/__pycache__/sqlite.cpython-311.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=ujY4xDh13KgiFNRe3vUcLquie0ih80MYBUcogCBPdSc,17358
|
||||
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-311.pyc,,
|
||||
alembic/operations/__pycache__/base.cpython-311.pyc,,
|
||||
alembic/operations/__pycache__/batch.cpython-311.pyc,,
|
||||
alembic/operations/__pycache__/ops.cpython-311.pyc,,
|
||||
alembic/operations/__pycache__/schemaobj.cpython-311.pyc,,
|
||||
alembic/operations/__pycache__/toimpl.cpython-311.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-311.pyc,,
|
||||
alembic/runtime/__pycache__/environment.cpython-311.pyc,,
|
||||
alembic/runtime/__pycache__/migration.cpython-311.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-311.pyc,,
|
||||
alembic/script/__pycache__/base.cpython-311.pyc,,
|
||||
alembic/script/__pycache__/revision.cpython-311.pyc,,
|
||||
alembic/script/__pycache__/write_hooks.cpython-311.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-311.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-311.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-311.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-311.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-311.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-311.pyc,,
|
||||
alembic/testing/__pycache__/assertions.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/env.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/fixtures.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/requirements.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/schemacompare.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/util.cpython-311.pyc,,
|
||||
alembic/testing/__pycache__/warnings.cpython-311.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-311.pyc,,
|
||||
alembic/testing/plugin/__pycache__/bootstrap.cpython-311.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-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/_autogen_fixtures.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_autogen_comments.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_autogen_computed.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_autogen_diffs.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_autogen_fks.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_autogen_identity.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_environment.cpython-311.pyc,,
|
||||
alembic/testing/suite/__pycache__/test_op.cpython-311.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-311.pyc,,
|
||||
alembic/util/__pycache__/compat.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/editor.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/exc.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/langhelpers.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/messaging.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/pyfiles.cpython-311.pyc,,
|
||||
alembic/util/__pycache__/sqla_compat.cpython-311.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
|
||||
5
venv/Lib/site-packages/alembic-1.16.4.dist-info/WHEEL
Normal file
5
venv/Lib/site-packages/alembic-1.16.4.dist-info/WHEEL
Normal file
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (80.9.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[console_scripts]
|
||||
alembic = alembic.config:main
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1 @@
|
||||
alembic
|
||||
4
venv/Lib/site-packages/alembic/__init__.py
Normal file
4
venv/Lib/site-packages/alembic/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import context
|
||||
from . import op
|
||||
|
||||
__version__ = "1.16.4"
|
||||
4
venv/Lib/site-packages/alembic/__main__.py
Normal file
4
venv/Lib/site-packages/alembic/__main__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .config import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(prog="alembic")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/alembic/__pycache__/op.cpython-311.pyc
Normal file
BIN
venv/Lib/site-packages/alembic/__pycache__/op.cpython-311.pyc
Normal file
Binary file not shown.
10
venv/Lib/site-packages/alembic/autogenerate/__init__.py
Normal file
10
venv/Lib/site-packages/alembic/autogenerate/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
650
venv/Lib/site-packages/alembic/autogenerate/api.py
Normal file
650
venv/Lib/site-packages/alembic/autogenerate/api.py
Normal file
@@ -0,0 +1,650 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import inspect
|
||||
|
||||
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."""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.engine import Dialect
|
||||
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
|
||||
from ..operations.ops import MigrationScript
|
||||
from ..operations.ops import UpgradeOps
|
||||
from ..runtime.environment import NameFilterParentNames
|
||||
from ..runtime.environment import NameFilterType
|
||||
from ..runtime.environment import ProcessRevisionDirectiveFn
|
||||
from ..runtime.environment import RenderItemFn
|
||||
from ..runtime.migration import MigrationContext
|
||||
from ..script.base import Script
|
||||
from ..script.base import ScriptDirectory
|
||||
from ..script.revision import _GetRevArg
|
||||
|
||||
|
||||
def compare_metadata(context: MigrationContext, metadata: MetaData) -> Any:
|
||||
"""Compare a database schema to that given in a
|
||||
:class:`~sqlalchemy.schema.MetaData` instance.
|
||||
|
||||
The database connection is presented in the context
|
||||
of a :class:`.MigrationContext` object, which
|
||||
provides database connectivity as well as optional
|
||||
comparison functions to use for datatypes and
|
||||
server defaults - see the "autogenerate" arguments
|
||||
at :meth:`.EnvironmentContext.configure`
|
||||
for details on these.
|
||||
|
||||
The return format is a list of "diff" directives,
|
||||
each representing individual differences::
|
||||
|
||||
from alembic.migration import MigrationContext
|
||||
from alembic.autogenerate import compare_metadata
|
||||
from sqlalchemy import (
|
||||
create_engine,
|
||||
MetaData,
|
||||
Column,
|
||||
Integer,
|
||||
String,
|
||||
Table,
|
||||
text,
|
||||
)
|
||||
import pprint
|
||||
|
||||
engine = create_engine("sqlite://")
|
||||
|
||||
with engine.begin() as conn:
|
||||
conn.execute(
|
||||
text(
|
||||
'''
|
||||
create table foo (
|
||||
id integer not null primary key,
|
||||
old_data varchar,
|
||||
x integer
|
||||
)
|
||||
'''
|
||||
)
|
||||
)
|
||||
conn.execute(text("create table bar (data varchar)"))
|
||||
|
||||
metadata = MetaData()
|
||||
Table(
|
||||
"foo",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True),
|
||||
Column("data", Integer),
|
||||
Column("x", Integer, nullable=False),
|
||||
)
|
||||
Table("bat", metadata, Column("info", String))
|
||||
|
||||
mc = MigrationContext.configure(engine.connect())
|
||||
|
||||
diff = compare_metadata(mc, metadata)
|
||||
pprint.pprint(diff, indent=2, width=20)
|
||||
|
||||
Output::
|
||||
|
||||
[
|
||||
(
|
||||
"add_table",
|
||||
Table(
|
||||
"bat",
|
||||
MetaData(),
|
||||
Column("info", String(), table=<bat>),
|
||||
schema=None,
|
||||
),
|
||||
),
|
||||
(
|
||||
"remove_table",
|
||||
Table(
|
||||
"bar",
|
||||
MetaData(),
|
||||
Column("data", VARCHAR(), table=<bar>),
|
||||
schema=None,
|
||||
),
|
||||
),
|
||||
(
|
||||
"add_column",
|
||||
None,
|
||||
"foo",
|
||||
Column("data", Integer(), table=<foo>),
|
||||
),
|
||||
[
|
||||
(
|
||||
"modify_nullable",
|
||||
None,
|
||||
"foo",
|
||||
"x",
|
||||
{
|
||||
"existing_comment": None,
|
||||
"existing_server_default": False,
|
||||
"existing_type": INTEGER(),
|
||||
},
|
||||
True,
|
||||
False,
|
||||
)
|
||||
],
|
||||
(
|
||||
"remove_column",
|
||||
None,
|
||||
"foo",
|
||||
Column("old_data", VARCHAR(), table=<foo>),
|
||||
),
|
||||
]
|
||||
|
||||
:param context: a :class:`.MigrationContext`
|
||||
instance.
|
||||
:param metadata: a :class:`~sqlalchemy.schema.MetaData`
|
||||
instance.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.produce_migrations` - produces a :class:`.MigrationScript`
|
||||
structure based on metadata comparison.
|
||||
|
||||
"""
|
||||
|
||||
migration_script = produce_migrations(context, metadata)
|
||||
assert migration_script.upgrade_ops is not None
|
||||
return migration_script.upgrade_ops.as_diffs()
|
||||
|
||||
|
||||
def produce_migrations(
|
||||
context: MigrationContext, metadata: MetaData
|
||||
) -> MigrationScript:
|
||||
"""Produce a :class:`.MigrationScript` structure based on schema
|
||||
comparison.
|
||||
|
||||
This function does essentially what :func:`.compare_metadata` does,
|
||||
but then runs the resulting list of diffs to produce the full
|
||||
:class:`.MigrationScript` object. For an example of what this looks like,
|
||||
see the example in :ref:`customizing_revision`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:func:`.compare_metadata` - returns more fundamental "diff"
|
||||
data from comparing a schema.
|
||||
|
||||
"""
|
||||
|
||||
autogen_context = AutogenContext(context, metadata=metadata)
|
||||
|
||||
migration_script = ops.MigrationScript(
|
||||
rev_id=None,
|
||||
upgrade_ops=ops.UpgradeOps([]),
|
||||
downgrade_ops=ops.DowngradeOps([]),
|
||||
)
|
||||
|
||||
compare._populate_migration_script(autogen_context, migration_script)
|
||||
|
||||
return migration_script
|
||||
|
||||
|
||||
def render_python_code(
|
||||
up_or_down_op: Union[UpgradeOps, DowngradeOps],
|
||||
sqlalchemy_module_prefix: str = "sa.",
|
||||
alembic_module_prefix: str = "op.",
|
||||
render_as_batch: bool = False,
|
||||
imports: Sequence[str] = (),
|
||||
render_item: Optional[RenderItemFn] = None,
|
||||
migration_context: Optional[MigrationContext] = None,
|
||||
user_module_prefix: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Render Python code given an :class:`.UpgradeOps` or
|
||||
:class:`.DowngradeOps` object.
|
||||
|
||||
This is a convenience function that can be used to test the
|
||||
autogenerate output of a user-defined :class:`.MigrationScript` structure.
|
||||
|
||||
:param up_or_down_op: :class:`.UpgradeOps` or :class:`.DowngradeOps` object
|
||||
:param sqlalchemy_module_prefix: module prefix for SQLAlchemy objects
|
||||
:param alembic_module_prefix: module prefix for Alembic constructs
|
||||
:param render_as_batch: use "batch operations" style for rendering
|
||||
:param imports: sequence of import symbols to add
|
||||
:param render_item: callable to render items
|
||||
:param migration_context: optional :class:`.MigrationContext`
|
||||
:param user_module_prefix: optional string prefix for user-defined types
|
||||
|
||||
.. versionadded:: 1.11.0
|
||||
|
||||
"""
|
||||
opts = {
|
||||
"sqlalchemy_module_prefix": sqlalchemy_module_prefix,
|
||||
"alembic_module_prefix": alembic_module_prefix,
|
||||
"render_item": render_item,
|
||||
"render_as_batch": render_as_batch,
|
||||
"user_module_prefix": user_module_prefix,
|
||||
}
|
||||
|
||||
if migration_context is None:
|
||||
from ..runtime.migration import MigrationContext
|
||||
from sqlalchemy.engine.default import DefaultDialect
|
||||
|
||||
migration_context = MigrationContext.configure(
|
||||
dialect=DefaultDialect()
|
||||
)
|
||||
|
||||
autogen_context = AutogenContext(migration_context, opts=opts)
|
||||
autogen_context.imports = set(imports)
|
||||
return render._indent(
|
||||
render._render_cmd_body(up_or_down_op, autogen_context)
|
||||
)
|
||||
|
||||
|
||||
def _render_migration_diffs(
|
||||
context: MigrationContext, template_args: Dict[Any, Any]
|
||||
) -> None:
|
||||
"""legacy, used by test_autogen_composition at the moment"""
|
||||
|
||||
autogen_context = AutogenContext(context)
|
||||
|
||||
upgrade_ops = ops.UpgradeOps([])
|
||||
compare._produce_net_changes(autogen_context, upgrade_ops)
|
||||
|
||||
migration_script = ops.MigrationScript(
|
||||
rev_id=None,
|
||||
upgrade_ops=upgrade_ops,
|
||||
downgrade_ops=upgrade_ops.reverse(),
|
||||
)
|
||||
|
||||
render._render_python_into_templatevars(
|
||||
autogen_context, migration_script, template_args
|
||||
)
|
||||
|
||||
|
||||
class AutogenContext:
|
||||
"""Maintains configuration and state that's specific to an
|
||||
autogenerate operation."""
|
||||
|
||||
metadata: Union[MetaData, Sequence[MetaData], None] = None
|
||||
"""The :class:`~sqlalchemy.schema.MetaData` object
|
||||
representing the destination.
|
||||
|
||||
This object is the one that is passed within ``env.py``
|
||||
to the :paramref:`.EnvironmentContext.configure.target_metadata`
|
||||
parameter. It represents the structure of :class:`.Table` and other
|
||||
objects as stated in the current database model, and represents the
|
||||
destination structure for the database being examined.
|
||||
|
||||
While the :class:`~sqlalchemy.schema.MetaData` object is primarily
|
||||
known as a collection of :class:`~sqlalchemy.schema.Table` objects,
|
||||
it also has an :attr:`~sqlalchemy.schema.MetaData.info` dictionary
|
||||
that may be used by end-user schemes to store additional schema-level
|
||||
objects that are to be compared in custom autogeneration schemes.
|
||||
|
||||
"""
|
||||
|
||||
connection: Optional[Connection] = None
|
||||
"""The :class:`~sqlalchemy.engine.base.Connection` object currently
|
||||
connected to the database backend being compared.
|
||||
|
||||
This is obtained from the :attr:`.MigrationContext.bind` and is
|
||||
ultimately set up in the ``env.py`` script.
|
||||
|
||||
"""
|
||||
|
||||
dialect: Optional[Dialect] = None
|
||||
"""The :class:`~sqlalchemy.engine.Dialect` object currently in use.
|
||||
|
||||
This is normally obtained from the
|
||||
:attr:`~sqlalchemy.engine.base.Connection.dialect` attribute.
|
||||
|
||||
"""
|
||||
|
||||
imports: Set[str] = None # type: ignore[assignment]
|
||||
"""A ``set()`` which contains string Python import directives.
|
||||
|
||||
The directives are to be rendered into the ``${imports}`` section
|
||||
of a script template. The set is normally empty and can be modified
|
||||
within hooks such as the
|
||||
:paramref:`.EnvironmentContext.configure.render_item` hook.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogen_render_types`
|
||||
|
||||
"""
|
||||
|
||||
migration_context: MigrationContext = None # type: ignore[assignment]
|
||||
"""The :class:`.MigrationContext` established by the ``env.py`` script."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
migration_context: MigrationContext,
|
||||
metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
||||
opts: Optional[Dict[str, Any]] = None,
|
||||
autogenerate: bool = True,
|
||||
) -> None:
|
||||
if (
|
||||
autogenerate
|
||||
and migration_context is not None
|
||||
and migration_context.as_sql
|
||||
):
|
||||
raise util.CommandError(
|
||||
"autogenerate can't use as_sql=True as it prevents querying "
|
||||
"the database for schema information"
|
||||
)
|
||||
|
||||
if opts is None:
|
||||
opts = migration_context.opts
|
||||
|
||||
self.metadata = metadata = (
|
||||
opts.get("target_metadata", None) if metadata is None else metadata
|
||||
)
|
||||
|
||||
if (
|
||||
autogenerate
|
||||
and metadata is None
|
||||
and migration_context is not None
|
||||
and migration_context.script is not None
|
||||
):
|
||||
raise util.CommandError(
|
||||
"Can't proceed with --autogenerate option; environment "
|
||||
"script %s does not provide "
|
||||
"a MetaData object or sequence of objects to the context."
|
||||
% (migration_context.script.env_py_location)
|
||||
)
|
||||
|
||||
include_object = opts.get("include_object", None)
|
||||
include_name = opts.get("include_name", None)
|
||||
|
||||
object_filters = []
|
||||
name_filters = []
|
||||
if include_object:
|
||||
object_filters.append(include_object)
|
||||
if include_name:
|
||||
name_filters.append(include_name)
|
||||
|
||||
self._object_filters = object_filters
|
||||
self._name_filters = name_filters
|
||||
|
||||
self.migration_context = migration_context
|
||||
if self.migration_context is not None:
|
||||
self.connection = self.migration_context.bind
|
||||
self.dialect = self.migration_context.dialect
|
||||
|
||||
self.imports = set()
|
||||
self.opts: Dict[str, Any] = opts
|
||||
self._has_batch: bool = False
|
||||
|
||||
@util.memoized_property
|
||||
def inspector(self) -> Inspector:
|
||||
if self.connection is None:
|
||||
raise TypeError(
|
||||
"can't return inspector as this "
|
||||
"AutogenContext has no database connection"
|
||||
)
|
||||
return inspect(self.connection)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _within_batch(self) -> Iterator[None]:
|
||||
self._has_batch = True
|
||||
yield
|
||||
self._has_batch = False
|
||||
|
||||
def run_name_filters(
|
||||
self,
|
||||
name: Optional[str],
|
||||
type_: NameFilterType,
|
||||
parent_names: NameFilterParentNames,
|
||||
) -> bool:
|
||||
"""Run the context's name filters and return True if the targets
|
||||
should be part of the autogenerate operation.
|
||||
|
||||
This method should be run for every kind of name encountered within the
|
||||
reflection side of an autogenerate operation, giving the environment
|
||||
the chance to filter what names should be reflected as database
|
||||
objects. The filters here are produced directly via the
|
||||
:paramref:`.EnvironmentContext.configure.include_name` parameter.
|
||||
|
||||
"""
|
||||
if "schema_name" in parent_names:
|
||||
if type_ == "table":
|
||||
table_name = name
|
||||
else:
|
||||
table_name = parent_names.get("table_name", None)
|
||||
if table_name:
|
||||
schema_name = parent_names["schema_name"]
|
||||
if schema_name:
|
||||
parent_names["schema_qualified_table_name"] = "%s.%s" % (
|
||||
schema_name,
|
||||
table_name,
|
||||
)
|
||||
else:
|
||||
parent_names["schema_qualified_table_name"] = table_name
|
||||
|
||||
for fn in self._name_filters:
|
||||
if not fn(name, type_, parent_names):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def run_object_filters(
|
||||
self,
|
||||
object_: SchemaItem,
|
||||
name: sqla_compat._ConstraintName,
|
||||
type_: NameFilterType,
|
||||
reflected: bool,
|
||||
compare_to: Optional[SchemaItem],
|
||||
) -> bool:
|
||||
"""Run the context's object filters and return True if the targets
|
||||
should be part of the autogenerate operation.
|
||||
|
||||
This method should be run for every kind of object encountered within
|
||||
an autogenerate operation, giving the environment the chance
|
||||
to filter what objects should be included in the comparison.
|
||||
The filters here are produced directly via the
|
||||
:paramref:`.EnvironmentContext.configure.include_object` parameter.
|
||||
|
||||
"""
|
||||
for fn in self._object_filters:
|
||||
if not fn(object_, name, type_, reflected, compare_to):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
run_filters = run_object_filters
|
||||
|
||||
@util.memoized_property
|
||||
def sorted_tables(self) -> List[Table]:
|
||||
"""Return an aggregate of the :attr:`.MetaData.sorted_tables`
|
||||
collection(s).
|
||||
|
||||
For a sequence of :class:`.MetaData` objects, this
|
||||
concatenates the :attr:`.MetaData.sorted_tables` collection
|
||||
for each individual :class:`.MetaData` in the order of the
|
||||
sequence. It does **not** collate the sorted tables collections.
|
||||
|
||||
"""
|
||||
result = []
|
||||
for m in util.to_list(self.metadata):
|
||||
result.extend(m.sorted_tables)
|
||||
return result
|
||||
|
||||
@util.memoized_property
|
||||
def table_key_to_table(self) -> Dict[str, Table]:
|
||||
"""Return an aggregate of the :attr:`.MetaData.tables` dictionaries.
|
||||
|
||||
The :attr:`.MetaData.tables` collection is a dictionary of table key
|
||||
to :class:`.Table`; this method aggregates the dictionary across
|
||||
multiple :class:`.MetaData` objects into one dictionary.
|
||||
|
||||
Duplicate table keys are **not** supported; if two :class:`.MetaData`
|
||||
objects contain the same table key, an exception is raised.
|
||||
|
||||
"""
|
||||
result: Dict[str, Table] = {}
|
||||
for m in util.to_list(self.metadata):
|
||||
intersect = set(result).intersection(set(m.tables))
|
||||
if intersect:
|
||||
raise ValueError(
|
||||
"Duplicate table keys across multiple "
|
||||
"MetaData objects: %s"
|
||||
% (", ".join('"%s"' % key for key in sorted(intersect)))
|
||||
)
|
||||
|
||||
result.update(m.tables)
|
||||
return result
|
||||
|
||||
|
||||
class RevisionContext:
|
||||
"""Maintains configuration and state that's specific to a revision
|
||||
file generation operation."""
|
||||
|
||||
generated_revisions: List[MigrationScript]
|
||||
process_revision_directives: Optional[ProcessRevisionDirectiveFn]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
script_directory: ScriptDirectory,
|
||||
command_args: Dict[str, Any],
|
||||
process_revision_directives: Optional[
|
||||
ProcessRevisionDirectiveFn
|
||||
] = None,
|
||||
) -> None:
|
||||
self.config = config
|
||||
self.script_directory = script_directory
|
||||
self.command_args = command_args
|
||||
self.process_revision_directives = process_revision_directives
|
||||
self.template_args = {
|
||||
"config": config # Let templates use config for
|
||||
# e.g. multiple databases
|
||||
}
|
||||
self.generated_revisions = [self._default_revision()]
|
||||
|
||||
def _to_script(
|
||||
self, migration_script: MigrationScript
|
||||
) -> Optional[Script]:
|
||||
template_args: Dict[str, Any] = self.template_args.copy()
|
||||
|
||||
if getattr(migration_script, "_needs_render", False):
|
||||
autogen_context = self._last_autogen_context
|
||||
|
||||
# clear out existing imports if we are doing multiple
|
||||
# renders
|
||||
autogen_context.imports = set()
|
||||
if migration_script.imports:
|
||||
autogen_context.imports.update(migration_script.imports)
|
||||
render._render_python_into_templatevars(
|
||||
autogen_context, migration_script, template_args
|
||||
)
|
||||
|
||||
assert migration_script.rev_id is not None
|
||||
return self.script_directory.generate_revision(
|
||||
migration_script.rev_id,
|
||||
migration_script.message,
|
||||
refresh=True,
|
||||
head=migration_script.head,
|
||||
splice=migration_script.splice,
|
||||
branch_labels=migration_script.branch_label,
|
||||
version_path=migration_script.version_path,
|
||||
depends_on=migration_script.depends_on,
|
||||
**template_args,
|
||||
)
|
||||
|
||||
def run_autogenerate(
|
||||
self, rev: _GetRevArg, migration_context: MigrationContext
|
||||
) -> None:
|
||||
self._run_environment(rev, migration_context, True)
|
||||
|
||||
def run_no_autogenerate(
|
||||
self, rev: _GetRevArg, migration_context: MigrationContext
|
||||
) -> None:
|
||||
self._run_environment(rev, migration_context, False)
|
||||
|
||||
def _run_environment(
|
||||
self,
|
||||
rev: _GetRevArg,
|
||||
migration_context: MigrationContext,
|
||||
autogenerate: bool,
|
||||
) -> None:
|
||||
if autogenerate:
|
||||
if self.command_args["sql"]:
|
||||
raise util.CommandError(
|
||||
"Using --sql with --autogenerate does not make any sense"
|
||||
)
|
||||
if set(self.script_directory.get_revisions(rev)) != set(
|
||||
self.script_directory.get_revisions("heads")
|
||||
):
|
||||
raise util.CommandError("Target database is not up to date.")
|
||||
|
||||
upgrade_token = migration_context.opts["upgrade_token"]
|
||||
downgrade_token = migration_context.opts["downgrade_token"]
|
||||
|
||||
migration_script = self.generated_revisions[-1]
|
||||
if not getattr(migration_script, "_needs_render", False):
|
||||
migration_script.upgrade_ops_list[-1].upgrade_token = upgrade_token
|
||||
migration_script.downgrade_ops_list[-1].downgrade_token = (
|
||||
downgrade_token
|
||||
)
|
||||
migration_script._needs_render = True
|
||||
else:
|
||||
migration_script._upgrade_ops.append(
|
||||
ops.UpgradeOps([], upgrade_token=upgrade_token)
|
||||
)
|
||||
migration_script._downgrade_ops.append(
|
||||
ops.DowngradeOps([], downgrade_token=downgrade_token)
|
||||
)
|
||||
|
||||
autogen_context = AutogenContext(
|
||||
migration_context, autogenerate=autogenerate
|
||||
)
|
||||
self._last_autogen_context: AutogenContext = autogen_context
|
||||
|
||||
if autogenerate:
|
||||
compare._populate_migration_script(
|
||||
autogen_context, migration_script
|
||||
)
|
||||
|
||||
if self.process_revision_directives:
|
||||
self.process_revision_directives(
|
||||
migration_context, rev, self.generated_revisions
|
||||
)
|
||||
|
||||
hook = migration_context.opts["process_revision_directives"]
|
||||
if hook:
|
||||
hook(migration_context, rev, self.generated_revisions)
|
||||
|
||||
for migration_script in self.generated_revisions:
|
||||
migration_script._needs_render = True
|
||||
|
||||
def _default_revision(self) -> MigrationScript:
|
||||
command_args: Dict[str, Any] = self.command_args
|
||||
op = ops.MigrationScript(
|
||||
rev_id=command_args["rev_id"] or util.rev_id(),
|
||||
message=command_args["message"],
|
||||
upgrade_ops=ops.UpgradeOps([]),
|
||||
downgrade_ops=ops.DowngradeOps([]),
|
||||
head=command_args["head"],
|
||||
splice=command_args["splice"],
|
||||
branch_label=command_args["branch_label"],
|
||||
version_path=command_args["version_path"],
|
||||
depends_on=command_args["depends_on"],
|
||||
)
|
||||
return op
|
||||
|
||||
def generate_scripts(self) -> Iterator[Optional[Script]]:
|
||||
for generated_revision in self.generated_revisions:
|
||||
yield self._to_script(generated_revision)
|
||||
1370
venv/Lib/site-packages/alembic/autogenerate/compare.py
Normal file
1370
venv/Lib/site-packages/alembic/autogenerate/compare.py
Normal file
File diff suppressed because it is too large
Load Diff
1172
venv/Lib/site-packages/alembic/autogenerate/render.py
Normal file
1172
venv/Lib/site-packages/alembic/autogenerate/render.py
Normal file
File diff suppressed because it is too large
Load Diff
240
venv/Lib/site-packages/alembic/autogenerate/rewriter.py
Normal file
240
venv/Lib/site-packages/alembic/autogenerate/rewriter.py
Normal file
@@ -0,0 +1,240 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from .. import util
|
||||
from ..operations import ops
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..operations.ops import AddColumnOp
|
||||
from ..operations.ops import AlterColumnOp
|
||||
from ..operations.ops import CreateTableOp
|
||||
from ..operations.ops import DowngradeOps
|
||||
from ..operations.ops import MigrateOperation
|
||||
from ..operations.ops import MigrationScript
|
||||
from ..operations.ops import ModifyTableOps
|
||||
from ..operations.ops import OpContainer
|
||||
from ..operations.ops import UpgradeOps
|
||||
from ..runtime.migration import MigrationContext
|
||||
from ..script.revision import _GetRevArg
|
||||
|
||||
ProcessRevisionDirectiveFn = Callable[
|
||||
["MigrationContext", "_GetRevArg", List["MigrationScript"]], None
|
||||
]
|
||||
|
||||
|
||||
class Rewriter:
|
||||
"""A helper object that allows easy 'rewriting' of ops streams.
|
||||
|
||||
The :class:`.Rewriter` object is intended to be passed along
|
||||
to the
|
||||
:paramref:`.EnvironmentContext.configure.process_revision_directives`
|
||||
parameter in an ``env.py`` script. Once constructed, any number
|
||||
of "rewrites" functions can be associated with it, which will be given
|
||||
the opportunity to modify the structure without having to have explicit
|
||||
knowledge of the overall structure.
|
||||
|
||||
The function is passed the :class:`.MigrationContext` object and
|
||||
``revision`` tuple that are passed to the :paramref:`.Environment
|
||||
Context.configure.process_revision_directives` function normally,
|
||||
and the third argument is an individual directive of the type
|
||||
noted in the decorator. The function has the choice of returning
|
||||
a single op directive, which normally can be the directive that
|
||||
was actually passed, or a new directive to replace it, or a list
|
||||
of zero or more directives to replace it.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogen_rewriter` - usage example
|
||||
|
||||
"""
|
||||
|
||||
_traverse = util.Dispatcher()
|
||||
|
||||
_chained: Tuple[Union[ProcessRevisionDirectiveFn, Rewriter], ...] = ()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.dispatch = util.Dispatcher()
|
||||
|
||||
def chain(
|
||||
self,
|
||||
other: Union[
|
||||
ProcessRevisionDirectiveFn,
|
||||
Rewriter,
|
||||
],
|
||||
) -> Rewriter:
|
||||
"""Produce a "chain" of this :class:`.Rewriter` to another.
|
||||
|
||||
This allows two or more rewriters to operate serially on a stream,
|
||||
e.g.::
|
||||
|
||||
writer1 = autogenerate.Rewriter()
|
||||
writer2 = autogenerate.Rewriter()
|
||||
|
||||
|
||||
@writer1.rewrites(ops.AddColumnOp)
|
||||
def add_column_nullable(context, revision, op):
|
||||
op.column.nullable = True
|
||||
return op
|
||||
|
||||
|
||||
@writer2.rewrites(ops.AddColumnOp)
|
||||
def add_column_idx(context, revision, op):
|
||||
idx_op = ops.CreateIndexOp(
|
||||
"ixc", op.table_name, [op.column.name]
|
||||
)
|
||||
return [op, idx_op]
|
||||
|
||||
writer = writer1.chain(writer2)
|
||||
|
||||
:param other: a :class:`.Rewriter` instance
|
||||
:return: a new :class:`.Rewriter` that will run the operations
|
||||
of this writer, then the "other" writer, in succession.
|
||||
|
||||
"""
|
||||
wr = self.__class__.__new__(self.__class__)
|
||||
wr.__dict__.update(self.__dict__)
|
||||
wr._chained += (other,)
|
||||
return wr
|
||||
|
||||
def rewrites(
|
||||
self,
|
||||
operator: Union[
|
||||
Type[AddColumnOp],
|
||||
Type[MigrateOperation],
|
||||
Type[AlterColumnOp],
|
||||
Type[CreateTableOp],
|
||||
Type[ModifyTableOps],
|
||||
],
|
||||
) -> Callable[..., Any]:
|
||||
"""Register a function as rewriter for a given type.
|
||||
|
||||
The function should receive three arguments, which are
|
||||
the :class:`.MigrationContext`, a ``revision`` tuple, and
|
||||
an op directive of the type indicated. E.g.::
|
||||
|
||||
@writer1.rewrites(ops.AddColumnOp)
|
||||
def add_column_nullable(context, revision, op):
|
||||
op.column.nullable = True
|
||||
return op
|
||||
|
||||
"""
|
||||
return self.dispatch.dispatch_for(operator)
|
||||
|
||||
def _rewrite(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directive: MigrateOperation,
|
||||
) -> Iterator[MigrateOperation]:
|
||||
try:
|
||||
_rewriter = self.dispatch.dispatch(directive)
|
||||
except ValueError:
|
||||
_rewriter = None
|
||||
yield directive
|
||||
else:
|
||||
if self in directive._mutations:
|
||||
yield directive
|
||||
else:
|
||||
for r_directive in util.to_list(
|
||||
_rewriter(context, revision, directive), []
|
||||
):
|
||||
r_directive._mutations = r_directive._mutations.union(
|
||||
[self]
|
||||
)
|
||||
yield r_directive
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directives: List[MigrationScript],
|
||||
) -> None:
|
||||
self.process_revision_directives(context, revision, directives)
|
||||
for process_revision_directives in self._chained:
|
||||
process_revision_directives(context, revision, directives)
|
||||
|
||||
@_traverse.dispatch_for(ops.MigrationScript)
|
||||
def _traverse_script(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directive: MigrationScript,
|
||||
) -> None:
|
||||
upgrade_ops_list: List[UpgradeOps] = []
|
||||
for upgrade_ops in directive.upgrade_ops_list:
|
||||
ret = self._traverse_for(context, revision, upgrade_ops)
|
||||
if len(ret) != 1:
|
||||
raise ValueError(
|
||||
"Can only return single object for UpgradeOps traverse"
|
||||
)
|
||||
upgrade_ops_list.append(ret[0])
|
||||
|
||||
directive.upgrade_ops = upgrade_ops_list
|
||||
|
||||
downgrade_ops_list: List[DowngradeOps] = []
|
||||
for downgrade_ops in directive.downgrade_ops_list:
|
||||
ret = self._traverse_for(context, revision, downgrade_ops)
|
||||
if len(ret) != 1:
|
||||
raise ValueError(
|
||||
"Can only return single object for DowngradeOps traverse"
|
||||
)
|
||||
downgrade_ops_list.append(ret[0])
|
||||
directive.downgrade_ops = downgrade_ops_list
|
||||
|
||||
@_traverse.dispatch_for(ops.OpContainer)
|
||||
def _traverse_op_container(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directive: OpContainer,
|
||||
) -> None:
|
||||
self._traverse_list(context, revision, directive.ops)
|
||||
|
||||
@_traverse.dispatch_for(ops.MigrateOperation)
|
||||
def _traverse_any_directive(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directive: MigrateOperation,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def _traverse_for(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directive: MigrateOperation,
|
||||
) -> Any:
|
||||
directives = list(self._rewrite(context, revision, directive))
|
||||
for directive in directives:
|
||||
traverser = self._traverse.dispatch(directive)
|
||||
traverser(self, context, revision, directive)
|
||||
return directives
|
||||
|
||||
def _traverse_list(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directives: Any,
|
||||
) -> None:
|
||||
dest = []
|
||||
for directive in directives:
|
||||
dest.extend(self._traverse_for(context, revision, directive))
|
||||
|
||||
directives[:] = dest
|
||||
|
||||
def process_revision_directives(
|
||||
self,
|
||||
context: MigrationContext,
|
||||
revision: _GetRevArg,
|
||||
directives: List[MigrationScript],
|
||||
) -> None:
|
||||
self._traverse_list(context, revision, directives)
|
||||
835
venv/Lib/site-packages/alembic/command.py
Normal file
835
venv/Lib/site-packages/alembic/command.py
Normal file
@@ -0,0 +1,835 @@
|
||||
# mypy: allow-untyped-defs, allow-untyped-calls
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from . import autogenerate as autogen
|
||||
from . import util
|
||||
from .runtime.environment import EnvironmentContext
|
||||
from .script import ScriptDirectory
|
||||
from .util import compat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from alembic.config import Config
|
||||
from alembic.script.base import Script
|
||||
from alembic.script.revision import _RevIdType
|
||||
from .runtime.environment import ProcessRevisionDirectiveFn
|
||||
|
||||
|
||||
def list_templates(config: Config) -> None:
|
||||
"""List available templates.
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
|
||||
"""
|
||||
|
||||
config.print_stdout("Available templates:\n")
|
||||
for tempname in config._get_template_path().iterdir():
|
||||
with (tempname / "README").open() as readme:
|
||||
synopsis = next(readme).rstrip()
|
||||
config.print_stdout("%s - %s", tempname.name, synopsis)
|
||||
|
||||
config.print_stdout("\nTemplates are used via the 'init' command, e.g.:")
|
||||
config.print_stdout("\n alembic init --template generic ./scripts")
|
||||
|
||||
|
||||
def init(
|
||||
config: Config,
|
||||
directory: str,
|
||||
template: str = "generic",
|
||||
package: bool = False,
|
||||
) -> None:
|
||||
"""Initialize a new scripts directory.
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
|
||||
:param directory: string path of the target directory.
|
||||
|
||||
:param template: string name of the migration environment template to
|
||||
use.
|
||||
|
||||
:param package: when True, write ``__init__.py`` files into the
|
||||
environment location as well as the versions/ location.
|
||||
|
||||
"""
|
||||
|
||||
directory_path = pathlib.Path(directory)
|
||||
if directory_path.exists() and list(directory_path.iterdir()):
|
||||
raise util.CommandError(
|
||||
"Directory %s already exists and is not empty" % directory_path
|
||||
)
|
||||
|
||||
template_path = config._get_template_path() / template
|
||||
|
||||
if not template_path.exists():
|
||||
raise util.CommandError(f"No such template {template_path}")
|
||||
|
||||
# left as os.access() to suit unit test mocking
|
||||
if not os.access(directory_path, os.F_OK):
|
||||
with util.status(
|
||||
f"Creating directory {directory_path.absolute()}",
|
||||
**config.messaging_opts,
|
||||
):
|
||||
os.makedirs(directory_path)
|
||||
|
||||
versions = directory_path / "versions"
|
||||
with util.status(
|
||||
f"Creating directory {versions.absolute()}",
|
||||
**config.messaging_opts,
|
||||
):
|
||||
os.makedirs(versions)
|
||||
|
||||
if not directory_path.is_absolute():
|
||||
# for non-absolute path, state config file in .ini / pyproject
|
||||
# as relative to the %(here)s token, which is where the config
|
||||
# file itself would be
|
||||
|
||||
if config._config_file_path is not None:
|
||||
rel_dir = compat.path_relative_to(
|
||||
directory_path.absolute(),
|
||||
config._config_file_path.absolute().parent,
|
||||
walk_up=True,
|
||||
)
|
||||
ini_script_location_directory = ("%(here)s" / rel_dir).as_posix()
|
||||
if config._toml_file_path is not None:
|
||||
rel_dir = compat.path_relative_to(
|
||||
directory_path.absolute(),
|
||||
config._toml_file_path.absolute().parent,
|
||||
walk_up=True,
|
||||
)
|
||||
toml_script_location_directory = ("%(here)s" / rel_dir).as_posix()
|
||||
|
||||
else:
|
||||
ini_script_location_directory = directory_path.as_posix()
|
||||
toml_script_location_directory = directory_path.as_posix()
|
||||
|
||||
script = ScriptDirectory(directory_path)
|
||||
|
||||
has_toml = False
|
||||
|
||||
config_file: pathlib.Path | None = None
|
||||
|
||||
for file_path in template_path.iterdir():
|
||||
file_ = file_path.name
|
||||
if file_ == "alembic.ini.mako":
|
||||
assert config.config_file_name is not None
|
||||
config_file = pathlib.Path(config.config_file_name).absolute()
|
||||
if config_file.exists():
|
||||
util.msg(
|
||||
f"File {config_file} already exists, skipping",
|
||||
**config.messaging_opts,
|
||||
)
|
||||
else:
|
||||
script._generate_template(
|
||||
file_path,
|
||||
config_file,
|
||||
script_location=ini_script_location_directory,
|
||||
)
|
||||
elif file_ == "pyproject.toml.mako":
|
||||
has_toml = True
|
||||
assert config._toml_file_path is not None
|
||||
toml_path = config._toml_file_path.absolute()
|
||||
|
||||
if toml_path.exists():
|
||||
# left as open() to suit unit test mocking
|
||||
with open(toml_path, "rb") as f:
|
||||
toml_data = compat.tomllib.load(f)
|
||||
if "tool" in toml_data and "alembic" in toml_data["tool"]:
|
||||
|
||||
util.msg(
|
||||
f"File {toml_path} already exists "
|
||||
"and already has a [tool.alembic] section, "
|
||||
"skipping",
|
||||
)
|
||||
continue
|
||||
script._append_template(
|
||||
file_path,
|
||||
toml_path,
|
||||
script_location=toml_script_location_directory,
|
||||
)
|
||||
else:
|
||||
script._generate_template(
|
||||
file_path,
|
||||
toml_path,
|
||||
script_location=toml_script_location_directory,
|
||||
)
|
||||
|
||||
elif file_path.is_file():
|
||||
output_file = directory_path / file_
|
||||
script._copy_file(file_path, output_file)
|
||||
|
||||
if package:
|
||||
for path in [
|
||||
directory_path.absolute() / "__init__.py",
|
||||
versions.absolute() / "__init__.py",
|
||||
]:
|
||||
with util.status(f"Adding {path!s}", **config.messaging_opts):
|
||||
# left as open() to suit unit test mocking
|
||||
with open(path, "w"):
|
||||
pass
|
||||
|
||||
assert config_file is not None
|
||||
|
||||
if has_toml:
|
||||
util.msg(
|
||||
f"Please edit configuration settings in {toml_path} and "
|
||||
"configuration/connection/logging "
|
||||
f"settings in {config_file} before proceeding.",
|
||||
**config.messaging_opts,
|
||||
)
|
||||
else:
|
||||
util.msg(
|
||||
"Please edit configuration/connection/logging "
|
||||
f"settings in {config_file} before proceeding.",
|
||||
**config.messaging_opts,
|
||||
)
|
||||
|
||||
|
||||
def revision(
|
||||
config: Config,
|
||||
message: Optional[str] = None,
|
||||
autogenerate: bool = False,
|
||||
sql: bool = False,
|
||||
head: str = "head",
|
||||
splice: bool = False,
|
||||
branch_label: Optional[_RevIdType] = None,
|
||||
version_path: Union[str, os.PathLike[str], None] = None,
|
||||
rev_id: Optional[str] = None,
|
||||
depends_on: Optional[str] = None,
|
||||
process_revision_directives: Optional[ProcessRevisionDirectiveFn] = None,
|
||||
) -> Union[Optional[Script], List[Optional[Script]]]:
|
||||
"""Create a new revision file.
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
|
||||
:param message: string message to apply to the revision; this is the
|
||||
``-m`` option to ``alembic revision``.
|
||||
|
||||
:param autogenerate: whether or not to autogenerate the script from
|
||||
the database; this is the ``--autogenerate`` option to
|
||||
``alembic revision``.
|
||||
|
||||
:param sql: whether to dump the script out as a SQL string; when specified,
|
||||
the script is dumped to stdout. This is the ``--sql`` option to
|
||||
``alembic revision``.
|
||||
|
||||
:param head: head revision to build the new revision upon as a parent;
|
||||
this is the ``--head`` option to ``alembic revision``.
|
||||
|
||||
:param splice: whether or not the new revision should be made into a
|
||||
new head of its own; is required when the given ``head`` is not itself
|
||||
a head. This is the ``--splice`` option to ``alembic revision``.
|
||||
|
||||
:param branch_label: string label to apply to the branch; this is the
|
||||
``--branch-label`` option to ``alembic revision``.
|
||||
|
||||
:param version_path: string symbol identifying a specific version path
|
||||
from the configuration; this is the ``--version-path`` option to
|
||||
``alembic revision``.
|
||||
|
||||
:param rev_id: optional revision identifier to use instead of having
|
||||
one generated; this is the ``--rev-id`` option to ``alembic revision``.
|
||||
|
||||
:param depends_on: optional list of "depends on" identifiers; this is the
|
||||
``--depends-on`` option to ``alembic revision``.
|
||||
|
||||
:param process_revision_directives: this is a callable that takes the
|
||||
same form as the callable described at
|
||||
:paramref:`.EnvironmentContext.configure.process_revision_directives`;
|
||||
will be applied to the structure generated by the revision process
|
||||
where it can be altered programmatically. Note that unlike all
|
||||
the other parameters, this option is only available via programmatic
|
||||
use of :func:`.command.revision`.
|
||||
|
||||
"""
|
||||
|
||||
script_directory = ScriptDirectory.from_config(config)
|
||||
|
||||
command_args = dict(
|
||||
message=message,
|
||||
autogenerate=autogenerate,
|
||||
sql=sql,
|
||||
head=head,
|
||||
splice=splice,
|
||||
branch_label=branch_label,
|
||||
version_path=version_path,
|
||||
rev_id=rev_id,
|
||||
depends_on=depends_on,
|
||||
)
|
||||
revision_context = autogen.RevisionContext(
|
||||
config,
|
||||
script_directory,
|
||||
command_args,
|
||||
process_revision_directives=process_revision_directives,
|
||||
)
|
||||
|
||||
environment = util.asbool(
|
||||
config.get_alembic_option("revision_environment")
|
||||
)
|
||||
|
||||
if autogenerate:
|
||||
environment = True
|
||||
|
||||
if sql:
|
||||
raise util.CommandError(
|
||||
"Using --sql with --autogenerate does not make any sense"
|
||||
)
|
||||
|
||||
def retrieve_migrations(rev, context):
|
||||
revision_context.run_autogenerate(rev, context)
|
||||
return []
|
||||
|
||||
elif environment:
|
||||
|
||||
def retrieve_migrations(rev, context):
|
||||
revision_context.run_no_autogenerate(rev, context)
|
||||
return []
|
||||
|
||||
elif sql:
|
||||
raise util.CommandError(
|
||||
"Using --sql with the revision command when "
|
||||
"revision_environment is not configured does not make any sense"
|
||||
)
|
||||
|
||||
if environment:
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script_directory,
|
||||
fn=retrieve_migrations,
|
||||
as_sql=sql,
|
||||
template_args=revision_context.template_args,
|
||||
revision_context=revision_context,
|
||||
):
|
||||
script_directory.run_env()
|
||||
|
||||
# the revision_context now has MigrationScript structure(s) present.
|
||||
# these could theoretically be further processed / rewritten *here*,
|
||||
# in addition to the hooks present within each run_migrations() call,
|
||||
# or at the end of env.py run_migrations_online().
|
||||
|
||||
scripts = [script for script in revision_context.generate_scripts()]
|
||||
if len(scripts) == 1:
|
||||
return scripts[0]
|
||||
else:
|
||||
return scripts
|
||||
|
||||
|
||||
def check(config: "Config") -> None:
|
||||
"""Check if revision command with autogenerate has pending upgrade ops.
|
||||
|
||||
:param config: a :class:`.Config` object.
|
||||
|
||||
.. versionadded:: 1.9.0
|
||||
|
||||
"""
|
||||
|
||||
script_directory = ScriptDirectory.from_config(config)
|
||||
|
||||
command_args = dict(
|
||||
message=None,
|
||||
autogenerate=True,
|
||||
sql=False,
|
||||
head="head",
|
||||
splice=False,
|
||||
branch_label=None,
|
||||
version_path=None,
|
||||
rev_id=None,
|
||||
depends_on=None,
|
||||
)
|
||||
revision_context = autogen.RevisionContext(
|
||||
config,
|
||||
script_directory,
|
||||
command_args,
|
||||
)
|
||||
|
||||
def retrieve_migrations(rev, context):
|
||||
revision_context.run_autogenerate(rev, context)
|
||||
return []
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script_directory,
|
||||
fn=retrieve_migrations,
|
||||
as_sql=False,
|
||||
template_args=revision_context.template_args,
|
||||
revision_context=revision_context,
|
||||
):
|
||||
script_directory.run_env()
|
||||
|
||||
# the revision_context now has MigrationScript structure(s) present.
|
||||
|
||||
migration_script = revision_context.generated_revisions[-1]
|
||||
diffs = []
|
||||
for upgrade_ops in migration_script.upgrade_ops_list:
|
||||
diffs.extend(upgrade_ops.as_diffs())
|
||||
|
||||
if diffs:
|
||||
raise util.AutogenerateDiffsDetected(
|
||||
f"New upgrade operations detected: {diffs}",
|
||||
revision_context=revision_context,
|
||||
diffs=diffs,
|
||||
)
|
||||
else:
|
||||
config.print_stdout("No new upgrade operations detected.")
|
||||
|
||||
|
||||
def merge(
|
||||
config: Config,
|
||||
revisions: _RevIdType,
|
||||
message: Optional[str] = None,
|
||||
branch_label: Optional[_RevIdType] = None,
|
||||
rev_id: Optional[str] = None,
|
||||
) -> Optional[Script]:
|
||||
"""Merge two revisions together. Creates a new migration file.
|
||||
|
||||
:param config: a :class:`.Config` instance
|
||||
|
||||
:param revisions: The revisions to merge.
|
||||
|
||||
:param message: string message to apply to the revision.
|
||||
|
||||
:param branch_label: string label name to apply to the new revision.
|
||||
|
||||
:param rev_id: hardcoded revision identifier instead of generating a new
|
||||
one.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`branches`
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
template_args = {
|
||||
"config": config # Let templates use config for
|
||||
# e.g. multiple databases
|
||||
}
|
||||
|
||||
environment = util.asbool(
|
||||
config.get_alembic_option("revision_environment")
|
||||
)
|
||||
|
||||
if environment:
|
||||
|
||||
def nothing(rev, context):
|
||||
return []
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script,
|
||||
fn=nothing,
|
||||
as_sql=False,
|
||||
template_args=template_args,
|
||||
):
|
||||
script.run_env()
|
||||
|
||||
return script.generate_revision(
|
||||
rev_id or util.rev_id(),
|
||||
message,
|
||||
refresh=True,
|
||||
head=revisions,
|
||||
branch_labels=branch_label,
|
||||
**template_args, # type:ignore[arg-type]
|
||||
)
|
||||
|
||||
|
||||
def upgrade(
|
||||
config: Config,
|
||||
revision: str,
|
||||
sql: bool = False,
|
||||
tag: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Upgrade to a later version.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: string revision target or range for --sql mode. May be
|
||||
``"heads"`` to target the most recent revision(s).
|
||||
|
||||
:param sql: if True, use ``--sql`` mode.
|
||||
|
||||
:param tag: an arbitrary "tag" that can be intercepted by custom
|
||||
``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument`
|
||||
method.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
starting_rev = None
|
||||
if ":" in revision:
|
||||
if not sql:
|
||||
raise util.CommandError("Range revision not allowed")
|
||||
starting_rev, revision = revision.split(":", 2)
|
||||
|
||||
def upgrade(rev, context):
|
||||
return script._upgrade_revs(revision, rev)
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script,
|
||||
fn=upgrade,
|
||||
as_sql=sql,
|
||||
starting_rev=starting_rev,
|
||||
destination_rev=revision,
|
||||
tag=tag,
|
||||
):
|
||||
script.run_env()
|
||||
|
||||
|
||||
def downgrade(
|
||||
config: Config,
|
||||
revision: str,
|
||||
sql: bool = False,
|
||||
tag: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Revert to a previous version.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: string revision target or range for --sql mode. May
|
||||
be ``"base"`` to target the first revision.
|
||||
|
||||
:param sql: if True, use ``--sql`` mode.
|
||||
|
||||
:param tag: an arbitrary "tag" that can be intercepted by custom
|
||||
``env.py`` scripts via the :meth:`.EnvironmentContext.get_tag_argument`
|
||||
method.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
starting_rev = None
|
||||
if ":" in revision:
|
||||
if not sql:
|
||||
raise util.CommandError("Range revision not allowed")
|
||||
starting_rev, revision = revision.split(":", 2)
|
||||
elif sql:
|
||||
raise util.CommandError(
|
||||
"downgrade with --sql requires <fromrev>:<torev>"
|
||||
)
|
||||
|
||||
def downgrade(rev, context):
|
||||
return script._downgrade_revs(revision, rev)
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script,
|
||||
fn=downgrade,
|
||||
as_sql=sql,
|
||||
starting_rev=starting_rev,
|
||||
destination_rev=revision,
|
||||
tag=tag,
|
||||
):
|
||||
script.run_env()
|
||||
|
||||
|
||||
def show(config: Config, rev: str) -> None:
|
||||
"""Show the revision(s) denoted by the given symbol.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param rev: string revision target. May be ``"current"`` to show the
|
||||
revision(s) currently applied in the database.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
if rev == "current":
|
||||
|
||||
def show_current(rev, context):
|
||||
for sc in script.get_revisions(rev):
|
||||
config.print_stdout(sc.log_entry)
|
||||
return []
|
||||
|
||||
with EnvironmentContext(config, script, fn=show_current):
|
||||
script.run_env()
|
||||
else:
|
||||
for sc in script.get_revisions(rev):
|
||||
config.print_stdout(sc.log_entry)
|
||||
|
||||
|
||||
def history(
|
||||
config: Config,
|
||||
rev_range: Optional[str] = None,
|
||||
verbose: bool = False,
|
||||
indicate_current: bool = False,
|
||||
) -> None:
|
||||
"""List changeset scripts in chronological order.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param rev_range: string revision range.
|
||||
|
||||
:param verbose: output in verbose mode.
|
||||
|
||||
:param indicate_current: indicate current revision.
|
||||
|
||||
"""
|
||||
base: Optional[str]
|
||||
head: Optional[str]
|
||||
script = ScriptDirectory.from_config(config)
|
||||
if rev_range is not None:
|
||||
if ":" not in rev_range:
|
||||
raise util.CommandError(
|
||||
"History range requires [start]:[end], " "[start]:, or :[end]"
|
||||
)
|
||||
base, head = rev_range.strip().split(":")
|
||||
else:
|
||||
base = head = None
|
||||
|
||||
environment = (
|
||||
util.asbool(config.get_alembic_option("revision_environment"))
|
||||
or indicate_current
|
||||
)
|
||||
|
||||
def _display_history(config, script, base, head, currents=()):
|
||||
for sc in script.walk_revisions(
|
||||
base=base or "base", head=head or "heads"
|
||||
):
|
||||
if indicate_current:
|
||||
sc._db_current_indicator = sc.revision in currents
|
||||
|
||||
config.print_stdout(
|
||||
sc.cmd_format(
|
||||
verbose=verbose,
|
||||
include_branches=True,
|
||||
include_doc=True,
|
||||
include_parents=True,
|
||||
)
|
||||
)
|
||||
|
||||
def _display_history_w_current(config, script, base, head):
|
||||
def _display_current_history(rev, context):
|
||||
if head == "current":
|
||||
_display_history(config, script, base, rev, rev)
|
||||
elif base == "current":
|
||||
_display_history(config, script, rev, head, rev)
|
||||
else:
|
||||
_display_history(config, script, base, head, rev)
|
||||
return []
|
||||
|
||||
with EnvironmentContext(config, script, fn=_display_current_history):
|
||||
script.run_env()
|
||||
|
||||
if base == "current" or head == "current" or environment:
|
||||
_display_history_w_current(config, script, base, head)
|
||||
else:
|
||||
_display_history(config, script, base, head)
|
||||
|
||||
|
||||
def heads(
|
||||
config: Config, verbose: bool = False, resolve_dependencies: bool = False
|
||||
) -> None:
|
||||
"""Show current available heads in the script directory.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param verbose: output in verbose mode.
|
||||
|
||||
:param resolve_dependencies: treat dependency version as down revisions.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
if resolve_dependencies:
|
||||
heads = script.get_revisions("heads")
|
||||
else:
|
||||
heads = script.get_revisions(script.get_heads())
|
||||
|
||||
for rev in heads:
|
||||
config.print_stdout(
|
||||
rev.cmd_format(
|
||||
verbose, include_branches=True, tree_indicators=False
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def branches(config: Config, verbose: bool = False) -> None:
|
||||
"""Show current branch points.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param verbose: output in verbose mode.
|
||||
|
||||
"""
|
||||
script = ScriptDirectory.from_config(config)
|
||||
for sc in script.walk_revisions():
|
||||
if sc.is_branch_point:
|
||||
config.print_stdout(
|
||||
"%s\n%s\n",
|
||||
sc.cmd_format(verbose, include_branches=True),
|
||||
"\n".join(
|
||||
"%s -> %s"
|
||||
% (
|
||||
" " * len(str(sc.revision)),
|
||||
rev_obj.cmd_format(
|
||||
False, include_branches=True, include_doc=verbose
|
||||
),
|
||||
)
|
||||
for rev_obj in (
|
||||
script.get_revision(rev) for rev in sc.nextrev
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def current(config: Config, verbose: bool = False) -> None:
|
||||
"""Display the current revision for a database.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param verbose: output in verbose mode.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
def display_version(rev, context):
|
||||
if verbose:
|
||||
config.print_stdout(
|
||||
"Current revision(s) for %s:",
|
||||
util.obfuscate_url_pw(context.connection.engine.url),
|
||||
)
|
||||
for rev in script.get_all_current(rev):
|
||||
config.print_stdout(rev.cmd_format(verbose))
|
||||
|
||||
return []
|
||||
|
||||
with EnvironmentContext(
|
||||
config, script, fn=display_version, dont_mutate=True
|
||||
):
|
||||
script.run_env()
|
||||
|
||||
|
||||
def stamp(
|
||||
config: Config,
|
||||
revision: _RevIdType,
|
||||
sql: bool = False,
|
||||
tag: Optional[str] = None,
|
||||
purge: bool = False,
|
||||
) -> None:
|
||||
"""'stamp' the revision table with the given revision; don't
|
||||
run any migrations.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param revision: target revision or list of revisions. May be a list
|
||||
to indicate stamping of multiple branch heads; may be ``"base"``
|
||||
to remove all revisions from the table or ``"heads"`` to stamp the
|
||||
most recent revision(s).
|
||||
|
||||
.. note:: this parameter is called "revisions" in the command line
|
||||
interface.
|
||||
|
||||
:param sql: use ``--sql`` mode
|
||||
|
||||
:param tag: an arbitrary "tag" that can be intercepted by custom
|
||||
``env.py`` scripts via the :class:`.EnvironmentContext.get_tag_argument`
|
||||
method.
|
||||
|
||||
:param purge: delete all entries in the version table before stamping.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
if sql:
|
||||
destination_revs = []
|
||||
starting_rev = None
|
||||
for _revision in util.to_list(revision):
|
||||
if ":" in _revision:
|
||||
srev, _revision = _revision.split(":", 2)
|
||||
|
||||
if starting_rev != srev:
|
||||
if starting_rev is None:
|
||||
starting_rev = srev
|
||||
else:
|
||||
raise util.CommandError(
|
||||
"Stamp operation with --sql only supports a "
|
||||
"single starting revision at a time"
|
||||
)
|
||||
destination_revs.append(_revision)
|
||||
else:
|
||||
destination_revs = util.to_list(revision)
|
||||
|
||||
def do_stamp(rev, context):
|
||||
return script._stamp_revs(util.to_tuple(destination_revs), rev)
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script,
|
||||
fn=do_stamp,
|
||||
as_sql=sql,
|
||||
starting_rev=starting_rev if sql else None,
|
||||
destination_rev=util.to_tuple(destination_revs),
|
||||
tag=tag,
|
||||
purge=purge,
|
||||
):
|
||||
script.run_env()
|
||||
|
||||
|
||||
def edit(config: Config, rev: str) -> None:
|
||||
"""Edit revision script(s) using $EDITOR.
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param rev: target revision.
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
if rev == "current":
|
||||
|
||||
def edit_current(rev, context):
|
||||
if not rev:
|
||||
raise util.CommandError("No current revisions")
|
||||
for sc in script.get_revisions(rev):
|
||||
util.open_in_editor(sc.path)
|
||||
return []
|
||||
|
||||
with EnvironmentContext(config, script, fn=edit_current):
|
||||
script.run_env()
|
||||
else:
|
||||
revs = script.get_revisions(rev)
|
||||
if not revs:
|
||||
raise util.CommandError(
|
||||
"No revision files indicated by symbol '%s'" % rev
|
||||
)
|
||||
for sc in revs:
|
||||
assert sc
|
||||
util.open_in_editor(sc.path)
|
||||
|
||||
|
||||
def ensure_version(config: Config, sql: bool = False) -> None:
|
||||
"""Create the alembic version table if it doesn't exist already .
|
||||
|
||||
:param config: a :class:`.Config` instance.
|
||||
|
||||
:param sql: use ``--sql`` mode.
|
||||
|
||||
.. versionadded:: 1.7.6
|
||||
|
||||
"""
|
||||
|
||||
script = ScriptDirectory.from_config(config)
|
||||
|
||||
def do_ensure_version(rev, context):
|
||||
context._ensure_version_table()
|
||||
return []
|
||||
|
||||
with EnvironmentContext(
|
||||
config,
|
||||
script,
|
||||
fn=do_ensure_version,
|
||||
as_sql=sql,
|
||||
):
|
||||
script.run_env()
|
||||
1020
venv/Lib/site-packages/alembic/config.py
Normal file
1020
venv/Lib/site-packages/alembic/config.py
Normal file
File diff suppressed because it is too large
Load Diff
5
venv/Lib/site-packages/alembic/context.py
Normal file
5
venv/Lib/site-packages/alembic/context.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .runtime.environment import EnvironmentContext
|
||||
|
||||
# create proxy functions for
|
||||
# each method on the EnvironmentContext class.
|
||||
EnvironmentContext.create_module_class_proxy(globals(), locals())
|
||||
856
venv/Lib/site-packages/alembic/context.pyi
Normal file
856
venv/Lib/site-packages/alembic/context.pyi
Normal file
@@ -0,0 +1,856 @@
|
||||
# ### this file stubs are generated by tools/write_pyi.py - do not edit ###
|
||||
# ### imports are manually managed
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Collection
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import MutableMapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import ContextManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine.url import URL
|
||||
from sqlalchemy.sql import Executable
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import FetchedValue
|
||||
from sqlalchemy.sql.schema import MetaData
|
||||
from sqlalchemy.sql.schema import SchemaItem
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .autogenerate.api import AutogenContext
|
||||
from .config import Config
|
||||
from .operations.ops import MigrationScript
|
||||
from .runtime.migration import _ProxyTransaction
|
||||
from .runtime.migration import MigrationContext
|
||||
from .runtime.migration import MigrationInfo
|
||||
from .script import ScriptDirectory
|
||||
|
||||
### end imports ###
|
||||
|
||||
def begin_transaction() -> (
|
||||
Union[_ProxyTransaction, ContextManager[None, Optional[bool]]]
|
||||
):
|
||||
"""Return a context manager that will
|
||||
enclose an operation within a "transaction",
|
||||
as defined by the environment's offline
|
||||
and transactional DDL settings.
|
||||
|
||||
e.g.::
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
:meth:`.begin_transaction` is intended to
|
||||
"do the right thing" regardless of
|
||||
calling context:
|
||||
|
||||
* If :meth:`.is_transactional_ddl` is ``False``,
|
||||
returns a "do nothing" context manager
|
||||
which otherwise produces no transactional
|
||||
state or directives.
|
||||
* If :meth:`.is_offline_mode` is ``True``,
|
||||
returns a context manager that will
|
||||
invoke the :meth:`.DefaultImpl.emit_begin`
|
||||
and :meth:`.DefaultImpl.emit_commit`
|
||||
methods, which will produce the string
|
||||
directives ``BEGIN`` and ``COMMIT`` on
|
||||
the output stream, as rendered by the
|
||||
target backend (e.g. SQL Server would
|
||||
emit ``BEGIN TRANSACTION``).
|
||||
* Otherwise, calls :meth:`sqlalchemy.engine.Connection.begin`
|
||||
on the current online connection, which
|
||||
returns a :class:`sqlalchemy.engine.Transaction`
|
||||
object. This object demarcates a real
|
||||
transaction and is itself a context manager,
|
||||
which will roll back if an exception
|
||||
is raised.
|
||||
|
||||
Note that a custom ``env.py`` script which
|
||||
has more specific transactional needs can of course
|
||||
manipulate the :class:`~sqlalchemy.engine.Connection`
|
||||
directly to produce transactional state in "online"
|
||||
mode.
|
||||
|
||||
"""
|
||||
|
||||
config: Config
|
||||
|
||||
def configure(
|
||||
connection: Optional[Connection] = None,
|
||||
url: Union[str, URL, None] = None,
|
||||
dialect_name: Optional[str] = None,
|
||||
dialect_opts: Optional[Dict[str, Any]] = None,
|
||||
transactional_ddl: Optional[bool] = None,
|
||||
transaction_per_migration: bool = False,
|
||||
output_buffer: Optional[TextIO] = None,
|
||||
starting_rev: Optional[str] = None,
|
||||
tag: Optional[str] = None,
|
||||
template_args: Optional[Dict[str, Any]] = None,
|
||||
render_as_batch: bool = False,
|
||||
target_metadata: Union[MetaData, Sequence[MetaData], None] = None,
|
||||
include_name: Optional[
|
||||
Callable[
|
||||
[
|
||||
Optional[str],
|
||||
Literal[
|
||||
"schema",
|
||||
"table",
|
||||
"column",
|
||||
"index",
|
||||
"unique_constraint",
|
||||
"foreign_key_constraint",
|
||||
],
|
||||
MutableMapping[
|
||||
Literal[
|
||||
"schema_name",
|
||||
"table_name",
|
||||
"schema_qualified_table_name",
|
||||
],
|
||||
Optional[str],
|
||||
],
|
||||
],
|
||||
bool,
|
||||
]
|
||||
] = None,
|
||||
include_object: Optional[
|
||||
Callable[
|
||||
[
|
||||
SchemaItem,
|
||||
Optional[str],
|
||||
Literal[
|
||||
"schema",
|
||||
"table",
|
||||
"column",
|
||||
"index",
|
||||
"unique_constraint",
|
||||
"foreign_key_constraint",
|
||||
],
|
||||
bool,
|
||||
Optional[SchemaItem],
|
||||
],
|
||||
bool,
|
||||
]
|
||||
] = None,
|
||||
include_schemas: bool = False,
|
||||
process_revision_directives: Optional[
|
||||
Callable[
|
||||
[
|
||||
MigrationContext,
|
||||
Union[str, Iterable[Optional[str]], Iterable[str]],
|
||||
List[MigrationScript],
|
||||
],
|
||||
None,
|
||||
]
|
||||
] = None,
|
||||
compare_type: Union[
|
||||
bool,
|
||||
Callable[
|
||||
[
|
||||
MigrationContext,
|
||||
Column[Any],
|
||||
Column[Any],
|
||||
TypeEngine[Any],
|
||||
TypeEngine[Any],
|
||||
],
|
||||
Optional[bool],
|
||||
],
|
||||
] = True,
|
||||
compare_server_default: Union[
|
||||
bool,
|
||||
Callable[
|
||||
[
|
||||
MigrationContext,
|
||||
Column[Any],
|
||||
Column[Any],
|
||||
Optional[str],
|
||||
Optional[FetchedValue],
|
||||
Optional[str],
|
||||
],
|
||||
Optional[bool],
|
||||
],
|
||||
] = False,
|
||||
render_item: Optional[
|
||||
Callable[[str, Any, AutogenContext], Union[str, Literal[False]]]
|
||||
] = None,
|
||||
literal_binds: bool = False,
|
||||
upgrade_token: str = "upgrades",
|
||||
downgrade_token: str = "downgrades",
|
||||
alembic_module_prefix: str = "op.",
|
||||
sqlalchemy_module_prefix: str = "sa.",
|
||||
user_module_prefix: Optional[str] = None,
|
||||
on_version_apply: Optional[
|
||||
Callable[
|
||||
[
|
||||
MigrationContext,
|
||||
MigrationInfo,
|
||||
Collection[Any],
|
||||
Mapping[str, Any],
|
||||
],
|
||||
None,
|
||||
]
|
||||
] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
"""Configure a :class:`.MigrationContext` within this
|
||||
:class:`.EnvironmentContext` which will provide database
|
||||
connectivity and other configuration to a series of
|
||||
migration scripts.
|
||||
|
||||
Many methods on :class:`.EnvironmentContext` require that
|
||||
this method has been called in order to function, as they
|
||||
ultimately need to have database access or at least access
|
||||
to the dialect in use. Those which do are documented as such.
|
||||
|
||||
The important thing needed by :meth:`.configure` is a
|
||||
means to determine what kind of database dialect is in use.
|
||||
An actual connection to that database is needed only if
|
||||
the :class:`.MigrationContext` is to be used in
|
||||
"online" mode.
|
||||
|
||||
If the :meth:`.is_offline_mode` function returns ``True``,
|
||||
then no connection is needed here. Otherwise, the
|
||||
``connection`` parameter should be present as an
|
||||
instance of :class:`sqlalchemy.engine.Connection`.
|
||||
|
||||
This function is typically called from the ``env.py``
|
||||
script within a migration environment. It can be called
|
||||
multiple times for an invocation. The most recent
|
||||
:class:`~sqlalchemy.engine.Connection`
|
||||
for which it was called is the one that will be operated upon
|
||||
by the next call to :meth:`.run_migrations`.
|
||||
|
||||
General parameters:
|
||||
|
||||
:param connection: a :class:`~sqlalchemy.engine.Connection`
|
||||
to use
|
||||
for SQL execution in "online" mode. When present, is also
|
||||
used to determine the type of dialect in use.
|
||||
:param url: a string database url, or a
|
||||
:class:`sqlalchemy.engine.url.URL` object.
|
||||
The type of dialect to be used will be derived from this if
|
||||
``connection`` is not passed.
|
||||
:param dialect_name: string name of a dialect, such as
|
||||
"postgresql", "mssql", etc.
|
||||
The type of dialect to be used will be derived from this if
|
||||
``connection`` and ``url`` are not passed.
|
||||
:param dialect_opts: dictionary of options to be passed to dialect
|
||||
constructor.
|
||||
:param transactional_ddl: Force the usage of "transactional"
|
||||
DDL on or off;
|
||||
this otherwise defaults to whether or not the dialect in
|
||||
use supports it.
|
||||
:param transaction_per_migration: if True, nest each migration script
|
||||
in a transaction rather than the full series of migrations to
|
||||
run.
|
||||
:param output_buffer: a file-like object that will be used
|
||||
for textual output
|
||||
when the ``--sql`` option is used to generate SQL scripts.
|
||||
Defaults to
|
||||
``sys.stdout`` if not passed here and also not present on
|
||||
the :class:`.Config`
|
||||
object. The value here overrides that of the :class:`.Config`
|
||||
object.
|
||||
:param output_encoding: when using ``--sql`` to generate SQL
|
||||
scripts, apply this encoding to the string output.
|
||||
:param literal_binds: when using ``--sql`` to generate SQL
|
||||
scripts, pass through the ``literal_binds`` flag to the compiler
|
||||
so that any literal values that would ordinarily be bound
|
||||
parameters are converted to plain strings.
|
||||
|
||||
.. warning:: Dialects can typically only handle simple datatypes
|
||||
like strings and numbers for auto-literal generation. Datatypes
|
||||
like dates, intervals, and others may still require manual
|
||||
formatting, typically using :meth:`.Operations.inline_literal`.
|
||||
|
||||
.. note:: the ``literal_binds`` flag is ignored on SQLAlchemy
|
||||
versions prior to 0.8 where this feature is not supported.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Operations.inline_literal`
|
||||
|
||||
:param starting_rev: Override the "starting revision" argument
|
||||
when using ``--sql`` mode.
|
||||
:param tag: a string tag for usage by custom ``env.py`` scripts.
|
||||
Set via the ``--tag`` option, can be overridden here.
|
||||
:param template_args: dictionary of template arguments which
|
||||
will be added to the template argument environment when
|
||||
running the "revision" command. Note that the script environment
|
||||
is only run within the "revision" command if the --autogenerate
|
||||
option is used, or if the option "revision_environment=true"
|
||||
is present in the alembic.ini file.
|
||||
|
||||
:param version_table: The name of the Alembic version table.
|
||||
The default is ``'alembic_version'``.
|
||||
:param version_table_schema: Optional schema to place version
|
||||
table within.
|
||||
:param version_table_pk: boolean, whether the Alembic version table
|
||||
should use a primary key constraint for the "value" column; this
|
||||
only takes effect when the table is first created.
|
||||
Defaults to True; setting to False should not be necessary and is
|
||||
here for backwards compatibility reasons.
|
||||
:param on_version_apply: a callable or collection of callables to be
|
||||
run for each migration step.
|
||||
The callables will be run in the order they are given, once for
|
||||
each migration step, after the respective operation has been
|
||||
applied but before its transaction is finalized.
|
||||
Each callable accepts no positional arguments and the following
|
||||
keyword arguments:
|
||||
|
||||
* ``ctx``: the :class:`.MigrationContext` running the migration,
|
||||
* ``step``: a :class:`.MigrationInfo` representing the
|
||||
step currently being applied,
|
||||
* ``heads``: a collection of version strings representing the
|
||||
current heads,
|
||||
* ``run_args``: the ``**kwargs`` passed to :meth:`.run_migrations`.
|
||||
|
||||
Parameters specific to the autogenerate feature, when
|
||||
``alembic revision`` is run with the ``--autogenerate`` feature:
|
||||
|
||||
:param target_metadata: a :class:`sqlalchemy.schema.MetaData`
|
||||
object, or a sequence of :class:`~sqlalchemy.schema.MetaData`
|
||||
objects, that will be consulted during autogeneration.
|
||||
The tables present in each :class:`~sqlalchemy.schema.MetaData`
|
||||
will be compared against
|
||||
what is locally available on the target
|
||||
:class:`~sqlalchemy.engine.Connection`
|
||||
to produce candidate upgrade/downgrade operations.
|
||||
:param compare_type: Indicates type comparison behavior during
|
||||
an autogenerate
|
||||
operation. Defaults to ``True`` turning on type comparison, which
|
||||
has good accuracy on most backends. See :ref:`compare_types`
|
||||
for an example as well as information on other type
|
||||
comparison options. Set to ``False`` which disables type
|
||||
comparison. A callable can also be passed to provide custom type
|
||||
comparison, see :ref:`compare_types` for additional details.
|
||||
|
||||
.. versionchanged:: 1.12.0 The default value of
|
||||
:paramref:`.EnvironmentContext.configure.compare_type` has been
|
||||
changed to ``True``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`compare_types`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.compare_server_default`
|
||||
|
||||
:param compare_server_default: Indicates server default comparison
|
||||
behavior during
|
||||
an autogenerate operation. Defaults to ``False`` which disables
|
||||
server default
|
||||
comparison. Set to ``True`` to turn on server default comparison,
|
||||
which has
|
||||
varied accuracy depending on backend.
|
||||
|
||||
To customize server default comparison behavior, a callable may
|
||||
be specified
|
||||
which can filter server default comparisons during an
|
||||
autogenerate operation.
|
||||
defaults during an autogenerate operation. The format of this
|
||||
callable is::
|
||||
|
||||
def my_compare_server_default(context, inspected_column,
|
||||
metadata_column, inspected_default, metadata_default,
|
||||
rendered_metadata_default):
|
||||
# return True if the defaults are different,
|
||||
# False if not, or None to allow the default implementation
|
||||
# to compare these defaults
|
||||
return None
|
||||
|
||||
context.configure(
|
||||
# ...
|
||||
compare_server_default = my_compare_server_default
|
||||
)
|
||||
|
||||
``inspected_column`` is a dictionary structure as returned by
|
||||
:meth:`sqlalchemy.engine.reflection.Inspector.get_columns`, whereas
|
||||
``metadata_column`` is a :class:`sqlalchemy.schema.Column` from
|
||||
the local model environment.
|
||||
|
||||
A return value of ``None`` indicates to allow default server default
|
||||
comparison
|
||||
to proceed. Note that some backends such as Postgresql actually
|
||||
execute
|
||||
the two defaults on the database side to compare for equivalence.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.compare_type`
|
||||
|
||||
:param include_name: A callable function which is given
|
||||
the chance to return ``True`` or ``False`` for any database reflected
|
||||
object based on its name, including database schema names when
|
||||
the :paramref:`.EnvironmentContext.configure.include_schemas` flag
|
||||
is set to ``True``.
|
||||
|
||||
The function accepts the following positional arguments:
|
||||
|
||||
* ``name``: the name of the object, such as schema name or table name.
|
||||
Will be ``None`` when indicating the default schema name of the
|
||||
database connection.
|
||||
* ``type``: a string describing the type of object; currently
|
||||
``"schema"``, ``"table"``, ``"column"``, ``"index"``,
|
||||
``"unique_constraint"``, or ``"foreign_key_constraint"``
|
||||
* ``parent_names``: a dictionary of "parent" object names, that are
|
||||
relative to the name being given. Keys in this dictionary may
|
||||
include: ``"schema_name"``, ``"table_name"`` or
|
||||
``"schema_qualified_table_name"``.
|
||||
|
||||
E.g.::
|
||||
|
||||
def include_name(name, type_, parent_names):
|
||||
if type_ == "schema":
|
||||
return name in ["schema_one", "schema_two"]
|
||||
else:
|
||||
return True
|
||||
|
||||
context.configure(
|
||||
# ...
|
||||
include_schemas = True,
|
||||
include_name = include_name
|
||||
)
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogenerate_include_hooks`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_object`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_schemas`
|
||||
|
||||
|
||||
:param include_object: A callable function which is given
|
||||
the chance to return ``True`` or ``False`` for any object,
|
||||
indicating if the given object should be considered in the
|
||||
autogenerate sweep.
|
||||
|
||||
The function accepts the following positional arguments:
|
||||
|
||||
* ``object``: a :class:`~sqlalchemy.schema.SchemaItem` object such
|
||||
as a :class:`~sqlalchemy.schema.Table`,
|
||||
:class:`~sqlalchemy.schema.Column`,
|
||||
:class:`~sqlalchemy.schema.Index`
|
||||
:class:`~sqlalchemy.schema.UniqueConstraint`,
|
||||
or :class:`~sqlalchemy.schema.ForeignKeyConstraint` object
|
||||
* ``name``: the name of the object. This is typically available
|
||||
via ``object.name``.
|
||||
* ``type``: a string describing the type of object; currently
|
||||
``"table"``, ``"column"``, ``"index"``, ``"unique_constraint"``,
|
||||
or ``"foreign_key_constraint"``
|
||||
* ``reflected``: ``True`` if the given object was produced based on
|
||||
table reflection, ``False`` if it's from a local :class:`.MetaData`
|
||||
object.
|
||||
* ``compare_to``: the object being compared against, if available,
|
||||
else ``None``.
|
||||
|
||||
E.g.::
|
||||
|
||||
def include_object(object, name, type_, reflected, compare_to):
|
||||
if (type_ == "column" and
|
||||
not reflected and
|
||||
object.info.get("skip_autogenerate", False)):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
context.configure(
|
||||
# ...
|
||||
include_object = include_object
|
||||
)
|
||||
|
||||
For the use case of omitting specific schemas from a target database
|
||||
when :paramref:`.EnvironmentContext.configure.include_schemas` is
|
||||
set to ``True``, the :attr:`~sqlalchemy.schema.Table.schema`
|
||||
attribute can be checked for each :class:`~sqlalchemy.schema.Table`
|
||||
object passed to the hook, however it is much more efficient
|
||||
to filter on schemas before reflection of objects takes place
|
||||
using the :paramref:`.EnvironmentContext.configure.include_name`
|
||||
hook.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogenerate_include_hooks`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_name`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_schemas`
|
||||
|
||||
:param render_as_batch: if True, commands which alter elements
|
||||
within a table will be placed under a ``with batch_alter_table():``
|
||||
directive, so that batch migrations will take place.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`batch_migrations`
|
||||
|
||||
:param include_schemas: If True, autogenerate will scan across
|
||||
all schemas located by the SQLAlchemy
|
||||
:meth:`~sqlalchemy.engine.reflection.Inspector.get_schema_names`
|
||||
method, and include all differences in tables found across all
|
||||
those schemas. When using this option, you may want to also
|
||||
use the :paramref:`.EnvironmentContext.configure.include_name`
|
||||
parameter to specify a callable which
|
||||
can filter the tables/schemas that get included.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogenerate_include_hooks`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_name`
|
||||
|
||||
:paramref:`.EnvironmentContext.configure.include_object`
|
||||
|
||||
:param render_item: Callable that can be used to override how
|
||||
any schema item, i.e. column, constraint, type,
|
||||
etc., is rendered for autogenerate. The callable receives a
|
||||
string describing the type of object, the object, and
|
||||
the autogen context. If it returns False, the
|
||||
default rendering method will be used. If it returns None,
|
||||
the item will not be rendered in the context of a Table
|
||||
construct, that is, can be used to skip columns or constraints
|
||||
within op.create_table()::
|
||||
|
||||
def my_render_column(type_, col, autogen_context):
|
||||
if type_ == "column" and isinstance(col, MySpecialCol):
|
||||
return repr(col)
|
||||
else:
|
||||
return False
|
||||
|
||||
context.configure(
|
||||
# ...
|
||||
render_item = my_render_column
|
||||
)
|
||||
|
||||
Available values for the type string include: ``"column"``,
|
||||
``"primary_key"``, ``"foreign_key"``, ``"unique"``, ``"check"``,
|
||||
``"type"``, ``"server_default"``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogen_render_types`
|
||||
|
||||
:param upgrade_token: When autogenerate completes, the text of the
|
||||
candidate upgrade operations will be present in this template
|
||||
variable when ``script.py.mako`` is rendered. Defaults to
|
||||
``upgrades``.
|
||||
:param downgrade_token: When autogenerate completes, the text of the
|
||||
candidate downgrade operations will be present in this
|
||||
template variable when ``script.py.mako`` is rendered. Defaults to
|
||||
``downgrades``.
|
||||
|
||||
:param alembic_module_prefix: When autogenerate refers to Alembic
|
||||
:mod:`alembic.operations` constructs, this prefix will be used
|
||||
(i.e. ``op.create_table``) Defaults to "``op.``".
|
||||
Can be ``None`` to indicate no prefix.
|
||||
|
||||
:param sqlalchemy_module_prefix: When autogenerate refers to
|
||||
SQLAlchemy
|
||||
:class:`~sqlalchemy.schema.Column` or type classes, this prefix
|
||||
will be used
|
||||
(i.e. ``sa.Column("somename", sa.Integer)``) Defaults to "``sa.``".
|
||||
Can be ``None`` to indicate no prefix.
|
||||
Note that when dialect-specific types are rendered, autogenerate
|
||||
will render them using the dialect module name, i.e. ``mssql.BIT()``,
|
||||
``postgresql.UUID()``.
|
||||
|
||||
:param user_module_prefix: When autogenerate refers to a SQLAlchemy
|
||||
type (e.g. :class:`.TypeEngine`) where the module name is not
|
||||
under the ``sqlalchemy`` namespace, this prefix will be used
|
||||
within autogenerate. If left at its default of
|
||||
``None``, the ``__module__`` attribute of the type is used to
|
||||
render the import module. It's a good practice to set this
|
||||
and to have all custom types be available from a fixed module space,
|
||||
in order to future-proof migration files against reorganizations
|
||||
in modules.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`autogen_module_prefix`
|
||||
|
||||
:param process_revision_directives: a callable function that will
|
||||
be passed a structure representing the end result of an autogenerate
|
||||
or plain "revision" operation, which can be manipulated to affect
|
||||
how the ``alembic revision`` command ultimately outputs new
|
||||
revision scripts. The structure of the callable is::
|
||||
|
||||
def process_revision_directives(context, revision, directives):
|
||||
pass
|
||||
|
||||
The ``directives`` parameter is a Python list containing
|
||||
a single :class:`.MigrationScript` directive, which represents
|
||||
the revision file to be generated. This list as well as its
|
||||
contents may be freely modified to produce any set of commands.
|
||||
The section :ref:`customizing_revision` shows an example of
|
||||
doing this. The ``context`` parameter is the
|
||||
:class:`.MigrationContext` in use,
|
||||
and ``revision`` is a tuple of revision identifiers representing the
|
||||
current revision of the database.
|
||||
|
||||
The callable is invoked at all times when the ``--autogenerate``
|
||||
option is passed to ``alembic revision``. If ``--autogenerate``
|
||||
is not passed, the callable is invoked only if the
|
||||
``revision_environment`` variable is set to True in the Alembic
|
||||
configuration, in which case the given ``directives`` collection
|
||||
will contain empty :class:`.UpgradeOps` and :class:`.DowngradeOps`
|
||||
collections for ``.upgrade_ops`` and ``.downgrade_ops``. The
|
||||
``--autogenerate`` option itself can be inferred by inspecting
|
||||
``context.config.cmd_opts.autogenerate``.
|
||||
|
||||
The callable function may optionally be an instance of
|
||||
a :class:`.Rewriter` object. This is a helper object that
|
||||
assists in the production of autogenerate-stream rewriter functions.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`customizing_revision`
|
||||
|
||||
:ref:`autogen_rewriter`
|
||||
|
||||
:paramref:`.command.revision.process_revision_directives`
|
||||
|
||||
Parameters specific to individual backends:
|
||||
|
||||
:param mssql_batch_separator: The "batch separator" which will
|
||||
be placed between each statement when generating offline SQL Server
|
||||
migrations. Defaults to ``GO``. Note this is in addition to the
|
||||
customary semicolon ``;`` at the end of each statement; SQL Server
|
||||
considers the "batch separator" to denote the end of an
|
||||
individual statement execution, and cannot group certain
|
||||
dependent operations in one step.
|
||||
:param oracle_batch_separator: The "batch separator" which will
|
||||
be placed between each statement when generating offline
|
||||
Oracle migrations. Defaults to ``/``. Oracle doesn't add a
|
||||
semicolon between statements like most other backends.
|
||||
|
||||
"""
|
||||
|
||||
def execute(
|
||||
sql: Union[Executable, str],
|
||||
execution_options: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
"""Execute the given SQL using the current change context.
|
||||
|
||||
The behavior of :meth:`.execute` is the same
|
||||
as that of :meth:`.Operations.execute`. Please see that
|
||||
function's documentation for full detail including
|
||||
caveats and limitations.
|
||||
|
||||
This function requires that a :class:`.MigrationContext` has
|
||||
first been made available via :meth:`.configure`.
|
||||
|
||||
"""
|
||||
|
||||
def get_bind() -> Connection:
|
||||
"""Return the current 'bind'.
|
||||
|
||||
In "online" mode, this is the
|
||||
:class:`sqlalchemy.engine.Connection` currently being used
|
||||
to emit SQL to the database.
|
||||
|
||||
This function requires that a :class:`.MigrationContext`
|
||||
has first been made available via :meth:`.configure`.
|
||||
|
||||
"""
|
||||
|
||||
def get_context() -> MigrationContext:
|
||||
"""Return the current :class:`.MigrationContext` object.
|
||||
|
||||
If :meth:`.EnvironmentContext.configure` has not been
|
||||
called yet, raises an exception.
|
||||
|
||||
"""
|
||||
|
||||
def get_head_revision() -> Union[str, Tuple[str, ...], None]:
|
||||
"""Return the hex identifier of the 'head' script revision.
|
||||
|
||||
If the script directory has multiple heads, this
|
||||
method raises a :class:`.CommandError`;
|
||||
:meth:`.EnvironmentContext.get_head_revisions` should be preferred.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
.. seealso:: :meth:`.EnvironmentContext.get_head_revisions`
|
||||
|
||||
"""
|
||||
|
||||
def get_head_revisions() -> Union[str, Tuple[str, ...], None]:
|
||||
"""Return the hex identifier of the 'heads' script revision(s).
|
||||
|
||||
This returns a tuple containing the version number of all
|
||||
heads in the script directory.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
"""
|
||||
|
||||
def get_revision_argument() -> Union[str, Tuple[str, ...], None]:
|
||||
"""Get the 'destination' revision argument.
|
||||
|
||||
This is typically the argument passed to the
|
||||
``upgrade`` or ``downgrade`` command.
|
||||
|
||||
If it was specified as ``head``, the actual
|
||||
version number is returned; if specified
|
||||
as ``base``, ``None`` is returned.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
"""
|
||||
|
||||
def get_starting_revision_argument() -> Union[str, Tuple[str, ...], None]:
|
||||
"""Return the 'starting revision' argument,
|
||||
if the revision was passed using ``start:end``.
|
||||
|
||||
This is only meaningful in "offline" mode.
|
||||
Returns ``None`` if no value is available
|
||||
or was configured.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
"""
|
||||
|
||||
def get_tag_argument() -> Optional[str]:
|
||||
"""Return the value passed for the ``--tag`` argument, if any.
|
||||
|
||||
The ``--tag`` argument is not used directly by Alembic,
|
||||
but is available for custom ``env.py`` configurations that
|
||||
wish to use it; particularly for offline generation scripts
|
||||
that wish to generate tagged filenames.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.EnvironmentContext.get_x_argument` - a newer and more
|
||||
open ended system of extending ``env.py`` scripts via the command
|
||||
line.
|
||||
|
||||
"""
|
||||
|
||||
@overload
|
||||
def get_x_argument(as_dictionary: Literal[False]) -> List[str]: ...
|
||||
@overload
|
||||
def get_x_argument(as_dictionary: Literal[True]) -> Dict[str, str]: ...
|
||||
@overload
|
||||
def get_x_argument(
|
||||
as_dictionary: bool = ...,
|
||||
) -> Union[List[str], Dict[str, str]]:
|
||||
"""Return the value(s) passed for the ``-x`` argument, if any.
|
||||
|
||||
The ``-x`` argument is an open ended flag that allows any user-defined
|
||||
value or values to be passed on the command line, then available
|
||||
here for consumption by a custom ``env.py`` script.
|
||||
|
||||
The return value is a list, returned directly from the ``argparse``
|
||||
structure. If ``as_dictionary=True`` is passed, the ``x`` arguments
|
||||
are parsed using ``key=value`` format into a dictionary that is
|
||||
then returned. If there is no ``=`` in the argument, value is an empty
|
||||
string.
|
||||
|
||||
.. versionchanged:: 1.13.1 Support ``as_dictionary=True`` when
|
||||
arguments are passed without the ``=`` symbol.
|
||||
|
||||
For example, to support passing a database URL on the command line,
|
||||
the standard ``env.py`` script can be modified like this::
|
||||
|
||||
cmd_line_url = context.get_x_argument(
|
||||
as_dictionary=True).get('dbname')
|
||||
if cmd_line_url:
|
||||
engine = create_engine(cmd_line_url)
|
||||
else:
|
||||
engine = engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool)
|
||||
|
||||
This then takes effect by running the ``alembic`` script as::
|
||||
|
||||
alembic -x dbname=postgresql://user:pass@host/dbname upgrade head
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.EnvironmentContext.get_tag_argument`
|
||||
|
||||
:attr:`.Config.cmd_opts`
|
||||
|
||||
"""
|
||||
|
||||
def is_offline_mode() -> bool:
|
||||
"""Return True if the current migrations environment
|
||||
is running in "offline mode".
|
||||
|
||||
This is ``True`` or ``False`` depending
|
||||
on the ``--sql`` flag passed.
|
||||
|
||||
This function does not require that the :class:`.MigrationContext`
|
||||
has been configured.
|
||||
|
||||
"""
|
||||
|
||||
def is_transactional_ddl() -> bool:
|
||||
"""Return True if the context is configured to expect a
|
||||
transactional DDL capable backend.
|
||||
|
||||
This defaults to the type of database in use, and
|
||||
can be overridden by the ``transactional_ddl`` argument
|
||||
to :meth:`.configure`
|
||||
|
||||
This function requires that a :class:`.MigrationContext`
|
||||
has first been made available via :meth:`.configure`.
|
||||
|
||||
"""
|
||||
|
||||
def run_migrations(**kw: Any) -> None:
|
||||
"""Run migrations as determined by the current command line
|
||||
configuration
|
||||
as well as versioning information present (or not) in the current
|
||||
database connection (if one is present).
|
||||
|
||||
The function accepts optional ``**kw`` arguments. If these are
|
||||
passed, they are sent directly to the ``upgrade()`` and
|
||||
``downgrade()``
|
||||
functions within each target revision file. By modifying the
|
||||
``script.py.mako`` file so that the ``upgrade()`` and ``downgrade()``
|
||||
functions accept arguments, parameters can be passed here so that
|
||||
contextual information, usually information to identify a particular
|
||||
database in use, can be passed from a custom ``env.py`` script
|
||||
to the migration functions.
|
||||
|
||||
This function requires that a :class:`.MigrationContext` has
|
||||
first been made available via :meth:`.configure`.
|
||||
|
||||
"""
|
||||
|
||||
script: ScriptDirectory
|
||||
|
||||
def static_output(text: str) -> None:
|
||||
"""Emit text directly to the "offline" SQL stream.
|
||||
|
||||
Typically this is for emitting comments that
|
||||
start with --. The statement is not treated
|
||||
as a SQL execution, no ; or batch separator
|
||||
is added, etc.
|
||||
|
||||
"""
|
||||
6
venv/Lib/site-packages/alembic/ddl/__init__.py
Normal file
6
venv/Lib/site-packages/alembic/ddl/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from . import mssql
|
||||
from . import mysql
|
||||
from . import oracle
|
||||
from . import postgresql
|
||||
from . import sqlite
|
||||
from .impl import DefaultImpl as DefaultImpl
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
329
venv/Lib/site-packages/alembic/ddl/_autogen.py
Normal file
329
venv/Lib/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
|
||||
364
venv/Lib/site-packages/alembic/ddl/base.py
Normal file
364
venv/Lib/site-packages/alembic/ddl/base.py
Normal file
@@ -0,0 +1,364 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy import Integer
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.schema import Column
|
||||
from sqlalchemy.schema import DDLElement
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
|
||||
from ..util.sqla_compat import _columns_for_constraint # noqa
|
||||
from ..util.sqla_compat import _find_columns # noqa
|
||||
from ..util.sqla_compat import _fk_spec # noqa
|
||||
from ..util.sqla_compat import _is_type_bound # noqa
|
||||
from ..util.sqla_compat import _table_for_constraint # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from 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
|
||||
from sqlalchemy.sql.functions import Function
|
||||
from sqlalchemy.sql.schema import FetchedValue
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .impl import DefaultImpl
|
||||
|
||||
_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
|
||||
is required, not a full Table object.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
table_name: str,
|
||||
schema: Optional[Union[quoted_name, str]] = None,
|
||||
) -> None:
|
||||
self.table_name = table_name
|
||||
self.schema = schema
|
||||
|
||||
|
||||
class RenameTable(AlterTable):
|
||||
def __init__(
|
||||
self,
|
||||
old_table_name: str,
|
||||
new_table_name: Union[quoted_name, str],
|
||||
schema: Optional[Union[quoted_name, str]] = None,
|
||||
) -> None:
|
||||
super().__init__(old_table_name, schema=schema)
|
||||
self.new_table_name = new_table_name
|
||||
|
||||
|
||||
class AlterColumn(AlterTable):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
schema: Optional[str] = None,
|
||||
existing_type: Optional[TypeEngine] = None,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_server_default: Optional[_ServerDefault] = None,
|
||||
existing_comment: Optional[str] = None,
|
||||
) -> None:
|
||||
super().__init__(name, schema=schema)
|
||||
self.column_name = column_name
|
||||
self.existing_type = (
|
||||
sqltypes.to_instance(existing_type)
|
||||
if existing_type is not None
|
||||
else None
|
||||
)
|
||||
self.existing_nullable = existing_nullable
|
||||
self.existing_server_default = existing_server_default
|
||||
self.existing_comment = existing_comment
|
||||
|
||||
|
||||
class ColumnNullable(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, nullable: bool, **kw
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.nullable = nullable
|
||||
|
||||
|
||||
class ColumnType(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, type_: TypeEngine, **kw
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.type_ = sqltypes.to_instance(type_)
|
||||
|
||||
|
||||
class ColumnName(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, newname: str, **kw
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.newname = newname
|
||||
|
||||
|
||||
class ColumnDefault(AlterColumn):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
default: Optional[_ServerDefault],
|
||||
**kw,
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.default = default
|
||||
|
||||
|
||||
class ComputedColumnDefault(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, default: Optional[Computed], **kw
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.default = default
|
||||
|
||||
|
||||
class IdentityColumnDefault(AlterColumn):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
default: Optional[Identity],
|
||||
impl: DefaultImpl,
|
||||
**kw,
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.default = default
|
||||
self.impl = impl
|
||||
|
||||
|
||||
class AddColumn(AlterTable):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column: Column[Any],
|
||||
schema: Optional[Union[quoted_name, str]] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> 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,
|
||||
if_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
super().__init__(name, schema=schema)
|
||||
self.column = column
|
||||
self.if_exists = if_exists
|
||||
|
||||
|
||||
class ColumnComment(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, comment: Optional[str], **kw
|
||||
) -> None:
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.comment = comment
|
||||
|
||||
|
||||
@compiles(RenameTable)
|
||||
def visit_rename_table(
|
||||
element: RenameTable, compiler: DDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s RENAME TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_table_name(compiler, element.new_table_name, element.schema),
|
||||
)
|
||||
|
||||
|
||||
@compiles(AddColumn)
|
||||
def visit_add_column(element: AddColumn, compiler: DDLCompiler, **kw) -> str:
|
||||
return "%s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
add_column(
|
||||
compiler, element.column, if_not_exists=element.if_not_exists, **kw
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(DropColumn)
|
||||
def visit_drop_column(element: DropColumn, compiler: DDLCompiler, **kw) -> str:
|
||||
return "%s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
drop_column(
|
||||
compiler, element.column.name, if_exists=element.if_exists, **kw
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnNullable)
|
||||
def visit_column_nullable(
|
||||
element: ColumnNullable, compiler: DDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
"DROP NOT NULL" if element.nullable else "SET NOT NULL",
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnType)
|
||||
def visit_column_type(element: ColumnType, compiler: DDLCompiler, **kw) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
"TYPE %s" % format_type(compiler, element.type_),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnName)
|
||||
def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
|
||||
return "%s RENAME %s TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
format_column_name(compiler, element.newname),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnDefault)
|
||||
def visit_column_default(
|
||||
element: ColumnDefault, compiler: DDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
(
|
||||
"SET DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DROP DEFAULT"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ComputedColumnDefault)
|
||||
def visit_computed_column(
|
||||
element: ComputedColumnDefault, compiler: DDLCompiler, **kw
|
||||
):
|
||||
raise exc.CompileError(
|
||||
'Adding or removing a "computed" construct, e.g. GENERATED '
|
||||
"ALWAYS AS, to or from an existing column is not supported."
|
||||
)
|
||||
|
||||
|
||||
@compiles(IdentityColumnDefault)
|
||||
def visit_identity_column(
|
||||
element: IdentityColumnDefault, compiler: DDLCompiler, **kw
|
||||
):
|
||||
raise exc.CompileError(
|
||||
'Adding, removing or modifying an "identity" construct, '
|
||||
"e.g. GENERATED AS IDENTITY, to or from an existing "
|
||||
"column is not supported in this dialect."
|
||||
)
|
||||
|
||||
|
||||
def quote_dotted(
|
||||
name: Union[quoted_name, str], quote: functools.partial
|
||||
) -> Union[quoted_name, str]:
|
||||
"""quote the elements of a dotted name"""
|
||||
|
||||
if isinstance(name, quoted_name):
|
||||
return quote(name)
|
||||
result = ".".join([quote(x) for x in name.split(".")])
|
||||
return result
|
||||
|
||||
|
||||
def format_table_name(
|
||||
compiler: Compiled,
|
||||
name: Union[quoted_name, str],
|
||||
schema: Optional[Union[quoted_name, str]],
|
||||
) -> Union[quoted_name, str]:
|
||||
quote = functools.partial(compiler.preparer.quote)
|
||||
if schema:
|
||||
return quote_dotted(schema, quote) + "." + quote(name)
|
||||
else:
|
||||
return quote(name)
|
||||
|
||||
|
||||
def format_column_name(
|
||||
compiler: DDLCompiler, name: Optional[Union[quoted_name, str]]
|
||||
) -> Union[quoted_name, str]:
|
||||
return compiler.preparer.quote(name) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def format_server_default(
|
||||
compiler: DDLCompiler,
|
||||
default: Optional[_ServerDefault],
|
||||
) -> str:
|
||||
# this can be updated to use compiler.render_default_string
|
||||
# for SQLAlchemy 2.0 and above; not in 1.4
|
||||
default_str = compiler.get_column_default_string(
|
||||
Column("x", Integer, server_default=default)
|
||||
)
|
||||
assert default_str is not None
|
||||
return default_str
|
||||
|
||||
|
||||
def format_type(compiler: DDLCompiler, type_: TypeEngine) -> str:
|
||||
return compiler.dialect.type_compiler.process(type_)
|
||||
|
||||
|
||||
def alter_table(
|
||||
compiler: DDLCompiler,
|
||||
name: str,
|
||||
schema: Optional[str],
|
||||
) -> str:
|
||||
return "ALTER TABLE %s" % format_table_name(compiler, name, schema)
|
||||
|
||||
|
||||
def drop_column(
|
||||
compiler: DDLCompiler, name: str, if_exists: Optional[bool] = None, **kw
|
||||
) -> str:
|
||||
return "DROP COLUMN %s%s" % (
|
||||
"IF EXISTS " if if_exists else "",
|
||||
format_column_name(compiler, name),
|
||||
)
|
||||
|
||||
|
||||
def alter_column(compiler: DDLCompiler, name: str) -> str:
|
||||
return "ALTER COLUMN %s" % format_column_name(compiler, name)
|
||||
|
||||
|
||||
def add_column(
|
||||
compiler: DDLCompiler,
|
||||
column: Column[Any],
|
||||
if_not_exists: Optional[bool] = None,
|
||||
**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
|
||||
)
|
||||
if const:
|
||||
text += " " + const
|
||||
|
||||
return text
|
||||
902
venv/Lib/site-packages/alembic/ddl/impl.py
Normal file
902
venv/Lib/site-packages/alembic/ddl/impl.py
Normal file
@@ -0,0 +1,902 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import NamedTuple
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import cast
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import PrimaryKeyConstraint
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy import text
|
||||
|
||||
from . import _autogen
|
||||
from . import base
|
||||
from ._autogen import _constraint_sig as _constraint_sig
|
||||
from ._autogen import ComparisonResult as ComparisonResult
|
||||
from .. import util
|
||||
from ..util import sqla_compat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
from typing import TextIO
|
||||
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.engine import Dialect
|
||||
from sqlalchemy.engine.cursor import CursorResult
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
from sqlalchemy.sql import ClauseElement
|
||||
from sqlalchemy.sql import Executable
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.sql.schema import Index
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
from sqlalchemy.sql.selectable import TableClause
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .base import _ServerDefault
|
||||
from ..autogenerate.api import AutogenContext
|
||||
from ..operations.batch import ApplyBatchImpl
|
||||
from ..operations.batch import BatchOperationsImpl
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImplMeta(type):
|
||||
def __init__(
|
||||
cls,
|
||||
classname: str,
|
||||
bases: Tuple[Type[DefaultImpl]],
|
||||
dict_: Dict[str, Any],
|
||||
):
|
||||
newtype = type.__init__(cls, classname, bases, dict_)
|
||||
if "__dialect__" in dict_:
|
||||
_impls[dict_["__dialect__"]] = cls # type: ignore[assignment]
|
||||
return newtype
|
||||
|
||||
|
||||
_impls: Dict[str, Type[DefaultImpl]] = {}
|
||||
|
||||
|
||||
class DefaultImpl(metaclass=ImplMeta):
|
||||
"""Provide the entrypoint for major migration operations,
|
||||
including database-specific behavioral variances.
|
||||
|
||||
While individual SQL/DDL constructs already provide
|
||||
for database-specific implementations, variances here
|
||||
allow for entirely different sequences of operations
|
||||
to take place for a particular migration, such as
|
||||
SQL Server's special 'IDENTITY INSERT' step for
|
||||
bulk inserts.
|
||||
|
||||
"""
|
||||
|
||||
__dialect__ = "default"
|
||||
|
||||
transactional_ddl = False
|
||||
command_terminator = ";"
|
||||
type_synonyms: Tuple[Set[str], ...] = ({"NUMERIC", "DECIMAL"},)
|
||||
type_arg_extract: Sequence[str] = ()
|
||||
# These attributes are deprecated in SQLAlchemy via #10247. They need to
|
||||
# be ignored to support older version that did not use dialect kwargs.
|
||||
# They only apply to Oracle and are replaced by oracle_order,
|
||||
# oracle_on_null
|
||||
identity_attrs_ignore: Tuple[str, ...] = ("order", "on_null")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dialect: Dialect,
|
||||
connection: Optional[Connection],
|
||||
as_sql: bool,
|
||||
transactional_ddl: Optional[bool],
|
||||
output_buffer: Optional[TextIO],
|
||||
context_opts: Dict[str, Any],
|
||||
) -> None:
|
||||
self.dialect = dialect
|
||||
self.connection = connection
|
||||
self.as_sql = as_sql
|
||||
self.literal_binds = context_opts.get("literal_binds", False)
|
||||
|
||||
self.output_buffer = output_buffer
|
||||
self.memo: dict = {}
|
||||
self.context_opts = context_opts
|
||||
if transactional_ddl is not None:
|
||||
self.transactional_ddl = transactional_ddl
|
||||
|
||||
if self.literal_binds:
|
||||
if not self.as_sql:
|
||||
raise util.CommandError(
|
||||
"Can't use literal_binds setting without as_sql mode"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_by_dialect(cls, dialect: Dialect) -> Type[DefaultImpl]:
|
||||
return _impls[dialect.name]
|
||||
|
||||
def static_output(self, text: str) -> None:
|
||||
assert self.output_buffer is not None
|
||||
self.output_buffer.write(text + "\n\n")
|
||||
self.output_buffer.flush()
|
||||
|
||||
def version_table_impl(
|
||||
self,
|
||||
*,
|
||||
version_table: str,
|
||||
version_table_schema: Optional[str],
|
||||
version_table_pk: bool,
|
||||
**kw: Any,
|
||||
) -> Table:
|
||||
"""Generate a :class:`.Table` object which will be used as the
|
||||
structure for the Alembic version table.
|
||||
|
||||
Third party dialects may override this hook to provide an alternate
|
||||
structure for this :class:`.Table`; requirements are only that it
|
||||
be named based on the ``version_table`` parameter and contains
|
||||
at least a single string-holding column named ``version_num``.
|
||||
|
||||
.. versionadded:: 1.14
|
||||
|
||||
"""
|
||||
vt = Table(
|
||||
version_table,
|
||||
MetaData(),
|
||||
Column("version_num", String(32), nullable=False),
|
||||
schema=version_table_schema,
|
||||
)
|
||||
if version_table_pk:
|
||||
vt.append_constraint(
|
||||
PrimaryKeyConstraint(
|
||||
"version_num", name=f"{version_table}_pkc"
|
||||
)
|
||||
)
|
||||
|
||||
return vt
|
||||
|
||||
def requires_recreate_in_batch(
|
||||
self, batch_op: BatchOperationsImpl
|
||||
) -> bool:
|
||||
"""Return True if the given :class:`.BatchOperationsImpl`
|
||||
would need the table to be recreated and copied in order to
|
||||
proceed.
|
||||
|
||||
Normally, only returns True on SQLite when operations other
|
||||
than add_column are present.
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
def prep_table_for_batch(
|
||||
self, batch_impl: ApplyBatchImpl, table: Table
|
||||
) -> None:
|
||||
"""perform any operations needed on a table before a new
|
||||
one is created to replace it in batch mode.
|
||||
|
||||
the PG dialect uses this to drop constraints on the table
|
||||
before the new one uses those same names.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def bind(self) -> Optional[Connection]:
|
||||
return self.connection
|
||||
|
||||
def _exec(
|
||||
self,
|
||||
construct: Union[Executable, str],
|
||||
execution_options: Optional[Mapping[str, Any]] = None,
|
||||
multiparams: Optional[Sequence[Mapping[str, Any]]] = None,
|
||||
params: Mapping[str, Any] = util.immutabledict(),
|
||||
) -> Optional[CursorResult]:
|
||||
if isinstance(construct, str):
|
||||
construct = text(construct)
|
||||
if self.as_sql:
|
||||
if multiparams is not None or params:
|
||||
raise TypeError("SQL parameters not allowed with as_sql")
|
||||
|
||||
compile_kw: dict[str, Any]
|
||||
if self.literal_binds and not isinstance(
|
||||
construct, schema.DDLElement
|
||||
):
|
||||
compile_kw = dict(compile_kwargs={"literal_binds": True})
|
||||
else:
|
||||
compile_kw = {}
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(construct, ClauseElement)
|
||||
compiled = construct.compile(dialect=self.dialect, **compile_kw)
|
||||
self.static_output(
|
||||
str(compiled).replace("\t", " ").strip()
|
||||
+ self.command_terminator
|
||||
)
|
||||
return None
|
||||
else:
|
||||
conn = self.connection
|
||||
assert conn is not None
|
||||
if execution_options:
|
||||
conn = conn.execution_options(**execution_options)
|
||||
|
||||
if params and multiparams is not None:
|
||||
raise TypeError(
|
||||
"Can't send params and multiparams at the same time"
|
||||
)
|
||||
|
||||
if multiparams:
|
||||
return conn.execute(construct, multiparams)
|
||||
else:
|
||||
return conn.execute(construct, params)
|
||||
|
||||
def execute(
|
||||
self,
|
||||
sql: Union[Executable, str],
|
||||
execution_options: Optional[dict[str, Any]] = None,
|
||||
) -> None:
|
||||
self._exec(sql, execution_options)
|
||||
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
autoincrement: Optional[bool] = None,
|
||||
comment: Optional[Union[str, Literal[False]]] = False,
|
||||
existing_comment: Optional[str] = None,
|
||||
existing_type: Optional[TypeEngine] = None,
|
||||
existing_server_default: Optional[_ServerDefault] = None,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_autoincrement: Optional[bool] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
if autoincrement is not None or existing_autoincrement is not None:
|
||||
util.warn(
|
||||
"autoincrement and existing_autoincrement "
|
||||
"only make sense for MySQL",
|
||||
stacklevel=3,
|
||||
)
|
||||
if nullable is not None:
|
||||
self._exec(
|
||||
base.ColumnNullable(
|
||||
table_name,
|
||||
column_name,
|
||||
nullable,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
existing_comment=existing_comment,
|
||||
)
|
||||
)
|
||||
if server_default is not False:
|
||||
kw = {}
|
||||
cls_: Type[
|
||||
Union[
|
||||
base.ComputedColumnDefault,
|
||||
base.IdentityColumnDefault,
|
||||
base.ColumnDefault,
|
||||
]
|
||||
]
|
||||
if sqla_compat._server_default_is_computed(
|
||||
server_default, existing_server_default
|
||||
):
|
||||
cls_ = base.ComputedColumnDefault
|
||||
elif sqla_compat._server_default_is_identity(
|
||||
server_default, existing_server_default
|
||||
):
|
||||
cls_ = base.IdentityColumnDefault
|
||||
kw["impl"] = self
|
||||
else:
|
||||
cls_ = base.ColumnDefault
|
||||
self._exec(
|
||||
cls_(
|
||||
table_name,
|
||||
column_name,
|
||||
server_default, # type:ignore[arg-type]
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
existing_comment=existing_comment,
|
||||
**kw,
|
||||
)
|
||||
)
|
||||
if type_ is not None:
|
||||
self._exec(
|
||||
base.ColumnType(
|
||||
table_name,
|
||||
column_name,
|
||||
type_,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
existing_comment=existing_comment,
|
||||
)
|
||||
)
|
||||
|
||||
if comment is not False:
|
||||
self._exec(
|
||||
base.ColumnComment(
|
||||
table_name,
|
||||
column_name,
|
||||
comment,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
existing_comment=existing_comment,
|
||||
)
|
||||
)
|
||||
|
||||
# do the new name last ;)
|
||||
if name is not None:
|
||||
self._exec(
|
||||
base.ColumnName(
|
||||
table_name,
|
||||
column_name,
|
||||
name,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
)
|
||||
)
|
||||
|
||||
def add_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[Union[str, quoted_name]] = None,
|
||||
if_not_exists: Optional[bool] = None,
|
||||
) -> None:
|
||||
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, 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, **kw: Any) -> None:
|
||||
self._exec(schema.DropConstraint(const, **kw))
|
||||
|
||||
def rename_table(
|
||||
self,
|
||||
old_table_name: str,
|
||||
new_table_name: Union[str, quoted_name],
|
||||
schema: Optional[Union[str, quoted_name]] = None,
|
||||
) -> None:
|
||||
self._exec(
|
||||
base.RenameTable(old_table_name, new_table_name, schema=schema)
|
||||
)
|
||||
|
||||
def create_table(self, table: Table, **kw: Any) -> None:
|
||||
table.dispatch.before_create(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
self._exec(schema.CreateTable(table, **kw))
|
||||
table.dispatch.after_create(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
for index in table.indexes:
|
||||
self._exec(schema.CreateIndex(index))
|
||||
|
||||
with_comment = (
|
||||
self.dialect.supports_comments and not self.dialect.inline_comments
|
||||
)
|
||||
comment = table.comment
|
||||
if comment and with_comment:
|
||||
self.create_table_comment(table)
|
||||
|
||||
for column in table.columns:
|
||||
comment = column.comment
|
||||
if comment and with_comment:
|
||||
self.create_column_comment(column)
|
||||
|
||||
def drop_table(self, table: Table, **kw: Any) -> None:
|
||||
table.dispatch.before_drop(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
self._exec(schema.DropTable(table, **kw))
|
||||
table.dispatch.after_drop(
|
||||
table, self.connection, checkfirst=False, _ddl_runner=self
|
||||
)
|
||||
|
||||
def create_index(self, index: Index, **kw: Any) -> None:
|
||||
self._exec(schema.CreateIndex(index, **kw))
|
||||
|
||||
def create_table_comment(self, table: Table) -> None:
|
||||
self._exec(schema.SetTableComment(table))
|
||||
|
||||
def drop_table_comment(self, table: Table) -> None:
|
||||
self._exec(schema.DropTableComment(table))
|
||||
|
||||
def create_column_comment(self, column: Column[Any]) -> None:
|
||||
self._exec(schema.SetColumnComment(column))
|
||||
|
||||
def drop_index(self, index: Index, **kw: Any) -> None:
|
||||
self._exec(schema.DropIndex(index, **kw))
|
||||
|
||||
def bulk_insert(
|
||||
self,
|
||||
table: Union[TableClause, Table],
|
||||
rows: List[dict],
|
||||
multiinsert: bool = True,
|
||||
) -> None:
|
||||
if not isinstance(rows, list):
|
||||
raise TypeError("List expected")
|
||||
elif rows and not isinstance(rows[0], dict):
|
||||
raise TypeError("List of dictionaries expected")
|
||||
if self.as_sql:
|
||||
for row in rows:
|
||||
self._exec(
|
||||
table.insert()
|
||||
.inline()
|
||||
.values(
|
||||
**{
|
||||
k: (
|
||||
sqla_compat._literal_bindparam(
|
||||
k, v, type_=table.c[k].type
|
||||
)
|
||||
if not isinstance(
|
||||
v, sqla_compat._literal_bindparam
|
||||
)
|
||||
else v
|
||||
)
|
||||
for k, v in row.items()
|
||||
}
|
||||
)
|
||||
)
|
||||
else:
|
||||
if rows:
|
||||
if multiinsert:
|
||||
self._exec(table.insert().inline(), multiparams=rows)
|
||||
else:
|
||||
for row in rows:
|
||||
self._exec(table.insert().inline().values(**row))
|
||||
|
||||
def _tokenize_column_type(self, column: Column) -> Params:
|
||||
definition: str
|
||||
definition = self.dialect.type_compiler.process(column.type).lower()
|
||||
|
||||
# tokenize the SQLAlchemy-generated version of a type, so that
|
||||
# the two can be compared.
|
||||
#
|
||||
# examples:
|
||||
# NUMERIC(10, 5)
|
||||
# TIMESTAMP WITH TIMEZONE
|
||||
# INTEGER UNSIGNED
|
||||
# INTEGER (10) UNSIGNED
|
||||
# INTEGER(10) UNSIGNED
|
||||
# varchar character set utf8
|
||||
#
|
||||
|
||||
tokens: List[str] = re.findall(r"[\w\-_]+|\(.+?\)", definition)
|
||||
|
||||
term_tokens: List[str] = []
|
||||
paren_term = None
|
||||
|
||||
for token in tokens:
|
||||
if re.match(r"^\(.*\)$", token):
|
||||
paren_term = token
|
||||
else:
|
||||
term_tokens.append(token)
|
||||
|
||||
params = Params(term_tokens[0], term_tokens[1:], [], {})
|
||||
|
||||
if paren_term:
|
||||
term: str
|
||||
for term in re.findall("[^(),]+", paren_term):
|
||||
if "=" in term:
|
||||
key, val = term.split("=")
|
||||
params.kwargs[key.strip()] = val.strip()
|
||||
else:
|
||||
params.args.append(term.strip())
|
||||
|
||||
return params
|
||||
|
||||
def _column_types_match(
|
||||
self, inspector_params: Params, metadata_params: Params
|
||||
) -> bool:
|
||||
if inspector_params.token0 == metadata_params.token0:
|
||||
return True
|
||||
|
||||
synonyms = [{t.lower() for t in batch} for batch in self.type_synonyms]
|
||||
inspector_all_terms = " ".join(
|
||||
[inspector_params.token0] + inspector_params.tokens
|
||||
)
|
||||
metadata_all_terms = " ".join(
|
||||
[metadata_params.token0] + metadata_params.tokens
|
||||
)
|
||||
|
||||
for batch in synonyms:
|
||||
if {inspector_all_terms, metadata_all_terms}.issubset(batch) or {
|
||||
inspector_params.token0,
|
||||
metadata_params.token0,
|
||||
}.issubset(batch):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _column_args_match(
|
||||
self, inspected_params: Params, meta_params: Params
|
||||
) -> bool:
|
||||
"""We want to compare column parameters. However, we only want
|
||||
to compare parameters that are set. If they both have `collation`,
|
||||
we want to make sure they are the same. However, if only one
|
||||
specifies it, dont flag it for being less specific
|
||||
"""
|
||||
|
||||
if (
|
||||
len(meta_params.tokens) == len(inspected_params.tokens)
|
||||
and meta_params.tokens != inspected_params.tokens
|
||||
):
|
||||
return False
|
||||
|
||||
if (
|
||||
len(meta_params.args) == len(inspected_params.args)
|
||||
and meta_params.args != inspected_params.args
|
||||
):
|
||||
return False
|
||||
|
||||
insp = " ".join(inspected_params.tokens).lower()
|
||||
meta = " ".join(meta_params.tokens).lower()
|
||||
|
||||
for reg in self.type_arg_extract:
|
||||
mi = re.search(reg, insp)
|
||||
mm = re.search(reg, meta)
|
||||
|
||||
if mi and mm and mi.group(1) != mm.group(1):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def compare_type(
|
||||
self, inspector_column: Column[Any], metadata_column: Column
|
||||
) -> bool:
|
||||
"""Returns True if there ARE differences between the types of the two
|
||||
columns. Takes impl.type_synonyms into account between retrospected
|
||||
and metadata types
|
||||
"""
|
||||
inspector_params = self._tokenize_column_type(inspector_column)
|
||||
metadata_params = self._tokenize_column_type(metadata_column)
|
||||
|
||||
if not self._column_types_match(inspector_params, metadata_params):
|
||||
return True
|
||||
if not self._column_args_match(inspector_params, metadata_params):
|
||||
return True
|
||||
return False
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column,
|
||||
metadata_column,
|
||||
rendered_metadata_default,
|
||||
rendered_inspector_default,
|
||||
):
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
|
||||
def correct_for_autogen_constraints(
|
||||
self,
|
||||
conn_uniques: Set[UniqueConstraint],
|
||||
conn_indexes: Set[Index],
|
||||
metadata_unique_constraints: Set[UniqueConstraint],
|
||||
metadata_indexes: Set[Index],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def cast_for_batch_migrate(self, existing, existing_transfer, new_type):
|
||||
if existing.type._type_affinity is not new_type._type_affinity:
|
||||
existing_transfer["expr"] = cast(
|
||||
existing_transfer["expr"], new_type
|
||||
)
|
||||
|
||||
def render_ddl_sql_expr(
|
||||
self, expr: ClauseElement, is_server_default: bool = False, **kw: Any
|
||||
) -> str:
|
||||
"""Render a SQL expression that is typically a server default,
|
||||
index expression, etc.
|
||||
|
||||
"""
|
||||
|
||||
compile_kw = {"literal_binds": True, "include_table": False}
|
||||
|
||||
return str(
|
||||
expr.compile(dialect=self.dialect, compile_kwargs=compile_kw)
|
||||
)
|
||||
|
||||
def _compat_autogen_column_reflect(self, inspector: Inspector) -> Callable:
|
||||
return self.autogen_column_reflect
|
||||
|
||||
def correct_for_autogen_foreignkeys(
|
||||
self,
|
||||
conn_fks: Set[ForeignKeyConstraint],
|
||||
metadata_fks: Set[ForeignKeyConstraint],
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def autogen_column_reflect(self, inspector, table, column_info):
|
||||
"""A hook that is attached to the 'column_reflect' event for when
|
||||
a Table is reflected from the database during the autogenerate
|
||||
process.
|
||||
|
||||
Dialects can elect to modify the information gathered here.
|
||||
|
||||
"""
|
||||
|
||||
def start_migrations(self) -> None:
|
||||
"""A hook called when :meth:`.EnvironmentContext.run_migrations`
|
||||
is called.
|
||||
|
||||
Implementations can set up per-migration-run state here.
|
||||
|
||||
"""
|
||||
|
||||
def emit_begin(self) -> None:
|
||||
"""Emit the string ``BEGIN``, or the backend-specific
|
||||
equivalent, on the current connection context.
|
||||
|
||||
This is used in offline mode and typically
|
||||
via :meth:`.EnvironmentContext.begin_transaction`.
|
||||
|
||||
"""
|
||||
self.static_output("BEGIN" + self.command_terminator)
|
||||
|
||||
def emit_commit(self) -> None:
|
||||
"""Emit the string ``COMMIT``, or the backend-specific
|
||||
equivalent, on the current connection context.
|
||||
|
||||
This is used in offline mode and typically
|
||||
via :meth:`.EnvironmentContext.begin_transaction`.
|
||||
|
||||
"""
|
||||
self.static_output("COMMIT" + self.command_terminator)
|
||||
|
||||
def render_type(
|
||||
self, type_obj: TypeEngine, autogen_context: AutogenContext
|
||||
) -> Union[str, Literal[False]]:
|
||||
return False
|
||||
|
||||
def _compare_identity_default(self, metadata_identity, inspector_identity):
|
||||
# ignored contains the attributes that were not considered
|
||||
# because assumed to their default values in the db.
|
||||
diff, ignored = _compare_identity_options(
|
||||
metadata_identity,
|
||||
inspector_identity,
|
||||
schema.Identity(),
|
||||
skip={"always"},
|
||||
)
|
||||
|
||||
meta_always = getattr(metadata_identity, "always", None)
|
||||
inspector_always = getattr(inspector_identity, "always", None)
|
||||
# None and False are the same in this comparison
|
||||
if bool(meta_always) != bool(inspector_always):
|
||||
diff.add("always")
|
||||
|
||||
diff.difference_update(self.identity_attrs_ignore)
|
||||
|
||||
# returns 3 values:
|
||||
return (
|
||||
# different identity attributes
|
||||
diff,
|
||||
# ignored identity attributes
|
||||
ignored,
|
||||
# if the two identity should be considered different
|
||||
bool(diff) or bool(metadata_identity) != bool(inspector_identity),
|
||||
)
|
||||
|
||||
def _compare_index_unique(
|
||||
self, metadata_index: Index, reflected_index: Index
|
||||
) -> Optional[str]:
|
||||
conn_unique = bool(reflected_index.unique)
|
||||
meta_unique = bool(metadata_index.unique)
|
||||
if conn_unique != meta_unique:
|
||||
return f"unique={conn_unique} to unique={meta_unique}"
|
||||
else:
|
||||
return None
|
||||
|
||||
def _create_metadata_constraint_sig(
|
||||
self, constraint: _autogen._C, **opts: Any
|
||||
) -> _constraint_sig[_autogen._C]:
|
||||
return _constraint_sig.from_constraint(True, self, constraint, **opts)
|
||||
|
||||
def _create_reflected_constraint_sig(
|
||||
self, constraint: _autogen._C, **opts: Any
|
||||
) -> _constraint_sig[_autogen._C]:
|
||||
return _constraint_sig.from_constraint(False, self, constraint, **opts)
|
||||
|
||||
def compare_indexes(
|
||||
self,
|
||||
metadata_index: Index,
|
||||
reflected_index: Index,
|
||||
) -> ComparisonResult:
|
||||
"""Compare two indexes by comparing the signature generated by
|
||||
``create_index_sig``.
|
||||
|
||||
This method returns a ``ComparisonResult``.
|
||||
"""
|
||||
msg: List[str] = []
|
||||
unique_msg = self._compare_index_unique(
|
||||
metadata_index, reflected_index
|
||||
)
|
||||
if unique_msg:
|
||||
msg.append(unique_msg)
|
||||
m_sig = self._create_metadata_constraint_sig(metadata_index)
|
||||
r_sig = self._create_reflected_constraint_sig(reflected_index)
|
||||
|
||||
assert _autogen.is_index_sig(m_sig)
|
||||
assert _autogen.is_index_sig(r_sig)
|
||||
|
||||
# The assumption is that the index have no expression
|
||||
for sig in m_sig, r_sig:
|
||||
if sig.has_expressions:
|
||||
log.warning(
|
||||
"Generating approximate signature for index %s. "
|
||||
"The dialect "
|
||||
"implementation should either skip expression indexes "
|
||||
"or provide a custom implementation.",
|
||||
sig.const,
|
||||
)
|
||||
|
||||
if m_sig.column_names != r_sig.column_names:
|
||||
msg.append(
|
||||
f"expression {r_sig.column_names} to {m_sig.column_names}"
|
||||
)
|
||||
|
||||
if msg:
|
||||
return ComparisonResult.Different(msg)
|
||||
else:
|
||||
return ComparisonResult.Equal()
|
||||
|
||||
def compare_unique_constraint(
|
||||
self,
|
||||
metadata_constraint: UniqueConstraint,
|
||||
reflected_constraint: UniqueConstraint,
|
||||
) -> ComparisonResult:
|
||||
"""Compare two unique constraints by comparing the two signatures.
|
||||
|
||||
The arguments are two tuples that contain the unique constraint and
|
||||
the signatures generated by ``create_unique_constraint_sig``.
|
||||
|
||||
This method returns a ``ComparisonResult``.
|
||||
"""
|
||||
metadata_tup = self._create_metadata_constraint_sig(
|
||||
metadata_constraint
|
||||
)
|
||||
reflected_tup = self._create_reflected_constraint_sig(
|
||||
reflected_constraint
|
||||
)
|
||||
|
||||
meta_sig = metadata_tup.unnamed
|
||||
conn_sig = reflected_tup.unnamed
|
||||
if conn_sig != meta_sig:
|
||||
return ComparisonResult.Different(
|
||||
f"expression {conn_sig} to {meta_sig}"
|
||||
)
|
||||
else:
|
||||
return ComparisonResult.Equal()
|
||||
|
||||
def _skip_functional_indexes(self, metadata_indexes, conn_indexes):
|
||||
conn_indexes_by_name = {c.name: c for c in conn_indexes}
|
||||
|
||||
for idx in list(metadata_indexes):
|
||||
if idx.name in conn_indexes_by_name:
|
||||
continue
|
||||
iex = sqla_compat.is_expression_index(idx)
|
||||
if iex:
|
||||
util.warn(
|
||||
"autogenerate skipping metadata-specified "
|
||||
"expression-based index "
|
||||
f"{idx.name!r}; dialect {self.__dialect__!r} under "
|
||||
f"SQLAlchemy {sqla_compat.sqlalchemy_version} can't "
|
||||
"reflect these indexes so they can't be compared"
|
||||
)
|
||||
metadata_indexes.discard(idx)
|
||||
|
||||
def adjust_reflected_dialect_options(
|
||||
self, reflected_object: Dict[str, Any], kind: str
|
||||
) -> Dict[str, Any]:
|
||||
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],
|
||||
default_io: Union[schema.Identity, schema.Sequence],
|
||||
skip: Set[str],
|
||||
):
|
||||
# this can be used for identity or sequence compare.
|
||||
# default_io is an instance of IdentityOption with all attributes to the
|
||||
# default value.
|
||||
meta_d = sqla_compat._get_identity_options_dict(metadata_io)
|
||||
insp_d = sqla_compat._get_identity_options_dict(inspector_io)
|
||||
|
||||
diff = set()
|
||||
ignored_attr = set()
|
||||
|
||||
def check_dicts(
|
||||
meta_dict: Mapping[str, Any],
|
||||
insp_dict: Mapping[str, Any],
|
||||
default_dict: Mapping[str, Any],
|
||||
attrs: Iterable[str],
|
||||
):
|
||||
for attr in set(attrs).difference(skip):
|
||||
meta_value = meta_dict.get(attr)
|
||||
insp_value = insp_dict.get(attr)
|
||||
if insp_value != meta_value:
|
||||
default_value = default_dict.get(attr)
|
||||
if meta_value == default_value:
|
||||
ignored_attr.add(attr)
|
||||
else:
|
||||
diff.add(attr)
|
||||
|
||||
check_dicts(
|
||||
meta_d,
|
||||
insp_d,
|
||||
sqla_compat._get_identity_options_dict(default_io),
|
||||
set(meta_d).union(insp_d),
|
||||
)
|
||||
if sqla_compat.identity_has_dialect_kwargs:
|
||||
assert hasattr(default_io, "dialect_kwargs")
|
||||
# use only the dialect kwargs in inspector_io since metadata_io
|
||||
# can have options for many backends
|
||||
check_dicts(
|
||||
getattr(metadata_io, "dialect_kwargs", {}),
|
||||
getattr(inspector_io, "dialect_kwargs", {}),
|
||||
default_io.dialect_kwargs,
|
||||
getattr(inspector_io, "dialect_kwargs", {}),
|
||||
)
|
||||
|
||||
return diff, ignored_attr
|
||||
421
venv/Lib/site-packages/alembic/ddl/mssql.py
Normal file
421
venv/Lib/site-packages/alembic/ddl/mssql.py
Normal file
@@ -0,0 +1,421 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.schema import Column
|
||||
from sqlalchemy.schema import CreateIndex
|
||||
from sqlalchemy.sql.base import Executable
|
||||
from sqlalchemy.sql.elements import ClauseElement
|
||||
|
||||
from .base import AddColumn
|
||||
from .base import alter_column
|
||||
from .base import alter_table
|
||||
from .base import ColumnDefault
|
||||
from .base import ColumnName
|
||||
from .base import ColumnNullable
|
||||
from .base import ColumnType
|
||||
from .base import format_column_name
|
||||
from .base import format_server_default
|
||||
from .base import format_table_name
|
||||
from .base import format_type
|
||||
from .base import RenameTable
|
||||
from .impl import DefaultImpl
|
||||
from .. import util
|
||||
from ..util import sqla_compat
|
||||
from ..util.sqla_compat import compiles
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from sqlalchemy.dialects.mssql.base import MSDDLCompiler
|
||||
from sqlalchemy.dialects.mssql.base import MSSQLCompiler
|
||||
from sqlalchemy.engine.cursor import CursorResult
|
||||
from sqlalchemy.sql.schema import Index
|
||||
from sqlalchemy.sql.schema import Table
|
||||
from sqlalchemy.sql.selectable import TableClause
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .base import _ServerDefault
|
||||
|
||||
|
||||
class MSSQLImpl(DefaultImpl):
|
||||
__dialect__ = "mssql"
|
||||
transactional_ddl = True
|
||||
batch_separator = "GO"
|
||||
|
||||
type_synonyms = DefaultImpl.type_synonyms + ({"VARCHAR", "NVARCHAR"},)
|
||||
identity_attrs_ignore = DefaultImpl.identity_attrs_ignore + (
|
||||
"minvalue",
|
||||
"maxvalue",
|
||||
"nominvalue",
|
||||
"nomaxvalue",
|
||||
"cycle",
|
||||
"cache",
|
||||
)
|
||||
|
||||
def __init__(self, *arg, **kw) -> None:
|
||||
super().__init__(*arg, **kw)
|
||||
self.batch_separator = self.context_opts.get(
|
||||
"mssql_batch_separator", self.batch_separator
|
||||
)
|
||||
|
||||
def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
|
||||
result = super()._exec(construct, *args, **kw)
|
||||
if self.as_sql and self.batch_separator:
|
||||
self.static_output(self.batch_separator)
|
||||
return result
|
||||
|
||||
def emit_begin(self) -> None:
|
||||
self.static_output("BEGIN TRANSACTION" + self.command_terminator)
|
||||
|
||||
def emit_commit(self) -> None:
|
||||
super().emit_commit()
|
||||
if self.as_sql and self.batch_separator:
|
||||
self.static_output(self.batch_separator)
|
||||
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
existing_type: Optional[TypeEngine] = None,
|
||||
existing_server_default: Optional[_ServerDefault] = None,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
if nullable is not None:
|
||||
if type_ is not None:
|
||||
# the NULL/NOT NULL alter will handle
|
||||
# the type alteration
|
||||
existing_type = type_
|
||||
type_ = None
|
||||
elif existing_type is None:
|
||||
raise util.CommandError(
|
||||
"MS-SQL ALTER COLUMN operations "
|
||||
"with NULL or NOT NULL require the "
|
||||
"existing_type or a new type_ be passed."
|
||||
)
|
||||
elif existing_nullable is not None and type_ is not None:
|
||||
nullable = existing_nullable
|
||||
|
||||
# the NULL/NOT NULL alter will handle
|
||||
# the type alteration
|
||||
existing_type = type_
|
||||
type_ = None
|
||||
|
||||
elif type_ is not None:
|
||||
util.warn(
|
||||
"MS-SQL ALTER COLUMN operations that specify type_= "
|
||||
"should also specify a nullable= or "
|
||||
"existing_nullable= argument to avoid implicit conversion "
|
||||
"of NOT NULL columns to NULL."
|
||||
)
|
||||
|
||||
used_default = False
|
||||
if sqla_compat._server_default_is_identity(
|
||||
server_default, existing_server_default
|
||||
) or sqla_compat._server_default_is_computed(
|
||||
server_default, existing_server_default
|
||||
):
|
||||
used_default = True
|
||||
kw["server_default"] = server_default
|
||||
kw["existing_server_default"] = existing_server_default
|
||||
|
||||
super().alter_column(
|
||||
table_name,
|
||||
column_name,
|
||||
nullable=nullable,
|
||||
type_=type_,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_nullable=existing_nullable,
|
||||
**kw,
|
||||
)
|
||||
|
||||
if server_default is not False and used_default is False:
|
||||
if existing_server_default is not False or server_default is None:
|
||||
self._exec(
|
||||
_ExecDropConstraint(
|
||||
table_name,
|
||||
column_name,
|
||||
"sys.default_constraints",
|
||||
schema,
|
||||
)
|
||||
)
|
||||
if server_default is not None:
|
||||
super().alter_column(
|
||||
table_name,
|
||||
column_name,
|
||||
schema=schema,
|
||||
server_default=server_default,
|
||||
)
|
||||
|
||||
if name is not None:
|
||||
super().alter_column(
|
||||
table_name, column_name, schema=schema, name=name
|
||||
)
|
||||
|
||||
def create_index(self, index: Index, **kw: Any) -> None:
|
||||
# this likely defaults to None if not present, so get()
|
||||
# should normally not return the default value. being
|
||||
# defensive in any case
|
||||
mssql_include = index.kwargs.get("mssql_include", None) or ()
|
||||
assert index.table is not None
|
||||
for col in mssql_include:
|
||||
if col not in index.table.c:
|
||||
index.table.append_column(Column(col, sqltypes.NullType))
|
||||
self._exec(CreateIndex(index, **kw))
|
||||
|
||||
def bulk_insert( # type:ignore[override]
|
||||
self, table: Union[TableClause, Table], rows: List[dict], **kw: Any
|
||||
) -> None:
|
||||
if self.as_sql:
|
||||
self._exec(
|
||||
"SET IDENTITY_INSERT %s ON"
|
||||
% self.dialect.identifier_preparer.format_table(table)
|
||||
)
|
||||
super().bulk_insert(table, rows, **kw)
|
||||
self._exec(
|
||||
"SET IDENTITY_INSERT %s OFF"
|
||||
% self.dialect.identifier_preparer.format_table(table)
|
||||
)
|
||||
else:
|
||||
super().bulk_insert(table, rows, **kw)
|
||||
|
||||
def drop_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
*,
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
drop_default = kw.pop("mssql_drop_default", False)
|
||||
if drop_default:
|
||||
self._exec(
|
||||
_ExecDropConstraint(
|
||||
table_name, column, "sys.default_constraints", schema
|
||||
)
|
||||
)
|
||||
drop_check = kw.pop("mssql_drop_check", False)
|
||||
if drop_check:
|
||||
self._exec(
|
||||
_ExecDropConstraint(
|
||||
table_name, column, "sys.check_constraints", schema
|
||||
)
|
||||
)
|
||||
drop_fks = kw.pop("mssql_drop_foreign_key", False)
|
||||
if drop_fks:
|
||||
self._exec(_ExecDropFKConstraint(table_name, column, schema))
|
||||
super().drop_column(table_name, column, schema=schema, **kw)
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column,
|
||||
metadata_column,
|
||||
rendered_metadata_default,
|
||||
rendered_inspector_default,
|
||||
):
|
||||
if rendered_metadata_default is not None:
|
||||
rendered_metadata_default = re.sub(
|
||||
r"[\(\) \"\']", "", rendered_metadata_default
|
||||
)
|
||||
|
||||
if rendered_inspector_default is not None:
|
||||
# SQL Server collapses whitespace and adds arbitrary parenthesis
|
||||
# within expressions. our only option is collapse all of it
|
||||
|
||||
rendered_inspector_default = re.sub(
|
||||
r"[\(\) \"\']", "", rendered_inspector_default
|
||||
)
|
||||
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
|
||||
def _compare_identity_default(self, metadata_identity, inspector_identity):
|
||||
diff, ignored, is_alter = super()._compare_identity_default(
|
||||
metadata_identity, inspector_identity
|
||||
)
|
||||
|
||||
if (
|
||||
metadata_identity is None
|
||||
and inspector_identity is not None
|
||||
and not diff
|
||||
and inspector_identity.column is not None
|
||||
and inspector_identity.column.primary_key
|
||||
):
|
||||
# mssql reflect primary keys with autoincrement as identity
|
||||
# columns. if no different attributes are present ignore them
|
||||
is_alter = False
|
||||
|
||||
return diff, ignored, is_alter
|
||||
|
||||
def adjust_reflected_dialect_options(
|
||||
self, reflected_object: Dict[str, Any], kind: str
|
||||
) -> Dict[str, Any]:
|
||||
options: Dict[str, Any]
|
||||
options = reflected_object.get("dialect_options", {}).copy()
|
||||
if not options.get("mssql_include"):
|
||||
options.pop("mssql_include", None)
|
||||
if not options.get("mssql_clustered"):
|
||||
options.pop("mssql_clustered", None)
|
||||
return options
|
||||
|
||||
|
||||
class _ExecDropConstraint(Executable, ClauseElement):
|
||||
inherit_cache = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tname: str,
|
||||
colname: Union[Column[Any], str],
|
||||
type_: str,
|
||||
schema: Optional[str],
|
||||
) -> None:
|
||||
self.tname = tname
|
||||
self.colname = colname
|
||||
self.type_ = type_
|
||||
self.schema = schema
|
||||
|
||||
|
||||
class _ExecDropFKConstraint(Executable, ClauseElement):
|
||||
inherit_cache = False
|
||||
|
||||
def __init__(
|
||||
self, tname: str, colname: Column[Any], schema: Optional[str]
|
||||
) -> None:
|
||||
self.tname = tname
|
||||
self.colname = colname
|
||||
self.schema = schema
|
||||
|
||||
|
||||
@compiles(_ExecDropConstraint, "mssql")
|
||||
def _exec_drop_col_constraint(
|
||||
element: _ExecDropConstraint, compiler: MSSQLCompiler, **kw
|
||||
) -> str:
|
||||
schema, tname, colname, type_ = (
|
||||
element.schema,
|
||||
element.tname,
|
||||
element.colname,
|
||||
element.type_,
|
||||
)
|
||||
# from http://www.mssqltips.com/sqlservertip/1425/\
|
||||
# working-with-default-constraints-in-sql-server/
|
||||
return """declare @const_name varchar(256)
|
||||
select @const_name = QUOTENAME([name]) from %(type)s
|
||||
where parent_object_id = object_id('%(schema_dot)s%(tname)s')
|
||||
and col_name(parent_object_id, parent_column_id) = '%(colname)s'
|
||||
exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
|
||||
"type": type_,
|
||||
"tname": tname,
|
||||
"colname": colname,
|
||||
"tname_quoted": format_table_name(compiler, tname, schema),
|
||||
"schema_dot": schema + "." if schema else "",
|
||||
}
|
||||
|
||||
|
||||
@compiles(_ExecDropFKConstraint, "mssql")
|
||||
def _exec_drop_col_fk_constraint(
|
||||
element: _ExecDropFKConstraint, compiler: MSSQLCompiler, **kw
|
||||
) -> str:
|
||||
schema, tname, colname = element.schema, element.tname, element.colname
|
||||
|
||||
return """declare @const_name varchar(256)
|
||||
select @const_name = QUOTENAME([name]) from
|
||||
sys.foreign_keys fk join sys.foreign_key_columns fkc
|
||||
on fk.object_id=fkc.constraint_object_id
|
||||
where fkc.parent_object_id = object_id('%(schema_dot)s%(tname)s')
|
||||
and col_name(fkc.parent_object_id, fkc.parent_column_id) = '%(colname)s'
|
||||
exec('alter table %(tname_quoted)s drop constraint ' + @const_name)""" % {
|
||||
"tname": tname,
|
||||
"colname": colname,
|
||||
"tname_quoted": format_table_name(compiler, tname, schema),
|
||||
"schema_dot": schema + "." if schema else "",
|
||||
}
|
||||
|
||||
|
||||
@compiles(AddColumn, "mssql")
|
||||
def visit_add_column(element: AddColumn, compiler: MSDDLCompiler, **kw) -> str:
|
||||
return "%s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
mssql_add_column(compiler, element.column, **kw),
|
||||
)
|
||||
|
||||
|
||||
def mssql_add_column(
|
||||
compiler: MSDDLCompiler, column: Column[Any], **kw
|
||||
) -> str:
|
||||
return "ADD %s" % compiler.get_column_specification(column, **kw)
|
||||
|
||||
|
||||
@compiles(ColumnNullable, "mssql")
|
||||
def visit_column_nullable(
|
||||
element: ColumnNullable, compiler: MSDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
format_type(compiler, element.existing_type), # type: ignore[arg-type]
|
||||
"NULL" if element.nullable else "NOT NULL",
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnDefault, "mssql")
|
||||
def visit_column_default(
|
||||
element: ColumnDefault, compiler: MSDDLCompiler, **kw
|
||||
) -> str:
|
||||
# TODO: there can also be a named constraint
|
||||
# with ADD CONSTRAINT here
|
||||
return "%s ADD DEFAULT %s FOR %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_server_default(compiler, element.default),
|
||||
format_column_name(compiler, element.column_name),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnName, "mssql")
|
||||
def visit_rename_column(
|
||||
element: ColumnName, compiler: MSDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "EXEC sp_rename '%s.%s', %s, 'COLUMN'" % (
|
||||
format_table_name(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
format_column_name(compiler, element.newname),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnType, "mssql")
|
||||
def visit_column_type(
|
||||
element: ColumnType, compiler: MSDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
format_type(compiler, element.type_),
|
||||
)
|
||||
|
||||
|
||||
@compiles(RenameTable, "mssql")
|
||||
def visit_rename_table(
|
||||
element: RenameTable, compiler: MSDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "EXEC sp_rename '%s', %s" % (
|
||||
format_table_name(compiler, element.table_name, element.schema),
|
||||
format_table_name(compiler, element.new_table_name, None),
|
||||
)
|
||||
495
venv/Lib/site-packages/alembic/ddl/mysql.py
Normal file
495
venv/Lib/site-packages/alembic/ddl/mysql.py
Normal file
@@ -0,0 +1,495 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import types as sqltypes
|
||||
|
||||
from .base import alter_table
|
||||
from .base import AlterColumn
|
||||
from .base import ColumnDefault
|
||||
from .base import ColumnName
|
||||
from .base import ColumnNullable
|
||||
from .base import ColumnType
|
||||
from .base import format_column_name
|
||||
from .base import format_server_default
|
||||
from .impl import DefaultImpl
|
||||
from .. import util
|
||||
from ..util import sqla_compat
|
||||
from ..util.sqla_compat import _is_type_bound
|
||||
from ..util.sqla_compat import compiles
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from sqlalchemy.dialects.mysql.base import MySQLDDLCompiler
|
||||
from sqlalchemy.sql.ddl import DropConstraint
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .base import _ServerDefault
|
||||
|
||||
|
||||
class MySQLImpl(DefaultImpl):
|
||||
__dialect__ = "mysql"
|
||||
|
||||
transactional_ddl = False
|
||||
type_synonyms = DefaultImpl.type_synonyms + (
|
||||
{"BOOL", "TINYINT"},
|
||||
{"JSON", "LONGTEXT"},
|
||||
)
|
||||
type_arg_extract = [r"character set ([\w\-_]+)", r"collate ([\w\-_]+)"]
|
||||
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
existing_type: Optional[TypeEngine] = None,
|
||||
existing_server_default: Optional[_ServerDefault] = None,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
autoincrement: Optional[bool] = None,
|
||||
existing_autoincrement: Optional[bool] = None,
|
||||
comment: Optional[Union[str, Literal[False]]] = False,
|
||||
existing_comment: Optional[str] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
if sqla_compat._server_default_is_identity(
|
||||
server_default, existing_server_default
|
||||
) or sqla_compat._server_default_is_computed(
|
||||
server_default, existing_server_default
|
||||
):
|
||||
# modifying computed or identity columns is not supported
|
||||
# the default will raise
|
||||
super().alter_column(
|
||||
table_name,
|
||||
column_name,
|
||||
nullable=nullable,
|
||||
type_=type_,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_nullable=existing_nullable,
|
||||
server_default=server_default,
|
||||
existing_server_default=existing_server_default,
|
||||
**kw,
|
||||
)
|
||||
if name is not None or self._is_mysql_allowed_functional_default(
|
||||
type_ if type_ is not None else existing_type, server_default
|
||||
):
|
||||
self._exec(
|
||||
MySQLChangeColumn(
|
||||
table_name,
|
||||
column_name,
|
||||
schema=schema,
|
||||
newname=name if name is not None else column_name,
|
||||
nullable=(
|
||||
nullable
|
||||
if nullable is not None
|
||||
else (
|
||||
existing_nullable
|
||||
if existing_nullable is not None
|
||||
else True
|
||||
)
|
||||
),
|
||||
type_=type_ if type_ is not None else existing_type,
|
||||
default=(
|
||||
server_default
|
||||
if server_default is not False
|
||||
else existing_server_default
|
||||
),
|
||||
autoincrement=(
|
||||
autoincrement
|
||||
if autoincrement is not None
|
||||
else existing_autoincrement
|
||||
),
|
||||
comment=(
|
||||
comment if comment is not False else existing_comment
|
||||
),
|
||||
)
|
||||
)
|
||||
elif (
|
||||
nullable is not None
|
||||
or type_ is not None
|
||||
or autoincrement is not None
|
||||
or comment is not False
|
||||
):
|
||||
self._exec(
|
||||
MySQLModifyColumn(
|
||||
table_name,
|
||||
column_name,
|
||||
schema=schema,
|
||||
newname=name if name is not None else column_name,
|
||||
nullable=(
|
||||
nullable
|
||||
if nullable is not None
|
||||
else (
|
||||
existing_nullable
|
||||
if existing_nullable is not None
|
||||
else True
|
||||
)
|
||||
),
|
||||
type_=type_ if type_ is not None else existing_type,
|
||||
default=(
|
||||
server_default
|
||||
if server_default is not False
|
||||
else existing_server_default
|
||||
),
|
||||
autoincrement=(
|
||||
autoincrement
|
||||
if autoincrement is not None
|
||||
else existing_autoincrement
|
||||
),
|
||||
comment=(
|
||||
comment if comment is not False else existing_comment
|
||||
),
|
||||
)
|
||||
)
|
||||
elif server_default is not False:
|
||||
self._exec(
|
||||
MySQLAlterDefault(
|
||||
table_name, column_name, server_default, schema=schema
|
||||
)
|
||||
)
|
||||
|
||||
def drop_constraint(
|
||||
self,
|
||||
const: Constraint,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
|
||||
return
|
||||
|
||||
super().drop_constraint(const)
|
||||
|
||||
def _is_mysql_allowed_functional_default(
|
||||
self,
|
||||
type_: Optional[TypeEngine],
|
||||
server_default: Optional[Union[_ServerDefault, Literal[False]]],
|
||||
) -> bool:
|
||||
return (
|
||||
type_ is not None
|
||||
and type_._type_affinity is sqltypes.DateTime
|
||||
and server_default is not None
|
||||
)
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column,
|
||||
metadata_column,
|
||||
rendered_metadata_default,
|
||||
rendered_inspector_default,
|
||||
):
|
||||
# partially a workaround for SQLAlchemy issue #3023; if the
|
||||
# column were created without "NOT NULL", MySQL may have added
|
||||
# an implicit default of '0' which we need to skip
|
||||
# TODO: this is not really covered anymore ?
|
||||
if (
|
||||
metadata_column.type._type_affinity is sqltypes.Integer
|
||||
and inspector_column.primary_key
|
||||
and not inspector_column.autoincrement
|
||||
and not rendered_metadata_default
|
||||
and rendered_inspector_default == "'0'"
|
||||
):
|
||||
return False
|
||||
elif (
|
||||
rendered_inspector_default
|
||||
and inspector_column.type._type_affinity is sqltypes.Integer
|
||||
):
|
||||
rendered_inspector_default = (
|
||||
re.sub(r"^'|'$", "", rendered_inspector_default)
|
||||
if rendered_inspector_default is not None
|
||||
else None
|
||||
)
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
elif (
|
||||
rendered_metadata_default
|
||||
and metadata_column.type._type_affinity is sqltypes.String
|
||||
):
|
||||
metadata_default = re.sub(r"^'|'$", "", rendered_metadata_default)
|
||||
return rendered_inspector_default != f"'{metadata_default}'"
|
||||
elif rendered_inspector_default and rendered_metadata_default:
|
||||
# adjust for "function()" vs. "FUNCTION" as can occur particularly
|
||||
# for the CURRENT_TIMESTAMP function on newer MariaDB versions
|
||||
|
||||
# SQLAlchemy MySQL dialect bundles ON UPDATE into the server
|
||||
# default; adjust for this possibly being present.
|
||||
onupdate_ins = re.match(
|
||||
r"(.*) (on update.*?)(?:\(\))?$",
|
||||
rendered_inspector_default.lower(),
|
||||
)
|
||||
onupdate_met = re.match(
|
||||
r"(.*) (on update.*?)(?:\(\))?$",
|
||||
rendered_metadata_default.lower(),
|
||||
)
|
||||
|
||||
if onupdate_ins:
|
||||
if not onupdate_met:
|
||||
return True
|
||||
elif onupdate_ins.group(2) != onupdate_met.group(2):
|
||||
return True
|
||||
|
||||
rendered_inspector_default = onupdate_ins.group(1)
|
||||
rendered_metadata_default = onupdate_met.group(1)
|
||||
|
||||
return re.sub(
|
||||
r"(.*?)(?:\(\))?$", r"\1", rendered_inspector_default.lower()
|
||||
) != re.sub(
|
||||
r"(.*?)(?:\(\))?$", r"\1", rendered_metadata_default.lower()
|
||||
)
|
||||
else:
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
|
||||
def correct_for_autogen_constraints(
|
||||
self,
|
||||
conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes,
|
||||
):
|
||||
# TODO: if SQLA 1.0, make use of "duplicates_index"
|
||||
# metadata
|
||||
removed = set()
|
||||
for idx in list(conn_indexes):
|
||||
if idx.unique:
|
||||
continue
|
||||
# MySQL puts implicit indexes on FK columns, even if
|
||||
# composite and even if MyISAM, so can't check this too easily.
|
||||
# the name of the index may be the column name or it may
|
||||
# be the name of the FK constraint.
|
||||
for col in idx.columns:
|
||||
if idx.name == col.name:
|
||||
conn_indexes.remove(idx)
|
||||
removed.add(idx.name)
|
||||
break
|
||||
for fk in col.foreign_keys:
|
||||
if fk.name == idx.name:
|
||||
conn_indexes.remove(idx)
|
||||
removed.add(idx.name)
|
||||
break
|
||||
if idx.name in removed:
|
||||
break
|
||||
|
||||
# then remove indexes from the "metadata_indexes"
|
||||
# that we've removed from reflected, otherwise they come out
|
||||
# as adds (see #202)
|
||||
for idx in list(metadata_indexes):
|
||||
if idx.name in removed:
|
||||
metadata_indexes.remove(idx)
|
||||
|
||||
def correct_for_autogen_foreignkeys(self, conn_fks, metadata_fks):
|
||||
conn_fk_by_sig = {
|
||||
self._create_reflected_constraint_sig(fk).unnamed_no_options: fk
|
||||
for fk in conn_fks
|
||||
}
|
||||
metadata_fk_by_sig = {
|
||||
self._create_metadata_constraint_sig(fk).unnamed_no_options: fk
|
||||
for fk in metadata_fks
|
||||
}
|
||||
|
||||
for sig in set(conn_fk_by_sig).intersection(metadata_fk_by_sig):
|
||||
mdfk = metadata_fk_by_sig[sig]
|
||||
cnfk = conn_fk_by_sig[sig]
|
||||
# MySQL considers RESTRICT to be the default and doesn't
|
||||
# report on it. if the model has explicit RESTRICT and
|
||||
# the conn FK has None, set it to RESTRICT
|
||||
if (
|
||||
mdfk.ondelete is not None
|
||||
and mdfk.ondelete.lower() == "restrict"
|
||||
and cnfk.ondelete is None
|
||||
):
|
||||
cnfk.ondelete = "RESTRICT"
|
||||
if (
|
||||
mdfk.onupdate is not None
|
||||
and mdfk.onupdate.lower() == "restrict"
|
||||
and cnfk.onupdate is None
|
||||
):
|
||||
cnfk.onupdate = "RESTRICT"
|
||||
|
||||
|
||||
class MariaDBImpl(MySQLImpl):
|
||||
__dialect__ = "mariadb"
|
||||
|
||||
|
||||
class MySQLAlterDefault(AlterColumn):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
default: Optional[_ServerDefault],
|
||||
schema: Optional[str] = None,
|
||||
) -> None:
|
||||
super(AlterColumn, self).__init__(name, schema=schema)
|
||||
self.column_name = column_name
|
||||
self.default = default
|
||||
|
||||
|
||||
class MySQLChangeColumn(AlterColumn):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
column_name: str,
|
||||
schema: Optional[str] = None,
|
||||
newname: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
nullable: Optional[bool] = None,
|
||||
default: Optional[Union[_ServerDefault, Literal[False]]] = False,
|
||||
autoincrement: Optional[bool] = None,
|
||||
comment: Optional[Union[str, Literal[False]]] = False,
|
||||
) -> None:
|
||||
super(AlterColumn, self).__init__(name, schema=schema)
|
||||
self.column_name = column_name
|
||||
self.nullable = nullable
|
||||
self.newname = newname
|
||||
self.default = default
|
||||
self.autoincrement = autoincrement
|
||||
self.comment = comment
|
||||
if type_ is None:
|
||||
raise util.CommandError(
|
||||
"All MySQL CHANGE/MODIFY COLUMN operations "
|
||||
"require the existing type."
|
||||
)
|
||||
|
||||
self.type_ = sqltypes.to_instance(type_)
|
||||
|
||||
|
||||
class MySQLModifyColumn(MySQLChangeColumn):
|
||||
pass
|
||||
|
||||
|
||||
@compiles(ColumnNullable, "mysql", "mariadb")
|
||||
@compiles(ColumnName, "mysql", "mariadb")
|
||||
@compiles(ColumnDefault, "mysql", "mariadb")
|
||||
@compiles(ColumnType, "mysql", "mariadb")
|
||||
def _mysql_doesnt_support_individual(element, compiler, **kw):
|
||||
raise NotImplementedError(
|
||||
"Individual alter column constructs not supported by MySQL"
|
||||
)
|
||||
|
||||
|
||||
@compiles(MySQLAlterDefault, "mysql", "mariadb")
|
||||
def _mysql_alter_default(
|
||||
element: MySQLAlterDefault, compiler: MySQLDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s ALTER COLUMN %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
(
|
||||
"SET DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DROP DEFAULT"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(MySQLModifyColumn, "mysql", "mariadb")
|
||||
def _mysql_modify_column(
|
||||
element: MySQLModifyColumn, compiler: MySQLDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s MODIFY %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
_mysql_colspec(
|
||||
compiler,
|
||||
nullable=element.nullable,
|
||||
server_default=element.default,
|
||||
type_=element.type_,
|
||||
autoincrement=element.autoincrement,
|
||||
comment=element.comment,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(MySQLChangeColumn, "mysql", "mariadb")
|
||||
def _mysql_change_column(
|
||||
element: MySQLChangeColumn, compiler: MySQLDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s CHANGE %s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
format_column_name(compiler, element.newname),
|
||||
_mysql_colspec(
|
||||
compiler,
|
||||
nullable=element.nullable,
|
||||
server_default=element.default,
|
||||
type_=element.type_,
|
||||
autoincrement=element.autoincrement,
|
||||
comment=element.comment,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _mysql_colspec(
|
||||
compiler: MySQLDDLCompiler,
|
||||
nullable: Optional[bool],
|
||||
server_default: Optional[Union[_ServerDefault, Literal[False]]],
|
||||
type_: TypeEngine,
|
||||
autoincrement: Optional[bool],
|
||||
comment: Optional[Union[str, Literal[False]]],
|
||||
) -> str:
|
||||
spec = "%s %s" % (
|
||||
compiler.dialect.type_compiler.process(type_),
|
||||
"NULL" if nullable else "NOT NULL",
|
||||
)
|
||||
if autoincrement:
|
||||
spec += " AUTO_INCREMENT"
|
||||
if server_default is not False and server_default is not None:
|
||||
spec += " DEFAULT %s" % format_server_default(compiler, server_default)
|
||||
if comment:
|
||||
spec += " COMMENT %s" % compiler.sql_compiler.render_literal_value(
|
||||
comment, sqltypes.String()
|
||||
)
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
@compiles(schema.DropConstraint, "mysql", "mariadb")
|
||||
def _mysql_drop_constraint(
|
||||
element: DropConstraint, compiler: MySQLDDLCompiler, **kw
|
||||
) -> str:
|
||||
"""Redefine SQLAlchemy's drop constraint to
|
||||
raise errors for invalid constraint type."""
|
||||
|
||||
constraint = element.element
|
||||
if isinstance(
|
||||
constraint,
|
||||
(
|
||||
schema.ForeignKeyConstraint,
|
||||
schema.PrimaryKeyConstraint,
|
||||
schema.UniqueConstraint,
|
||||
),
|
||||
):
|
||||
assert not kw
|
||||
return compiler.visit_drop_constraint(element)
|
||||
elif isinstance(constraint, schema.CheckConstraint):
|
||||
# note that SQLAlchemy as of 1.2 does not yet support
|
||||
# DROP CONSTRAINT for MySQL/MariaDB, so we implement fully
|
||||
# here.
|
||||
if compiler.dialect.is_mariadb: # type: ignore[attr-defined]
|
||||
return "ALTER TABLE %s DROP CONSTRAINT %s" % (
|
||||
compiler.preparer.format_table(constraint.table),
|
||||
compiler.preparer.format_constraint(constraint),
|
||||
)
|
||||
else:
|
||||
return "ALTER TABLE %s DROP CHECK %s" % (
|
||||
compiler.preparer.format_table(constraint.table),
|
||||
compiler.preparer.format_constraint(constraint),
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"No generic 'DROP CONSTRAINT' in MySQL - "
|
||||
"please specify constraint type"
|
||||
)
|
||||
202
venv/Lib/site-packages/alembic/ddl/oracle.py
Normal file
202
venv/Lib/site-packages/alembic/ddl/oracle.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy.sql import sqltypes
|
||||
|
||||
from .base import AddColumn
|
||||
from .base import alter_table
|
||||
from .base import ColumnComment
|
||||
from .base import ColumnDefault
|
||||
from .base import ColumnName
|
||||
from .base import ColumnNullable
|
||||
from .base import ColumnType
|
||||
from .base import format_column_name
|
||||
from .base import format_server_default
|
||||
from .base import format_table_name
|
||||
from .base import format_type
|
||||
from .base import IdentityColumnDefault
|
||||
from .base import RenameTable
|
||||
from .impl import DefaultImpl
|
||||
from ..util.sqla_compat import compiles
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.dialects.oracle.base import OracleDDLCompiler
|
||||
from sqlalchemy.engine.cursor import CursorResult
|
||||
from sqlalchemy.sql.schema import Column
|
||||
|
||||
|
||||
class OracleImpl(DefaultImpl):
|
||||
__dialect__ = "oracle"
|
||||
transactional_ddl = False
|
||||
batch_separator = "/"
|
||||
command_terminator = ""
|
||||
type_synonyms = DefaultImpl.type_synonyms + (
|
||||
{"VARCHAR", "VARCHAR2"},
|
||||
{"BIGINT", "INTEGER", "SMALLINT", "DECIMAL", "NUMERIC", "NUMBER"},
|
||||
{"DOUBLE", "FLOAT", "DOUBLE_PRECISION"},
|
||||
)
|
||||
identity_attrs_ignore = ()
|
||||
|
||||
def __init__(self, *arg, **kw) -> None:
|
||||
super().__init__(*arg, **kw)
|
||||
self.batch_separator = self.context_opts.get(
|
||||
"oracle_batch_separator", self.batch_separator
|
||||
)
|
||||
|
||||
def _exec(self, construct: Any, *args, **kw) -> Optional[CursorResult]:
|
||||
result = super()._exec(construct, *args, **kw)
|
||||
if self.as_sql and self.batch_separator:
|
||||
self.static_output(self.batch_separator)
|
||||
return result
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column,
|
||||
metadata_column,
|
||||
rendered_metadata_default,
|
||||
rendered_inspector_default,
|
||||
):
|
||||
if rendered_metadata_default is not None:
|
||||
rendered_metadata_default = re.sub(
|
||||
r"^\((.+)\)$", r"\1", rendered_metadata_default
|
||||
)
|
||||
|
||||
rendered_metadata_default = re.sub(
|
||||
r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
|
||||
)
|
||||
|
||||
if rendered_inspector_default is not None:
|
||||
rendered_inspector_default = re.sub(
|
||||
r"^\((.+)\)$", r"\1", rendered_inspector_default
|
||||
)
|
||||
|
||||
rendered_inspector_default = re.sub(
|
||||
r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
|
||||
)
|
||||
|
||||
rendered_inspector_default = rendered_inspector_default.strip()
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
|
||||
def emit_begin(self) -> None:
|
||||
self._exec("SET TRANSACTION READ WRITE")
|
||||
|
||||
def emit_commit(self) -> None:
|
||||
self._exec("COMMIT")
|
||||
|
||||
|
||||
@compiles(AddColumn, "oracle")
|
||||
def visit_add_column(
|
||||
element: AddColumn, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
add_column(compiler, element.column, **kw),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnNullable, "oracle")
|
||||
def visit_column_nullable(
|
||||
element: ColumnNullable, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
"NULL" if element.nullable else "NOT NULL",
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnType, "oracle")
|
||||
def visit_column_type(
|
||||
element: ColumnType, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
"%s" % format_type(compiler, element.type_),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnName, "oracle")
|
||||
def visit_column_name(
|
||||
element: ColumnName, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s RENAME COLUMN %s TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
format_column_name(compiler, element.newname),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnDefault, "oracle")
|
||||
def visit_column_default(
|
||||
element: ColumnDefault, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
(
|
||||
"DEFAULT %s" % format_server_default(compiler, element.default)
|
||||
if element.default is not None
|
||||
else "DEFAULT NULL"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnComment, "oracle")
|
||||
def visit_column_comment(
|
||||
element: ColumnComment, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
|
||||
|
||||
comment = compiler.sql_compiler.render_literal_value(
|
||||
(element.comment if element.comment is not None else ""),
|
||||
sqltypes.String(),
|
||||
)
|
||||
|
||||
return ddl.format(
|
||||
table_name=element.table_name,
|
||||
column_name=element.column_name,
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
|
||||
@compiles(RenameTable, "oracle")
|
||||
def visit_rename_table(
|
||||
element: RenameTable, compiler: OracleDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s RENAME TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_table_name(compiler, element.new_table_name, None),
|
||||
)
|
||||
|
||||
|
||||
def alter_column(compiler: OracleDDLCompiler, name: str) -> str:
|
||||
return "MODIFY %s" % format_column_name(compiler, name)
|
||||
|
||||
|
||||
def add_column(compiler: OracleDDLCompiler, column: Column[Any], **kw) -> str:
|
||||
return "ADD %s" % compiler.get_column_specification(column, **kw)
|
||||
|
||||
|
||||
@compiles(IdentityColumnDefault, "oracle")
|
||||
def visit_identity_column(
|
||||
element: IdentityColumnDefault, compiler: OracleDDLCompiler, **kw
|
||||
):
|
||||
text = "%s %s " % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
)
|
||||
if element.default is None:
|
||||
# drop identity
|
||||
text += "DROP IDENTITY"
|
||||
return text
|
||||
else:
|
||||
text += compiler.visit_identity_column(element.default)
|
||||
return text
|
||||
854
venv/Lib/site-packages/alembic/ddl/postgresql.py
Normal file
854
venv/Lib/site-packages/alembic/ddl/postgresql.py
Normal file
@@ -0,0 +1,854 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import cast
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import Float
|
||||
from sqlalchemy import Identity
|
||||
from sqlalchemy import literal_column
|
||||
from sqlalchemy import Numeric
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.dialects.postgresql import BIGINT
|
||||
from sqlalchemy.dialects.postgresql import ExcludeConstraint
|
||||
from sqlalchemy.dialects.postgresql import INTEGER
|
||||
from sqlalchemy.schema import CreateIndex
|
||||
from sqlalchemy.sql.elements import ColumnClause
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.functions import FunctionElement
|
||||
from sqlalchemy.types import NULLTYPE
|
||||
|
||||
from .base import alter_column
|
||||
from .base import alter_table
|
||||
from .base import AlterColumn
|
||||
from .base import ColumnComment
|
||||
from .base import format_column_name
|
||||
from .base import format_table_name
|
||||
from .base import format_type
|
||||
from .base import IdentityColumnDefault
|
||||
from .base import RenameTable
|
||||
from .impl import ComparisonResult
|
||||
from .impl import DefaultImpl
|
||||
from .. import util
|
||||
from ..autogenerate import render
|
||||
from ..operations import ops
|
||||
from ..operations import schemaobj
|
||||
from ..operations.base import BatchOperations
|
||||
from ..operations.base import Operations
|
||||
from ..util import sqla_compat
|
||||
from ..util.sqla_compat import compiles
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from sqlalchemy import Index
|
||||
from sqlalchemy import UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql.array import ARRAY
|
||||
from sqlalchemy.dialects.postgresql.base import PGDDLCompiler
|
||||
from sqlalchemy.dialects.postgresql.hstore import HSTORE
|
||||
from sqlalchemy.dialects.postgresql.json import JSON
|
||||
from sqlalchemy.dialects.postgresql.json import JSONB
|
||||
from sqlalchemy.sql.elements import ClauseElement
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
from sqlalchemy.sql.schema import MetaData
|
||||
from sqlalchemy.sql.schema import Table
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from .base import _ServerDefault
|
||||
from ..autogenerate.api import AutogenContext
|
||||
from ..autogenerate.render import _f_name
|
||||
from ..runtime.migration import MigrationContext
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PostgresqlImpl(DefaultImpl):
|
||||
__dialect__ = "postgresql"
|
||||
transactional_ddl = True
|
||||
type_synonyms = DefaultImpl.type_synonyms + (
|
||||
{"FLOAT", "DOUBLE PRECISION"},
|
||||
)
|
||||
|
||||
def create_index(self, index: Index, **kw: Any) -> None:
|
||||
# this likely defaults to None if not present, so get()
|
||||
# should normally not return the default value. being
|
||||
# defensive in any case
|
||||
postgresql_include = index.kwargs.get("postgresql_include", None) or ()
|
||||
for col in postgresql_include:
|
||||
if col not in index.table.c: # type: ignore[union-attr]
|
||||
index.table.append_column( # type: ignore[union-attr]
|
||||
Column(col, sqltypes.NullType)
|
||||
)
|
||||
self._exec(CreateIndex(index, **kw))
|
||||
|
||||
def prep_table_for_batch(self, batch_impl, table):
|
||||
for constraint in table.constraints:
|
||||
if (
|
||||
constraint.name is not None
|
||||
and constraint.name in batch_impl.named_constraints
|
||||
):
|
||||
self.drop_constraint(constraint)
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column,
|
||||
metadata_column,
|
||||
rendered_metadata_default,
|
||||
rendered_inspector_default,
|
||||
):
|
||||
# don't do defaults for SERIAL columns
|
||||
if (
|
||||
metadata_column.primary_key
|
||||
and metadata_column is metadata_column.table._autoincrement_column
|
||||
):
|
||||
return False
|
||||
|
||||
conn_col_default = rendered_inspector_default
|
||||
|
||||
defaults_equal = conn_col_default == rendered_metadata_default
|
||||
if defaults_equal:
|
||||
return False
|
||||
|
||||
if None in (
|
||||
conn_col_default,
|
||||
rendered_metadata_default,
|
||||
metadata_column.server_default,
|
||||
):
|
||||
return not defaults_equal
|
||||
|
||||
metadata_default = metadata_column.server_default.arg
|
||||
|
||||
if isinstance(metadata_default, str):
|
||||
if not isinstance(inspector_column.type, (Numeric, Float)):
|
||||
metadata_default = re.sub(r"^'|'$", "", metadata_default)
|
||||
metadata_default = f"'{metadata_default}'"
|
||||
|
||||
metadata_default = literal_column(metadata_default)
|
||||
|
||||
# run a real compare against the server
|
||||
conn = self.connection
|
||||
assert conn is not None
|
||||
return not conn.scalar(
|
||||
select(literal_column(conn_col_default) == metadata_default)
|
||||
)
|
||||
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
*,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[
|
||||
Union[_ServerDefault, Literal[False]]
|
||||
] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
schema: Optional[str] = None,
|
||||
autoincrement: Optional[bool] = None,
|
||||
existing_type: Optional[TypeEngine] = None,
|
||||
existing_server_default: Optional[_ServerDefault] = None,
|
||||
existing_nullable: Optional[bool] = None,
|
||||
existing_autoincrement: Optional[bool] = None,
|
||||
**kw: Any,
|
||||
) -> None:
|
||||
using = kw.pop("postgresql_using", None)
|
||||
|
||||
if using is not None and type_ is None:
|
||||
raise util.CommandError(
|
||||
"postgresql_using must be used with the type_ parameter"
|
||||
)
|
||||
|
||||
if type_ is not None:
|
||||
self._exec(
|
||||
PostgresqlColumnType(
|
||||
table_name,
|
||||
column_name,
|
||||
type_,
|
||||
schema=schema,
|
||||
using=using,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
)
|
||||
)
|
||||
|
||||
super().alter_column(
|
||||
table_name,
|
||||
column_name,
|
||||
nullable=nullable,
|
||||
server_default=server_default,
|
||||
name=name,
|
||||
schema=schema,
|
||||
autoincrement=autoincrement,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
existing_autoincrement=existing_autoincrement,
|
||||
**kw,
|
||||
)
|
||||
|
||||
def autogen_column_reflect(self, inspector, table, column_info):
|
||||
if column_info.get("default") and isinstance(
|
||||
column_info["type"], (INTEGER, BIGINT)
|
||||
):
|
||||
seq_match = re.match(
|
||||
r"nextval\('(.+?)'::regclass\)", column_info["default"]
|
||||
)
|
||||
if seq_match:
|
||||
info = sqla_compat._exec_on_inspector(
|
||||
inspector,
|
||||
text(
|
||||
"select c.relname, a.attname "
|
||||
"from pg_class as c join "
|
||||
"pg_depend d on d.objid=c.oid and "
|
||||
"d.classid='pg_class'::regclass and "
|
||||
"d.refclassid='pg_class'::regclass "
|
||||
"join pg_class t on t.oid=d.refobjid "
|
||||
"join pg_attribute a on a.attrelid=t.oid and "
|
||||
"a.attnum=d.refobjsubid "
|
||||
"where c.relkind='S' and "
|
||||
"c.oid=cast(:seqname as regclass)"
|
||||
),
|
||||
seqname=seq_match.group(1),
|
||||
).first()
|
||||
if info:
|
||||
seqname, colname = info
|
||||
if colname == column_info["name"]:
|
||||
log.info(
|
||||
"Detected sequence named '%s' as "
|
||||
"owned by integer column '%s(%s)', "
|
||||
"assuming SERIAL and omitting",
|
||||
seqname,
|
||||
table.name,
|
||||
colname,
|
||||
)
|
||||
# sequence, and the owner is this column,
|
||||
# its a SERIAL - whack it!
|
||||
del column_info["default"]
|
||||
|
||||
def correct_for_autogen_constraints(
|
||||
self,
|
||||
conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes,
|
||||
):
|
||||
doubled_constraints = {
|
||||
index
|
||||
for index in conn_indexes
|
||||
if index.info.get("duplicates_constraint")
|
||||
}
|
||||
|
||||
for ix in doubled_constraints:
|
||||
conn_indexes.remove(ix)
|
||||
|
||||
if not sqla_compat.sqla_2:
|
||||
self._skip_functional_indexes(metadata_indexes, conn_indexes)
|
||||
|
||||
# pg behavior regarding modifiers
|
||||
# | # | compiled sql | returned sql | regexp. group is removed |
|
||||
# | - | ---------------- | -----------------| ------------------------ |
|
||||
# | 1 | nulls first | nulls first | - |
|
||||
# | 2 | nulls last | | (?<! 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()}.", "")
|
||||
|
||||
if "::" in expr:
|
||||
# strip :: cast. types can have spaces in them
|
||||
expr = re.sub(r"(::[\w ]+\w)", "", expr)
|
||||
|
||||
while expr and expr[0] == "(" and expr[-1] == ")":
|
||||
expr = expr[1:-1]
|
||||
|
||||
# NOTE: when parsing the connection expression this cleanup could
|
||||
# be skipped
|
||||
for rs in self._default_modifiers_re:
|
||||
if match := rs.search(expr):
|
||||
start, end = match.span(1)
|
||||
expr = expr[:start] + expr[end:]
|
||||
break
|
||||
|
||||
while expr and expr[0] == "(" and expr[-1] == ")":
|
||||
expr = expr[1:-1]
|
||||
|
||||
# strip casts
|
||||
cast_re = re.compile(r"cast\s*\(")
|
||||
if cast_re.match(expr):
|
||||
expr = cast_re.sub("", expr)
|
||||
# remove the as type
|
||||
expr = re.sub(r"as\s+[^)]+\)", "", expr)
|
||||
# remove spaces
|
||||
expr = expr.replace(" ", "")
|
||||
return expr
|
||||
|
||||
def _dialect_options(
|
||||
self, item: Union[Index, UniqueConstraint]
|
||||
) -> Tuple[Any, ...]:
|
||||
# only the positive case is returned by sqlalchemy reflection so
|
||||
# None and False are threated the same
|
||||
if item.dialect_kwargs.get("postgresql_nulls_not_distinct"):
|
||||
return ("nulls_not_distinct",)
|
||||
return ()
|
||||
|
||||
def compare_indexes(
|
||||
self,
|
||||
metadata_index: Index,
|
||||
reflected_index: Index,
|
||||
) -> ComparisonResult:
|
||||
msg = []
|
||||
unique_msg = self._compare_index_unique(
|
||||
metadata_index, reflected_index
|
||||
)
|
||||
if unique_msg:
|
||||
msg.append(unique_msg)
|
||||
m_exprs = metadata_index.expressions
|
||||
r_exprs = reflected_index.expressions
|
||||
if len(m_exprs) != len(r_exprs):
|
||||
msg.append(f"expression number {len(r_exprs)} to {len(m_exprs)}")
|
||||
if msg:
|
||||
# no point going further, return early
|
||||
return ComparisonResult.Different(msg)
|
||||
skip = []
|
||||
for pos, (m_e, r_e) in enumerate(zip(m_exprs, r_exprs), 1):
|
||||
m_compile = self._compile_element(m_e)
|
||||
m_text = self._cleanup_index_expr(metadata_index, m_compile)
|
||||
# print(f"META ORIG: {m_compile!r} CLEANUP: {m_text!r}")
|
||||
r_compile = self._compile_element(r_e)
|
||||
r_text = self._cleanup_index_expr(metadata_index, r_compile)
|
||||
# print(f"CONN ORIG: {r_compile!r} CLEANUP: {r_text!r}")
|
||||
if m_text == r_text:
|
||||
continue # expressions these are equal
|
||||
elif m_compile.strip().endswith("_ops") and (
|
||||
" " in m_compile or ")" in m_compile # is an expression
|
||||
):
|
||||
skip.append(
|
||||
f"expression #{pos} {m_compile!r} detected "
|
||||
"as including operator clause."
|
||||
)
|
||||
util.warn(
|
||||
f"Expression #{pos} {m_compile!r} in index "
|
||||
f"{reflected_index.name!r} detected to include "
|
||||
"an operator clause. Expression compare cannot proceed. "
|
||||
"Please move the operator clause to the "
|
||||
"``postgresql_ops`` dict to enable proper compare "
|
||||
"of the index expressions: "
|
||||
"https://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#operator-classes", # noqa: E501
|
||||
)
|
||||
else:
|
||||
msg.append(f"expression #{pos} {r_compile!r} to {m_compile!r}")
|
||||
|
||||
m_options = self._dialect_options(metadata_index)
|
||||
r_options = self._dialect_options(reflected_index)
|
||||
if m_options != r_options:
|
||||
msg.extend(f"options {r_options} to {m_options}")
|
||||
|
||||
if msg:
|
||||
return ComparisonResult.Different(msg)
|
||||
elif skip:
|
||||
# if there are other changes detected don't skip the index
|
||||
return ComparisonResult.Skip(skip)
|
||||
else:
|
||||
return ComparisonResult.Equal()
|
||||
|
||||
def compare_unique_constraint(
|
||||
self,
|
||||
metadata_constraint: UniqueConstraint,
|
||||
reflected_constraint: UniqueConstraint,
|
||||
) -> ComparisonResult:
|
||||
metadata_tup = self._create_metadata_constraint_sig(
|
||||
metadata_constraint
|
||||
)
|
||||
reflected_tup = self._create_reflected_constraint_sig(
|
||||
reflected_constraint
|
||||
)
|
||||
|
||||
meta_sig = metadata_tup.unnamed
|
||||
conn_sig = reflected_tup.unnamed
|
||||
if conn_sig != meta_sig:
|
||||
return ComparisonResult.Different(
|
||||
f"expression {conn_sig} to {meta_sig}"
|
||||
)
|
||||
|
||||
metadata_do = self._dialect_options(metadata_tup.const)
|
||||
conn_do = self._dialect_options(reflected_tup.const)
|
||||
if metadata_do != conn_do:
|
||||
return ComparisonResult.Different(
|
||||
f"expression {conn_do} to {metadata_do}"
|
||||
)
|
||||
|
||||
return ComparisonResult.Equal()
|
||||
|
||||
def adjust_reflected_dialect_options(
|
||||
self, reflected_options: Dict[str, Any], kind: str
|
||||
) -> Dict[str, Any]:
|
||||
options: Dict[str, Any]
|
||||
options = reflected_options.get("dialect_options", {}).copy()
|
||||
if not options.get("postgresql_include"):
|
||||
options.pop("postgresql_include", None)
|
||||
return options
|
||||
|
||||
def _compile_element(self, element: Union[ClauseElement, str]) -> str:
|
||||
if isinstance(element, str):
|
||||
return element
|
||||
return element.compile(
|
||||
dialect=self.dialect,
|
||||
compile_kwargs={"literal_binds": True, "include_table": False},
|
||||
).string
|
||||
|
||||
def render_ddl_sql_expr(
|
||||
self,
|
||||
expr: ClauseElement,
|
||||
is_server_default: bool = False,
|
||||
is_index: bool = False,
|
||||
**kw: Any,
|
||||
) -> str:
|
||||
"""Render a SQL expression that is typically a server default,
|
||||
index expression, etc.
|
||||
|
||||
"""
|
||||
|
||||
# apply self_group to index expressions;
|
||||
# see https://github.com/sqlalchemy/sqlalchemy/blob/
|
||||
# 82fa95cfce070fab401d020c6e6e4a6a96cc2578/
|
||||
# lib/sqlalchemy/dialects/postgresql/base.py#L2261
|
||||
if is_index and not isinstance(expr, ColumnClause):
|
||||
expr = expr.self_group()
|
||||
|
||||
return super().render_ddl_sql_expr(
|
||||
expr, is_server_default=is_server_default, is_index=is_index, **kw
|
||||
)
|
||||
|
||||
def render_type(
|
||||
self, type_: TypeEngine, autogen_context: AutogenContext
|
||||
) -> Union[str, Literal[False]]:
|
||||
mod = type(type_).__module__
|
||||
if not mod.startswith("sqlalchemy.dialects.postgresql"):
|
||||
return False
|
||||
|
||||
if hasattr(self, "_render_%s_type" % type_.__visit_name__):
|
||||
meth = getattr(self, "_render_%s_type" % type_.__visit_name__)
|
||||
return meth(type_, autogen_context)
|
||||
|
||||
return False
|
||||
|
||||
def _render_HSTORE_type(
|
||||
self, type_: HSTORE, autogen_context: AutogenContext
|
||||
) -> str:
|
||||
return cast(
|
||||
str,
|
||||
render._render_type_w_subtype(
|
||||
type_, autogen_context, "text_type", r"(.+?\(.*text_type=)"
|
||||
),
|
||||
)
|
||||
|
||||
def _render_ARRAY_type(
|
||||
self, type_: ARRAY, autogen_context: AutogenContext
|
||||
) -> str:
|
||||
return cast(
|
||||
str,
|
||||
render._render_type_w_subtype(
|
||||
type_, autogen_context, "item_type", r"(.+?\()"
|
||||
),
|
||||
)
|
||||
|
||||
def _render_JSON_type(
|
||||
self, type_: JSON, autogen_context: AutogenContext
|
||||
) -> str:
|
||||
return cast(
|
||||
str,
|
||||
render._render_type_w_subtype(
|
||||
type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
|
||||
),
|
||||
)
|
||||
|
||||
def _render_JSONB_type(
|
||||
self, type_: JSONB, autogen_context: AutogenContext
|
||||
) -> str:
|
||||
return cast(
|
||||
str,
|
||||
render._render_type_w_subtype(
|
||||
type_, autogen_context, "astext_type", r"(.+?\(.*astext_type=)"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class PostgresqlColumnType(AlterColumn):
|
||||
def __init__(
|
||||
self, name: str, column_name: str, type_: TypeEngine, **kw
|
||||
) -> None:
|
||||
using = kw.pop("using", None)
|
||||
super().__init__(name, column_name, **kw)
|
||||
self.type_ = sqltypes.to_instance(type_)
|
||||
self.using = using
|
||||
|
||||
|
||||
@compiles(RenameTable, "postgresql")
|
||||
def visit_rename_table(
|
||||
element: RenameTable, compiler: PGDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s RENAME TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_table_name(compiler, element.new_table_name, None),
|
||||
)
|
||||
|
||||
|
||||
@compiles(PostgresqlColumnType, "postgresql")
|
||||
def visit_column_type(
|
||||
element: PostgresqlColumnType, compiler: PGDDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s %s %s %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
"TYPE %s" % format_type(compiler, element.type_),
|
||||
"USING %s" % element.using if element.using else "",
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnComment, "postgresql")
|
||||
def visit_column_comment(
|
||||
element: ColumnComment, compiler: PGDDLCompiler, **kw
|
||||
) -> str:
|
||||
ddl = "COMMENT ON COLUMN {table_name}.{column_name} IS {comment}"
|
||||
comment = (
|
||||
compiler.sql_compiler.render_literal_value(
|
||||
element.comment, sqltypes.String()
|
||||
)
|
||||
if element.comment is not None
|
||||
else "NULL"
|
||||
)
|
||||
|
||||
return ddl.format(
|
||||
table_name=format_table_name(
|
||||
compiler, element.table_name, element.schema
|
||||
),
|
||||
column_name=format_column_name(compiler, element.column_name),
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
|
||||
@compiles(IdentityColumnDefault, "postgresql")
|
||||
def visit_identity_column(
|
||||
element: IdentityColumnDefault, compiler: PGDDLCompiler, **kw
|
||||
):
|
||||
text = "%s %s " % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
alter_column(compiler, element.column_name),
|
||||
)
|
||||
if element.default is None:
|
||||
# drop identity
|
||||
text += "DROP IDENTITY"
|
||||
return text
|
||||
elif element.existing_server_default is None:
|
||||
# add identity options
|
||||
text += "ADD "
|
||||
text += compiler.visit_identity_column(element.default)
|
||||
return text
|
||||
else:
|
||||
# alter identity
|
||||
diff, _, _ = element.impl._compare_identity_default(
|
||||
element.default, element.existing_server_default
|
||||
)
|
||||
identity = element.default
|
||||
for attr in sorted(diff):
|
||||
if attr == "always":
|
||||
text += "SET GENERATED %s " % (
|
||||
"ALWAYS" if identity.always else "BY DEFAULT"
|
||||
)
|
||||
else:
|
||||
text += "SET %s " % compiler.get_identity_options(
|
||||
Identity(**{attr: getattr(identity, attr)})
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
@Operations.register_operation("create_exclude_constraint")
|
||||
@BatchOperations.register_operation(
|
||||
"create_exclude_constraint", "batch_create_exclude_constraint"
|
||||
)
|
||||
@ops.AddConstraintOp.register_add_constraint("exclude_constraint")
|
||||
class CreateExcludeConstraintOp(ops.AddConstraintOp):
|
||||
"""Represent a create exclude constraint operation."""
|
||||
|
||||
constraint_type = "exclude"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
constraint_name: sqla_compat._ConstraintName,
|
||||
table_name: Union[str, quoted_name],
|
||||
elements: Union[
|
||||
Sequence[Tuple[str, str]],
|
||||
Sequence[Tuple[ColumnClause[Any], str]],
|
||||
],
|
||||
where: Optional[Union[ColumnElement[bool], str]] = None,
|
||||
schema: Optional[str] = None,
|
||||
_orig_constraint: Optional[ExcludeConstraint] = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
self.constraint_name = constraint_name
|
||||
self.table_name = table_name
|
||||
self.elements = elements
|
||||
self.where = where
|
||||
self.schema = schema
|
||||
self._orig_constraint = _orig_constraint
|
||||
self.kw = kw
|
||||
|
||||
@classmethod
|
||||
def from_constraint( # type:ignore[override]
|
||||
cls, constraint: ExcludeConstraint
|
||||
) -> CreateExcludeConstraintOp:
|
||||
constraint_table = sqla_compat._table_for_constraint(constraint)
|
||||
return cls(
|
||||
constraint.name,
|
||||
constraint_table.name,
|
||||
[ # type: ignore
|
||||
(expr, op) for expr, name, op in constraint._render_exprs
|
||||
],
|
||||
where=cast("ColumnElement[bool] | None", constraint.where),
|
||||
schema=constraint_table.schema,
|
||||
_orig_constraint=constraint,
|
||||
deferrable=constraint.deferrable,
|
||||
initially=constraint.initially,
|
||||
using=constraint.using,
|
||||
)
|
||||
|
||||
def to_constraint(
|
||||
self, migration_context: Optional[MigrationContext] = None
|
||||
) -> ExcludeConstraint:
|
||||
if self._orig_constraint is not None:
|
||||
return self._orig_constraint
|
||||
schema_obj = schemaobj.SchemaObjects(migration_context)
|
||||
t = schema_obj.table(self.table_name, schema=self.schema)
|
||||
excl = ExcludeConstraint(
|
||||
*self.elements,
|
||||
name=self.constraint_name,
|
||||
where=self.where,
|
||||
**self.kw,
|
||||
)
|
||||
for (
|
||||
expr,
|
||||
name,
|
||||
oper,
|
||||
) in excl._render_exprs:
|
||||
t.append_column(Column(name, NULLTYPE))
|
||||
t.append_constraint(excl)
|
||||
return excl
|
||||
|
||||
@classmethod
|
||||
def create_exclude_constraint(
|
||||
cls,
|
||||
operations: Operations,
|
||||
constraint_name: str,
|
||||
table_name: str,
|
||||
*elements: Any,
|
||||
**kw: Any,
|
||||
) -> Optional[Table]:
|
||||
"""Issue an alter to create an EXCLUDE constraint using the
|
||||
current migration context.
|
||||
|
||||
.. note:: This method is Postgresql specific, and additionally
|
||||
requires at least SQLAlchemy 1.0.
|
||||
|
||||
e.g.::
|
||||
|
||||
from alembic import op
|
||||
|
||||
op.create_exclude_constraint(
|
||||
"user_excl",
|
||||
"user",
|
||||
("period", "&&"),
|
||||
("group", "="),
|
||||
where=("group != 'some group'"),
|
||||
)
|
||||
|
||||
Note that the expressions work the same way as that of
|
||||
the ``ExcludeConstraint`` object itself; if plain strings are
|
||||
passed, quoting rules must be applied manually.
|
||||
|
||||
:param name: Name of the constraint.
|
||||
:param table_name: String name of the source table.
|
||||
:param elements: exclude conditions.
|
||||
:param where: SQL expression or SQL string with optional WHERE
|
||||
clause.
|
||||
:param deferrable: optional bool. If set, emit DEFERRABLE or
|
||||
NOT DEFERRABLE when issuing DDL for this constraint.
|
||||
:param initially: optional string. If set, emit INITIALLY <value>
|
||||
when issuing DDL for this constraint.
|
||||
:param schema: Optional schema name to operate within.
|
||||
|
||||
"""
|
||||
op = cls(constraint_name, table_name, elements, **kw)
|
||||
return operations.invoke(op)
|
||||
|
||||
@classmethod
|
||||
def batch_create_exclude_constraint(
|
||||
cls,
|
||||
operations: BatchOperations,
|
||||
constraint_name: str,
|
||||
*elements: Any,
|
||||
**kw: Any,
|
||||
) -> Optional[Table]:
|
||||
"""Issue a "create exclude constraint" instruction using the
|
||||
current batch migration context.
|
||||
|
||||
.. note:: This method is Postgresql specific, and additionally
|
||||
requires at least SQLAlchemy 1.0.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:meth:`.Operations.create_exclude_constraint`
|
||||
|
||||
"""
|
||||
kw["schema"] = operations.impl.schema
|
||||
op = cls(constraint_name, operations.impl.table_name, elements, **kw)
|
||||
return operations.invoke(op)
|
||||
|
||||
|
||||
@render.renderers.dispatch_for(CreateExcludeConstraintOp)
|
||||
def _add_exclude_constraint(
|
||||
autogen_context: AutogenContext, op: CreateExcludeConstraintOp
|
||||
) -> str:
|
||||
return _exclude_constraint(op.to_constraint(), autogen_context, alter=True)
|
||||
|
||||
|
||||
@render._constraint_renderers.dispatch_for(ExcludeConstraint)
|
||||
def _render_inline_exclude_constraint(
|
||||
constraint: ExcludeConstraint,
|
||||
autogen_context: AutogenContext,
|
||||
namespace_metadata: MetaData,
|
||||
) -> str:
|
||||
rendered = render._user_defined_render(
|
||||
"exclude", constraint, autogen_context
|
||||
)
|
||||
if rendered is not False:
|
||||
return rendered
|
||||
|
||||
return _exclude_constraint(constraint, autogen_context, False)
|
||||
|
||||
|
||||
def _postgresql_autogenerate_prefix(autogen_context: AutogenContext) -> str:
|
||||
imports = autogen_context.imports
|
||||
if imports is not None:
|
||||
imports.add("from sqlalchemy.dialects import postgresql")
|
||||
return "postgresql."
|
||||
|
||||
|
||||
def _exclude_constraint(
|
||||
constraint: ExcludeConstraint,
|
||||
autogen_context: AutogenContext,
|
||||
alter: bool,
|
||||
) -> str:
|
||||
opts: List[Tuple[str, Union[quoted_name, str, _f_name, None]]] = []
|
||||
|
||||
has_batch = autogen_context._has_batch
|
||||
|
||||
if constraint.deferrable:
|
||||
opts.append(("deferrable", str(constraint.deferrable)))
|
||||
if constraint.initially:
|
||||
opts.append(("initially", str(constraint.initially)))
|
||||
if constraint.using:
|
||||
opts.append(("using", str(constraint.using)))
|
||||
if not has_batch and alter and constraint.table.schema:
|
||||
opts.append(("schema", render._ident(constraint.table.schema)))
|
||||
if not alter and constraint.name:
|
||||
opts.append(
|
||||
("name", render._render_gen_name(autogen_context, constraint.name))
|
||||
)
|
||||
|
||||
def do_expr_where_opts():
|
||||
args = [
|
||||
"(%s, %r)"
|
||||
% (
|
||||
_render_potential_column(
|
||||
sqltext, # type:ignore[arg-type]
|
||||
autogen_context,
|
||||
),
|
||||
opstring,
|
||||
)
|
||||
for sqltext, name, opstring in constraint._render_exprs
|
||||
]
|
||||
if constraint.where is not None:
|
||||
args.append(
|
||||
"where=%s"
|
||||
% render._render_potential_expr(
|
||||
constraint.where, autogen_context
|
||||
)
|
||||
)
|
||||
args.extend(["%s=%r" % (k, v) for k, v in opts])
|
||||
return args
|
||||
|
||||
if alter:
|
||||
args = [
|
||||
repr(render._render_gen_name(autogen_context, constraint.name))
|
||||
]
|
||||
if not has_batch:
|
||||
args += [repr(render._ident(constraint.table.name))]
|
||||
args.extend(do_expr_where_opts())
|
||||
return "%(prefix)screate_exclude_constraint(%(args)s)" % {
|
||||
"prefix": render._alembic_autogenerate_prefix(autogen_context),
|
||||
"args": ", ".join(args),
|
||||
}
|
||||
else:
|
||||
args = do_expr_where_opts()
|
||||
return "%(prefix)sExcludeConstraint(%(args)s)" % {
|
||||
"prefix": _postgresql_autogenerate_prefix(autogen_context),
|
||||
"args": ", ".join(args),
|
||||
}
|
||||
|
||||
|
||||
def _render_potential_column(
|
||||
value: Union[
|
||||
ColumnClause[Any], Column[Any], TextClause, FunctionElement[Any]
|
||||
],
|
||||
autogen_context: AutogenContext,
|
||||
) -> str:
|
||||
if isinstance(value, ColumnClause):
|
||||
if value.is_literal:
|
||||
# like literal_column("int8range(from, to)") in ExcludeConstraint
|
||||
template = "%(prefix)sliteral_column(%(name)r)"
|
||||
else:
|
||||
template = "%(prefix)scolumn(%(name)r)"
|
||||
|
||||
return template % {
|
||||
"prefix": render._sqlalchemy_autogenerate_prefix(autogen_context),
|
||||
"name": value.name,
|
||||
}
|
||||
else:
|
||||
return render._render_potential_expr(
|
||||
value,
|
||||
autogen_context,
|
||||
wrap_in_element=isinstance(value, (TextClause, FunctionElement)),
|
||||
)
|
||||
237
venv/Lib/site-packages/alembic/ddl/sqlite.py
Normal file
237
venv/Lib/site-packages/alembic/ddl/sqlite.py
Normal file
@@ -0,0 +1,237 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import cast
|
||||
from sqlalchemy import Computed
|
||||
from sqlalchemy import JSON
|
||||
from sqlalchemy import schema
|
||||
from sqlalchemy import sql
|
||||
|
||||
from .base import alter_table
|
||||
from .base import ColumnName
|
||||
from .base import format_column_name
|
||||
from .base import format_table_name
|
||||
from .base import RenameTable
|
||||
from .impl import DefaultImpl
|
||||
from .. import util
|
||||
from ..util.sqla_compat import compiles
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.engine.reflection import Inspector
|
||||
from sqlalchemy.sql.compiler import DDLCompiler
|
||||
from sqlalchemy.sql.elements import Cast
|
||||
from sqlalchemy.sql.elements import ClauseElement
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.schema import Table
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from ..operations.batch import BatchOperationsImpl
|
||||
|
||||
|
||||
class SQLiteImpl(DefaultImpl):
|
||||
__dialect__ = "sqlite"
|
||||
|
||||
transactional_ddl = False
|
||||
"""SQLite supports transactional DDL, but pysqlite does not:
|
||||
see: http://bugs.python.org/issue10740
|
||||
"""
|
||||
|
||||
def requires_recreate_in_batch(
|
||||
self, batch_op: BatchOperationsImpl
|
||||
) -> bool:
|
||||
"""Return True if the given :class:`.BatchOperationsImpl`
|
||||
would need the table to be recreated and copied in order to
|
||||
proceed.
|
||||
|
||||
Normally, only returns True on SQLite when operations other
|
||||
than add_column are present.
|
||||
|
||||
"""
|
||||
for op in batch_op.batch:
|
||||
if op[0] == "add_column":
|
||||
col = op[1][1]
|
||||
if isinstance(
|
||||
col.server_default, schema.DefaultClause
|
||||
) and isinstance(col.server_default.arg, sql.ClauseElement):
|
||||
return True
|
||||
elif (
|
||||
isinstance(col.server_default, Computed)
|
||||
and col.server_default.persisted
|
||||
):
|
||||
return True
|
||||
elif op[0] not in ("create_index", "drop_index"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_constraint(self, const: Constraint):
|
||||
# attempt to distinguish between an
|
||||
# auto-gen constraint and an explicit one
|
||||
if const._create_rule is None:
|
||||
raise NotImplementedError(
|
||||
"No support for ALTER of constraints in SQLite dialect. "
|
||||
"Please refer to the batch mode feature which allows for "
|
||||
"SQLite migrations using a copy-and-move strategy."
|
||||
)
|
||||
elif const._create_rule(self):
|
||||
util.warn(
|
||||
"Skipping unsupported ALTER for "
|
||||
"creation of implicit constraint. "
|
||||
"Please refer to the batch mode feature which allows for "
|
||||
"SQLite migrations using a copy-and-move strategy."
|
||||
)
|
||||
|
||||
def drop_constraint(self, const: Constraint, **kw: Any):
|
||||
if const._create_rule is None:
|
||||
raise NotImplementedError(
|
||||
"No support for ALTER of constraints in SQLite dialect. "
|
||||
"Please refer to the batch mode feature which allows for "
|
||||
"SQLite migrations using a copy-and-move strategy."
|
||||
)
|
||||
|
||||
def compare_server_default(
|
||||
self,
|
||||
inspector_column: Column[Any],
|
||||
metadata_column: Column[Any],
|
||||
rendered_metadata_default: Optional[str],
|
||||
rendered_inspector_default: Optional[str],
|
||||
) -> bool:
|
||||
if rendered_metadata_default is not None:
|
||||
rendered_metadata_default = re.sub(
|
||||
r"^\((.+)\)$", r"\1", rendered_metadata_default
|
||||
)
|
||||
|
||||
rendered_metadata_default = re.sub(
|
||||
r"^\"?'(.+)'\"?$", r"\1", rendered_metadata_default
|
||||
)
|
||||
|
||||
if rendered_inspector_default is not None:
|
||||
rendered_inspector_default = re.sub(
|
||||
r"^\((.+)\)$", r"\1", rendered_inspector_default
|
||||
)
|
||||
|
||||
rendered_inspector_default = re.sub(
|
||||
r"^\"?'(.+)'\"?$", r"\1", rendered_inspector_default
|
||||
)
|
||||
|
||||
return rendered_inspector_default != rendered_metadata_default
|
||||
|
||||
def _guess_if_default_is_unparenthesized_sql_expr(
|
||||
self, expr: Optional[str]
|
||||
) -> bool:
|
||||
"""Determine if a server default is a SQL expression or a constant.
|
||||
|
||||
There are too many assertions that expect server defaults to round-trip
|
||||
identically without parenthesis added so we will add parens only in
|
||||
very specific cases.
|
||||
|
||||
"""
|
||||
if not expr:
|
||||
return False
|
||||
elif re.match(r"^[0-9\.]$", expr):
|
||||
return False
|
||||
elif re.match(r"^'.+'$", expr):
|
||||
return False
|
||||
elif re.match(r"^\(.+\)$", expr):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def autogen_column_reflect(
|
||||
self,
|
||||
inspector: Inspector,
|
||||
table: Table,
|
||||
column_info: Dict[str, Any],
|
||||
) -> None:
|
||||
# SQLite expression defaults require parenthesis when sent
|
||||
# as DDL
|
||||
if self._guess_if_default_is_unparenthesized_sql_expr(
|
||||
column_info.get("default", None)
|
||||
):
|
||||
column_info["default"] = "(%s)" % (column_info["default"],)
|
||||
|
||||
def render_ddl_sql_expr(
|
||||
self, expr: ClauseElement, is_server_default: bool = False, **kw
|
||||
) -> str:
|
||||
# SQLite expression defaults require parenthesis when sent
|
||||
# as DDL
|
||||
str_expr = super().render_ddl_sql_expr(
|
||||
expr, is_server_default=is_server_default, **kw
|
||||
)
|
||||
|
||||
if (
|
||||
is_server_default
|
||||
and self._guess_if_default_is_unparenthesized_sql_expr(str_expr)
|
||||
):
|
||||
str_expr = "(%s)" % (str_expr,)
|
||||
return str_expr
|
||||
|
||||
def cast_for_batch_migrate(
|
||||
self,
|
||||
existing: Column[Any],
|
||||
existing_transfer: Dict[str, Union[TypeEngine, Cast]],
|
||||
new_type: TypeEngine,
|
||||
) -> None:
|
||||
if (
|
||||
existing.type._type_affinity is not new_type._type_affinity
|
||||
and not isinstance(new_type, JSON)
|
||||
):
|
||||
existing_transfer["expr"] = cast(
|
||||
existing_transfer["expr"], new_type
|
||||
)
|
||||
|
||||
def correct_for_autogen_constraints(
|
||||
self,
|
||||
conn_unique_constraints,
|
||||
conn_indexes,
|
||||
metadata_unique_constraints,
|
||||
metadata_indexes,
|
||||
):
|
||||
self._skip_functional_indexes(metadata_indexes, conn_indexes)
|
||||
|
||||
|
||||
@compiles(RenameTable, "sqlite")
|
||||
def visit_rename_table(
|
||||
element: RenameTable, compiler: DDLCompiler, **kw
|
||||
) -> str:
|
||||
return "%s RENAME TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_table_name(compiler, element.new_table_name, None),
|
||||
)
|
||||
|
||||
|
||||
@compiles(ColumnName, "sqlite")
|
||||
def visit_column_name(element: ColumnName, compiler: DDLCompiler, **kw) -> str:
|
||||
return "%s RENAME COLUMN %s TO %s" % (
|
||||
alter_table(compiler, element.table_name, element.schema),
|
||||
format_column_name(compiler, element.column_name),
|
||||
format_column_name(compiler, element.newname),
|
||||
)
|
||||
|
||||
|
||||
# @compiles(AddColumn, 'sqlite')
|
||||
# def visit_add_column(element, compiler, **kw):
|
||||
# return "%s %s" % (
|
||||
# alter_table(compiler, element.table_name, element.schema),
|
||||
# add_column(compiler, element.column, **kw)
|
||||
# )
|
||||
|
||||
|
||||
# def add_column(compiler, column, **kw):
|
||||
# text = "ADD COLUMN %s" % compiler.get_column_specification(column, **kw)
|
||||
# need to modify SQLAlchemy so that the CHECK associated with a Boolean
|
||||
# or Enum gets placed as part of the column constraints, not the Table
|
||||
# see ticket 98
|
||||
# for const in column.constraints:
|
||||
# text += compiler.process(AddConstraint(const))
|
||||
# return text
|
||||
1
venv/Lib/site-packages/alembic/environment.py
Normal file
1
venv/Lib/site-packages/alembic/environment.py
Normal file
@@ -0,0 +1 @@
|
||||
from .runtime.environment import * # noqa
|
||||
1
venv/Lib/site-packages/alembic/migration.py
Normal file
1
venv/Lib/site-packages/alembic/migration.py
Normal file
@@ -0,0 +1 @@
|
||||
from .runtime.migration import * # noqa
|
||||
5
venv/Lib/site-packages/alembic/op.py
Normal file
5
venv/Lib/site-packages/alembic/op.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .operations.base import Operations
|
||||
|
||||
# create proxy functions for
|
||||
# each method on the Operations class.
|
||||
Operations.create_module_class_proxy(globals(), locals())
|
||||
1356
venv/Lib/site-packages/alembic/op.pyi
Normal file
1356
venv/Lib/site-packages/alembic/op.pyi
Normal file
File diff suppressed because it is too large
Load Diff
15
venv/Lib/site-packages/alembic/operations/__init__.py
Normal file
15
venv/Lib/site-packages/alembic/operations/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from . import toimpl
|
||||
from .base import AbstractOperations
|
||||
from .base import BatchOperations
|
||||
from .base import Operations
|
||||
from .ops import MigrateOperation
|
||||
from .ops import MigrationScript
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AbstractOperations",
|
||||
"Operations",
|
||||
"BatchOperations",
|
||||
"MigrateOperation",
|
||||
"MigrationScript",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1923
venv/Lib/site-packages/alembic/operations/base.py
Normal file
1923
venv/Lib/site-packages/alembic/operations/base.py
Normal file
File diff suppressed because it is too large
Load Diff
718
venv/Lib/site-packages/alembic/operations/batch.py
Normal file
718
venv/Lib/site-packages/alembic/operations/batch.py
Normal file
@@ -0,0 +1,718 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import CheckConstraint
|
||||
from sqlalchemy import Column
|
||||
from sqlalchemy import ForeignKeyConstraint
|
||||
from sqlalchemy import Index
|
||||
from sqlalchemy import MetaData
|
||||
from sqlalchemy import PrimaryKeyConstraint
|
||||
from sqlalchemy import schema as sql_schema
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import Table
|
||||
from sqlalchemy import types as sqltypes
|
||||
from sqlalchemy.sql.schema import SchemaEventTarget
|
||||
from sqlalchemy.util import OrderedDict
|
||||
from sqlalchemy.util import topological
|
||||
|
||||
from ..util import exc
|
||||
from ..util.sqla_compat import _columns_for_constraint
|
||||
from ..util.sqla_compat import _copy
|
||||
from ..util.sqla_compat import _copy_expression
|
||||
from ..util.sqla_compat import _ensure_scope_for_ddl
|
||||
from ..util.sqla_compat import _fk_is_self_referential
|
||||
from ..util.sqla_compat import _idx_table_bound_expressions
|
||||
from ..util.sqla_compat import _is_type_bound
|
||||
from ..util.sqla_compat import _remove_column_from_collection
|
||||
from ..util.sqla_compat import _resolve_for_variant
|
||||
from ..util.sqla_compat import constraint_name_defined
|
||||
from ..util.sqla_compat import constraint_name_string
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
from sqlalchemy.engine import Dialect
|
||||
from sqlalchemy.sql.elements import ColumnClause
|
||||
from sqlalchemy.sql.elements import quoted_name
|
||||
from sqlalchemy.sql.functions import Function
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from ..ddl.impl import DefaultImpl
|
||||
|
||||
|
||||
class BatchOperationsImpl:
|
||||
def __init__(
|
||||
self,
|
||||
operations,
|
||||
table_name,
|
||||
schema,
|
||||
recreate,
|
||||
copy_from,
|
||||
table_args,
|
||||
table_kwargs,
|
||||
reflect_args,
|
||||
reflect_kwargs,
|
||||
naming_convention,
|
||||
partial_reordering,
|
||||
):
|
||||
self.operations = operations
|
||||
self.table_name = table_name
|
||||
self.schema = schema
|
||||
if recreate not in ("auto", "always", "never"):
|
||||
raise ValueError(
|
||||
"recreate may be one of 'auto', 'always', or 'never'."
|
||||
)
|
||||
self.recreate = recreate
|
||||
self.copy_from = copy_from
|
||||
self.table_args = table_args
|
||||
self.table_kwargs = dict(table_kwargs)
|
||||
self.reflect_args = reflect_args
|
||||
self.reflect_kwargs = dict(reflect_kwargs)
|
||||
self.reflect_kwargs.setdefault(
|
||||
"listeners", list(self.reflect_kwargs.get("listeners", ()))
|
||||
)
|
||||
self.reflect_kwargs["listeners"].append(
|
||||
("column_reflect", operations.impl.autogen_column_reflect)
|
||||
)
|
||||
self.naming_convention = naming_convention
|
||||
self.partial_reordering = partial_reordering
|
||||
self.batch = []
|
||||
|
||||
@property
|
||||
def dialect(self) -> Dialect:
|
||||
return self.operations.impl.dialect
|
||||
|
||||
@property
|
||||
def impl(self) -> DefaultImpl:
|
||||
return self.operations.impl
|
||||
|
||||
def _should_recreate(self) -> bool:
|
||||
if self.recreate == "auto":
|
||||
return self.operations.impl.requires_recreate_in_batch(self)
|
||||
elif self.recreate == "always":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def flush(self) -> None:
|
||||
should_recreate = self._should_recreate()
|
||||
|
||||
with _ensure_scope_for_ddl(self.impl.connection):
|
||||
if not should_recreate:
|
||||
for opname, arg, kw in self.batch:
|
||||
fn = getattr(self.operations.impl, opname)
|
||||
fn(*arg, **kw)
|
||||
else:
|
||||
if self.naming_convention:
|
||||
m1 = MetaData(naming_convention=self.naming_convention)
|
||||
else:
|
||||
m1 = MetaData()
|
||||
|
||||
if self.copy_from is not None:
|
||||
existing_table = self.copy_from
|
||||
reflected = False
|
||||
else:
|
||||
if self.operations.migration_context.as_sql:
|
||||
raise exc.CommandError(
|
||||
f"This operation cannot proceed in --sql mode; "
|
||||
f"batch mode with dialect "
|
||||
f"{self.operations.migration_context.dialect.name} " # noqa: E501
|
||||
f"requires a live database connection with which "
|
||||
f'to reflect the table "{self.table_name}". '
|
||||
f"To generate a batch SQL migration script using "
|
||||
"table "
|
||||
'"move and copy", a complete Table object '
|
||||
f'should be passed to the "copy_from" argument '
|
||||
"of the batch_alter_table() method so that table "
|
||||
"reflection can be skipped."
|
||||
)
|
||||
|
||||
existing_table = Table(
|
||||
self.table_name,
|
||||
m1,
|
||||
schema=self.schema,
|
||||
autoload_with=self.operations.get_bind(),
|
||||
*self.reflect_args,
|
||||
**self.reflect_kwargs,
|
||||
)
|
||||
reflected = True
|
||||
|
||||
batch_impl = ApplyBatchImpl(
|
||||
self.impl,
|
||||
existing_table,
|
||||
self.table_args,
|
||||
self.table_kwargs,
|
||||
reflected,
|
||||
partial_reordering=self.partial_reordering,
|
||||
)
|
||||
for opname, arg, kw in self.batch:
|
||||
fn = getattr(batch_impl, opname)
|
||||
fn(*arg, **kw)
|
||||
|
||||
batch_impl._create(self.impl)
|
||||
|
||||
def alter_column(self, *arg, **kw) -> None:
|
||||
self.batch.append(("alter_column", arg, kw))
|
||||
|
||||
def add_column(self, *arg, **kw) -> None:
|
||||
if (
|
||||
"insert_before" in kw or "insert_after" in kw
|
||||
) and not self._should_recreate():
|
||||
raise exc.CommandError(
|
||||
"Can't specify insert_before or insert_after when using "
|
||||
"ALTER; please specify recreate='always'"
|
||||
)
|
||||
self.batch.append(("add_column", arg, kw))
|
||||
|
||||
def drop_column(self, *arg, **kw) -> None:
|
||||
self.batch.append(("drop_column", arg, kw))
|
||||
|
||||
def add_constraint(self, const: Constraint) -> None:
|
||||
self.batch.append(("add_constraint", (const,), {}))
|
||||
|
||||
def drop_constraint(self, const: Constraint) -> None:
|
||||
self.batch.append(("drop_constraint", (const,), {}))
|
||||
|
||||
def rename_table(self, *arg, **kw):
|
||||
self.batch.append(("rename_table", arg, kw))
|
||||
|
||||
def create_index(self, idx: Index, **kw: Any) -> None:
|
||||
self.batch.append(("create_index", (idx,), kw))
|
||||
|
||||
def drop_index(self, idx: Index, **kw: Any) -> None:
|
||||
self.batch.append(("drop_index", (idx,), kw))
|
||||
|
||||
def create_table_comment(self, table):
|
||||
self.batch.append(("create_table_comment", (table,), {}))
|
||||
|
||||
def drop_table_comment(self, table):
|
||||
self.batch.append(("drop_table_comment", (table,), {}))
|
||||
|
||||
def create_table(self, table):
|
||||
raise NotImplementedError("Can't create table in batch mode")
|
||||
|
||||
def drop_table(self, table):
|
||||
raise NotImplementedError("Can't drop table in batch mode")
|
||||
|
||||
def create_column_comment(self, column):
|
||||
self.batch.append(("create_column_comment", (column,), {}))
|
||||
|
||||
|
||||
class ApplyBatchImpl:
|
||||
def __init__(
|
||||
self,
|
||||
impl: DefaultImpl,
|
||||
table: Table,
|
||||
table_args: tuple,
|
||||
table_kwargs: Dict[str, Any],
|
||||
reflected: bool,
|
||||
partial_reordering: tuple = (),
|
||||
) -> None:
|
||||
self.impl = impl
|
||||
self.table = table # this is a Table object
|
||||
self.table_args = table_args
|
||||
self.table_kwargs = table_kwargs
|
||||
self.temp_table_name = self._calc_temp_name(table.name)
|
||||
self.new_table: Optional[Table] = None
|
||||
|
||||
self.partial_reordering = partial_reordering # tuple of tuples
|
||||
self.add_col_ordering: Tuple[
|
||||
Tuple[str, str], ...
|
||||
] = () # tuple of tuples
|
||||
|
||||
self.column_transfers = OrderedDict(
|
||||
(c.name, {"expr": c}) for c in self.table.c
|
||||
)
|
||||
self.existing_ordering = list(self.column_transfers)
|
||||
|
||||
self.reflected = reflected
|
||||
self._grab_table_elements()
|
||||
|
||||
@classmethod
|
||||
def _calc_temp_name(cls, tablename: Union[quoted_name, str]) -> str:
|
||||
return ("_alembic_tmp_%s" % tablename)[0:50]
|
||||
|
||||
def _grab_table_elements(self) -> None:
|
||||
schema = self.table.schema
|
||||
self.columns: Dict[str, Column[Any]] = OrderedDict()
|
||||
for c in self.table.c:
|
||||
c_copy = _copy(c, schema=schema)
|
||||
c_copy.unique = c_copy.index = False
|
||||
# ensure that the type object was copied,
|
||||
# as we may need to modify it in-place
|
||||
if isinstance(c.type, SchemaEventTarget):
|
||||
assert c_copy.type is not c.type
|
||||
self.columns[c.name] = c_copy
|
||||
self.named_constraints: Dict[str, Constraint] = {}
|
||||
self.unnamed_constraints = []
|
||||
self.col_named_constraints = {}
|
||||
self.indexes: Dict[str, Index] = {}
|
||||
self.new_indexes: Dict[str, Index] = {}
|
||||
|
||||
for const in self.table.constraints:
|
||||
if _is_type_bound(const):
|
||||
continue
|
||||
elif (
|
||||
self.reflected
|
||||
and isinstance(const, CheckConstraint)
|
||||
and not const.name
|
||||
):
|
||||
# TODO: we are skipping unnamed reflected CheckConstraint
|
||||
# because
|
||||
# we have no way to determine _is_type_bound() for these.
|
||||
pass
|
||||
elif constraint_name_string(const.name):
|
||||
self.named_constraints[const.name] = const
|
||||
else:
|
||||
self.unnamed_constraints.append(const)
|
||||
|
||||
if not self.reflected:
|
||||
for col in self.table.c:
|
||||
for const in col.constraints:
|
||||
if const.name:
|
||||
self.col_named_constraints[const.name] = (col, const)
|
||||
|
||||
for idx in self.table.indexes:
|
||||
self.indexes[idx.name] = idx # type: ignore[index]
|
||||
|
||||
for k in self.table.kwargs:
|
||||
self.table_kwargs.setdefault(k, self.table.kwargs[k])
|
||||
|
||||
def _adjust_self_columns_for_partial_reordering(self) -> None:
|
||||
pairs = set()
|
||||
|
||||
col_by_idx = list(self.columns)
|
||||
|
||||
if self.partial_reordering:
|
||||
for tuple_ in self.partial_reordering:
|
||||
for index, elem in enumerate(tuple_):
|
||||
if index > 0:
|
||||
pairs.add((tuple_[index - 1], elem))
|
||||
else:
|
||||
for index, elem in enumerate(self.existing_ordering):
|
||||
if index > 0:
|
||||
pairs.add((col_by_idx[index - 1], elem))
|
||||
|
||||
pairs.update(self.add_col_ordering)
|
||||
|
||||
# this can happen if some columns were dropped and not removed
|
||||
# from existing_ordering. this should be prevented already, but
|
||||
# conservatively making sure this didn't happen
|
||||
pairs_list = [p for p in pairs if p[0] != p[1]]
|
||||
|
||||
sorted_ = list(
|
||||
topological.sort(pairs_list, col_by_idx, deterministic_order=True)
|
||||
)
|
||||
self.columns = OrderedDict((k, self.columns[k]) for k in sorted_)
|
||||
self.column_transfers = OrderedDict(
|
||||
(k, self.column_transfers[k]) for k in sorted_
|
||||
)
|
||||
|
||||
def _transfer_elements_to_new_table(self) -> None:
|
||||
assert self.new_table is None, "Can only create new table once"
|
||||
|
||||
m = MetaData()
|
||||
schema = self.table.schema
|
||||
|
||||
if self.partial_reordering or self.add_col_ordering:
|
||||
self._adjust_self_columns_for_partial_reordering()
|
||||
|
||||
self.new_table = new_table = Table(
|
||||
self.temp_table_name,
|
||||
m,
|
||||
*(list(self.columns.values()) + list(self.table_args)),
|
||||
schema=schema,
|
||||
**self.table_kwargs,
|
||||
)
|
||||
|
||||
for const in (
|
||||
list(self.named_constraints.values()) + self.unnamed_constraints
|
||||
):
|
||||
const_columns = {c.key for c in _columns_for_constraint(const)}
|
||||
|
||||
if not const_columns.issubset(self.column_transfers):
|
||||
continue
|
||||
|
||||
const_copy: Constraint
|
||||
if isinstance(const, ForeignKeyConstraint):
|
||||
if _fk_is_self_referential(const):
|
||||
# for self-referential constraint, refer to the
|
||||
# *original* table name, and not _alembic_batch_temp.
|
||||
# This is consistent with how we're handling
|
||||
# FK constraints from other tables; we assume SQLite
|
||||
# no foreign keys just keeps the names unchanged, so
|
||||
# when we rename back, they match again.
|
||||
const_copy = _copy(
|
||||
const, schema=schema, target_table=self.table
|
||||
)
|
||||
else:
|
||||
# "target_table" for ForeignKeyConstraint.copy() is
|
||||
# only used if the FK is detected as being
|
||||
# self-referential, which we are handling above.
|
||||
const_copy = _copy(const, schema=schema)
|
||||
else:
|
||||
const_copy = _copy(
|
||||
const, schema=schema, target_table=new_table
|
||||
)
|
||||
if isinstance(const, ForeignKeyConstraint):
|
||||
self._setup_referent(m, const)
|
||||
new_table.append_constraint(const_copy)
|
||||
|
||||
def _gather_indexes_from_both_tables(self) -> List[Index]:
|
||||
assert self.new_table is not None
|
||||
idx: List[Index] = []
|
||||
|
||||
for idx_existing in self.indexes.values():
|
||||
# this is a lift-and-move from Table.to_metadata
|
||||
|
||||
if idx_existing._column_flag:
|
||||
continue
|
||||
|
||||
idx_copy = Index(
|
||||
idx_existing.name,
|
||||
unique=idx_existing.unique,
|
||||
*[
|
||||
_copy_expression(expr, self.new_table)
|
||||
for expr in _idx_table_bound_expressions(idx_existing)
|
||||
],
|
||||
_table=self.new_table,
|
||||
**idx_existing.kwargs,
|
||||
)
|
||||
idx.append(idx_copy)
|
||||
|
||||
for index in self.new_indexes.values():
|
||||
idx.append(
|
||||
Index(
|
||||
index.name,
|
||||
unique=index.unique,
|
||||
*[self.new_table.c[col] for col in index.columns.keys()],
|
||||
**index.kwargs,
|
||||
)
|
||||
)
|
||||
return idx
|
||||
|
||||
def _setup_referent(
|
||||
self, metadata: MetaData, constraint: ForeignKeyConstraint
|
||||
) -> None:
|
||||
spec = constraint.elements[0]._get_colspec()
|
||||
parts = spec.split(".")
|
||||
tname = parts[-2]
|
||||
if len(parts) == 3:
|
||||
referent_schema = parts[0]
|
||||
else:
|
||||
referent_schema = None
|
||||
|
||||
if tname != self.temp_table_name:
|
||||
key = sql_schema._get_table_key(tname, referent_schema)
|
||||
|
||||
def colspec(elem: Any):
|
||||
return elem._get_colspec()
|
||||
|
||||
if key in metadata.tables:
|
||||
t = metadata.tables[key]
|
||||
for elem in constraint.elements:
|
||||
colname = colspec(elem).split(".")[-1]
|
||||
if colname not in t.c:
|
||||
t.append_column(Column(colname, sqltypes.NULLTYPE))
|
||||
else:
|
||||
Table(
|
||||
tname,
|
||||
metadata,
|
||||
*[
|
||||
Column(n, sqltypes.NULLTYPE)
|
||||
for n in [
|
||||
colspec(elem).split(".")[-1]
|
||||
for elem in constraint.elements
|
||||
]
|
||||
],
|
||||
schema=referent_schema,
|
||||
)
|
||||
|
||||
def _create(self, op_impl: DefaultImpl) -> None:
|
||||
self._transfer_elements_to_new_table()
|
||||
|
||||
op_impl.prep_table_for_batch(self, self.table)
|
||||
assert self.new_table is not None
|
||||
op_impl.create_table(self.new_table)
|
||||
|
||||
try:
|
||||
op_impl._exec(
|
||||
self.new_table.insert()
|
||||
.inline()
|
||||
.from_select(
|
||||
list(
|
||||
k
|
||||
for k, transfer in self.column_transfers.items()
|
||||
if "expr" in transfer
|
||||
),
|
||||
select(
|
||||
*[
|
||||
transfer["expr"]
|
||||
for transfer in self.column_transfers.values()
|
||||
if "expr" in transfer
|
||||
]
|
||||
),
|
||||
)
|
||||
)
|
||||
op_impl.drop_table(self.table)
|
||||
except:
|
||||
op_impl.drop_table(self.new_table)
|
||||
raise
|
||||
else:
|
||||
op_impl.rename_table(
|
||||
self.temp_table_name, self.table.name, schema=self.table.schema
|
||||
)
|
||||
self.new_table.name = self.table.name
|
||||
try:
|
||||
for idx in self._gather_indexes_from_both_tables():
|
||||
op_impl.create_index(idx)
|
||||
finally:
|
||||
self.new_table.name = self.temp_table_name
|
||||
|
||||
def alter_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column_name: str,
|
||||
nullable: Optional[bool] = None,
|
||||
server_default: Optional[Union[Function[Any], str, bool]] = False,
|
||||
name: Optional[str] = None,
|
||||
type_: Optional[TypeEngine] = None,
|
||||
autoincrement: Optional[Union[bool, Literal["auto"]]] = None,
|
||||
comment: Union[str, Literal[False]] = False,
|
||||
**kw,
|
||||
) -> None:
|
||||
existing = self.columns[column_name]
|
||||
existing_transfer: Dict[str, Any] = self.column_transfers[column_name]
|
||||
if name is not None and name != column_name:
|
||||
# note that we don't change '.key' - we keep referring
|
||||
# to the renamed column by its old key in _create(). neat!
|
||||
existing.name = name
|
||||
existing_transfer["name"] = name
|
||||
|
||||
existing_type = kw.get("existing_type", None)
|
||||
if existing_type:
|
||||
resolved_existing_type = _resolve_for_variant(
|
||||
kw["existing_type"], self.impl.dialect
|
||||
)
|
||||
|
||||
# pop named constraints for Boolean/Enum for rename
|
||||
if (
|
||||
isinstance(resolved_existing_type, SchemaEventTarget)
|
||||
and resolved_existing_type.name # type:ignore[attr-defined] # noqa E501
|
||||
):
|
||||
self.named_constraints.pop(
|
||||
resolved_existing_type.name, # type:ignore[attr-defined] # noqa E501
|
||||
None,
|
||||
)
|
||||
|
||||
if type_ is not None:
|
||||
type_ = sqltypes.to_instance(type_)
|
||||
# old type is being discarded so turn off eventing
|
||||
# rules. Alternatively we can
|
||||
# erase the events set up by this type, but this is simpler.
|
||||
# we also ignore the drop_constraint that will come here from
|
||||
# Operations.implementation_for(alter_column)
|
||||
|
||||
if isinstance(existing.type, SchemaEventTarget):
|
||||
existing.type._create_events = ( # type:ignore[attr-defined]
|
||||
existing.type.create_constraint # type:ignore[attr-defined] # noqa
|
||||
) = False
|
||||
|
||||
self.impl.cast_for_batch_migrate(
|
||||
existing, existing_transfer, type_
|
||||
)
|
||||
|
||||
existing.type = type_
|
||||
|
||||
# we *dont* however set events for the new type, because
|
||||
# alter_column is invoked from
|
||||
# Operations.implementation_for(alter_column) which already
|
||||
# will emit an add_constraint()
|
||||
|
||||
if nullable is not None:
|
||||
existing.nullable = nullable
|
||||
if server_default is not False:
|
||||
if server_default is None:
|
||||
existing.server_default = None
|
||||
else:
|
||||
sql_schema.DefaultClause(
|
||||
server_default # type: ignore[arg-type]
|
||||
)._set_parent(existing)
|
||||
if autoincrement is not None:
|
||||
existing.autoincrement = bool(autoincrement)
|
||||
|
||||
if comment is not False:
|
||||
existing.comment = comment
|
||||
|
||||
def _setup_dependencies_for_add_column(
|
||||
self,
|
||||
colname: str,
|
||||
insert_before: Optional[str],
|
||||
insert_after: Optional[str],
|
||||
) -> None:
|
||||
index_cols = self.existing_ordering
|
||||
col_indexes = {name: i for i, name in enumerate(index_cols)}
|
||||
|
||||
if not self.partial_reordering:
|
||||
if insert_after:
|
||||
if not insert_before:
|
||||
if insert_after in col_indexes:
|
||||
# insert after an existing column
|
||||
idx = col_indexes[insert_after] + 1
|
||||
if idx < len(index_cols):
|
||||
insert_before = index_cols[idx]
|
||||
else:
|
||||
# insert after a column that is also new
|
||||
insert_before = dict(self.add_col_ordering)[
|
||||
insert_after
|
||||
]
|
||||
if insert_before:
|
||||
if not insert_after:
|
||||
if insert_before in col_indexes:
|
||||
# insert before an existing column
|
||||
idx = col_indexes[insert_before] - 1
|
||||
if idx >= 0:
|
||||
insert_after = index_cols[idx]
|
||||
else:
|
||||
# insert before a column that is also new
|
||||
insert_after = {
|
||||
b: a for a, b in self.add_col_ordering
|
||||
}[insert_before]
|
||||
|
||||
if insert_before:
|
||||
self.add_col_ordering += ((colname, insert_before),)
|
||||
if insert_after:
|
||||
self.add_col_ordering += ((insert_after, colname),)
|
||||
|
||||
if (
|
||||
not self.partial_reordering
|
||||
and not insert_before
|
||||
and not insert_after
|
||||
and col_indexes
|
||||
):
|
||||
self.add_col_ordering += ((index_cols[-1], colname),)
|
||||
|
||||
def add_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column: Column[Any],
|
||||
insert_before: Optional[str] = None,
|
||||
insert_after: Optional[str] = None,
|
||||
**kw,
|
||||
) -> None:
|
||||
self._setup_dependencies_for_add_column(
|
||||
column.name, insert_before, insert_after
|
||||
)
|
||||
# we copy the column because operations.add_column()
|
||||
# gives us a Column that is part of a Table already.
|
||||
self.columns[column.name] = _copy(column, schema=self.table.schema)
|
||||
self.column_transfers[column.name] = {}
|
||||
|
||||
def drop_column(
|
||||
self,
|
||||
table_name: str,
|
||||
column: Union[ColumnClause[Any], Column[Any]],
|
||||
**kw,
|
||||
) -> None:
|
||||
if column.name in self.table.primary_key.columns:
|
||||
_remove_column_from_collection(
|
||||
self.table.primary_key.columns, column
|
||||
)
|
||||
del self.columns[column.name]
|
||||
del self.column_transfers[column.name]
|
||||
self.existing_ordering.remove(column.name)
|
||||
|
||||
# pop named constraints for Boolean/Enum for rename
|
||||
if (
|
||||
"existing_type" in kw
|
||||
and isinstance(kw["existing_type"], SchemaEventTarget)
|
||||
and kw["existing_type"].name # type:ignore[attr-defined]
|
||||
):
|
||||
self.named_constraints.pop(
|
||||
kw["existing_type"].name, None # type:ignore[attr-defined]
|
||||
)
|
||||
|
||||
def create_column_comment(self, column):
|
||||
"""the batch table creation function will issue create_column_comment
|
||||
on the real "impl" as part of the create table process.
|
||||
|
||||
That is, the Column object will have the comment on it already,
|
||||
so when it is received by add_column() it will be a normal part of
|
||||
the CREATE TABLE and doesn't need an extra step here.
|
||||
|
||||
"""
|
||||
|
||||
def create_table_comment(self, table):
|
||||
"""the batch table creation function will issue create_table_comment
|
||||
on the real "impl" as part of the create table process.
|
||||
|
||||
"""
|
||||
|
||||
def drop_table_comment(self, table):
|
||||
"""the batch table creation function will issue drop_table_comment
|
||||
on the real "impl" as part of the create table process.
|
||||
|
||||
"""
|
||||
|
||||
def add_constraint(self, const: Constraint) -> None:
|
||||
if not constraint_name_defined(const.name):
|
||||
raise ValueError("Constraint must have a name")
|
||||
if isinstance(const, sql_schema.PrimaryKeyConstraint):
|
||||
if self.table.primary_key in self.unnamed_constraints:
|
||||
self.unnamed_constraints.remove(self.table.primary_key)
|
||||
|
||||
if constraint_name_string(const.name):
|
||||
self.named_constraints[const.name] = const
|
||||
else:
|
||||
self.unnamed_constraints.append(const)
|
||||
|
||||
def drop_constraint(self, const: Constraint) -> None:
|
||||
if not const.name:
|
||||
raise ValueError("Constraint must have a name")
|
||||
try:
|
||||
if const.name in self.col_named_constraints:
|
||||
col, const = self.col_named_constraints.pop(const.name)
|
||||
|
||||
for col_const in list(self.columns[col.name].constraints):
|
||||
if col_const.name == const.name:
|
||||
self.columns[col.name].constraints.remove(col_const)
|
||||
elif constraint_name_string(const.name):
|
||||
const = self.named_constraints.pop(const.name)
|
||||
elif const in self.unnamed_constraints:
|
||||
self.unnamed_constraints.remove(const)
|
||||
|
||||
except KeyError:
|
||||
if _is_type_bound(const):
|
||||
# type-bound constraints are only included in the new
|
||||
# table via their type object in any case, so ignore the
|
||||
# drop_constraint() that comes here via the
|
||||
# Operations.implementation_for(alter_column)
|
||||
return
|
||||
raise ValueError("No such constraint: '%s'" % const.name)
|
||||
else:
|
||||
if isinstance(const, PrimaryKeyConstraint):
|
||||
for col in const.columns:
|
||||
self.columns[col.name].primary_key = False
|
||||
|
||||
def create_index(self, idx: Index) -> None:
|
||||
self.new_indexes[idx.name] = idx # type: ignore[index]
|
||||
|
||||
def drop_index(self, idx: Index) -> None:
|
||||
try:
|
||||
del self.indexes[idx.name] # type: ignore[arg-type]
|
||||
except KeyError:
|
||||
raise ValueError("No such index: '%s'" % idx.name)
|
||||
|
||||
def rename_table(self, *arg, **kw):
|
||||
raise NotImplementedError("TODO")
|
||||
2842
venv/Lib/site-packages/alembic/operations/ops.py
Normal file
2842
venv/Lib/site-packages/alembic/operations/ops.py
Normal file
File diff suppressed because it is too large
Load Diff
290
venv/Lib/site-packages/alembic/operations/schemaobj.py
Normal file
290
venv/Lib/site-packages/alembic/operations/schemaobj.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
from sqlalchemy.sql.schema import Column
|
||||
from sqlalchemy.sql.schema import Constraint
|
||||
from sqlalchemy.sql.schema import Index
|
||||
from sqlalchemy.types import Integer
|
||||
from sqlalchemy.types import NULLTYPE
|
||||
|
||||
from .. import util
|
||||
from ..util import sqla_compat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.elements import ColumnElement
|
||||
from sqlalchemy.sql.elements import TextClause
|
||||
from sqlalchemy.sql.schema import CheckConstraint
|
||||
from sqlalchemy.sql.schema import ForeignKey
|
||||
from sqlalchemy.sql.schema import ForeignKeyConstraint
|
||||
from sqlalchemy.sql.schema import MetaData
|
||||
from sqlalchemy.sql.schema import PrimaryKeyConstraint
|
||||
from sqlalchemy.sql.schema import Table
|
||||
from sqlalchemy.sql.schema import UniqueConstraint
|
||||
from sqlalchemy.sql.type_api import TypeEngine
|
||||
|
||||
from ..runtime.migration import MigrationContext
|
||||
|
||||
|
||||
class SchemaObjects:
|
||||
def __init__(
|
||||
self, migration_context: Optional[MigrationContext] = None
|
||||
) -> None:
|
||||
self.migration_context = migration_context
|
||||
|
||||
def primary_key_constraint(
|
||||
self,
|
||||
name: Optional[sqla_compat._ConstraintNameDefined],
|
||||
table_name: str,
|
||||
cols: Sequence[str],
|
||||
schema: Optional[str] = None,
|
||||
**dialect_kw,
|
||||
) -> PrimaryKeyConstraint:
|
||||
m = self.metadata()
|
||||
columns = [sa_schema.Column(n, NULLTYPE) for n in cols]
|
||||
t = sa_schema.Table(table_name, m, *columns, schema=schema)
|
||||
# SQLAlchemy primary key constraint name arg is wrongly typed on
|
||||
# the SQLAlchemy side through 2.0.5 at least
|
||||
p = sa_schema.PrimaryKeyConstraint(
|
||||
*[t.c[n] for n in cols], name=name, **dialect_kw # type: ignore
|
||||
)
|
||||
return p
|
||||
|
||||
def foreign_key_constraint(
|
||||
self,
|
||||
name: Optional[sqla_compat._ConstraintNameDefined],
|
||||
source: str,
|
||||
referent: str,
|
||||
local_cols: List[str],
|
||||
remote_cols: List[str],
|
||||
onupdate: Optional[str] = None,
|
||||
ondelete: Optional[str] = None,
|
||||
deferrable: Optional[bool] = None,
|
||||
source_schema: Optional[str] = None,
|
||||
referent_schema: Optional[str] = None,
|
||||
initially: Optional[str] = None,
|
||||
match: Optional[str] = None,
|
||||
**dialect_kw,
|
||||
) -> ForeignKeyConstraint:
|
||||
m = self.metadata()
|
||||
if source == referent and source_schema == referent_schema:
|
||||
t1_cols = local_cols + remote_cols
|
||||
else:
|
||||
t1_cols = local_cols
|
||||
sa_schema.Table(
|
||||
referent,
|
||||
m,
|
||||
*[sa_schema.Column(n, NULLTYPE) for n in remote_cols],
|
||||
schema=referent_schema,
|
||||
)
|
||||
|
||||
t1 = sa_schema.Table(
|
||||
source,
|
||||
m,
|
||||
*[
|
||||
sa_schema.Column(n, NULLTYPE)
|
||||
for n in util.unique_list(t1_cols)
|
||||
],
|
||||
schema=source_schema,
|
||||
)
|
||||
|
||||
tname = (
|
||||
"%s.%s" % (referent_schema, referent)
|
||||
if referent_schema
|
||||
else referent
|
||||
)
|
||||
|
||||
dialect_kw["match"] = match
|
||||
|
||||
f = sa_schema.ForeignKeyConstraint(
|
||||
local_cols,
|
||||
["%s.%s" % (tname, n) for n in remote_cols],
|
||||
name=name,
|
||||
onupdate=onupdate,
|
||||
ondelete=ondelete,
|
||||
deferrable=deferrable,
|
||||
initially=initially,
|
||||
**dialect_kw,
|
||||
)
|
||||
t1.append_constraint(f)
|
||||
|
||||
return f
|
||||
|
||||
def unique_constraint(
|
||||
self,
|
||||
name: Optional[sqla_compat._ConstraintNameDefined],
|
||||
source: str,
|
||||
local_cols: Sequence[str],
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> UniqueConstraint:
|
||||
t = sa_schema.Table(
|
||||
source,
|
||||
self.metadata(),
|
||||
*[sa_schema.Column(n, NULLTYPE) for n in local_cols],
|
||||
schema=schema,
|
||||
)
|
||||
kw["name"] = name
|
||||
uq = sa_schema.UniqueConstraint(*[t.c[n] for n in local_cols], **kw)
|
||||
# TODO: need event tests to ensure the event
|
||||
# is fired off here
|
||||
t.append_constraint(uq)
|
||||
return uq
|
||||
|
||||
def check_constraint(
|
||||
self,
|
||||
name: Optional[sqla_compat._ConstraintNameDefined],
|
||||
source: str,
|
||||
condition: Union[str, TextClause, ColumnElement[Any]],
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> Union[CheckConstraint]:
|
||||
t = sa_schema.Table(
|
||||
source,
|
||||
self.metadata(),
|
||||
sa_schema.Column("x", Integer),
|
||||
schema=schema,
|
||||
)
|
||||
ck = sa_schema.CheckConstraint(condition, name=name, **kw)
|
||||
t.append_constraint(ck)
|
||||
return ck
|
||||
|
||||
def generic_constraint(
|
||||
self,
|
||||
name: Optional[sqla_compat._ConstraintNameDefined],
|
||||
table_name: str,
|
||||
type_: Optional[str],
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> Any:
|
||||
t = self.table(table_name, schema=schema)
|
||||
types: Dict[Optional[str], Any] = {
|
||||
"foreignkey": lambda name: sa_schema.ForeignKeyConstraint(
|
||||
[], [], name=name
|
||||
),
|
||||
"primary": sa_schema.PrimaryKeyConstraint,
|
||||
"unique": sa_schema.UniqueConstraint,
|
||||
"check": lambda name: sa_schema.CheckConstraint("", name=name),
|
||||
None: sa_schema.Constraint,
|
||||
}
|
||||
try:
|
||||
const = types[type_]
|
||||
except KeyError as ke:
|
||||
raise TypeError(
|
||||
"'type' can be one of %s"
|
||||
% ", ".join(sorted(repr(x) for x in types))
|
||||
) from ke
|
||||
else:
|
||||
const = const(name=name)
|
||||
t.append_constraint(const)
|
||||
return const
|
||||
|
||||
def metadata(self) -> MetaData:
|
||||
kw = {}
|
||||
if (
|
||||
self.migration_context is not None
|
||||
and "target_metadata" in self.migration_context.opts
|
||||
):
|
||||
mt = self.migration_context.opts["target_metadata"]
|
||||
if hasattr(mt, "naming_convention"):
|
||||
kw["naming_convention"] = mt.naming_convention
|
||||
return sa_schema.MetaData(**kw)
|
||||
|
||||
def table(self, name: str, *columns, **kw) -> Table:
|
||||
m = self.metadata()
|
||||
|
||||
cols = [
|
||||
sqla_compat._copy(c) if c.table is not None else c
|
||||
for c in columns
|
||||
if isinstance(c, Column)
|
||||
]
|
||||
# these flags have already added their UniqueConstraint /
|
||||
# Index objects to the table, so flip them off here.
|
||||
# SQLAlchemy tometadata() avoids this instead by preserving the
|
||||
# flags and skipping the constraints that have _type_bound on them,
|
||||
# but for a migration we'd rather list out the constraints
|
||||
# explicitly.
|
||||
_constraints_included = kw.pop("_constraints_included", False)
|
||||
if _constraints_included:
|
||||
for c in cols:
|
||||
c.unique = c.index = False
|
||||
|
||||
t = sa_schema.Table(name, m, *cols, **kw)
|
||||
|
||||
constraints = [
|
||||
(
|
||||
sqla_compat._copy(elem, target_table=t)
|
||||
if getattr(elem, "parent", None) is not t
|
||||
and getattr(elem, "parent", None) is not None
|
||||
else elem
|
||||
)
|
||||
for elem in columns
|
||||
if isinstance(elem, (Constraint, Index))
|
||||
]
|
||||
|
||||
for const in constraints:
|
||||
t.append_constraint(const)
|
||||
|
||||
for f in t.foreign_keys:
|
||||
self._ensure_table_for_fk(m, f)
|
||||
return t
|
||||
|
||||
def column(self, name: str, type_: TypeEngine, **kw) -> Column:
|
||||
return sa_schema.Column(name, type_, **kw)
|
||||
|
||||
def index(
|
||||
self,
|
||||
name: Optional[str],
|
||||
tablename: Optional[str],
|
||||
columns: Sequence[Union[str, TextClause, ColumnElement[Any]]],
|
||||
schema: Optional[str] = None,
|
||||
**kw,
|
||||
) -> Index:
|
||||
t = sa_schema.Table(
|
||||
tablename or "no_table",
|
||||
self.metadata(),
|
||||
schema=schema,
|
||||
)
|
||||
kw["_table"] = t
|
||||
idx = sa_schema.Index(
|
||||
name,
|
||||
*[util.sqla_compat._textual_index_column(t, n) for n in columns],
|
||||
**kw,
|
||||
)
|
||||
return idx
|
||||
|
||||
def _parse_table_key(self, table_key: str) -> Tuple[Optional[str], str]:
|
||||
if "." in table_key:
|
||||
tokens = table_key.split(".")
|
||||
sname: Optional[str] = ".".join(tokens[0:-1])
|
||||
tname = tokens[-1]
|
||||
else:
|
||||
tname = table_key
|
||||
sname = None
|
||||
return (sname, tname)
|
||||
|
||||
def _ensure_table_for_fk(self, metadata: MetaData, fk: ForeignKey) -> None:
|
||||
"""create a placeholder Table object for the referent of a
|
||||
ForeignKey.
|
||||
|
||||
"""
|
||||
if isinstance(fk._colspec, str):
|
||||
table_key, cname = fk._colspec.rsplit(".", 1)
|
||||
sname, tname = self._parse_table_key(table_key)
|
||||
if table_key not in metadata.tables:
|
||||
rel_t = sa_schema.Table(tname, metadata, schema=sname)
|
||||
else:
|
||||
rel_t = metadata.tables[table_key]
|
||||
if cname not in rel_t.c:
|
||||
rel_t.append_column(sa_schema.Column(cname, NULLTYPE))
|
||||
242
venv/Lib/site-packages/alembic/operations/toimpl.py
Normal file
242
venv/Lib/site-packages/alembic/operations/toimpl.py
Normal file
@@ -0,0 +1,242 @@
|
||||
# mypy: allow-untyped-defs, allow-incomplete-defs, allow-untyped-calls
|
||||
# mypy: no-warn-return-any, allow-any-generics
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import schema as sa_schema
|
||||
|
||||
from . import ops
|
||||
from .base import Operations
|
||||
from ..util.sqla_compat import _copy
|
||||
from ..util.sqla_compat import sqla_2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.sql.schema import Table
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.AlterColumnOp)
|
||||
def alter_column(
|
||||
operations: "Operations", operation: "ops.AlterColumnOp"
|
||||
) -> None:
|
||||
compiler = operations.impl.dialect.statement_compiler(
|
||||
operations.impl.dialect, None
|
||||
)
|
||||
|
||||
existing_type = operation.existing_type
|
||||
existing_nullable = operation.existing_nullable
|
||||
existing_server_default = operation.existing_server_default
|
||||
type_ = operation.modify_type
|
||||
column_name = operation.column_name
|
||||
table_name = operation.table_name
|
||||
schema = operation.schema
|
||||
server_default = operation.modify_server_default
|
||||
new_column_name = operation.modify_name
|
||||
nullable = operation.modify_nullable
|
||||
comment = operation.modify_comment
|
||||
existing_comment = operation.existing_comment
|
||||
|
||||
def _count_constraint(constraint):
|
||||
return not isinstance(constraint, sa_schema.PrimaryKeyConstraint) and (
|
||||
not constraint._create_rule or constraint._create_rule(compiler)
|
||||
)
|
||||
|
||||
if existing_type and type_:
|
||||
t = operations.schema_obj.table(
|
||||
table_name,
|
||||
sa_schema.Column(column_name, existing_type),
|
||||
schema=schema,
|
||||
)
|
||||
for constraint in t.constraints:
|
||||
if _count_constraint(constraint):
|
||||
operations.impl.drop_constraint(constraint)
|
||||
|
||||
operations.impl.alter_column(
|
||||
table_name,
|
||||
column_name,
|
||||
nullable=nullable,
|
||||
server_default=server_default,
|
||||
name=new_column_name,
|
||||
type_=type_,
|
||||
schema=schema,
|
||||
existing_type=existing_type,
|
||||
existing_server_default=existing_server_default,
|
||||
existing_nullable=existing_nullable,
|
||||
comment=comment,
|
||||
existing_comment=existing_comment,
|
||||
**operation.kw,
|
||||
)
|
||||
|
||||
if type_:
|
||||
t = operations.schema_obj.table(
|
||||
table_name,
|
||||
operations.schema_obj.column(column_name, type_),
|
||||
schema=schema,
|
||||
)
|
||||
for constraint in t.constraints:
|
||||
if _count_constraint(constraint):
|
||||
operations.impl.add_constraint(constraint)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.DropTableOp)
|
||||
def drop_table(operations: "Operations", operation: "ops.DropTableOp") -> None:
|
||||
kw = {}
|
||||
if operation.if_exists is not None:
|
||||
kw["if_exists"] = operation.if_exists
|
||||
operations.impl.drop_table(
|
||||
operation.to_table(operations.migration_context), **kw
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.DropColumnOp)
|
||||
def drop_column(
|
||||
operations: "Operations", operation: "ops.DropColumnOp"
|
||||
) -> None:
|
||||
column = operation.to_column(operations.migration_context)
|
||||
operations.impl.drop_column(
|
||||
operation.table_name,
|
||||
column,
|
||||
schema=operation.schema,
|
||||
if_exists=operation.if_exists,
|
||||
**operation.kw,
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.CreateIndexOp)
|
||||
def create_index(
|
||||
operations: "Operations", operation: "ops.CreateIndexOp"
|
||||
) -> None:
|
||||
idx = operation.to_index(operations.migration_context)
|
||||
kw = {}
|
||||
if operation.if_not_exists is not None:
|
||||
kw["if_not_exists"] = operation.if_not_exists
|
||||
operations.impl.create_index(idx, **kw)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.DropIndexOp)
|
||||
def drop_index(operations: "Operations", operation: "ops.DropIndexOp") -> None:
|
||||
kw = {}
|
||||
if operation.if_exists is not None:
|
||||
kw["if_exists"] = operation.if_exists
|
||||
|
||||
operations.impl.drop_index(
|
||||
operation.to_index(operations.migration_context),
|
||||
**kw,
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.CreateTableOp)
|
||||
def create_table(
|
||||
operations: "Operations", operation: "ops.CreateTableOp"
|
||||
) -> "Table":
|
||||
kw = {}
|
||||
if operation.if_not_exists is not None:
|
||||
kw["if_not_exists"] = operation.if_not_exists
|
||||
table = operation.to_table(operations.migration_context)
|
||||
operations.impl.create_table(table, **kw)
|
||||
return table
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.RenameTableOp)
|
||||
def rename_table(
|
||||
operations: "Operations", operation: "ops.RenameTableOp"
|
||||
) -> None:
|
||||
operations.impl.rename_table(
|
||||
operation.table_name, operation.new_table_name, schema=operation.schema
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.CreateTableCommentOp)
|
||||
def create_table_comment(
|
||||
operations: "Operations", operation: "ops.CreateTableCommentOp"
|
||||
) -> None:
|
||||
table = operation.to_table(operations.migration_context)
|
||||
operations.impl.create_table_comment(table)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.DropTableCommentOp)
|
||||
def drop_table_comment(
|
||||
operations: "Operations", operation: "ops.DropTableCommentOp"
|
||||
) -> None:
|
||||
table = operation.to_table(operations.migration_context)
|
||||
operations.impl.drop_table_comment(table)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.AddColumnOp)
|
||||
def add_column(operations: "Operations", operation: "ops.AddColumnOp") -> None:
|
||||
table_name = operation.table_name
|
||||
column = operation.column
|
||||
schema = operation.schema
|
||||
kw = operation.kw
|
||||
|
||||
if column.table is not None:
|
||||
column = _copy(column)
|
||||
|
||||
t = operations.schema_obj.table(table_name, column, schema=schema)
|
||||
operations.impl.add_column(
|
||||
table_name,
|
||||
column,
|
||||
schema=schema,
|
||||
if_not_exists=operation.if_not_exists,
|
||||
**kw,
|
||||
)
|
||||
|
||||
for constraint in t.constraints:
|
||||
if not isinstance(constraint, sa_schema.PrimaryKeyConstraint):
|
||||
operations.impl.add_constraint(constraint)
|
||||
for index in t.indexes:
|
||||
operations.impl.create_index(index)
|
||||
|
||||
with_comment = (
|
||||
operations.impl.dialect.supports_comments
|
||||
and not operations.impl.dialect.inline_comments
|
||||
)
|
||||
comment = column.comment
|
||||
if comment and with_comment:
|
||||
operations.impl.create_column_comment(column)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.AddConstraintOp)
|
||||
def create_constraint(
|
||||
operations: "Operations", operation: "ops.AddConstraintOp"
|
||||
) -> None:
|
||||
operations.impl.add_constraint(
|
||||
operation.to_constraint(operations.migration_context)
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.DropConstraintOp)
|
||||
def drop_constraint(
|
||||
operations: "Operations", operation: "ops.DropConstraintOp"
|
||||
) -> None:
|
||||
kw = {}
|
||||
if operation.if_exists is not None:
|
||||
if not sqla_2:
|
||||
raise NotImplementedError("SQLAlchemy 2.0 required")
|
||||
kw["if_exists"] = operation.if_exists
|
||||
operations.impl.drop_constraint(
|
||||
operations.schema_obj.generic_constraint(
|
||||
operation.constraint_name,
|
||||
operation.table_name,
|
||||
operation.constraint_type,
|
||||
schema=operation.schema,
|
||||
),
|
||||
**kw,
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.BulkInsertOp)
|
||||
def bulk_insert(
|
||||
operations: "Operations", operation: "ops.BulkInsertOp"
|
||||
) -> None:
|
||||
operations.impl.bulk_insert( # type: ignore[union-attr]
|
||||
operation.table, operation.rows, multiinsert=operation.multiinsert
|
||||
)
|
||||
|
||||
|
||||
@Operations.implementation_for(ops.ExecuteSQLOp)
|
||||
def execute_sql(
|
||||
operations: "Operations", operation: "ops.ExecuteSQLOp"
|
||||
) -> None:
|
||||
operations.migration_context.impl.execute(
|
||||
operation.sqltext, execution_options=operation.execution_options
|
||||
)
|
||||
0
venv/Lib/site-packages/alembic/py.typed
Normal file
0
venv/Lib/site-packages/alembic/py.typed
Normal file
0
venv/Lib/site-packages/alembic/runtime/__init__.py
Normal file
0
venv/Lib/site-packages/alembic/runtime/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
1051
venv/Lib/site-packages/alembic/runtime/environment.py
Normal file
1051
venv/Lib/site-packages/alembic/runtime/environment.py
Normal file
File diff suppressed because it is too large
Load Diff
1395
venv/Lib/site-packages/alembic/runtime/migration.py
Normal file
1395
venv/Lib/site-packages/alembic/runtime/migration.py
Normal file
File diff suppressed because it is too large
Load Diff
4
venv/Lib/site-packages/alembic/script/__init__.py
Normal file
4
venv/Lib/site-packages/alembic/script/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .base import Script
|
||||
from .base import ScriptDirectory
|
||||
|
||||
__all__ = ["ScriptDirectory", "Script"]
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user