wry 0.3.1.dev0__tar.gz → 0.3.2.dev2__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 (144) hide show
  1. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/.gitignore +3 -0
  2. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/AI_KNOWLEDGE_BASE.md +131 -34
  3. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/CHANGELOG.md +57 -0
  4. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/PKG-INFO +83 -9
  5. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/README.md +82 -8
  6. wry-0.3.2.dev2/TODO.md +184 -0
  7. wry-0.3.2.dev2/examples/autowrymodel_comprehensive.py +247 -0
  8. wry-0.3.1.dev0/examples/multi_model_example.py → wry-0.3.2.dev2/examples/multimodel_comprehensive.py +4 -4
  9. wry-0.3.2.dev2/examples/wrymodel_comprehensive.py +202 -0
  10. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/pyproject.toml +5 -6
  11. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/features/test_auto_model.py +5 -5
  12. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/features/test_multi_model.py +7 -7
  13. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/features/test_source_precedence.py +8 -13
  14. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/integration/test_click_edge_cases.py +6 -6
  15. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/integration/test_click_integration.py +7 -7
  16. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/integration/test_click_integration_extended.py +15 -14
  17. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/integration/test_context_handling.py +6 -6
  18. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_auto_model_edge_cases.py +4 -4
  19. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_field_annotations.py +10 -12
  20. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_argument_types.py +6 -6
  21. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_bool_flag_handling.py +3 -3
  22. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_decorator_edge_cases.py +5 -5
  23. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_parameter_generation.py +4 -4
  24. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_closure_extraction_errors.py +4 -4
  25. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_closure_handling.py +4 -4
  26. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_env_vars_option.py +4 -4
  27. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_explicit_argument_help_injection.py +5 -5
  28. wry-0.3.2.dev2/tests/unit/click/test_field_alias_with_click_options.py +671 -0
  29. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_format_constraint_text.py +12 -12
  30. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_json_config_loading.py +4 -4
  31. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_strict_mode_errors.py +4 -4
  32. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_type_handling.py +4 -4
  33. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_advanced_features.py +49 -2
  34. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_argument_help_injection.py +6 -6
  35. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_exclude_enum.py +5 -5
  36. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_generate_click_classmethod.py +3 -1
  37. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_init_edge_cases.py +2 -2
  38. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_multiple_option_bug.py +7 -5
  39. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_variadic_argument_bug.py +4 -2
  40. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/__init__.py +21 -4
  41. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/_version.py +3 -3
  42. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/auto_model.py +1 -1
  43. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/click_integration.py +20 -6
  44. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/env_utils.py +20 -3
  45. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/model.py +49 -12
  46. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/multi_model.py +3 -3
  47. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry.egg-info/PKG-INFO +83 -9
  48. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry.egg-info/SOURCES.txt +4 -12
  49. wry-0.3.1.dev0/TODO.md +0 -104
  50. wry-0.3.1.dev0/examples/argument_help_injection.py +0 -51
  51. wry-0.3.1.dev0/examples/auto_instantiate_edge_cases.py +0 -165
  52. wry-0.3.1.dev0/examples/auto_instantiate_poc.py +0 -330
  53. wry-0.3.1.dev0/examples/auto_model_example.py +0 -187
  54. wry-0.3.1.dev0/examples/explicit_argument_help.py +0 -48
  55. wry-0.3.1.dev0/examples/field_exclusion.py +0 -82
  56. wry-0.3.1.dev0/examples/field_exclusion_example.py +0 -52
  57. wry-0.3.1.dev0/examples/intermediate_example.py +0 -89
  58. wry-0.3.1.dev0/examples/simple_cli.py +0 -55
  59. wry-0.3.1.dev0/examples/source_tracking_comprehensive.py +0 -133
  60. wry-0.3.1.dev0/examples/source_tracking_example.py +0 -102
  61. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/.github/workflows/ci-cd.yml +0 -0
  62. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/.markdownlint.json +0 -0
  63. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/.pre-commit-config.yaml +0 -0
  64. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/LICENSE +0 -0
  65. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/RELEASE_PROCESS.md +0 -0
  66. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/check.sh +0 -0
  67. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/examples/config.json +0 -0
  68. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/examples/sample_config.json +0 -0
  69. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/README.md +0 -0
  70. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/extract_release_notes.py +0 -0
  71. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/test_all_versions.sh +0 -0
  72. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/test_ci_locally.sh +0 -0
  73. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/test_with_act.sh +0 -0
  74. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/scripts/update-dependencies.sh +0 -0
  75. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/setup.cfg +0 -0
  76. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/README.md +0 -0
  77. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/__init__.py +0 -0
  78. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/features/__init__.py +0 -0
  79. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/integration/__init__.py +0 -0
  80. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/__init__.py +0 -0
  81. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/__init__.py +0 -0
  82. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  83. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  84. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  85. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/auto_model/test_type_inference.py +0 -0
  86. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  87. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/__init__.py +0 -0
  88. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_config_building.py +0 -0
  89. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  90. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_interval_constraints.py +0 -0
  91. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_lambda_parsing.py +0 -0
  92. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_length_constraints.py +0 -0
  93. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_click_predicate_handling.py +0 -0
  94. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_constraint_behavior.py +0 -0
  95. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  96. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_lambda_behavior.py +0 -0
  97. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_lambda_error_handling.py +0 -0
  98. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/click/test_predicate_source_errors.py +0 -0
  99. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/__init__.py +0 -0
  100. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_accessors.py +0 -0
  101. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_core.py +0 -0
  102. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_edge_cases.py +0 -0
  103. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_env_utils.py +0 -0
  104. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  105. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_field_utils.py +0 -0
  106. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  107. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_sources.py +0 -0
  108. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/core/test_type_checking_blocks.py +0 -0
  109. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/__init__.py +0 -0
  110. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_accessor_caching.py +0 -0
  111. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_extract_edge_cases.py +0 -0
  112. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_extract_subset_edge_cases.py +0 -0
  113. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_click_context_handling.py +0 -0
  114. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_data_extraction.py +0 -0
  115. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_default_handling.py +0 -0
  116. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_edge_cases.py +0 -0
  117. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_environment_integration.py +0 -0
  118. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  119. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_extraction_methods.py +0 -0
  120. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_field_errors.py +0 -0
  121. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_model_object_extraction.py +0 -0
  122. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  123. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  124. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/multi_model/__init__.py +0 -0
  125. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/multi_model/test_multi_model.py +0 -0
  126. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/multi_model/test_type_checking.py +0 -0
  127. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_auto_model_field_processing.py +0 -0
  128. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_comprehensive_imports.py +0 -0
  129. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_help_system.py +0 -0
  130. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_init.py +0 -0
  131. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_init_version_edge_cases.py +0 -0
  132. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_model_extraction_methods.py +0 -0
  133. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_type_checking_imports.py +0 -0
  134. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_version_fallback.py +0 -0
  135. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/tests/unit/test_version_parsing.py +0 -0
  136. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/__init__.py +0 -0
  137. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/accessors.py +0 -0
  138. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/field_utils.py +0 -0
  139. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/core/sources.py +0 -0
  140. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/help_system.py +0 -0
  141. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry/py.typed +0 -0
  142. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry.egg-info/dependency_links.txt +0 -0
  143. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry.egg-info/requires.txt +0 -0
  144. {wry-0.3.1.dev0 → wry-0.3.2.dev2}/wry.egg-info/top_level.txt +0 -0
@@ -137,3 +137,6 @@ dmypy.json
137
137
 
138
138
  # macOS
139
139
  .DS_Store
140
+
141
+ # Auto-generated version file (generated by setuptools-scm at build time)
142
+ wry/_version.py
@@ -1,7 +1,7 @@
1
1
  # wry - Comprehensive AI/LLM Knowledge Base
2
2
 
3
- **Last Updated**: 2025-10-01
4
- **Version**: 0.2.3+
3
+ **Last Updated**: 2025-10-04
4
+ **Version**: 0.3.2+
5
5
  **Purpose**: Complete reference for AI assistants and LLMs to understand wry without reading the entire codebase
6
6
 
7
7
  ---
@@ -14,11 +14,12 @@
14
14
  2. Automatically generating Click CLI parameters
15
15
  3. Supporting multiple configuration sources (CLI/ENV/JSON/DEFAULT)
16
16
  4. Tracking exactly where each value came from
17
- 5. Enforcing strict precedence: CLI > ENV > JSON > DEFAULT
17
+ 5. Enforcing strict precedence: CLI > JSON > ENV > DEFAULT
18
+ 6. **NEW v0.3.2**: Automatic alias-based CLI option generation
18
19
 
19
20
  **Key Innovation**: Single source of truth for configuration with comprehensive source tracking.
20
21
 
21
- **Stats**: 918 lines of code, 407 tests (all passing), 92% coverage, supports Python 3.10-3.12
22
+ **Stats**: 436 tests (all passing), 92%+ coverage, supports Python 3.10-3.12, Pydantic v2.11+ compatible
22
23
 
23
24
  ---
24
25
 
@@ -75,14 +76,25 @@ tests/
75
76
  └── multi_model/ # Multi-model
76
77
 
77
78
  examples/
78
- ├── simple_cli.py # Basic usage
79
- ├── source_tracking_comprehensive.py # All 4 sources demo
80
- ├── source_tracking_example.py
81
- ├── explicit_argument_help.py # Argument help
82
- ├── multi_model_example.py # Multiple models
83
- ├── field_exclusion_example.py # AutoExclude
84
- └── sample_config.json # Sample config
85
- ```
79
+ ├── autowrymodel_comprehensive.py # Complete AutoWryModel features
80
+ │ - Simple options, arguments, constraints
81
+ │ - Pydantic aliases for custom CLI names
82
+ │ - Explicit click.option() for short flags
83
+ │ - Field exclusion with AutoExclude
84
+ │ - Environment variables and JSON config
85
+ ├── wrymodel_comprehensive.py # WryModel with source tracking
86
+ │ - Manual field annotation (AutoOption/AutoArgument)
87
+ │ - Full source tracking (CLI/ENV/JSON/DEFAULT)
88
+ │ - Pydantic aliases with WryModel
89
+ │ - Configuration precedence demonstration
90
+ ├── multimodel_comprehensive.py # Multi-model usage
91
+ │ - Multiple models in one CLI
92
+ │ - split_kwargs_by_model
93
+ │ - create_models helper
94
+ └── config.json # Sample config
95
+ ```
96
+
97
+ **Note**: v0.3.2 consolidated 16 example files into 3 comprehensive examples for easier discovery and learning.
86
98
 
87
99
  ---
88
100
 
@@ -96,12 +108,18 @@ examples/
96
108
 
97
109
  ```python
98
110
  class WryModel(BaseModel):
99
- model_config = ConfigDict(arbitrary_types_allowed=True)
111
+ model_config = ConfigDict(
112
+ arbitrary_types_allowed=True,
113
+ validate_by_name=True, # NEW v0.3.2: Accept field names
114
+ validate_by_alias=True # NEW v0.3.2: Accept aliases
115
+ )
100
116
  env_prefix: ClassVar[str] = "" # Environment variable prefix
101
117
  _value_sources: dict[str, ValueSource] = {} # Tracks where values came from
102
118
  _accessor_instances: dict[str, Any] = {} # Caches property accessors
103
119
  ```
104
120
 
121
+ **Note**: `validate_by_name=True` and `validate_by_alias=True` enable Pydantic alias support out of the box (v0.3.2+). This allows fields to be populated using either their field name or alias, which is essential for the alias-based CLI option generation feature.
122
+
105
123
  **Property Accessors** (lazy-initialized, cached):
106
124
 
107
125
  ```python
@@ -499,7 +517,7 @@ Converts constraint dict to human-readable list:
499
517
 
500
518
  ```python
501
519
  @classmethod
502
- def __init_subclass__(cls, **kwargs):
520
+ def __init_subclass__(cls, **kwargs: Any):
503
521
  """Called when subclass is defined."""
504
522
  super().__init_subclass__(**kwargs)
505
523
 
@@ -538,7 +556,7 @@ def __init_subclass__(cls, **kwargs):
538
556
 
539
557
  **Result**: All fields become `--options` unless explicitly configured.
540
558
 
541
- **`create_auto_model(name, fields, **kwargs)`**:
559
+ **`create_auto_model(name, fields, **kwargs: Any)`**:
542
560
  Dynamic model creation at runtime.
543
561
 
544
562
  ### 8. `wry/multi_model.py` - Multi-Model Support (48 lines, 100% coverage)
@@ -574,11 +592,11 @@ db = configs[DatabaseConfig]
574
592
  ```python
575
593
  # Decorator that applies generate_click_parameters for each model
576
594
  @multi_model(ServerConfig, DatabaseConfig)
577
- def serve(ctx, **kwargs):
595
+ def serve(ctx: click.Context, **kwargs: Any):
578
596
  # All options from both models available
579
597
  ```
580
598
 
581
- **`singleton_option(*args, **kwargs)`**:
599
+ **`singleton_option(*args, **kwargs: Any)`**:
582
600
  Wrapper around `click.option` that prevents duplicate options.
583
601
 
584
602
  ### 9. `wry/help_system.py` - Documentation System (89 lines)
@@ -674,7 +692,7 @@ CLICK PROCESSES (order matters!)
674
692
  │ └─> Click detects: ParameterSource.ENVIRONMENT
675
693
 
676
694
  └─> 4. CALL COMMAND FUNCTION
677
- └─> def main(ctx, **kwargs):
695
+ └─> def main(ctx: click.Context, **kwargs: Any):
678
696
  kwargs = {"host": "api.com", "port": 9000}
679
697
 
680
698
 
@@ -877,14 +895,14 @@ class Config(AutoWryModel):
877
895
  ```python
878
896
  # NO source tracking ❌
879
897
  @Config.generate_click_parameters()
880
- def main(**kwargs):
898
+ def main(**kwargs: Any):
881
899
  config = Config(**kwargs)
882
900
  # config.source.* always shows CLI
883
901
 
884
902
  # FULL source tracking ✅
885
903
  @Config.generate_click_parameters()
886
904
  @click.pass_context
887
- def main(ctx, **kwargs):
905
+ def main(ctx: click.Context, **kwargs: Any):
888
906
  config = Config.from_click_context(ctx, **kwargs)
889
907
  # config.source.* shows actual source
890
908
  ```
@@ -1007,7 +1025,7 @@ class Config(AutoWryModel):
1007
1025
 
1008
1026
  @click.command()
1009
1027
  @Config.generate_click_parameters()
1010
- def main(**kwargs):
1028
+ def main(**kwargs: Any):
1011
1029
  config = Config(**kwargs)
1012
1030
  print(f"{config.host}:{config.port}")
1013
1031
  ```
@@ -1034,7 +1052,7 @@ class Config(AutoWryModel):
1034
1052
  @click.command()
1035
1053
  @Config.generate_click_parameters()
1036
1054
  @click.pass_context # ⚠️ Required
1037
- def main(ctx, **kwargs): # ⚠️ ctx parameter required
1055
+ def main(ctx: click.Context, **kwargs: Any): # ⚠️ ctx parameter required
1038
1056
  config = Config.from_click_context(ctx, **kwargs) # ⚠️ Not Config(**kwargs)
1039
1057
 
1040
1058
  # Access sources
@@ -1062,7 +1080,7 @@ class Config(WryModel):
1062
1080
 
1063
1081
  @click.command()
1064
1082
  @Config.generate_click_parameters()
1065
- def main(**kwargs):
1083
+ def main(**kwargs: Any):
1066
1084
  config = Config(**kwargs)
1067
1085
  # Usage: python app.py input.txt output.txt --format yaml --verbose
1068
1086
  ```
@@ -1106,7 +1124,7 @@ class DatabaseConfig(WryModel):
1106
1124
  @click.command()
1107
1125
  @multi_model(ServerConfig, DatabaseConfig)
1108
1126
  @click.pass_context
1109
- def serve(ctx, **kwargs):
1127
+ def serve(ctx: click.Context, **kwargs: Any):
1110
1128
  configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
1111
1129
  server = configs[ServerConfig]
1112
1130
  db = configs[DatabaseConfig]
@@ -1137,6 +1155,85 @@ class Config(AutoWryModel):
1137
1155
  - Polymorphic validation fields
1138
1156
  - Secrets not from CLI
1139
1157
 
1158
+ ### Pattern 7: Pydantic Aliases for Custom CLI Names (v0.3.2+)
1159
+
1160
+ **New in v0.3.2**: Pydantic field aliases automatically control CLI option names and environment variable names!
1161
+
1162
+ ```python
1163
+ from pydantic import Field
1164
+ from wry import AutoWryModel
1165
+
1166
+ class DatabaseConfig(AutoWryModel):
1167
+ env_prefix = "DB_"
1168
+
1169
+ # Concise Python field name: db_url
1170
+ # Alias controls CLI option: --database-url
1171
+ # Environment variable: DB_DATABASE_URL
1172
+ db_url: str = Field(
1173
+ alias="database_url",
1174
+ default="sqlite:///app.db",
1175
+ description="Database connection URL"
1176
+ )
1177
+
1178
+ pool_size: int = Field(
1179
+ alias="connection_pool_size",
1180
+ default=5,
1181
+ description="Maximum connection pool size"
1182
+ )
1183
+
1184
+ @click.command()
1185
+ @DatabaseConfig.generate_click_parameters()
1186
+ @click.pass_context
1187
+ def main(ctx: click.Context, **kwargs: Any):
1188
+ config = DatabaseConfig.from_click_context(ctx, **kwargs)
1189
+
1190
+ # Access via concise field names
1191
+ print(f"URL: {config.db_url}")
1192
+ print(f"Pool: {config.pool_size}")
1193
+
1194
+ # Source tracking works
1195
+ print(f"URL from: {config.source.db_url.value}")
1196
+ ```
1197
+
1198
+ **How it works**:
1199
+
1200
+ - **Python field**: `db_url` (concise, easy to type in code)
1201
+ - **CLI option**: `--database-url` (descriptive, user-friendly)
1202
+ - **Environment variable**: `DB_DATABASE_URL` (consistent with CLI)
1203
+ - **JSON config**: Accepts both `db_url` and `database_url`
1204
+
1205
+ **Requirements**:
1206
+
1207
+ - **None!** WryModel automatically sets `validate_by_name=True` and `validate_by_alias=True`
1208
+ - No configuration needed - it just works!
1209
+
1210
+ **Full support**:
1211
+
1212
+ - ✅ Aliases automatically control auto-generated option names
1213
+ - ✅ Environment variables use alias names (consistent with CLI)
1214
+ - ✅ Source tracking works correctly
1215
+ - ✅ JSON config accepts both field names and aliases
1216
+
1217
+ **Why this pattern exists:**
1218
+
1219
+ Before v0.3.2, custom CLI option names required explicit `click.option()` decorators for every field. The alias feature eliminates this boilerplate for the common case.
1220
+
1221
+ **For advanced use cases** (short options, custom Click types), combine aliases with explicit decorators:
1222
+
1223
+ ```python
1224
+ class Config(AutoWryModel):
1225
+ # Explicit click.option for short option support
1226
+ verbose: Annotated[int, click.option("-v", "--verbose", count=True)] = Field(default=0)
1227
+
1228
+ # Or combine with aliases for descriptive field names
1229
+ database_connection_string: Annotated[
1230
+ str,
1231
+ click.option("--db-url", "-d", default="sqlite:///app.db")
1232
+ ] = Field(alias='db_url', default="sqlite:///app.db")
1233
+ ```
1234
+
1235
+ See `examples/autowrymodel_comprehensive.py` and `examples/wrymodel_comprehensive.py` for complete examples.
1236
+
1140
1237
  ---
1141
1238
 
1142
1239
  ## Test Coverage Details
@@ -1280,10 +1377,10 @@ format_constraint_text(constraints) → list[str]
1280
1377
  multi_model(*model_classes, strict=False) → decorator
1281
1378
  create_models(ctx, kwargs, *model_classes) → dict[type, instance]
1282
1379
  split_kwargs_by_model(kwargs, *model_classes) → dict[type, dict]
1283
- singleton_option(*args, **kwargs) → decorator
1380
+ singleton_option(*args, **kwargs: Any) → decorator
1284
1381
 
1285
1382
  # Auto Model
1286
- create_auto_model(name, fields, **kwargs) → type[AutoWryModel]
1383
+ create_auto_model(name, fields, **kwargs: Any) → type[AutoWryModel]
1287
1384
 
1288
1385
  # Field Utilities
1289
1386
  extract_field_constraints(field_info) → dict[str, Any]
@@ -1442,7 +1539,7 @@ python -c "import wry; print(wry.__version__)"
1442
1539
  # WRONG ❌
1443
1540
  @click.command()
1444
1541
  @Config.generate_click_parameters()
1445
- def main(**kwargs): # No ctx parameter
1542
+ def main(**kwargs: Any): # No ctx parameter
1446
1543
  config = Config(**kwargs) # Direct instantiation
1447
1544
  # Result: All sources show as CLI
1448
1545
 
@@ -1450,7 +1547,7 @@ def main(**kwargs): # No ctx parameter
1450
1547
  @click.command()
1451
1548
  @Config.generate_click_parameters()
1452
1549
  @click.pass_context # Add this
1453
- def main(ctx, **kwargs): # Add ctx parameter
1550
+ def main(ctx: click.Context, **kwargs: Any): # Add ctx parameter
1454
1551
  config = Config.from_click_context(ctx, **kwargs) # Use this
1455
1552
  # Result: Accurate source tracking
1456
1553
  ```
@@ -1647,7 +1744,7 @@ config_data = {"host": TrackedValue("api.com", ValueSource.CLI)}
1647
1744
  ### Why model_dump() Excludes Accessors?
1648
1745
 
1649
1746
  ```python
1650
- def model_dump(self, **kwargs) -> dict[str, Any]:
1747
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
1651
1748
  data = super().model_dump(**kwargs)
1652
1749
  accessor_keys = {"source", "minimum", "maximum", "constraints", "defaults"}
1653
1750
  return {k: v for k, v in data.items() if k not in accessor_keys}
@@ -1806,7 +1903,7 @@ class Config(AutoWryModel):
1806
1903
 
1807
1904
  ```python
1808
1905
  @click.pass_context
1809
- def main(ctx, **kwargs):
1906
+ def main(ctx: click.Context, **kwargs: Any):
1810
1907
  config = Config(**kwargs) # ⚠️ Not using context
1811
1908
  ```
1812
1909
 
@@ -1837,7 +1934,7 @@ class Config(AutoWryModel):
1837
1934
 
1838
1935
  @click.command()
1839
1936
  @Config.generate_click_parameters()
1840
- def main(**kwargs):
1937
+ def main(**kwargs: Any):
1841
1938
  config = Config(**kwargs)
1842
1939
  print(f"Connecting to {config.host}:{config.port}")
1843
1940
 
@@ -1881,7 +1978,7 @@ class Config(AutoWryModel):
1881
1978
  @click.command()
1882
1979
  @Config.generate_click_parameters()
1883
1980
  @click.pass_context # ⚠️ Required for source tracking
1884
- def main(ctx, **kwargs): # ⚠️ ctx parameter required
1981
+ def main(ctx: click.Context, **kwargs: Any): # ⚠️ ctx parameter required
1885
1982
  """Process files with configuration."""
1886
1983
  config = Config.from_click_context(ctx, **kwargs)
1887
1984
 
@@ -1943,7 +2040,7 @@ class DatabaseConfig(WryModel):
1943
2040
  @click.command()
1944
2041
  @multi_model(ServerConfig, DatabaseConfig)
1945
2042
  @click.pass_context
1946
- def serve(ctx, **kwargs):
2043
+ def serve(ctx: click.Context, **kwargs: Any):
1947
2044
  """Start server with database connection."""
1948
2045
  # Create both models from kwargs
1949
2046
  configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
@@ -2040,7 +2137,7 @@ Config = create_auto_model(
2040
2137
  # Use like any AutoWryModel
2041
2138
  @click.command()
2042
2139
  @Config.generate_click_parameters()
2043
- def main(**kwargs):
2140
+ def main(**kwargs: Any):
2044
2141
  config = Config(**kwargs)
2045
2142
  print(f"{config.host}:{config.port}")
2046
2143
  ```
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2025-10-04
11
+
12
+ ### Added
13
+
14
+ - **Automatic Pydantic alias-based CLI option generation** 🎉
15
+ - Aliases now automatically control generated CLI option names and environment variable names
16
+ - Example: `db_url` field with `alias="database_url"` generates `--database-url` option and `DB_DATABASE_URL` env var
17
+ - No configuration required - works out of the box!
18
+ - `WryModel` now sets `validate_by_name=True` and `validate_by_alias=True` by default
19
+ - Full source tracking support (CLI/ENV/JSON/DEFAULT) with aliases
20
+ - JSON config files accept both field names and aliases
21
+ - New example: `examples/field_alias_example.py` demonstrating automatic alias-based generation
22
+ - Note: For short options (e.g., `-v`), continue using explicit `click.option()` decorators as shown in `examples/auto_model_example.py`
23
+ - Comprehensive test suite: 20 tests covering all alias scenarios including precedence chains
24
+
25
+ ### Changed
26
+
27
+ - **Pydantic v2.11+ compatibility** - replaced deprecated `populate_by_name` with `validate_by_name` and `validate_by_alias`
28
+ - `WryModel.model_config` now includes `validate_by_name=True` and `validate_by_alias=True` by default
29
+ - `generate_click_parameters()` now uses field aliases for auto-generated option names
30
+ - `get_env_var_names()` now uses field aliases for environment variable names
31
+ - Updated all documentation (README, AI_KNOWLEDGE_BASE, TODO) with alias feature details
32
+
33
+ ### Fixed
34
+
35
+ - Source tracking now works correctly when using Pydantic aliases
36
+ - JSON config loading properly handles both field names and aliases
37
+ - Parameter source detection checks both alias and field name for Click compatibility
38
+ - Removed all deprecated `Field(annotation=...)` usage in tests
39
+ - All deprecation warnings eliminated for Pydantic v3 compatibility
40
+ - Click now properly validates required arguments and options, providing native Click error messages
41
+ - Fixed type display in `--show-env-vars` to correctly extract types from Annotated fields
42
+
43
+ ### Technical Details
44
+
45
+ - Modified `from_click_context()` to build alias-to-field mapping and handle both field names and aliases
46
+ - Enhanced kwargs filtering to accept both field names and aliases, mapping aliases back to field names for source tracking
47
+ - Updated JSON config handling to map aliases to field names
48
+ - Improved parameter source checking with alias fallback
49
+ - Updated `generate_click_parameters()` to use aliases for option name generation
50
+ - Updated `get_env_var_names()` to use aliases for environment variable names
51
+ - Modified argument generation to properly handle `required` flag based on field defaults and environment variables
52
+ - Removed forced `required=False` override in `extract_and_modify_argument_decorator()` to preserve original Click behavior
53
+ - Improved `print_env_vars()` to correctly extract base types from Annotated fields
54
+ - Consolidated 16 example files into 3 comprehensive examples for easier discovery:
55
+ - `examples/autowrymodel_comprehensive.py` - All AutoWryModel features
56
+ - `examples/wrymodel_comprehensive.py` - WryModel with source tracking
57
+ - `examples/multimodel_comprehensive.py` - Multi-model usage
58
+ - All 436 tests pass with 92%+ code coverage
59
+
60
+ ## [0.3.1] - 2025-10-02
61
+
62
+ ### Fixed
63
+
64
+ - CI/CD build configuration for setuptools-scm
65
+ - Version generation now works correctly with git tags
66
+
10
67
  ## [0.3.0] - 2025-10-02
11
68
 
12
69
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wry
3
- Version: 0.3.1.dev0
3
+ Version: 0.3.2.dev2
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
@@ -90,7 +90,7 @@ class AppArgs(AutoWryModel):
90
90
 
91
91
  @click.command()
92
92
  @AppArgs.generate_click_parameters()
93
- def main(**kwargs):
93
+ def main(**kwargs: Any):
94
94
  """My simple CLI application."""
95
95
  # Create the model instance from kwargs
96
96
  config = AppArgs(**kwargs)
@@ -100,7 +100,10 @@ if __name__ == "__main__":
100
100
  main()
101
101
  ```
102
102
 
103
- **Note**: Currently, `generate_click_parameters()` passes individual parameters as kwargs to your function. You need to instantiate the model yourself. See the [Future Features](#future-features) section for a potential cleaner API.
103
+ **See comprehensive examples:**
104
+ - `examples/autowrymodel_comprehensive.py` - All AutoWryModel features including aliases
105
+ - `examples/wrymodel_comprehensive.py` - WryModel with source tracking
106
+ - `examples/multimodel_comprehensive.py` - Multi-model usage
104
107
 
105
108
  Run it:
106
109
 
@@ -129,7 +132,7 @@ wry tracks where each configuration value came from, supporting all four sources
129
132
  ```python
130
133
  @click.command()
131
134
  @AppArgs.generate_click_parameters()
132
- def main(**kwargs):
135
+ def main(**kwargs: Any):
133
136
  # Simple instantiation - no source tracking
134
137
  config = AppArgs(**kwargs)
135
138
  # Works fine, but config.source.* will always show CLI
@@ -143,7 +146,7 @@ To enable accurate source tracking, use `@click.pass_context` and `from_click_co
143
146
  @click.command()
144
147
  @AppArgs.generate_click_parameters()
145
148
  @click.pass_context
146
- def main(ctx, **kwargs):
149
+ def main(ctx: click.Context, **kwargs: Any):
147
150
  # Full source tracking with context
148
151
  config = AppArgs.from_click_context(ctx, **kwargs)
149
152
 
@@ -181,7 +184,8 @@ python examples/source_tracking_comprehensive.py --config examples/sample_config
181
184
  ```
182
185
 
183
186
  **Output shows source for each field:**
184
- ```
187
+
188
+ ```text
185
189
  host = json-server.com [from JSON]
186
190
  port = 3000 [from CLI] ← CLI overrides JSON
187
191
  debug = True [from ENV]
@@ -257,7 +261,7 @@ class DatabaseArgs(WryModel):
257
261
  @click.command()
258
262
  @multi_model(ServerConfig, DatabaseConfig)
259
263
  @click.pass_context
260
- def serve(ctx, **kwargs):
264
+ def serve(ctx: click.Context, **kwargs: Any):
261
265
  # Create model instances
262
266
  configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
263
267
  server = configs[ServerConfig]
@@ -330,7 +334,7 @@ class AppArgs(WryModel):
330
334
  @click.command()
331
335
  @multi_model(DatabaseConfig, AppArgs)
332
336
  @click.pass_context
333
- def main(ctx, **kwargs):
337
+ def main(ctx: click.Context, **kwargs: Any):
334
338
  # Automatically splits kwargs between models
335
339
  configs = create_models(ctx, kwargs, DatabaseConfig, AppArgs)
336
340
 
@@ -349,7 +353,7 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
349
353
  @click.command()
350
354
  @Config.generate_click_parameters() # strict=True by default
351
355
  @Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
352
- def main(**kwargs):
356
+ def main(**kwargs: Any):
353
357
  pass
354
358
  ```
355
359
 
@@ -391,6 +395,76 @@ class Config(WryModel):
391
395
  )
392
396
  ```
393
397
 
398
+ ### Using Pydantic Aliases for Custom CLI Names
399
+
400
+ **New in v0.3.2+**: Pydantic field aliases automatically control the generated CLI option names and environment variable names!
401
+
402
+ This allows you to have concise Python field names while exposing descriptive CLI options:
403
+
404
+ ```python
405
+ from pydantic import Field
406
+ from wry import AutoWryModel
407
+
408
+ class DatabaseConfig(AutoWryModel):
409
+ env_prefix = "DB_"
410
+
411
+ # Concise Python field name: db_url
412
+ # Alias controls CLI option: --database-url
413
+ # Environment variable: DB_DATABASE_URL
414
+ db_url: str = Field(
415
+ alias="database_url",
416
+ default="sqlite:///app.db",
417
+ description="Database connection URL"
418
+ )
419
+
420
+ pool_size: int = Field(
421
+ alias="connection_pool_size",
422
+ default=5,
423
+ description="Maximum connection pool size"
424
+ )
425
+ ```
426
+
427
+ **How it works:**
428
+
429
+ - **Python field**: `db_url` (concise, easy to type)
430
+ - **CLI option**: `--database-url` (descriptive, user-friendly)
431
+ - **Environment variable**: `DB_DATABASE_URL` (consistent with CLI)
432
+ - **JSON config**: Accepts both `db_url` and `database_url`
433
+
434
+ **Requirements:**
435
+
436
+ - **None!** `WryModel` automatically sets `validate_by_name=True` and `validate_by_alias=True`
437
+ - This tells Pydantic to accept both field names and aliases
438
+ - No need to configure anything - it just works!
439
+ - Aliases automatically control option names, env var names, and help text
440
+
441
+ **Full support (v0.3.2+):**
442
+
443
+ - ✅ Aliases automatically control auto-generated option names
444
+ - ✅ Environment variables use alias names (consistent with CLI)
445
+ - ✅ Source tracking works correctly
446
+ - ✅ JSON config accepts both field names and aliases
447
+
448
+ **Why this feature exists:**
449
+
450
+ Before v0.3.2, if you wanted custom CLI option names, you had to use explicit `click.option()` decorators for every field. The alias feature eliminates this boilerplate for the common case where you just want different names.
451
+
452
+ **For advanced use cases** (short options, custom Click types):
453
+
454
+ You can still combine aliases with explicit `click.option()` decorators:
455
+
456
+ ```python
457
+ class Config(AutoWryModel):
458
+ # Explicit click.option for short option support
459
+ verbose: Annotated[int, click.option("-v", "--verbose", count=True)] = Field(default=0)
460
+ ```
461
+
462
+ See `examples/autowrymodel_comprehensive.py` for examples of explicit Click decorators.
463
+
464
+ **See also:**
465
+ - `examples/autowrymodel_comprehensive.py` - Complete AutoWryModel example with aliases
466
+ - `examples/wrymodel_comprehensive.py` - WryModel with aliases and source tracking
467
+
394
468
  ## Development
395
469
 
396
470
  ### Prerequisites