From aa921355b9f945d00bc25f92d4387d8ba8476d81 Mon Sep 17 00:00:00 2001 From: long2ice Date: Thu, 21 May 2020 21:22:06 +0800 Subject: [PATCH] Store versions in db --- CHANGELOG.rst | 1 + README.rst | 15 ++++++++- aerich/cli.py | 82 +++++++++++++++++++++++++---------------------- aerich/migrate.py | 54 +++++++++++++------------------ aerich/models.py | 1 + aerich/utils.py | 6 ++-- tests/__init__.py | 2 +- 7 files changed, 86 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e9ae04..2047551 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ ChangeLog ----- - Add sqlite and postgres support. - Fix dependency import. +- Store versions in db. 0.1.4 ----- diff --git a/README.rst b/README.rst index fe33932..cb5f536 100644 --- a/README.rst +++ b/README.rst @@ -54,6 +54,19 @@ Quick Start Usage ===== +You need add ``aerich.models`` to your ``Tortoise-ORM`` config first, example: + +.. code-block:: python + + TORTOISE_ORM = { + "connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"}, + "apps": { + "models": { + "models": ["tests.models", "aerich.models"], + "default_connection": "default", + }, + }, + } Initialization -------------- @@ -100,7 +113,7 @@ Update models and make migrate Success migrate 1_202029051520102929_drop_column.json -Format of migrate filename is ``{version}_{datetime}_{name|update}.json`` +Format of migrate filename is ``{version_num}_{datetime}_{name|update}.json`` Upgrade to latest version ------------------------- diff --git a/aerich/cli.py b/aerich/cli.py index 9be1059..ec6f367 100644 --- a/aerich/cli.py +++ b/aerich/cli.py @@ -13,6 +13,7 @@ from aerich.migrate import Migrate from aerich.utils import get_app_connection, get_app_connection_name, get_tortoise_config from . import __version__ +from .models import Aerich class Color(str, Enum): @@ -83,24 +84,23 @@ async def migrate(ctx: Context, name): @cli.command(help="Upgrade to latest version.") @click.pass_context async def upgrade(ctx: Context): - app = ctx.obj["app"] config = ctx.obj["config"] - available_versions = Migrate.get_all_version_files(is_all=False) - if not available_versions: - return click.secho("No migrate items", fg=Color.yellow) - async with in_transaction(get_app_connection_name(config, app)) as conn: - for file in available_versions: - file_path = os.path.join(Migrate.migrate_location, file) - with open(file_path, "r") as f: - content = json.load(f) - upgrade_query_list = content.get("upgrade") - for upgrade_query in upgrade_query_list: - await conn.execute_query(upgrade_query) - - with open(file_path, "w") as f: - content["migrate"] = True - json.dump(content, f, indent=2, ensure_ascii=False) - click.secho(f"Success upgrade {file}", fg=Color.green) + app = ctx.obj["app"] + migrated = False + for version in Migrate.get_all_version_files(): + if not await Aerich.exists(version=version, app=app): + 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: + content = json.load(f) + upgrade_query_list = content.get("upgrade") + for upgrade_query in upgrade_query_list: + await conn.execute_query(upgrade_query) + await Aerich.create(version=version, app=app) + click.secho(f"Success upgrade {version}", fg=Color.green) + migrated = True + if not migrated: + click.secho("No migrate items", fg=Color.yellow) @cli.command(help="Downgrade to previous version.") @@ -108,39 +108,43 @@ async def upgrade(ctx: Context): async def downgrade(ctx: Context): app = ctx.obj["app"] config = ctx.obj["config"] - available_versions = Migrate.get_all_version_files() - if not available_versions: - return click.secho("No migrate items", fg=Color.yellow) - + last_version = await Migrate.get_last_version() + if not last_version: + return click.secho("No last version found", fg=Color.yellow) + file = last_version.version async with in_transaction(get_app_connection_name(config, app)) as conn: - for file in reversed(available_versions): - file_path = os.path.join(Migrate.migrate_location, file) - with open(file_path, "r") as f: - content = json.load(f) - if content.get("migrate"): - downgrade_query_list = content.get("downgrade") - for downgrade_query in downgrade_query_list: - await conn.execute_query(downgrade_query) - else: - continue - with open(file_path, "w") as f: - content["migrate"] = False - json.dump(content, f, indent=2, ensure_ascii=False) - return click.secho(f"Success downgrade {file}", fg=Color.green) + file_path = os.path.join(Migrate.migrate_location, file) + with open(file_path, "r") as f: + content = json.load(f) + downgrade_query_list = content.get("downgrade") + for downgrade_query in downgrade_query_list: + await conn.execute_query(downgrade_query) + await last_version.delete() + return click.secho(f"Success downgrade {file}", fg=Color.green) @cli.command(help="Show current available heads in migrate location.") @click.pass_context -def heads(ctx: Context): - for version in Migrate.get_all_version_files(is_all=False): - click.secho(version, fg=Color.green) +async def heads(ctx: Context): + app = ctx.obj["app"] + versions = Migrate.get_all_version_files() + is_heads = False + for version in versions: + if not await Aerich.exists(version=version, app=app): + click.secho(version, fg=Color.green) + is_heads = True + if not is_heads: + click.secho("No available heads,try migrate", fg=Color.green) @cli.command(help="List all migrate items.") @click.pass_context def history(ctx): - for version in Migrate.get_all_version_files(): + versions = Migrate.get_all_version_files() + for version in versions: click.secho(version, fg=Color.green) + if not versions: + click.secho("No history,try migrate", fg=Color.green) @cli.command(help="Init config file and generate root migrate location.") diff --git a/aerich/migrate.py b/aerich/migrate.py index d2440a2..ea15f66 100644 --- a/aerich/migrate.py +++ b/aerich/migrate.py @@ -3,7 +3,7 @@ import os import re from copy import deepcopy from datetime import datetime -from typing import Dict, List, Type +from typing import Dict, List, Tuple, Type from tortoise import ( BackwardFKRelation, @@ -16,7 +16,6 @@ from tortoise import ( from tortoise.fields import Field from aerich.ddl import BaseDDL -from aerich.exceptions import ConfigurationError from aerich.models import Aerich from aerich.utils import get_app_connection @@ -41,26 +40,12 @@ class Migrate: return cls.old_models + ".py" @classmethod - def _get_all_migrate_files(cls): + def get_all_version_files(cls) -> List[str]: return sorted(filter(lambda x: x.endswith("json"), os.listdir(cls.migrate_location))) @classmethod - def _get_latest_version(cls) -> int: - ret = cls._get_all_migrate_files() - if ret: - return int(ret[-1].split("_")[0]) - return 0 - - @classmethod - def get_all_version_files(cls, is_all=True): - files = cls._get_all_migrate_files() - ret = [] - for file in files: - with open(os.path.join(cls.migrate_location, file), "r") as f: - content = json.load(f) - if is_all or not content.get("migrate"): - ret.append(file) - return ret + async def get_last_version(cls) -> Aerich: + return await Aerich.filter(app=cls.app).first() @classmethod async def init_with_old_models(cls, config: dict, app: str, location: str): @@ -88,22 +73,29 @@ class Migrate: 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 + version = last_version.version + return version.split("_")[0] + @classmethod async def _generate_diff_sql(cls, name): now = datetime.now().strftime("%Y%M%D%H%M%S").replace("/", "") - version = f"{cls._get_latest_version() + 1}_{now}_{name}" - filename = f"{version}.json" + last_version_num = await cls._get_last_version_num() + version = f"{last_version_num + 1}_{now}_{name}.json" content = { "upgrade": cls.upgrade_operators, "downgrade": cls.downgrade_operators, } - with open(os.path.join(cls.migrate_location, filename), "w") as f: + with open(os.path.join(cls.migrate_location, version), "w") as f: json.dump(content, f, indent=2, ensure_ascii=False) - await Aerich.create(version=version) - return filename + return version @classmethod - async def migrate(cls, name): + async def migrate(cls, name) -> str: """ diff old models and new models to generate diff content :param name: @@ -119,7 +111,7 @@ class Migrate: cls._merge_operators() if not cls.upgrade_operators: - return False + return "" return await cls._generate_diff_sql(name) @@ -260,7 +252,7 @@ class Migrate: ): cls._add_operator( cls._remove_index( - old_model, [old_field.model_field_name], old_field.unique + old_model, (old_field.model_field_name,), old_field.unique ), upgrade, isinstance(old_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), @@ -269,7 +261,7 @@ class Migrate: new_field.unique and not old_field.unique ): cls._add_operator( - cls._add_index(new_model, [new_field.model_field_name], new_field.unique), + cls._add_index(new_model, (new_field.model_field_name,), new_field.unique), upgrade, isinstance(new_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), ) @@ -299,7 +291,7 @@ class Migrate: cls._add_operator(cls._remove_index(old_model, old_unique, unique=True), upgrade) @classmethod - def _resolve_fk_fields_name(cls, model: Type[Model], fields_name: List[str]): + def _resolve_fk_fields_name(cls, model: Type[Model], fields_name: Tuple[str]): ret = [] for field_name in fields_name: if field_name in model._meta.fk_fields: @@ -309,12 +301,12 @@ class Migrate: return ret @classmethod - def _remove_index(cls, model: Type[Model], fields_name: List[str], unique=False): + def _remove_index(cls, model: Type[Model], fields_name: Tuple[str], unique=False): fields_name = cls._resolve_fk_fields_name(model, fields_name) return cls.ddl.drop_index(model, fields_name, unique) @classmethod - def _add_index(cls, model: Type[Model], fields_name: List[str], unique=False): + def _add_index(cls, model: Type[Model], fields_name: Tuple[str], unique=False): fields_name = cls._resolve_fk_fields_name(model, fields_name) return cls.ddl.add_index(model, fields_name, unique) diff --git a/aerich/models.py b/aerich/models.py index 0d3774b..27e9474 100644 --- a/aerich/models.py +++ b/aerich/models.py @@ -3,6 +3,7 @@ from tortoise import Model, fields class Aerich(Model): version = fields.CharField(max_length=50) + app = fields.CharField(max_length=20) class Meta: ordering = ["-id"] diff --git a/aerich/utils.py b/aerich/utils.py index a9873c3..4085699 100644 --- a/aerich/utils.py +++ b/aerich/utils.py @@ -1,10 +1,10 @@ import importlib from asyncclick import BadOptionUsage, Context -from tortoise import Tortoise +from tortoise import BaseDBAsyncClient, Tortoise -def get_app_connection_name(config, app): +def get_app_connection_name(config, app) -> str: """ get connection name :param config: @@ -14,7 +14,7 @@ def get_app_connection_name(config, app): return config.get("apps").get(app).get("default_connection") -def get_app_connection(config, app): +def get_app_connection(config, app) -> BaseDBAsyncClient: """ get connection name :param config: diff --git a/tests/__init__.py b/tests/__init__.py index 5650167..4abc103 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,6 @@ TORTOISE_ORM = { "connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"}, "apps": { - "models": {"models": ["tests.models", "aerich.models"], "default_connection": "default",}, + "models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"} }, }