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.
Files changed (133) hide show
  1. {wry-0.3.2.dev2 → wry-0.4.1}/AI_KNOWLEDGE_BASE.md +191 -4
  2. {wry-0.3.2.dev2 → wry-0.4.1}/CHANGELOG.md +43 -0
  3. {wry-0.3.2.dev2 → wry-0.4.1}/PKG-INFO +112 -1
  4. {wry-0.3.2.dev2 → wry-0.4.1}/README.md +111 -0
  5. wry-0.4.1/tests/features/test_inheritance.py +661 -0
  6. {wry-0.3.2.dev2 → wry-0.4.1}/wry/_version.py +3 -3
  7. {wry-0.3.2.dev2 → wry-0.4.1}/wry/auto_model.py +3 -2
  8. {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/PKG-INFO +112 -1
  9. {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/SOURCES.txt +1 -0
  10. {wry-0.3.2.dev2 → wry-0.4.1}/.github/workflows/ci-cd.yml +0 -0
  11. {wry-0.3.2.dev2 → wry-0.4.1}/.gitignore +0 -0
  12. {wry-0.3.2.dev2 → wry-0.4.1}/.markdownlint.json +0 -0
  13. {wry-0.3.2.dev2 → wry-0.4.1}/.pre-commit-config.yaml +0 -0
  14. {wry-0.3.2.dev2 → wry-0.4.1}/LICENSE +0 -0
  15. {wry-0.3.2.dev2 → wry-0.4.1}/RELEASE_PROCESS.md +0 -0
  16. {wry-0.3.2.dev2 → wry-0.4.1}/TODO.md +0 -0
  17. {wry-0.3.2.dev2 → wry-0.4.1}/check.sh +0 -0
  18. {wry-0.3.2.dev2 → wry-0.4.1}/examples/autowrymodel_comprehensive.py +0 -0
  19. {wry-0.3.2.dev2 → wry-0.4.1}/examples/config.json +0 -0
  20. {wry-0.3.2.dev2 → wry-0.4.1}/examples/multimodel_comprehensive.py +0 -0
  21. {wry-0.3.2.dev2 → wry-0.4.1}/examples/sample_config.json +0 -0
  22. {wry-0.3.2.dev2 → wry-0.4.1}/examples/wrymodel_comprehensive.py +0 -0
  23. {wry-0.3.2.dev2 → wry-0.4.1}/pyproject.toml +0 -0
  24. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/README.md +0 -0
  25. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/extract_release_notes.py +0 -0
  26. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_all_versions.sh +0 -0
  27. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_ci_locally.sh +0 -0
  28. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/test_with_act.sh +0 -0
  29. {wry-0.3.2.dev2 → wry-0.4.1}/scripts/update-dependencies.sh +0 -0
  30. {wry-0.3.2.dev2 → wry-0.4.1}/setup.cfg +0 -0
  31. {wry-0.3.2.dev2 → wry-0.4.1}/tests/README.md +0 -0
  32. {wry-0.3.2.dev2 → wry-0.4.1}/tests/__init__.py +0 -0
  33. {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/__init__.py +0 -0
  34. {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_auto_model.py +0 -0
  35. {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_multi_model.py +0 -0
  36. {wry-0.3.2.dev2 → wry-0.4.1}/tests/features/test_source_precedence.py +0 -0
  37. {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/__init__.py +0 -0
  38. {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_edge_cases.py +0 -0
  39. {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_integration.py +0 -0
  40. {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_click_integration_extended.py +0 -0
  41. {wry-0.3.2.dev2 → wry-0.4.1}/tests/integration/test_context_handling.py +0 -0
  42. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/__init__.py +0 -0
  43. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/__init__.py +0 -0
  44. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  45. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_edge_cases.py +0 -0
  46. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  47. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  48. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_field_annotations.py +0 -0
  49. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/auto_model/test_type_inference.py +0 -0
  50. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  51. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/__init__.py +0 -0
  52. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_argument_types.py +0 -0
  53. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_bool_flag_handling.py +0 -0
  54. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_config_building.py +0 -0
  55. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  56. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
  57. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_interval_constraints.py +0 -0
  58. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_lambda_parsing.py +0 -0
  59. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_length_constraints.py +0 -0
  60. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_parameter_generation.py +0 -0
  61. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_click_predicate_handling.py +0 -0
  62. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_closure_extraction_errors.py +0 -0
  63. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_closure_handling.py +0 -0
  64. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_constraint_behavior.py +0 -0
  65. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  66. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_env_vars_option.py +0 -0
  67. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_explicit_argument_help_injection.py +0 -0
  68. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_field_alias_with_click_options.py +0 -0
  69. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_format_constraint_text.py +0 -0
  70. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_json_config_loading.py +0 -0
  71. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_lambda_behavior.py +0 -0
  72. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_lambda_error_handling.py +0 -0
  73. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_predicate_source_errors.py +0 -0
  74. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_strict_mode_errors.py +0 -0
  75. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/click/test_type_handling.py +0 -0
  76. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/__init__.py +0 -0
  77. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_accessors.py +0 -0
  78. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_advanced_features.py +0 -0
  79. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_core.py +0 -0
  80. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_edge_cases.py +0 -0
  81. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_env_utils.py +0 -0
  82. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  83. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_utils.py +0 -0
  84. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  85. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_sources.py +0 -0
  86. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/core/test_type_checking_blocks.py +0 -0
  87. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/__init__.py +0 -0
  88. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_accessor_caching.py +0 -0
  89. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_extract_edge_cases.py +0 -0
  90. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_extract_subset_edge_cases.py +0 -0
  91. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_click_context_handling.py +0 -0
  92. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_data_extraction.py +0 -0
  93. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_default_handling.py +0 -0
  94. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_edge_cases.py +0 -0
  95. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_environment_integration.py +0 -0
  96. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  97. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_extraction_methods.py +0 -0
  98. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_field_errors.py +0 -0
  99. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_model_object_extraction.py +0 -0
  100. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  101. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  102. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/__init__.py +0 -0
  103. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/test_multi_model.py +0 -0
  104. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/multi_model/test_type_checking.py +0 -0
  105. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_argument_help_injection.py +0 -0
  106. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_auto_model_field_processing.py +0 -0
  107. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_comprehensive_imports.py +0 -0
  108. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_exclude_enum.py +0 -0
  109. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_generate_click_classmethod.py +0 -0
  110. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_help_system.py +0 -0
  111. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init.py +0 -0
  112. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init_edge_cases.py +0 -0
  113. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_init_version_edge_cases.py +0 -0
  114. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_model_extraction_methods.py +0 -0
  115. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_multiple_option_bug.py +0 -0
  116. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_type_checking_imports.py +0 -0
  117. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_variadic_argument_bug.py +0 -0
  118. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_version_fallback.py +0 -0
  119. {wry-0.3.2.dev2 → wry-0.4.1}/tests/unit/test_version_parsing.py +0 -0
  120. {wry-0.3.2.dev2 → wry-0.4.1}/wry/__init__.py +0 -0
  121. {wry-0.3.2.dev2 → wry-0.4.1}/wry/click_integration.py +0 -0
  122. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/__init__.py +0 -0
  123. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/accessors.py +0 -0
  124. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/env_utils.py +0 -0
  125. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/field_utils.py +0 -0
  126. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/model.py +0 -0
  127. {wry-0.3.2.dev2 → wry-0.4.1}/wry/core/sources.py +0 -0
  128. {wry-0.3.2.dev2 → wry-0.4.1}/wry/help_system.py +0 -0
  129. {wry-0.3.2.dev2 → wry-0.4.1}/wry/multi_model.py +0 -0
  130. {wry-0.3.2.dev2 → wry-0.4.1}/wry/py.typed +0 -0
  131. {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/dependency_links.txt +0 -0
  132. {wry-0.3.2.dev2 → wry-0.4.1}/wry.egg-info/requires.txt +0 -0
  133. {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**: 407 (all passing)
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**: 50+ test files
1247
- - **Lines of Test Code**: ~3000 lines
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.2.dev2
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