feat: support skip table migration by set managed=False (#397)

This commit is contained in:
Waket Zheng 2025-02-21 17:08:03 +08:00 committed by GitHub
parent 41df464e8b
commit 91adf9334e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 84 additions and 10 deletions

View File

@ -26,6 +26,8 @@ jobs:
- tortoise022 - tortoise022
- tortoise023 - tortoise023
- tortoise024 - tortoise024
# TODO: add dev back when drop python3.8 support
# - tortoisedev
steps: steps:
- name: Start MySQL - name: Start MySQL
run: sudo systemctl start mysql.service run: sudo systemctl start mysql.service

View File

@ -7,6 +7,7 @@
#### Added #### Added
- feat: support command `python -m aerich`. ([#417]) - feat: support command `python -m aerich`. ([#417])
- feat: add --fake to upgrade/downgrade. ([#398]) - feat: add --fake to upgrade/downgrade. ([#398])
- Support ignore table by settings `managed=False` in `Meta` class. ([#397])
#### Fixed #### Fixed
- fix: aerich migrate raises tortoise.exceptions.FieldError when `index.INDEX_TYPE` is not empty. ([#415]) - fix: aerich migrate raises tortoise.exceptions.FieldError when `index.INDEX_TYPE` is not empty. ([#415])
@ -17,6 +18,7 @@
### Changed ### Changed
- Refactored version management to use `importlib.metadata.version(__package__)` instead of hardcoded version string ([#412]) - Refactored version management to use `importlib.metadata.version(__package__)` instead of hardcoded version string ([#412])
[#397]: https://github.com/tortoise/aerich/pull/397
[#398]: https://github.com/tortoise/aerich/pull/398 [#398]: https://github.com/tortoise/aerich/pull/398
[#401]: https://github.com/tortoise/aerich/pull/401 [#401]: https://github.com/tortoise/aerich/pull/401
[#404]: https://github.com/tortoise/aerich/pull/404 [#404]: https://github.com/tortoise/aerich/pull/404

View File

@ -295,6 +295,16 @@ aerich downgrade --fake -v 2
aerich --app models downgrade --fake -v 2 aerich --app models downgrade --fake -v 2
``` ```
### Ignore tables
You can tell aerich to ignore table by setting `managed=False` in the `Meta` class, e.g.:
```py
class MyModel(Model):
class Meta:
managed = False
```
**Note** `managed=False` does not recognized by `tortoise-orm` and `aerich init-db`, it is only for `aerich migrate`.
## License ## License
This project is licensed under the This project is licensed under the

View File

@ -264,7 +264,11 @@ class Migrate:
) -> None: ) -> None:
old_m2m_fields = cast("list[dict]", old_model_describe.get("m2m_fields", [])) old_m2m_fields = cast("list[dict]", old_model_describe.get("m2m_fields", []))
new_m2m_fields = cast("list[dict]", new_model_describe.get("m2m_fields", [])) new_m2m_fields = cast("list[dict]", new_model_describe.get("m2m_fields", []))
new_tables: dict[str, dict] = {field["table"]: field for field in new_models.values()} new_tables: dict[str, dict] = {
field["table"]: field
for field in new_models.values()
if field.get("managed") is not False
}
for action, option, change in get_dict_diff_by_key(old_m2m_fields, new_m2m_fields): for action, option, change in get_dict_diff_by_key(old_m2m_fields, new_m2m_fields):
if (option and option[-1] == "nullable") or change[0][0] == "db_constraint": if (option and option[-1] == "nullable") or change[0][0] == "db_constraint":
continue continue
@ -387,6 +391,8 @@ class Migrate:
models_with_rename_field: set[str] = set() # models that trigger the click.prompt models_with_rename_field: set[str] = set() # models that trigger the click.prompt
for new_model_str, new_model_describe in new_models.items(): for new_model_str, new_model_describe in new_models.items():
if upgrade and new_model_describe.get("managed") is False:
continue
model = cls._get_model(new_model_describe["name"].split(".")[1]) model = cls._get_model(new_model_describe["name"].split(".")[1])
if new_model_str not in old_models: if new_model_str not in old_models:
if upgrade: if upgrade:
@ -397,6 +403,8 @@ class Migrate:
pass pass
else: else:
old_model_describe = cast(dict, old_models.get(new_model_str)) old_model_describe = cast(dict, old_models.get(new_model_str))
if not upgrade and old_model_describe.get("managed") is False:
continue
# rename table # rename table
new_table = cast(str, new_model_describe.get("table")) new_table = cast(str, new_model_describe.get("table"))
old_table = cast(str, old_model_describe.get("table")) old_table = cast(str, old_model_describe.get("table"))
@ -593,6 +601,8 @@ class Migrate:
) )
for old_model in old_models.keys() - new_models.keys(): for old_model in old_models.keys() - new_models.keys():
if not upgrade and old_models[old_model].get("managed") is False:
continue
cls._add_operator(cls.drop_model(old_models[old_model]["table"]), upgrade) cls._add_operator(cls.drop_model(old_models[old_model]["table"]), upgrade)
@classmethod @classmethod

View File

@ -34,15 +34,11 @@ def get_app_connection_name(config, app_name: str) -> str:
get connection name get connection name
:param config: :param config:
:param app_name: :param app_name:
:return: :return: the default connection name (Usally it is 'default')
""" """
app = config.get("apps").get(app_name) if app := config.get("apps").get(app_name):
if not app: return app.get("default_connection", "default")
raise BadOptionUsage( raise BadOptionUsage(option_name="--app", message=f"Can't get app named {app_name!r}")
option_name="--app",
message=f'Can\'t get app named "{app_name}"',
)
return app.get("default_connection", "default")
def get_app_connection(config, app) -> BaseDBAsyncClient: def get_app_connection(config, app) -> BaseDBAsyncClient:
@ -89,8 +85,9 @@ def get_models_describe(app: str) -> dict:
""" """
ret = {} ret = {}
for model in Tortoise.apps[app].values(): for model in Tortoise.apps[app].values():
managed = getattr(model.Meta, "managed", None)
describe = model.describe() describe = model.describe()
ret[describe.get("name")] = describe ret[describe.get("name")] = dict(describe, managed=managed)
return ret return ret

View File

@ -102,6 +102,7 @@ class Product(Model):
class Meta: class Meta:
unique_together = (("name", "type"),) unique_together = (("name", "type"),)
indexes = (("name", "type"),) indexes = (("name", "type"),)
managed = True
class Config(Model): class Config(Model):
@ -118,6 +119,21 @@ class Config(Model):
email: fields.OneToOneRelation[Email] email: fields.OneToOneRelation[Email]
class Meta:
managed = True
class DontManageMe(Model):
name = fields.CharField(max_length=50)
class Meta:
managed = False
class Ignore(Model):
class Meta:
managed = False
class NewModel(Model): class NewModel(Model):
name = fields.CharField(max_length=50) name = fields.CharField(max_length=50)

View File

@ -89,3 +89,40 @@ class Config(Model):
class Meta: class Meta:
table = "configs" table = "configs"
class DontManageMe(Model):
name = fields.CharField(max_length=50)
class Meta:
table = "dont_manage"
class Ignore(Model):
name = fields.CharField(max_length=50)
class Meta:
managed = True
def main() -> None:
"""Generate a python file for the old_models_describe"""
from pathlib import Path
from tortoise import run_async
from tortoise.contrib.test import init_memory_sqlite
from aerich.utils import get_models_describe
@init_memory_sqlite
async def run() -> None:
old_models_describe = get_models_describe("models")
p = Path("old_models_describe.py")
p.write_text(f"{old_models_describe = }", encoding="utf-8")
print(f"Write value to {p}\nYou can reformat it by `ruff format {p}`")
run_async(run())
if __name__ == "__main__":
main()