wry 0.3.2.dev2__tar.gz → 0.4.1__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.
- {wry-0.3.2.dev2 → wry-0.4.1}/AI_KNOWLEDGE_BASE.md +191 -4
- {wry-0.3.2.dev2 → wry-0.4.1}/CHANGELOG.md +43 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/PKG-INFO +112 -1
- {wry-0.3.2.dev2 → wry-0.4.1}/README.md +111 -0
- wry-0.4.1/tests/features/test_inheritance.py +661 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/_version.py +3 -3
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/auto_model.py +3 -2
- {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/PKG-INFO +112 -1
- {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/SOURCES.txt +1 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/.github/workflows/ci-cd.yml +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/.gitignore +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/.markdownlint.json +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/.pre-commit-config.yaml +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/LICENSE +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/RELEASE_PROCESS.md +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/TODO.md +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/check.sh +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/examples/autowrymodel_comprehensive.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/examples/config.json +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/examples/multimodel_comprehensive.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/examples/sample_config.json +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/examples/wrymodel_comprehensive.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/pyproject.toml +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/README.md +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/extract_release_notes.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_all_versions.sh +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_ci_locally.sh +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_with_act.sh +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/scripts/update-dependencies.sh +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/setup.cfg +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/README.md +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_auto_model.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_multi_model.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_source_precedence.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_integration.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_integration_extended.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_context_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_field_annotations.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_type_inference.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_argument_types.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_bool_flag_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_config_building.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_constraint_formatting.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_interval_constraints.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_lambda_parsing.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_length_constraints.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_parameter_generation.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_predicate_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_closure_extraction_errors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_closure_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_constraint_behavior.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_constraint_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_env_vars_option.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_explicit_argument_help_injection.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_field_alias_with_click_options.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_format_constraint_text.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_json_config_loading.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_lambda_behavior.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_lambda_error_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_predicate_source_errors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_strict_mode_errors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_type_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_accessors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_advanced_features.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_core.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_env_utils.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_constraint_extraction.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_utils.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_sources.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_type_checking_blocks.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_accessor_caching.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_extract_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_extract_subset_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_click_context_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_data_extraction.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_default_handling.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_environment_integration.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_extraction_methods.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_field_errors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_object_extraction.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_object_attribute_extraction.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/test_multi_model.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/test_type_checking.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_argument_help_injection.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_auto_model_field_processing.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_comprehensive_imports.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_exclude_enum.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_generate_click_classmethod.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_help_system.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init_version_edge_cases.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_model_extraction_methods.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_multiple_option_bug.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_type_checking_imports.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_variadic_argument_bug.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_version_fallback.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_version_parsing.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/click_integration.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/__init__.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/accessors.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/env_utils.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/field_utils.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/model.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/sources.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/help_system.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/multi_model.py +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry/py.typed +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/dependency_links.txt +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/requires.txt +0 -0
- {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/top_level.txt +0 -0
|
@@ -1234,22 +1234,209 @@ class Config(AutoWryModel):
|
|
|
1234
1234
|
|
|
1235
1235
|
See `examples/autowrymodel_comprehensive.py` and `examples/wrymodel_comprehensive.py` for complete examples.
|
|
1236
1236
|
|
|
1237
|
+
### Pattern 8: Model Inheritance (v0.3.3+)
|
|
1238
|
+
|
|
1239
|
+
**New in v0.3.3**: Full support for inheriting from `WryModel` and `AutoWryModel` classes!
|
|
1240
|
+
|
|
1241
|
+
#### Basic Inheritance
|
|
1242
|
+
|
|
1243
|
+
```python
|
|
1244
|
+
from wry import AutoWryModel
|
|
1245
|
+
from pydantic import Field
|
|
1246
|
+
|
|
1247
|
+
class BaseConfig(AutoWryModel):
|
|
1248
|
+
"""Common configuration shared across all environments."""
|
|
1249
|
+
env_prefix = "APP_"
|
|
1250
|
+
debug: bool = Field(default=False, description="Enable debug mode")
|
|
1251
|
+
log_level: str = Field(default="INFO", description="Logging level")
|
|
1252
|
+
|
|
1253
|
+
class ProductionConfig(BaseConfig):
|
|
1254
|
+
"""Production-specific configuration."""
|
|
1255
|
+
workers: int = Field(default=4, ge=1, le=32, description="Number of workers")
|
|
1256
|
+
timeout: int = Field(default=30, ge=1, description="Request timeout")
|
|
1257
|
+
|
|
1258
|
+
# ProductionConfig has ALL fields: debug, log_level, workers, timeout
|
|
1259
|
+
@click.command()
|
|
1260
|
+
@ProductionConfig.generate_click_parameters()
|
|
1261
|
+
@click.pass_context
|
|
1262
|
+
def serve(ctx: click.Context, **kwargs: Any):
|
|
1263
|
+
config = ProductionConfig.from_click_context(ctx, **kwargs)
|
|
1264
|
+
print(f"Workers: {config.workers}, Debug: {config.debug}")
|
|
1265
|
+
print(f"Log level: {config.log_level}")
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
**CLI Output:**
|
|
1269
|
+
|
|
1270
|
+
```bash
|
|
1271
|
+
$ python app.py --help
|
|
1272
|
+
Options:
|
|
1273
|
+
--debug / --no-debug Enable debug mode [default: False]
|
|
1274
|
+
--log-level TEXT Logging level [default: INFO]
|
|
1275
|
+
--workers INTEGER Number of workers [default: 4]
|
|
1276
|
+
--timeout INTEGER Request timeout [default: 30]
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
#### Multiple Inheritance Levels
|
|
1280
|
+
|
|
1281
|
+
```python
|
|
1282
|
+
class Level1(AutoWryModel):
|
|
1283
|
+
field1: str = Field(default="v1", description="Level 1")
|
|
1284
|
+
|
|
1285
|
+
class Level2(Level1):
|
|
1286
|
+
field2: str = Field(default="v2", description="Level 2")
|
|
1287
|
+
|
|
1288
|
+
class Level3(Level2):
|
|
1289
|
+
field3: str = Field(default="v3", description="Level 3")
|
|
1290
|
+
|
|
1291
|
+
# Level3 has: field1, field2, field3
|
|
1292
|
+
config = Level3()
|
|
1293
|
+
assert config.field1 == "v1"
|
|
1294
|
+
assert config.field2 == "v2"
|
|
1295
|
+
assert config.field3 == "v3"
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
#### Inheritance with Multi-Model
|
|
1299
|
+
|
|
1300
|
+
```python
|
|
1301
|
+
from wry import multi_model, create_models
|
|
1302
|
+
|
|
1303
|
+
class BaseServer(AutoWryModel):
|
|
1304
|
+
host: str = Field(default="localhost", description="Host")
|
|
1305
|
+
|
|
1306
|
+
class ProductionServer(BaseServer):
|
|
1307
|
+
port: int = Field(default=8080, description="Port")
|
|
1308
|
+
ssl_enabled: bool = Field(default=True, description="Enable SSL")
|
|
1309
|
+
|
|
1310
|
+
class BaseDatabase(AutoWryModel):
|
|
1311
|
+
db_url: str = Field(default="sqlite:///app.db", description="Database URL")
|
|
1312
|
+
|
|
1313
|
+
class ProductionDatabase(BaseDatabase):
|
|
1314
|
+
pool_size: int = Field(default=10, description="Connection pool size")
|
|
1315
|
+
timeout: int = Field(default=30, description="Query timeout")
|
|
1316
|
+
|
|
1317
|
+
@click.command()
|
|
1318
|
+
@multi_model(ProductionServer, ProductionDatabase)
|
|
1319
|
+
@click.pass_context
|
|
1320
|
+
def deploy(ctx: click.Context, **kwargs: Any):
|
|
1321
|
+
configs = create_models(ctx, kwargs, ProductionServer, ProductionDatabase)
|
|
1322
|
+
server = configs[ProductionServer]
|
|
1323
|
+
db = configs[ProductionDatabase]
|
|
1324
|
+
|
|
1325
|
+
# All inherited fields available
|
|
1326
|
+
print(f"Server: {server.host}:{server.port} (SSL: {server.ssl_enabled})")
|
|
1327
|
+
print(f"Database: {db.db_url} (pool={db.pool_size}, timeout={db.timeout})")
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
#### WryModel Inheritance
|
|
1331
|
+
|
|
1332
|
+
```python
|
|
1333
|
+
from wry import WryModel, AutoOption
|
|
1334
|
+
from typing import Annotated
|
|
1335
|
+
|
|
1336
|
+
class BaseConfig(WryModel):
|
|
1337
|
+
base_field: Annotated[str, AutoOption] = Field(default="base")
|
|
1338
|
+
|
|
1339
|
+
class ChildConfig(BaseConfig):
|
|
1340
|
+
child_field: Annotated[str, AutoOption] = Field(default="child")
|
|
1341
|
+
|
|
1342
|
+
# Both fields available with explicit annotations
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
#### Mixing WryModel and AutoWryModel
|
|
1346
|
+
|
|
1347
|
+
```python
|
|
1348
|
+
class BaseWry(WryModel):
|
|
1349
|
+
# Explicit annotation required
|
|
1350
|
+
explicit_field: Annotated[str, AutoOption] = Field(default="explicit")
|
|
1351
|
+
|
|
1352
|
+
class ChildAuto(BaseWry, AutoWryModel):
|
|
1353
|
+
# This automatically becomes an option (AutoWryModel processing)
|
|
1354
|
+
auto_field: str = Field(default="auto")
|
|
1355
|
+
|
|
1356
|
+
# Both fields work in CLI
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
#### Use Cases for Inheritance
|
|
1360
|
+
|
|
1361
|
+
1. **Environment-Specific Configs:**
|
|
1362
|
+
|
|
1363
|
+
```python
|
|
1364
|
+
class BaseConfig(AutoWryModel):
|
|
1365
|
+
app_name: str
|
|
1366
|
+
debug: bool = False
|
|
1367
|
+
|
|
1368
|
+
class DevConfig(BaseConfig):
|
|
1369
|
+
hot_reload: bool = True
|
|
1370
|
+
|
|
1371
|
+
class ProdConfig(BaseConfig):
|
|
1372
|
+
workers: int = 4
|
|
1373
|
+
ssl_cert: str
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
2. **Feature Flags:**
|
|
1377
|
+
|
|
1378
|
+
```python
|
|
1379
|
+
class CoreFeatures(AutoWryModel):
|
|
1380
|
+
feature_a: bool = True
|
|
1381
|
+
feature_b: bool = True
|
|
1382
|
+
|
|
1383
|
+
class BetaFeatures(CoreFeatures):
|
|
1384
|
+
experimental_feature: bool = False
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
3. **Shared Authentication:**
|
|
1388
|
+
|
|
1389
|
+
```python
|
|
1390
|
+
class BaseAuthConfig(AutoWryModel):
|
|
1391
|
+
api_key: str
|
|
1392
|
+
timeout: int = 30
|
|
1393
|
+
|
|
1394
|
+
class ServiceAConfig(BaseAuthConfig):
|
|
1395
|
+
service_a_endpoint: str
|
|
1396
|
+
|
|
1397
|
+
class ServiceBConfig(BaseAuthConfig):
|
|
1398
|
+
service_b_endpoint: str
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
**Key Features:**
|
|
1402
|
+
|
|
1403
|
+
- ✅ All inherited fields automatically become CLI options
|
|
1404
|
+
- ✅ Source tracking works correctly for inherited fields
|
|
1405
|
+
- ✅ `env_prefix` can be inherited or overridden in child classes
|
|
1406
|
+
- ✅ Field constraints and validation are inherited
|
|
1407
|
+
- ✅ Works with all wry features: arguments, options, exclusions, aliases
|
|
1408
|
+
- ✅ Multiple inheritance supported
|
|
1409
|
+
- ✅ Deep inheritance chains work correctly
|
|
1410
|
+
|
|
1411
|
+
**Technical Details:**
|
|
1412
|
+
|
|
1413
|
+
The inheritance bug was fixed in v0.3.3. Previously, `__init_subclass__` used `hasattr()` which checked the entire inheritance chain, causing child class fields to be skipped. Now it uses `"_autowrymodel_processed" in cls.__dict__` to check only the current class.
|
|
1414
|
+
|
|
1415
|
+
**Tests:** See `tests/features/test_inheritance.py` for 24 comprehensive tests covering all inheritance scenarios.
|
|
1416
|
+
|
|
1237
1417
|
---
|
|
1238
1418
|
|
|
1239
1419
|
## Test Coverage Details
|
|
1240
1420
|
|
|
1241
1421
|
### Test Statistics
|
|
1242
1422
|
|
|
1243
|
-
- **Total Tests**:
|
|
1244
|
-
- **Total Coverage**: 92
|
|
1423
|
+
- **Total Tests**: 431+ (all passing, includes 24 inheritance tests added in v0.3.3)
|
|
1424
|
+
- **Total Coverage**: 92%+
|
|
1245
1425
|
- **Core Modules**: 100% coverage (sources, accessors, env_utils, multi_model)
|
|
1246
|
-
- **Test Files**:
|
|
1247
|
-
- **Lines of Test Code**: ~
|
|
1426
|
+
- **Test Files**: 51+ test files (including test_inheritance.py)
|
|
1427
|
+
- **Lines of Test Code**: ~3600+ lines
|
|
1248
1428
|
|
|
1249
1429
|
### Critical Test Files
|
|
1250
1430
|
|
|
1251
1431
|
**`tests/features/test_source_precedence.py`** ⭐ MOST IMPORTANT
|
|
1252
1432
|
|
|
1433
|
+
**`tests/features/test_inheritance.py`** ⭐ NEW IN v0.3.3
|
|
1434
|
+
|
|
1435
|
+
- 24 comprehensive inheritance tests
|
|
1436
|
+
- Covers AutoWryModel, WryModel, and multi-model inheritance
|
|
1437
|
+
- Tests multiple inheritance levels, source tracking, mixed model types
|
|
1438
|
+
- Validates the inheritance bug fix
|
|
1439
|
+
|
|
1253
1440
|
```python
|
|
1254
1441
|
class TestSourcePrecedence:
|
|
1255
1442
|
def test_complete_precedence_chain(self):
|
|
@@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2025-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **AutoWryModel inheritance bug** 🐛
|
|
15
|
+
- Fixed critical bug where child classes inheriting from `AutoWryModel` were not processing their own fields
|
|
16
|
+
- Previously used `hasattr()` which checked the entire inheritance chain, causing child class fields to be skipped
|
|
17
|
+
- Now uses `"_autowrymodel_processed" in cls.__dict__` to check only the current class
|
|
18
|
+
- All inherited fields now correctly become CLI options in child classes
|
|
19
|
+
- Multiple levels of inheritance now work correctly
|
|
20
|
+
- Note: `WryModel` was not affected by this bug and has always supported inheritance correctly
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Comprehensive inheritance test suite** (24 new tests in `tests/features/test_inheritance.py`)
|
|
25
|
+
- Tests for `AutoWryModel` inheritance (10 tests - verify the bug fix)
|
|
26
|
+
- Tests for `WryModel` inheritance (6 tests - verify continued functionality)
|
|
27
|
+
- Tests for multi-model inheritance (5 tests - mix of both types)
|
|
28
|
+
- Edge case tests (3 tests - various scenarios)
|
|
29
|
+
- Coverage includes: basic inheritance, multiple levels, CLI generation, source tracking, constraints, env_prefix, mixed model types, and more
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- **New inheritance section in README.md**
|
|
34
|
+
- Basic inheritance examples
|
|
35
|
+
- Multiple levels of inheritance
|
|
36
|
+
- Inheritance with multi-model
|
|
37
|
+
- Mixing `WryModel` and `AutoWryModel`
|
|
38
|
+
- Use cases and best practices
|
|
39
|
+
|
|
40
|
+
- **New Pattern 8 in AI_KNOWLEDGE_BASE.md**
|
|
41
|
+
- Comprehensive inheritance patterns and examples
|
|
42
|
+
- Technical details about the bug fix
|
|
43
|
+
- Use cases: environment-specific configs, feature flags, shared authentication
|
|
44
|
+
- Updated test statistics (407 → 431+ tests)
|
|
45
|
+
|
|
46
|
+
### Technical Details
|
|
47
|
+
|
|
48
|
+
- Bug was in `AutoWryModel.__init_subclass__()` at line 46 of `wry/auto_model.py`
|
|
49
|
+
- Changed from checking inherited attributes to checking class-specific attributes
|
|
50
|
+
- This enables proper field processing for each class in an inheritance hierarchy
|
|
51
|
+
- All existing tests continue to pass (no breaking changes)
|
|
52
|
+
|
|
10
53
|
## [0.4.0] - 2025-10-04
|
|
11
54
|
|
|
12
55
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wry
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Why Repeat Yourself? - Define your CLI once with Pydantic models
|
|
5
5
|
Author-email: Tyler House <26489166+tahouse@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -101,6 +101,7 @@ if __name__ == "__main__":
|
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
**See comprehensive examples:**
|
|
104
|
+
|
|
104
105
|
- `examples/autowrymodel_comprehensive.py` - All AutoWryModel features including aliases
|
|
105
106
|
- `examples/wrymodel_comprehensive.py` - WryModel with source tracking
|
|
106
107
|
- `examples/multimodel_comprehensive.py` - Multi-model usage
|
|
@@ -462,9 +463,119 @@ class Config(AutoWryModel):
|
|
|
462
463
|
See `examples/autowrymodel_comprehensive.py` for examples of explicit Click decorators.
|
|
463
464
|
|
|
464
465
|
**See also:**
|
|
466
|
+
|
|
465
467
|
- `examples/autowrymodel_comprehensive.py` - Complete AutoWryModel example with aliases
|
|
466
468
|
- `examples/wrymodel_comprehensive.py` - WryModel with aliases and source tracking
|
|
467
469
|
|
|
470
|
+
### Model Inheritance
|
|
471
|
+
|
|
472
|
+
**New in v0.3.3+**: Both `WryModel` and `AutoWryModel` fully support inheritance! Create base configuration classes and extend them with additional fields.
|
|
473
|
+
|
|
474
|
+
#### Basic Inheritance
|
|
475
|
+
|
|
476
|
+
```python
|
|
477
|
+
from wry import AutoWryModel
|
|
478
|
+
from pydantic import Field
|
|
479
|
+
|
|
480
|
+
class BaseConfig(AutoWryModel):
|
|
481
|
+
"""Common configuration shared across environments."""
|
|
482
|
+
env_prefix = "APP_"
|
|
483
|
+
debug: bool = Field(default=False, description="Enable debug mode")
|
|
484
|
+
log_level: str = Field(default="INFO", description="Logging level")
|
|
485
|
+
|
|
486
|
+
class ProductionConfig(BaseConfig):
|
|
487
|
+
"""Production-specific configuration."""
|
|
488
|
+
workers: int = Field(default=4, ge=1, le=32, description="Number of workers")
|
|
489
|
+
timeout: int = Field(default=30, ge=1, description="Request timeout")
|
|
490
|
+
|
|
491
|
+
# ProductionConfig has all fields: debug, log_level, workers, timeout
|
|
492
|
+
@click.command()
|
|
493
|
+
@ProductionConfig.generate_click_parameters()
|
|
494
|
+
@click.pass_context
|
|
495
|
+
def serve(ctx: click.Context, **kwargs: Any):
|
|
496
|
+
config = ProductionConfig.from_click_context(ctx, **kwargs)
|
|
497
|
+
click.echo(f"Starting with {config.workers} workers, debug={config.debug}")
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
#### Multiple Levels of Inheritance
|
|
501
|
+
|
|
502
|
+
```python
|
|
503
|
+
class Level1Config(AutoWryModel):
|
|
504
|
+
field1: str = Field(default="v1", description="Level 1 field")
|
|
505
|
+
|
|
506
|
+
class Level2Config(Level1Config):
|
|
507
|
+
field2: str = Field(default="v2", description="Level 2 field")
|
|
508
|
+
|
|
509
|
+
class Level3Config(Level2Config):
|
|
510
|
+
field3: str = Field(default="v3", description="Level 3 field")
|
|
511
|
+
|
|
512
|
+
# Level3Config has all fields: field1, field2, field3
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### Inheritance with Multi-Model
|
|
516
|
+
|
|
517
|
+
```python
|
|
518
|
+
from wry import multi_model, create_models
|
|
519
|
+
|
|
520
|
+
class BaseServer(AutoWryModel):
|
|
521
|
+
host: str = Field(default="localhost", description="Server host")
|
|
522
|
+
|
|
523
|
+
class ExtendedServer(BaseServer):
|
|
524
|
+
port: int = Field(default=8080, description="Server port")
|
|
525
|
+
|
|
526
|
+
class BaseDatabase(AutoWryModel):
|
|
527
|
+
db_url: str = Field(default="sqlite:///app.db", description="Database URL")
|
|
528
|
+
|
|
529
|
+
class ExtendedDatabase(BaseDatabase):
|
|
530
|
+
pool_size: int = Field(default=5, description="Connection pool size")
|
|
531
|
+
|
|
532
|
+
@click.command()
|
|
533
|
+
@multi_model(ExtendedServer, ExtendedDatabase)
|
|
534
|
+
@click.pass_context
|
|
535
|
+
def main(ctx: click.Context, **kwargs: Any):
|
|
536
|
+
configs = create_models(ctx, kwargs, ExtendedServer, ExtendedDatabase)
|
|
537
|
+
server = configs[ExtendedServer]
|
|
538
|
+
db = configs[ExtendedDatabase]
|
|
539
|
+
|
|
540
|
+
# Both base and extended fields are available
|
|
541
|
+
click.echo(f"Server: {server.host}:{server.port}")
|
|
542
|
+
click.echo(f"Database: {db.db_url} (pool={db.pool_size})")
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
#### Mixing WryModel and AutoWryModel
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
from typing import Annotated
|
|
549
|
+
from wry import WryModel, AutoWryModel, AutoOption
|
|
550
|
+
|
|
551
|
+
# Start with explicit WryModel
|
|
552
|
+
class BaseConfig(WryModel):
|
|
553
|
+
base_field: Annotated[str, AutoOption] = Field(default="base")
|
|
554
|
+
|
|
555
|
+
# Extend with AutoWryModel for automatic options
|
|
556
|
+
class ChildConfig(BaseConfig, AutoWryModel):
|
|
557
|
+
# This automatically becomes an option
|
|
558
|
+
child_field: str = Field(default="child")
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
#### Benefits of Inheritance
|
|
562
|
+
|
|
563
|
+
1. **DRY Principle**: Define common fields once
|
|
564
|
+
2. **Environment-Specific Configs**: Base config + dev/staging/prod extensions
|
|
565
|
+
3. **Feature Flags**: Base config + feature-specific configurations
|
|
566
|
+
4. **Reusable Components**: Create libraries of configuration classes
|
|
567
|
+
5. **Full Source Tracking**: Inherited fields track their sources correctly
|
|
568
|
+
|
|
569
|
+
**Important Notes:**
|
|
570
|
+
|
|
571
|
+
- All inherited fields automatically become CLI options (for `AutoWryModel`)
|
|
572
|
+
- Source tracking works correctly for inherited fields
|
|
573
|
+
- `env_prefix` can be inherited or overridden
|
|
574
|
+
- Field constraints and validation are inherited
|
|
575
|
+
- Works with all wry features: arguments, options, exclusions, aliases
|
|
576
|
+
|
|
577
|
+
See `tests/features/test_inheritance.py` for comprehensive examples.
|
|
578
|
+
|
|
468
579
|
## Development
|
|
469
580
|
|
|
470
581
|
### Prerequisites
|
|
@@ -57,6 +57,7 @@ if __name__ == "__main__":
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
**See comprehensive examples:**
|
|
60
|
+
|
|
60
61
|
- `examples/autowrymodel_comprehensive.py` - All AutoWryModel features including aliases
|
|
61
62
|
- `examples/wrymodel_comprehensive.py` - WryModel with source tracking
|
|
62
63
|
- `examples/multimodel_comprehensive.py` - Multi-model usage
|
|
@@ -418,9 +419,119 @@ class Config(AutoWryModel):
|
|
|
418
419
|
See `examples/autowrymodel_comprehensive.py` for examples of explicit Click decorators.
|
|
419
420
|
|
|
420
421
|
**See also:**
|
|
422
|
+
|
|
421
423
|
- `examples/autowrymodel_comprehensive.py` - Complete AutoWryModel example with aliases
|
|
422
424
|
- `examples/wrymodel_comprehensive.py` - WryModel with aliases and source tracking
|
|
423
425
|
|
|
426
|
+
### Model Inheritance
|
|
427
|
+
|
|
428
|
+
**New in v0.3.3+**: Both `WryModel` and `AutoWryModel` fully support inheritance! Create base configuration classes and extend them with additional fields.
|
|
429
|
+
|
|
430
|
+
#### Basic Inheritance
|
|
431
|
+
|
|
432
|
+
```python
|
|
433
|
+
from wry import AutoWryModel
|
|
434
|
+
from pydantic import Field
|
|
435
|
+
|
|
436
|
+
class BaseConfig(AutoWryModel):
|
|
437
|
+
"""Common configuration shared across environments."""
|
|
438
|
+
env_prefix = "APP_"
|
|
439
|
+
debug: bool = Field(default=False, description="Enable debug mode")
|
|
440
|
+
log_level: str = Field(default="INFO", description="Logging level")
|
|
441
|
+
|
|
442
|
+
class ProductionConfig(BaseConfig):
|
|
443
|
+
"""Production-specific configuration."""
|
|
444
|
+
workers: int = Field(default=4, ge=1, le=32, description="Number of workers")
|
|
445
|
+
timeout: int = Field(default=30, ge=1, description="Request timeout")
|
|
446
|
+
|
|
447
|
+
# ProductionConfig has all fields: debug, log_level, workers, timeout
|
|
448
|
+
@click.command()
|
|
449
|
+
@ProductionConfig.generate_click_parameters()
|
|
450
|
+
@click.pass_context
|
|
451
|
+
def serve(ctx: click.Context, **kwargs: Any):
|
|
452
|
+
config = ProductionConfig.from_click_context(ctx, **kwargs)
|
|
453
|
+
click.echo(f"Starting with {config.workers} workers, debug={config.debug}")
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Multiple Levels of Inheritance
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
class Level1Config(AutoWryModel):
|
|
460
|
+
field1: str = Field(default="v1", description="Level 1 field")
|
|
461
|
+
|
|
462
|
+
class Level2Config(Level1Config):
|
|
463
|
+
field2: str = Field(default="v2", description="Level 2 field")
|
|
464
|
+
|
|
465
|
+
class Level3Config(Level2Config):
|
|
466
|
+
field3: str = Field(default="v3", description="Level 3 field")
|
|
467
|
+
|
|
468
|
+
# Level3Config has all fields: field1, field2, field3
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### Inheritance with Multi-Model
|
|
472
|
+
|
|
473
|
+
```python
|
|
474
|
+
from wry import multi_model, create_models
|
|
475
|
+
|
|
476
|
+
class BaseServer(AutoWryModel):
|
|
477
|
+
host: str = Field(default="localhost", description="Server host")
|
|
478
|
+
|
|
479
|
+
class ExtendedServer(BaseServer):
|
|
480
|
+
port: int = Field(default=8080, description="Server port")
|
|
481
|
+
|
|
482
|
+
class BaseDatabase(AutoWryModel):
|
|
483
|
+
db_url: str = Field(default="sqlite:///app.db", description="Database URL")
|
|
484
|
+
|
|
485
|
+
class ExtendedDatabase(BaseDatabase):
|
|
486
|
+
pool_size: int = Field(default=5, description="Connection pool size")
|
|
487
|
+
|
|
488
|
+
@click.command()
|
|
489
|
+
@multi_model(ExtendedServer, ExtendedDatabase)
|
|
490
|
+
@click.pass_context
|
|
491
|
+
def main(ctx: click.Context, **kwargs: Any):
|
|
492
|
+
configs = create_models(ctx, kwargs, ExtendedServer, ExtendedDatabase)
|
|
493
|
+
server = configs[ExtendedServer]
|
|
494
|
+
db = configs[ExtendedDatabase]
|
|
495
|
+
|
|
496
|
+
# Both base and extended fields are available
|
|
497
|
+
click.echo(f"Server: {server.host}:{server.port}")
|
|
498
|
+
click.echo(f"Database: {db.db_url} (pool={db.pool_size})")
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### Mixing WryModel and AutoWryModel
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
from typing import Annotated
|
|
505
|
+
from wry import WryModel, AutoWryModel, AutoOption
|
|
506
|
+
|
|
507
|
+
# Start with explicit WryModel
|
|
508
|
+
class BaseConfig(WryModel):
|
|
509
|
+
base_field: Annotated[str, AutoOption] = Field(default="base")
|
|
510
|
+
|
|
511
|
+
# Extend with AutoWryModel for automatic options
|
|
512
|
+
class ChildConfig(BaseConfig, AutoWryModel):
|
|
513
|
+
# This automatically becomes an option
|
|
514
|
+
child_field: str = Field(default="child")
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### Benefits of Inheritance
|
|
518
|
+
|
|
519
|
+
1. **DRY Principle**: Define common fields once
|
|
520
|
+
2. **Environment-Specific Configs**: Base config + dev/staging/prod extensions
|
|
521
|
+
3. **Feature Flags**: Base config + feature-specific configurations
|
|
522
|
+
4. **Reusable Components**: Create libraries of configuration classes
|
|
523
|
+
5. **Full Source Tracking**: Inherited fields track their sources correctly
|
|
524
|
+
|
|
525
|
+
**Important Notes:**
|
|
526
|
+
|
|
527
|
+
- All inherited fields automatically become CLI options (for `AutoWryModel`)
|
|
528
|
+
- Source tracking works correctly for inherited fields
|
|
529
|
+
- `env_prefix` can be inherited or overridden
|
|
530
|
+
- Field constraints and validation are inherited
|
|
531
|
+
- Works with all wry features: arguments, options, exclusions, aliases
|
|
532
|
+
|
|
533
|
+
See `tests/features/test_inheritance.py` for comprehensive examples.
|
|
534
|
+
|
|
424
535
|
## Development
|
|
425
536
|
|
|
426
537
|
### Prerequisites
|