wry 0.1.6.dev3__tar.gz → 0.1.8__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 (117) hide show
  1. {wry-0.1.6.dev3 → wry-0.1.8}/.github/workflows/ci-cd.yml +16 -0
  2. {wry-0.1.6.dev3 → wry-0.1.8}/.pre-commit-config.yaml +9 -4
  3. {wry-0.1.6.dev3 → wry-0.1.8}/CHANGELOG.md +23 -1
  4. {wry-0.1.6.dev3 → wry-0.1.8}/PKG-INFO +1 -3
  5. {wry-0.1.6.dev3 → wry-0.1.8}/README.md +0 -2
  6. wry-0.1.8/tests/unit/test_generate_click_classmethod.py +47 -0
  7. {wry-0.1.6.dev3 → wry-0.1.8}/wry/_version.py +3 -3
  8. {wry-0.1.6.dev3 → wry-0.1.8}/wry/click_integration.py +7 -3
  9. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/model.py +34 -4
  10. {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/PKG-INFO +1 -3
  11. {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/SOURCES.txt +1 -0
  12. {wry-0.1.6.dev3 → wry-0.1.8}/.gitignore +0 -0
  13. {wry-0.1.6.dev3 → wry-0.1.8}/.markdownlint.json +0 -0
  14. {wry-0.1.6.dev3 → wry-0.1.8}/LICENSE +0 -0
  15. {wry-0.1.6.dev3 → wry-0.1.8}/TODO.md +0 -0
  16. {wry-0.1.6.dev3 → wry-0.1.8}/check.sh +0 -0
  17. {wry-0.1.6.dev3 → wry-0.1.8}/examples/auto_model_example.py +0 -0
  18. {wry-0.1.6.dev3 → wry-0.1.8}/examples/config.json +0 -0
  19. {wry-0.1.6.dev3 → wry-0.1.8}/examples/intermediate_example.py +0 -0
  20. {wry-0.1.6.dev3 → wry-0.1.8}/examples/multi_model_example.py +0 -0
  21. {wry-0.1.6.dev3 → wry-0.1.8}/examples/simple_cli.py +0 -0
  22. {wry-0.1.6.dev3 → wry-0.1.8}/examples/source_tracking_example.py +0 -0
  23. {wry-0.1.6.dev3 → wry-0.1.8}/pyproject.toml +0 -0
  24. {wry-0.1.6.dev3 → wry-0.1.8}/scripts/README.md +0 -0
  25. {wry-0.1.6.dev3 → wry-0.1.8}/scripts/extract_release_notes.py +0 -0
  26. {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_all_versions.sh +0 -0
  27. {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_ci_locally.sh +0 -0
  28. {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_with_act.sh +0 -0
  29. {wry-0.1.6.dev3 → wry-0.1.8}/setup.cfg +0 -0
  30. {wry-0.1.6.dev3 → wry-0.1.8}/tests/README.md +0 -0
  31. {wry-0.1.6.dev3 → wry-0.1.8}/tests/__init__.py +0 -0
  32. {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/__init__.py +0 -0
  33. {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_auto_model.py +0 -0
  34. {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_multi_model.py +0 -0
  35. {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_source_precedence.py +0 -0
  36. {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/__init__.py +0 -0
  37. {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_edge_cases.py +0 -0
  38. {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_integration.py +0 -0
  39. {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_integration_extended.py +0 -0
  40. {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_context_handling.py +0 -0
  41. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/__init__.py +0 -0
  42. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/__init__.py +0 -0
  43. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  44. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  45. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  46. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_field_annotations.py +0 -0
  47. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_type_inference.py +0 -0
  48. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  49. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/__init__.py +0 -0
  50. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_bool_flag_handling.py +0 -0
  51. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_config_building.py +0 -0
  52. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  53. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
  54. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_interval_constraints.py +0 -0
  55. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_lambda_parsing.py +0 -0
  56. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_length_constraints.py +0 -0
  57. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_parameter_generation.py +0 -0
  58. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_predicate_handling.py +0 -0
  59. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_closure_extraction_errors.py +0 -0
  60. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_closure_handling.py +0 -0
  61. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_constraint_behavior.py +0 -0
  62. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  63. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_env_vars_option.py +0 -0
  64. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_json_config_loading.py +0 -0
  65. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_lambda_behavior.py +0 -0
  66. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_lambda_error_handling.py +0 -0
  67. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_predicate_source_errors.py +0 -0
  68. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_strict_mode_errors.py +0 -0
  69. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_type_handling.py +0 -0
  70. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/__init__.py +0 -0
  71. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_accessors.py +0 -0
  72. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_advanced_features.py +0 -0
  73. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_core.py +0 -0
  74. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_edge_cases.py +0 -0
  75. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_env_utils.py +0 -0
  76. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  77. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_utils.py +0 -0
  78. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  79. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_sources.py +0 -0
  80. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_type_checking_blocks.py +0 -0
  81. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/__init__.py +0 -0
  82. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_accessor_caching.py +0 -0
  83. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_extract_edge_cases.py +0 -0
  84. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_click_context_handling.py +0 -0
  85. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_data_extraction.py +0 -0
  86. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_default_handling.py +0 -0
  87. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_environment_integration.py +0 -0
  88. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  89. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_extraction_methods.py +0 -0
  90. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_field_errors.py +0 -0
  91. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_object_extraction.py +0 -0
  92. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  93. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  94. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/__init__.py +0 -0
  95. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/test_multi_model.py +0 -0
  96. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/test_type_checking.py +0 -0
  97. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_auto_model_field_processing.py +0 -0
  98. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_comprehensive_imports.py +0 -0
  99. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init.py +0 -0
  100. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init_edge_cases.py +0 -0
  101. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init_version_edge_cases.py +0 -0
  102. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_model_extraction_methods.py +0 -0
  103. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_type_checking_imports.py +0 -0
  104. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_version_fallback.py +0 -0
  105. {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_version_parsing.py +0 -0
  106. {wry-0.1.6.dev3 → wry-0.1.8}/wry/__init__.py +0 -0
  107. {wry-0.1.6.dev3 → wry-0.1.8}/wry/auto_model.py +0 -0
  108. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/__init__.py +0 -0
  109. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/accessors.py +0 -0
  110. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/env_utils.py +0 -0
  111. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/field_utils.py +0 -0
  112. {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/sources.py +0 -0
  113. {wry-0.1.6.dev3 → wry-0.1.8}/wry/multi_model.py +0 -0
  114. {wry-0.1.6.dev3 → wry-0.1.8}/wry/py.typed +0 -0
  115. {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/dependency_links.txt +0 -0
  116. {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/requires.txt +0 -0
  117. {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/top_level.txt +0 -0
@@ -177,6 +177,14 @@ jobs:
177
177
  ./dist/*.tar.gz
178
178
  ./dist/*.whl
179
179
 
180
+ - name: Get previous tag
181
+ id: previous_tag
182
+ run: |
183
+ # Get the previous tag (excluding the current one)
184
+ PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v '${{ github.ref_name }}' | head -n 1)
185
+ echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
186
+ echo "Previous tag: $PREVIOUS_TAG"
187
+
180
188
  - name: Extract release notes from CHANGELOG
181
189
  id: release_notes
182
190
  run: |
@@ -186,6 +194,14 @@ jobs:
186
194
  # Extract release notes and save to file
187
195
  python scripts/extract_release_notes.py '${{ github.ref_name }}' > release_notes.md
188
196
 
197
+ # Add comparison link if we have a previous tag
198
+ if [ -n "${{ steps.previous_tag.outputs.previous_tag }}" ]; then
199
+ echo "" >> release_notes.md
200
+ echo "---" >> release_notes.md
201
+ echo "" >> release_notes.md
202
+ echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.previous_tag.outputs.previous_tag }}...${{ github.ref_name }}" >> release_notes.md
203
+ fi
204
+
189
205
  # Read the content for the release body
190
206
  echo 'RELEASE_NOTES<<EOF' >> $GITHUB_ENV
191
207
  cat release_notes.md >> $GITHUB_ENV
@@ -28,13 +28,18 @@ repos:
28
28
  args: [--cov=wry, --cov-branch, --cov-report=xml, --cov-report=term-missing, --cov-fail-under=90]
29
29
  pass_filenames: false
30
30
  always_run: true
31
+
32
+ - repo: https://github.com/pre-commit/mirrors-mypy
33
+ rev: v1.17.1
34
+ hooks:
31
35
  - id: mypy
32
- name: mypy
33
- entry: mypy
34
- language: system
35
- types: [python]
36
36
  args: [wry/]
37
37
  pass_filenames: false
38
+ additional_dependencies:
39
+ - pydantic>=2.9.2
40
+ - click>=8.0.0
41
+ - annotated-types>=0.6.0
42
+ - setuptools-scm>=8.0
38
43
 
39
44
  # Temporarily disabled - bandit has dependency issues in pre-commit
40
45
  # - repo: https://github.com/PyCQA/bandit
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.7] - 2025-09-09
11
+
12
+ ### Added
13
+
14
+ - `generate_click_parameters` as a classmethod on `WryModel` and `AutoWryModel`
15
+ - Allows using `@MyModel.generate_click_parameters()` directly without imports
16
+ - Simplifies API and reduces import requirements in downstream projects
17
+
18
+ ## [0.1.6] - 2025-09-08
19
+
20
+ ### Added
21
+
22
+ - GitHub releases now include commit comparison links to previous releases
23
+ - Development versions now use `.devN` format and show as pre-releases on PyPI
24
+
25
+ ### Changed
26
+
27
+ - Switched setuptools-scm from `post-release` to `guess-next-dev` version scheme
28
+ - Development versions now appear as pre-releases instead of regular releases on PyPI
29
+
10
30
  ## [0.1.5] - 2025-09-08
11
31
 
12
32
  ### Fixed
@@ -115,7 +135,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
115
135
 
116
136
  - Initial version as `drycli` package (before rename to `wry`)
117
137
 
118
- [Unreleased]: https://github.com/tahouse/wry/compare/v0.1.5...HEAD
138
+ [Unreleased]: https://github.com/tahouse/wry/compare/v0.1.7...HEAD
139
+ [0.1.7]: https://github.com/tahouse/wry/compare/v0.1.6...v0.1.7
140
+ [0.1.6]: https://github.com/tahouse/wry/compare/v0.1.5...v0.1.6
119
141
  [0.1.5]: https://github.com/tahouse/wry/compare/v0.1.4...v0.1.5
120
142
  [0.1.4]: https://github.com/tahouse/wry/compare/v0.1.3...v0.1.4
121
143
  [0.1.3]: https://github.com/tahouse/wry/compare/v0.1.2...v0.1.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wry
3
- Version: 0.1.6.dev3
3
+ Version: 0.1.8
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
@@ -243,8 +243,6 @@ class QuickConfig(AutoWryModel):
243
243
  age: int = Field(default=30, ge=0, le=120)
244
244
  email: str = Field(description="Your email")
245
245
 
246
- # No need for Annotated[..., AutoOption]!
247
-
248
246
  @click.command()
249
247
  @generate_click_parameters(QuickConfig)
250
248
  def quickstart(config: QuickConfig):
@@ -197,8 +197,6 @@ class QuickConfig(AutoWryModel):
197
197
  age: int = Field(default=30, ge=0, le=120)
198
198
  email: str = Field(description="Your email")
199
199
 
200
- # No need for Annotated[..., AutoOption]!
201
-
202
200
  @click.command()
203
201
  @generate_click_parameters(QuickConfig)
204
202
  def quickstart(config: QuickConfig):
@@ -0,0 +1,47 @@
1
+ """Test generate_click_parameters as a classmethod."""
2
+
3
+ import click
4
+ from pydantic import Field
5
+
6
+ from wry import AutoWryModel, WryModel, generate_click_parameters
7
+
8
+
9
+ class TestGenerateClickClassmethod:
10
+ """Test the generate_click_parameters classmethod."""
11
+
12
+ def test_wry_model_has_classmethod(self):
13
+ """Test that WryModel has generate_click_parameters classmethod."""
14
+ assert hasattr(WryModel, "generate_click_parameters")
15
+ assert callable(WryModel.generate_click_parameters)
16
+
17
+ def test_auto_wry_model_has_classmethod(self):
18
+ """Test that AutoWryModel has generate_click_parameters classmethod."""
19
+ assert hasattr(AutoWryModel, "generate_click_parameters")
20
+ assert callable(AutoWryModel.generate_click_parameters)
21
+
22
+ def test_classmethod_returns_same_as_function(self):
23
+ """Test that classmethod returns same decorator as direct function call."""
24
+
25
+ class SimpleModel(WryModel):
26
+ value: int = Field(default=1)
27
+
28
+ # Get decorator from classmethod
29
+ classmethod_decorator = SimpleModel.generate_click_parameters()
30
+ # Get decorator from function
31
+ function_decorator = generate_click_parameters(SimpleModel)
32
+
33
+ # Both should be callables
34
+ assert callable(classmethod_decorator)
35
+ assert callable(function_decorator)
36
+
37
+ # They should have the same behavior (though not necessarily the same object)
38
+ # The important thing is that the classmethod works
39
+ @click.command()
40
+ @classmethod_decorator
41
+ def dummy(**kwargs):
42
+ pass
43
+
44
+ # Should have config and show_env_vars options added
45
+ param_names = [p.name for p in dummy.params]
46
+ assert "config" in param_names
47
+ assert "show_env_vars" in param_names
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.1.6.dev3'
32
- __version_tuple__ = version_tuple = (0, 1, 6, 'dev3')
31
+ __version__ = version = '0.1.8'
32
+ __version_tuple__ = version_tuple = (0, 1, 8)
33
33
 
34
- __commit_id__ = commit_id = 'gef20e4f4a'
34
+ __commit_id__ = commit_id = 'g2ea5305d9'
@@ -314,7 +314,7 @@ def generate_click_parameters(
314
314
  model_class: type[BaseModel],
315
315
  add_config_option: bool = True,
316
316
  strict: bool = True,
317
- ) -> Callable[..., Any]:
317
+ ) -> Callable[[FC], FC]:
318
318
  """Generate Click options and arguments with smart auto-generation.
319
319
 
320
320
  This decorator automatically generates Click CLI parameters from a Pydantic
@@ -357,7 +357,11 @@ def generate_click_parameters(
357
357
  field_type = AutoClickParameter.REQUIRED_OPTION
358
358
  elif item == AutoClickParameter.ARGUMENT:
359
359
  field_type = AutoClickParameter.ARGUMENT
360
- elif hasattr(item, "__module__") and "click" in str(item.__module__):
360
+ elif (
361
+ hasattr(item, "__module__")
362
+ and "click" in str(item.__module__)
363
+ and not isinstance(item, AutoClickParameter)
364
+ ):
361
365
  click_parameter = item
362
366
  break
363
367
 
@@ -504,7 +508,7 @@ def generate_click_parameters(
504
508
  )
505
509
  )
506
510
 
507
- def decorator(func: Callable[[FC], FC]) -> Callable[[FC], FC]:
511
+ def decorator(func: FC) -> FC:
508
512
  # Check for duplicate decorator application
509
513
  if hasattr(func, "_wry_models"):
510
514
  existing_models = getattr(func, "_wry_models", [])
@@ -1,13 +1,17 @@
1
1
  """Core WryModel implementation."""
2
2
 
3
3
  import json
4
+ from collections.abc import Callable
4
5
  from pathlib import Path
5
- from typing import Any, ClassVar, TypeVar
6
+ from typing import TYPE_CHECKING, Any, ClassVar, TypeVar, cast
6
7
 
7
8
  import click
8
9
  from pydantic import BaseModel, ConfigDict
9
10
  from pydantic_core import PydanticUndefined
10
11
 
12
+ if TYPE_CHECKING:
13
+ from click.decorators import FC
14
+
11
15
  from .accessors import (
12
16
  ConstraintsAccessor,
13
17
  DefaultsAccessor,
@@ -416,7 +420,10 @@ class WryModel(BaseModel):
416
420
  if field_info.default is not PydanticUndefined:
417
421
  config_data[field_name] = TrackedValue(field_info.default, ValueSource.DEFAULT)
418
422
  elif field_info.default_factory is not None:
419
- config_data[field_name] = TrackedValue(field_info.default_factory(), ValueSource.DEFAULT)
423
+ config_data[field_name] = TrackedValue(
424
+ cast(Callable[[], Any], field_info.default_factory)(),
425
+ ValueSource.DEFAULT,
426
+ )
420
427
 
421
428
  return cls.create_with_sources(config_data)
422
429
 
@@ -495,7 +502,7 @@ class WryModel(BaseModel):
495
502
  config_data[field_name] = TrackedValue(field_info.default, ValueSource.DEFAULT)
496
503
  elif field_info.default_factory is not None:
497
504
  factory = field_info.default_factory
498
- config_data[field_name] = TrackedValue(factory(), ValueSource.DEFAULT)
505
+ config_data[field_name] = TrackedValue(cast(Callable[[], Any], factory)(), ValueSource.DEFAULT)
499
506
 
500
507
  # 2. Override with environment values
501
508
  for field_name, value in env_values.items():
@@ -624,6 +631,29 @@ class WryModel(BaseModel):
624
631
  if field_info.default is not PydanticUndefined:
625
632
  result[field_name] = field_info.default
626
633
  elif field_info.default_factory is not None:
627
- result[field_name] = field_info.default_factory()
634
+ result[field_name] = cast(Callable[[], Any], field_info.default_factory)()
628
635
 
629
636
  return result
637
+
638
+ @classmethod
639
+ def generate_click_parameters(cls) -> "Callable[[FC], FC]":
640
+ """Generate Click parameters decorator for this model.
641
+
642
+ This is a convenience method that allows using the decorator
643
+ directly from the model class without needing to import it.
644
+
645
+ Example:
646
+ ```python
647
+ @click.command()
648
+ @MyConfig.generate_click_parameters()
649
+ @click.pass_context
650
+ def cli(ctx, **kwargs):
651
+ config = MyConfig.from_click_context(ctx, **kwargs)
652
+ ```
653
+
654
+ Returns:
655
+ The generate_click_parameters decorator configured for this model
656
+ """
657
+ from ..click_integration import generate_click_parameters
658
+
659
+ return generate_click_parameters(cls)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wry
3
- Version: 0.1.6.dev3
3
+ Version: 0.1.8
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
@@ -243,8 +243,6 @@ class QuickConfig(AutoWryModel):
243
243
  age: int = Field(default=30, ge=0, le=120)
244
244
  email: str = Field(description="Your email")
245
245
 
246
- # No need for Annotated[..., AutoOption]!
247
-
248
246
  @click.command()
249
247
  @generate_click_parameters(QuickConfig)
250
248
  def quickstart(config: QuickConfig):
@@ -33,6 +33,7 @@ tests/integration/test_context_handling.py
33
33
  tests/unit/__init__.py
34
34
  tests/unit/test_auto_model_field_processing.py
35
35
  tests/unit/test_comprehensive_imports.py
36
+ tests/unit/test_generate_click_classmethod.py
36
37
  tests/unit/test_init.py
37
38
  tests/unit/test_init_edge_cases.py
38
39
  tests/unit/test_init_version_edge_cases.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes