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