wry 0.1.9.dev2__tar.gz → 0.2.0__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.9.dev2/wry.egg-info → wry-0.2.0}/PKG-INFO +115 -51
- wry-0.1.9.dev2/PKG-INFO → wry-0.2.0/README.md +100 -68
- wry-0.2.0/pyproject.toml +152 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/__init__.py +5 -22
- wry-0.2.0/wry/_version.py +14 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/auto_model.py +56 -49
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/click_integration.py +30 -3
- wry-0.1.9.dev2/.github/workflows/ci-cd.yml +0 -224
- wry-0.1.9.dev2/.gitignore +0 -142
- wry-0.1.9.dev2/.markdownlint.json +0 -3
- wry-0.1.9.dev2/.pre-commit-config.yaml +0 -59
- wry-0.1.9.dev2/CHANGELOG.md +0 -157
- wry-0.1.9.dev2/README.md +0 -602
- wry-0.1.9.dev2/RELEASE_PROCESS.md +0 -80
- wry-0.1.9.dev2/TODO.md +0 -104
- wry-0.1.9.dev2/check.sh +0 -3
- wry-0.1.9.dev2/examples/auto_model_example.py +0 -176
- wry-0.1.9.dev2/examples/config.json +0 -6
- wry-0.1.9.dev2/examples/intermediate_example.py +0 -89
- wry-0.1.9.dev2/examples/multi_model_example.py +0 -171
- wry-0.1.9.dev2/examples/simple_cli.py +0 -40
- wry-0.1.9.dev2/examples/source_tracking_example.py +0 -102
- wry-0.1.9.dev2/pyproject.toml +0 -140
- wry-0.1.9.dev2/scripts/README.md +0 -23
- wry-0.1.9.dev2/scripts/extract_release_notes.py +0 -112
- wry-0.1.9.dev2/scripts/test_all_versions.sh +0 -86
- wry-0.1.9.dev2/scripts/test_ci_locally.sh +0 -71
- wry-0.1.9.dev2/scripts/test_with_act.sh +0 -36
- wry-0.1.9.dev2/setup.cfg +0 -4
- wry-0.1.9.dev2/tests/README.md +0 -65
- wry-0.1.9.dev2/tests/__init__.py +0 -1
- wry-0.1.9.dev2/tests/features/__init__.py +0 -1
- wry-0.1.9.dev2/tests/features/test_auto_model.py +0 -253
- wry-0.1.9.dev2/tests/features/test_multi_model.py +0 -304
- wry-0.1.9.dev2/tests/features/test_source_precedence.py +0 -302
- wry-0.1.9.dev2/tests/integration/__init__.py +0 -1
- wry-0.1.9.dev2/tests/integration/test_click_edge_cases.py +0 -171
- wry-0.1.9.dev2/tests/integration/test_click_integration.py +0 -217
- wry-0.1.9.dev2/tests/integration/test_click_integration_extended.py +0 -483
- wry-0.1.9.dev2/tests/integration/test_context_handling.py +0 -124
- wry-0.1.9.dev2/tests/unit/__init__.py +0 -1
- wry-0.1.9.dev2/tests/unit/auto_model/__init__.py +0 -0
- wry-0.1.9.dev2/tests/unit/auto_model/test_auto_model_annotation_inference.py +0 -47
- wry-0.1.9.dev2/tests/unit/auto_model/test_auto_model_field_processing.py +0 -64
- wry-0.1.9.dev2/tests/unit/auto_model/test_field_annotation_handling.py +0 -64
- wry-0.1.9.dev2/tests/unit/auto_model/test_field_annotations.py +0 -172
- wry-0.1.9.dev2/tests/unit/auto_model/test_type_inference.py +0 -70
- wry-0.1.9.dev2/tests/unit/click/README_TESTING_STRATEGY.md +0 -50
- wry-0.1.9.dev2/tests/unit/click/__init__.py +0 -0
- wry-0.1.9.dev2/tests/unit/click/test_bool_flag_handling.py +0 -58
- wry-0.1.9.dev2/tests/unit/click/test_click_config_building.py +0 -51
- wry-0.1.9.dev2/tests/unit/click/test_click_constraint_formatting.py +0 -90
- wry-0.1.9.dev2/tests/unit/click/test_click_decorator_edge_cases.py +0 -81
- wry-0.1.9.dev2/tests/unit/click/test_click_interval_constraints.py +0 -45
- wry-0.1.9.dev2/tests/unit/click/test_click_lambda_parsing.py +0 -56
- wry-0.1.9.dev2/tests/unit/click/test_click_length_constraints.py +0 -47
- wry-0.1.9.dev2/tests/unit/click/test_click_parameter_generation.py +0 -108
- wry-0.1.9.dev2/tests/unit/click/test_click_predicate_handling.py +0 -69
- wry-0.1.9.dev2/tests/unit/click/test_closure_extraction_errors.py +0 -94
- wry-0.1.9.dev2/tests/unit/click/test_closure_handling.py +0 -82
- wry-0.1.9.dev2/tests/unit/click/test_constraint_behavior.py +0 -113
- wry-0.1.9.dev2/tests/unit/click/test_constraint_edge_cases.py +0 -84
- wry-0.1.9.dev2/tests/unit/click/test_env_vars_option.py +0 -70
- wry-0.1.9.dev2/tests/unit/click/test_json_config_loading.py +0 -102
- wry-0.1.9.dev2/tests/unit/click/test_lambda_behavior.py +0 -97
- wry-0.1.9.dev2/tests/unit/click/test_lambda_error_handling.py +0 -63
- wry-0.1.9.dev2/tests/unit/click/test_predicate_source_errors.py +0 -46
- wry-0.1.9.dev2/tests/unit/click/test_strict_mode_errors.py +0 -81
- wry-0.1.9.dev2/tests/unit/click/test_type_handling.py +0 -87
- wry-0.1.9.dev2/tests/unit/core/__init__.py +0 -0
- wry-0.1.9.dev2/tests/unit/core/test_accessors.py +0 -69
- wry-0.1.9.dev2/tests/unit/core/test_advanced_features.py +0 -518
- wry-0.1.9.dev2/tests/unit/core/test_core.py +0 -176
- wry-0.1.9.dev2/tests/unit/core/test_edge_cases.py +0 -291
- wry-0.1.9.dev2/tests/unit/core/test_env_utils.py +0 -73
- wry-0.1.9.dev2/tests/unit/core/test_field_constraint_extraction.py +0 -65
- wry-0.1.9.dev2/tests/unit/core/test_field_utils.py +0 -107
- wry-0.1.9.dev2/tests/unit/core/test_field_utils_edge_cases.py +0 -66
- wry-0.1.9.dev2/tests/unit/core/test_sources.py +0 -32
- wry-0.1.9.dev2/tests/unit/core/test_type_checking_blocks.py +0 -56
- wry-0.1.9.dev2/tests/unit/model/__init__.py +0 -0
- wry-0.1.9.dev2/tests/unit/model/test_accessor_caching.py +0 -78
- wry-0.1.9.dev2/tests/unit/model/test_extract_edge_cases.py +0 -118
- wry-0.1.9.dev2/tests/unit/model/test_model_click_context_handling.py +0 -123
- wry-0.1.9.dev2/tests/unit/model/test_model_data_extraction.py +0 -96
- wry-0.1.9.dev2/tests/unit/model/test_model_default_handling.py +0 -74
- wry-0.1.9.dev2/tests/unit/model/test_model_environment_integration.py +0 -66
- wry-0.1.9.dev2/tests/unit/model/test_model_extract_subset_edge_cases.py +0 -116
- wry-0.1.9.dev2/tests/unit/model/test_model_extraction_methods.py +0 -88
- wry-0.1.9.dev2/tests/unit/model/test_model_field_errors.py +0 -54
- wry-0.1.9.dev2/tests/unit/model/test_model_object_extraction.py +0 -102
- wry-0.1.9.dev2/tests/unit/model/test_non_dict_object_extraction.py +0 -84
- wry-0.1.9.dev2/tests/unit/model/test_object_attribute_extraction.py +0 -110
- wry-0.1.9.dev2/tests/unit/multi_model/__init__.py +0 -0
- wry-0.1.9.dev2/tests/unit/multi_model/test_multi_model.py +0 -48
- wry-0.1.9.dev2/tests/unit/multi_model/test_type_checking.py +0 -24
- wry-0.1.9.dev2/tests/unit/test_auto_model_field_processing.py +0 -64
- wry-0.1.9.dev2/tests/unit/test_comprehensive_imports.py +0 -71
- wry-0.1.9.dev2/tests/unit/test_generate_click_classmethod.py +0 -47
- wry-0.1.9.dev2/tests/unit/test_init.py +0 -90
- wry-0.1.9.dev2/tests/unit/test_init_edge_cases.py +0 -123
- wry-0.1.9.dev2/tests/unit/test_init_version_edge_cases.py +0 -64
- wry-0.1.9.dev2/tests/unit/test_model_extraction_methods.py +0 -88
- wry-0.1.9.dev2/tests/unit/test_type_checking_imports.py +0 -49
- wry-0.1.9.dev2/tests/unit/test_version_fallback.py +0 -60
- wry-0.1.9.dev2/tests/unit/test_version_parsing.py +0 -62
- wry-0.1.9.dev2/wry/_version.py +0 -34
- wry-0.1.9.dev2/wry.egg-info/SOURCES.txt +0 -116
- wry-0.1.9.dev2/wry.egg-info/dependency_links.txt +0 -1
- wry-0.1.9.dev2/wry.egg-info/requires.txt +0 -22
- wry-0.1.9.dev2/wry.egg-info/top_level.txt +0 -1
- {wry-0.1.9.dev2 → wry-0.2.0}/LICENSE +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/__init__.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/accessors.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/env_utils.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/field_utils.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/model.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/core/sources.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/multi_model.py +0 -0
- {wry-0.1.9.dev2 → wry-0.2.0}/wry/py.typed +0 -0
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wry
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Why Repeat Yourself? - Define your CLI once with Pydantic models
|
|
5
|
-
Author-email: Tyler House <26489166+tahouse@users.noreply.github.com>
|
|
6
5
|
License: MIT
|
|
7
|
-
|
|
8
|
-
Project-URL: Repository, https://github.com/tahouse/wry
|
|
9
|
-
Project-URL: Issues, https://github.com/tahouse/wry/issues
|
|
10
|
-
Project-URL: Documentation, https://github.com/tahouse/wry#readme
|
|
6
|
+
License-File: LICENSE
|
|
11
7
|
Keywords: cli,pydantic,click,wry,dry,configuration,type-safe
|
|
8
|
+
Author: Tyler House
|
|
9
|
+
Author-email: 26489166+tahouse@users.noreply.github.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
12
11
|
Classifier: Development Status :: 1 - Planning
|
|
13
12
|
Classifier: Intended Audience :: Developers
|
|
14
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -16,33 +15,19 @@ Classifier: Programming Language :: Python :: 3
|
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
21
|
Classifier: Topic :: System :: System Shells
|
|
21
22
|
Classifier: Topic :: Utilities
|
|
22
|
-
Requires-
|
|
23
|
+
Requires-Dist: annotated-types (>=0.6.0,<0.7.0)
|
|
24
|
+
Requires-Dist: click (>=8.0,<9.0)
|
|
25
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
26
|
+
Requires-Dist: pydantic-core (>=2.23.4,<3.0.0)
|
|
27
|
+
Project-URL: Documentation, https://github.com/tahouse/wry#readme
|
|
28
|
+
Project-URL: Homepage, https://github.com/tahouse/wry
|
|
29
|
+
Project-URL: Repository, https://github.com/tahouse/wry
|
|
23
30
|
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: click>=8.0
|
|
26
|
-
Requires-Dist: pydantic>=2.9.2
|
|
27
|
-
Requires-Dist: annotated-types>=0.5.0
|
|
28
|
-
Requires-Dist: pydantic-core>=2.23.4
|
|
29
|
-
Provides-Extra: dev
|
|
30
|
-
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
31
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-xdist; extra == "dev"
|
|
33
|
-
Requires-Dist: ruff; extra == "dev"
|
|
34
|
-
Requires-Dist: mypy>=1.17.1; extra == "dev"
|
|
35
|
-
Requires-Dist: build; extra == "dev"
|
|
36
|
-
Requires-Dist: twine; extra == "dev"
|
|
37
|
-
Requires-Dist: setuptools-scm>=8.0; extra == "dev"
|
|
38
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
39
|
-
Requires-Dist: safety; extra == "dev"
|
|
40
|
-
Requires-Dist: bandit[toml]; extra == "dev"
|
|
41
|
-
Provides-Extra: test
|
|
42
|
-
Requires-Dist: pytest>=6.0; extra == "test"
|
|
43
|
-
Requires-Dist: pytest-cov; extra == "test"
|
|
44
|
-
Requires-Dist: pytest-xdist; extra == "test"
|
|
45
|
-
Dynamic: license-file
|
|
46
31
|
|
|
47
32
|
# wry - Why Repeat Yourself? CLI
|
|
48
33
|
|
|
@@ -81,7 +66,7 @@ The simplest way to use wry is with `AutoWryModel`, which automatically generate
|
|
|
81
66
|
```python
|
|
82
67
|
import click
|
|
83
68
|
from pydantic import Field
|
|
84
|
-
from wry import AutoWryModel
|
|
69
|
+
from wry import AutoWryModel
|
|
85
70
|
|
|
86
71
|
class AppArgs(AutoWryModel):
|
|
87
72
|
"""Configuration for my app."""
|
|
@@ -91,9 +76,10 @@ class AppArgs(AutoWryModel):
|
|
|
91
76
|
verbose: bool = Field(default=False, description="Verbose output")
|
|
92
77
|
|
|
93
78
|
@click.command()
|
|
94
|
-
@generate_click_parameters(
|
|
79
|
+
@AppArgs.generate_click_parameters()
|
|
95
80
|
def main(**kwargs):
|
|
96
81
|
"""My simple CLI application."""
|
|
82
|
+
# Create the model instance from kwargs
|
|
97
83
|
config = AppArgs(**kwargs)
|
|
98
84
|
click.echo(f"Hello {config.name}, you are {config.age} years old!")
|
|
99
85
|
|
|
@@ -101,6 +87,8 @@ if __name__ == "__main__":
|
|
|
101
87
|
main()
|
|
102
88
|
```
|
|
103
89
|
|
|
90
|
+
**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.
|
|
91
|
+
|
|
104
92
|
Run it:
|
|
105
93
|
|
|
106
94
|
```bash
|
|
@@ -122,7 +110,7 @@ wry can track where each configuration value came from. You have two options:
|
|
|
122
110
|
|
|
123
111
|
```python
|
|
124
112
|
@click.command()
|
|
125
|
-
@generate_click_parameters(
|
|
113
|
+
@AppArgs.generate_click_parameters()
|
|
126
114
|
def main(**kwargs):
|
|
127
115
|
# Simple instantiation - no source tracking
|
|
128
116
|
config = AppArgs(**kwargs)
|
|
@@ -133,7 +121,7 @@ def main(**kwargs):
|
|
|
133
121
|
|
|
134
122
|
```python
|
|
135
123
|
@click.command()
|
|
136
|
-
@generate_click_parameters(
|
|
124
|
+
@AppArgs.generate_click_parameters()
|
|
137
125
|
@click.pass_context
|
|
138
126
|
def main(ctx, **kwargs):
|
|
139
127
|
# Full source tracking with context
|
|
@@ -210,7 +198,7 @@ Use multiple Pydantic models in a single command:
|
|
|
210
198
|
```python
|
|
211
199
|
from typing import Annotated
|
|
212
200
|
import click
|
|
213
|
-
from wry import WryModel, AutoOption,
|
|
201
|
+
from wry import WryModel, AutoOption, multi_model, create_models
|
|
214
202
|
|
|
215
203
|
class ServerConfig(WryModel):
|
|
216
204
|
host: Annotated[str, AutoOption] = "localhost"
|
|
@@ -222,7 +210,13 @@ class DatabaseArgs(WryModel):
|
|
|
222
210
|
|
|
223
211
|
@click.command()
|
|
224
212
|
@multi_model(ServerConfig, DatabaseConfig)
|
|
225
|
-
|
|
213
|
+
@click.pass_context
|
|
214
|
+
def serve(ctx, **kwargs):
|
|
215
|
+
# Create model instances
|
|
216
|
+
configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
|
|
217
|
+
server = configs[ServerConfig]
|
|
218
|
+
database = configs[DatabaseConfig]
|
|
219
|
+
|
|
226
220
|
print(f"Starting server at {server.host}:{server.port}")
|
|
227
221
|
print(f"Database: {database.db_url} (pool size: {database.pool_size})")
|
|
228
222
|
```
|
|
@@ -233,7 +227,7 @@ Automatically generate options for all fields:
|
|
|
233
227
|
|
|
234
228
|
```python
|
|
235
229
|
import click
|
|
236
|
-
from wry import AutoWryModel
|
|
230
|
+
from wry import AutoWryModel
|
|
237
231
|
from pydantic import Field
|
|
238
232
|
|
|
239
233
|
class QuickConfig(AutoWryModel):
|
|
@@ -244,7 +238,7 @@ class QuickConfig(AutoWryModel):
|
|
|
244
238
|
email: str = Field(description="Your email")
|
|
245
239
|
|
|
246
240
|
@click.command()
|
|
247
|
-
@generate_click_parameters(
|
|
241
|
+
@QuickConfig.generate_click_parameters()
|
|
248
242
|
def quickstart(config: QuickConfig):
|
|
249
243
|
print(f"Hello {config.name}!")
|
|
250
244
|
```
|
|
@@ -307,8 +301,8 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
|
|
|
307
301
|
|
|
308
302
|
```python
|
|
309
303
|
@click.command()
|
|
310
|
-
@generate_click_parameters(
|
|
311
|
-
@generate_click_parameters(
|
|
304
|
+
@Config.generate_click_parameters() # strict=True by default
|
|
305
|
+
@Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
|
|
312
306
|
def main(**kwargs):
|
|
313
307
|
pass
|
|
314
308
|
```
|
|
@@ -316,7 +310,7 @@ def main(**kwargs):
|
|
|
316
310
|
To allow multiple decorators (not recommended):
|
|
317
311
|
|
|
318
312
|
```python
|
|
319
|
-
@generate_click_parameters(
|
|
313
|
+
@Config.generate_click_parameters(strict=False)
|
|
320
314
|
```
|
|
321
315
|
|
|
322
316
|
### Manual Field Control
|
|
@@ -369,11 +363,14 @@ cd wry
|
|
|
369
363
|
python -m venv venv
|
|
370
364
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
371
365
|
|
|
372
|
-
# Install
|
|
373
|
-
pip install
|
|
366
|
+
# Install Poetry (if not already installed)
|
|
367
|
+
pip install poetry
|
|
368
|
+
|
|
369
|
+
# Install all dependencies in development mode
|
|
370
|
+
poetry install
|
|
374
371
|
|
|
375
372
|
# Install pre-commit hooks
|
|
376
|
-
pre-commit install
|
|
373
|
+
poetry run pre-commit install
|
|
377
374
|
```
|
|
378
375
|
|
|
379
376
|
### Running Tests
|
|
@@ -434,7 +431,8 @@ To ensure consistent behavior between local development and CI:
|
|
|
434
431
|
Install the exact versions used in CI:
|
|
435
432
|
|
|
436
433
|
```bash
|
|
437
|
-
|
|
434
|
+
poetry update
|
|
435
|
+
poetry install
|
|
438
436
|
```
|
|
439
437
|
|
|
440
438
|
### Coverage Requirements
|
|
@@ -517,6 +515,46 @@ The wry codebase is organized into focused modules:
|
|
|
517
515
|
|
|
518
516
|
We welcome contributions! Please follow these guidelines to ensure a smooth process.
|
|
519
517
|
|
|
518
|
+
### Development Setup
|
|
519
|
+
|
|
520
|
+
We use Poetry for dependency management to ensure consistent environments:
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# Install Poetry (if not already installed)
|
|
524
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
525
|
+
|
|
526
|
+
# Install dependencies (including dev dependencies)
|
|
527
|
+
poetry install
|
|
528
|
+
|
|
529
|
+
# Activate the virtual environment
|
|
530
|
+
poetry shell
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Important**: Poetry automatically creates an isolated virtual environment and uses `poetry.lock` to ensure everyone has the exact same dependencies.
|
|
534
|
+
|
|
535
|
+
### Common Poetry Commands
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
# Install dependencies from lock file
|
|
539
|
+
poetry install
|
|
540
|
+
|
|
541
|
+
# Update dependencies within version constraints
|
|
542
|
+
poetry update
|
|
543
|
+
|
|
544
|
+
# Add a new dependency
|
|
545
|
+
poetry add package-name
|
|
546
|
+
|
|
547
|
+
# Add a development dependency
|
|
548
|
+
poetry add --group dev package-name
|
|
549
|
+
|
|
550
|
+
# Show installed packages
|
|
551
|
+
poetry show
|
|
552
|
+
|
|
553
|
+
# Run commands in the Poetry environment
|
|
554
|
+
poetry run python script.py
|
|
555
|
+
poetry run pytest
|
|
556
|
+
```
|
|
557
|
+
|
|
520
558
|
### Getting Started
|
|
521
559
|
|
|
522
560
|
1. **Fork the repository** on GitHub
|
|
@@ -544,10 +582,9 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
544
582
|
1. **Set up development environment**:
|
|
545
583
|
|
|
546
584
|
```bash
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
pre-commit install
|
|
585
|
+
poetry install # Creates venv and installs all dependencies
|
|
586
|
+
poetry shell # Activate the virtual environment
|
|
587
|
+
poetry run pre-commit install # Set up git hooks
|
|
551
588
|
```
|
|
552
589
|
|
|
553
590
|
2. **Make your changes**:
|
|
@@ -560,13 +597,13 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
560
597
|
|
|
561
598
|
```bash
|
|
562
599
|
# Run tests
|
|
563
|
-
pytest
|
|
600
|
+
poetry run pytest
|
|
564
601
|
|
|
565
602
|
# Check coverage (must be 100%)
|
|
566
|
-
pytest --cov=wry --cov-report=term-missing
|
|
603
|
+
poetry run pytest --cov=wry --cov-report=term-missing
|
|
567
604
|
|
|
568
605
|
# Run linting
|
|
569
|
-
pre-commit run --all-files
|
|
606
|
+
poetry run pre-commit run --all-files
|
|
570
607
|
```
|
|
571
608
|
|
|
572
609
|
4. **Commit your changes**:
|
|
@@ -642,7 +679,34 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
642
679
|
|
|
643
680
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
644
681
|
|
|
682
|
+
## Future Features
|
|
683
|
+
|
|
684
|
+
### Automatic Model Instantiation (Proof of Concept)
|
|
685
|
+
|
|
686
|
+
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:
|
|
687
|
+
|
|
688
|
+
```python
|
|
689
|
+
# Potential future syntax
|
|
690
|
+
@click.command()
|
|
691
|
+
@AppConfig.click_command() # or @auto_instantiate(AppConfig)
|
|
692
|
+
def main(config: AppConfig):
|
|
693
|
+
"""The decorator would handle instantiation automatically."""
|
|
694
|
+
click.echo(f"Hello {config.name}!")
|
|
695
|
+
# Source tracking would work automatically too!
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
This would:
|
|
699
|
+
|
|
700
|
+
- Automatically handle `@click.pass_context` when needed for source tracking
|
|
701
|
+
- Instantiate the model and pass it with the correct parameter name
|
|
702
|
+
- Support multiple models in a single command
|
|
703
|
+
- Make the API more intuitive and similar to other libraries
|
|
704
|
+
|
|
705
|
+
If you're interested in this feature, please provide feedback!
|
|
706
|
+
|
|
645
707
|
## Acknowledgments
|
|
646
708
|
|
|
647
709
|
- Built on top of [Click](https://click.palletsprojects.com/) and [Pydantic](https://pydantic-docs.helpmanual.io/)
|
|
648
710
|
- Inspired by the DRY (Don't Repeat Yourself) principle
|
|
711
|
+
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`.
|
|
712
|
+
|
|
@@ -1,49 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: wry
|
|
3
|
-
Version: 0.1.9.dev2
|
|
4
|
-
Summary: Why Repeat Yourself? - Define your CLI once with Pydantic models
|
|
5
|
-
Author-email: Tyler House <26489166+tahouse@users.noreply.github.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/tahouse/wry
|
|
8
|
-
Project-URL: Repository, https://github.com/tahouse/wry
|
|
9
|
-
Project-URL: Issues, https://github.com/tahouse/wry/issues
|
|
10
|
-
Project-URL: Documentation, https://github.com/tahouse/wry#readme
|
|
11
|
-
Keywords: cli,pydantic,click,wry,dry,configuration,type-safe
|
|
12
|
-
Classifier: Development Status :: 1 - Planning
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
-
Classifier: Topic :: System :: System Shells
|
|
21
|
-
Classifier: Topic :: Utilities
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: click>=8.0
|
|
26
|
-
Requires-Dist: pydantic>=2.9.2
|
|
27
|
-
Requires-Dist: annotated-types>=0.5.0
|
|
28
|
-
Requires-Dist: pydantic-core>=2.23.4
|
|
29
|
-
Provides-Extra: dev
|
|
30
|
-
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
31
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-xdist; extra == "dev"
|
|
33
|
-
Requires-Dist: ruff; extra == "dev"
|
|
34
|
-
Requires-Dist: mypy>=1.17.1; extra == "dev"
|
|
35
|
-
Requires-Dist: build; extra == "dev"
|
|
36
|
-
Requires-Dist: twine; extra == "dev"
|
|
37
|
-
Requires-Dist: setuptools-scm>=8.0; extra == "dev"
|
|
38
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
39
|
-
Requires-Dist: safety; extra == "dev"
|
|
40
|
-
Requires-Dist: bandit[toml]; extra == "dev"
|
|
41
|
-
Provides-Extra: test
|
|
42
|
-
Requires-Dist: pytest>=6.0; extra == "test"
|
|
43
|
-
Requires-Dist: pytest-cov; extra == "test"
|
|
44
|
-
Requires-Dist: pytest-xdist; extra == "test"
|
|
45
|
-
Dynamic: license-file
|
|
46
|
-
|
|
47
1
|
# wry - Why Repeat Yourself? CLI
|
|
48
2
|
|
|
49
3
|
[](https://badge.fury.io/py/wry)
|
|
@@ -81,7 +35,7 @@ The simplest way to use wry is with `AutoWryModel`, which automatically generate
|
|
|
81
35
|
```python
|
|
82
36
|
import click
|
|
83
37
|
from pydantic import Field
|
|
84
|
-
from wry import AutoWryModel
|
|
38
|
+
from wry import AutoWryModel
|
|
85
39
|
|
|
86
40
|
class AppArgs(AutoWryModel):
|
|
87
41
|
"""Configuration for my app."""
|
|
@@ -91,9 +45,10 @@ class AppArgs(AutoWryModel):
|
|
|
91
45
|
verbose: bool = Field(default=False, description="Verbose output")
|
|
92
46
|
|
|
93
47
|
@click.command()
|
|
94
|
-
@generate_click_parameters(
|
|
48
|
+
@AppArgs.generate_click_parameters()
|
|
95
49
|
def main(**kwargs):
|
|
96
50
|
"""My simple CLI application."""
|
|
51
|
+
# Create the model instance from kwargs
|
|
97
52
|
config = AppArgs(**kwargs)
|
|
98
53
|
click.echo(f"Hello {config.name}, you are {config.age} years old!")
|
|
99
54
|
|
|
@@ -101,6 +56,8 @@ if __name__ == "__main__":
|
|
|
101
56
|
main()
|
|
102
57
|
```
|
|
103
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
|
+
|
|
104
61
|
Run it:
|
|
105
62
|
|
|
106
63
|
```bash
|
|
@@ -122,7 +79,7 @@ wry can track where each configuration value came from. You have two options:
|
|
|
122
79
|
|
|
123
80
|
```python
|
|
124
81
|
@click.command()
|
|
125
|
-
@generate_click_parameters(
|
|
82
|
+
@AppArgs.generate_click_parameters()
|
|
126
83
|
def main(**kwargs):
|
|
127
84
|
# Simple instantiation - no source tracking
|
|
128
85
|
config = AppArgs(**kwargs)
|
|
@@ -133,7 +90,7 @@ def main(**kwargs):
|
|
|
133
90
|
|
|
134
91
|
```python
|
|
135
92
|
@click.command()
|
|
136
|
-
@generate_click_parameters(
|
|
93
|
+
@AppArgs.generate_click_parameters()
|
|
137
94
|
@click.pass_context
|
|
138
95
|
def main(ctx, **kwargs):
|
|
139
96
|
# Full source tracking with context
|
|
@@ -210,7 +167,7 @@ Use multiple Pydantic models in a single command:
|
|
|
210
167
|
```python
|
|
211
168
|
from typing import Annotated
|
|
212
169
|
import click
|
|
213
|
-
from wry import WryModel, AutoOption,
|
|
170
|
+
from wry import WryModel, AutoOption, multi_model, create_models
|
|
214
171
|
|
|
215
172
|
class ServerConfig(WryModel):
|
|
216
173
|
host: Annotated[str, AutoOption] = "localhost"
|
|
@@ -222,7 +179,13 @@ class DatabaseArgs(WryModel):
|
|
|
222
179
|
|
|
223
180
|
@click.command()
|
|
224
181
|
@multi_model(ServerConfig, DatabaseConfig)
|
|
225
|
-
|
|
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
|
+
|
|
226
189
|
print(f"Starting server at {server.host}:{server.port}")
|
|
227
190
|
print(f"Database: {database.db_url} (pool size: {database.pool_size})")
|
|
228
191
|
```
|
|
@@ -233,7 +196,7 @@ Automatically generate options for all fields:
|
|
|
233
196
|
|
|
234
197
|
```python
|
|
235
198
|
import click
|
|
236
|
-
from wry import AutoWryModel
|
|
199
|
+
from wry import AutoWryModel
|
|
237
200
|
from pydantic import Field
|
|
238
201
|
|
|
239
202
|
class QuickConfig(AutoWryModel):
|
|
@@ -244,7 +207,7 @@ class QuickConfig(AutoWryModel):
|
|
|
244
207
|
email: str = Field(description="Your email")
|
|
245
208
|
|
|
246
209
|
@click.command()
|
|
247
|
-
@generate_click_parameters(
|
|
210
|
+
@QuickConfig.generate_click_parameters()
|
|
248
211
|
def quickstart(config: QuickConfig):
|
|
249
212
|
print(f"Hello {config.name}!")
|
|
250
213
|
```
|
|
@@ -307,8 +270,8 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
|
|
|
307
270
|
|
|
308
271
|
```python
|
|
309
272
|
@click.command()
|
|
310
|
-
@generate_click_parameters(
|
|
311
|
-
@generate_click_parameters(
|
|
273
|
+
@Config.generate_click_parameters() # strict=True by default
|
|
274
|
+
@Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
|
|
312
275
|
def main(**kwargs):
|
|
313
276
|
pass
|
|
314
277
|
```
|
|
@@ -316,7 +279,7 @@ def main(**kwargs):
|
|
|
316
279
|
To allow multiple decorators (not recommended):
|
|
317
280
|
|
|
318
281
|
```python
|
|
319
|
-
@generate_click_parameters(
|
|
282
|
+
@Config.generate_click_parameters(strict=False)
|
|
320
283
|
```
|
|
321
284
|
|
|
322
285
|
### Manual Field Control
|
|
@@ -369,11 +332,14 @@ cd wry
|
|
|
369
332
|
python -m venv venv
|
|
370
333
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
371
334
|
|
|
372
|
-
# Install
|
|
373
|
-
pip install
|
|
335
|
+
# Install Poetry (if not already installed)
|
|
336
|
+
pip install poetry
|
|
337
|
+
|
|
338
|
+
# Install all dependencies in development mode
|
|
339
|
+
poetry install
|
|
374
340
|
|
|
375
341
|
# Install pre-commit hooks
|
|
376
|
-
pre-commit install
|
|
342
|
+
poetry run pre-commit install
|
|
377
343
|
```
|
|
378
344
|
|
|
379
345
|
### Running Tests
|
|
@@ -434,7 +400,8 @@ To ensure consistent behavior between local development and CI:
|
|
|
434
400
|
Install the exact versions used in CI:
|
|
435
401
|
|
|
436
402
|
```bash
|
|
437
|
-
|
|
403
|
+
poetry update
|
|
404
|
+
poetry install
|
|
438
405
|
```
|
|
439
406
|
|
|
440
407
|
### Coverage Requirements
|
|
@@ -517,6 +484,46 @@ The wry codebase is organized into focused modules:
|
|
|
517
484
|
|
|
518
485
|
We welcome contributions! Please follow these guidelines to ensure a smooth process.
|
|
519
486
|
|
|
487
|
+
### Development Setup
|
|
488
|
+
|
|
489
|
+
We use Poetry for dependency management to ensure consistent environments:
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# Install Poetry (if not already installed)
|
|
493
|
+
curl -sSL https://install.python-poetry.org | python3 -
|
|
494
|
+
|
|
495
|
+
# Install dependencies (including dev dependencies)
|
|
496
|
+
poetry install
|
|
497
|
+
|
|
498
|
+
# Activate the virtual environment
|
|
499
|
+
poetry shell
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Important**: Poetry automatically creates an isolated virtual environment and uses `poetry.lock` to ensure everyone has the exact same dependencies.
|
|
503
|
+
|
|
504
|
+
### Common Poetry Commands
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Install dependencies from lock file
|
|
508
|
+
poetry install
|
|
509
|
+
|
|
510
|
+
# Update dependencies within version constraints
|
|
511
|
+
poetry update
|
|
512
|
+
|
|
513
|
+
# Add a new dependency
|
|
514
|
+
poetry add package-name
|
|
515
|
+
|
|
516
|
+
# Add a development dependency
|
|
517
|
+
poetry add --group dev package-name
|
|
518
|
+
|
|
519
|
+
# Show installed packages
|
|
520
|
+
poetry show
|
|
521
|
+
|
|
522
|
+
# Run commands in the Poetry environment
|
|
523
|
+
poetry run python script.py
|
|
524
|
+
poetry run pytest
|
|
525
|
+
```
|
|
526
|
+
|
|
520
527
|
### Getting Started
|
|
521
528
|
|
|
522
529
|
1. **Fork the repository** on GitHub
|
|
@@ -544,10 +551,9 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
544
551
|
1. **Set up development environment**:
|
|
545
552
|
|
|
546
553
|
```bash
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
pre-commit install
|
|
554
|
+
poetry install # Creates venv and installs all dependencies
|
|
555
|
+
poetry shell # Activate the virtual environment
|
|
556
|
+
poetry run pre-commit install # Set up git hooks
|
|
551
557
|
```
|
|
552
558
|
|
|
553
559
|
2. **Make your changes**:
|
|
@@ -560,13 +566,13 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
560
566
|
|
|
561
567
|
```bash
|
|
562
568
|
# Run tests
|
|
563
|
-
pytest
|
|
569
|
+
poetry run pytest
|
|
564
570
|
|
|
565
571
|
# Check coverage (must be 100%)
|
|
566
|
-
pytest --cov=wry --cov-report=term-missing
|
|
572
|
+
poetry run pytest --cov=wry --cov-report=term-missing
|
|
567
573
|
|
|
568
574
|
# Run linting
|
|
569
|
-
pre-commit run --all-files
|
|
575
|
+
poetry run pre-commit run --all-files
|
|
570
576
|
```
|
|
571
577
|
|
|
572
578
|
4. **Commit your changes**:
|
|
@@ -642,7 +648,33 @@ We welcome contributions! Please follow these guidelines to ensure a smooth proc
|
|
|
642
648
|
|
|
643
649
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
644
650
|
|
|
651
|
+
## Future Features
|
|
652
|
+
|
|
653
|
+
### Automatic Model Instantiation (Proof of Concept)
|
|
654
|
+
|
|
655
|
+
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:
|
|
656
|
+
|
|
657
|
+
```python
|
|
658
|
+
# Potential future syntax
|
|
659
|
+
@click.command()
|
|
660
|
+
@AppConfig.click_command() # or @auto_instantiate(AppConfig)
|
|
661
|
+
def main(config: AppConfig):
|
|
662
|
+
"""The decorator would handle instantiation automatically."""
|
|
663
|
+
click.echo(f"Hello {config.name}!")
|
|
664
|
+
# Source tracking would work automatically too!
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
This would:
|
|
668
|
+
|
|
669
|
+
- Automatically handle `@click.pass_context` when needed for source tracking
|
|
670
|
+
- Instantiate the model and pass it with the correct parameter name
|
|
671
|
+
- Support multiple models in a single command
|
|
672
|
+
- Make the API more intuitive and similar to other libraries
|
|
673
|
+
|
|
674
|
+
If you're interested in this feature, please provide feedback!
|
|
675
|
+
|
|
645
676
|
## Acknowledgments
|
|
646
677
|
|
|
647
678
|
- Built on top of [Click](https://click.palletsprojects.com/) and [Pydantic](https://pydantic-docs.helpmanual.io/)
|
|
648
679
|
- Inspired by the DRY (Don't Repeat Yourself) principle
|
|
680
|
+
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`.
|