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.
- {wry-0.1.6.dev3 → wry-0.1.8}/.github/workflows/ci-cd.yml +16 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/.pre-commit-config.yaml +9 -4
- {wry-0.1.6.dev3 → wry-0.1.8}/CHANGELOG.md +23 -1
- {wry-0.1.6.dev3 → wry-0.1.8}/PKG-INFO +1 -3
- {wry-0.1.6.dev3 → wry-0.1.8}/README.md +0 -2
- wry-0.1.8/tests/unit/test_generate_click_classmethod.py +47 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/_version.py +3 -3
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/click_integration.py +7 -3
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/model.py +34 -4
- {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/PKG-INFO +1 -3
- {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/SOURCES.txt +1 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/.gitignore +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/.markdownlint.json +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/LICENSE +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/TODO.md +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/check.sh +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/auto_model_example.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/config.json +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/intermediate_example.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/multi_model_example.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/simple_cli.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/examples/source_tracking_example.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/pyproject.toml +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/scripts/README.md +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/scripts/extract_release_notes.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_all_versions.sh +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_ci_locally.sh +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/scripts/test_with_act.sh +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/setup.cfg +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/README.md +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_auto_model.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_multi_model.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/features/test_source_precedence.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_integration.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_click_integration_extended.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/integration/test_context_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_field_annotations.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/auto_model/test_type_inference.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_bool_flag_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_config_building.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_constraint_formatting.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_interval_constraints.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_lambda_parsing.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_length_constraints.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_parameter_generation.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_click_predicate_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_closure_extraction_errors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_closure_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_constraint_behavior.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_constraint_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_env_vars_option.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_json_config_loading.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_lambda_behavior.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_lambda_error_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_predicate_source_errors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_strict_mode_errors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/click/test_type_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_accessors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_advanced_features.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_core.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_env_utils.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_constraint_extraction.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_utils.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_sources.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/core/test_type_checking_blocks.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_accessor_caching.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_extract_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_click_context_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_data_extraction.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_default_handling.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_environment_integration.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_extraction_methods.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_field_errors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_model_object_extraction.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/model/test_object_attribute_extraction.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/test_multi_model.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/multi_model/test_type_checking.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_auto_model_field_processing.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_comprehensive_imports.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_init_version_edge_cases.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_model_extraction_methods.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_type_checking_imports.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_version_fallback.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/tests/unit/test_version_parsing.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/auto_model.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/__init__.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/accessors.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/env_utils.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/field_utils.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/core/sources.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/multi_model.py +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry/py.typed +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/dependency_links.txt +0 -0
- {wry-0.1.6.dev3 → wry-0.1.8}/wry.egg-info/requires.txt +0 -0
- {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.
|
|
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.
|
|
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.8'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 8)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
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[
|
|
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
|
|
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:
|
|
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(
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|