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.
Files changed (125) hide show
  1. wry-0.1.10.dev4/.github/workflows/check-requirements.yml +32 -0
  2. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.github/workflows/ci-cd.yml +5 -2
  3. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.pre-commit-config.yaml +2 -9
  4. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/CHANGELOG.md +28 -1
  5. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/PKG-INFO +61 -12
  6. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/README.md +60 -11
  7. wry-0.1.10.dev4/RELEASE_PROCESS.md +80 -0
  8. wry-0.1.10.dev4/examples/auto_instantiate_edge_cases.py +165 -0
  9. wry-0.1.10.dev4/examples/auto_instantiate_poc.py +330 -0
  10. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/auto_model_example.py +50 -39
  11. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/simple_cli.py +18 -3
  12. wry-0.1.10.dev4/requirements-dev.txt +28 -0
  13. wry-0.1.10.dev4/scripts/update-requirements.sh +59 -0
  14. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_lambda_parsing.py +6 -6
  15. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_parameter_generation.py +1 -1
  16. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_predicate_handling.py +3 -3
  17. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_lambda_behavior.py +3 -15
  18. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_lambda_error_handling.py +5 -6
  19. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_type_handling.py +2 -2
  20. wry-0.1.10.dev4/tests/unit/test_multiple_option_bug.py +337 -0
  21. wry-0.1.10.dev4/tests/unit/test_variadic_argument_bug.py +154 -0
  22. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/_version.py +3 -3
  23. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/click_integration.py +18 -1
  24. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/PKG-INFO +61 -12
  25. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/SOURCES.txt +8 -0
  26. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.gitignore +0 -0
  27. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/.markdownlint.json +0 -0
  28. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/LICENSE +0 -0
  29. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/TODO.md +0 -0
  30. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/check.sh +0 -0
  31. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/config.json +0 -0
  32. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/intermediate_example.py +0 -0
  33. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/multi_model_example.py +0 -0
  34. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/examples/source_tracking_example.py +0 -0
  35. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/pyproject.toml +0 -0
  36. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/README.md +0 -0
  37. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/extract_release_notes.py +0 -0
  38. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_all_versions.sh +0 -0
  39. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_ci_locally.sh +0 -0
  40. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/scripts/test_with_act.sh +0 -0
  41. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/setup.cfg +0 -0
  42. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/README.md +0 -0
  43. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/__init__.py +0 -0
  44. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/__init__.py +0 -0
  45. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_auto_model.py +0 -0
  46. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_multi_model.py +0 -0
  47. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/features/test_source_precedence.py +0 -0
  48. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/__init__.py +0 -0
  49. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_edge_cases.py +0 -0
  50. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_integration.py +0 -0
  51. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_click_integration_extended.py +0 -0
  52. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/integration/test_context_handling.py +0 -0
  53. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/__init__.py +0 -0
  54. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/__init__.py +0 -0
  55. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -0
  56. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_auto_model_field_processing.py +0 -0
  57. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_field_annotation_handling.py +0 -0
  58. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_field_annotations.py +0 -0
  59. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/auto_model/test_type_inference.py +0 -0
  60. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/README_TESTING_STRATEGY.md +0 -0
  61. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/__init__.py +0 -0
  62. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_bool_flag_handling.py +0 -0
  63. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_config_building.py +0 -0
  64. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_constraint_formatting.py +0 -0
  65. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_decorator_edge_cases.py +0 -0
  66. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_interval_constraints.py +0 -0
  67. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_click_length_constraints.py +0 -0
  68. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_closure_extraction_errors.py +0 -0
  69. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_closure_handling.py +0 -0
  70. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_constraint_behavior.py +0 -0
  71. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_constraint_edge_cases.py +0 -0
  72. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_env_vars_option.py +0 -0
  73. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_json_config_loading.py +0 -0
  74. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_predicate_source_errors.py +0 -0
  75. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/click/test_strict_mode_errors.py +0 -0
  76. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/__init__.py +0 -0
  77. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_accessors.py +0 -0
  78. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_advanced_features.py +0 -0
  79. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_core.py +0 -0
  80. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_edge_cases.py +0 -0
  81. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_env_utils.py +0 -0
  82. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_constraint_extraction.py +0 -0
  83. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_utils.py +0 -0
  84. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_field_utils_edge_cases.py +0 -0
  85. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_sources.py +0 -0
  86. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/core/test_type_checking_blocks.py +0 -0
  87. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/__init__.py +0 -0
  88. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_accessor_caching.py +0 -0
  89. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_extract_edge_cases.py +0 -0
  90. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_click_context_handling.py +0 -0
  91. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_data_extraction.py +0 -0
  92. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_default_handling.py +0 -0
  93. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_environment_integration.py +0 -0
  94. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -0
  95. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_extraction_methods.py +0 -0
  96. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_field_errors.py +0 -0
  97. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_model_object_extraction.py +0 -0
  98. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_non_dict_object_extraction.py +0 -0
  99. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/model/test_object_attribute_extraction.py +0 -0
  100. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/__init__.py +0 -0
  101. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/test_multi_model.py +0 -0
  102. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/multi_model/test_type_checking.py +0 -0
  103. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_auto_model_field_processing.py +0 -0
  104. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_comprehensive_imports.py +0 -0
  105. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_generate_click_classmethod.py +0 -0
  106. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init.py +0 -0
  107. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init_edge_cases.py +0 -0
  108. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_init_version_edge_cases.py +0 -0
  109. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_model_extraction_methods.py +0 -0
  110. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_type_checking_imports.py +0 -0
  111. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_version_fallback.py +0 -0
  112. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/tests/unit/test_version_parsing.py +0 -0
  113. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/__init__.py +0 -0
  114. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/auto_model.py +0 -0
  115. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/__init__.py +0 -0
  116. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/accessors.py +0 -0
  117. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/env_utils.py +0 -0
  118. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/field_utils.py +0 -0
  119. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/model.py +0 -0
  120. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/core/sources.py +0 -0
  121. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/multi_model.py +0 -0
  122. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry/py.typed +0 -0
  123. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/dependency_links.txt +0 -0
  124. {wry-0.1.9.dev1 → wry-0.1.10.dev4}/wry.egg-info/requires.txt +0 -0
  125. {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
- pip install -e ".[dev,test]"
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: v4.4.0
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=90]
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.8...HEAD
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.9.dev1
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, generate_click_parameters
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(AppArgs)
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(AppArgs)
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(AppArgs)
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, generate_click_parameters, multi_model
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
- def serve(server: ServerConfig, database: DatabaseConfig):
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, generate_click_parameters
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(QuickConfig)
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(Config) # strict=True by default
311
- @generate_click_parameters(Config) # ERROR: Duplicate decorator detected!
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(Config, strict=False)
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, generate_click_parameters
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(AppArgs)
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(AppArgs)
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(AppArgs)
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, generate_click_parameters, multi_model
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
- def serve(server: ServerConfig, database: DatabaseConfig):
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, generate_click_parameters
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(QuickConfig)
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(Config) # strict=True by default
265
- @generate_click_parameters(Config) # ERROR: Duplicate decorator detected!
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(Config, strict=False)
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