Store versions in db
This commit is contained in:
		| @@ -8,6 +8,7 @@ ChangeLog | |||||||
| ----- | ----- | ||||||
| - Add sqlite and postgres support. | - Add sqlite and postgres support. | ||||||
| - Fix dependency import. | - Fix dependency import. | ||||||
|  | - Store versions in db. | ||||||
|  |  | ||||||
| 0.1.4 | 0.1.4 | ||||||
| ----- | ----- | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.rst
									
									
									
									
									
								
							| @@ -54,6 +54,19 @@ Quick Start | |||||||
|  |  | ||||||
| Usage | 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 | Initialization | ||||||
| -------------- | -------------- | ||||||
| @@ -100,7 +113,7 @@ Update models and make migrate | |||||||
|  |  | ||||||
|     Success migrate 1_202029051520102929_drop_column.json |     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 | Upgrade to latest version | ||||||
| ------------------------- | ------------------------- | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ from aerich.migrate import Migrate | |||||||
| from aerich.utils import get_app_connection, get_app_connection_name, get_tortoise_config | from aerich.utils import get_app_connection, get_app_connection_name, get_tortoise_config | ||||||
|  |  | ||||||
| from . import __version__ | from . import __version__ | ||||||
|  | from .models import Aerich | ||||||
|  |  | ||||||
|  |  | ||||||
| class Color(str, Enum): | class Color(str, Enum): | ||||||
| @@ -83,24 +84,23 @@ async def migrate(ctx: Context, name): | |||||||
| @cli.command(help="Upgrade to latest version.") | @cli.command(help="Upgrade to latest version.") | ||||||
| @click.pass_context | @click.pass_context | ||||||
| async def upgrade(ctx: Context): | async def upgrade(ctx: Context): | ||||||
|     app = ctx.obj["app"] |  | ||||||
|     config = ctx.obj["config"] |     config = ctx.obj["config"] | ||||||
|     available_versions = Migrate.get_all_version_files(is_all=False) |     app = ctx.obj["app"] | ||||||
|     if not available_versions: |     migrated = False | ||||||
|         return click.secho("No migrate items", fg=Color.yellow) |     for version in Migrate.get_all_version_files(): | ||||||
|     async with in_transaction(get_app_connection_name(config, app)) as conn: |         if not await Aerich.exists(version=version, app=app): | ||||||
|         for file in available_versions: |             async with in_transaction(get_app_connection_name(config, app)) as conn: | ||||||
|             file_path = os.path.join(Migrate.migrate_location, file) |                 file_path = os.path.join(Migrate.migrate_location, version) | ||||||
|             with open(file_path, "r") as f: |                 with open(file_path, "r") as f: | ||||||
|                 content = json.load(f) |                     content = json.load(f) | ||||||
|                 upgrade_query_list = content.get("upgrade") |                     upgrade_query_list = content.get("upgrade") | ||||||
|                 for upgrade_query in upgrade_query_list: |                     for upgrade_query in upgrade_query_list: | ||||||
|                     await conn.execute_query(upgrade_query) |                         await conn.execute_query(upgrade_query) | ||||||
|  |             await Aerich.create(version=version, app=app) | ||||||
|             with open(file_path, "w") as f: |             click.secho(f"Success upgrade {version}", fg=Color.green) | ||||||
|                 content["migrate"] = True |             migrated = True | ||||||
|                 json.dump(content, f, indent=2, ensure_ascii=False) |     if not migrated: | ||||||
|                 click.secho(f"Success upgrade {file}", fg=Color.green) |         click.secho("No migrate items", fg=Color.yellow) | ||||||
|  |  | ||||||
|  |  | ||||||
| @cli.command(help="Downgrade to previous version.") | @cli.command(help="Downgrade to previous version.") | ||||||
| @@ -108,39 +108,43 @@ async def upgrade(ctx: Context): | |||||||
| async def downgrade(ctx: Context): | async def downgrade(ctx: Context): | ||||||
|     app = ctx.obj["app"] |     app = ctx.obj["app"] | ||||||
|     config = ctx.obj["config"] |     config = ctx.obj["config"] | ||||||
|     available_versions = Migrate.get_all_version_files() |     last_version = await Migrate.get_last_version() | ||||||
|     if not available_versions: |     if not last_version: | ||||||
|         return click.secho("No migrate items", fg=Color.yellow) |         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: |     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) | ||||||
|             file_path = os.path.join(Migrate.migrate_location, file) |         with open(file_path, "r") as f: | ||||||
|             with open(file_path, "r") as f: |             content = json.load(f) | ||||||
|                 content = json.load(f) |             downgrade_query_list = content.get("downgrade") | ||||||
|                 if content.get("migrate"): |             for downgrade_query in downgrade_query_list: | ||||||
|                     downgrade_query_list = content.get("downgrade") |                 await conn.execute_query(downgrade_query) | ||||||
|                     for downgrade_query in downgrade_query_list: |             await last_version.delete() | ||||||
|                         await conn.execute_query(downgrade_query) |         return click.secho(f"Success downgrade {file}", fg=Color.green) | ||||||
|                 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) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @cli.command(help="Show current available heads in migrate location.") | @cli.command(help="Show current available heads in migrate location.") | ||||||
| @click.pass_context | @click.pass_context | ||||||
| def heads(ctx: Context): | async def heads(ctx: Context): | ||||||
|     for version in Migrate.get_all_version_files(is_all=False): |     app = ctx.obj["app"] | ||||||
|         click.secho(version, fg=Color.green) |     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.") | @cli.command(help="List all migrate items.") | ||||||
| @click.pass_context | @click.pass_context | ||||||
| def history(ctx): | 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) |         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.") | @cli.command(help="Init config file and generate root migrate location.") | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import os | |||||||
| import re | import re | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from typing import Dict, List, Type | from typing import Dict, List, Tuple, Type | ||||||
|  |  | ||||||
| from tortoise import ( | from tortoise import ( | ||||||
|     BackwardFKRelation, |     BackwardFKRelation, | ||||||
| @@ -16,7 +16,6 @@ from tortoise import ( | |||||||
| from tortoise.fields import Field | from tortoise.fields import Field | ||||||
|  |  | ||||||
| from aerich.ddl import BaseDDL | from aerich.ddl import BaseDDL | ||||||
| from aerich.exceptions import ConfigurationError |  | ||||||
| from aerich.models import Aerich | from aerich.models import Aerich | ||||||
| from aerich.utils import get_app_connection | from aerich.utils import get_app_connection | ||||||
|  |  | ||||||
| @@ -41,26 +40,12 @@ class Migrate: | |||||||
|         return cls.old_models + ".py" |         return cls.old_models + ".py" | ||||||
|  |  | ||||||
|     @classmethod |     @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))) |         return sorted(filter(lambda x: x.endswith("json"), os.listdir(cls.migrate_location))) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _get_latest_version(cls) -> int: |     async def get_last_version(cls) -> Aerich: | ||||||
|         ret = cls._get_all_migrate_files() |         return await Aerich.filter(app=cls.app).first() | ||||||
|         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 |  | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     async def init_with_old_models(cls, config: dict, app: str, location: str): |     async def init_with_old_models(cls, config: dict, app: str, location: str): | ||||||
| @@ -88,22 +73,29 @@ class Migrate: | |||||||
|         else: |         else: | ||||||
|             raise NotImplementedError("Current only support MySQL") |             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 |     @classmethod | ||||||
|     async def _generate_diff_sql(cls, name): |     async def _generate_diff_sql(cls, name): | ||||||
|         now = datetime.now().strftime("%Y%M%D%H%M%S").replace("/", "") |         now = datetime.now().strftime("%Y%M%D%H%M%S").replace("/", "") | ||||||
|         version = f"{cls._get_latest_version() + 1}_{now}_{name}" |         last_version_num = await cls._get_last_version_num() | ||||||
|         filename = f"{version}.json" |         version = f"{last_version_num + 1}_{now}_{name}.json" | ||||||
|         content = { |         content = { | ||||||
|             "upgrade": cls.upgrade_operators, |             "upgrade": cls.upgrade_operators, | ||||||
|             "downgrade": cls.downgrade_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) |             json.dump(content, f, indent=2, ensure_ascii=False) | ||||||
|         await Aerich.create(version=version) |         return version | ||||||
|         return filename |  | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     async def migrate(cls, name): |     async def migrate(cls, name) -> str: | ||||||
|         """ |         """ | ||||||
|         diff old models and new models to generate diff content |         diff old models and new models to generate diff content | ||||||
|         :param name: |         :param name: | ||||||
| @@ -119,7 +111,7 @@ class Migrate: | |||||||
|         cls._merge_operators() |         cls._merge_operators() | ||||||
|  |  | ||||||
|         if not cls.upgrade_operators: |         if not cls.upgrade_operators: | ||||||
|             return False |             return "" | ||||||
|  |  | ||||||
|         return await cls._generate_diff_sql(name) |         return await cls._generate_diff_sql(name) | ||||||
|  |  | ||||||
| @@ -260,7 +252,7 @@ class Migrate: | |||||||
|                 ): |                 ): | ||||||
|                     cls._add_operator( |                     cls._add_operator( | ||||||
|                         cls._remove_index( |                         cls._remove_index( | ||||||
|                             old_model, [old_field.model_field_name], old_field.unique |                             old_model, (old_field.model_field_name,), old_field.unique | ||||||
|                         ), |                         ), | ||||||
|                         upgrade, |                         upgrade, | ||||||
|                         isinstance(old_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), |                         isinstance(old_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), | ||||||
| @@ -269,7 +261,7 @@ class Migrate: | |||||||
|                     new_field.unique and not old_field.unique |                     new_field.unique and not old_field.unique | ||||||
|                 ): |                 ): | ||||||
|                     cls._add_operator( |                     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, |                         upgrade, | ||||||
|                         isinstance(new_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), |                         isinstance(new_field, (ForeignKeyFieldInstance, ManyToManyFieldInstance)), | ||||||
|                     ) |                     ) | ||||||
| @@ -299,7 +291,7 @@ class Migrate: | |||||||
|                 cls._add_operator(cls._remove_index(old_model, old_unique, unique=True), upgrade) |                 cls._add_operator(cls._remove_index(old_model, old_unique, unique=True), upgrade) | ||||||
|  |  | ||||||
|     @classmethod |     @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 = [] |         ret = [] | ||||||
|         for field_name in fields_name: |         for field_name in fields_name: | ||||||
|             if field_name in model._meta.fk_fields: |             if field_name in model._meta.fk_fields: | ||||||
| @@ -309,12 +301,12 @@ class Migrate: | |||||||
|         return ret |         return ret | ||||||
|  |  | ||||||
|     @classmethod |     @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) |         fields_name = cls._resolve_fk_fields_name(model, fields_name) | ||||||
|         return cls.ddl.drop_index(model, fields_name, unique) |         return cls.ddl.drop_index(model, fields_name, unique) | ||||||
|  |  | ||||||
|     @classmethod |     @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) |         fields_name = cls._resolve_fk_fields_name(model, fields_name) | ||||||
|         return cls.ddl.add_index(model, fields_name, unique) |         return cls.ddl.add_index(model, fields_name, unique) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ from tortoise import Model, fields | |||||||
|  |  | ||||||
| class Aerich(Model): | class Aerich(Model): | ||||||
|     version = fields.CharField(max_length=50) |     version = fields.CharField(max_length=50) | ||||||
|  |     app = fields.CharField(max_length=20) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         ordering = ["-id"] |         ordering = ["-id"] | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import importlib | import importlib | ||||||
|  |  | ||||||
| from asyncclick import BadOptionUsage, Context | 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 |     get connection name | ||||||
|     :param config: |     :param config: | ||||||
| @@ -14,7 +14,7 @@ def get_app_connection_name(config, app): | |||||||
|     return config.get("apps").get(app).get("default_connection") |     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 |     get connection name | ||||||
|     :param config: |     :param config: | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| TORTOISE_ORM = { | TORTOISE_ORM = { | ||||||
|     "connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"}, |     "connections": {"default": "mysql://root:123456@127.0.0.1:3306/test"}, | ||||||
|     "apps": { |     "apps": { | ||||||
|         "models": {"models": ["tests.models", "aerich.models"], "default_connection": "default",}, |         "models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"} | ||||||
|     }, |     }, | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user