wry 0.1.9.dev1__tar.gz → 0.1.10.dev4__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.10.dev4/.github/workflows/check-requirements.yml +32 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.github/workflows/ci-cd.yml +5 -2
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.pre-commit-config.yaml +2 -9
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/CHANGELOG.md +28 -1
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/PKG-INFO +61 -12
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/README.md +60 -11
- wry-0.1.10.dev4/RELEASE_PROCESS.md +80 -0
- wry-0.1.10.dev4/examples/auto_instantiate_edge_cases.py +165 -0
- wry-0.1.10.dev4/examples/auto_instantiate_poc.py +330 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/auto_model_example.py +50 -39
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/simple_cli.py +18 -3
- wry-0.1.10.dev4/requirements-dev.txt +28 -0
- wry-0.1.10.dev4/scripts/update-requirements.sh +59 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_lambda_parsing.py +6 -6
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_parameter_generation.py +1 -1
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_predicate_handling.py +3 -3
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_lambda_behavior.py +3 -15
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_lambda_error_handling.py +5 -6
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_type_handling.py +2 -2
- wry-0.1.10.dev4/tests/unit/test_multiple_option_bug.py +337 -0
- wry-0.1.10.dev4/tests/unit/test_variadic_argument_bug.py +154 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/_version.py +3 -3
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/click_integration.py +18 -1
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/PKG-INFO +61 -12
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/SOURCES.txt +8 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.gitignore +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.markdownlint.json +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/LICENSE +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/TODO.md +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/check.sh +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/config.json +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/intermediate_example.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/multi_model_example.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/source_tracking_example.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/pyproject.toml +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/README.md +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/extract_release_notes.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_all_versions.sh +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_ci_locally.sh +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_with_act.sh +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/setup.cfg +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/README.md +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_auto_model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_multi_model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_source_precedence.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_integration.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_integration_extended.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_context_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_field_annotations.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_type_inference.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_bool_flag_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_config_building.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_constraint_formatting.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_interval_constraints.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_length_constraints.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_closure_extraction_errors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_closure_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_constraint_behavior.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_constraint_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_env_vars_option.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_json_config_loading.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_predicate_source_errors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_strict_mode_errors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_accessors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_advanced_features.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_core.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_env_utils.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_constraint_extraction.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_utils.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_sources.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_type_checking_blocks.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_accessor_caching.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_extract_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_click_context_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_data_extraction.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_default_handling.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_environment_integration.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_extraction_methods.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_field_errors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_object_extraction.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_object_attribute_extraction.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/test_multi_model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/test_type_checking.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_auto_model_field_processing.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_comprehensive_imports.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_generate_click_classmethod.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init_version_edge_cases.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_model_extraction_methods.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_type_checking_imports.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_version_fallback.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_version_parsing.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/auto_model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/__init__.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/accessors.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/env_utils.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/field_utils.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/sources.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/multi_model.py +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/py.typed +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/dependency_links.txt +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/requires.txt +0 -0
- {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Check Requirements
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- 'pyproject.toml'
|
|
7
|
+
- 'requirements-dev.txt'
|
|
8
|
+
- '.github/workflows/check-requirements.yml'
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
check-requirements:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Set up Python
|
|
17
|
+
uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: '3.12'
|
|
20
|
+
|
|
21
|
+
- name: Check requirements consistency
|
|
22
|
+
run: |
|
|
23
|
+
echo "Checking that requirements-dev.txt matches pyproject.toml constraints..."
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
|
|
26
|
+
# Install from requirements file
|
|
27
|
+
pip install -r requirements-dev.txt
|
|
28
|
+
|
|
29
|
+
# Check if package can be installed with these deps
|
|
30
|
+
pip install --no-deps -e .
|
|
31
|
+
|
|
32
|
+
echo "✅ Requirements file is valid and consistent!"
|
|
@@ -35,14 +35,17 @@ jobs:
|
|
|
35
35
|
uses: actions/cache@v4
|
|
36
36
|
with:
|
|
37
37
|
path: ~/.cache/pip
|
|
38
|
-
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
|
|
38
|
+
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml', 'requirements-dev.txt') }}
|
|
39
39
|
restore-keys: |
|
|
40
40
|
${{ runner.os }}-pip-
|
|
41
41
|
|
|
42
42
|
- name: Install dependencies
|
|
43
43
|
run: |
|
|
44
44
|
python -m pip install --upgrade pip
|
|
45
|
-
|
|
45
|
+
# Install exact versions from requirements file first
|
|
46
|
+
pip install -r requirements-dev.txt
|
|
47
|
+
# Then install the package in editable mode (without deps to avoid conflicts)
|
|
48
|
+
pip install --no-deps -e .
|
|
46
49
|
|
|
47
50
|
- name: Install pre-commit hooks
|
|
48
51
|
run: |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev:
|
|
3
|
+
rev: v6.0.0
|
|
4
4
|
hooks:
|
|
5
5
|
- id: trailing-whitespace
|
|
6
6
|
- id: end-of-file-fixer
|
|
@@ -25,7 +25,7 @@ repos:
|
|
|
25
25
|
entry: pytest
|
|
26
26
|
language: system
|
|
27
27
|
types: [python]
|
|
28
|
-
args: [--cov=wry, --cov-branch, --cov-report=xml, --cov-report=term-missing, --cov-fail-under=
|
|
28
|
+
args: [tests/unit/, --cov=wry, --cov-branch, --cov-report=xml, --cov-report=term-missing, --cov-fail-under=85]
|
|
29
29
|
pass_filenames: false
|
|
30
30
|
always_run: true
|
|
31
31
|
|
|
@@ -41,13 +41,6 @@ repos:
|
|
|
41
41
|
- annotated-types>=0.6.0
|
|
42
42
|
- setuptools-scm>=8.0
|
|
43
43
|
|
|
44
|
-
# Temporarily disabled - bandit has dependency issues in pre-commit
|
|
45
|
-
# - repo: https://github.com/PyCQA/bandit
|
|
46
|
-
# rev: 1.7.5
|
|
47
|
-
# hooks:
|
|
48
|
-
# - id: bandit
|
|
49
|
-
# args: [-c, pyproject.toml]
|
|
50
|
-
|
|
51
44
|
exclude: |
|
|
52
45
|
(?x)(
|
|
53
46
|
# Exclude auto-generated files
|
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.9] - 2025-09-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Automatic multiple option support for list types**
|
|
15
|
+
- `list[str]` and `tuple[str, ...]` fields now automatically generate Click options with `multiple=True`
|
|
16
|
+
- Supports both `AutoWryModel` and `WryModel` with proper type conversion
|
|
17
|
+
- Handles edge cases: empty lists, single values, and multiple values
|
|
18
|
+
- Works with different data types: `list[int]`, `list[str]`, etc.
|
|
19
|
+
- Maintains type safety: Click tuples are correctly converted to Python lists
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Variadic argument bug resolution**
|
|
24
|
+
- Fixed issue where variadic Click arguments (`nargs=-1`) were incorrectly converted to strings
|
|
25
|
+
- Variadic arguments now preserve their tuple type when used with `@generate_click_parameters`
|
|
26
|
+
- Resolves duplicate parameter warnings and validation errors
|
|
27
|
+
|
|
28
|
+
### Testing
|
|
29
|
+
|
|
30
|
+
- **Comprehensive test coverage for multiple options**
|
|
31
|
+
- Added `tests/unit/test_multiple_option_bug.py` with 6 test cases
|
|
32
|
+
- Added `tests/unit/test_variadic_argument_bug.py` with 3 test cases
|
|
33
|
+
- Tests cover AutoWryModel, WryModel, edge cases, type validation, and different data types
|
|
34
|
+
- All tests pass with proper type checking and validation
|
|
35
|
+
|
|
10
36
|
## [0.1.8] - 2025-09-10
|
|
11
37
|
|
|
12
38
|
### Fixed
|
|
@@ -144,7 +170,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
144
170
|
|
|
145
171
|
- Initial version as `drycli` package (before rename to `wry`)
|
|
146
172
|
|
|
147
|
-
[Unreleased]: https://github.com/tahouse/wry/compare/v0.1.
|
|
173
|
+
[Unreleased]: https://github.com/tahouse/wry/compare/v0.1.9...HEAD
|
|
174
|
+
[0.1.9]: https://github.com/tahouse/wry/compare/v0.1.8...v0.1.9
|
|
148
175
|
[0.1.8]: https://github.com/tahouse/wry/compare/v0.1.7...v0.1.8
|
|
149
176
|
[0.1.7]: https://github.com/tahouse/wry/compare/v0.1.6...v0.1.7
|
|
150
177
|
[0.1.6]: https://github.com/tahouse/wry/compare/v0.1.5...v0.1.6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wry
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10.dev4
|
|
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
|
|
@@ -81,7 +81,7 @@ The simplest way to use wry is with `AutoWryModel`, which automatically generate
|
|
|
81
81
|
```python
|
|
82
82
|
import click
|
|
83
83
|
from pydantic import Field
|
|
84
|
-
from wry import AutoWryModel
|
|
84
|
+
from wry import AutoWryModel
|
|
85
85
|
|
|
86
86
|
class AppArgs(AutoWryModel):
|
|
87
87
|
"""Configuration for my app."""
|
|
@@ -91,9 +91,10 @@ class AppArgs(AutoWryModel):
|
|
|
91
91
|
verbose: bool = Field(default=False, description="Verbose output")
|
|
92
92
|
|
|
93
93
|
@click.command()
|
|
94
|
-
@generate_click_parameters(
|
|
94
|
+
@AppArgs.generate_click_parameters()
|
|
95
95
|
def main(**kwargs):
|
|
96
96
|
"""My simple CLI application."""
|
|
97
|
+
# Create the model instance from kwargs
|
|
97
98
|
config = AppArgs(**kwargs)
|
|
98
99
|
click.echo(f"Hello {config.name}, you are {config.age} years old!")
|
|
99
100
|
|
|
@@ -101,6 +102,8 @@ if __name__ == "__main__":
|
|
|
101
102
|
main()
|
|
102
103
|
```
|
|
103
104
|
|
|
105
|
+
**Note**: Currently, `generate_click_parameters()` passes individual parameters as kwargs to your function. You need to instantiate the model yourself. See the [Future Features](#future-features) section for a potential cleaner API.
|
|
106
|
+
|
|
104
107
|
Run it:
|
|
105
108
|
|
|
106
109
|
```bash
|
|
@@ -122,7 +125,7 @@ wry can track where each configuration value came from. You have two options:
|
|
|
122
125
|
|
|
123
126
|
```python
|
|
124
127
|
@click.command()
|
|
125
|
-
@generate_click_parameters(
|
|
128
|
+
@AppArgs.generate_click_parameters()
|
|
126
129
|
def main(**kwargs):
|
|
127
130
|
# Simple instantiation - no source tracking
|
|
128
131
|
config = AppArgs(**kwargs)
|
|
@@ -133,7 +136,7 @@ def main(**kwargs):
|
|
|
133
136
|
|
|
134
137
|
```python
|
|
135
138
|
@click.command()
|
|
136
|
-
@generate_click_parameters(
|
|
139
|
+
@AppArgs.generate_click_parameters()
|
|
137
140
|
@click.pass_context
|
|
138
141
|
def main(ctx, **kwargs):
|
|
139
142
|
# Full source tracking with context
|
|
@@ -210,7 +213,7 @@ Use multiple Pydantic models in a single command:
|
|
|
210
213
|
```python
|
|
211
214
|
from typing import Annotated
|
|
212
215
|
import click
|
|
213
|
-
from wry import WryModel, AutoOption,
|
|
216
|
+
from wry import WryModel, AutoOption, multi_model, create_models
|
|
214
217
|
|
|
215
218
|
class ServerConfig(WryModel):
|
|
216
219
|
host: Annotated[str, AutoOption] = "localhost"
|
|
@@ -222,7 +225,13 @@ class DatabaseArgs(WryModel):
|
|
|
222
225
|
|
|
223
226
|
@click.command()
|
|
224
227
|
@multi_model(ServerConfig, DatabaseConfig)
|
|
225
|
-
|
|
228
|
+
@click.pass_context
|
|
229
|
+
def serve(ctx, **kwargs):
|
|
230
|
+
# Create model instances
|
|
231
|
+
configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
|
|
232
|
+
server = configs[ServerConfig]
|
|
233
|
+
database = configs[DatabaseConfig]
|
|
234
|
+
|
|
226
235
|
print(f"Starting server at {server.host}:{server.port}")
|
|
227
236
|
print(f"Database: {database.db_url} (pool size: {database.pool_size})")
|
|
228
237
|
```
|
|
@@ -233,7 +242,7 @@ Automatically generate options for all fields:
|
|
|
233
242
|
|
|
234
243
|
```python
|
|
235
244
|
import click
|
|
236
|
-
from wry import AutoWryModel
|
|
245
|
+
from wry import AutoWryModel
|
|
237
246
|
from pydantic import Field
|
|
238
247
|
|
|
239
248
|
class QuickConfig(AutoWryModel):
|
|
@@ -244,7 +253,7 @@ class QuickConfig(AutoWryModel):
|
|
|
244
253
|
email: str = Field(description="Your email")
|
|
245
254
|
|
|
246
255
|
@click.command()
|
|
247
|
-
@generate_click_parameters(
|
|
256
|
+
@QuickConfig.generate_click_parameters()
|
|
248
257
|
def quickstart(config: QuickConfig):
|
|
249
258
|
print(f"Hello {config.name}!")
|
|
250
259
|
```
|
|
@@ -307,8 +316,8 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
|
|
|
307
316
|
|
|
308
317
|
```python
|
|
309
318
|
@click.command()
|
|
310
|
-
@generate_click_parameters(
|
|
311
|
-
@generate_click_parameters(
|
|
319
|
+
@Config.generate_click_parameters() # strict=True by default
|
|
320
|
+
@Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
|
|
312
321
|
def main(**kwargs):
|
|
313
322
|
pass
|
|
314
323
|
```
|
|
@@ -316,7 +325,7 @@ def main(**kwargs):
|
|
|
316
325
|
To allow multiple decorators (not recommended):
|
|
317
326
|
|
|
318
327
|
```python
|
|
319
|
-
@generate_click_parameters(
|
|
328
|
+
@Config.generate_click_parameters(strict=False)
|
|
320
329
|
```
|
|
321
330
|
|
|
322
331
|
### Manual Field Control
|
|
@@ -517,6 +526,20 @@ The wry codebase is organized into focused modules:
|
|
|
517
526
|
|
|
518
527
|
We welcome contributions! Please follow these guidelines to ensure a smooth process.
|
|
519
528
|
|
|
529
|
+
### Development Setup
|
|
530
|
+
|
|
531
|
+
To ensure consistency between local development and CI environments, we use pinned dependencies:
|
|
532
|
+
|
|
533
|
+
```bash
|
|
534
|
+
# Install development dependencies with exact versions
|
|
535
|
+
pip install -r requirements-dev.txt
|
|
536
|
+
|
|
537
|
+
# Install the package in editable mode
|
|
538
|
+
pip install -e .
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Important**: Always use `requirements-dev.txt` for development to ensure your local environment matches CI/CD.
|
|
542
|
+
|
|
520
543
|
### Getting Started
|
|
521
544
|
|
|
522
545
|
1. **Fork the repository** on GitHub
|
|
@@ -642,7 +665,33 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
642
665
|
|
|
643
666
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
644
667
|
|
|
668
|
+
## Future Features
|
|
669
|
+
|
|
670
|
+
### Automatic Model Instantiation (Proof of Concept)
|
|
671
|
+
|
|
672
|
+
We're exploring a cleaner API that would automatically instantiate models and pass them to your function. See `examples/auto_instantiate_poc.py` for a working proof of concept:
|
|
673
|
+
|
|
674
|
+
```python
|
|
675
|
+
# Potential future syntax
|
|
676
|
+
@click.command()
|
|
677
|
+
@AppConfig.click_command() # or @auto_instantiate(AppConfig)
|
|
678
|
+
def main(config: AppConfig):
|
|
679
|
+
"""The decorator would handle instantiation automatically."""
|
|
680
|
+
click.echo(f"Hello {config.name}!")
|
|
681
|
+
# Source tracking would work automatically too!
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
This would:
|
|
685
|
+
|
|
686
|
+
- Automatically handle `@click.pass_context` when needed for source tracking
|
|
687
|
+
- Instantiate the model and pass it with the correct parameter name
|
|
688
|
+
- Support multiple models in a single command
|
|
689
|
+
- Make the API more intuitive and similar to other libraries
|
|
690
|
+
|
|
691
|
+
If you're interested in this feature, please provide feedback!
|
|
692
|
+
|
|
645
693
|
## Acknowledgments
|
|
646
694
|
|
|
647
695
|
- Built on top of [Click](https://click.palletsprojects.com/) and [Pydantic](https://pydantic-docs.helpmanual.io/)
|
|
648
696
|
- Inspired by the DRY (Don't Repeat Yourself) principle
|
|
697
|
+
We'd also like to acknowledgme `pydanclick`, which uses a similar clean syntax (no kwargs to command functions). The code for this feature will be independently written given that `wry` supports source tracking, constraint help text creation, instantiation from config files, and several other features not supported by `pydanclick`.
|
|
@@ -35,7 +35,7 @@ The simplest way to use wry is with `AutoWryModel`, which automatically generate
|
|
|
35
35
|
```python
|
|
36
36
|
import click
|
|
37
37
|
from pydantic import Field
|
|
38
|
-
from wry import AutoWryModel
|
|
38
|
+
from wry import AutoWryModel
|
|
39
39
|
|
|
40
40
|
class AppArgs(AutoWryModel):
|
|
41
41
|
"""Configuration for my app."""
|
|
@@ -45,9 +45,10 @@ class AppArgs(AutoWryModel):
|
|
|
45
45
|
verbose: bool = Field(default=False, description="Verbose output")
|
|
46
46
|
|
|
47
47
|
@click.command()
|
|
48
|
-
@generate_click_parameters(
|
|
48
|
+
@AppArgs.generate_click_parameters()
|
|
49
49
|
def main(**kwargs):
|
|
50
50
|
"""My simple CLI application."""
|
|
51
|
+
# Create the model instance from kwargs
|
|
51
52
|
config = AppArgs(**kwargs)
|
|
52
53
|
click.echo(f"Hello {config.name}, you are {config.age} years old!")
|
|
53
54
|
|
|
@@ -55,6 +56,8 @@ if __name__ == "__main__":
|
|
|
55
56
|
main()
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
**Note**: Currently, `generate_click_parameters()` passes individual parameters as kwargs to your function. You need to instantiate the model yourself. See the [Future Features](#future-features) section for a potential cleaner API.
|
|
60
|
+
|
|
58
61
|
Run it:
|
|
59
62
|
|
|
60
63
|
```bash
|
|
@@ -76,7 +79,7 @@ wry can track where each configuration value came from. You have two options:
|
|
|
76
79
|
|
|
77
80
|
```python
|
|
78
81
|
@click.command()
|
|
79
|
-
@generate_click_parameters(
|
|
82
|
+
@AppArgs.generate_click_parameters()
|
|
80
83
|
def main(**kwargs):
|
|
81
84
|
# Simple instantiation - no source tracking
|
|
82
85
|
config = AppArgs(**kwargs)
|
|
@@ -87,7 +90,7 @@ def main(**kwargs):
|
|
|
87
90
|
|
|
88
91
|
```python
|
|
89
92
|
@click.command()
|
|
90
|
-
@generate_click_parameters(
|
|
93
|
+
@AppArgs.generate_click_parameters()
|
|
91
94
|
@click.pass_context
|
|
92
95
|
def main(ctx, **kwargs):
|
|
93
96
|
# Full source tracking with context
|
|
@@ -164,7 +167,7 @@ Use multiple Pydantic models in a single command:
|
|
|
164
167
|
```python
|
|
165
168
|
from typing import Annotated
|
|
166
169
|
import click
|
|
167
|
-
from wry import WryModel, AutoOption,
|
|
170
|
+
from wry import WryModel, AutoOption, multi_model, create_models
|
|
168
171
|
|
|
169
172
|
class ServerConfig(WryModel):
|
|
170
173
|
host: Annotated[str, AutoOption] = "localhost"
|
|
@@ -176,7 +179,13 @@ class DatabaseArgs(WryModel):
|
|
|
176
179
|
|
|
177
180
|
@click.command()
|
|
178
181
|
@multi_model(ServerConfig, DatabaseConfig)
|
|
179
|
-
|
|
182
|
+
@click.pass_context
|
|
183
|
+
def serve(ctx, **kwargs):
|
|
184
|
+
# Create model instances
|
|
185
|
+
configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
|
|
186
|
+
server = configs[ServerConfig]
|
|
187
|
+
database = configs[DatabaseConfig]
|
|
188
|
+
|
|
180
189
|
print(f"Starting server at {server.host}:{server.port}")
|
|
181
190
|
print(f"Database: {database.db_url} (pool size: {database.pool_size})")
|
|
182
191
|
```
|
|
@@ -187,7 +196,7 @@ Automatically generate options for all fields:
|
|
|
187
196
|
|
|
188
197
|
```python
|
|
189
198
|
import click
|
|
190
|
-
from wry import AutoWryModel
|
|
199
|
+
from wry import AutoWryModel
|
|
191
200
|
from pydantic import Field
|
|
192
201
|
|
|
193
202
|
class QuickConfig(AutoWryModel):
|
|
@@ -198,7 +207,7 @@ class QuickConfig(AutoWryModel):
|
|
|
198
207
|
email: str = Field(description="Your email")
|
|
199
208
|
|
|
200
209
|
@click.command()
|
|
201
|
-
@generate_click_parameters(
|
|
210
|
+
@QuickConfig.generate_click_parameters()
|
|
202
211
|
def quickstart(config: QuickConfig):
|
|
203
212
|
print(f"Hello {config.name}!")
|
|
204
213
|
```
|
|
@@ -261,8 +270,8 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
|
|
|
261
270
|
|
|
262
271
|
```python
|
|
263
272
|
@click.command()
|
|
264
|
-
@generate_click_parameters(
|
|
265
|
-
@generate_click_parameters(
|
|
273
|
+
@Config.generate_click_parameters() # strict=True by default
|
|
274
|
+
@Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
|
|
266
275
|
def main(**kwargs):
|
|
267
276
|
pass
|
|
268
277
|
```
|
|
@@ -270,7 +279,7 @@ def main(**kwargs):
|
|
|
270
279
|
To allow multiple decorators (not recommended):
|
|
271
280
|
|
|
272
281
|
```python
|
|
273
|
-
@generate_click_parameters(
|
|
282
|
+
@Config.generate_click_parameters(strict=False)
|
|
274
283
|
```
|
|
275
284
|
|
|
276
285
|
### Manual Field Control
|
|
@@ -471,6 +480,20 @@ The wry codebase is organized into focused modules:
|
|
|
471
480
|
|
|
472
481
|
We welcome contributions! Please follow these guidelines to ensure a smooth process.
|
|
473
482
|
|
|
483
|
+
### Development Setup
|
|
484
|
+
|
|
485
|
+
To ensure consistency between local development and CI environments, we use pinned dependencies:
|
|
486
|
+
|
|
487
|
+
```bash
|
|
488
|
+
# Install development dependencies with exact versions
|
|
489
|
+
pip install -r requirements-dev.txt
|
|
490
|
+
|
|
491
|
+
# Install the package in editable mode
|
|
492
|
+
pip install -e .
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Important**: Always use `requirements-dev.txt` for development to ensure your local environment matches CI/CD.
|
|
496
|
+
|
|
474
497
|
### Getting Started
|
|
475
498
|
|
|
476
499
|
1. **Fork the repository** on GitHub
|
|
@@ -596,7 +619,33 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
596
619
|
|
|
597
620
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
598
621
|
|
|
622
|
+
## Future Features
|
|
623
|
+
|
|
624
|
+
### Automatic Model Instantiation (Proof of Concept)
|
|
625
|
+
|
|
626
|
+
We're exploring a cleaner API that would automatically instantiate models and pass them to your function. See `examples/auto_instantiate_poc.py` for a working proof of concept:
|
|
627
|
+
|
|
628
|
+
```python
|
|
629
|
+
# Potential future syntax
|
|
630
|
+
@click.command()
|
|
631
|
+
@AppConfig.click_command() # or @auto_instantiate(AppConfig)
|
|
632
|
+
def main(config: AppConfig):
|
|
633
|
+
"""The decorator would handle instantiation automatically."""
|
|
634
|
+
click.echo(f"Hello {config.name}!")
|
|
635
|
+
# Source tracking would work automatically too!
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
This would:
|
|
639
|
+
|
|
640
|
+
- Automatically handle `@click.pass_context` when needed for source tracking
|
|
641
|
+
- Instantiate the model and pass it with the correct parameter name
|
|
642
|
+
- Support multiple models in a single command
|
|
643
|
+
- Make the API more intuitive and similar to other libraries
|
|
644
|
+
|
|
645
|
+
If you're interested in this feature, please provide feedback!
|
|
646
|
+
|
|
599
647
|
## Acknowledgments
|
|
600
648
|
|
|
601
649
|
- Built on top of [Click](https://click.palletsprojects.com/) and [Pydantic](https://pydantic-docs.helpmanual.io/)
|
|
602
650
|
- Inspired by the DRY (Don't Repeat Yourself) principle
|
|
651
|
+
We'd also like to acknowledgme `pydanclick`, which uses a similar clean syntax (no kwargs to command functions). The code for this feature will be independently written given that `wry` supports source tracking, constraint help text creation, instantiation from config files, and several other features not supported by `pydanclick`.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Release Process for Wry
|
|
2
|
+
|
|
3
|
+
This document outlines the correct process for creating a new release.
|
|
4
|
+
|
|
5
|
+
## Release Checklist
|
|
6
|
+
|
|
7
|
+
Follow these steps IN ORDER for each release:
|
|
8
|
+
|
|
9
|
+
1. **Make all code changes**
|
|
10
|
+
- Implement features/fixes
|
|
11
|
+
- Ensure all tests pass (`./check.sh`)
|
|
12
|
+
- Fix any linting issues
|
|
13
|
+
|
|
14
|
+
2. **Update documentation**
|
|
15
|
+
- Update README.md if needed
|
|
16
|
+
- Update any affected documentation
|
|
17
|
+
|
|
18
|
+
3. **Update CHANGELOG.md**
|
|
19
|
+
- Add a new version section under `## [Unreleased]`
|
|
20
|
+
- Document all changes following [Keep a Changelog](https://keepachangelog.com/) format
|
|
21
|
+
- Update the comparison links at the bottom of the file
|
|
22
|
+
|
|
23
|
+
4. **Commit all changes**
|
|
24
|
+
```bash
|
|
25
|
+
git add -A
|
|
26
|
+
git commit -m "chore: prepare for vX.Y.Z release"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
5. **Create signed tag**
|
|
30
|
+
```bash
|
|
31
|
+
git tag -s vX.Y.Z -m "Release vX.Y.Z: brief description"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
6. **Push changes and tag**
|
|
35
|
+
```bash
|
|
36
|
+
git push origin main
|
|
37
|
+
git push origin vX.Y.Z
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Example
|
|
41
|
+
|
|
42
|
+
For v0.1.8 release:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# 1. Make code changes
|
|
46
|
+
# ... implement type annotation improvements ...
|
|
47
|
+
|
|
48
|
+
# 2. Update CHANGELOG.md
|
|
49
|
+
# Add new section:
|
|
50
|
+
## [0.1.8] - 2025-09-10
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- Improved type annotations for `generate_click_parameters` decorator
|
|
54
|
+
|
|
55
|
+
# 3. Commit everything
|
|
56
|
+
git add -A
|
|
57
|
+
git commit -m "chore: prepare for v0.1.8 release"
|
|
58
|
+
|
|
59
|
+
# 4. Create tag
|
|
60
|
+
git tag -s v0.1.8 -m "Release v0.1.8: improve type annotations"
|
|
61
|
+
|
|
62
|
+
# 5. Push
|
|
63
|
+
git push origin main
|
|
64
|
+
git push origin v0.1.8
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Common Mistakes to Avoid
|
|
68
|
+
|
|
69
|
+
- ❌ Creating the tag before updating CHANGELOG.md
|
|
70
|
+
- ❌ Forgetting to update comparison links in CHANGELOG.md
|
|
71
|
+
- ❌ Not running `./check.sh` before tagging
|
|
72
|
+
- ❌ Creating unsigned tags (always use `-s` for signed tags)
|
|
73
|
+
|
|
74
|
+
## CI/CD Pipeline
|
|
75
|
+
|
|
76
|
+
Once a tag is pushed, the GitHub Actions workflow will:
|
|
77
|
+
1. Run all tests across Python 3.10, 3.11, and 3.12
|
|
78
|
+
2. Build the distribution packages
|
|
79
|
+
3. Publish to PyPI (for tags) or TestPyPI (for main branch)
|
|
80
|
+
4. Create a GitHub release with notes from CHANGELOG.md
|