34 Commits

Author SHA1 Message Date
long2ice
73b75349ee update version 0.2.0 2020-06-12 17:53:17 +08:00
long2ice
7bc553221a raise NotImplementedError 2020-06-12 09:31:01 +08:00
long2ice
7413a05e19 set --safe bool 2020-06-08 18:07:41 +08:00
long2ice
bf194ca8ce Update model file find method 2020-06-03 18:42:35 +08:00
long2ice
b06da0223a add --build 2020-06-03 09:39:52 +08:00
long2ice
83554cdc5d Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	.github/workflows/pypi.yml
#	.github/workflows/test.yml
#	Makefile
2020-06-03 09:38:20 +08:00
long2ice
6c76bfccad Merge remote-tracking branch 'origin/dev' into dev 2020-06-02 22:23:26 +08:00
long2ice
a1746e457c update github actions
update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions
2020-06-02 22:23:15 +08:00
long2ice
2a0435dea9 update github actions 2020-06-02 22:14:44 +08:00
long2ice
e87f67f1e1 update github actions
update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions

update github actions
2020-06-02 22:02:46 +08:00
long2ice
7b4b7ac749 update github actions 2020-06-02 18:58:59 +08:00
long2ice
5b9b51db3f update github actions 2020-06-02 18:38:39 +08:00
long2ice
ffeee3c901 update github actions 2020-06-02 18:28:50 +08:00
long2ice
b4366d2427 update github actions 2020-06-02 18:20:55 +08:00
long2ice
ec1c80f3a9 remove requirements 2020-06-01 14:57:29 +08:00
long2ice
d2083632eb add cli -V 2020-05-27 12:44:49 +08:00
long2ice
125389461f check tortoise add aerich.models 2020-05-26 14:44:55 +08:00
long2ice
c09c878eaf add support modify column
diff mysql ddl
2020-05-26 10:22:02 +08:00
long2ice
ef3e0c11d5 update version 2020-05-25 23:46:35 +08:00
long2ice
881f70f748 Fix default_connection when upgrade 2020-05-25 23:44:42 +08:00
long2ice
6ffca1a0c7 add support modify column 2020-05-25 22:39:39 +08:00
long2ice
95e41720cb Fix init db sql error 2020-05-25 18:53:34 +08:00
long2ice
40c0008e6e Fix upgrade error when migrate 2020-05-25 18:02:56 +08:00
long2ice
ce75e55d60 update README.rst 2020-05-25 16:36:18 +08:00
long2ice
4d4f951e09 update README.rst 2020-05-25 16:33:56 +08:00
long2ice
354e861dad add more test 2020-05-24 13:47:10 +08:00
long2ice
3a76486993 migrate raise error 2020-05-24 00:05:45 +08:00
long2ice
4d0a6b4de6 Fix version num str 2020-05-22 15:35:35 +08:00
long2ice
c01d2993e0 Exclude models.Aerich.
Add init record when init-db.
2020-05-22 11:59:03 +08:00
long2ice
bab5ebf2f0 migrate exclude aerich.models 2020-05-22 11:14:16 +08:00
long2ice
7e5cefd7d6 write old models exclude aerich.models 2020-05-22 11:03:52 +08:00
long2ice
0cea28d521 update version 2020-05-21 23:57:13 +08:00
long2ice
b92e6551fd update dependency_links 2020-05-21 23:33:58 +08:00
long2ice
bbabde32a1 update version 2020-05-21 21:24:21 +08:00
22 changed files with 490 additions and 365 deletions

View File

@@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- uses: dschep/install-poetry-action@v1.3
- name: Build dists
run: |
python3 setup.py sdist
run: make build
- name: Pypi Publish
uses: pypa/gh-action-pypi-publish@master
with:

View File

@@ -19,10 +19,7 @@ jobs:
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- uses: dschep/install-poetry-action@v1.3
- name: CI
env:
MYSQL_PASS: root
@@ -31,4 +28,4 @@ jobs:
POSTGRES_PASS: 123456
POSTGRES_HOST: 127.0.0.1
POSTGRES_PORT: 5432
run: make testall
run: make ci

View File

@@ -1,9 +1,38 @@
=========
ChangeLog
=========
0.2
===
0.2.0
-----
- Update model file find method.
- Set ``--safe`` bool.
0.1
===
0.1.9
-----
- Fix default_connection when upgrade
- Find default app instead of default.
- Diff MySQL ddl.
- Check tortoise config.
0.1.8
-----
- Fix upgrade error when migrate.
- Fix init db sql error.
- Support change column.
0.1.7
-----
- Exclude models.Aerich.
- Add init record when init-db.
- Fix version num str.
0.1.6
-----
- update dependency_links
0.1.5
-----
- Add sqlite and postgres support.

View File

@@ -1,3 +0,0 @@
include LICENSE
include README.rst
include requirements.txt

View File

@@ -1,4 +1,4 @@
checkfiles = aerich/ tests/
checkfiles = aerich/ tests/ conftest.py
black_opts = -l 100 -t py38
py_warn = PYTHONDEVMODE=1
MYSQL_HOST ?= "127.0.0.1"
@@ -12,15 +12,17 @@ help:
@echo "usage: make <target>"
@echo "Targets:"
@echo " up Updates dev/test dependencies"
@echo " deps Ensure dev/test dependencies are installed"
@echo " deps Ensure dev/test dependencies are installed"
@echo " check Checks that build is sane"
@echo " lint Reports all linter violations"
@echo " test Runs all tests"
@echo " style Auto-formats the code"
up:
@poetry update
deps:
@which pip-sync > /dev/null || pip install -q pip-tools
@pip install -r requirements-dev.txt
@poetry install -E dbdrivers
style: deps
isort -rc $(checkfiles)
@@ -50,10 +52,10 @@ test_postgres:
testall: deps test_sqlite test_postgres test_mysql
publish: deps
rm -fR dist/
python setup.py sdist
twine upload dist/*
build: deps
@poetry build
ci:
@act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04 -b
publish: deps
@poetry publish --build
ci: testall

View File

@@ -104,6 +104,10 @@ Init db
Success create app migrate location ./migrations/models
Success generate schema for app "models"
.. note::
If your Tortoise-ORM app is not default ``models``, you must specify ``--app`` like ``aerich --app other_models init-db``.
Update models and make migrate
------------------------------
@@ -155,6 +159,11 @@ Show heads to be migrated
1_202029051520102929_drop_column.json
Limitations
===========
* Not support ``rename column`` now.
* ``Sqlite`` and ``Postgres`` may not work as expected because I don't use those in my work.
License
=======
This project is licensed under the `MIT <https://github.com/long2ice/aerich/blob/master/LICENSE>`_ License.

View File

@@ -1 +1 @@
__version__ = "0.1.4"
__version__ = "0.1.9"

View File

@@ -6,8 +6,10 @@ from enum import Enum
import asyncclick as click
from asyncclick import Context, UsageError
from tortoise import ConfigurationError, Tortoise, generate_schema_for_client
from tortoise import Tortoise, generate_schema_for_client
from tortoise.exceptions import OperationalError
from tortoise.transactions import in_transaction
from tortoise.utils import get_schema_sql
from aerich.migrate import Migrate
from aerich.utils import get_app_connection, get_app_connection_name, get_tortoise_config
@@ -26,11 +28,11 @@ parser = ConfigParser()
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
@click.version_option(__version__)
@click.version_option(__version__, "-V", "--version")
@click.option(
"-c", "--config", default="aerich.ini", show_default=True, help="Config file.",
)
@click.option("--app", default="models", show_default=True, help="Tortoise-ORM app name.")
@click.option("--app", required=False, help="Tortoise-ORM app name.")
@click.option(
"-n",
"--name",
@@ -43,7 +45,6 @@ async def cli(ctx: Context, config, app, name):
ctx.ensure_object(dict)
ctx.obj["config_file"] = config
ctx.obj["name"] = name
ctx.obj["app"] = app
invoked_subcommand = ctx.invoked_subcommand
if invoked_subcommand != "init":
@@ -55,15 +56,15 @@ async def cli(ctx: Context, config, app, name):
tortoise_orm = parser[name]["tortoise_orm"]
tortoise_config = get_tortoise_config(ctx, tortoise_orm)
app = app or list(tortoise_config.get("apps").keys())[0]
if "aerich.models" not in tortoise_config.get("apps").get(app).get("models"):
raise UsageError("Check your tortoise config and add aerich.models to it.", ctx=ctx)
ctx.obj["config"] = tortoise_config
ctx.obj["location"] = location
ctx.obj["app"] = app
if invoked_subcommand != "init-db":
try:
await Migrate.init_with_old_models(tortoise_config, app, location)
except ConfigurationError:
raise UsageError(ctx=ctx, message="You must exec init-db first")
await Migrate.init_with_old_models(tortoise_config, app, location)
@cli.command(help="Generate migrate changes file.")
@@ -88,7 +89,11 @@ async def upgrade(ctx: Context):
app = ctx.obj["app"]
migrated = False
for version in Migrate.get_all_version_files():
if not await Aerich.exists(version=version, app=app):
try:
exists = await Aerich.exists(version=version, app=app)
except OperationalError:
exists = False
if not exists:
async with in_transaction(get_app_connection_name(config, app)) as conn:
file_path = os.path.join(Migrate.migrate_location, version)
with open(file_path, "r") as f:
@@ -117,6 +122,8 @@ async def downgrade(ctx: Context):
with open(file_path, "r") as f:
content = json.load(f)
downgrade_query_list = content.get("downgrade")
if not downgrade_query_list:
return click.secho(f"No downgrade item dound", fg=Color.yellow)
for downgrade_query in downgrade_query_list:
await conn.execute_query(downgrade_query)
await last_version.delete()
@@ -183,7 +190,7 @@ async def init(
@cli.command(help="Generate schema and generate app migrate location.")
@click.option(
"--safe",
is_flag=True,
type=bool,
default=True,
help="When set to true, creates the table only when it does not already exist.",
show_default=True,
@@ -198,6 +205,8 @@ async def init_db(ctx: Context, safe):
if not os.path.isdir(dirname):
os.mkdir(dirname)
click.secho(f"Success create app migrate location {dirname}", fg=Color.green)
else:
return click.secho(f"Inited {app} already", fg=Color.yellow)
Migrate.write_old_models(config, app, location)
@@ -205,6 +214,15 @@ async def init_db(ctx: Context, safe):
connection = get_app_connection(config, app)
await generate_schema_for_client(connection, safe)
schema = get_schema_sql(connection, safe)
version = await Migrate.generate_version()
await Aerich.create(version=version, app=app)
with open(os.path.join(dirname, version), "w") as f:
content = {
"upgrade": [schema],
}
json.dump(content, f, ensure_ascii=False, indent=2)
return click.secho(f'Success generate schema for app "{app}"', fg=Color.green)

View File

@@ -8,16 +8,17 @@ from tortoise.fields import Field, JSONField, TextField, UUIDField
class BaseDDL:
schema_generator_cls: Type[BaseSchemaGenerator] = BaseSchemaGenerator
DIALECT = "sql"
_DROP_TABLE_TEMPLATE = "DROP TABLE IF EXISTS {table_name}"
_ADD_COLUMN_TEMPLATE = "ALTER TABLE {table_name} ADD {column}"
_DROP_COLUMN_TEMPLATE = "ALTER TABLE {table_name} DROP COLUMN {column_name}"
_DROP_TABLE_TEMPLATE = 'DROP TABLE IF EXISTS "{table_name}"'
_ADD_COLUMN_TEMPLATE = 'ALTER TABLE "{table_name}" ADD {column}'
_DROP_COLUMN_TEMPLATE = 'ALTER TABLE "{table_name}" DROP COLUMN "{column_name}"'
_ADD_INDEX_TEMPLATE = (
"ALTER TABLE {table_name} ADD {unique} INDEX {index_name} ({column_names})"
'ALTER TABLE "{table_name}" ADD {unique} INDEX "{index_name}" ({column_names})'
)
_DROP_INDEX_TEMPLATE = "ALTER TABLE {table_name} DROP INDEX {index_name}"
_ADD_FK_TEMPLATE = "ALTER TABLE {table_name} ADD CONSTRAINT `{fk_name}` FOREIGN KEY (`{db_column}`) REFERENCES `{table}` (`{field}`) ON DELETE {on_delete}"
_DROP_FK_TEMPLATE = "ALTER TABLE {table_name} DROP FOREIGN KEY {fk_name}"
_M2M_TABLE_TEMPLATE = "CREATE TABLE {table_name} ({backward_key} {backward_type} NOT NULL REFERENCES {backward_table} ({backward_field}) ON DELETE CASCADE,{forward_key} {forward_type} NOT NULL REFERENCES {forward_table} ({forward_field}) ON DELETE CASCADE){extra}{comment};"
_DROP_INDEX_TEMPLATE = 'ALTER TABLE "{table_name}" DROP INDEX "{index_name}"'
_ADD_FK_TEMPLATE = 'ALTER TABLE "{table_name}" ADD CONSTRAINT "{fk_name}" FOREIGN KEY ("{db_column}") REFERENCES "{table}" ("{field}") ON DELETE {on_delete}'
_DROP_FK_TEMPLATE = 'ALTER TABLE "{table_name}" DROP FOREIGN KEY "{fk_name}"'
_M2M_TABLE_TEMPLATE = 'CREATE TABLE "{table_name}" ("{backward_key}" {backward_type} NOT NULL REFERENCES "{backward_table}" ("{backward_field}") ON DELETE CASCADE,"{forward_key}" {forward_type} NOT NULL REFERENCES "{forward_table}" ("{forward_field}") ON DELETE CASCADE){extra}{comment};'
_MODIFY_COLUMN_TEMPLATE = 'ALTER TABLE "{table_name}" MODIFY COLUMN {column}'
def __init__(self, client: "BaseDBAsyncClient"):
self.client = client
@@ -51,7 +52,7 @@ class BaseDDL:
def drop_m2m(self, field: ManyToManyFieldInstance):
return self._DROP_TABLE_TEMPLATE.format(table_name=field.through)
def add_column(self, model: "Type[Model]", field_object: Field):
def _get_default(self, model: "Type[Model]", field_object: Field):
db_table = model._meta.db_table
default = field_object.default
db_column = field_object.model_field_name
@@ -74,6 +75,11 @@ class BaseDDL:
default = ""
else:
default = ""
return default
def add_column(self, model: "Type[Model]", field_object: Field):
db_table = model._meta.db_table
return self._ADD_COLUMN_TEMPLATE.format(
table_name=db_table,
column=self.schema_generator._create_string(
@@ -89,7 +95,7 @@ class BaseDDL:
if field_object.description
else "",
is_primary_key=field_object.pk,
default=default,
default=self._get_default(model, field_object),
),
)
@@ -98,6 +104,27 @@ class BaseDDL:
table_name=model._meta.db_table, column_name=column_name
)
def modify_column(self, model: "Type[Model]", field_object: Field):
db_table = model._meta.db_table
return self._MODIFY_COLUMN_TEMPLATE.format(
table_name=db_table,
column=self.schema_generator._create_string(
db_column=field_object.model_field_name,
field_type=field_object.get_for_dialect(self.DIALECT, "SQL_TYPE"),
nullable="NOT NULL" if not field_object.null else "",
unique="",
comment=self.schema_generator._column_comment_generator(
table=db_table,
column=field_object.model_field_name,
comment=field_object.description,
)
if field_object.description
else "",
is_primary_key=field_object.pk,
default=self._get_default(model, field_object),
),
)
def add_index(self, model: "Type[Model]", field_names: List[str], unique=False):
return self._ADD_INDEX_TEMPLATE.format(
unique="UNIQUE" if unique else "",

View File

@@ -6,3 +6,14 @@ from aerich.ddl import BaseDDL
class MysqlDDL(BaseDDL):
schema_generator_cls = MySQLSchemaGenerator
DIALECT = MySQLSchemaGenerator.DIALECT
_DROP_TABLE_TEMPLATE = "DROP TABLE IF EXISTS `{table_name}`"
_ADD_COLUMN_TEMPLATE = "ALTER TABLE `{table_name}` ADD {column}"
_DROP_COLUMN_TEMPLATE = "ALTER TABLE `{table_name}` DROP COLUMN `{column_name}`"
_ADD_INDEX_TEMPLATE = (
"ALTER TABLE `{table_name}` ADD {unique} INDEX `{index_name}` ({column_names})"
)
_DROP_INDEX_TEMPLATE = "ALTER TABLE `{table_name}` DROP INDEX `{index_name}`"
_ADD_FK_TEMPLATE = "ALTER TABLE `{table_name}` ADD CONSTRAINT `{fk_name}` FOREIGN KEY (`{db_column}`) REFERENCES `{table}` (`{field}`) ON DELETE {on_delete}"
_DROP_FK_TEMPLATE = "ALTER TABLE `{table_name}` DROP FOREIGN KEY `{fk_name}`"
_M2M_TABLE_TEMPLATE = "CREATE TABLE `{table_name}` (`{backward_key}` {backward_type} NOT NULL REFERENCES `{backward_table}` (`{backward_field}`) ON DELETE CASCADE,`{forward_key}` {forward_type} NOT NULL REFERENCES `{forward_table}` (`{forward_field}`) ON DELETE CASCADE){extra}{comment};"
_MODIFY_COLUMN_TEMPLATE = "ALTER TABLE `{table_name}` MODIFY COLUMN {column}"

View File

@@ -3,6 +3,7 @@ import os
import re
from copy import deepcopy
from datetime import datetime
from importlib import import_module
from typing import Dict, List, Tuple, Type
from tortoise import (
@@ -27,6 +28,7 @@ class Migrate:
_downgrade_fk_m2m_index_operators: List[str] = []
_upgrade_m2m: List[str] = []
_downgrade_m2m: List[str] = []
_aerich = Aerich.__name__
ddl: BaseDDL
migrate_config: dict
@@ -70,22 +72,26 @@ class Migrate:
from aerich.ddl.postgres import PostgresDDL
cls.ddl = PostgresDDL(connection)
else:
raise NotImplementedError("Current only support MySQL")
@classmethod
async def _get_last_version_num(cls):
last_version = await cls.get_last_version()
if not last_version:
return 0
return None
version = last_version.version
return version.split("_")[0]
return int(version.split("_")[0])
@classmethod
async def generate_version(cls, name=None):
now = datetime.now().strftime("%Y%M%D%H%M%S").replace("/", "")
last_version_num = await cls._get_last_version_num()
if last_version_num is None:
return f"0_{now}_init.json"
return f"{last_version_num + 1}_{now}_{name}.json"
@classmethod
async def _generate_diff_sql(cls, name):
now = datetime.now().strftime("%Y%M%D%H%M%S").replace("/", "")
last_version_num = await cls._get_last_version_num()
version = f"{last_version_num + 1}_{now}_{name}.json"
version = await cls.generate_version(name)
content = {
"upgrade": cls.upgrade_operators,
"downgrade": cls.downgrade_operators,
@@ -105,8 +111,8 @@ class Migrate:
diff_models = apps.get(cls.diff_app)
app_models = apps.get(cls.app)
cls._diff_models(diff_models, app_models)
cls._diff_models(app_models, diff_models, False)
cls.diff_models(diff_models, app_models)
cls.diff_models(app_models, diff_models, False)
cls._merge_operators()
@@ -167,7 +173,10 @@ class Migrate:
temp_config = deepcopy(config)
path = os.path.join(location, app, cls.old_models)
path = path.replace("/", ".").lstrip(".")
temp_config["apps"][cls.diff_app] = {"models": [path]}
temp_config["apps"][cls.diff_app] = {
"models": [path],
"default_connection": config.get("apps").get(app).get("default_connection", "default"),
}
return temp_config
@classmethod
@@ -179,15 +188,17 @@ class Migrate:
:param location:
:return:
"""
cls.app = app
old_model_files = []
models = config.get("apps").get(app).get("models")
for model in models:
old_model_files.append(model.replace(".", "/") + ".py")
old_model_files.append(import_module(model).__file__)
cls.cp_models(app, old_model_files, os.path.join(location, app, cls.get_old_model_file()))
@classmethod
def _diff_models(
def diff_models(
cls, old_models: Dict[str, Type[Model]], new_models: Dict[str, Type[Model]], upgrade=True
):
"""
@@ -197,6 +208,9 @@ class Migrate:
:param upgrade:
:return:
"""
old_models.pop(cls._aerich, None)
new_models.pop(cls._aerich, None)
for new_model_str, new_model in new_models.items():
if new_model_str not in old_models.keys():
cls._add_operator(cls.add_model(new_model), upgrade)
@@ -207,6 +221,10 @@ class Migrate:
if old_model not in new_models.keys():
cls._add_operator(cls.remove_model(old_models.get(old_model)), upgrade)
@classmethod
def _is_fk_m2m(cls, field: Field):
return isinstance(field, (ForeignKeyFieldInstance, ManyToManyFieldInstance))
@classmethod
def add_model(cls, model: Type[Model]):
return cls.ddl.create_table(model)
@@ -247,6 +265,14 @@ class Migrate:
)
else:
old_field = old_fields_map.get(new_key)
new_field_dict = new_field.describe(serializable=True)
new_field_dict.pop("unique")
new_field_dict.pop("indexed")
old_field_dict = old_field.describe(serializable=True)
old_field_dict.pop("unique")
old_field_dict.pop("indexed")
if not cls._is_fk_m2m(new_field) and new_field_dict != old_field_dict:
cls._add_operator(cls._modify_field(new_model, new_field), upgrade=upgrade)
if (old_field.index and not new_field.index) or (
old_field.unique and not new_field.unique
):
@@ -255,7 +281,7 @@ class Migrate:
old_model, (old_field.model_field_name,), old_field.unique
),
upgrade,
isinstance(old_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)),
cls._is_fk_m2m(old_field),
)
elif (new_field.index and not old_field.index) or (
new_field.unique and not old_field.unique
@@ -263,16 +289,14 @@ class Migrate:
cls._add_operator(
cls._add_index(new_model, (new_field.model_field_name,), new_field.unique),
upgrade,
isinstance(new_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)),
cls._is_fk_m2m(new_field),
)
for old_key in old_keys:
field = old_fields_map.get(old_key)
if old_key not in new_keys and not cls._exclude_field(field, upgrade):
cls._add_operator(
cls._remove_field(old_model, field),
upgrade,
isinstance(field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)),
cls._remove_field(old_model, field), upgrade, cls._is_fk_m2m(field),
)
for new_index in new_indexes:
@@ -341,6 +365,10 @@ class Migrate:
return cls.ddl.create_m2m_table(model, field)
return cls.ddl.add_column(model, field)
@classmethod
def _modify_field(cls, model: Type[Model], field: Field):
return cls.ddl.modify_column(model, field)
@classmethod
def _remove_field(cls, model: Type[Model], field: Field):
if isinstance(field, ForeignKeyFieldInstance):

View File

@@ -1,11 +1,61 @@
import asyncio
import os
import pytest
from tortoise.contrib.test import finalizer, initializer
from tortoise import Tortoise, expand_db_url, generate_schema_for_client
from tortoise.backends.asyncpg.schema_generator import AsyncpgSchemaGenerator
from tortoise.backends.mysql.schema_generator import MySQLSchemaGenerator
from tortoise.backends.sqlite.schema_generator import SqliteSchemaGenerator
from aerich.ddl.mysql import MysqlDDL
from aerich.ddl.postgres import PostgresDDL
from aerich.ddl.sqlite import SqliteDDL
from aerich.migrate import Migrate
db_url = os.getenv("TEST_DB", "sqlite://:memory:")
tortoise_orm = {
"connections": {"default": expand_db_url(db_url, True)},
"apps": {
"models": {"models": ["tests.models", "aerich.models"], "default_connection": "default",},
},
}
@pytest.fixture(scope="module", autouse=True)
def initialize_tests(request):
db_url = os.getenv("TEST_DB", "sqlite://:memory:")
initializer(["tests.models"], db_url=db_url)
request.addfinalizer(finalizer)
@pytest.fixture(scope="function", autouse=True)
def reset_migrate():
Migrate.upgrade_operators = []
Migrate.downgrade_operators = []
Migrate._upgrade_fk_m2m_index_operators = []
Migrate._downgrade_fk_m2m_index_operators = []
Migrate._upgrade_m2m = []
Migrate._downgrade_m2m = []
@pytest.fixture(scope="session")
def loop():
loop = asyncio.get_event_loop()
return loop
@pytest.fixture(scope="session", autouse=True)
def initialize_tests(loop, request):
tortoise_orm["connections"]["diff_models"] = "sqlite://:memory:"
tortoise_orm["apps"]["diff_models"] = {
"models": ["tests.diff_models"],
"default_connection": "diff_models",
}
loop.run_until_complete(Tortoise.init(config=tortoise_orm, _create_db=True))
loop.run_until_complete(
generate_schema_for_client(Tortoise.get_connection("default"), safe=True)
)
client = Tortoise.get_connection("default")
if client.schema_generator is MySQLSchemaGenerator:
Migrate.ddl = MysqlDDL(client)
elif client.schema_generator is SqliteSchemaGenerator:
Migrate.ddl = SqliteDDL(client)
elif client.schema_generator is AsyncpgSchemaGenerator:
Migrate.ddl = PostgresDDL(client)
request.addfinalizer(lambda: loop.run_until_complete(Tortoise._drop_databases()))

100
poetry.lock generated
View File

@@ -1,8 +1,8 @@
[[package]]
category = "dev"
category = "main"
description = "MySQL driver for asyncio."
name = "aiomysql"
optional = false
optional = true
python-versions = "*"
version = "0.0.20"
@@ -26,7 +26,7 @@ description = "High level compatibility layer for multiple asynchronous event lo
name = "anyio"
optional = false
python-versions = ">=3.5.3"
version = "1.3.0"
version = "1.3.1"
[package.dependencies]
async-generator = "*"
@@ -78,10 +78,10 @@ dev = ["coverage", "pytest-runner", "pytest-trio", "pytest (>=3)", "sphinx", "to
docs = ["sphinx"]
[[package]]
category = "dev"
category = "main"
description = "An asyncio PostgreSQL driver"
name = "asyncpg"
optional = false
optional = true
python-versions = ">=3.5.0"
version = "0.20.1"
@@ -90,14 +90,6 @@ dev = ["Cython (0.29.14)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphin
docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"]
test = ["pycodestyle (>=2.5.0,<2.6.0)", "flake8 (>=3.7.9,<3.8.0)", "uvloop (>=0.14.0,<0.15.0)"]
[[package]]
category = "dev"
description = "Enhance the standard unittest package with features for testing asyncio libraries"
name = "asynctest"
optional = false
python-versions = ">=3.5"
version = "0.13.0"
[[package]]
category = "dev"
description = "Atomic file writes."
@@ -142,10 +134,10 @@ typed-ast = ">=1.4.0"
d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
[[package]]
category = "dev"
category = "main"
description = "Foreign Function Interface for Python calling C code."
name = "cffi"
optional = false
optional = true
python-versions = "*"
version = "1.14.0"
@@ -179,10 +171,10 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "0.4.3"
[[package]]
category = "dev"
category = "main"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
name = "cryptography"
optional = false
optional = true
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "2.9.2"
@@ -217,7 +209,7 @@ description = "the modular source code checker: pep8 pyflakes and co"
name = "flake8"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
version = "3.8.1"
version = "3.8.2"
[package.dependencies]
mccabe = ">=0.6.0,<0.7.0"
@@ -335,10 +327,10 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.6.0"
[[package]]
category = "dev"
category = "main"
description = "C parser in Python"
name = "pycparser"
optional = false
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.20"
@@ -364,10 +356,10 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "2.2.0"
[[package]]
category = "dev"
category = "main"
description = "Pure Python MySQL Driver"
name = "pymysql"
optional = false
optional = true
python-versions = "*"
version = "0.9.2"
@@ -388,7 +380,7 @@ description = "A SQL query builder API for Python"
name = "pypika"
optional = false
python-versions = "*"
version = "0.37.6"
version = "0.37.7"
[[package]]
category = "dev"
@@ -412,6 +404,20 @@ wcwidth = "*"
checkqa-mypy = ["mypy (v0.761)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
category = "dev"
description = "Pytest support for asyncio."
name = "pytest-asyncio"
optional = false
python-versions = ">= 3.5"
version = "0.12.0"
[package.dependencies]
pytest = ">=5.4.0"
[package.extras]
testing = ["async_generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"]
[[package]]
category = "dev"
description = "run tests in isolated forked subprocesses"
@@ -449,12 +455,12 @@ python-versions = "*"
version = "2020.5.14"
[[package]]
category = "dev"
category = "main"
description = "Python 2 and 3 compatibility utilities"
name = "six"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
version = "1.14.0"
version = "1.15.0"
[[package]]
category = "main"
@@ -489,7 +495,7 @@ description = "Easy async ORM for python, built with relations in mind"
name = "tortoise-orm"
optional = false
python-versions = "*"
version = "0.16.11"
version = "0.16.12"
[package.dependencies]
aiosqlite = ">=0.11.0"
@@ -498,10 +504,6 @@ iso8601 = ">=0.1.12"
pypika = ">=0.36.5"
typing-extensions = ">=3.7"
[package.source]
reference = "1f67b7a0ca1384365d6ff89d9e245e733166d1a6"
type = "git"
url = "https://github.com/long2ice/tortoise-orm.git"
[[package]]
category = "dev"
description = "a fork of Python 2 and 3 ast modules with type comment support"
@@ -520,14 +522,17 @@ version = "3.7.4.2"
[[package]]
category = "dev"
description = "Measures number of Terminal column cells of wide-character codes"
description = "Measures the displayed width of unicode strings in a terminal"
name = "wcwidth"
optional = false
python-versions = "*"
version = "0.1.9"
version = "0.2.2"
[extras]
dbdrivers = ["aiomysql", "asyncpg"]
[metadata]
content-hash = "58a032bbb47859e87d2bce036af24149060cc531ff9220a14f6cd48db6252f39"
content-hash = "6b1f30cb32cf5915f1ee1f6c6b0e52130bc8f7af92f1a9703dc9632ebce2a977"
python-versions = "^3.8"
[metadata.files]
@@ -540,8 +545,8 @@ aiosqlite = [
{file = "aiosqlite-0.13.0.tar.gz", hash = "sha256:6e92961ae9e606b43b05e29b129e346b29e400fcbd63e3c0c564d89230257645"},
]
anyio = [
{file = "anyio-1.3.0-py3-none-any.whl", hash = "sha256:db2c3d21576870b95d4fd0b8f4a0f9c64057f777c578f3a8127179a17c8c067e"},
{file = "anyio-1.3.0.tar.gz", hash = "sha256:7deae0315dd10aa41c21528b83352e4b52f44e6153a21081a3d1cd8c03728e46"},
{file = "anyio-1.3.1-py3-none-any.whl", hash = "sha256:f21b4fafeec1b7db81e09a907e44e374a1e39718d782a488fdfcdcf949c8950c"},
{file = "anyio-1.3.1.tar.gz", hash = "sha256:a46bb2b7743455434afd9adea848a3c4e0b7321aee3e9d08844b11d348d3b5a0"},
]
apipkg = [
{file = "apipkg-1.5-py2.py3-none-any.whl", hash = "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"},
@@ -581,10 +586,6 @@ asyncpg = [
{file = "asyncpg-0.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:2af6a5a705accd36e13292ea43d08c20b15e52d684beb522cb3a7d3c9c8f3f48"},
{file = "asyncpg-0.20.1.tar.gz", hash = "sha256:394bf19bdddbba07a38cd6fb526ebf66e120444d6b3097332b78efd5b26495b0"},
]
asynctest = [
{file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"},
{file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
@@ -664,8 +665,8 @@ execnet = [
{file = "execnet-1.7.1.tar.gz", hash = "sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50"},
]
flake8 = [
{file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"},
{file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"},
{file = "flake8-3.8.2-py2.py3-none-any.whl", hash = "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5"},
{file = "flake8-3.8.2.tar.gz", hash = "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634"},
]
iso8601 = [
{file = "iso8601-0.1.12-py2.py3-none-any.whl", hash = "sha256:210e0134677cc0d02f6028087fee1df1e1d76d372ee1db0bf30bf66c5c1c89a3"},
@@ -760,12 +761,15 @@ pyparsing = [
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pypika = [
{file = "PyPika-0.37.6.tar.gz", hash = "sha256:64510fa36667e8bb654bdc1be5a3a77bac1dbc2f03d4848efac08e39d9cac6f5"},
{file = "PyPika-0.37.7.tar.gz", hash = "sha256:20bebc05983cd401d428e3beb62d037e5f0271daab2bb5aba82f4e092d4a3694"},
]
pytest = [
{file = "pytest-5.4.2-py3-none-any.whl", hash = "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3"},
{file = "pytest-5.4.2.tar.gz", hash = "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"},
]
pytest-asyncio = [
{file = "pytest-asyncio-0.12.0.tar.gz", hash = "sha256:475bd2f3dc0bc11d2463656b3cbaafdbec5a47b47508ea0b329ee693040eebd2"},
]
pytest-forked = [
{file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"},
{file = "pytest_forked-1.1.3-py2.py3-none-any.whl", hash = "sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87"},
@@ -798,8 +802,8 @@ regex = [
{file = "regex-2020.5.14.tar.gz", hash = "sha256:ce450ffbfec93821ab1fea94779a8440e10cf63819be6e176eb1973a6017aff5"},
]
six = [
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
sniffio = [
{file = "sniffio-1.1.0-py3-none-any.whl", hash = "sha256:20ed6d5b46f8ae136d00b9dcb807615d83ed82ceea6b2058cecb696765246da5"},
@@ -813,7 +817,9 @@ toml = [
{file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
]
tortoise-orm = []
tortoise-orm = [
{file = "tortoise-orm-0.16.12.tar.gz", hash = "sha256:170e4bbfe1c98223ad1fba33d7fded7923e4bb49c9d74c78bd173a0ebc861658"},
]
typed-ast = [
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
@@ -843,6 +849,6 @@ typing-extensions = [
{file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"},
]
wcwidth = [
{file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"},
{file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"},
{file = "wcwidth-0.2.2-py2.py3-none-any.whl", hash = "sha256:b651b6b081476420e4e9ae61239ac4c1b49d0c5ace42b2e81dc2ff49ed50c566"},
{file = "wcwidth-0.2.2.tar.gz", hash = "sha256:3de2e41158cb650b91f9654cbf9a3e053cee0719c9df4ddc11e4b568669e9829"},
]

View File

@@ -1,32 +1,43 @@
[tool.poetry]
name = "aerich"
version = "0.1.1"
version = "0.2.0"
description = "A database migrations tool for Tortoise ORM."
authors = ["long2ice <long2ice@gmail.com>"]
license = "MIT"
readme = "README.rst"
homepage = "https://github.com/long2ice/aerich"
repository = "git@github.com:long2ice/aerich.git"
documentation = "https://github.com/long2ice/aerich"
keywords = ["migrate", "Tortoise-ORM", "mysql"]
packages = [
{ include = "aerich" }
]
include = ["CHANGELOG.rst", "LICENSE", "README.rst"]
[tool.poetry.dependencies]
python = "^3.8"
tortoise-orm = {git = "https://github.com/long2ice/tortoise-orm.git", branch = "develop"}
tortoise-orm = "*"
asyncclick = "*"
pydantic = "*"
aiomysql = {version = "*", optional = true}
asyncpg = {version = "*", optional = true}
[tool.poetry.dev-dependencies]
taskipy = "*"
asynctest = "*"
flake8 = "*"
isort = "*"
black = "^19.10b0"
pytest = "*"
aiomysql = "*"
asyncpg = "*"
pytest-xdist = "*"
mypy = "*"
pytest-asyncio = "*"
[tool.taskipy.tasks]
export = "poetry export -f requirements.txt --without-hashes > requirements.txt"
export-dev = "poetry export -f requirements.txt --dev --without-hashes > requirements-dev.txt"
[tool.poetry.extras]
dbdrivers = ["aiomysql", "asyncpg"]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.poetry.scripts]
aerich = "aerich.cli:main"

View File

@@ -1,48 +0,0 @@
aiomysql==0.0.20
aiosqlite==0.13.0
anyio==1.3.0
apipkg==1.5
appdirs==1.4.4
async-generator==1.10
asyncclick==7.0.9
asyncpg==0.20.1
asynctest==0.13.0
atomicwrites==1.4.0; sys_platform == "win32"
attrs==19.3.0
black==19.10b0
cffi==1.14.0
ciso8601==2.1.3; sys_platform != "win32" and implementation_name == "cpython"
click==7.1.2
colorama==0.4.3; sys_platform == "win32"
cryptography==2.9.2
execnet==1.7.1
flake8==3.8.1
iso8601==0.1.12; sys_platform == "win32" or implementation_name != "cpython"
isort==4.3.21
mccabe==0.6.1
more-itertools==8.3.0
mypy==0.770
mypy-extensions==0.4.3
packaging==20.4
pathspec==0.8.0
pluggy==0.13.1
py==1.8.1
pycodestyle==2.6.0
pycparser==2.20
pydantic==1.5.1
pyflakes==2.2.0
pymysql==0.9.2
pyparsing==2.4.7
pypika==0.37.6
pytest==5.4.2
pytest-forked==1.1.3
pytest-xdist==1.32.0
regex==2020.5.14
six==1.14.0
sniffio==1.1.0
taskipy==1.2.1
toml==0.10.1
-e git+https://github.com/long2ice/tortoise-orm.git@1f67b7a0ca1384365d6ff89d9e245e733166d1a6#egg=tortoise-orm
typed-ast==1.4.1
typing-extensions==3.7.4.2
wcwidth==0.1.9

View File

@@ -1,10 +0,0 @@
aiosqlite==0.13.0
anyio==1.3.0
async-generator==1.10
asyncclick==7.0.9
ciso8601==2.1.3; sys_platform != "win32" and implementation_name == "cpython"
iso8601==0.1.12; sys_platform == "win32" or implementation_name != "cpython"
pydantic==1.5.1
pypika==0.37.6
sniffio==1.1.0
typing-extensions==3.7.4.2

View File

@@ -1,47 +0,0 @@
[flake8]
max-line-length = 100
exclude =
ignore = E501,W503,DAR101,DAR201,DAR402
[darglint]
docstring_style=sphinx
[isort]
not_skip=__init__.py
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=100
[tool:pytest]
addopts = -n auto --tb=native -q
[mypy]
pretty = True
ignore_missing_imports = True
check_untyped_defs = True
disallow_subclassing_any = True
disallow_untyped_calls = True
disallow_untyped_defs = False
disallow_incomplete_defs = False
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_no_return = True
warn_return_any = False
warn_unused_configs = True
warn_unreachable = True
allow_redefinition = True
strict_equality = True
show_error_context = True
[mypy-tests.*]
check_untyped_defs = False
disallow_untyped_defs = False
disallow_incomplete_defs = False
warn_unreachable = False
[mypy-conftest]
disallow_untyped_defs = False

View File

@@ -1,44 +0,0 @@
import os
import re
from setuptools import find_packages, setup
def version():
ver_str_line = open('aerich/__init__.py', 'rt').read()
mob = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", ver_str_line, re.M)
if not mob:
raise RuntimeError("Unable to find version string")
return mob.group(1)
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f:
long_description = f.read()
def requirements():
return open('requirements.txt', 'rt').read().splitlines()
setup(
name='aerich',
version=version(),
description='A database migrations tool for Tortoise-ORM.',
author='long2ice',
long_description_content_type='text/x-rst',
long_description=long_description,
author_email='long2ice@gmail.com',
url='https://github.com/long2ice/aerich',
license='MIT License',
packages=find_packages(include=['aerich*']),
include_package_data=True,
zip_safe=True,
entry_points={
'console_scripts': ['aerich = aerich.cli:main'],
},
platforms='any',
keywords=(
'migrate Tortoise-ORM mysql'
),
dependency_links=['https://github.com/long2ice/tortoise-orm.git@develop#egg=tortoise-orm'],
install_requires=requirements(),
)

View File

@@ -1,6 +0,0 @@
TORTOISE_ORM = {
"connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"},
"apps": {
"models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"}
},
}

56
tests/diff_models.py Normal file
View File

@@ -0,0 +1,56 @@
import datetime
from enum import IntEnum
from tortoise import Model, fields
class ProductType(IntEnum):
article = 1
page = 2
class PermissionAction(IntEnum):
create = 1
delete = 2
update = 3
read = 4
class Status(IntEnum):
on = 1
off = 0
class User(Model):
username = fields.CharField(max_length=20,)
password = fields.CharField(max_length=200)
last_login = fields.DatetimeField(description="Last Login", default=datetime.datetime.now)
is_active = fields.BooleanField(default=True, description="Is Active")
is_superuser = fields.BooleanField(default=False, description="Is SuperUser")
avatar = fields.CharField(max_length=200, default="")
intro = fields.TextField(default="")
class Category(Model):
slug = fields.CharField(max_length=200)
user = fields.ForeignKeyField("diff_models.User", description="User")
created_at = fields.DatetimeField(auto_now_add=True)
class Product(Model):
categories = fields.ManyToManyField("diff_models.Category")
name = fields.CharField(max_length=50)
view_num = fields.IntField(description="View Num")
sort = fields.IntField()
is_reviewed = fields.BooleanField(description="Is Reviewed")
type = fields.IntEnumField(ProductType, description="Product Type")
image = fields.CharField(max_length=200)
body = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
class Config(Model):
label = fields.CharField(max_length=200)
key = fields.CharField(max_length=20)
value = fields.JSONField()
status: Status = fields.IntEnumField(Status, default=Status.on)

View File

@@ -1,114 +1,123 @@
from tortoise import Tortoise
from tortoise.backends.asyncpg.schema_generator import AsyncpgSchemaGenerator
from tortoise.backends.mysql.schema_generator import MySQLSchemaGenerator
from tortoise.backends.sqlite.schema_generator import SqliteSchemaGenerator
from tortoise.contrib import test
from aerich.ddl.mysql import MysqlDDL
from aerich.ddl.postgres import PostgresDDL
from aerich.ddl.sqlite import SqliteDDL
from aerich.migrate import Migrate
from tests.models import Category
class TestDDL(test.TruncationTestCase):
maxDiff = None
def setUp(self) -> None:
client = Tortoise.get_connection("models")
if client.schema_generator is MySQLSchemaGenerator:
self.ddl = MysqlDDL(client)
elif client.schema_generator is SqliteSchemaGenerator:
self.ddl = SqliteDDL(client)
elif client.schema_generator is AsyncpgSchemaGenerator:
self.ddl = PostgresDDL(client)
def test_create_table(self):
ret = self.ddl.create_table(Category)
if isinstance(self.ddl, MysqlDDL):
self.assertEqual(
ret,
"""CREATE TABLE IF NOT EXISTS `category` (
def test_create_table():
ret = Migrate.ddl.create_table(Category)
if isinstance(Migrate.ddl, MysqlDDL):
assert (
ret
== """CREATE TABLE IF NOT EXISTS `category` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`slug` VARCHAR(200) NOT NULL,
`name` VARCHAR(200) NOT NULL,
`created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`user_id` INT NOT NULL COMMENT 'User',
CONSTRAINT `fk_category_user_e2e3874c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
) CHARACTER SET utf8mb4;""",
)
elif isinstance(self.ddl, SqliteDDL):
self.assertEqual(
ret,
"""CREATE TABLE IF NOT EXISTS "category" (
) CHARACTER SET utf8mb4;"""
)
elif isinstance(Migrate.ddl, SqliteDDL):
assert (
ret
== """CREATE TABLE IF NOT EXISTS "category" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"slug" VARCHAR(200) NOT NULL,
"name" VARCHAR(200) NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE /* User */
);""",
)
elif isinstance(self.ddl, PostgresDDL):
self.assertEqual(
ret,
"""CREATE TABLE IF NOT EXISTS "category" (
);"""
)
elif isinstance(Migrate.ddl, PostgresDDL):
assert (
ret
== """CREATE TABLE IF NOT EXISTS "category" (
"id" SERIAL NOT NULL PRIMARY KEY,
"slug" VARCHAR(200) NOT NULL,
"name" VARCHAR(200) NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"user_id" INT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
);
COMMENT ON COLUMN "category"."user_id" IS 'User';""",
)
def test_drop_table(self):
ret = self.ddl.drop_table(Category)
self.assertEqual(ret, "DROP TABLE IF EXISTS category")
def test_add_column(self):
ret = self.ddl.add_column(Category, Category._meta.fields_map.get("name"))
if isinstance(self.ddl, MysqlDDL):
self.assertEqual(ret, "ALTER TABLE category ADD `name` VARCHAR(200) NOT NULL")
elif isinstance(self.ddl, PostgresDDL):
self.assertEqual(ret, 'ALTER TABLE category ADD "name" VARCHAR(200) NOT NULL')
elif isinstance(self.ddl, SqliteDDL):
self.assertEqual(ret, 'ALTER TABLE category ADD "name" VARCHAR(200) NOT NULL')
def test_drop_column(self):
ret = self.ddl.drop_column(Category, "name")
self.assertEqual(ret, "ALTER TABLE category DROP COLUMN name")
self.assertEqual(ret, "ALTER TABLE category DROP COLUMN name")
def test_add_index(self):
index = self.ddl.add_index(Category, ["name"])
index_u = self.ddl.add_index(Category, ["name"], True)
if isinstance(self.ddl, MysqlDDL):
self.assertEqual(
index, "ALTER TABLE category ADD INDEX idx_category_name_8b0cb9 (`name`)"
)
self.assertEqual(
index_u, "ALTER TABLE category ADD UNIQUE INDEX uid_category_name_8b0cb9 (`name`)"
)
elif isinstance(self.ddl, SqliteDDL):
self.assertEqual(
index_u, 'ALTER TABLE category ADD UNIQUE INDEX uid_category_name_8b0cb9 ("name")'
)
self.assertEqual(
index_u, 'ALTER TABLE category ADD UNIQUE INDEX uid_category_name_8b0cb9 ("name")'
)
def test_drop_index(self):
ret = self.ddl.drop_index(Category, ["name"])
self.assertEqual(ret, "ALTER TABLE category DROP INDEX idx_category_name_8b0cb9")
ret = self.ddl.drop_index(Category, ["name"], True)
self.assertEqual(ret, "ALTER TABLE category DROP INDEX uid_category_name_8b0cb9")
def test_add_fk(self):
ret = self.ddl.add_fk(Category, Category._meta.fields_map.get("user"))
self.assertEqual(
ret,
"ALTER TABLE category ADD CONSTRAINT `fk_category_user_e2e3874c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE",
COMMENT ON COLUMN "category"."user_id" IS 'User';"""
)
def test_drop_fk(self):
ret = self.ddl.drop_fk(Category, Category._meta.fields_map.get("user"))
self.assertEqual(ret, "ALTER TABLE category DROP FOREIGN KEY fk_category_user_e2e3874c")
def test_drop_table():
ret = Migrate.ddl.drop_table(Category)
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "DROP TABLE IF EXISTS `category`"
else:
assert ret == 'DROP TABLE IF EXISTS "category"'
def test_add_column():
ret = Migrate.ddl.add_column(Category, Category._meta.fields_map.get("name"))
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` ADD `name` VARCHAR(200) NOT NULL"
else:
assert ret == 'ALTER TABLE "category" ADD "name" VARCHAR(200) NOT NULL'
def test_modify_column():
ret = Migrate.ddl.modify_column(Category, Category._meta.fields_map.get("name"))
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL"
else:
assert ret == 'ALTER TABLE "category" MODIFY COLUMN "name" VARCHAR(200) NOT NULL'
def test_drop_column():
ret = Migrate.ddl.drop_column(Category, "name")
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` DROP COLUMN `name`"
else:
assert ret == 'ALTER TABLE "category" DROP COLUMN "name"'
def test_add_index():
index = Migrate.ddl.add_index(Category, ["name"])
index_u = Migrate.ddl.add_index(Category, ["name"], True)
if isinstance(Migrate.ddl, MysqlDDL):
assert index == "ALTER TABLE `category` ADD INDEX `idx_category_name_8b0cb9` (`name`)"
assert (
index_u == "ALTER TABLE `category` ADD UNIQUE INDEX `uid_category_name_8b0cb9` (`name`)"
)
else:
assert index == 'ALTER TABLE "category" ADD INDEX "idx_category_name_8b0cb9" ("name")'
assert (
index_u == 'ALTER TABLE "category" ADD UNIQUE INDEX "uid_category_name_8b0cb9" ("name")'
)
def test_drop_index():
ret = Migrate.ddl.drop_index(Category, ["name"])
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` DROP INDEX `idx_category_name_8b0cb9`"
else:
assert ret == 'ALTER TABLE "category" DROP INDEX "idx_category_name_8b0cb9"'
def test_add_fk():
ret = Migrate.ddl.add_fk(Category, Category._meta.fields_map.get("user"))
if isinstance(Migrate.ddl, MysqlDDL):
assert (
ret
== "ALTER TABLE `category` ADD CONSTRAINT `fk_category_user_e2e3874c` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE"
)
else:
assert (
ret
== 'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_e2e3874c" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE'
)
def test_drop_fk():
ret = Migrate.ddl.drop_fk(Category, Category._meta.fields_map.get("user"))
if isinstance(Migrate.ddl, MysqlDDL):
assert ret == "ALTER TABLE `category` DROP FOREIGN KEY `fk_category_user_e2e3874c`"
else:
assert ret == 'ALTER TABLE "category" DROP FOREIGN KEY "fk_category_user_e2e3874c"'

30
tests/test_migrate.py Normal file
View File

@@ -0,0 +1,30 @@
from tortoise import Tortoise
from aerich.ddl.mysql import MysqlDDL
from aerich.migrate import Migrate
def test_migrate():
apps = Tortoise.apps
models = apps.get("models")
diff_models = apps.get("diff_models")
Migrate.diff_models(diff_models, models)
Migrate.diff_models(models, diff_models, False)
if isinstance(Migrate.ddl, MysqlDDL):
assert Migrate.upgrade_operators == [
"ALTER TABLE `category` ADD `name` VARCHAR(200) NOT NULL",
"ALTER TABLE `user` ADD UNIQUE INDEX `uid_user_usernam_9987ab` (`username`)",
]
assert Migrate.downgrade_operators == [
"ALTER TABLE `category` DROP COLUMN `name`",
"ALTER TABLE `user` DROP INDEX `uid_user_usernam_9987ab`",
]
else:
assert Migrate.upgrade_operators == [
'ALTER TABLE "category" ADD "name" VARCHAR(200) NOT NULL',
'ALTER TABLE "user" ADD UNIQUE INDEX "uid_user_usernam_9987ab" ("username")',
]
assert Migrate.downgrade_operators == [
'ALTER TABLE "category" DROP COLUMN "name"',
'ALTER TABLE "user" DROP INDEX "uid_user_usernam_9987ab"',
]