feat: support --fake
for aerich upgrade (#398)
* feat: support `--fake` for aerich upgrade * Add `--fake` to downgrade * tests: check --fake result for aerich upgrade and downgrade * Update readme * Fix unittest failed because of `db_field_types` changed * refactor: improve type hints and document
This commit is contained in:
parent
ac847ba616
commit
b46ceafb2e
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@ -25,20 +25,20 @@ jobs:
|
|||||||
- tortoise021
|
- tortoise021
|
||||||
- tortoise022
|
- tortoise022
|
||||||
- tortoise023
|
- tortoise023
|
||||||
- tortoisedev
|
- tortoise024
|
||||||
steps:
|
steps:
|
||||||
- name: Start MySQL
|
- name: Start MySQL
|
||||||
run: sudo systemctl start mysql.service
|
run: sudo systemctl start mysql.service
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
- uses: actions/cache@v4
|
- uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/poetry.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pip-
|
${{ runner.os }}-pip-
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
- name: Install and configure Poetry
|
- name: Install and configure Poetry
|
||||||
run: |
|
run: |
|
||||||
pip install -U pip poetry
|
pip install -U pip poetry
|
||||||
@ -54,11 +54,23 @@ jobs:
|
|||||||
- name: Install TortoiseORM v0.23
|
- name: Install TortoiseORM v0.23
|
||||||
if: matrix.tortoise-orm == 'tortoise023'
|
if: matrix.tortoise-orm == 'tortoise023'
|
||||||
run: poetry run pip install --upgrade "tortoise-orm>=0.23,<0.24"
|
run: poetry run pip install --upgrade "tortoise-orm>=0.23,<0.24"
|
||||||
|
- name: Install TortoiseORM v0.24
|
||||||
|
if: matrix.tortoise-orm == 'tortoise024'
|
||||||
|
run: |
|
||||||
|
if [[ "${{ matrix.python-version }}" == "3.8" ]]; then
|
||||||
|
echo "Skip test for tortoise v0.24 as it does not support Python3.8"
|
||||||
|
else
|
||||||
|
poetry run pip install --upgrade "tortoise-orm>=0.24,<0.25"
|
||||||
|
fi
|
||||||
- name: Install TortoiseORM develop branch
|
- name: Install TortoiseORM develop branch
|
||||||
if: matrix.tortoise-orm == 'tortoisedev'
|
if: matrix.tortoise-orm == 'tortoisedev'
|
||||||
run: |
|
run: |
|
||||||
poetry run pip uninstall -y tortoise-orm
|
if [[ "${{ matrix.python-version }}" == "3.8" ]]; then
|
||||||
poetry run pip install --upgrade "git+https://github.com/tortoise/tortoise-orm"
|
echo "Skip test for tortoise develop branch as it does not support Python3.8"
|
||||||
|
else
|
||||||
|
poetry run pip uninstall -y tortoise-orm
|
||||||
|
poetry run pip install --upgrade "git+https://github.com/tortoise/tortoise-orm"
|
||||||
|
fi
|
||||||
- name: CI
|
- name: CI
|
||||||
env:
|
env:
|
||||||
MYSQL_PASS: root
|
MYSQL_PASS: root
|
||||||
|
@ -4,9 +4,13 @@
|
|||||||
|
|
||||||
### [0.8.2]**(Unreleased)**
|
### [0.8.2]**(Unreleased)**
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- feat: add --fake to upgrade/downgrade. ([#398])
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
- fix: inspectdb raise KeyError 'int2' for smallint. ([#401])
|
- fix: inspectdb raise KeyError 'int2' for smallint. ([#401])
|
||||||
|
|
||||||
|
[#398]: https://github.com/tortoise/aerich/pull/398
|
||||||
[#401]: https://github.com/tortoise/aerich/pull/401
|
[#401]: https://github.com/tortoise/aerich/pull/401
|
||||||
|
|
||||||
### [0.8.1](../../releases/tag/v0.8.1) - 2024-12-27
|
### [0.8.1](../../releases/tag/v0.8.1) - 2024-12-27
|
||||||
|
17
README.md
17
README.md
@ -278,6 +278,23 @@ await command.init()
|
|||||||
await command.migrate('test')
|
await command.migrate('test')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Upgrade/Downgrade with `--fake` option
|
||||||
|
|
||||||
|
Marks the migrations up to the latest one(or back to the target one) as applied, but without actually running the SQL to change your database schema.
|
||||||
|
|
||||||
|
- Upgrade
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aerich upgrade --fake
|
||||||
|
aerich --app models upgrade --fake
|
||||||
|
```
|
||||||
|
- Downgrade
|
||||||
|
|
||||||
|
```bash
|
||||||
|
aerich downgrade --fake -v 2
|
||||||
|
aerich --app models downgrade --fake -v 2
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the
|
This project is licensed under the
|
||||||
|
@ -39,18 +39,19 @@ class Command:
|
|||||||
async def init(self) -> None:
|
async def init(self) -> None:
|
||||||
await Migrate.init(self.tortoise_config, self.app, self.location)
|
await Migrate.init(self.tortoise_config, self.app, self.location)
|
||||||
|
|
||||||
async def _upgrade(self, conn, version_file) -> None:
|
async def _upgrade(self, conn, version_file, fake=False) -> None:
|
||||||
file_path = Path(Migrate.migrate_location, version_file)
|
file_path = Path(Migrate.migrate_location, version_file)
|
||||||
m = import_py_file(file_path)
|
m = import_py_file(file_path)
|
||||||
upgrade = m.upgrade
|
upgrade = m.upgrade
|
||||||
await conn.execute_script(await upgrade(conn))
|
if not fake:
|
||||||
|
await conn.execute_script(await upgrade(conn))
|
||||||
await Aerich.create(
|
await Aerich.create(
|
||||||
version=version_file,
|
version=version_file,
|
||||||
app=self.app,
|
app=self.app,
|
||||||
content=get_models_describe(self.app),
|
content=get_models_describe(self.app),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def upgrade(self, run_in_transaction: bool = True) -> List[str]:
|
async def upgrade(self, run_in_transaction: bool = True, fake=False) -> List[str]:
|
||||||
migrated = []
|
migrated = []
|
||||||
for version_file in Migrate.get_all_version_files():
|
for version_file in Migrate.get_all_version_files():
|
||||||
try:
|
try:
|
||||||
@ -61,14 +62,14 @@ class Command:
|
|||||||
app_conn_name = get_app_connection_name(self.tortoise_config, self.app)
|
app_conn_name = get_app_connection_name(self.tortoise_config, self.app)
|
||||||
if run_in_transaction:
|
if run_in_transaction:
|
||||||
async with in_transaction(app_conn_name) as conn:
|
async with in_transaction(app_conn_name) as conn:
|
||||||
await self._upgrade(conn, version_file)
|
await self._upgrade(conn, version_file, fake=fake)
|
||||||
else:
|
else:
|
||||||
app_conn = get_app_connection(self.tortoise_config, self.app)
|
app_conn = get_app_connection(self.tortoise_config, self.app)
|
||||||
await self._upgrade(app_conn, version_file)
|
await self._upgrade(app_conn, version_file, fake=fake)
|
||||||
migrated.append(version_file)
|
migrated.append(version_file)
|
||||||
return migrated
|
return migrated
|
||||||
|
|
||||||
async def downgrade(self, version: int, delete: bool) -> List[str]:
|
async def downgrade(self, version: int, delete: bool, fake=False) -> List[str]:
|
||||||
ret: List[str] = []
|
ret: List[str] = []
|
||||||
if version == -1:
|
if version == -1:
|
||||||
specified_version = await Migrate.get_last_version()
|
specified_version = await Migrate.get_last_version()
|
||||||
@ -93,7 +94,8 @@ class Command:
|
|||||||
downgrade_sql = await downgrade(conn)
|
downgrade_sql = await downgrade(conn)
|
||||||
if not downgrade_sql.strip():
|
if not downgrade_sql.strip():
|
||||||
raise DowngradeError("No downgrade items found")
|
raise DowngradeError("No downgrade items found")
|
||||||
await conn.execute_script(downgrade_sql)
|
if not fake:
|
||||||
|
await conn.execute_script(downgrade_sql)
|
||||||
await version_obj.delete()
|
await version_obj.delete()
|
||||||
if delete:
|
if delete:
|
||||||
os.unlink(file_path)
|
os.unlink(file_path)
|
||||||
|
@ -93,15 +93,26 @@ async def migrate(ctx: Context, name, empty) -> None:
|
|||||||
type=bool,
|
type=bool,
|
||||||
help="Make migrations in a single transaction or not. Can be helpful for large migrations or creating concurrent indexes.",
|
help="Make migrations in a single transaction or not. Can be helpful for large migrations or creating concurrent indexes.",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--fake",
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help="Mark migrations as run without actually running them.",
|
||||||
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
async def upgrade(ctx: Context, in_transaction: bool) -> None:
|
async def upgrade(ctx: Context, in_transaction: bool, fake: bool) -> None:
|
||||||
command = ctx.obj["command"]
|
command = ctx.obj["command"]
|
||||||
migrated = await command.upgrade(run_in_transaction=in_transaction)
|
migrated = await command.upgrade(run_in_transaction=in_transaction, fake=fake)
|
||||||
if not migrated:
|
if not migrated:
|
||||||
click.secho("No upgrade items found", fg=Color.yellow)
|
click.secho("No upgrade items found", fg=Color.yellow)
|
||||||
else:
|
else:
|
||||||
for version_file in migrated:
|
for version_file in migrated:
|
||||||
click.secho(f"Success upgrading to {version_file}", fg=Color.green)
|
if fake:
|
||||||
|
click.echo(
|
||||||
|
f"Upgrading to {version_file}... " + click.style("FAKED", fg=Color.green)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
click.secho(f"Success upgrading to {version_file}", fg=Color.green)
|
||||||
|
|
||||||
|
|
||||||
@cli.command(help="Downgrade to specified version.")
|
@cli.command(help="Downgrade to specified version.")
|
||||||
@ -121,18 +132,27 @@ async def upgrade(ctx: Context, in_transaction: bool) -> None:
|
|||||||
show_default=True,
|
show_default=True,
|
||||||
help="Also delete the migration files.",
|
help="Also delete the migration files.",
|
||||||
)
|
)
|
||||||
|
@click.option(
|
||||||
|
"--fake",
|
||||||
|
default=False,
|
||||||
|
is_flag=True,
|
||||||
|
help="Mark migrations as run without actually running them.",
|
||||||
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
@click.confirmation_option(
|
@click.confirmation_option(
|
||||||
prompt="Downgrade is dangerous: you might lose your data! Are you sure?",
|
prompt="Downgrade is dangerous: you might lose your data! Are you sure?",
|
||||||
)
|
)
|
||||||
async def downgrade(ctx: Context, version: int, delete: bool) -> None:
|
async def downgrade(ctx: Context, version: int, delete: bool, fake: bool) -> None:
|
||||||
command = ctx.obj["command"]
|
command = ctx.obj["command"]
|
||||||
try:
|
try:
|
||||||
files = await command.downgrade(version, delete)
|
files = await command.downgrade(version, delete, fake=fake)
|
||||||
except DowngradeError as e:
|
except DowngradeError as e:
|
||||||
return click.secho(str(e), fg=Color.yellow)
|
return click.secho(str(e), fg=Color.yellow)
|
||||||
for file in files:
|
for file in files:
|
||||||
click.secho(f"Success downgrading to {file}", fg=Color.green)
|
if fake:
|
||||||
|
click.echo(f"Downgrading to {file}... " + click.style("FAKED", fg=Color.green))
|
||||||
|
else:
|
||||||
|
click.secho(f"Success downgrading to {file}", fg=Color.green)
|
||||||
|
|
||||||
|
|
||||||
@cli.command(help="Show currently available heads (unapplied migrations).")
|
@cli.command(help="Show currently available heads (unapplied migrations).")
|
||||||
|
@ -4,7 +4,7 @@ import importlib
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Iterable, List, Optional, Set, Tuple, Type, Union, cast
|
from typing import Iterable, Optional, Union, cast
|
||||||
|
|
||||||
import asyncclick as click
|
import asyncclick as click
|
||||||
import tortoise
|
import tortoise
|
||||||
@ -37,17 +37,17 @@ async def downgrade(db: BaseDBAsyncClient) -> str:
|
|||||||
|
|
||||||
|
|
||||||
class Migrate:
|
class Migrate:
|
||||||
upgrade_operators: List[str] = []
|
upgrade_operators: list[str] = []
|
||||||
downgrade_operators: List[str] = []
|
downgrade_operators: list[str] = []
|
||||||
_upgrade_fk_m2m_index_operators: List[str] = []
|
_upgrade_fk_m2m_index_operators: list[str] = []
|
||||||
_downgrade_fk_m2m_index_operators: List[str] = []
|
_downgrade_fk_m2m_index_operators: list[str] = []
|
||||||
_upgrade_m2m: List[str] = []
|
_upgrade_m2m: list[str] = []
|
||||||
_downgrade_m2m: List[str] = []
|
_downgrade_m2m: list[str] = []
|
||||||
_aerich = Aerich.__name__
|
_aerich = Aerich.__name__
|
||||||
_rename_fields: Dict[str, Dict[str, str]] = {} # {'model': {'old_field': 'new_field'}}
|
_rename_fields: dict[str, dict[str, str]] = {} # {'model': {'old_field': 'new_field'}}
|
||||||
|
|
||||||
ddl: BaseDDL
|
ddl: BaseDDL
|
||||||
ddl_class: Type[BaseDDL]
|
ddl_class: type[BaseDDL]
|
||||||
_last_version_content: Optional[dict] = None
|
_last_version_content: Optional[dict] = None
|
||||||
app: str
|
app: str
|
||||||
migrate_location: Path
|
migrate_location: Path
|
||||||
@ -55,11 +55,11 @@ class Migrate:
|
|||||||
_db_version: Optional[str] = None
|
_db_version: Optional[str] = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_field_by_name(name: str, fields: List[dict]) -> dict:
|
def get_field_by_name(name: str, fields: list[dict]) -> dict:
|
||||||
return next(filter(lambda x: x.get("name") == name, fields))
|
return next(filter(lambda x: x.get("name") == name, fields))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_all_version_files(cls) -> List[str]:
|
def get_all_version_files(cls) -> list[str]:
|
||||||
def get_file_version(file_name: str) -> str:
|
def get_file_version(file_name: str) -> str:
|
||||||
return file_name.split("_")[0]
|
return file_name.split("_")[0]
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ class Migrate:
|
|||||||
return sorted(files, key=lambda x: int(get_file_version(x)))
|
return sorted(files, key=lambda x: int(get_file_version(x)))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_model(cls, model: str) -> Type[Model]:
|
def _get_model(cls, model: str) -> type[Model]:
|
||||||
return Tortoise.apps[cls.app].get(model) # type: ignore
|
return Tortoise.apps[cls.app].get(model) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -92,7 +92,7 @@ class Migrate:
|
|||||||
cls._db_version = ret[1][0].get("version")
|
cls._db_version = ret[1][0].get("version")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def load_ddl_class(cls) -> Type[BaseDDL]:
|
async def load_ddl_class(cls) -> type[BaseDDL]:
|
||||||
ddl_dialect_module = importlib.import_module(f"aerich.ddl.{cls.dialect}")
|
ddl_dialect_module = importlib.import_module(f"aerich.ddl.{cls.dialect}")
|
||||||
return getattr(ddl_dialect_module, f"{cls.dialect.capitalize()}DDL")
|
return getattr(ddl_dialect_module, f"{cls.dialect.capitalize()}DDL")
|
||||||
|
|
||||||
@ -142,6 +142,22 @@ class Migrate:
|
|||||||
Path(cls.migrate_location, version).write_text(content, encoding="utf-8")
|
Path(cls.migrate_location, version).write_text(content, encoding="utf-8")
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _exclude_extra_field_types(cls, diffs) -> list[tuple]:
|
||||||
|
# Exclude changes of db_field_types that is not about the current dialect, e.g.:
|
||||||
|
# {"db_field_types": {
|
||||||
|
# "oracle": "VARCHAR(255)" --> "oracle": "NVARCHAR2(255)"
|
||||||
|
# }}
|
||||||
|
return [
|
||||||
|
c
|
||||||
|
for c in diffs
|
||||||
|
if not (
|
||||||
|
len(c) == 3
|
||||||
|
and c[1] == "db_field_types"
|
||||||
|
and not ({i[0] for i in c[2]} & {cls.dialect, ""})
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def migrate(cls, name: str, empty: bool) -> str:
|
async def migrate(cls, name: str, empty: bool) -> str:
|
||||||
"""
|
"""
|
||||||
@ -170,7 +186,7 @@ class Migrate:
|
|||||||
builds content for diff file from template
|
builds content for diff file from template
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def join_lines(lines: List[str]) -> str:
|
def join_lines(lines: list[str]) -> str:
|
||||||
if not lines:
|
if not lines:
|
||||||
return ""
|
return ""
|
||||||
return ";\n ".join(lines) + ";"
|
return ";\n ".join(lines) + ";"
|
||||||
@ -202,7 +218,7 @@ class Migrate:
|
|||||||
cls.downgrade_operators.append(operator)
|
cls.downgrade_operators.append(operator)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _handle_indexes(cls, model: Type[Model], indexes: List[Union[Tuple[str], Index]]) -> list:
|
def _handle_indexes(cls, model: type[Model], indexes: list[Union[tuple[str], Index]]) -> list:
|
||||||
if tortoise.__version__ > "0.22.2":
|
if tortoise.__version__ > "0.22.2":
|
||||||
# The min version of tortoise is '0.11.0', so we can compare it by a `>`,
|
# The min version of tortoise is '0.11.0', so we can compare it by a `>`,
|
||||||
# tortoise>0.22.2 have __eq__/__hash__ with Index class since 313ee76.
|
# tortoise>0.22.2 have __eq__/__hash__ with Index class since 313ee76.
|
||||||
@ -224,13 +240,13 @@ class Migrate:
|
|||||||
return indexes
|
return indexes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_indexes(cls, model, model_describe: dict) -> Set[Union[Index, Tuple[str, ...]]]:
|
def _get_indexes(cls, model, model_describe: dict) -> set[Union[Index, tuple[str, ...]]]:
|
||||||
indexes: Set[Union[Index, Tuple[str, ...]]] = set()
|
indexes: set[Union[Index, tuple[str, ...]]] = set()
|
||||||
for x in cls._handle_indexes(model, model_describe.get("indexes", [])):
|
for x in cls._handle_indexes(model, model_describe.get("indexes", [])):
|
||||||
if isinstance(x, Index):
|
if isinstance(x, Index):
|
||||||
indexes.add(x)
|
indexes.add(x)
|
||||||
else:
|
else:
|
||||||
indexes.add(cast(Tuple[str, ...], tuple(x)))
|
indexes.add(cast("tuple[str, ...]", tuple(x)))
|
||||||
return indexes
|
return indexes
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -240,11 +256,11 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _handle_m2m_fields(
|
def _handle_m2m_fields(
|
||||||
cls, old_model_describe: Dict, new_model_describe: Dict, model, new_models, upgrade=True
|
cls, old_model_describe: dict, new_model_describe: dict, model, new_models, upgrade=True
|
||||||
) -> 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()}
|
||||||
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
|
||||||
@ -290,18 +306,18 @@ class Migrate:
|
|||||||
def _handle_relational(
|
def _handle_relational(
|
||||||
cls,
|
cls,
|
||||||
key: str,
|
key: str,
|
||||||
old_model_describe: Dict,
|
old_model_describe: dict,
|
||||||
new_model_describe: Dict,
|
new_model_describe: dict,
|
||||||
model: Type[Model],
|
model: type[Model],
|
||||||
old_models: Dict,
|
old_models: dict,
|
||||||
new_models: Dict,
|
new_models: dict,
|
||||||
upgrade=True,
|
upgrade=True,
|
||||||
) -> None:
|
) -> None:
|
||||||
old_fk_fields = cast(List[dict], old_model_describe.get(key))
|
old_fk_fields = cast("list[dict]", old_model_describe.get(key))
|
||||||
new_fk_fields = cast(List[dict], new_model_describe.get(key))
|
new_fk_fields = cast("list[dict]", new_model_describe.get(key))
|
||||||
|
|
||||||
old_fk_fields_name: List[str] = [i.get("name", "") for i in old_fk_fields]
|
old_fk_fields_name: list[str] = [i.get("name", "") for i in old_fk_fields]
|
||||||
new_fk_fields_name: List[str] = [i.get("name", "") for i in new_fk_fields]
|
new_fk_fields_name: list[str] = [i.get("name", "") for i in new_fk_fields]
|
||||||
|
|
||||||
# add
|
# add
|
||||||
for new_fk_field_name in set(new_fk_fields_name).difference(set(old_fk_fields_name)):
|
for new_fk_field_name in set(new_fk_fields_name).difference(set(old_fk_fields_name)):
|
||||||
@ -312,7 +328,9 @@ class Migrate:
|
|||||||
cls._add_operator(sql, upgrade, fk_m2m_index=True)
|
cls._add_operator(sql, upgrade, fk_m2m_index=True)
|
||||||
# drop
|
# drop
|
||||||
for old_fk_field_name in set(old_fk_fields_name).difference(set(new_fk_fields_name)):
|
for old_fk_field_name in set(old_fk_fields_name).difference(set(new_fk_fields_name)):
|
||||||
old_fk_field = cls.get_field_by_name(old_fk_field_name, cast(List[dict], old_fk_fields))
|
old_fk_field = cls.get_field_by_name(
|
||||||
|
old_fk_field_name, cast("list[dict]", old_fk_fields)
|
||||||
|
)
|
||||||
if old_fk_field.get("db_constraint"):
|
if old_fk_field.get("db_constraint"):
|
||||||
ref_describe = cast(dict, old_models[old_fk_field["python_type"]])
|
ref_describe = cast(dict, old_models[old_fk_field["python_type"]])
|
||||||
sql = cls._drop_fk(model, old_fk_field, ref_describe)
|
sql = cls._drop_fk(model, old_fk_field, ref_describe)
|
||||||
@ -321,11 +339,11 @@ class Migrate:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _handle_fk_fields(
|
def _handle_fk_fields(
|
||||||
cls,
|
cls,
|
||||||
old_model_describe: Dict,
|
old_model_describe: dict,
|
||||||
new_model_describe: Dict,
|
new_model_describe: dict,
|
||||||
model: Type[Model],
|
model: type[Model],
|
||||||
old_models: Dict,
|
old_models: dict,
|
||||||
new_models: Dict,
|
new_models: dict,
|
||||||
upgrade=True,
|
upgrade=True,
|
||||||
) -> None:
|
) -> None:
|
||||||
key = "fk_fields"
|
key = "fk_fields"
|
||||||
@ -336,11 +354,11 @@ class Migrate:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _handle_o2o_fields(
|
def _handle_o2o_fields(
|
||||||
cls,
|
cls,
|
||||||
old_model_describe: Dict,
|
old_model_describe: dict,
|
||||||
new_model_describe: Dict,
|
new_model_describe: dict,
|
||||||
model: Type[Model],
|
model: type[Model],
|
||||||
old_models: Dict,
|
old_models: dict,
|
||||||
new_models: Dict,
|
new_models: dict,
|
||||||
upgrade=True,
|
upgrade=True,
|
||||||
) -> None:
|
) -> None:
|
||||||
key = "o2o_fields"
|
key = "o2o_fields"
|
||||||
@ -350,7 +368,7 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def diff_models(
|
def diff_models(
|
||||||
cls, old_models: Dict[str, dict], new_models: Dict[str, dict], upgrade=True
|
cls, old_models: dict[str, dict], new_models: dict[str, dict], upgrade=True
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
diff models and add operators
|
diff models and add operators
|
||||||
@ -362,7 +380,7 @@ class Migrate:
|
|||||||
_aerich = f"{cls.app}.{cls._aerich}"
|
_aerich = f"{cls.app}.{cls._aerich}"
|
||||||
old_models.pop(_aerich, None)
|
old_models.pop(_aerich, None)
|
||||||
new_models.pop(_aerich, None)
|
new_models.pop(_aerich, None)
|
||||||
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():
|
||||||
model = cls._get_model(new_model_describe["name"].split(".")[1])
|
model = cls._get_model(new_model_describe["name"].split(".")[1])
|
||||||
@ -383,13 +401,13 @@ class Migrate:
|
|||||||
old_unique_together = set(
|
old_unique_together = set(
|
||||||
map(
|
map(
|
||||||
lambda x: tuple(x),
|
lambda x: tuple(x),
|
||||||
cast(List[Iterable[str]], old_model_describe.get("unique_together")),
|
cast("list[Iterable[str]]", old_model_describe.get("unique_together")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
new_unique_together = set(
|
new_unique_together = set(
|
||||||
map(
|
map(
|
||||||
lambda x: tuple(x),
|
lambda x: tuple(x),
|
||||||
cast(List[Iterable[str]], new_model_describe.get("unique_together")),
|
cast("list[Iterable[str]]", new_model_describe.get("unique_together")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
old_indexes = cls._get_indexes(model, old_model_describe)
|
old_indexes = cls._get_indexes(model, old_model_describe)
|
||||||
@ -428,18 +446,18 @@ class Migrate:
|
|||||||
old_data_fields = list(
|
old_data_fields = list(
|
||||||
filter(
|
filter(
|
||||||
lambda x: x.get("db_field_types") is not None,
|
lambda x: x.get("db_field_types") is not None,
|
||||||
cast(List[dict], old_model_describe.get("data_fields")),
|
cast("list[dict]", old_model_describe.get("data_fields")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
new_data_fields = list(
|
new_data_fields = list(
|
||||||
filter(
|
filter(
|
||||||
lambda x: x.get("db_field_types") is not None,
|
lambda x: x.get("db_field_types") is not None,
|
||||||
cast(List[dict], new_model_describe.get("data_fields")),
|
cast("list[dict]", new_model_describe.get("data_fields")),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
old_data_fields_name = cast(List[str], [i.get("name") for i in old_data_fields])
|
old_data_fields_name = cast("list[str]", [i.get("name") for i in old_data_fields])
|
||||||
new_data_fields_name = cast(List[str], [i.get("name") for i in new_data_fields])
|
new_data_fields_name = cast("list[str]", [i.get("name") for i in new_data_fields])
|
||||||
|
|
||||||
# add fields or rename fields
|
# add fields or rename fields
|
||||||
for new_data_field_name in set(new_data_fields_name).difference(
|
for new_data_field_name in set(new_data_fields_name).difference(
|
||||||
@ -459,7 +477,9 @@ class Migrate:
|
|||||||
len(new_name.symmetric_difference(set(f.get("name", "")))),
|
len(new_name.symmetric_difference(set(f.get("name", "")))),
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
changes = list(diff(old_data_field, new_data_field))
|
changes = cls._exclude_extra_field_types(
|
||||||
|
diff(old_data_field, new_data_field)
|
||||||
|
)
|
||||||
old_data_field_name = cast(str, old_data_field.get("name"))
|
old_data_field_name = cast(str, old_data_field.get("name"))
|
||||||
if len(changes) == 2:
|
if len(changes) == 2:
|
||||||
# rename field
|
# rename field
|
||||||
@ -566,7 +586,7 @@ class Migrate:
|
|||||||
for field_name in set(new_data_fields_name).intersection(set(old_data_fields_name)):
|
for field_name in set(new_data_fields_name).intersection(set(old_data_fields_name)):
|
||||||
old_data_field = cls.get_field_by_name(field_name, old_data_fields)
|
old_data_field = cls.get_field_by_name(field_name, old_data_fields)
|
||||||
new_data_field = cls.get_field_by_name(field_name, new_data_fields)
|
new_data_field = cls.get_field_by_name(field_name, new_data_fields)
|
||||||
changes = diff(old_data_field, new_data_field)
|
changes = cls._exclude_extra_field_types(diff(old_data_field, new_data_field))
|
||||||
modified = False
|
modified = False
|
||||||
for change in changes:
|
for change in changes:
|
||||||
_, option, old_new = change
|
_, option, old_new = change
|
||||||
@ -622,11 +642,11 @@ class Migrate:
|
|||||||
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
|
||||||
def rename_table(cls, model: Type[Model], old_table_name: str, new_table_name: str) -> str:
|
def rename_table(cls, model: type[Model], old_table_name: str, new_table_name: str) -> str:
|
||||||
return cls.ddl.rename_table(model, old_table_name, new_table_name)
|
return cls.ddl.rename_table(model, old_table_name, new_table_name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_model(cls, model: Type[Model]) -> str:
|
def add_model(cls, model: type[Model]) -> str:
|
||||||
return cls.ddl.create_table(model)
|
return cls.ddl.create_table(model)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -635,7 +655,7 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_m2m(
|
def create_m2m(
|
||||||
cls, model: Type[Model], field_describe: dict, reference_table_describe: dict
|
cls, model: type[Model], field_describe: dict, reference_table_describe: dict
|
||||||
) -> str:
|
) -> str:
|
||||||
return cls.ddl.create_m2m(model, field_describe, reference_table_describe)
|
return cls.ddl.create_m2m(model, field_describe, reference_table_describe)
|
||||||
|
|
||||||
@ -644,7 +664,7 @@ class Migrate:
|
|||||||
return cls.ddl.drop_m2m(table_name)
|
return cls.ddl.drop_m2m(table_name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _resolve_fk_fields_name(cls, model: Type[Model], fields_name: Iterable[str]) -> List[str]:
|
def _resolve_fk_fields_name(cls, model: type[Model], fields_name: Iterable[str]) -> list[str]:
|
||||||
ret = []
|
ret = []
|
||||||
for field_name in fields_name:
|
for field_name in fields_name:
|
||||||
try:
|
try:
|
||||||
@ -662,7 +682,7 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _drop_index(
|
def _drop_index(
|
||||||
cls, model: Type[Model], fields_name: Union[Iterable[str], Index], unique=False
|
cls, model: type[Model], fields_name: Union[Iterable[str], Index], unique=False
|
||||||
) -> str:
|
) -> str:
|
||||||
if isinstance(fields_name, Index):
|
if isinstance(fields_name, Index):
|
||||||
return cls.ddl.drop_index_by_name(
|
return cls.ddl.drop_index_by_name(
|
||||||
@ -673,7 +693,7 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _add_index(
|
def _add_index(
|
||||||
cls, model: Type[Model], fields_name: Union[Iterable[str], Index], unique=False
|
cls, model: type[Model], fields_name: Union[Iterable[str], Index], unique=False
|
||||||
) -> str:
|
) -> str:
|
||||||
if isinstance(fields_name, Index):
|
if isinstance(fields_name, Index):
|
||||||
return fields_name.get_sql(cls.ddl.schema_generator, model, False)
|
return fields_name.get_sql(cls.ddl.schema_generator, model, False)
|
||||||
@ -681,42 +701,42 @@ class Migrate:
|
|||||||
return cls.ddl.add_index(model, field_names, unique)
|
return cls.ddl.add_index(model, field_names, unique)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _add_field(cls, model: Type[Model], field_describe: dict, is_pk: bool = False) -> str:
|
def _add_field(cls, model: type[Model], field_describe: dict, is_pk: bool = False) -> str:
|
||||||
return cls.ddl.add_column(model, field_describe, is_pk)
|
return cls.ddl.add_column(model, field_describe, is_pk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _alter_default(cls, model: Type[Model], field_describe: dict) -> str:
|
def _alter_default(cls, model: type[Model], field_describe: dict) -> str:
|
||||||
return cls.ddl.alter_column_default(model, field_describe)
|
return cls.ddl.alter_column_default(model, field_describe)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _alter_null(cls, model: Type[Model], field_describe: dict) -> str:
|
def _alter_null(cls, model: type[Model], field_describe: dict) -> str:
|
||||||
return cls.ddl.alter_column_null(model, field_describe)
|
return cls.ddl.alter_column_null(model, field_describe)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_comment(cls, model: Type[Model], field_describe: dict) -> str:
|
def _set_comment(cls, model: type[Model], field_describe: dict) -> str:
|
||||||
return cls.ddl.set_comment(model, field_describe)
|
return cls.ddl.set_comment(model, field_describe)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _modify_field(cls, model: Type[Model], field_describe: dict) -> str:
|
def _modify_field(cls, model: type[Model], field_describe: dict) -> str:
|
||||||
return cls.ddl.modify_column(model, field_describe)
|
return cls.ddl.modify_column(model, field_describe)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _drop_fk(
|
def _drop_fk(
|
||||||
cls, model: Type[Model], field_describe: dict, reference_table_describe: dict
|
cls, model: type[Model], field_describe: dict, reference_table_describe: dict
|
||||||
) -> str:
|
) -> str:
|
||||||
return cls.ddl.drop_fk(model, field_describe, reference_table_describe)
|
return cls.ddl.drop_fk(model, field_describe, reference_table_describe)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _remove_field(cls, model: Type[Model], column_name: str) -> str:
|
def _remove_field(cls, model: type[Model], column_name: str) -> str:
|
||||||
return cls.ddl.drop_column(model, column_name)
|
return cls.ddl.drop_column(model, column_name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rename_field(cls, model: Type[Model], old_field_name: str, new_field_name: str) -> str:
|
def _rename_field(cls, model: type[Model], old_field_name: str, new_field_name: str) -> str:
|
||||||
return cls.ddl.rename_column(model, old_field_name, new_field_name)
|
return cls.ddl.rename_column(model, old_field_name, new_field_name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _change_field(
|
def _change_field(
|
||||||
cls, model: Type[Model], old_field_describe: dict, new_field_describe: dict
|
cls, model: type[Model], old_field_describe: dict, new_field_describe: dict
|
||||||
) -> str:
|
) -> str:
|
||||||
db_field_types = cast(dict, new_field_describe.get("db_field_types"))
|
db_field_types = cast(dict, new_field_describe.get("db_field_types"))
|
||||||
return cls.ddl.change_column(
|
return cls.ddl.change_column(
|
||||||
@ -728,7 +748,7 @@ class Migrate:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _add_fk(
|
def _add_fk(
|
||||||
cls, model: Type[Model], field_describe: dict, reference_table_describe: dict
|
cls, model: type[Model], field_describe: dict, reference_table_describe: dict
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
add fk
|
add fk
|
||||||
|
@ -6,7 +6,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Dict, Generator, Optional, Union
|
from typing import Generator, Optional, Union
|
||||||
|
|
||||||
from asyncclick import BadOptionUsage, ClickException, Context
|
from asyncclick import BadOptionUsage, ClickException, Context
|
||||||
from dictdiffer import diff
|
from dictdiffer import diff
|
||||||
@ -37,20 +37,20 @@ def get_app_connection_name(config, app_name: str) -> str:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
app = config.get("apps").get(app_name)
|
app = config.get("apps").get(app_name)
|
||||||
if app:
|
if not app:
|
||||||
return app.get("default_connection", "default")
|
raise BadOptionUsage(
|
||||||
raise BadOptionUsage(
|
option_name="--app",
|
||||||
option_name="--app",
|
message=f'Can\'t get app named "{app_name}"',
|
||||||
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:
|
||||||
"""
|
"""
|
||||||
get connection name
|
get connection client
|
||||||
:param config:
|
:param config:
|
||||||
:param app:
|
:param app:
|
||||||
:return:
|
:return: client instance
|
||||||
"""
|
"""
|
||||||
return Tortoise.get_connection(get_app_connection_name(config, app))
|
return Tortoise.get_connection(get_app_connection_name(config, app))
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ def get_tortoise_config(ctx: Context, tortoise_orm: str) -> dict:
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def get_models_describe(app: str) -> Dict:
|
def get_models_describe(app: str) -> dict:
|
||||||
"""
|
"""
|
||||||
get app models describe
|
get app models describe
|
||||||
:param app:
|
:param app:
|
||||||
|
28
conftest.py
28
conftest.py
@ -1,27 +1,26 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
from typing import Generator
|
from typing import Generator
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from tortoise import Tortoise, expand_db_url, generate_schema_for_client
|
from tortoise import Tortoise, expand_db_url
|
||||||
from tortoise.backends.asyncpg.schema_generator import AsyncpgSchemaGenerator
|
from tortoise.backends.asyncpg.schema_generator import AsyncpgSchemaGenerator
|
||||||
from tortoise.backends.mysql.schema_generator import MySQLSchemaGenerator
|
from tortoise.backends.mysql.schema_generator import MySQLSchemaGenerator
|
||||||
from tortoise.backends.sqlite.schema_generator import SqliteSchemaGenerator
|
from tortoise.backends.sqlite.schema_generator import SqliteSchemaGenerator
|
||||||
from tortoise.contrib.test import MEMORY_SQLITE
|
from tortoise.contrib.test import MEMORY_SQLITE
|
||||||
from tortoise.exceptions import DBConnectionError, OperationalError
|
|
||||||
|
|
||||||
from aerich.ddl.mysql import MysqlDDL
|
from aerich.ddl.mysql import MysqlDDL
|
||||||
from aerich.ddl.postgres import PostgresDDL
|
from aerich.ddl.postgres import PostgresDDL
|
||||||
from aerich.ddl.sqlite import SqliteDDL
|
from aerich.ddl.sqlite import SqliteDDL
|
||||||
from aerich.migrate import Migrate
|
from aerich.migrate import Migrate
|
||||||
|
from tests._utils import init_db
|
||||||
|
|
||||||
db_url = os.getenv("TEST_DB", MEMORY_SQLITE)
|
db_url = os.getenv("TEST_DB", MEMORY_SQLITE)
|
||||||
db_url_second = os.getenv("TEST_DB_SECOND", MEMORY_SQLITE)
|
db_url_second = os.getenv("TEST_DB_SECOND", MEMORY_SQLITE)
|
||||||
tortoise_orm = {
|
tortoise_orm = {
|
||||||
"connections": {
|
"connections": {
|
||||||
"default": expand_db_url(db_url, True),
|
"default": expand_db_url(db_url, testing=True),
|
||||||
"second": expand_db_url(db_url_second, True),
|
"second": expand_db_url(db_url_second, testing=True),
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"},
|
"models": {"models": ["tests.models", "aerich.models"], "default_connection": "default"},
|
||||||
@ -55,24 +54,7 @@ def event_loop() -> Generator:
|
|||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
async def initialize_tests(event_loop, request) -> None:
|
async def initialize_tests(event_loop, request) -> None:
|
||||||
# Placing init outside the try block since it doesn't
|
await init_db(tortoise_orm)
|
||||||
# establish connections to the DB eagerly.
|
|
||||||
await Tortoise.init(config=tortoise_orm)
|
|
||||||
with contextlib.suppress(DBConnectionError, OperationalError):
|
|
||||||
await Tortoise._drop_databases()
|
|
||||||
await Tortoise.init(config=tortoise_orm, _create_db=True)
|
|
||||||
try:
|
|
||||||
await generate_schema_for_client(Tortoise.get_connection("default"), safe=True)
|
|
||||||
except OperationalError as e:
|
|
||||||
if (s := "IF NOT EXISTS") not in str(e):
|
|
||||||
raise e
|
|
||||||
# MySQL does not support `CREATE INDEX IF NOT EXISTS` syntax
|
|
||||||
client = Tortoise.get_connection("default")
|
|
||||||
generator = client.schema_generator(client)
|
|
||||||
schema = generator.get_create_schema_sql(safe=True)
|
|
||||||
schema = schema.replace(f" INDEX {s}", " INDEX")
|
|
||||||
await generator.generate_from_string(schema)
|
|
||||||
|
|
||||||
client = Tortoise.get_connection("default")
|
client = Tortoise.get_connection("default")
|
||||||
if client.schema_generator is MySQLSchemaGenerator:
|
if client.schema_generator is MySQLSchemaGenerator:
|
||||||
Migrate.ddl = MysqlDDL(client)
|
Migrate.ddl = MysqlDDL(client)
|
||||||
|
197
poetry.lock
generated
197
poetry.lock
generated
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiosqlite"
|
name = "aiosqlite"
|
||||||
@ -6,6 +6,7 @@ version = "0.20.0"
|
|||||||
description = "asyncio bridge to the standard sqlite3 module"
|
description = "asyncio bridge to the standard sqlite3 module"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"},
|
{file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"},
|
||||||
{file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"},
|
{file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"},
|
||||||
@ -24,6 +25,7 @@ version = "0.7.0"
|
|||||||
description = "Reusable constraint types to use with typing.Annotated"
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
@ -38,6 +40,7 @@ version = "4.5.2"
|
|||||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"},
|
{file = "anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f"},
|
||||||
{file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"},
|
{file = "anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b"},
|
||||||
@ -60,6 +63,8 @@ version = "5.0.1"
|
|||||||
description = "Timeout context manager for asyncio programs"
|
description = "Timeout context manager for asyncio programs"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "extra == \"asyncpg\" and python_version < \"3.11.0\""
|
||||||
files = [
|
files = [
|
||||||
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
||||||
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
||||||
@ -71,6 +76,7 @@ version = "8.1.7.2"
|
|||||||
description = "Composable command line interface toolkit, async version"
|
description = "Composable command line interface toolkit, async version"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "asyncclick-8.1.7.2-py3-none-any.whl", hash = "sha256:1ab940b04b22cb89b5b400725132b069d01b0c3472a9702c7a2c9d5d007ded02"},
|
{file = "asyncclick-8.1.7.2-py3-none-any.whl", hash = "sha256:1ab940b04b22cb89b5b400725132b069d01b0c3472a9702c7a2c9d5d007ded02"},
|
||||||
{file = "asyncclick-8.1.7.2.tar.gz", hash = "sha256:219ea0f29ccdc1bb4ff43bcab7ce0769ac6d48a04f997b43ec6bee99a222daa0"},
|
{file = "asyncclick-8.1.7.2.tar.gz", hash = "sha256:219ea0f29ccdc1bb4ff43bcab7ce0769ac6d48a04f997b43ec6bee99a222daa0"},
|
||||||
@ -86,6 +92,8 @@ version = "0.2.10"
|
|||||||
description = "A fast asyncio MySQL driver"
|
description = "A fast asyncio MySQL driver"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.8,<4.0"
|
python-versions = ">=3.8,<4.0"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "extra == \"asyncmy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "asyncmy-0.2.10-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:c2237c8756b8f374099bd320c53b16f7ec0cee8258f00d72eed5a2cd3d251066"},
|
{file = "asyncmy-0.2.10-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:c2237c8756b8f374099bd320c53b16f7ec0cee8258f00d72eed5a2cd3d251066"},
|
||||||
{file = "asyncmy-0.2.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6e98d4fbf7ea0d99dfecb24968c9c350b019397ba1af9f181d51bb0f6f81919b"},
|
{file = "asyncmy-0.2.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6e98d4fbf7ea0d99dfecb24968c9c350b019397ba1af9f181d51bb0f6f81919b"},
|
||||||
@ -151,6 +159,8 @@ version = "0.30.0"
|
|||||||
description = "An asyncio PostgreSQL driver"
|
description = "An asyncio PostgreSQL driver"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "extra == \"asyncpg\""
|
||||||
files = [
|
files = [
|
||||||
{file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"},
|
{file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e"},
|
||||||
{file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"},
|
{file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0"},
|
||||||
@ -217,6 +227,7 @@ version = "1.7.10"
|
|||||||
description = "Security oriented static analyser for python code."
|
description = "Security oriented static analyser for python code."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
|
{file = "bandit-1.7.10-py3-none-any.whl", hash = "sha256:665721d7bebbb4485a339c55161ac0eedde27d51e638000d91c8c2d68343ad02"},
|
||||||
{file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
|
{file = "bandit-1.7.10.tar.gz", hash = "sha256:59ed5caf5d92b6ada4bf65bc6437feea4a9da1093384445fed4d472acc6cff7b"},
|
||||||
@ -241,6 +252,7 @@ version = "24.8.0"
|
|||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
{file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"},
|
||||||
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
{file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"},
|
||||||
@ -287,6 +299,8 @@ version = "1.17.1"
|
|||||||
description = "Foreign Function Interface for Python calling C code."
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
markers = "platform_python_implementation != \"PyPy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
{file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"},
|
||||||
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
{file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"},
|
||||||
@ -366,6 +380,7 @@ version = "8.1.8"
|
|||||||
description = "Composable command line interface toolkit"
|
description = "Composable command line interface toolkit"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
||||||
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
||||||
@ -380,10 +395,12 @@ version = "0.4.6"
|
|||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
markers = {main = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
@ -391,6 +408,7 @@ version = "43.0.3"
|
|||||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
|
{file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"},
|
||||||
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
|
{file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"},
|
||||||
@ -440,6 +458,7 @@ version = "0.9.0"
|
|||||||
description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
|
description = "Dictdiffer is a library that helps you to diff and patch dictionaries."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
|
{file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"},
|
||||||
{file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
|
{file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"},
|
||||||
@ -457,6 +476,8 @@ version = "1.2.2"
|
|||||||
description = "Backport of PEP 654 (exception groups)"
|
description = "Backport of PEP 654 (exception groups)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main", "dev"]
|
||||||
|
markers = "python_version < \"3.11\""
|
||||||
files = [
|
files = [
|
||||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||||
@ -471,6 +492,7 @@ version = "2.1.1"
|
|||||||
description = "execnet: rapid multi-Python deployment"
|
description = "execnet: rapid multi-Python deployment"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||||
@ -485,6 +507,7 @@ version = "3.10"
|
|||||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||||
@ -499,6 +522,7 @@ version = "2.0.0"
|
|||||||
description = "brain-dead simple config-ini parsing"
|
description = "brain-dead simple config-ini parsing"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
|
||||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
@ -510,6 +534,7 @@ version = "2.1.0"
|
|||||||
description = "Simple module to parse ISO 8601 dates"
|
description = "Simple module to parse ISO 8601 dates"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242"},
|
{file = "iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242"},
|
||||||
{file = "iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df"},
|
{file = "iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df"},
|
||||||
@ -521,6 +546,7 @@ version = "5.13.2"
|
|||||||
description = "A Python utility / library to sort Python imports."
|
description = "A Python utility / library to sort Python imports."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
|
||||||
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
|
||||||
@ -535,6 +561,7 @@ version = "3.0.0"
|
|||||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||||
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||||
@ -559,6 +586,7 @@ version = "0.1.2"
|
|||||||
description = "Markdown URL utilities"
|
description = "Markdown URL utilities"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||||
@ -566,43 +594,50 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.14.0"
|
version = "1.14.1"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87"},
|
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
|
||||||
{file = "mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179"},
|
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
|
||||||
{file = "mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e"},
|
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
|
||||||
{file = "mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3"},
|
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
|
||||||
{file = "mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44"},
|
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
|
||||||
{file = "mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a"},
|
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
|
||||||
{file = "mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc"},
|
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
|
||||||
{file = "mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015"},
|
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
|
||||||
{file = "mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb"},
|
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
|
||||||
{file = "mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc"},
|
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
|
||||||
{file = "mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd"},
|
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
|
||||||
{file = "mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1"},
|
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
|
||||||
{file = "mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63"},
|
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
|
||||||
{file = "mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d"},
|
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
|
||||||
{file = "mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba"},
|
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
|
||||||
{file = "mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741"},
|
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
|
||||||
{file = "mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7"},
|
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
|
||||||
{file = "mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8"},
|
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
|
||||||
{file = "mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc"},
|
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
|
||||||
{file = "mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f"},
|
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
|
||||||
{file = "mypy-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b16738b1d80ec4334654e89e798eb705ac0c36c8a5c4798496cd3623aa02286"},
|
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
|
||||||
{file = "mypy-1.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:10065fcebb7c66df04b05fc799a854b1ae24d9963c8bb27e9064a9bdb43aa8ad"},
|
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
|
||||||
{file = "mypy-1.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fbb7d683fa6bdecaa106e8368aa973ecc0ddb79a9eaeb4b821591ecd07e9e03c"},
|
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
|
||||||
{file = "mypy-1.14.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3498cb55448dc5533e438cd13d6ddd28654559c8c4d1fd4b5ca57a31b81bac01"},
|
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
|
||||||
{file = "mypy-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c7b243408ea43755f3a21a0a08e5c5ae30eddb4c58a80f415ca6b118816e60aa"},
|
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
|
||||||
{file = "mypy-1.14.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:14117b9da3305b39860d0aa34b8f1ff74d209a368829a584eb77524389a9c13e"},
|
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
|
||||||
{file = "mypy-1.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af98c5a958f9c37404bd4eef2f920b94874507e146ed6ee559f185b8809c44cc"},
|
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
|
||||||
{file = "mypy-1.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b343a1d3989547024377c2ba0dca9c74a2428ad6ed24283c213af8dbb0710b"},
|
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
|
||||||
{file = "mypy-1.14.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cdb5563c1726c85fb201be383168f8c866032db95e1095600806625b3a648cb7"},
|
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
|
||||||
{file = "mypy-1.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:74e925649c1ee0a79aa7448baf2668d81cc287dc5782cff6a04ee93f40fb8d3f"},
|
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
|
||||||
{file = "mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab"},
|
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
|
||||||
{file = "mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6"},
|
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
|
||||||
|
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
|
||||||
|
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
|
||||||
|
{file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
|
||||||
|
{file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
|
||||||
|
{file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
|
||||||
|
{file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -623,6 +658,7 @@ version = "1.0.0"
|
|||||||
description = "Type system extensions for programs checked with the mypy type checker."
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||||
@ -634,6 +670,7 @@ version = "24.2"
|
|||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
@ -645,6 +682,7 @@ version = "0.12.1"
|
|||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||||
@ -656,6 +694,7 @@ version = "6.1.0"
|
|||||||
description = "Python Build Reasonableness"
|
description = "Python Build Reasonableness"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.6"
|
python-versions = ">=2.6"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"},
|
{file = "pbr-6.1.0-py2.py3-none-any.whl", hash = "sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a"},
|
||||||
{file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"},
|
{file = "pbr-6.1.0.tar.gz", hash = "sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24"},
|
||||||
@ -667,6 +706,7 @@ version = "4.3.6"
|
|||||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||||
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||||
@ -683,6 +723,7 @@ version = "1.5.0"
|
|||||||
description = "plugin and hook calling mechanisms for python"
|
description = "plugin and hook calling mechanisms for python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
|
||||||
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
|
||||||
@ -698,6 +739,8 @@ version = "2.22"
|
|||||||
description = "C parser in Python"
|
description = "C parser in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
markers = "platform_python_implementation != \"PyPy\""
|
||||||
files = [
|
files = [
|
||||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||||
@ -705,13 +748,14 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.10.4"
|
version = "2.10.6"
|
||||||
description = "Data validation using Python type hints"
|
description = "Data validation using Python type hints"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"},
|
{file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"},
|
||||||
{file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"},
|
{file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -729,6 +773,7 @@ version = "2.27.2"
|
|||||||
description = "Core functionality for Pydantic validation and serialization"
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"},
|
||||||
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
{file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"},
|
||||||
@ -837,13 +882,14 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.18.0"
|
version = "2.19.1"
|
||||||
description = "Pygments is a syntax highlighting package written in Python."
|
description = "Pygments is a syntax highlighting package written in Python."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
|
||||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -855,6 +901,7 @@ version = "0.3.2"
|
|||||||
description = "Forked from pypika and streamline just for tortoise-orm"
|
description = "Forked from pypika and streamline just for tortoise-orm"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8,<4.0"
|
python-versions = ">=3.8,<4.0"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pypika_tortoise-0.3.2-py3-none-any.whl", hash = "sha256:c5c52bc4473fe6f3db36cf659340750246ec5dd0f980d04ae7811430e299c3a2"},
|
{file = "pypika_tortoise-0.3.2-py3-none-any.whl", hash = "sha256:c5c52bc4473fe6f3db36cf659340750246ec5dd0f980d04ae7811430e299c3a2"},
|
||||||
{file = "pypika_tortoise-0.3.2.tar.gz", hash = "sha256:f5d508e2ef00255e52ec6ac79ef889e10dbab328f218c55cd134c4d02ff9f6f4"},
|
{file = "pypika_tortoise-0.3.2.tar.gz", hash = "sha256:f5d508e2ef00255e52ec6ac79ef889e10dbab328f218c55cd134c4d02ff9f6f4"},
|
||||||
@ -866,6 +913,7 @@ version = "8.3.4"
|
|||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||||
@ -888,6 +936,7 @@ version = "0.21.2"
|
|||||||
description = "Pytest support for asyncio"
|
description = "Pytest support for asyncio"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"},
|
{file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"},
|
||||||
{file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"},
|
{file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"},
|
||||||
@ -906,6 +955,7 @@ version = "3.14.0"
|
|||||||
description = "Thin-wrapper around the mock package for easier use with pytest"
|
description = "Thin-wrapper around the mock package for easier use with pytest"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
|
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
|
||||||
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
|
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
|
||||||
@ -923,6 +973,7 @@ version = "3.6.1"
|
|||||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||||
@ -939,13 +990,14 @@ testing = ["filelock"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytz"
|
name = "pytz"
|
||||||
version = "2024.2"
|
version = "2025.1"
|
||||||
description = "World timezone definitions, modern and historical"
|
description = "World timezone definitions, modern and historical"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
|
{file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"},
|
||||||
{file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
|
{file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -954,6 +1006,7 @@ version = "6.0.2"
|
|||||||
description = "YAML parser and emitter for Python"
|
description = "YAML parser and emitter for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||||
@ -1016,6 +1069,7 @@ version = "13.9.4"
|
|||||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
|
{file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
|
||||||
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
|
{file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
|
||||||
@ -1031,29 +1085,30 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.8.4"
|
version = "0.9.4"
|
||||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60"},
|
{file = "ruff-0.9.4-py3-none-linux_armv6l.whl", hash = "sha256:64e73d25b954f71ff100bb70f39f1ee09e880728efb4250c632ceed4e4cdf706"},
|
||||||
{file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac"},
|
{file = "ruff-0.9.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6ce6743ed64d9afab4fafeaea70d3631b4d4b28b592db21a5c2d1f0ef52934bf"},
|
||||||
{file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296"},
|
{file = "ruff-0.9.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:54499fb08408e32b57360f6f9de7157a5fec24ad79cb3f42ef2c3f3f728dfe2b"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37c892540108314a6f01f105040b5106aeb829fa5fb0561d2dcaf71485021137"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de9edf2ce4b9ddf43fd93e20ef635a900e25f622f87ed6e3047a664d0e8f810e"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87c90c32357c74f11deb7fbb065126d91771b207bf9bfaaee01277ca59b574ec"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56acd6c694da3695a7461cc55775f3a409c3815ac467279dfa126061d84b314b"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0c93e7d47ed951b9394cf352d6695b31498e68fd5782d6cbc282425655f687a"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d4c8772670aecf037d1bf7a07c39106574d143b26cfe5ed1787d2f31e800214"},
|
||||||
{file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720"},
|
{file = "ruff-0.9.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfc5f1d7afeda8d5d37660eeca6d389b142d7f2b5a1ab659d9214ebd0e025231"},
|
||||||
{file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae"},
|
{file = "ruff-0.9.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faa935fc00ae854d8b638c16a5f1ce881bc3f67446957dd6f2af440a5fc8526b"},
|
||||||
{file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7"},
|
{file = "ruff-0.9.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6c634fc6f5a0ceae1ab3e13c58183978185d131a29c425e4eaa9f40afe1e6d6"},
|
||||||
{file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111"},
|
{file = "ruff-0.9.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:433dedf6ddfdec7f1ac7575ec1eb9844fa60c4c8c2f8887a070672b8d353d34c"},
|
||||||
{file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8"},
|
{file = "ruff-0.9.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d612dbd0f3a919a8cc1d12037168bfa536862066808960e0cc901404b77968f0"},
|
||||||
{file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835"},
|
{file = "ruff-0.9.4-py3-none-win32.whl", hash = "sha256:db1192ddda2200671f9ef61d9597fcef89d934f5d1705e571a93a67fb13a4402"},
|
||||||
{file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d"},
|
{file = "ruff-0.9.4-py3-none-win_amd64.whl", hash = "sha256:05bebf4cdbe3ef75430d26c375773978950bbf4ee3c95ccb5448940dc092408e"},
|
||||||
{file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08"},
|
{file = "ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41"},
|
||||||
{file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8"},
|
{file = "ruff-0.9.4.tar.gz", hash = "sha256:6907ee3529244bb0ed066683e075f09285b38dd5b4039370df6ff06041ca19e7"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1062,6 +1117,7 @@ version = "1.3.1"
|
|||||||
description = "Sniff out which async library your code is running under"
|
description = "Sniff out which async library your code is running under"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
|
||||||
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
|
||||||
@ -1073,6 +1129,7 @@ version = "5.3.0"
|
|||||||
description = "Manage dynamic plugins for Python applications"
|
description = "Manage dynamic plugins for Python applications"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"},
|
{file = "stevedore-5.3.0-py3-none-any.whl", hash = "sha256:1efd34ca08f474dad08d9b19e934a22c68bb6fe416926479ba29e5013bcc8f78"},
|
||||||
{file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"},
|
{file = "stevedore-5.3.0.tar.gz", hash = "sha256:9a64265f4060312828151c204efbe9b7a9852a0d9228756344dbc7e4023e375a"},
|
||||||
@ -1087,6 +1144,8 @@ version = "2.2.1"
|
|||||||
description = "A lil' TOML parser"
|
description = "A lil' TOML parser"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["dev"]
|
||||||
|
markers = "python_version < \"3.11\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||||
@ -1124,13 +1183,15 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli-w"
|
name = "tomli-w"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
description = "A lil' TOML writer"
|
description = "A lil' TOML writer"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version >= \"3.11\" and extra == \"toml\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli_w-1.1.0-py3-none-any.whl", hash = "sha256:1403179c78193e3184bfaade390ddbd071cba48a32a2e62ba11aae47490c63f7"},
|
{file = "tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90"},
|
||||||
{file = "tomli_w-1.1.0.tar.gz", hash = "sha256:49e847a3a304d516a169a601184932ef0f6b61623fe680f836a2aa7128ed0d33"},
|
{file = "tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1139,6 +1200,8 @@ version = "0.13.2"
|
|||||||
description = "Style preserving TOML library"
|
description = "Style preserving TOML library"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version < \"3.11\" and extra == \"toml\""
|
||||||
files = [
|
files = [
|
||||||
{file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
|
{file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"},
|
||||||
{file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
|
{file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"},
|
||||||
@ -1150,6 +1213,7 @@ version = "0.23.0"
|
|||||||
description = "Easy async ORM for python, built with relations in mind"
|
description = "Easy async ORM for python, built with relations in mind"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8,<4.0"
|
python-versions = ">=3.8,<4.0"
|
||||||
|
groups = ["main"]
|
||||||
files = [
|
files = [
|
||||||
{file = "tortoise_orm-0.23.0-py3-none-any.whl", hash = "sha256:deaabed1619ea8aab6213508dff025571a701b7f34ee534473d7bb7661aa9f4f"},
|
{file = "tortoise_orm-0.23.0-py3-none-any.whl", hash = "sha256:deaabed1619ea8aab6213508dff025571a701b7f34ee534473d7bb7661aa9f4f"},
|
||||||
{file = "tortoise_orm-0.23.0.tar.gz", hash = "sha256:f25d431ef4fb521a84edad582f4b9c53dccc5abf6cfbc6f228cbece5a13952fa"},
|
{file = "tortoise_orm-0.23.0.tar.gz", hash = "sha256:f25d431ef4fb521a84edad582f4b9c53dccc5abf6cfbc6f228cbece5a13952fa"},
|
||||||
@ -1175,6 +1239,7 @@ version = "4.12.2"
|
|||||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main", "dev"]
|
||||||
files = [
|
files = [
|
||||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||||
@ -1186,6 +1251,6 @@ asyncpg = ["asyncpg"]
|
|||||||
toml = ["tomli-w", "tomlkit"]
|
toml = ["tomli-w", "tomlkit"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.8"
|
python-versions = "^3.8"
|
||||||
content-hash = "5a17cf1dd79829b76fc2c71cbd83032d70ada4f129cf56973c417eac91a975f6"
|
content-hash = "5a17cf1dd79829b76fc2c71cbd83032d70ada4f129cf56973c417eac91a975f6"
|
||||||
|
@ -59,7 +59,7 @@ aerich = "aerich.cli:main"
|
|||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
|
target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313']
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = 'auto'
|
asyncio_mode = 'auto'
|
||||||
|
46
tests/_utils.py
Normal file
46
tests/_utils.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tortoise import Tortoise, generate_schema_for_client
|
||||||
|
from tortoise.exceptions import DBConnectionError, OperationalError
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
from contextlib import chdir
|
||||||
|
else:
|
||||||
|
|
||||||
|
class chdir(contextlib.AbstractContextManager): # Copied from source code of Python3.13
|
||||||
|
"""Non thread-safe context manager to change the current working directory."""
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
self._old_cwd = []
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._old_cwd.append(os.getcwd())
|
||||||
|
os.chdir(self.path)
|
||||||
|
|
||||||
|
def __exit__(self, *excinfo):
|
||||||
|
os.chdir(self._old_cwd.pop())
|
||||||
|
|
||||||
|
|
||||||
|
async def drop_db(tortoise_orm) -> None:
|
||||||
|
# Placing init outside the try-block(suppress) since it doesn't
|
||||||
|
# establish connections to the DB eagerly.
|
||||||
|
await Tortoise.init(config=tortoise_orm)
|
||||||
|
with contextlib.suppress(DBConnectionError, OperationalError):
|
||||||
|
await Tortoise._drop_databases()
|
||||||
|
|
||||||
|
|
||||||
|
async def init_db(tortoise_orm, generate_schemas=True) -> None:
|
||||||
|
await drop_db(tortoise_orm)
|
||||||
|
await Tortoise.init(config=tortoise_orm, _create_db=True)
|
||||||
|
if generate_schemas:
|
||||||
|
await generate_schema_for_client(Tortoise.get_connection("default"), safe=True)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_files(*src_files: Path, target_dir: Path) -> None:
|
||||||
|
for src in src_files:
|
||||||
|
shutil.copy(src, target_dir)
|
72
tests/assets/fake/_tests.py
Normal file
72
tests/assets/fake/_tests.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import pytest
|
||||||
|
from models import NewModel
|
||||||
|
from models_second import Config
|
||||||
|
from settings import TORTOISE_ORM
|
||||||
|
from tortoise import Tortoise
|
||||||
|
from tortoise.exceptions import OperationalError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def anyio_backend() -> str:
|
||||||
|
return "asyncio"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def init_connections():
|
||||||
|
await Tortoise.init(TORTOISE_ORM)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
await Tortoise.close_connections()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_init_db():
|
||||||
|
m1 = await NewModel.filter(name="")
|
||||||
|
assert isinstance(m1, list)
|
||||||
|
m2 = await Config.filter(key="")
|
||||||
|
assert isinstance(m2, list)
|
||||||
|
await NewModel.create(name="")
|
||||||
|
await Config.create(key="", label="", value={})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_fake_field_1():
|
||||||
|
assert "field_1" in NewModel._meta.fields_map
|
||||||
|
assert "field_1" in Config._meta.fields_map
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await NewModel.create(name="", field_1=1)
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await Config.create(key="", label="", value={}, field_1=1)
|
||||||
|
|
||||||
|
obj1 = NewModel(name="", field_1=1)
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await obj1.save()
|
||||||
|
obj1 = NewModel(name="")
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await obj1.save()
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
obj1 = await NewModel.first()
|
||||||
|
obj1 = await NewModel.all().first().values("id", "name")
|
||||||
|
assert obj1 and obj1["id"]
|
||||||
|
|
||||||
|
obj2 = Config(key="", label="", value={}, field_1=1)
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await obj2.save()
|
||||||
|
obj2 = Config(key="", label="", value={})
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await obj2.save()
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
obj2 = await Config.first()
|
||||||
|
obj2 = await Config.all().first().values("id", "key")
|
||||||
|
assert obj2 and obj2["id"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_fake_field_2():
|
||||||
|
assert "field_2" in NewModel._meta.fields_map
|
||||||
|
assert "field_2" in Config._meta.fields_map
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await NewModel.create(name="")
|
||||||
|
with pytest.raises(OperationalError):
|
||||||
|
await Config.create(key="", label="", value={})
|
28
tests/assets/fake/db.py
Normal file
28
tests/assets/fake/db.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import asyncclick as click
|
||||||
|
from settings import TORTOISE_ORM
|
||||||
|
|
||||||
|
from tests._utils import drop_db, init_db
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
def cli(): ...
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
async def create():
|
||||||
|
await init_db(TORTOISE_ORM, False)
|
||||||
|
click.echo(f"Success to create databases for {TORTOISE_ORM['connections']}")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
async def drop():
|
||||||
|
await drop_db(TORTOISE_ORM)
|
||||||
|
click.echo(f"Dropped databases for {TORTOISE_ORM['connections']}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cli()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
22
tests/assets/fake/settings.py
Normal file
22
tests/assets/fake/settings.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from tortoise.contrib.test import MEMORY_SQLITE
|
||||||
|
|
||||||
|
DB_URL = (
|
||||||
|
_u.replace("\\{\\}", f"aerich_fake_{date.today():%Y%m%d}")
|
||||||
|
if (_u := os.getenv("TEST_DB"))
|
||||||
|
else MEMORY_SQLITE
|
||||||
|
)
|
||||||
|
DB_URL_SECOND = (DB_URL + "_second") if DB_URL != MEMORY_SQLITE else MEMORY_SQLITE
|
||||||
|
|
||||||
|
TORTOISE_ORM = {
|
||||||
|
"connections": {
|
||||||
|
"default": DB_URL.replace(MEMORY_SQLITE, "sqlite://db.sqlite3"),
|
||||||
|
"second": DB_URL_SECOND.replace(MEMORY_SQLITE, "sqlite://db_second.sqlite3"),
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"models": {"models": ["models", "aerich.models"], "default_connection": "default"},
|
||||||
|
"models_second": {"models": ["models_second"], "default_connection": "second"},
|
||||||
|
},
|
||||||
|
}
|
75
tests/assets/sqlite_migrate/_tests.py
Normal file
75
tests/assets/sqlite_migrate/_tests.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from models import Foo
|
||||||
|
from tortoise.exceptions import IntegrityError
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_allow_duplicate() -> None:
|
||||||
|
await Foo.all().delete()
|
||||||
|
await Foo.create(name="foo")
|
||||||
|
obj = await Foo.create(name="foo")
|
||||||
|
assert (await Foo.all().count()) == 2
|
||||||
|
await obj.delete()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_unique_is_true() -> None:
|
||||||
|
with pytest.raises(IntegrityError):
|
||||||
|
await Foo.create(name="foo")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_unique_field() -> None:
|
||||||
|
if not await Foo.filter(age=0).exists():
|
||||||
|
await Foo.create(name="0_" + uuid.uuid4().hex, age=0)
|
||||||
|
with pytest.raises(IntegrityError):
|
||||||
|
await Foo.create(name=uuid.uuid4().hex, age=0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_drop_unique_field() -> None:
|
||||||
|
name = "1_" + uuid.uuid4().hex
|
||||||
|
await Foo.create(name=name, age=0)
|
||||||
|
assert await Foo.filter(name=name).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_with_age_field() -> None:
|
||||||
|
name = "2_" + uuid.uuid4().hex
|
||||||
|
await Foo.create(name=name, age=0)
|
||||||
|
obj = await Foo.get(name=name)
|
||||||
|
assert obj.age == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_without_age_field() -> None:
|
||||||
|
name = "3_" + uuid.uuid4().hex
|
||||||
|
await Foo.create(name=name, age=0)
|
||||||
|
obj = await Foo.get(name=name)
|
||||||
|
assert getattr(obj, "age", None) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_m2m_with_custom_through() -> None:
|
||||||
|
from models import FooGroup, Group
|
||||||
|
|
||||||
|
name = "4_" + uuid.uuid4().hex
|
||||||
|
foo = await Foo.create(name=name)
|
||||||
|
group = await Group.create(name=name + "1")
|
||||||
|
await FooGroup.all().delete()
|
||||||
|
await foo.groups.add(group)
|
||||||
|
foo_group = await FooGroup.get(foo=foo, group=group)
|
||||||
|
assert not foo_group.is_active
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_m2m_field_after_init_db() -> None:
|
||||||
|
from models import Group
|
||||||
|
|
||||||
|
name = "5_" + uuid.uuid4().hex
|
||||||
|
foo = await Foo.create(name=name)
|
||||||
|
group = await Group.create(name=name + "1")
|
||||||
|
await foo.groups.add(group)
|
||||||
|
assert (await group.users.all().first()) == foo
|
26
tests/assets/sqlite_migrate/conftest_.py
Normal file
26
tests/assets/sqlite_migrate/conftest_.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
import settings
|
||||||
|
from tortoise import Tortoise, connections
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def event_loop() -> Generator:
|
||||||
|
policy = asyncio.get_event_loop_policy()
|
||||||
|
res = policy.new_event_loop()
|
||||||
|
asyncio.set_event_loop(res)
|
||||||
|
res._close = res.close # type:ignore[attr-defined]
|
||||||
|
res.close = lambda: None # type:ignore[method-assign]
|
||||||
|
|
||||||
|
yield res
|
||||||
|
|
||||||
|
res._close() # type:ignore[attr-defined]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture(scope="session", autouse=True)
|
||||||
|
async def api(event_loop, request):
|
||||||
|
await Tortoise.init(config=settings.TORTOISE_ORM)
|
||||||
|
request.addfinalizer(lambda: event_loop.run_until_complete(connections.close_all(discard=True)))
|
5
tests/assets/sqlite_migrate/models.py
Normal file
5
tests/assets/sqlite_migrate/models.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from tortoise import Model, fields
|
||||||
|
|
||||||
|
|
||||||
|
class Foo(Model):
|
||||||
|
name = fields.CharField(max_length=60, db_index=False)
|
4
tests/assets/sqlite_migrate/settings.py
Normal file
4
tests/assets/sqlite_migrate/settings.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
TORTOISE_ORM = {
|
||||||
|
"connections": {"default": "sqlite://db.sqlite3"},
|
||||||
|
"apps": {"models": {"models": ["models", "aerich.models"]}},
|
||||||
|
}
|
@ -52,6 +52,7 @@ class Category(Model):
|
|||||||
user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
|
user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
|
||||||
"models.User", description="User"
|
"models.User", description="User"
|
||||||
)
|
)
|
||||||
|
title = fields.CharField(max_length=20, unique=True)
|
||||||
created_at = fields.DatetimeField(auto_now_add=True)
|
created_at = fields.DatetimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
|
||||||
|
149
tests/test_fake.py
Normal file
149
tests/test_fake.py
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aerich.ddl.sqlite import SqliteDDL
|
||||||
|
from aerich.migrate import Migrate
|
||||||
|
from tests._utils import chdir, copy_files
|
||||||
|
|
||||||
|
|
||||||
|
def run_shell(command: str, capture_output=True, **kw) -> str:
|
||||||
|
r = subprocess.run(shlex.split(command), capture_output=capture_output)
|
||||||
|
if r.returncode != 0 and r.stderr:
|
||||||
|
return r.stderr.decode()
|
||||||
|
if not r.stdout:
|
||||||
|
return ""
|
||||||
|
return r.stdout.decode()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_aerich_project(tmp_path: Path):
|
||||||
|
test_dir = Path(__file__).parent
|
||||||
|
asset_dir = test_dir / "assets" / "fake"
|
||||||
|
settings_py = asset_dir / "settings.py"
|
||||||
|
_tests_py = asset_dir / "_tests.py"
|
||||||
|
db_py = asset_dir / "db.py"
|
||||||
|
models_py = test_dir / "models.py"
|
||||||
|
models_second_py = test_dir / "models_second.py"
|
||||||
|
copy_files(settings_py, _tests_py, models_py, models_second_py, db_py, target_dir=tmp_path)
|
||||||
|
dst_dir = tmp_path / "tests"
|
||||||
|
dst_dir.mkdir()
|
||||||
|
dst_dir.joinpath("__init__.py").touch()
|
||||||
|
copy_files(test_dir / "_utils.py", test_dir / "indexes.py", target_dir=dst_dir)
|
||||||
|
if should_remove := str(tmp_path) not in sys.path:
|
||||||
|
sys.path.append(str(tmp_path))
|
||||||
|
with chdir(tmp_path):
|
||||||
|
run_shell("python db.py create", capture_output=False)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if not os.getenv("AERICH_DONT_DROP_FAKE_DB"):
|
||||||
|
run_shell("python db.py drop", capture_output=False)
|
||||||
|
if should_remove:
|
||||||
|
sys.path.remove(str(tmp_path))
|
||||||
|
|
||||||
|
|
||||||
|
def _append_field(*files: str, name="field_1") -> None:
|
||||||
|
for file in files:
|
||||||
|
p = Path(file)
|
||||||
|
field = f" {name} = fields.IntField(default=0)"
|
||||||
|
with p.open("a") as f:
|
||||||
|
f.write(os.linesep + field)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fake(new_aerich_project):
|
||||||
|
if (ddl := getattr(Migrate, "ddl", None)) and isinstance(ddl, SqliteDDL):
|
||||||
|
# TODO: go ahead if sqlite alter-column supported
|
||||||
|
return
|
||||||
|
output = run_shell("aerich init -t settings.TORTOISE_ORM")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich init-db")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich --app models_second init-db")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("pytest _tests.py::test_init_db")
|
||||||
|
assert "error" not in output.lower()
|
||||||
|
_append_field("models.py", "models_second.py")
|
||||||
|
output = run_shell("aerich migrate")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich --app models_second migrate")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("pytest _tests.py::test_fake_field_1")
|
||||||
|
assert "error" not in output.lower()
|
||||||
|
_append_field("models.py", "models_second.py", name="field_2")
|
||||||
|
output = run_shell("aerich migrate")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich --app models_second migrate")
|
||||||
|
assert "Success" in output
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "_update.py" in output
|
||||||
|
output = run_shell("aerich upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("pytest _tests.py::test_fake_field_2")
|
||||||
|
assert "error" not in output.lower()
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "No available heads." in output
|
||||||
|
output = run_shell("aerich --app models_second heads")
|
||||||
|
assert "No available heads." in output
|
||||||
|
_append_field("models.py", "models_second.py", name="field_3")
|
||||||
|
run_shell("aerich migrate", capture_output=False)
|
||||||
|
run_shell("aerich --app models_second migrate", capture_output=False)
|
||||||
|
run_shell("aerich upgrade --fake", capture_output=False)
|
||||||
|
run_shell("aerich --app models_second upgrade --fake", capture_output=False)
|
||||||
|
output = run_shell("aerich downgrade --fake -v 2 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second downgrade --fake -v 2 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert not re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
||||||
|
output = run_shell("aerich --app models_second heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert not re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
||||||
|
output = run_shell("aerich downgrade --fake -v 1 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second downgrade --fake -v 1 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
||||||
|
output = run_shell("aerich --app models_second heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
||||||
|
output = run_shell("aerich upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second upgrade --fake")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "No available heads." in output
|
||||||
|
output = run_shell("aerich --app models_second heads")
|
||||||
|
assert "No available heads." in output
|
||||||
|
output = run_shell("aerich downgrade --fake -v 1 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich --app models_second downgrade --fake -v 1 --yes", input="y\n")
|
||||||
|
assert "FAKED" in output
|
||||||
|
output = run_shell("aerich heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
||||||
|
output = run_shell("aerich --app models_second heads")
|
||||||
|
assert "No available heads." not in output
|
||||||
|
assert re.search(r"1_\d+_update\.py", output)
|
||||||
|
assert re.search(r"2_\d+_update\.py", output)
|
@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -13,6 +15,14 @@ from aerich.migrate import MIGRATE_TEMPLATE, Migrate
|
|||||||
from aerich.utils import get_models_describe
|
from aerich.utils import get_models_describe
|
||||||
from tests.indexes import CustomIndex
|
from tests.indexes import CustomIndex
|
||||||
|
|
||||||
|
|
||||||
|
def describe_index(idx: Index) -> Index | dict:
|
||||||
|
# tortoise-orm>=0.24 changes Index desribe to be dict
|
||||||
|
if tortoise.__version__ < "0.24":
|
||||||
|
return idx
|
||||||
|
return idx.describe() # type:ignore
|
||||||
|
|
||||||
|
|
||||||
# tortoise-orm>=0.21 changes IntField constraints
|
# tortoise-orm>=0.21 changes IntField constraints
|
||||||
# from {"ge": 1, "le": 2147483647} to {"ge": -2147483648, "le": 2147483647}
|
# from {"ge": 1, "le": 2147483647} to {"ge": -2147483648, "le": 2147483647}
|
||||||
MIN_INT = 1 if tortoise.__version__ < "0.21" else -2147483648
|
MIN_INT = 1 if tortoise.__version__ < "0.21" else -2147483648
|
||||||
@ -640,7 +650,10 @@ old_models_describe = {
|
|||||||
"description": None,
|
"description": None,
|
||||||
"docstring": None,
|
"docstring": None,
|
||||||
"unique_together": [],
|
"unique_together": [],
|
||||||
"indexes": [Index(fields=("username", "is_active")), CustomIndex(fields=("is_superuser",))],
|
"indexes": [
|
||||||
|
describe_index(Index(fields=("username", "is_active"))),
|
||||||
|
describe_index(CustomIndex(fields=("is_superuser",))),
|
||||||
|
],
|
||||||
"pk_field": {
|
"pk_field": {
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"field_type": "IntField",
|
"field_type": "IntField",
|
||||||
@ -958,7 +971,6 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
"ALTER TABLE `config` ADD `user_id` INT NOT NULL COMMENT 'User'",
|
"ALTER TABLE `config` ADD `user_id` INT NOT NULL COMMENT 'User'",
|
||||||
"ALTER TABLE `config` ADD CONSTRAINT `fk_config_user_17daa970` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE",
|
"ALTER TABLE `config` ADD CONSTRAINT `fk_config_user_17daa970` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE",
|
||||||
"ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT",
|
"ALTER TABLE `config` ALTER COLUMN `status` DROP DEFAULT",
|
||||||
"ALTER TABLE `config` MODIFY COLUMN `value` JSON NOT NULL",
|
|
||||||
"ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL",
|
"ALTER TABLE `email` ADD `address` VARCHAR(200) NOT NULL",
|
||||||
"ALTER TABLE `email` ADD CONSTRAINT `fk_email_config_76a9dc71` FOREIGN KEY (`config_id`) REFERENCES `config` (`id`) ON DELETE CASCADE",
|
"ALTER TABLE `email` ADD CONSTRAINT `fk_email_config_76a9dc71` FOREIGN KEY (`config_id`) REFERENCES `config` (`id`) ON DELETE CASCADE",
|
||||||
"ALTER TABLE `email` ADD `config_id` INT NOT NULL UNIQUE",
|
"ALTER TABLE `email` ADD `config_id` INT NOT NULL UNIQUE",
|
||||||
@ -971,23 +983,24 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
"ALTER TABLE `email` ADD INDEX `idx_email_email_4a1a33` (`email`)",
|
"ALTER TABLE `email` ADD INDEX `idx_email_email_4a1a33` (`email`)",
|
||||||
"ALTER TABLE `product` ADD UNIQUE INDEX `uid_product_name_869427` (`name`, `type_db_alias`)",
|
"ALTER TABLE `product` ADD UNIQUE INDEX `uid_product_name_869427` (`name`, `type_db_alias`)",
|
||||||
"ALTER TABLE `product` ALTER COLUMN `view_num` SET DEFAULT 0",
|
"ALTER TABLE `product` ALTER COLUMN `view_num` SET DEFAULT 0",
|
||||||
"ALTER TABLE `product` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)",
|
|
||||||
"ALTER TABLE `product` RENAME COLUMN `is_delete` TO `is_deleted`",
|
"ALTER TABLE `product` RENAME COLUMN `is_delete` TO `is_deleted`",
|
||||||
"ALTER TABLE `product` RENAME COLUMN `is_review` TO `is_reviewed`",
|
"ALTER TABLE `product` RENAME COLUMN `is_review` TO `is_reviewed`",
|
||||||
"ALTER TABLE `user` DROP COLUMN `avatar`",
|
"ALTER TABLE `user` DROP COLUMN `avatar`",
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `password` VARCHAR(100) NOT NULL",
|
"ALTER TABLE `user` MODIFY COLUMN `password` VARCHAR(100) NOT NULL",
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `intro` LONGTEXT NOT NULL",
|
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `last_login` DATETIME(6) NOT NULL COMMENT 'Last Login'",
|
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `longitude` DECIMAL(10,8) NOT NULL",
|
"ALTER TABLE `user` MODIFY COLUMN `longitude` DECIMAL(10,8) NOT NULL",
|
||||||
"ALTER TABLE `user` ADD UNIQUE INDEX `username` (`username`)",
|
"ALTER TABLE `user` ADD UNIQUE INDEX `username` (`username`)",
|
||||||
"CREATE TABLE `email_user` (\n `email_id` INT NOT NULL REFERENCES `email` (`email_id`) ON DELETE CASCADE,\n `user_id` INT NOT NULL REFERENCES `user` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
"CREATE TABLE `email_user` (\n `email_id` INT NOT NULL REFERENCES `email` (`email_id`) ON DELETE CASCADE,\n `user_id` INT NOT NULL REFERENCES `user` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
||||||
"CREATE TABLE IF NOT EXISTS `newmodel` (\n `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n `name` VARCHAR(50) NOT NULL\n) CHARACTER SET utf8mb4",
|
"CREATE TABLE IF NOT EXISTS `newmodel` (\n `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,\n `name` VARCHAR(50) NOT NULL\n) CHARACTER SET utf8mb4",
|
||||||
"ALTER TABLE `category` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)",
|
|
||||||
"ALTER TABLE `product` MODIFY COLUMN `body` LONGTEXT NOT NULL",
|
|
||||||
"CREATE TABLE `product_user` (\n `product_id` INT NOT NULL REFERENCES `product` (`id`) ON DELETE CASCADE,\n `user_id` INT NOT NULL REFERENCES `user` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
"CREATE TABLE `product_user` (\n `product_id` INT NOT NULL REFERENCES `product` (`id`) ON DELETE CASCADE,\n `user_id` INT NOT NULL REFERENCES `user` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
||||||
"CREATE TABLE `config_category_map` (\n `category_id` INT NOT NULL REFERENCES `category` (`id`) ON DELETE CASCADE,\n `config_id` INT NOT NULL REFERENCES `config` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
"CREATE TABLE `config_category_map` (\n `category_id` INT NOT NULL REFERENCES `category` (`id`) ON DELETE CASCADE,\n `config_id` INT NOT NULL REFERENCES `config` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
||||||
"DROP TABLE IF EXISTS `config_category`",
|
"DROP TABLE IF EXISTS `config_category`",
|
||||||
}
|
}
|
||||||
|
upgrade_operators = set(Migrate.upgrade_operators)
|
||||||
|
upgrade_more_than_expected = upgrade_operators - expected_upgrade_operators
|
||||||
|
assert not upgrade_more_than_expected
|
||||||
|
upgrade_less_than_expected = expected_upgrade_operators - upgrade_operators
|
||||||
|
assert not upgrade_less_than_expected
|
||||||
|
|
||||||
expected_downgrade_operators = {
|
expected_downgrade_operators = {
|
||||||
"ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL",
|
"ALTER TABLE `category` MODIFY COLUMN `name` VARCHAR(200) NOT NULL",
|
||||||
"ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(200) NOT NULL",
|
"ALTER TABLE `category` MODIFY COLUMN `slug` VARCHAR(200) NOT NULL",
|
||||||
@ -1020,28 +1033,21 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
"DROP TABLE IF EXISTS `email_user`",
|
"DROP TABLE IF EXISTS `email_user`",
|
||||||
"DROP TABLE IF EXISTS `newmodel`",
|
"DROP TABLE IF EXISTS `newmodel`",
|
||||||
"DROP TABLE IF EXISTS `product_user`",
|
"DROP TABLE IF EXISTS `product_user`",
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `intro` LONGTEXT NOT NULL",
|
|
||||||
"ALTER TABLE `config` MODIFY COLUMN `value` TEXT NOT NULL",
|
|
||||||
"ALTER TABLE `category` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)",
|
|
||||||
"ALTER TABLE `product` MODIFY COLUMN `created_at` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)",
|
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `last_login` DATETIME(6) NOT NULL COMMENT 'Last Login'",
|
|
||||||
"ALTER TABLE `user` MODIFY COLUMN `longitude` DECIMAL(12,9) NOT NULL",
|
"ALTER TABLE `user` MODIFY COLUMN `longitude` DECIMAL(12,9) NOT NULL",
|
||||||
"ALTER TABLE `product` MODIFY COLUMN `body` LONGTEXT NOT NULL",
|
|
||||||
"CREATE TABLE `config_category` (\n `config_id` INT NOT NULL REFERENCES `config` (`id`) ON DELETE CASCADE,\n `category_id` INT NOT NULL REFERENCES `category` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
"CREATE TABLE `config_category` (\n `config_id` INT NOT NULL REFERENCES `config` (`id`) ON DELETE CASCADE,\n `category_id` INT NOT NULL REFERENCES `category` (`id`) ON DELETE CASCADE\n) CHARACTER SET utf8mb4",
|
||||||
"DROP TABLE IF EXISTS `config_category_map`",
|
"DROP TABLE IF EXISTS `config_category_map`",
|
||||||
}
|
}
|
||||||
assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators)
|
downgrade_operators = set(Migrate.downgrade_operators)
|
||||||
|
downgrade_more_than_expected = downgrade_operators - expected_downgrade_operators
|
||||||
assert not set(Migrate.downgrade_operators).symmetric_difference(
|
assert not downgrade_more_than_expected
|
||||||
expected_downgrade_operators
|
downgrade_less_than_expected = expected_downgrade_operators - downgrade_operators
|
||||||
)
|
assert not downgrade_less_than_expected
|
||||||
|
|
||||||
elif isinstance(Migrate.ddl, PostgresDDL):
|
elif isinstance(Migrate.ddl, PostgresDDL):
|
||||||
expected_upgrade_operators = {
|
expected_upgrade_operators = {
|
||||||
'DROP INDEX IF EXISTS "uid_category_title_f7fc03"',
|
'DROP INDEX IF EXISTS "uid_category_title_f7fc03"',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "name" DROP NOT NULL',
|
'ALTER TABLE "category" ALTER COLUMN "name" DROP NOT NULL',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(100) USING "slug"::VARCHAR(100)',
|
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(100) USING "slug"::VARCHAR(100)',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "category" RENAME COLUMN "user_id" TO "owner_id"',
|
'ALTER TABLE "category" RENAME COLUMN "user_id" TO "owner_id"',
|
||||||
'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_110d4c63" FOREIGN KEY ("owner_id") REFERENCES "user" ("id") ON DELETE CASCADE',
|
'ALTER TABLE "category" ADD CONSTRAINT "fk_category_user_110d4c63" FOREIGN KEY ("owner_id") REFERENCES "user" ("id") ON DELETE CASCADE',
|
||||||
'ALTER TABLE "config" DROP COLUMN "name"',
|
'ALTER TABLE "config" DROP COLUMN "name"',
|
||||||
@ -1049,7 +1055,6 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "config" ADD "user_id" INT NOT NULL',
|
'ALTER TABLE "config" ADD "user_id" INT NOT NULL',
|
||||||
'ALTER TABLE "config" ADD CONSTRAINT "fk_config_user_17daa970" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE',
|
'ALTER TABLE "config" ADD CONSTRAINT "fk_config_user_17daa970" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE',
|
||||||
'ALTER TABLE "config" ALTER COLUMN "status" DROP DEFAULT',
|
'ALTER TABLE "config" ALTER COLUMN "status" DROP DEFAULT',
|
||||||
'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB',
|
|
||||||
'ALTER TABLE "configs" RENAME TO "config"',
|
'ALTER TABLE "configs" RENAME TO "config"',
|
||||||
'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL',
|
'ALTER TABLE "email" ADD "address" VARCHAR(200) NOT NULL',
|
||||||
'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"',
|
'ALTER TABLE "email" RENAME COLUMN "id" TO "email_id"',
|
||||||
@ -1060,14 +1065,10 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "product" DROP COLUMN "uuid"',
|
'ALTER TABLE "product" DROP COLUMN "uuid"',
|
||||||
'ALTER TABLE "product" ALTER COLUMN "view_num" SET DEFAULT 0',
|
'ALTER TABLE "product" ALTER COLUMN "view_num" SET DEFAULT 0',
|
||||||
'ALTER TABLE "product" RENAME COLUMN "image" TO "pic"',
|
'ALTER TABLE "product" RENAME COLUMN "image" TO "pic"',
|
||||||
'ALTER TABLE "product" ALTER COLUMN "body" TYPE TEXT USING "body"::TEXT',
|
|
||||||
'ALTER TABLE "product" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "product" RENAME COLUMN "is_review" TO "is_reviewed"',
|
'ALTER TABLE "product" RENAME COLUMN "is_review" TO "is_reviewed"',
|
||||||
'ALTER TABLE "product" RENAME COLUMN "is_delete" TO "is_deleted"',
|
'ALTER TABLE "product" RENAME COLUMN "is_delete" TO "is_deleted"',
|
||||||
'ALTER TABLE "user" ALTER COLUMN "password" TYPE VARCHAR(100) USING "password"::VARCHAR(100)',
|
'ALTER TABLE "user" ALTER COLUMN "password" TYPE VARCHAR(100) USING "password"::VARCHAR(100)',
|
||||||
'ALTER TABLE "user" DROP COLUMN "avatar"',
|
'ALTER TABLE "user" DROP COLUMN "avatar"',
|
||||||
'ALTER TABLE "user" ALTER COLUMN "last_login" TYPE TIMESTAMPTZ USING "last_login"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "user" ALTER COLUMN "intro" TYPE TEXT USING "intro"::TEXT',
|
|
||||||
'ALTER TABLE "user" ALTER COLUMN "longitude" TYPE DECIMAL(10,8) USING "longitude"::DECIMAL(10,8)',
|
'ALTER TABLE "user" ALTER COLUMN "longitude" TYPE DECIMAL(10,8) USING "longitude"::DECIMAL(10,8)',
|
||||||
'CREATE INDEX "idx_product_name_869427" ON "product" ("name", "type_db_alias")',
|
'CREATE INDEX "idx_product_name_869427" ON "product" ("name", "type_db_alias")',
|
||||||
'CREATE INDEX "idx_email_email_4a1a33" ON "email" ("email")',
|
'CREATE INDEX "idx_email_email_4a1a33" ON "email" ("email")',
|
||||||
@ -1079,11 +1080,16 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'CREATE TABLE "config_category_map" (\n "category_id" INT NOT NULL REFERENCES "category" ("id") ON DELETE CASCADE,\n "config_id" INT NOT NULL REFERENCES "config" ("id") ON DELETE CASCADE\n)',
|
'CREATE TABLE "config_category_map" (\n "category_id" INT NOT NULL REFERENCES "category" ("id") ON DELETE CASCADE,\n "config_id" INT NOT NULL REFERENCES "config" ("id") ON DELETE CASCADE\n)',
|
||||||
'DROP TABLE IF EXISTS "config_category"',
|
'DROP TABLE IF EXISTS "config_category"',
|
||||||
}
|
}
|
||||||
|
upgrade_operators = set(Migrate.upgrade_operators)
|
||||||
|
upgrade_more_than_expected = upgrade_operators - expected_upgrade_operators
|
||||||
|
assert not upgrade_more_than_expected
|
||||||
|
upgrade_less_than_expected = expected_upgrade_operators - upgrade_operators
|
||||||
|
assert not upgrade_less_than_expected
|
||||||
|
|
||||||
expected_downgrade_operators = {
|
expected_downgrade_operators = {
|
||||||
'CREATE UNIQUE INDEX "uid_category_title_f7fc03" ON "category" ("title")',
|
'CREATE UNIQUE INDEX "uid_category_title_f7fc03" ON "category" ("title")',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "name" SET NOT NULL',
|
'ALTER TABLE "category" ALTER COLUMN "name" SET NOT NULL',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(200) USING "slug"::VARCHAR(200)',
|
'ALTER TABLE "category" ALTER COLUMN "slug" TYPE VARCHAR(200) USING "slug"::VARCHAR(200)',
|
||||||
'ALTER TABLE "category" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "category" RENAME COLUMN "owner_id" TO "user_id"',
|
'ALTER TABLE "category" RENAME COLUMN "owner_id" TO "user_id"',
|
||||||
'ALTER TABLE "category" DROP CONSTRAINT IF EXISTS "fk_category_user_110d4c63"',
|
'ALTER TABLE "category" DROP CONSTRAINT IF EXISTS "fk_category_user_110d4c63"',
|
||||||
'ALTER TABLE "config" ADD "name" VARCHAR(100) NOT NULL UNIQUE',
|
'ALTER TABLE "config" ADD "name" VARCHAR(100) NOT NULL UNIQUE',
|
||||||
@ -1091,7 +1097,6 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "config" ALTER COLUMN "status" SET DEFAULT 1',
|
'ALTER TABLE "config" ALTER COLUMN "status" SET DEFAULT 1',
|
||||||
'ALTER TABLE "config" DROP CONSTRAINT IF EXISTS "fk_config_user_17daa970"',
|
'ALTER TABLE "config" DROP CONSTRAINT IF EXISTS "fk_config_user_17daa970"',
|
||||||
'ALTER TABLE "config" RENAME TO "configs"',
|
'ALTER TABLE "config" RENAME TO "configs"',
|
||||||
'ALTER TABLE "config" ALTER COLUMN "value" TYPE JSONB USING "value"::JSONB',
|
|
||||||
'ALTER TABLE "config" DROP COLUMN "user_id"',
|
'ALTER TABLE "config" DROP COLUMN "user_id"',
|
||||||
'ALTER TABLE "email" ADD "user_id" INT NOT NULL',
|
'ALTER TABLE "email" ADD "user_id" INT NOT NULL',
|
||||||
'ALTER TABLE "email" DROP COLUMN "address"',
|
'ALTER TABLE "email" DROP COLUMN "address"',
|
||||||
@ -1106,11 +1111,7 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'ALTER TABLE "product" RENAME COLUMN "is_reviewed" TO "is_review"',
|
'ALTER TABLE "product" RENAME COLUMN "is_reviewed" TO "is_review"',
|
||||||
'ALTER TABLE "user" ADD "avatar" VARCHAR(200) NOT NULL DEFAULT \'\'',
|
'ALTER TABLE "user" ADD "avatar" VARCHAR(200) NOT NULL DEFAULT \'\'',
|
||||||
'ALTER TABLE "user" ALTER COLUMN "password" TYPE VARCHAR(200) USING "password"::VARCHAR(200)',
|
'ALTER TABLE "user" ALTER COLUMN "password" TYPE VARCHAR(200) USING "password"::VARCHAR(200)',
|
||||||
'ALTER TABLE "user" ALTER COLUMN "last_login" TYPE TIMESTAMPTZ USING "last_login"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "user" ALTER COLUMN "intro" TYPE TEXT USING "intro"::TEXT',
|
|
||||||
'ALTER TABLE "user" ALTER COLUMN "longitude" TYPE DECIMAL(12,9) USING "longitude"::DECIMAL(12,9)',
|
'ALTER TABLE "user" ALTER COLUMN "longitude" TYPE DECIMAL(12,9) USING "longitude"::DECIMAL(12,9)',
|
||||||
'ALTER TABLE "product" ALTER COLUMN "created_at" TYPE TIMESTAMPTZ USING "created_at"::TIMESTAMPTZ',
|
|
||||||
'ALTER TABLE "product" ALTER COLUMN "body" TYPE TEXT USING "body"::TEXT',
|
|
||||||
'DROP TABLE IF EXISTS "product_user"',
|
'DROP TABLE IF EXISTS "product_user"',
|
||||||
'DROP INDEX IF EXISTS "idx_product_name_869427"',
|
'DROP INDEX IF EXISTS "idx_product_name_869427"',
|
||||||
'DROP INDEX IF EXISTS "idx_email_email_4a1a33"',
|
'DROP INDEX IF EXISTS "idx_email_email_4a1a33"',
|
||||||
@ -1121,10 +1122,11 @@ def test_migrate(mocker: MockerFixture):
|
|||||||
'CREATE TABLE "config_category" (\n "config_id" INT NOT NULL REFERENCES "config" ("id") ON DELETE CASCADE,\n "category_id" INT NOT NULL REFERENCES "category" ("id") ON DELETE CASCADE\n)',
|
'CREATE TABLE "config_category" (\n "config_id" INT NOT NULL REFERENCES "config" ("id") ON DELETE CASCADE,\n "category_id" INT NOT NULL REFERENCES "category" ("id") ON DELETE CASCADE\n)',
|
||||||
'DROP TABLE IF EXISTS "config_category_map"',
|
'DROP TABLE IF EXISTS "config_category_map"',
|
||||||
}
|
}
|
||||||
assert not set(Migrate.upgrade_operators).symmetric_difference(expected_upgrade_operators)
|
downgrade_operators = set(Migrate.downgrade_operators)
|
||||||
assert not set(Migrate.downgrade_operators).symmetric_difference(
|
downgrade_more_than_expected = downgrade_operators - expected_downgrade_operators
|
||||||
expected_downgrade_operators
|
assert not downgrade_more_than_expected
|
||||||
)
|
downgrade_less_than_expected = expected_downgrade_operators - downgrade_operators
|
||||||
|
assert not downgrade_less_than_expected
|
||||||
|
|
||||||
elif isinstance(Migrate.ddl, SqliteDDL):
|
elif isinstance(Migrate.ddl, SqliteDDL):
|
||||||
assert Migrate.upgrade_operators == []
|
assert Migrate.upgrade_operators == []
|
||||||
|
@ -3,161 +3,16 @@ import os
|
|||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aerich.ddl.sqlite import SqliteDDL
|
from aerich.ddl.sqlite import SqliteDDL
|
||||||
from aerich.migrate import Migrate
|
from aerich.migrate import Migrate
|
||||||
|
from tests._utils import chdir, copy_files
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
from contextlib import chdir
|
|
||||||
else:
|
|
||||||
|
|
||||||
class chdir(contextlib.AbstractContextManager): # Copied from source code of Python3.13
|
|
||||||
"""Non thread-safe context manager to change the current working directory."""
|
|
||||||
|
|
||||||
def __init__(self, path):
|
|
||||||
self.path = path
|
|
||||||
self._old_cwd = []
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._old_cwd.append(os.getcwd())
|
|
||||||
os.chdir(self.path)
|
|
||||||
|
|
||||||
def __exit__(self, *excinfo):
|
|
||||||
os.chdir(self._old_cwd.pop())
|
|
||||||
|
|
||||||
|
|
||||||
MODELS = """from __future__ import annotations
|
|
||||||
|
|
||||||
from tortoise import Model, fields
|
|
||||||
|
|
||||||
|
|
||||||
class Foo(Model):
|
|
||||||
name = fields.CharField(max_length=60, db_index=False)
|
|
||||||
"""
|
|
||||||
|
|
||||||
SETTINGS = """from __future__ import annotations
|
|
||||||
|
|
||||||
TORTOISE_ORM = {
|
|
||||||
"connections": {"default": "sqlite://db.sqlite3"},
|
|
||||||
"apps": {"models": {"models": ["models", "aerich.models"]}},
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
|
|
||||||
CONFTEST = """from __future__ import annotations
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
import pytest_asyncio
|
|
||||||
from tortoise import Tortoise, connections
|
|
||||||
|
|
||||||
import settings
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def event_loop() -> Generator:
|
|
||||||
policy = asyncio.get_event_loop_policy()
|
|
||||||
res = policy.new_event_loop()
|
|
||||||
asyncio.set_event_loop(res)
|
|
||||||
res._close = res.close # type:ignore[attr-defined]
|
|
||||||
res.close = lambda: None # type:ignore[method-assign]
|
|
||||||
|
|
||||||
yield res
|
|
||||||
|
|
||||||
res._close() # type:ignore[attr-defined]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="session", autouse=True)
|
|
||||||
async def api(event_loop, request):
|
|
||||||
await Tortoise.init(config=settings.TORTOISE_ORM)
|
|
||||||
request.addfinalizer(lambda: event_loop.run_until_complete(connections.close_all(discard=True)))
|
|
||||||
"""
|
|
||||||
|
|
||||||
TESTS = """from __future__ import annotations
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from tortoise.exceptions import IntegrityError
|
|
||||||
|
|
||||||
from models import Foo
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_allow_duplicate() -> None:
|
|
||||||
await Foo.all().delete()
|
|
||||||
await Foo.create(name="foo")
|
|
||||||
obj = await Foo.create(name="foo")
|
|
||||||
assert (await Foo.all().count()) == 2
|
|
||||||
await obj.delete()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_unique_is_true() -> None:
|
|
||||||
with pytest.raises(IntegrityError):
|
|
||||||
await Foo.create(name="foo")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_add_unique_field() -> None:
|
|
||||||
if not await Foo.filter(age=0).exists():
|
|
||||||
await Foo.create(name="0_"+uuid.uuid4().hex, age=0)
|
|
||||||
with pytest.raises(IntegrityError):
|
|
||||||
await Foo.create(name=uuid.uuid4().hex, age=0)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_drop_unique_field() -> None:
|
|
||||||
name = "1_" + uuid.uuid4().hex
|
|
||||||
await Foo.create(name=name, age=0)
|
|
||||||
assert (await Foo.filter(name=name).exists())
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_with_age_field() -> None:
|
|
||||||
name = "2_" + uuid.uuid4().hex
|
|
||||||
await Foo.create(name=name, age=0)
|
|
||||||
obj = await Foo.get(name=name)
|
|
||||||
assert obj.age == 0
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_without_age_field() -> None:
|
|
||||||
name = "3_" + uuid.uuid4().hex
|
|
||||||
await Foo.create(name=name, age=0)
|
|
||||||
obj = await Foo.get(name=name)
|
|
||||||
assert getattr(obj, "age", None) is None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_m2m_with_custom_through() -> None:
|
|
||||||
from models import Group, FooGroup
|
|
||||||
name = "4_" + uuid.uuid4().hex
|
|
||||||
foo = await Foo.create(name=name)
|
|
||||||
group = await Group.create(name=name+"1")
|
|
||||||
await FooGroup.all().delete()
|
|
||||||
await foo.groups.add(group)
|
|
||||||
foo_group = await FooGroup.get(foo=foo, group=group)
|
|
||||||
assert not foo_group.is_active
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_add_m2m_field_after_init_db() -> None:
|
|
||||||
from models import Group
|
|
||||||
name = "5_" + uuid.uuid4().hex
|
|
||||||
foo = await Foo.create(name=name)
|
|
||||||
group = await Group.create(name=name+"1")
|
|
||||||
await foo.groups.add(group)
|
|
||||||
assert (await group.users.all().first()) == foo
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def run_aerich(cmd: str) -> None:
|
def run_aerich(cmd: str) -> None:
|
||||||
with contextlib.suppress(subprocess.TimeoutExpired):
|
with contextlib.suppress(subprocess.TimeoutExpired):
|
||||||
if not cmd.startswith("aerich"):
|
if not cmd.startswith("aerich") and not cmd.startswith("poetry"):
|
||||||
cmd = "aerich " + cmd
|
cmd = "aerich " + cmd
|
||||||
subprocess.run(shlex.split(cmd), timeout=2)
|
subprocess.run(shlex.split(cmd), timeout=2)
|
||||||
|
|
||||||
@ -170,60 +25,60 @@ def run_shell(cmd: str) -> subprocess.CompletedProcess:
|
|||||||
def test_sqlite_migrate(tmp_path: Path) -> None:
|
def test_sqlite_migrate(tmp_path: Path) -> None:
|
||||||
if (ddl := getattr(Migrate, "ddl", None)) and not isinstance(ddl, SqliteDDL):
|
if (ddl := getattr(Migrate, "ddl", None)) and not isinstance(ddl, SqliteDDL):
|
||||||
return
|
return
|
||||||
|
test_dir = Path(__file__).parent
|
||||||
|
asset_dir = test_dir / "assets" / "sqlite_migrate"
|
||||||
with chdir(tmp_path):
|
with chdir(tmp_path):
|
||||||
models_py = Path("models.py")
|
files = ("models.py", "settings.py", "_tests.py")
|
||||||
settings_py = Path("settings.py")
|
copy_files(*(asset_dir / f for f in files), target_dir=Path())
|
||||||
test_py = Path("_test.py")
|
models_py, settings_py, test_py = (Path(f) for f in files)
|
||||||
models_py.write_text(MODELS)
|
copy_files(asset_dir / "conftest_.py", target_dir=Path("conftest.py"))
|
||||||
settings_py.write_text(SETTINGS)
|
|
||||||
test_py.write_text(TESTS)
|
|
||||||
Path("conftest.py").write_text(CONFTEST)
|
|
||||||
if (db_file := Path("db.sqlite3")).exists():
|
if (db_file := Path("db.sqlite3")).exists():
|
||||||
db_file.unlink()
|
db_file.unlink()
|
||||||
|
MODELS = models_py.read_text("utf-8")
|
||||||
run_aerich("aerich init -t settings.TORTOISE_ORM")
|
run_aerich("aerich init -t settings.TORTOISE_ORM")
|
||||||
config_file = Path("pyproject.toml")
|
config_file = Path("pyproject.toml")
|
||||||
modify_time = config_file.stat().st_mtime
|
modify_time = config_file.stat().st_mtime
|
||||||
run_aerich("aerich init-db")
|
run_aerich("aerich init-db")
|
||||||
run_aerich("aerich init -t settings.TORTOISE_ORM")
|
run_aerich("aerich init -t settings.TORTOISE_ORM")
|
||||||
assert modify_time == config_file.stat().st_mtime
|
assert modify_time == config_file.stat().st_mtime
|
||||||
r = run_shell("pytest _test.py::test_allow_duplicate")
|
r = run_shell("pytest _tests.py::test_allow_duplicate")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Add index
|
# Add index
|
||||||
models_py.write_text(MODELS.replace("index=False", "index=True"))
|
models_py.write_text(MODELS.replace("index=False", "index=True"))
|
||||||
run_aerich("aerich migrate") # migrations/models/1_
|
run_aerich("aerich migrate") # migrations/models/1_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest -s _test.py::test_allow_duplicate")
|
r = run_shell("pytest -s _tests.py::test_allow_duplicate")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Drop index
|
# Drop index
|
||||||
models_py.write_text(MODELS)
|
models_py.write_text(MODELS)
|
||||||
run_aerich("aerich migrate") # migrations/models/2_
|
run_aerich("aerich migrate") # migrations/models/2_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest -s _test.py::test_allow_duplicate")
|
r = run_shell("pytest -s _tests.py::test_allow_duplicate")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Add unique index
|
# Add unique index
|
||||||
models_py.write_text(MODELS.replace("index=False", "index=True, unique=True"))
|
models_py.write_text(MODELS.replace("index=False", "index=True, unique=True"))
|
||||||
run_aerich("aerich migrate") # migrations/models/3_
|
run_aerich("aerich migrate") # migrations/models/3_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest _test.py::test_unique_is_true")
|
r = run_shell("pytest _tests.py::test_unique_is_true")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Drop unique index
|
# Drop unique index
|
||||||
models_py.write_text(MODELS)
|
models_py.write_text(MODELS)
|
||||||
run_aerich("aerich migrate") # migrations/models/4_
|
run_aerich("aerich migrate") # migrations/models/4_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest _test.py::test_allow_duplicate")
|
r = run_shell("pytest _tests.py::test_allow_duplicate")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Add field with unique=True
|
# Add field with unique=True
|
||||||
with models_py.open("a") as f:
|
with models_py.open("a") as f:
|
||||||
f.write(" age = fields.IntField(unique=True, default=0)")
|
f.write(" age = fields.IntField(unique=True, default=0)")
|
||||||
run_aerich("aerich migrate") # migrations/models/5_
|
run_aerich("aerich migrate") # migrations/models/5_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest _test.py::test_add_unique_field")
|
r = run_shell("pytest _tests.py::test_add_unique_field")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
# Drop unique field
|
# Drop unique field
|
||||||
models_py.write_text(MODELS)
|
models_py.write_text(MODELS)
|
||||||
run_aerich("aerich migrate") # migrations/models/6_
|
run_aerich("aerich migrate") # migrations/models/6_
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
r = run_shell("pytest -s _test.py::test_drop_unique_field")
|
r = run_shell("pytest -s _tests.py::test_drop_unique_field")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
|
|
||||||
# Initial with indexed field and then drop it
|
# Initial with indexed field and then drop it
|
||||||
@ -235,14 +90,14 @@ def test_sqlite_migrate(tmp_path: Path) -> None:
|
|||||||
run_aerich("aerich init-db")
|
run_aerich("aerich init-db")
|
||||||
migration_file = list(migrations_dir.glob("0_*.py"))[0]
|
migration_file = list(migrations_dir.glob("0_*.py"))[0]
|
||||||
assert "CREATE INDEX" in migration_file.read_text()
|
assert "CREATE INDEX" in migration_file.read_text()
|
||||||
r = run_shell("pytest _test.py::test_with_age_field")
|
r = run_shell("pytest _tests.py::test_with_age_field")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
models_py.write_text(MODELS)
|
models_py.write_text(MODELS)
|
||||||
run_aerich("aerich migrate")
|
run_aerich("aerich migrate")
|
||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
||||||
assert "DROP INDEX" in migration_file_1.read_text()
|
assert "DROP INDEX" in migration_file_1.read_text()
|
||||||
r = run_shell("pytest _test.py::test_without_age_field")
|
r = run_shell("pytest _tests.py::test_without_age_field")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
|
|
||||||
# Generate migration file in emptry directory
|
# Generate migration file in emptry directory
|
||||||
@ -283,7 +138,7 @@ class FooGroup(Model):
|
|||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
||||||
assert "foo_group" in migration_file_1.read_text()
|
assert "foo_group" in migration_file_1.read_text()
|
||||||
r = run_shell("pytest _test.py::test_m2m_with_custom_through")
|
r = run_shell("pytest _tests.py::test_m2m_with_custom_through")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
|
|
||||||
# add m2m field after init-db
|
# add m2m field after init-db
|
||||||
@ -304,5 +159,5 @@ class Group(Model):
|
|||||||
run_aerich("aerich upgrade")
|
run_aerich("aerich upgrade")
|
||||||
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
migration_file_1 = list(migrations_dir.glob("1_*.py"))[0]
|
||||||
assert "foo_group" in migration_file_1.read_text()
|
assert "foo_group" in migration_file_1.read_text()
|
||||||
r = run_shell("pytest _test.py::test_add_m2m_field_after_init_db")
|
r = run_shell("pytest _tests.py::test_add_m2m_field_after_init_db")
|
||||||
assert r.returncode == 0
|
assert r.returncode == 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user