wry 0.6.0__tar.gz → 0.6.2.dev1__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 (150) hide show
  1. wry-0.6.2.dev1/.cursorrules +11 -0
  2. wry-0.6.2.dev1/AGENTS.md +266 -0
  3. {wry-0.6.0 → wry-0.6.2.dev1}/CONTRIBUTING.md +5 -5
  4. {wry-0.6.0 → wry-0.6.2.dev1}/DEPRECATION.md +1 -1
  5. {wry-0.6.0/wry.egg-info → wry-0.6.2.dev1}/PKG-INFO +4 -4
  6. {wry-0.6.0 → wry-0.6.2.dev1}/README.md +3 -3
  7. {wry-0.6.0 → wry-0.6.2.dev1}/RELEASE_PROCESS.md +2 -2
  8. wry-0.6.2.dev1/TODO.md +75 -0
  9. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_exclude_enum.py +4 -4
  10. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_help_system.py +6 -6
  11. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_type_checking_imports.py +5 -11
  12. wry-0.6.2.dev1/wry/_version.py +24 -0
  13. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/model.py +3 -2
  14. {wry-0.6.0 → wry-0.6.2.dev1}/wry/help_system.py +21 -20
  15. {wry-0.6.0 → wry-0.6.2.dev1/wry.egg-info}/PKG-INFO +4 -4
  16. {wry-0.6.0 → wry-0.6.2.dev1}/wry.egg-info/SOURCES.txt +3 -1
  17. wry-0.6.2.dev1/wry.egg-info/scm_file_list.json +140 -0
  18. wry-0.6.2.dev1/wry.egg-info/scm_version.json +8 -0
  19. wry-0.6.0/.cursorrules +0 -198
  20. wry-0.6.0/AI_KNOWLEDGE_BASE.md +0 -2913
  21. wry-0.6.0/TODO.md +0 -184
  22. wry-0.6.0/wry/_version.py +0 -34
  23. {wry-0.6.0 → wry-0.6.2.dev1}/.github/workflows/ci-cd.yml +0 -0
  24. {wry-0.6.0 → wry-0.6.2.dev1}/.gitignore +0 -0
  25. {wry-0.6.0 → wry-0.6.2.dev1}/.markdownlint.json +0 -0
  26. {wry-0.6.0 → wry-0.6.2.dev1}/.pre-commit-config.yaml +0 -0
  27. {wry-0.6.0 → wry-0.6.2.dev1}/CHANGELOG.md +0 -0
  28. {wry-0.6.0 → wry-0.6.2.dev1}/LICENSE +0 -0
  29. {wry-0.6.0 → wry-0.6.2.dev1}/check.sh +0 -0
  30. {wry-0.6.0 → wry-0.6.2.dev1}/examples/autowrymodel_comprehensive.py +0 -0
  31. {wry-0.6.0 → wry-0.6.2.dev1}/examples/config.json +0 -0
  32. {wry-0.6.0 → wry-0.6.2.dev1}/examples/multimodel_comprehensive.py +0 -0
  33. {wry-0.6.0 → wry-0.6.2.dev1}/examples/sample_config.json +0 -0
  34. {wry-0.6.0 → wry-0.6.2.dev1}/examples/wrymodel_comprehensive.py +0 -0
  35. {wry-0.6.0 → wry-0.6.2.dev1}/pyproject.toml +0 -0
  36. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/README.md +0 -0
  37. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/extract_release_notes.py +0 -0
  38. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/test_all_versions.sh +0 -0
  39. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/test_ci_locally.sh +0 -0
  40. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/test_with_act.sh +0 -0
  41. {wry-0.6.0 → wry-0.6.2.dev1}/scripts/update-dependencies.sh +0 -0
  42. {wry-0.6.0 → wry-0.6.2.dev1}/setup.cfg +0 -0
  43. {wry-0.6.0 → wry-0.6.2.dev1}/tests/README.md +0 -0
  44. {wry-0.6.0 → wry-0.6.2.dev1}/tests/__init__.py +0 -0
  45. {wry-0.6.0 → wry-0.6.2.dev1}/tests/features/__init__.py +0 -0
  46. {wry-0.6.0 → wry-0.6.2.dev1}/tests/features/test_auto_model.py +0 -0
  47. {wry-0.6.0 → wry-0.6.2.dev1}/tests/features/test_inheritance.py +0 -0
  48. {wry-0.6.0 → wry-0.6.2.dev1}/tests/features/test_multi_model.py +0 -0
  49. {wry-0.6.0 → wry-0.6.2.dev1}/tests/features/test_source_precedence.py +0 -0
  50. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/__init__.py +0 -0
  51. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/test_boolean_flags_integration.py +0 -0
  52. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/test_click_edge_cases.py +0 -0
  53. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/test_click_integration.py +0 -0
  54. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/test_click_integration_extended.py +0 -0
  55. {wry-0.6.0 → wry-0.6.2.dev1}/tests/integration/test_context_handling.py +0 -0
  56. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/__init__.py +0 -0
  57. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/__init__.py +0 -0
  58. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  59. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_auto_model_edge_cases.py +0 -0
  60. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  61. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_auto_model_list_fields.py +0 -0
  62. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_boolean_on_off_flags.py +0 -0
  63. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_comma_separated_lists.py +0 -0
  64. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  65. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_field_annotations.py +0 -0
  66. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_new_marker_api.py +0 -0
  67. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_optional_list_comma_separated.py +0 -0
  68. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/auto_model/test_type_inference.py +0 -0
  69. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  70. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/__init__.py +0 -0
  71. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_argument_types.py +0 -0
  72. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_bool_flag_handling.py +0 -0
  73. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_config_building.py +0 -0
  74. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  75. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
  76. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_interval_constraints.py +0 -0
  77. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_lambda_parsing.py +0 -0
  78. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_length_constraints.py +0 -0
  79. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_parameter_generation.py +0 -0
  80. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_click_predicate_handling.py +0 -0
  81. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_closure_extraction_errors.py +0 -0
  82. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_closure_handling.py +0 -0
  83. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_constraint_behavior.py +0 -0
  84. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  85. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_env_vars_option.py +0 -0
  86. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_explicit_argument_help_injection.py +0 -0
  87. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_field_alias_with_click_options.py +0 -0
  88. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_format_constraint_text.py +0 -0
  89. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_json_config_loading.py +0 -0
  90. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_lambda_behavior.py +0 -0
  91. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_lambda_error_handling.py +0 -0
  92. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_predicate_source_errors.py +0 -0
  93. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_strict_mode_errors.py +0 -0
  94. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/click/test_type_handling.py +0 -0
  95. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/__init__.py +0 -0
  96. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_accessors.py +0 -0
  97. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_advanced_features.py +0 -0
  98. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_classvar_migration.py +0 -0
  99. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_core.py +0 -0
  100. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_edge_cases.py +0 -0
  101. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_env_utils.py +0 -0
  102. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  103. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_field_utils.py +0 -0
  104. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  105. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_sources.py +0 -0
  106. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/core/test_type_checking_blocks.py +0 -0
  107. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/__init__.py +0 -0
  108. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_accessor_caching.py +0 -0
  109. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_extract_edge_cases.py +0 -0
  110. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_extract_subset_edge_cases.py +0 -0
  111. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_click_context_handling.py +0 -0
  112. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_data_extraction.py +0 -0
  113. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_default_handling.py +0 -0
  114. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_edge_cases.py +0 -0
  115. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_environment_integration.py +0 -0
  116. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  117. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_extraction_methods.py +0 -0
  118. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_field_errors.py +0 -0
  119. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_model_object_extraction.py +0 -0
  120. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  121. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  122. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/multi_model/__init__.py +0 -0
  123. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/multi_model/test_multi_model.py +0 -0
  124. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/multi_model/test_type_checking.py +0 -0
  125. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_argument_help_injection.py +0 -0
  126. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_auto_model_field_processing.py +0 -0
  127. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_comprehensive_imports.py +0 -0
  128. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_generate_click_classmethod.py +0 -0
  129. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_init.py +0 -0
  130. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_init_edge_cases.py +0 -0
  131. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_init_version_edge_cases.py +0 -0
  132. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_model_extraction_methods.py +0 -0
  133. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_multiple_option_bug.py +0 -0
  134. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_variadic_argument_bug.py +0 -0
  135. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_version_fallback.py +0 -0
  136. {wry-0.6.0 → wry-0.6.2.dev1}/tests/unit/test_version_parsing.py +0 -0
  137. {wry-0.6.0 → wry-0.6.2.dev1}/wry/__init__.py +0 -0
  138. {wry-0.6.0 → wry-0.6.2.dev1}/wry/auto_model.py +0 -0
  139. {wry-0.6.0 → wry-0.6.2.dev1}/wry/click_integration.py +0 -0
  140. {wry-0.6.0 → wry-0.6.2.dev1}/wry/comma_separated.py +0 -0
  141. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/__init__.py +0 -0
  142. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/accessors.py +0 -0
  143. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/env_utils.py +0 -0
  144. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/field_utils.py +0 -0
  145. {wry-0.6.0 → wry-0.6.2.dev1}/wry/core/sources.py +0 -0
  146. {wry-0.6.0 → wry-0.6.2.dev1}/wry/multi_model.py +0 -0
  147. {wry-0.6.0 → wry-0.6.2.dev1}/wry/py.typed +0 -0
  148. {wry-0.6.0 → wry-0.6.2.dev1}/wry.egg-info/dependency_links.txt +0 -0
  149. {wry-0.6.0 → wry-0.6.2.dev1}/wry.egg-info/requires.txt +0 -0
  150. {wry-0.6.0 → wry-0.6.2.dev1}/wry.egg-info/top_level.txt +0 -0
@@ -0,0 +1,11 @@
1
+ # Cursor AI Rules for wry Repository
2
+
3
+ Read `AGENTS.md` for the complete AI assistant reference, including:
4
+ - Architecture overview
5
+ - Critical gotchas and edge cases
6
+ - Development rules and pre-commit requirements
7
+ - Correct vs incorrect patterns
8
+ - Debugging quick reference
9
+
10
+ For comprehensive contributor guidelines, see `CONTRIBUTING.md`.
11
+ For release workflow, see `RELEASE_PROCESS.md`.
@@ -0,0 +1,266 @@
1
+ # AGENTS.md - AI Assistant Reference for wry
2
+
3
+ > For user-facing docs see README.md. For contributor guidelines see CONTRIBUTING.md.
4
+ > For version history see CHANGELOG.md. For release workflow see RELEASE_PROCESS.md.
5
+
6
+ ---
7
+
8
+ ## What is wry?
9
+
10
+ **wry** (Why Repeat Yourself?) is a Python library that eliminates repetitive CLI configuration by:
11
+
12
+ 1. Defining configuration once in Pydantic models
13
+ 2. Automatically generating Click CLI parameters
14
+ 3. Supporting multiple configuration sources (CLI / ENV / JSON / DEFAULT)
15
+ 4. Tracking exactly where each value came from
16
+ 5. Enforcing strict precedence: **CLI > JSON > ENV > DEFAULT**
17
+
18
+ **Key innovation**: Source tracking — no other library tells you *where* each config value came from.
19
+
20
+ ---
21
+
22
+ ## Architecture Overview
23
+
24
+ ```
25
+ wry/
26
+ ├── __init__.py # Public API exports
27
+ ├── click_integration.py # Click parameter generation (largest module)
28
+ ├── auto_model.py # AutoWryModel — zero-config model via __init_subclass__
29
+ ├── multi_model.py # Multi-model support (split_kwargs, create_models)
30
+ ├── help_system.py # Documentation system
31
+ ├── comma_separated.py # CommaSeparated list types
32
+ └── core/
33
+ ├── model.py # WryModel base class, from_click_context(), source tracking
34
+ ├── sources.py # ValueSource enum, TrackedValue, FieldWithSource
35
+ ├── accessors.py # Property accessors (source, minimum, maximum, etc.)
36
+ ├── field_utils.py # Constraint extraction from Pydantic fields
37
+ └── env_utils.py # Environment variable handling and type conversion
38
+ ```
39
+
40
+ **Key classes**: `WryModel` (explicit annotations), `AutoWryModel` (auto-generates options for all fields)
41
+
42
+ **Key flow**: User defines Pydantic model -> `generate_click_parameters()` creates Click decorators -> `from_click_context()` builds model with source tracking
43
+
44
+ ---
45
+
46
+ ## Critical Gotchas
47
+
48
+ These are the most common issues that trip up both users and AI assistants.
49
+
50
+ ### 1. `env_prefix` and other config must be ClassVar
51
+
52
+ ```python
53
+ # WRONG - Pydantic treats as a field, generates CLI option, breaks config
54
+ class Config(AutoWryModel):
55
+ env_prefix: str = "MYAPP_"
56
+
57
+ # CORRECT
58
+ class Config(AutoWryModel):
59
+ wry_env_prefix: ClassVar[str] = "MYAPP_"
60
+ ```
61
+
62
+ Without `ClassVar`, the attribute appears in `model_fields` and gets a CLI option generated. Use the `wry_` prefixed names (v0.6.0+). Old unprefixed names still work but emit deprecation warnings.
63
+
64
+ ### 2. Source tracking requires context
65
+
66
+ ```python
67
+ # NO source tracking - all sources show as CLI
68
+ @Config.generate_click_parameters()
69
+ def main(**kwargs):
70
+ config = Config(**kwargs) # Wrong
71
+
72
+ # FULL source tracking
73
+ @Config.generate_click_parameters()
74
+ @click.pass_context # Required
75
+ def main(ctx: click.Context, **kwargs):
76
+ config = Config.from_click_context(ctx, **kwargs) # Correct
77
+ ```
78
+
79
+ ### 3. Boolean fields use on/off pattern by default (v0.6.0+)
80
+
81
+ ```python
82
+ debug: bool = Field(default=False)
83
+ # Generates: --debug/--no-debug (not just --debug)
84
+
85
+ # Opt out to single flag:
86
+ debug: Annotated[bool, AutoOption(flag_enable_on_off=False)] = Field(default=False)
87
+ ```
88
+
89
+ ### 4. List fields auto-get multiple=True
90
+
91
+ ```python
92
+ tags: list[str] = Field(default_factory=list)
93
+ # Usage: --tags python --tags rust (NOT --tags python,rust)
94
+
95
+ # For comma-separated: use Annotated[list[str], CommaSeparated]
96
+ # Or model-wide: wry_comma_separated_lists: ClassVar[bool] = True
97
+ ```
98
+
99
+ ### 5. Arguments are always optional in Click (for --config compatibility)
100
+
101
+ ```python
102
+ input_file: Annotated[str, AutoArgument] = Field()
103
+ # Click sees required=False, but Pydantic still validates it's required
104
+ ```
105
+
106
+ ### 6. Click argument doesn't accept help parameter
107
+
108
+ wry works around this by injecting help text into the command docstring. Use `Field(description="...")` for argument help.
109
+
110
+ ### 7. Click 8.4+ compatibility
111
+
112
+ Click 8.4.0 changed `ParameterSource` from a string-valued to integer-valued enum. wry v0.6.1+ handles this. If source tracking silently fails (all values show as DEFAULT), upgrade wry.
113
+
114
+ ### 8. Pydantic 2.11+ deprecation
115
+
116
+ Use `cls.model_fields` or `self.__class__.model_fields`, never `self.model_fields` (deprecated).
117
+
118
+ ### 9. eager_json_config must not modify param.default
119
+
120
+ The JSON config callback only sets `p.required = False` on fields found in JSON. It must NOT set `p.default = json_value` — doing so breaks Click's parameter source detection and causes CLI values to be treated as defaults. (Fixed in v0.2.2.)
121
+
122
+ ### 10. Duplicate decorator detection
123
+
124
+ ```python
125
+ @Config.generate_click_parameters()
126
+ @Config.generate_click_parameters() # Error in strict mode (default)
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Correct vs Incorrect Patterns
132
+
133
+ ### Pattern: Creating a config model
134
+
135
+ ```python
136
+ # CORRECT - AutoWryModel for simple cases
137
+ class Config(AutoWryModel):
138
+ wry_env_prefix: ClassVar[str] = "APP_"
139
+ host: str = Field(default="localhost", description="Server host")
140
+ port: int = Field(default=8080, ge=1, le=65535)
141
+
142
+ # CORRECT - WryModel for mixed arguments/options
143
+ class Config(WryModel):
144
+ input_file: Annotated[str, AutoArgument] = Field(description="Input")
145
+ format: Annotated[str, AutoOption] = Field(default="json")
146
+ internal: Annotated[str, AutoExclude] = Field(default="")
147
+ ```
148
+
149
+ ### Pattern: Using aliases for CLI names
150
+
151
+ ```python
152
+ class Config(AutoWryModel):
153
+ # Python: config.db_url | CLI: --database-url | Env: APP_DATABASE_URL
154
+ db_url: str = Field(alias="database_url", default="sqlite:///app.db")
155
+ ```
156
+
157
+ No extra configuration needed — `validate_by_name=True` and `validate_by_alias=True` are set by WryModel.
158
+
159
+ ### Pattern: Multi-model commands
160
+
161
+ ```python
162
+ @click.command()
163
+ @multi_model(ServerConfig, DatabaseConfig)
164
+ @click.pass_context
165
+ def serve(ctx, **kwargs):
166
+ configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Development Rules
172
+
173
+ ### Pre-Commit Requirements
174
+
175
+ Before any commit, ensure:
176
+
177
+ 1. **CHANGELOG.md**: Add entry under `[Unreleased]` for ANY change
178
+ 2. **Tests**: Add/update tests, ensure all pass (`pytest`)
179
+ 3. **Code quality**: Type annotations, docstrings, no dead code
180
+ 4. **Linting**: `ruff check`, `ruff format`, `mypy` must pass
181
+ 5. **TODO.md**: Mark completed tasks if applicable
182
+ 6. **Conventional Commits**: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`
183
+
184
+ The pre-commit hook runs all checks automatically. **Never use `--no-verify`.**
185
+
186
+ ### Code Style
187
+
188
+ - Type annotations on all functions (use `-> None` for void)
189
+ - Google-style docstrings on public API
190
+ - Fix linter errors properly — avoid `# type: ignore`, `# noqa`, `cast()` unless truly necessary
191
+ - Use `git rm` / `git mv` for tracked files (preserves history)
192
+
193
+ ### Testing
194
+
195
+ - **Never skip/disable tests** to make suites pass — fix the issue
196
+ - Test all four sources: CLI, ENV, JSON, DEFAULT
197
+ - Test source tracking for new features
198
+ - Use `CliRunner` for Click command tests
199
+ - Coverage requirement: 90%+
200
+ - Test organization: `tests/unit/`, `tests/integration/`, `tests/features/`
201
+
202
+ ### wry-Specific Rules
203
+
204
+ - All `WryModel`/`AutoWryModel` config attributes must use `ClassVar`
205
+ - Source tracking is a core feature — ensure it works for new features
206
+ - List fields auto-get `multiple=True` unless comma-separated is enabled
207
+ - Update README.md for user-facing changes, AGENTS.md for architecture/API changes
208
+ - See CONTRIBUTING.md for comprehensive guidelines
209
+
210
+ ---
211
+
212
+ ## Debugging Quick Reference
213
+
214
+ | User says | Check |
215
+ |-----------|-------|
216
+ | "Sources all show as CLI" | Using `@click.pass_context` + `from_click_context()`? |
217
+ | "CLI doesn't override config file" | Version >= 0.2.2? (bug fixed) |
218
+ | "Env vars not working" | `env_prefix` is `ClassVar`? Try `--show-env-vars` |
219
+ | "Required field not enforced" | Version >= 0.2.2? Has no default? Not satisfied by env/config? |
220
+ | "Argument help not showing" | Using `Field(description="...")`? Version >= 0.2.3? |
221
+ | "Field excluded but still in CLI" | Using `Annotated[str, AutoExclude]`? (not `AutoExclude[str]`) |
222
+ | "Source tracking shows DEFAULT for everything" | Click 8.4+ issue — upgrade wry to 0.6.1+ |
223
+
224
+ ### Key Test Files
225
+
226
+ - `tests/features/test_source_precedence.py` — Tests all 4 sources with proper precedence
227
+ - `tests/features/test_inheritance.py` — Model inheritance scenarios
228
+ - `tests/features/test_auto_model.py` — AutoWryModel features
229
+
230
+ ### Useful Commands
231
+
232
+ ```bash
233
+ pytest # All tests
234
+ pytest tests/features/test_source_precedence.py -xvs # Key precedence test
235
+ pytest --cov=wry --cov-report=term-missing # Coverage
236
+ python -m wry.help_system ai # This file via help system
237
+ ./check.sh # Pre-commit checks
238
+ ```
239
+
240
+ ---
241
+
242
+ ## Key Files for Common Tasks
243
+
244
+ | Task | Files to modify |
245
+ |------|----------------|
246
+ | Add new field type support | `click_integration.py`, `env_utils.py`, tests |
247
+ | Add new source type | `sources.py`, `model.py` (from_click_context) |
248
+ | Change CLI generation | `click_integration.py`, `auto_model.py` |
249
+ | Fix source tracking | `model.py` (lines ~586-624) |
250
+ | Add new accessor | `accessors.py`, `model.py` (add @property) |
251
+ | Add new constraint | `field_utils.py`, `click_integration.py` (format_constraint_text) |
252
+
253
+ ---
254
+
255
+ ## Configuration Precedence (how it works internally)
256
+
257
+ `from_click_context()` builds config in 4 layers:
258
+
259
+ 1. **DEFAULT** — Pydantic field defaults
260
+ 2. **ENV** — `get_env_values()` reads `os.environ` with `env_prefix`
261
+ 3. **JSON** — `ctx.obj['json_data']` from eager `--config` callback
262
+ 4. **CLI** — `ctx.get_parameter_source()` checks for `COMMANDLINE`
263
+
264
+ Each layer overrides the previous. The result is a dict of `TrackedValue(value, source)` objects passed to `create_with_sources()`.
265
+
266
+ **Critical detail**: Click's `get_parameter_source()` returns an enum. wry uses `.name` (not `str()`) to check for `"COMMANDLINE"` — this is what Click 8.4+ compatibility depends on.
@@ -43,7 +43,7 @@ wry/
43
43
 
44
44
  ├── scripts/ # Development tools
45
45
  ├── docs/ # Documentation
46
- ├── AI_KNOWLEDGE_BASE.md # Complete reference for AI/LLMs
46
+ ├── AGENTS.md # Complete reference for AI/LLMs
47
47
  ├── README.md # User documentation
48
48
  └── CHANGELOG.md # Version history
49
49
  ```
@@ -448,7 +448,7 @@ def test_feature_with_clear_name():
448
448
  - `Removed` - Removed features
449
449
  - `Security` - Security fixes
450
450
  - [ ] **README.md**: Update if user-facing features, usage, or API changed
451
- - [ ] **AI_KNOWLEDGE_BASE.md**: Update if architecture, implementation details, or patterns changed
451
+ - [ ] **AGENTS.md**: Update if architecture, implementation details, or patterns changed
452
452
  - [ ] **Module docstrings**: Update docstrings in modified modules
453
453
  - [ ] **Examples**: Add or update examples if demonstrating new features
454
454
 
@@ -456,7 +456,7 @@ def test_feature_with_clear_name():
456
456
 
457
457
  - **Do not create excessive markdown files** unless explicitly needed
458
458
  - If markdown files exist, add content to the appropriate existing file
459
- - If unsure which file is appropriate, consider whether the content belongs in README.md, AI_KNOWLEDGE_BASE.md, or CHANGELOG.md
459
+ - If unsure which file is appropriate, consider whether the content belongs in README.md, AGENTS.md, or CHANGELOG.md
460
460
  - Before creating a new markdown file, ask: Is this really necessary?
461
461
 
462
462
  ### 2. Code Quality
@@ -639,7 +639,7 @@ If pre-commit hooks are failing:
639
639
  ✅ Write comprehensive docstrings
640
640
  ✅ Add tests for edge cases
641
641
  ✅ Use `from_click_context()` for source tracking
642
- ✅ Update both README.md and AI_KNOWLEDGE_BASE.md
642
+ ✅ Update both README.md and AGENTS.md
643
643
 
644
644
  ### DON'T
645
645
 
@@ -665,7 +665,7 @@ For information about creating releases, see **`RELEASE_PROCESS.md`**.
665
665
  ## Questions?
666
666
 
667
667
  - Check `examples/` for usage patterns
668
- - Check `AI_KNOWLEDGE_BASE.md` for complete technical reference
668
+ - Check `AGENTS.md` for complete technical reference
669
669
  - Check `README.md` for user documentation
670
670
  - Check `RELEASE_PROCESS.md` for release workflow
671
671
  - Check `TODO.md` for current tasks and planned features
@@ -146,7 +146,7 @@ When removing deprecated features in v1.0.0:
146
146
  - [ ] Remove deprecation sections from `CHANGELOG.md`
147
147
  - [ ] Remove migration guide from `CHANGELOG.md`
148
148
  - [ ] Remove deprecated patterns from examples
149
- - [ ] Update `AI_KNOWLEDGE_BASE.md` to remove old API references
149
+ - [ ] Update `AGENTS.md` to remove old API references
150
150
  - [ ] Update `README.md` to remove old patterns
151
151
  - [ ] Archive this `DEPRECATION.md` file
152
152
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wry
3
- Version: 0.6.0
3
+ Version: 0.6.2.dev1
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
@@ -1019,7 +1019,7 @@ We welcome contributions! Please see **`CONTRIBUTING.md`** for comprehensive gui
1019
1019
 
1020
1020
  - 📖 **`CONTRIBUTING.md`** - Complete contributor guide (for humans)
1021
1021
  - 🤖 **`.cursorrules`** - AI assistant quick reference
1022
- - 📚 **`AI_KNOWLEDGE_BASE.md`** - Complete technical reference
1022
+ - 📚 **`AGENTS.md`** - AI/LLM assistant reference
1023
1023
  - 🚀 **`RELEASE_PROCESS.md`** - Release workflow and versioning
1024
1024
 
1025
1025
  ### Development Setup
@@ -1208,7 +1208,7 @@ wry has comprehensive documentation for different audiences:
1208
1208
 
1209
1209
  ### Technical Reference
1210
1210
 
1211
- - 📚 **`AI_KNOWLEDGE_BASE.md`** - Complete technical reference for AI/LLMs (also useful for humans)
1211
+ - 📚 **`AGENTS.md`** - AI/LLM assistant reference
1212
1212
  - 📝 **`CHANGELOG.md`** - Version history and all changes
1213
1213
  - 🧪 **`tests/README.md`** - Test organization and structure
1214
1214
  - 🔧 **`scripts/README.md`** - Development scripts and tools
@@ -1233,7 +1233,7 @@ wry has comprehensive documentation for different audiences:
1233
1233
  **I'm an AI assistant, I want to...**
1234
1234
 
1235
1235
  - Quick reference → `.cursorrules`
1236
- - Technical details → `AI_KNOWLEDGE_BASE.md`
1236
+ - Technical details → `AGENTS.md`
1237
1237
  - Code patterns → `CONTRIBUTING.md`
1238
1238
  - Test examples → `tests/features/test_source_precedence.py`
1239
1239
 
@@ -975,7 +975,7 @@ We welcome contributions! Please see **`CONTRIBUTING.md`** for comprehensive gui
975
975
 
976
976
  - 📖 **`CONTRIBUTING.md`** - Complete contributor guide (for humans)
977
977
  - 🤖 **`.cursorrules`** - AI assistant quick reference
978
- - 📚 **`AI_KNOWLEDGE_BASE.md`** - Complete technical reference
978
+ - 📚 **`AGENTS.md`** - AI/LLM assistant reference
979
979
  - 🚀 **`RELEASE_PROCESS.md`** - Release workflow and versioning
980
980
 
981
981
  ### Development Setup
@@ -1164,7 +1164,7 @@ wry has comprehensive documentation for different audiences:
1164
1164
 
1165
1165
  ### Technical Reference
1166
1166
 
1167
- - 📚 **`AI_KNOWLEDGE_BASE.md`** - Complete technical reference for AI/LLMs (also useful for humans)
1167
+ - 📚 **`AGENTS.md`** - AI/LLM assistant reference
1168
1168
  - 📝 **`CHANGELOG.md`** - Version history and all changes
1169
1169
  - 🧪 **`tests/README.md`** - Test organization and structure
1170
1170
  - 🔧 **`scripts/README.md`** - Development scripts and tools
@@ -1189,7 +1189,7 @@ wry has comprehensive documentation for different audiences:
1189
1189
  **I'm an AI assistant, I want to...**
1190
1190
 
1191
1191
  - Quick reference → `.cursorrules`
1192
- - Technical details → `AI_KNOWLEDGE_BASE.md`
1192
+ - Technical details → `AGENTS.md`
1193
1193
  - Code patterns → `CONTRIBUTING.md`
1194
1194
  - Test examples → `tests/features/test_source_precedence.py`
1195
1195
 
@@ -47,7 +47,7 @@ Follow these steps IN ORDER:
47
47
 
48
48
  3. **Update documentation**
49
49
  - Update README.md if needed
50
- - Update AI_KNOWLEDGE_BASE.md version numbers
50
+ - Update AGENTS.md version numbers
51
51
  - Update any affected documentation
52
52
 
53
53
  4. **Commit the release preparation**
@@ -75,7 +75,7 @@ Continue development by:
75
75
  - Archive or remove completed items from the released version
76
76
  - Add new tasks or features planned for next release
77
77
  - Update roadmap and priorities
78
- 3. Update version numbers in AI_KNOWLEDGE_BASE.md if not already done
78
+ 3. Update version numbers in AGENTS.md if not already done
79
79
 
80
80
  ## Example
81
81
 
wry-0.6.2.dev1/TODO.md ADDED
@@ -0,0 +1,75 @@
1
+ # TODO List for wry
2
+
3
+ ## Current Sprint
4
+
5
+ ### In Progress
6
+
7
+ - [ ] Achieve higher code coverage
8
+ - Current coverage: ~90%
9
+ - Target: 95%+ for core modules
10
+
11
+ ### Completed (Recent)
12
+
13
+ - [x] Click 8.4+ compatibility fix (v0.6.1)
14
+ - [x] Boolean on/off flags (`--option/--no-option`) (v0.6.0)
15
+ - [x] Callable marker API: `AutoOption()`, `AutoArgument()`, `AutoExclude()` (v0.6.0)
16
+ - [x] wry-prefixed ClassVars for configuration (v0.6.0)
17
+ - [x] Comma-separated list support (v0.5.0)
18
+ - [x] Model inheritance support (v0.4.1)
19
+ - [x] Pydantic alias-based CLI option generation (v0.4.0)
20
+ - [x] Comprehensive test suite (538 tests, all passing)
21
+ - [x] Consolidated AI docs into AGENTS.md
22
+
23
+ ## Future Enhancements
24
+
25
+ ### High Priority
26
+
27
+ - [ ] **Short option aliases for CLI options**
28
+ - Allow specifying short form: `--node/-n`, `--cluster/-c`
29
+ - Possible API: `AutoOption(short='n')` or auto-generate from first letter
30
+ - Need conflict detection
31
+
32
+ - [ ] **Document/improve environment variable support for arguments**
33
+ - Arguments CAN use env vars via `from_click_context()` but it's not obvious
34
+ - Add env var info to argument docstring injection
35
+ - Document prominently in README
36
+
37
+ - [ ] Add support for YAML configuration files
38
+ - [ ] Add support for TOML configuration files
39
+ - [ ] Add async support for Click commands
40
+ - [ ] Create documentation site (Sphinx/MkDocs)
41
+
42
+ ### Medium Priority
43
+
44
+ - [ ] Add support for configuration file validation/schema generation
45
+ - [ ] Create migration guide from plain Click to wry
46
+ - [ ] Add benchmarks comparing performance with plain Click
47
+
48
+ ### Low Priority
49
+
50
+ - [ ] Add support for custom value source types
51
+ - [ ] Add support for remote configuration sources
52
+ - [ ] Create plugin system for extensions
53
+
54
+ ## Technical Debt
55
+
56
+ - [ ] Use newer Python 3.10+ syntax where appropriate (Union types with `|`, etc.)
57
+ - [ ] Improve error messages for validation failures
58
+ - [ ] Optimize import times
59
+
60
+ ## Documentation
61
+
62
+ - [ ] Create API documentation site
63
+ - [ ] Create comparison with similar libraries (Typer, Clidantic, etc.)
64
+
65
+ ## Release Planning
66
+
67
+ - [ ] v0.7.0 - Short option aliases, YAML/TOML support
68
+ - [ ] v1.0.0 - API stability guarantee, remove deprecated v0.6.0 APIs
69
+
70
+ ## Notes
71
+
72
+ - Keep backward compatibility in mind for all changes
73
+ - Follow semantic versioning strictly
74
+ - Ensure all new features have corresponding tests
75
+ - Update documentation for every public API change
@@ -45,11 +45,11 @@ class TestExcludeEnum:
45
45
  # Test that excluded fields are not available as CLI options
46
46
  result = runner.invoke(cli, ["--polymorphic-input", "changed"])
47
47
  assert result.exit_code != 0
48
- assert "no such option: --polymorphic-input" in result.output.lower()
48
+ assert "no such option" in result.output.lower()
49
49
 
50
50
  result = runner.invoke(cli, ["--computed-result", "changed"])
51
51
  assert result.exit_code != 0
52
- assert "no such option: --computed-result" in result.output.lower()
52
+ assert "no such option" in result.output.lower()
53
53
 
54
54
  # Test that regular fields work
55
55
  result = runner.invoke(cli, ["--name", "Alice", "--count", "5"])
@@ -81,7 +81,7 @@ class TestExcludeEnum:
81
81
  # Test that excluded field is not available
82
82
  result = runner.invoke(cli, ["--excluded-field", "changed"])
83
83
  assert result.exit_code != 0
84
- assert "no such option: --excluded-field" in result.output.lower()
84
+ assert "no such option" in result.output.lower()
85
85
 
86
86
  # Test that regular fields work
87
87
  result = runner.invoke(cli, ["--name", "Bob"])
@@ -107,7 +107,7 @@ class TestExcludeEnum:
107
107
  # Test that conflicted field is excluded
108
108
  result = runner.invoke(cli, ["--conflicted", "changed"])
109
109
  assert result.exit_code != 0
110
- assert "no such option: --conflicted" in result.output.lower()
110
+ assert "no such option" in result.output.lower()
111
111
 
112
112
  # Normal field should work
113
113
  result = runner.invoke(cli, ["--normal", "changed"])
@@ -16,17 +16,17 @@ class TestHelpSystem:
16
16
  assert len(content) > 100
17
17
 
18
18
  def test_get_help_content_ai(self):
19
- """Test getting AI knowledge base."""
19
+ """Test getting AI assistant reference."""
20
20
  content = get_help_content("ai")
21
- assert "AI/LLM Knowledge Base" in content
22
- assert "Configuration Flow" in content
23
- assert len(content) > 1000
21
+ assert "AI Assistant Reference" in content
22
+ assert "Architecture Overview" in content
23
+ assert len(content) > 500
24
24
 
25
25
  def test_get_help_content_sources(self):
26
26
  """Test getting source tracking info."""
27
27
  content = get_help_content("sources")
28
- # Should extract from AI KB or return not found message
29
- assert "Source Tracking" in content or "Test Coverage" in content or "not found" in content
28
+ # Should extract from AGENTS.md or README, or return not found
29
+ assert "Configuration Precedence" in content or "Source Tracking" in content or "not found" in content
30
30
 
31
31
  def test_get_help_content_architecture(self):
32
32
  """Test getting architecture info."""
@@ -1,22 +1,16 @@
1
1
  """Test TYPE_CHECKING imports coverage."""
2
2
 
3
- from unittest.mock import patch
4
-
5
3
 
6
4
  class TestTypeCheckingImports:
7
5
  """Test TYPE_CHECKING conditional imports."""
8
6
 
9
- def test_version_module_type_checking_true(self):
10
- """Test _version module when TYPE_CHECKING is True."""
11
- # Import the module first to ensure it exists
7
+ def test_version_module_importable(self):
8
+ """Test _version module can be imported and has version info."""
12
9
  import wry._version
13
10
 
14
- # Patch TYPE_CHECKING to True
15
- with patch("wry._version.TYPE_CHECKING", True):
16
- # The module should still work
17
- assert hasattr(wry._version, "__version__")
18
- assert hasattr(wry._version, "VERSION_TUPLE")
19
- assert hasattr(wry._version, "COMMIT_ID")
11
+ # The auto-generated module should always have version info
12
+ assert hasattr(wry._version, "__version__")
13
+ assert isinstance(wry._version.__version__, str)
20
14
 
21
15
  def test_core_modules_type_checking(self):
22
16
  """Test TYPE_CHECKING blocks in core modules."""
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.6.2.dev1'
22
+ __version_tuple__ = version_tuple = (0, 6, 2, 'dev1')
23
+
24
+ __commit_id__ = commit_id = 'g4dd42ce80'
@@ -601,9 +601,10 @@ class WryModel(BaseModel):
601
601
  param_source = ctx.get_parameter_source(field_name)
602
602
 
603
603
  if param_source is not None:
604
- source_str = str(param_source)
604
+ # Use .name for Click 8.4+ compatibility (integer-valued enum)
605
+ source_name = param_source.name if hasattr(param_source, "name") else str(param_source)
605
606
  # Only override if it's actually from CLI
606
- if "COMMANDLINE" in source_str:
607
+ if "COMMANDLINE" in source_name:
607
608
  config_data[field_name] = TrackedValue(value, ValueSource.CLI)
608
609
  continue
609
610
  # Skip if it's DEFAULT or ENVIRONMENT - already handled above