model-generator-kit 0.1.2__tar.gz → 0.1.3__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.2 → model_generator_kit-0.1.3}/PKG-INFO +2 -2
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/README.md +1 -1
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/pyproject.toml +1 -1
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/__init__.py +1 -1
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/api/request.py.j2 +21 -21
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/api/response.py.j2 +14 -2
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/validators.py.j2 +17 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/PKG-INFO +2 -2
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_generators.py +187 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/LICENSE +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/setup.cfg +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generate.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/__init__.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/api.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/constraints.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/database.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/enums.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/infrastructure.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/migrations.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/py.typed +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/schema/model.schema.json +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/config.yaml +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/_shared/_base.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/_shared/_entity.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/_shared/_examples.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/_shared/_fields.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/_shared/_tests.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/api/init.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/api/pagination.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/api/route.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/database/constraints.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/database/enums.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/database/factory.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/database/init.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/database/model.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/auth_router.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/base.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/csrf.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/database_init.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/encrypted_bytes.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/engine.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/errors.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/gitignore.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/main.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/pyproject.toml.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/rate_limit.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/request_limit.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/types.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/infrastructure/utils.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/migrations/env.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/migrations/ini.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/migrations/script.py.mako.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/tests/conftest_root.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/stacks/python-fastapi/templates/tests/contract.py.j2 +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/__init__.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/conftest_generator.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/constants.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/loaders.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/parser.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/quality.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/templates.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/validate.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/__init__.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/actions/__init__.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/actions/clean.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/actions/generate.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/actions/project_setup.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/actions/test_runner.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/menu.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/prompts.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/SOURCES.txt +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/dependency_links.txt +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/entry_points.txt +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/requires.txt +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator_kit.egg-info/top_level.txt +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_cleanup.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_cli.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_edge_cases.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_enum_examples.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_full_generation.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_integration.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_template_utils.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_utils.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_validate.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/tests/test_validation.py +0 -0
- {model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/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.3
|
|
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.3
|
|
@@ -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.3"
|
|
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"}
|
|
@@ -57,11 +57,13 @@ This module contains:
|
|
|
57
57
|
{% endif %}
|
|
58
58
|
{#- Check for financial/counter fields with constraints needing validators -#}
|
|
59
59
|
{% if not field.api_exclude_create | default(false) or not field.api_exclude_update | default(false) %}
|
|
60
|
-
{% if field.type == 'financial'
|
|
61
|
-
{
|
|
62
|
-
{%
|
|
63
|
-
{% if
|
|
64
|
-
{%
|
|
60
|
+
{% if field.type == 'financial' %}
|
|
61
|
+
{#- Choose validator by declared constraint: non-negative when constrained, signed otherwise -#}
|
|
62
|
+
{% set ctypes = (field.constraints | map(attribute='type') | list) if (field.constraints is defined and field.constraints) else [] %}
|
|
63
|
+
{% if 'non_negative' in ctypes or 'non_negative_or_null' in ctypes %}{% set fin_validator = 'validate_non_negative_decimal' %}
|
|
64
|
+
{% elif 'positive' in ctypes or 'positive_or_null' in ctypes %}{% set fin_validator = 'validate_positive_decimal' %}
|
|
65
|
+
{% else %}{% set fin_validator = 'validate_decimal' %}{% endif %}
|
|
66
|
+
{% if fin_validator not in ns.needs_validators %}{% set _ = ns.needs_validators.append(fin_validator) %}{% endif %}
|
|
65
67
|
{% elif field.type == 'percentage' %}
|
|
66
68
|
{% if 'validate_percentage' not in ns.needs_validators %}{% set _ = ns.needs_validators.append('validate_percentage') %}{% endif %}
|
|
67
69
|
{% elif field.type == 'counter' and field.constraints is defined %}
|
|
@@ -271,14 +273,13 @@ class Create{{ entity_name }}Request(BaseModel):
|
|
|
271
273
|
{#- Collect constraint-driven validators (financial, percentage, counter) -#}
|
|
272
274
|
{% for item in create_fields %}
|
|
273
275
|
{% set field = item.field %}
|
|
274
|
-
{% if field.type == 'financial'
|
|
275
|
-
{
|
|
276
|
-
{%
|
|
277
|
-
{%
|
|
278
|
-
{% elif
|
|
279
|
-
{% set
|
|
280
|
-
{%
|
|
281
|
-
{% endfor %}
|
|
276
|
+
{% if field.type == 'financial' %}
|
|
277
|
+
{#- Choose validator by declared constraint: non-negative when constrained, signed otherwise -#}
|
|
278
|
+
{% set ctypes = (field.constraints | map(attribute='type') | list) if (field.constraints is defined and field.constraints) else [] %}
|
|
279
|
+
{% if 'non_negative' in ctypes or 'non_negative_or_null' in ctypes %}{% set fin_validator = 'validate_non_negative_decimal' %}
|
|
280
|
+
{% elif 'positive' in ctypes or 'positive_or_null' in ctypes %}{% set fin_validator = 'validate_positive_decimal' %}
|
|
281
|
+
{% else %}{% set fin_validator = 'validate_decimal' %}{% endif %}
|
|
282
|
+
{% set _ = field_validators.append({'name': item.name, 'validator': fin_validator}) %}
|
|
282
283
|
{% elif field.type == 'percentage' %}
|
|
283
284
|
{% set _ = field_validators.append({'name': item.name, 'validator': 'validate_percentage'}) %}
|
|
284
285
|
{% elif field.type == 'counter' and field.constraints is defined %}
|
|
@@ -416,14 +417,13 @@ class Update{{ entity_name }}Request(BaseModel):
|
|
|
416
417
|
{% set update_validators = [] %}
|
|
417
418
|
{% for item in update_fields %}
|
|
418
419
|
{% set field = item.field %}
|
|
419
|
-
{% if field.type == 'financial'
|
|
420
|
-
{
|
|
421
|
-
{%
|
|
422
|
-
{%
|
|
423
|
-
{% elif
|
|
424
|
-
{% set
|
|
425
|
-
{%
|
|
426
|
-
{% endfor %}
|
|
420
|
+
{% if field.type == 'financial' %}
|
|
421
|
+
{#- Choose validator by declared constraint: non-negative when constrained, signed otherwise -#}
|
|
422
|
+
{% set ctypes = (field.constraints | map(attribute='type') | list) if (field.constraints is defined and field.constraints) else [] %}
|
|
423
|
+
{% if 'non_negative' in ctypes or 'non_negative_or_null' in ctypes %}{% set fin_validator = 'validate_non_negative_decimal' %}
|
|
424
|
+
{% elif 'positive' in ctypes or 'positive_or_null' in ctypes %}{% set fin_validator = 'validate_positive_decimal' %}
|
|
425
|
+
{% else %}{% set fin_validator = 'validate_decimal' %}{% endif %}
|
|
426
|
+
{% set _ = update_validators.append({'name': item.name, 'validator': fin_validator}) %}
|
|
427
427
|
{% elif field.type == 'percentage' %}
|
|
428
428
|
{% set _ = update_validators.append({'name': item.name, 'validator': 'validate_percentage'}) %}
|
|
429
429
|
{% elif field.type == 'counter' and field.constraints is defined %}
|
|
@@ -26,7 +26,14 @@ This module contains:
|
|
|
26
26
|
{% if not field.api_exclude_response | default(false) %}
|
|
27
27
|
{% if field.type in ['json_object', 'json_array'] %}{% set ns.has_any = true %}{% endif %}
|
|
28
28
|
{% if field.type == 'enum' and field.enum_name not in ns.enum_imports %}{% set _ = ns.enum_imports.append(field.enum_name) %}{% endif %}
|
|
29
|
-
{% if field.type == 'financial'
|
|
29
|
+
{% if field.type == 'financial' %}
|
|
30
|
+
{#- Choose validator by declared constraint: non-negative when constrained, signed otherwise -#}
|
|
31
|
+
{% set ctypes = (field.constraints | map(attribute='type') | list) if (field.constraints is defined and field.constraints) else [] %}
|
|
32
|
+
{% if 'non_negative' in ctypes or 'non_negative_or_null' in ctypes %}{% set fin_validator = 'validate_non_negative_decimal' %}
|
|
33
|
+
{% elif 'positive' in ctypes or 'positive_or_null' in ctypes %}{% set fin_validator = 'validate_positive_decimal' %}
|
|
34
|
+
{% else %}{% set fin_validator = 'validate_decimal' %}{% endif %}
|
|
35
|
+
{% if fin_validator not in ns.needs_validators %}{% set _ = ns.needs_validators.append(fin_validator) %}{% endif %}
|
|
36
|
+
{% endif %}
|
|
30
37
|
{% if field.type == 'percentage' and 'validate_percentage' not in ns.needs_validators %}{% set _ = ns.needs_validators.append('validate_percentage') %}{% endif %}
|
|
31
38
|
{% if field.type == 'counter' and 'validate_non_negative_int' not in ns.needs_validators %}{% set _ = ns.needs_validators.append('validate_non_negative_int') %}{% endif %}
|
|
32
39
|
{% endif %}
|
|
@@ -173,7 +180,12 @@ class {{ entity_name }}Response(BaseModel):
|
|
|
173
180
|
{% for field_name, field in entity.fields.items() %}
|
|
174
181
|
{% if not field.api_exclude_response | default(false) %}
|
|
175
182
|
{% if field.type == 'financial' %}
|
|
176
|
-
{
|
|
183
|
+
{#- Choose validator by declared constraint: non-negative when constrained, signed otherwise -#}
|
|
184
|
+
{% set ctypes = (field.constraints | map(attribute='type') | list) if (field.constraints is defined and field.constraints) else [] %}
|
|
185
|
+
{% if 'non_negative' in ctypes or 'non_negative_or_null' in ctypes %}{% set fin_validator = 'validate_non_negative_decimal' %}
|
|
186
|
+
{% elif 'positive' in ctypes or 'positive_or_null' in ctypes %}{% set fin_validator = 'validate_positive_decimal' %}
|
|
187
|
+
{% else %}{% set fin_validator = 'validate_decimal' %}{% endif %}
|
|
188
|
+
{% set _ = validated_fields.append({'name': field_name, 'validator': fin_validator}) %}
|
|
177
189
|
{% elif field.type == 'percentage' %}
|
|
178
190
|
{% set _ = validated_fields.append({'name': field_name, 'validator': 'validate_percentage'}) %}
|
|
179
191
|
{% elif field.type == 'counter' %}
|
|
@@ -23,6 +23,23 @@ from typing import Any
|
|
|
23
23
|
{{ section_divider('DECIMAL VALIDATORS') }}
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def validate_decimal(value: Any) -> str | None:
|
|
27
|
+
"""Validate that a value is a well-formed decimal (sign-agnostic).
|
|
28
|
+
|
|
29
|
+
Accepts negative, zero, and positive values. Use this for signed
|
|
30
|
+
financial fields (e.g. PnL, returns) that may legitimately be negative.
|
|
31
|
+
"""
|
|
32
|
+
if value is None:
|
|
33
|
+
return value
|
|
34
|
+
try:
|
|
35
|
+
decimal_value = Decimal(str(value))
|
|
36
|
+
except InvalidOperation:
|
|
37
|
+
raise ValueError("Invalid decimal value") from None
|
|
38
|
+
if not decimal_value.is_finite():
|
|
39
|
+
raise ValueError("Value must be a finite decimal")
|
|
40
|
+
return str(value)
|
|
41
|
+
|
|
42
|
+
|
|
26
43
|
def validate_non_negative_decimal(value: Any) -> str | None:
|
|
27
44
|
"""Validate that a decimal value is non-negative (>= 0)."""
|
|
28
45
|
if value is None:
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/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.3
|
|
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.3
|
|
@@ -1106,6 +1106,193 @@ class TestApiModelsGeneratorPerEntity:
|
|
|
1106
1106
|
assert "CreatePostRequest" not in author_req
|
|
1107
1107
|
|
|
1108
1108
|
|
|
1109
|
+
@pytest.fixture
|
|
1110
|
+
def financial_model() -> dict[str, Any]:
|
|
1111
|
+
"""Single entity exercising signed vs constrained financial fields.
|
|
1112
|
+
|
|
1113
|
+
- unrealized_pnl: no constraint -> signed -> validate_decimal
|
|
1114
|
+
- balance: non_negative -> validate_non_negative_decimal
|
|
1115
|
+
- deposit: positive -> validate_positive_decimal
|
|
1116
|
+
"""
|
|
1117
|
+
return {
|
|
1118
|
+
"domain": "ledger",
|
|
1119
|
+
"description": "Ledger domain with signed and constrained money",
|
|
1120
|
+
"entities": {
|
|
1121
|
+
"Account": {
|
|
1122
|
+
"table": "accounts",
|
|
1123
|
+
"description": "Trading account",
|
|
1124
|
+
"fields": {
|
|
1125
|
+
"id": {
|
|
1126
|
+
"type": "uuid",
|
|
1127
|
+
"primary_key": True,
|
|
1128
|
+
"auto_generate": True,
|
|
1129
|
+
},
|
|
1130
|
+
"unrealized_pnl": {
|
|
1131
|
+
"type": "financial",
|
|
1132
|
+
"required": True,
|
|
1133
|
+
},
|
|
1134
|
+
"balance": {
|
|
1135
|
+
"type": "financial",
|
|
1136
|
+
"required": True,
|
|
1137
|
+
"constraints": [{"type": "non_negative"}],
|
|
1138
|
+
},
|
|
1139
|
+
"deposit": {
|
|
1140
|
+
"type": "financial",
|
|
1141
|
+
"required": True,
|
|
1142
|
+
"constraints": [{"type": "positive"}],
|
|
1143
|
+
},
|
|
1144
|
+
},
|
|
1145
|
+
"timestamps": {"created": True, "updated": True},
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
class TestFinancialValidatorSelection:
|
|
1152
|
+
"""Signed financial fields must not be forced non-negative.
|
|
1153
|
+
|
|
1154
|
+
Regression: the response/request templates hardcoded
|
|
1155
|
+
validate_non_negative_decimal for every financial field, so any
|
|
1156
|
+
legitimately negative value (PnL, returns) failed validation.
|
|
1157
|
+
"""
|
|
1158
|
+
|
|
1159
|
+
def _content(self, model: dict[str, Any], env_fixture: Any, suffix: str) -> str:
|
|
1160
|
+
project_root, config, env = env_fixture
|
|
1161
|
+
result = generate_api_models(model, config, env, project_root)
|
|
1162
|
+
assert isinstance(result, list)
|
|
1163
|
+
content = next(r["content"] for r in result if r["path"].name.endswith(suffix))
|
|
1164
|
+
assert isinstance(content, str)
|
|
1165
|
+
return content
|
|
1166
|
+
|
|
1167
|
+
def test_response_signed_financial_uses_validate_decimal(
|
|
1168
|
+
self, financial_model: dict[str, Any], project_env_per_entity: Any
|
|
1169
|
+
) -> None:
|
|
1170
|
+
content = self._content(
|
|
1171
|
+
financial_model, project_env_per_entity, "account_response.py"
|
|
1172
|
+
)
|
|
1173
|
+
# Unconstrained financial -> signed validator
|
|
1174
|
+
assert (
|
|
1175
|
+
'_validate_unrealized_pnl = field_validator("unrealized_pnl")'
|
|
1176
|
+
"(validate_decimal)" in content
|
|
1177
|
+
)
|
|
1178
|
+
# Constrained financial -> stays non-negative / positive
|
|
1179
|
+
assert (
|
|
1180
|
+
'_validate_balance = field_validator("balance")'
|
|
1181
|
+
"(validate_non_negative_decimal)" in content
|
|
1182
|
+
)
|
|
1183
|
+
assert (
|
|
1184
|
+
'_validate_deposit = field_validator("deposit")'
|
|
1185
|
+
"(validate_positive_decimal)" in content
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
def test_response_imports_match_validators_used(
|
|
1189
|
+
self, financial_model: dict[str, Any], project_env_per_entity: Any
|
|
1190
|
+
) -> None:
|
|
1191
|
+
content = self._content(
|
|
1192
|
+
financial_model, project_env_per_entity, "account_response.py"
|
|
1193
|
+
)
|
|
1194
|
+
import_line = next(
|
|
1195
|
+
line
|
|
1196
|
+
for line in content.splitlines()
|
|
1197
|
+
if line.startswith("from") and "validators import" in line
|
|
1198
|
+
)
|
|
1199
|
+
assert "validate_decimal" in import_line
|
|
1200
|
+
assert "validate_non_negative_decimal" in import_line
|
|
1201
|
+
assert "validate_positive_decimal" in import_line
|
|
1202
|
+
|
|
1203
|
+
def test_request_signed_financial_uses_validate_decimal(
|
|
1204
|
+
self, financial_model: dict[str, Any], project_env_per_entity: Any
|
|
1205
|
+
) -> None:
|
|
1206
|
+
content = self._content(
|
|
1207
|
+
financial_model, project_env_per_entity, "account_requests.py"
|
|
1208
|
+
)
|
|
1209
|
+
assert (
|
|
1210
|
+
'_validate_unrealized_pnl = field_validator("unrealized_pnl")'
|
|
1211
|
+
"(validate_decimal)" in content
|
|
1212
|
+
)
|
|
1213
|
+
assert (
|
|
1214
|
+
'_validate_balance = field_validator("balance")'
|
|
1215
|
+
"(validate_non_negative_decimal)" in content
|
|
1216
|
+
)
|
|
1217
|
+
assert (
|
|
1218
|
+
'_validate_deposit = field_validator("deposit")'
|
|
1219
|
+
"(validate_positive_decimal)" in content
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
def test_request_imports_match_validators_used(
|
|
1223
|
+
self, financial_model: dict[str, Any], project_env_per_entity: Any
|
|
1224
|
+
) -> None:
|
|
1225
|
+
content = self._content(
|
|
1226
|
+
financial_model, project_env_per_entity, "account_requests.py"
|
|
1227
|
+
)
|
|
1228
|
+
import_line = next(
|
|
1229
|
+
line
|
|
1230
|
+
for line in content.splitlines()
|
|
1231
|
+
if line.startswith("from") and "validators import" in line
|
|
1232
|
+
)
|
|
1233
|
+
assert "validate_decimal" in import_line
|
|
1234
|
+
assert "validate_non_negative_decimal" in import_line
|
|
1235
|
+
assert "validate_positive_decimal" in import_line
|
|
1236
|
+
|
|
1237
|
+
def test_validators_module_defines_validate_decimal(
|
|
1238
|
+
self, project_env_per_entity: Any
|
|
1239
|
+
) -> None:
|
|
1240
|
+
project_root, config, env = project_env_per_entity
|
|
1241
|
+
result = generate_validators(config, env, project_root)
|
|
1242
|
+
assert result is not None
|
|
1243
|
+
assert "def validate_decimal(value: Any) -> str | None:" in result["content"]
|
|
1244
|
+
|
|
1245
|
+
def test_validate_decimal_rejects_non_finite(
|
|
1246
|
+
self, project_env_per_entity: Any
|
|
1247
|
+
) -> None:
|
|
1248
|
+
"""The emitted validate_decimal must reject NaN/Infinity, not just
|
|
1249
|
+
malformed strings (financial fields cannot store non-finite values)."""
|
|
1250
|
+
project_root, config, env = project_env_per_entity
|
|
1251
|
+
result = generate_validators(config, env, project_root)
|
|
1252
|
+
assert result is not None
|
|
1253
|
+
ns: dict[str, Any] = {}
|
|
1254
|
+
exec(result["content"], ns)
|
|
1255
|
+
validate_decimal = ns["validate_decimal"]
|
|
1256
|
+
# Signed finite values pass.
|
|
1257
|
+
assert validate_decimal("-12.5") == "-12.5"
|
|
1258
|
+
assert validate_decimal(None) is None
|
|
1259
|
+
# Non-finite values are rejected.
|
|
1260
|
+
for bad in ("NaN", "Infinity", "-Infinity", "sNaN"):
|
|
1261
|
+
with pytest.raises(ValueError):
|
|
1262
|
+
validate_decimal(bad)
|
|
1263
|
+
with pytest.raises(ValueError):
|
|
1264
|
+
validate_decimal("not-a-number")
|
|
1265
|
+
|
|
1266
|
+
def test_empty_constraints_fall_back_to_signed(
|
|
1267
|
+
self, project_env_per_entity: Any
|
|
1268
|
+
) -> None:
|
|
1269
|
+
"""A financial field with an empty (falsy) `constraints` list falls
|
|
1270
|
+
back to the signed default rather than crashing on the `map` filter."""
|
|
1271
|
+
model = {
|
|
1272
|
+
"domain": "ledger",
|
|
1273
|
+
"entities": {
|
|
1274
|
+
"Account": {
|
|
1275
|
+
"table": "accounts",
|
|
1276
|
+
"fields": {
|
|
1277
|
+
"id": {
|
|
1278
|
+
"type": "uuid",
|
|
1279
|
+
"primary_key": True,
|
|
1280
|
+
"auto_generate": True,
|
|
1281
|
+
},
|
|
1282
|
+
"pnl": {
|
|
1283
|
+
"type": "financial",
|
|
1284
|
+
"required": True,
|
|
1285
|
+
"constraints": [],
|
|
1286
|
+
},
|
|
1287
|
+
},
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
}
|
|
1291
|
+
for suffix in ("account_response.py", "account_requests.py"):
|
|
1292
|
+
content = self._content(model, project_env_per_entity, suffix)
|
|
1293
|
+
assert '_validate_pnl = field_validator("pnl")(validate_decimal)' in content
|
|
1294
|
+
|
|
1295
|
+
|
|
1109
1296
|
class TestGenerateApiInitPerEntity:
|
|
1110
1297
|
"""Per-entity api __init__.py emission."""
|
|
1111
1298
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/__init__.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/api.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/database.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/enums.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/generators/migrations.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/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
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/constants.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/loaders.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/quality.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/utils/templates.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/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.2 → model_generator_kit-0.1.3}/src/model_generator/wizard/prompts.py
RENAMED
|
File without changes
|
{model_generator_kit-0.1.2 → model_generator_kit-0.1.3}/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
|