wry 0.1.6.dev3__tar.gz → 0.1.8.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 (117) hide show
  1. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/.github/workflows/ci-cd.yml +16 -0
  2. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/.pre-commit-config.yaml +9 -4
  3. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/CHANGELOG.md +23 -1
  4. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/PKG-INFO +1 -1
  5. wry-0.1.8.dev1/tests/unit/test_generate_click_classmethod.py +47 -0
  6. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/_version.py +3 -3
  7. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/click_integration.py +5 -1
  8. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/model.py +31 -4
  9. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry.egg-info/PKG-INFO +1 -1
  10. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry.egg-info/SOURCES.txt +1 -0
  11. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/.gitignore +0 -0
  12. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/.markdownlint.json +0 -0
  13. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/LICENSE +0 -0
  14. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/README.md +0 -0
  15. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/TODO.md +0 -0
  16. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/check.sh +0 -0
  17. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/auto_model_example.py +0 -0
  18. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/config.json +0 -0
  19. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/intermediate_example.py +0 -0
  20. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/multi_model_example.py +0 -0
  21. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/simple_cli.py +0 -0
  22. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/examples/source_tracking_example.py +0 -0
  23. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/pyproject.toml +0 -0
  24. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/scripts/README.md +0 -0
  25. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/scripts/extract_release_notes.py +0 -0
  26. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/scripts/test_all_versions.sh +0 -0
  27. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/scripts/test_ci_locally.sh +0 -0
  28. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/scripts/test_with_act.sh +0 -0
  29. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/setup.cfg +0 -0
  30. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/README.md +0 -0
  31. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/__init__.py +0 -0
  32. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/features/__init__.py +0 -0
  33. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/features/test_auto_model.py +0 -0
  34. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/features/test_multi_model.py +0 -0
  35. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/features/test_source_precedence.py +0 -0
  36. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/integration/__init__.py +0 -0
  37. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/integration/test_click_edge_cases.py +0 -0
  38. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/integration/test_click_integration.py +0 -0
  39. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/integration/test_click_integration_extended.py +0 -0
  40. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/integration/test_context_handling.py +0 -0
  41. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/__init__.py +0 -0
  42. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/__init__.py +0 -0
  43. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  44. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  45. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  46. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/test_field_annotations.py +0 -0
  47. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/auto_model/test_type_inference.py +0 -0
  48. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  49. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/__init__.py +0 -0
  50. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_bool_flag_handling.py +0 -0
  51. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_config_building.py +0 -0
  52. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  53. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
  54. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_interval_constraints.py +0 -0
  55. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_lambda_parsing.py +0 -0
  56. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_length_constraints.py +0 -0
  57. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_parameter_generation.py +0 -0
  58. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_click_predicate_handling.py +0 -0
  59. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_closure_extraction_errors.py +0 -0
  60. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_closure_handling.py +0 -0
  61. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_constraint_behavior.py +0 -0
  62. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  63. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_env_vars_option.py +0 -0
  64. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_json_config_loading.py +0 -0
  65. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_lambda_behavior.py +0 -0
  66. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_lambda_error_handling.py +0 -0
  67. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_predicate_source_errors.py +0 -0
  68. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_strict_mode_errors.py +0 -0
  69. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/click/test_type_handling.py +0 -0
  70. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/__init__.py +0 -0
  71. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_accessors.py +0 -0
  72. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_advanced_features.py +0 -0
  73. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_core.py +0 -0
  74. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_edge_cases.py +0 -0
  75. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_env_utils.py +0 -0
  76. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  77. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_field_utils.py +0 -0
  78. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  79. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_sources.py +0 -0
  80. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/core/test_type_checking_blocks.py +0 -0
  81. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/__init__.py +0 -0
  82. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_accessor_caching.py +0 -0
  83. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_extract_edge_cases.py +0 -0
  84. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_click_context_handling.py +0 -0
  85. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_data_extraction.py +0 -0
  86. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_default_handling.py +0 -0
  87. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_environment_integration.py +0 -0
  88. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  89. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_extraction_methods.py +0 -0
  90. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_field_errors.py +0 -0
  91. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_model_object_extraction.py +0 -0
  92. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  93. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  94. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/multi_model/__init__.py +0 -0
  95. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/multi_model/test_multi_model.py +0 -0
  96. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/multi_model/test_type_checking.py +0 -0
  97. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_auto_model_field_processing.py +0 -0
  98. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_comprehensive_imports.py +0 -0
  99. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_init.py +0 -0
  100. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_init_edge_cases.py +0 -0
  101. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_init_version_edge_cases.py +0 -0
  102. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_model_extraction_methods.py +0 -0
  103. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_type_checking_imports.py +0 -0
  104. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_version_fallback.py +0 -0
  105. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/tests/unit/test_version_parsing.py +0 -0
  106. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/__init__.py +0 -0
  107. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/auto_model.py +0 -0
  108. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/__init__.py +0 -0
  109. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/accessors.py +0 -0
  110. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/env_utils.py +0 -0
  111. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/field_utils.py +0 -0
  112. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/core/sources.py +0 -0
  113. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/multi_model.py +0 -0
  114. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry/py.typed +0 -0
  115. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry.egg-info/dependency_links.txt +0 -0
  116. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/wry.egg-info/requires.txt +0 -0
  117. {wry-0.1.6.dev3 → wry-0.1.8.dev1}/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.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
@@ -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.dev1'
32
+ __version_tuple__ = version_tuple = (0, 1, 8, 'dev1')
33
33
 
34
- __commit_id__ = commit_id = 'gef20e4f4a'
34
+ __commit_id__ = commit_id = 'g4e1c76e68'
@@ -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
 
@@ -1,8 +1,9 @@
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 Any, ClassVar, TypeVar, cast
6
7
 
7
8
  import click
8
9
  from pydantic import BaseModel, ConfigDict
@@ -416,7 +417,10 @@ class WryModel(BaseModel):
416
417
  if field_info.default is not PydanticUndefined:
417
418
  config_data[field_name] = TrackedValue(field_info.default, ValueSource.DEFAULT)
418
419
  elif field_info.default_factory is not None:
419
- config_data[field_name] = TrackedValue(field_info.default_factory(), ValueSource.DEFAULT)
420
+ config_data[field_name] = TrackedValue(
421
+ cast(Callable[[], Any], field_info.default_factory)(),
422
+ ValueSource.DEFAULT,
423
+ )
420
424
 
421
425
  return cls.create_with_sources(config_data)
422
426
 
@@ -495,7 +499,7 @@ class WryModel(BaseModel):
495
499
  config_data[field_name] = TrackedValue(field_info.default, ValueSource.DEFAULT)
496
500
  elif field_info.default_factory is not None:
497
501
  factory = field_info.default_factory
498
- config_data[field_name] = TrackedValue(factory(), ValueSource.DEFAULT)
502
+ config_data[field_name] = TrackedValue(cast(Callable[[], Any], factory)(), ValueSource.DEFAULT)
499
503
 
500
504
  # 2. Override with environment values
501
505
  for field_name, value in env_values.items():
@@ -624,6 +628,29 @@ class WryModel(BaseModel):
624
628
  if field_info.default is not PydanticUndefined:
625
629
  result[field_name] = field_info.default
626
630
  elif field_info.default_factory is not None:
627
- result[field_name] = field_info.default_factory()
631
+ result[field_name] = cast(Callable[[], Any], field_info.default_factory)()
628
632
 
629
633
  return result
634
+
635
+ @classmethod
636
+ def generate_click_parameters(cls) -> Callable:
637
+ """Generate Click parameters decorator for this model.
638
+
639
+ This is a convenience method that allows using the decorator
640
+ directly from the model class without needing to import it.
641
+
642
+ Example:
643
+ ```python
644
+ @click.command()
645
+ @MyConfig.generate_click_parameters()
646
+ @click.pass_context
647
+ def cli(ctx, **kwargs):
648
+ config = MyConfig.from_click_context(ctx, **kwargs)
649
+ ```
650
+
651
+ Returns:
652
+ The generate_click_parameters decorator configured for this model
653
+ """
654
+ from ..click_integration import generate_click_parameters
655
+
656
+ 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.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
@@ -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