model-generator-kit 0.1.3__tar.gz → 0.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/PKG-INFO +2 -2
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/README.md +1 -1
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/pyproject.toml +1 -1
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/__init__.py +1 -1
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/api/request.py.j2 +1 -1
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/migrations/env.py.j2 +61 -1
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/PKG-INFO +2 -2
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_generators.py +105 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/LICENSE +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/setup.cfg +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generate.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/__init__.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/api.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/constraints.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/database.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/enums.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/infrastructure.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/migrations.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/py.typed +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/schema/model.schema.json +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/config.yaml +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/_shared/_base.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/_shared/_entity.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/_shared/_examples.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/_shared/_fields.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/_shared/_tests.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/api/init.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/api/pagination.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/api/response.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/api/route.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/database/constraints.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/database/enums.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/database/factory.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/database/init.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/database/model.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/auth_router.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/base.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/csrf.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/database_init.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/encrypted_bytes.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/engine.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/errors.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/gitignore.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/main.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/pyproject.toml.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/rate_limit.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/request_limit.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/types.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/utils.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/infrastructure/validators.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/migrations/ini.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/migrations/script.py.mako.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/tests/conftest_root.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/stacks/python-fastapi/templates/tests/contract.py.j2 +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/__init__.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/conftest_generator.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/constants.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/loaders.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/parser.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/quality.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/templates.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/validate.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/__init__.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/__init__.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/clean.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/generate.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/project_setup.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/test_runner.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/menu.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/prompts.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/SOURCES.txt +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/dependency_links.txt +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/entry_points.txt +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/requires.txt +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/top_level.txt +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_cleanup.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_cli.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_edge_cases.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_enum_examples.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_full_generation.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_integration.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_template_utils.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_utils.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_validate.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_validation.py +0 -0
- {model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/tests/test_wizard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model-generator-kit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: One-shot bootstrap generator for database models, API models, routes, and tests
|
|
5
5
|
Author-email: nuncaeslupus <imarcos@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -140,4 +140,4 @@ Define specifications once in JSON. Generate production-ready scaffolds. Then ma
|
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
143
|
-
**Model Generator** | Bootstrap Tool for API Backends | v0.1.
|
|
143
|
+
**Model Generator** | Bootstrap Tool for API Backends | v0.1.4
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "model-generator-kit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.4"
|
|
8
8
|
description = "One-shot bootstrap generator for database models, API models, routes, and tests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -210,7 +210,7 @@ class Create{{ entity_name }}Request(BaseModel):
|
|
|
210
210
|
{% elif field.type == 'text' and db_name == 'username' %}
|
|
211
211
|
"{{ field_name }}": "john_doe",
|
|
212
212
|
{% elif field.type == 'text' and 'password' in db_name.lower() %}
|
|
213
|
-
"{{ field_name }}": "
|
|
213
|
+
"{{ field_name }}": "your-password-here",
|
|
214
214
|
{% elif field.type == 'text' and 'name' in db_name.lower() %}
|
|
215
215
|
"{{ field_name }}": "{{ db_name.replace('_', ' ').title() }}",
|
|
216
216
|
{% elif field.type == 'text' %}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
{{ generated_marker() }}
|
|
11
11
|
"""Alembic environment configuration."""
|
|
12
12
|
from logging.config import fileConfig
|
|
13
|
+
from typing import Any
|
|
13
14
|
|
|
14
15
|
from sqlalchemy import engine_from_config
|
|
15
16
|
from sqlalchemy import pool
|
|
@@ -32,6 +33,63 @@ if config.config_file_name is not None:
|
|
|
32
33
|
from {{ config.paths.database_models | path_to_import }}.base import Base
|
|
33
34
|
target_metadata = Base.metadata
|
|
34
35
|
|
|
36
|
+
# Project custom SQLAlchemy types live here. Autogenerate must import them into
|
|
37
|
+
# the migrations it writes (see render_item below), otherwise the generated
|
|
38
|
+
# migration references e.g. PortableUuid() without an import and fails to load.
|
|
39
|
+
_CUSTOM_TYPE_MODULE = "{{ config.paths.database_models.rsplit('/', 1)[0] | path_to_import }}.types"
|
|
40
|
+
_CUSTOM_TYPE_NAMES = ("PortableNumeric", "PortableUuid")
|
|
41
|
+
|
|
42
|
+
# Async drivers map to their sync equivalents. Alembic runs migrations
|
|
43
|
+
# synchronously, so an async DATABASE_URL (e.g. postgresql+asyncpg://) must be
|
|
44
|
+
# coerced to a sync driver before an Engine is built.
|
|
45
|
+
_ASYNC_TO_SYNC_DRIVERS = {
|
|
46
|
+
"+asyncpg": "+psycopg2",
|
|
47
|
+
"+aiosqlite": "",
|
|
48
|
+
"+aiomysql": "+pymysql",
|
|
49
|
+
"+asyncmy": "+pymysql",
|
|
50
|
+
"+aioodbc": "+pyodbc",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _coerce_sync_driver(url: str) -> str:
|
|
55
|
+
"""Return ``url`` with any known async driver in the *scheme* swapped for a
|
|
56
|
+
sync one. Only the scheme (before "://") is touched, so a credential or
|
|
57
|
+
database name that happens to contain a driver substring is never mangled.
|
|
58
|
+
"""
|
|
59
|
+
scheme, sep, rest = url.partition("://")
|
|
60
|
+
if not sep:
|
|
61
|
+
return url
|
|
62
|
+
for async_driver, sync_driver in _ASYNC_TO_SYNC_DRIVERS.items():
|
|
63
|
+
if scheme.endswith(async_driver):
|
|
64
|
+
scheme = scheme[: -len(async_driver)] + sync_driver
|
|
65
|
+
break
|
|
66
|
+
return scheme + sep + rest
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def render_item(type_: str, obj: Any, autogen_context: Any) -> str | bool:
|
|
70
|
+
"""Render the project's custom column types with their import.
|
|
71
|
+
|
|
72
|
+
Without this, autogenerate emits ``PortableUuid()`` / ``PortableNumeric()``
|
|
73
|
+
in the migration body but never imports them, so the migration raises
|
|
74
|
+
``NameError`` on load. Returning ``False`` falls back to Alembic's default
|
|
75
|
+
rendering for everything else.
|
|
76
|
+
"""
|
|
77
|
+
if type_ == "type" and type(obj).__name__ in _CUSTOM_TYPE_NAMES:
|
|
78
|
+
cls = type(obj).__name__
|
|
79
|
+
autogen_context.imports.add(f"from {_CUSTOM_TYPE_MODULE} import {cls}")
|
|
80
|
+
if cls == "PortableNumeric":
|
|
81
|
+
args = [
|
|
82
|
+
f"{name}={value}"
|
|
83
|
+
for name, value in (
|
|
84
|
+
("precision", obj.precision),
|
|
85
|
+
("scale", obj.scale),
|
|
86
|
+
)
|
|
87
|
+
if value is not None
|
|
88
|
+
]
|
|
89
|
+
return f"{cls}({', '.join(args)})"
|
|
90
|
+
return f"{cls}()"
|
|
91
|
+
return False
|
|
92
|
+
|
|
35
93
|
# other values from the config, defined by the needs of env.py,
|
|
36
94
|
# can be acquired:
|
|
37
95
|
# my_important_option = config.get_main_option("my_important_option")
|
|
@@ -60,7 +118,7 @@ def get_url() -> str:
|
|
|
60
118
|
"alembic.ini, must be set"
|
|
61
119
|
)
|
|
62
120
|
raise RuntimeError(msg)
|
|
63
|
-
return url
|
|
121
|
+
return _coerce_sync_driver(url)
|
|
64
122
|
|
|
65
123
|
|
|
66
124
|
def run_migrations_offline() -> None:
|
|
@@ -83,6 +141,7 @@ def run_migrations_offline() -> None:
|
|
|
83
141
|
dialect_opts={"paramstyle": "named"},
|
|
84
142
|
compare_type=True,
|
|
85
143
|
compare_server_default=True,
|
|
144
|
+
render_item=render_item,
|
|
86
145
|
)
|
|
87
146
|
|
|
88
147
|
with context.begin_transaction():
|
|
@@ -113,6 +172,7 @@ def run_migrations_online() -> None:
|
|
|
113
172
|
target_metadata=target_metadata,
|
|
114
173
|
compare_type=True,
|
|
115
174
|
compare_server_default=True,
|
|
175
|
+
render_item=render_item,
|
|
116
176
|
)
|
|
117
177
|
|
|
118
178
|
with context.begin_transaction():
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model-generator-kit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: One-shot bootstrap generator for database models, API models, routes, and tests
|
|
5
5
|
Author-email: nuncaeslupus <imarcos@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -140,4 +140,4 @@ Define specifications once in JSON. Generate production-ready scaffolds. Then ma
|
|
|
140
140
|
|
|
141
141
|
---
|
|
142
142
|
|
|
143
|
-
**Model Generator** | Bootstrap Tool for API Backends | v0.1.
|
|
143
|
+
**Model Generator** | Bootstrap Tool for API Backends | v0.1.4
|
|
@@ -1105,6 +1105,36 @@ class TestApiModelsGeneratorPerEntity:
|
|
|
1105
1105
|
assert "class UpdateAuthorRequest(BaseModel):" in author_req
|
|
1106
1106
|
assert "CreatePostRequest" not in author_req
|
|
1107
1107
|
|
|
1108
|
+
def test_password_example_is_not_a_secret(
|
|
1109
|
+
self, project_env_per_entity: Any
|
|
1110
|
+
) -> None:
|
|
1111
|
+
"""The OpenAPI example for a password field must be a non-secret
|
|
1112
|
+
placeholder so secret scanners (GitGuardian) don't flag generated repos."""
|
|
1113
|
+
project_root, config, env = project_env_per_entity
|
|
1114
|
+
model = {
|
|
1115
|
+
"domain": "accounts",
|
|
1116
|
+
"entities": {
|
|
1117
|
+
"User": {
|
|
1118
|
+
"table": "users",
|
|
1119
|
+
"fields": {
|
|
1120
|
+
"id": {
|
|
1121
|
+
"type": "uuid",
|
|
1122
|
+
"primary_key": True,
|
|
1123
|
+
"auto_generate": True,
|
|
1124
|
+
},
|
|
1125
|
+
"password": {"type": "text", "required": True},
|
|
1126
|
+
},
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
}
|
|
1130
|
+
result = generate_api_models(model, config, env, project_root)
|
|
1131
|
+
assert isinstance(result, list)
|
|
1132
|
+
content = next(
|
|
1133
|
+
r["content"] for r in result if r["path"].name == "user_requests.py"
|
|
1134
|
+
)
|
|
1135
|
+
assert "SecureP@ssw0rd!" not in content
|
|
1136
|
+
assert '"password": "your-password-here"' in content
|
|
1137
|
+
|
|
1108
1138
|
|
|
1109
1139
|
@pytest.fixture
|
|
1110
1140
|
def financial_model() -> dict[str, Any]:
|
|
@@ -3045,6 +3075,81 @@ class TestMigrationGenerator:
|
|
|
3045
3075
|
# config.paths.database_models is used in the import path
|
|
3046
3076
|
assert "src.database.models" in env_py["content"]
|
|
3047
3077
|
|
|
3078
|
+
def _env_py(self, minimal_model: dict[str, Any], project_env: Any) -> str:
|
|
3079
|
+
project_root, config, env = project_env
|
|
3080
|
+
result = generate_migration_init(minimal_model, config, env, project_root)
|
|
3081
|
+
assert isinstance(result, list)
|
|
3082
|
+
env_py = next(
|
|
3083
|
+
r for r in result if r["path"] == project_root / "alembic" / "env.py"
|
|
3084
|
+
)
|
|
3085
|
+
content = env_py["content"]
|
|
3086
|
+
assert isinstance(content, str)
|
|
3087
|
+
return content
|
|
3088
|
+
|
|
3089
|
+
def test_env_py_is_valid_python(
|
|
3090
|
+
self, minimal_model: dict[str, Any], project_env: Any
|
|
3091
|
+
) -> None:
|
|
3092
|
+
"""The rendered env.py must parse — guards the new helper blocks."""
|
|
3093
|
+
import ast
|
|
3094
|
+
|
|
3095
|
+
ast.parse(self._env_py(minimal_model, project_env))
|
|
3096
|
+
|
|
3097
|
+
def test_env_py_coerces_async_drivers_to_sync(
|
|
3098
|
+
self, minimal_model: dict[str, Any], project_env: Any
|
|
3099
|
+
) -> None:
|
|
3100
|
+
"""Alembic runs sync, so get_url() must coerce async drivers."""
|
|
3101
|
+
content = self._env_py(minimal_model, project_env)
|
|
3102
|
+
assert "def _coerce_sync_driver(url: str) -> str:" in content
|
|
3103
|
+
assert '"+asyncpg": "+psycopg2",' in content
|
|
3104
|
+
assert '"+aiosqlite": "",' in content
|
|
3105
|
+
# get_url returns the coerced URL, not the raw one.
|
|
3106
|
+
assert "return _coerce_sync_driver(url)" in content
|
|
3107
|
+
# Only the scheme is rewritten, so credentials/db names are never mangled.
|
|
3108
|
+
assert 'scheme, sep, rest = url.partition("://")' in content
|
|
3109
|
+
assert "scheme.endswith(async_driver)" in content
|
|
3110
|
+
|
|
3111
|
+
def test_coerce_sync_driver_behaviour(
|
|
3112
|
+
self, minimal_model: dict[str, Any], project_env: Any
|
|
3113
|
+
) -> None:
|
|
3114
|
+
"""Exercise the generated _coerce_sync_driver in isolation (executing
|
|
3115
|
+
the whole module would run migrations)."""
|
|
3116
|
+
import ast
|
|
3117
|
+
|
|
3118
|
+
content = self._env_py(minimal_model, project_env)
|
|
3119
|
+
tree = ast.parse(content)
|
|
3120
|
+
wanted = {"_ASYNC_TO_SYNC_DRIVERS", "_coerce_sync_driver"}
|
|
3121
|
+
nodes: list[ast.stmt] = [
|
|
3122
|
+
n
|
|
3123
|
+
for n in tree.body
|
|
3124
|
+
if (isinstance(n, ast.FunctionDef) and n.name in wanted)
|
|
3125
|
+
or (
|
|
3126
|
+
isinstance(n, ast.Assign)
|
|
3127
|
+
and any(getattr(t, "id", None) in wanted for t in n.targets)
|
|
3128
|
+
)
|
|
3129
|
+
]
|
|
3130
|
+
ns: dict[str, Any] = {}
|
|
3131
|
+
exec(compile(ast.Module(body=nodes, type_ignores=[]), "<env>", "exec"), ns)
|
|
3132
|
+
coerce = ns["_coerce_sync_driver"]
|
|
3133
|
+
assert (
|
|
3134
|
+
coerce("postgresql+asyncpg://u:p@h/db") == "postgresql+psycopg2://u:p@h/db"
|
|
3135
|
+
)
|
|
3136
|
+
assert coerce("sqlite+aiosqlite:///x.db") == "sqlite:///x.db"
|
|
3137
|
+
# A driver substring inside credentials must NOT be rewritten.
|
|
3138
|
+
assert coerce("postgresql://u:+asyncpg@h/db") == "postgresql://u:+asyncpg@h/db"
|
|
3139
|
+
# Already-sync URLs pass through untouched.
|
|
3140
|
+
assert coerce("postgresql://u:p@h/db") == "postgresql://u:p@h/db"
|
|
3141
|
+
|
|
3142
|
+
def test_env_py_renders_custom_types_with_import(
|
|
3143
|
+
self, minimal_model: dict[str, Any], project_env: Any
|
|
3144
|
+
) -> None:
|
|
3145
|
+
"""render_item must be wired into both configure() calls and import
|
|
3146
|
+
the project's custom column types into generated migrations."""
|
|
3147
|
+
content = self._env_py(minimal_model, project_env)
|
|
3148
|
+
assert "def render_item(type_: str, obj: Any, autogen_context: Any)" in content
|
|
3149
|
+
assert '_CUSTOM_TYPE_MODULE = "src.database.types"' in content
|
|
3150
|
+
# Wired into offline and online configure() calls.
|
|
3151
|
+
assert content.count("render_item=render_item,") == 2
|
|
3152
|
+
|
|
3048
3153
|
def test_custom_migrations_path(
|
|
3049
3154
|
self, minimal_model: dict[str, Any], project_env: Any
|
|
3050
3155
|
) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/__init__.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/api.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/database.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/generators/migrations.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/schema/model.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/constants.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/loaders.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/quality.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/utils/templates.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/actions/clean.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator/wizard/prompts.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.3 → model_generator_kit-0.1.4}/src/model_generator_kit.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|