wry 0.3.1.dev0__py3-none-any.whl → 0.3.2.dev2__py3-none-any.whl
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/__init__.py +21 -4
- wry/_version.py +2 -2
- wry/auto_model.py +1 -1
- wry/click_integration.py +20 -6
- wry/core/env_utils.py +20 -3
- wry/core/model.py +49 -12
- wry/multi_model.py +3 -3
- {wry-0.3.1.dev0.dist-info → wry-0.3.2.dev2.dist-info}/METADATA +83 -9
- wry-0.3.2.dev2.dist-info/RECORD +18 -0
- wry-0.3.1.dev0.dist-info/RECORD +0 -18
- {wry-0.3.1.dev0.dist-info → wry-0.3.2.dev2.dist-info}/WHEEL +0 -0
- {wry-0.3.1.dev0.dist-info → wry-0.3.2.dev2.dist-info}/licenses/LICENSE +0 -0
- {wry-0.3.1.dev0.dist-info → wry-0.3.2.dev2.dist-info}/top_level.txt +0 -0
wry/__init__.py
CHANGED
|
@@ -55,10 +55,27 @@ try:
|
|
|
55
55
|
if __commit_id__:
|
|
56
56
|
__version_full__ += f"+{__commit_id__}"
|
|
57
57
|
except ImportError:
|
|
58
|
-
# Fallback for development
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
# Fallback for development/editable installs
|
|
59
|
+
try:
|
|
60
|
+
# Try to get version dynamically for editable installs
|
|
61
|
+
from setuptools_scm import get_version
|
|
62
|
+
|
|
63
|
+
__version__ = get_version(root="../..", relative_to=__file__)
|
|
64
|
+
# Extract commit hash from version if present
|
|
65
|
+
if "+" in __version__ and "g" in __version__:
|
|
66
|
+
# Version like "0.0.2+g1234567" or "0.0.2.dev1+g1234567"
|
|
67
|
+
__commit_id__ = __version__.split("+")[-1].split(".")[0]
|
|
68
|
+
__version_full__ = __version__
|
|
69
|
+
else:
|
|
70
|
+
__commit_id__ = None
|
|
71
|
+
__version_full__ = __version__
|
|
72
|
+
# Clean version for consistency
|
|
73
|
+
__version__ = __version__.split("+")[0]
|
|
74
|
+
except Exception:
|
|
75
|
+
# Ultimate fallback
|
|
76
|
+
__version__ = "0.0.1-dev"
|
|
77
|
+
__version_full__ = __version__
|
|
78
|
+
__commit_id__ = None
|
|
62
79
|
|
|
63
80
|
__author__ = "Tyler House"
|
|
64
81
|
__email__ = "26489166+tahouse@users.noreply.github.com"
|
wry/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.3.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 3,
|
|
31
|
+
__version__ = version = '0.3.2.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 3, 2, 'dev2')
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
wry/auto_model.py
CHANGED
|
@@ -138,7 +138,7 @@ def create_auto_model(name: str, fields: dict[str, Any], **kwargs: Any) -> type[
|
|
|
138
138
|
# Use it with Click
|
|
139
139
|
@click.command()
|
|
140
140
|
@generate_click_parameters(MyConfig)
|
|
141
|
-
def my_command(**kwargs):
|
|
141
|
+
def my_command(**kwargs: Any):
|
|
142
142
|
config = MyConfig.from_click_context(**kwargs)
|
|
143
143
|
print(f"Connecting to {config.host}:{config.port}")
|
|
144
144
|
```
|
wry/click_integration.py
CHANGED
|
@@ -378,7 +378,9 @@ def generate_click_parameters(
|
|
|
378
378
|
|
|
379
379
|
if field_type == AutoClickParameter.OPTION or field_type == AutoClickParameter.REQUIRED_OPTION:
|
|
380
380
|
# Auto-generate Click option from Field info
|
|
381
|
-
|
|
381
|
+
# Use alias if available, otherwise use field name
|
|
382
|
+
name_for_option = field_info.alias if field_info.alias else field_name
|
|
383
|
+
option_name = f"--{name_for_option.replace('_', '-')}"
|
|
382
384
|
|
|
383
385
|
# Check if field is required first (needed to decide on default handling)
|
|
384
386
|
is_required = field_info.is_required() or field_type == AutoClickParameter.REQUIRED_OPTION
|
|
@@ -457,7 +459,9 @@ def generate_click_parameters(
|
|
|
457
459
|
|
|
458
460
|
# Get the environment variable prefix
|
|
459
461
|
env_prefix = getattr(model_class, "env_prefix", "DRYCLI_")
|
|
460
|
-
|
|
462
|
+
# Use alias for env var name if available, otherwise use field name
|
|
463
|
+
name_for_env = field_info.alias if field_info.alias else field_name
|
|
464
|
+
env_var_name = f"{env_prefix}{name_for_env.upper()}"
|
|
461
465
|
|
|
462
466
|
env_var_set = env_var_name in os.environ
|
|
463
467
|
|
|
@@ -508,8 +512,18 @@ def generate_click_parameters(
|
|
|
508
512
|
elif base_type is not str: # Only specify type if not string (Click default)
|
|
509
513
|
click_kwargs["type"] = base_type
|
|
510
514
|
|
|
511
|
-
#
|
|
512
|
-
|
|
515
|
+
# Check if field has a default or if env var is set
|
|
516
|
+
import os
|
|
517
|
+
|
|
518
|
+
env_prefix = getattr(model_class, "env_prefix", "")
|
|
519
|
+
name_for_env = field_info.alias if field_info.alias else field_name
|
|
520
|
+
env_var_name = f"{env_prefix}{name_for_env.upper()}"
|
|
521
|
+
env_var_set = env_var_name in os.environ
|
|
522
|
+
|
|
523
|
+
# Mark as not required if field has default or env var is set
|
|
524
|
+
is_required_arg = field_info.is_required() and not env_var_set
|
|
525
|
+
|
|
526
|
+
arguments.append(click.argument(argument_name, **click_kwargs, required=is_required_arg))
|
|
513
527
|
|
|
514
528
|
# Track argument description for docstring injection
|
|
515
529
|
if field_info.description:
|
|
@@ -633,7 +647,7 @@ def extract_and_modify_argument_decorator(
|
|
|
633
647
|
"""
|
|
634
648
|
# Default values - use a safe fallback
|
|
635
649
|
param_decls: list[str] = ["argument"]
|
|
636
|
-
attrs: dict[str, Any] = {
|
|
650
|
+
attrs: dict[str, Any] = {} # Don't override required - let Click handle it
|
|
637
651
|
|
|
638
652
|
# Try to extract from closure, but don't rely on it
|
|
639
653
|
try:
|
|
@@ -658,7 +672,7 @@ def extract_and_modify_argument_decorator(
|
|
|
658
672
|
for k, v in dict_contents.items():
|
|
659
673
|
if isinstance(k, str):
|
|
660
674
|
attrs[k] = v
|
|
661
|
-
|
|
675
|
+
# Don't override required - preserve original setting
|
|
662
676
|
|
|
663
677
|
# Handle Click parameter classes
|
|
664
678
|
elif isinstance(contents, type):
|
wry/core/env_utils.py
CHANGED
|
@@ -23,8 +23,10 @@ def get_env_var_names(model_class: type[T]) -> dict[str, str]:
|
|
|
23
23
|
prefix = getattr(model_class, "env_prefix", "")
|
|
24
24
|
env_vars = {}
|
|
25
25
|
|
|
26
|
-
for field_name in model_class.model_fields:
|
|
27
|
-
|
|
26
|
+
for field_name, field_info in model_class.model_fields.items():
|
|
27
|
+
# Use alias if available, otherwise use field name
|
|
28
|
+
name_for_env = field_info.alias if field_info.alias else field_name
|
|
29
|
+
env_name = f"{prefix}{name_for_env.upper()}"
|
|
28
30
|
env_vars[field_name] = env_name
|
|
29
31
|
|
|
30
32
|
return env_vars
|
|
@@ -46,9 +48,24 @@ def print_env_vars(model_class: type[T]) -> None:
|
|
|
46
48
|
field_info = model_class.model_fields[field_name]
|
|
47
49
|
field_type = type_hints.get(field_name, Any)
|
|
48
50
|
|
|
49
|
-
#
|
|
51
|
+
# Extract base type from Annotated types
|
|
52
|
+
from typing import Annotated, get_args, get_origin
|
|
53
|
+
|
|
54
|
+
# Handle Annotated[Type, ...] - extract the actual type
|
|
55
|
+
origin = get_origin(field_type)
|
|
56
|
+
if origin is not None and str(origin) == str(Annotated):
|
|
57
|
+
args = get_args(field_type)
|
|
58
|
+
if args:
|
|
59
|
+
# First arg is the actual type (might be a Union)
|
|
60
|
+
field_type = args[0]
|
|
61
|
+
|
|
62
|
+
# Format type for display (preserve Union types like str | None)
|
|
50
63
|
type_str = getattr(field_type, "__name__", str(field_type))
|
|
51
64
|
|
|
65
|
+
# Clean up type string representation for display
|
|
66
|
+
if "typing." in type_str:
|
|
67
|
+
type_str = type_str.replace("typing.", "")
|
|
68
|
+
|
|
52
69
|
# Check if field is required
|
|
53
70
|
required = field_info.default is PydanticUndefined and field_info.default_factory is None
|
|
54
71
|
|
wry/core/model.py
CHANGED
|
@@ -52,7 +52,7 @@ class WryModel(BaseModel):
|
|
|
52
52
|
```
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
55
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, validate_by_name=True, validate_by_alias=True)
|
|
56
56
|
|
|
57
57
|
# Class variables that should not trigger Pydantic warnings
|
|
58
58
|
env_prefix: ClassVar[str] = ""
|
|
@@ -450,7 +450,7 @@ class WryModel(BaseModel):
|
|
|
450
450
|
@click.command()
|
|
451
451
|
@generate_click_parameters(MyConfig)
|
|
452
452
|
@click.pass_context
|
|
453
|
-
def my_command(ctx, **kwargs):
|
|
453
|
+
def my_command(ctx: click.Context, **kwargs: Any):
|
|
454
454
|
config = MyConfig.from_click_context(ctx, **kwargs)
|
|
455
455
|
print(f"Hello {config.name}")
|
|
456
456
|
print(f"Source: {config.source.name}") # Accurate source
|
|
@@ -474,18 +474,41 @@ class WryModel(BaseModel):
|
|
|
474
474
|
strict = cls.model_config.get("extra", "ignore") == "forbid"
|
|
475
475
|
|
|
476
476
|
if strict:
|
|
477
|
-
#
|
|
478
|
-
|
|
477
|
+
# Build alias set for validation
|
|
478
|
+
valid_aliases = {field_info.alias for field_info in cls.model_fields.values() if field_info.alias}
|
|
479
|
+
# Check for extra fields (allow both field names and aliases)
|
|
480
|
+
valid_keys = set(cls.model_fields.keys()) | valid_aliases
|
|
481
|
+
extra_fields = set(kwargs.keys()) - valid_keys
|
|
479
482
|
if extra_fields:
|
|
480
483
|
raise ValueError(f"Extra fields not allowed: {extra_fields}")
|
|
481
484
|
|
|
482
|
-
#
|
|
485
|
+
# Build alias-to-field mapping for handling Pydantic aliases
|
|
486
|
+
alias_to_field: dict[str, str] = {}
|
|
487
|
+
for field_name, field_info in cls.model_fields.items():
|
|
488
|
+
if field_info.alias:
|
|
489
|
+
alias_to_field[field_info.alias] = field_name
|
|
490
|
+
|
|
491
|
+
# Filter kwargs to include both field names AND aliases
|
|
483
492
|
model_fields = set(cls.model_fields.keys())
|
|
484
|
-
filtered_kwargs
|
|
493
|
+
filtered_kwargs: dict[str, Any] = {}
|
|
494
|
+
|
|
495
|
+
for k, v in kwargs.items():
|
|
496
|
+
if k in model_fields:
|
|
497
|
+
# Direct field name match
|
|
498
|
+
filtered_kwargs[k] = v
|
|
499
|
+
elif k in alias_to_field:
|
|
500
|
+
# Alias match - map to field name
|
|
501
|
+
field_name = alias_to_field[k]
|
|
502
|
+
filtered_kwargs[field_name] = v
|
|
485
503
|
|
|
486
504
|
# If kwargs are empty but ctx.params has values, use those (for test compatibility)
|
|
487
505
|
if not filtered_kwargs and hasattr(ctx, "params") and ctx.params:
|
|
488
|
-
|
|
506
|
+
for k, v in ctx.params.items():
|
|
507
|
+
if k in model_fields:
|
|
508
|
+
filtered_kwargs[k] = v
|
|
509
|
+
elif k in alias_to_field:
|
|
510
|
+
field_name = alias_to_field[k]
|
|
511
|
+
filtered_kwargs[field_name] = v
|
|
489
512
|
|
|
490
513
|
# Get JSON data from context if available
|
|
491
514
|
json_data = ctx.obj.get("json_data", {}) if ctx.obj else {}
|
|
@@ -508,19 +531,33 @@ class WryModel(BaseModel):
|
|
|
508
531
|
for field_name, value in env_values.items():
|
|
509
532
|
config_data[field_name] = TrackedValue(value, ValueSource.ENV)
|
|
510
533
|
|
|
511
|
-
# 3. Override with JSON values
|
|
512
|
-
for
|
|
513
|
-
if
|
|
534
|
+
# 3. Override with JSON values (handle both field names and aliases)
|
|
535
|
+
for key, value in json_data.items():
|
|
536
|
+
if key in cls.model_fields:
|
|
537
|
+
# Direct field name match
|
|
538
|
+
config_data[key] = TrackedValue(value, ValueSource.JSON)
|
|
539
|
+
elif key in alias_to_field:
|
|
540
|
+
# Alias match - map to field name
|
|
541
|
+
field_name = alias_to_field[key]
|
|
514
542
|
config_data[field_name] = TrackedValue(value, ValueSource.JSON)
|
|
515
543
|
|
|
516
544
|
# 4. Override with CLI values from kwargs (but respect Click's source info)
|
|
517
545
|
for field_name in cls.model_fields:
|
|
518
546
|
if field_name in filtered_kwargs:
|
|
519
547
|
value = filtered_kwargs[field_name]
|
|
548
|
+
field_info = cls.model_fields[field_name]
|
|
520
549
|
|
|
521
550
|
# Check Click's parameter source if available
|
|
551
|
+
# Try both alias and field name since Click might know it by either
|
|
522
552
|
try:
|
|
523
|
-
|
|
553
|
+
# First try the alias if it exists (for explicit click.option with custom names)
|
|
554
|
+
param_name = field_info.alias if field_info.alias else field_name
|
|
555
|
+
param_source = ctx.get_parameter_source(param_name)
|
|
556
|
+
|
|
557
|
+
# If alias didn't work, try field name
|
|
558
|
+
if param_source is None and field_info.alias:
|
|
559
|
+
param_source = ctx.get_parameter_source(field_name)
|
|
560
|
+
|
|
524
561
|
if param_source is not None:
|
|
525
562
|
source_str = str(param_source)
|
|
526
563
|
# Only override if it's actually from CLI
|
|
@@ -648,7 +685,7 @@ class WryModel(BaseModel):
|
|
|
648
685
|
@click.command()
|
|
649
686
|
@MyConfig.generate_click_parameters()
|
|
650
687
|
@click.pass_context
|
|
651
|
-
def cli(ctx, **kwargs):
|
|
688
|
+
def cli(ctx: click.Context, **kwargs: Any):
|
|
652
689
|
config = MyConfig.from_click_context(ctx, **kwargs)
|
|
653
690
|
```
|
|
654
691
|
|
wry/multi_model.py
CHANGED
|
@@ -39,7 +39,7 @@ def split_kwargs_by_model(
|
|
|
39
39
|
@click.command()
|
|
40
40
|
@generate_click_parameters(ServerConfig)
|
|
41
41
|
@generate_click_parameters(DatabaseConfig)
|
|
42
|
-
def cmd(**kwargs):
|
|
42
|
+
def cmd(**kwargs: Any):
|
|
43
43
|
configs = split_kwargs_by_model(kwargs, ServerConfig, DatabaseConfig)
|
|
44
44
|
server_config = ServerConfig(**configs[ServerConfig])
|
|
45
45
|
db_config = DatabaseConfig(**configs[DatabaseConfig])
|
|
@@ -89,7 +89,7 @@ def create_models(
|
|
|
89
89
|
@generate_click_parameters(ServerModel)
|
|
90
90
|
@generate_click_parameters(DatabaseModel)
|
|
91
91
|
@click.pass_context
|
|
92
|
-
def cmd(ctx, **kwargs):
|
|
92
|
+
def cmd(ctx: click.Context, **kwargs: Any):
|
|
93
93
|
models = create_models(ctx, kwargs, ServerModel, DatabaseModel)
|
|
94
94
|
server = models[ServerModel]
|
|
95
95
|
db = models[DatabaseModel]
|
|
@@ -127,7 +127,7 @@ def multi_model(*model_classes: type[BaseModel], strict: bool = False) -> Any:
|
|
|
127
127
|
@click.command()
|
|
128
128
|
@multi_model(ServerModel, DatabaseModel)
|
|
129
129
|
@click.pass_context # Add this for source tracking
|
|
130
|
-
def my_command(ctx, **kwargs):
|
|
130
|
+
def my_command(ctx: click.Context, **kwargs: Any):
|
|
131
131
|
models = create_models(ctx, kwargs, ServerModel, DatabaseModel)
|
|
132
132
|
server = models[ServerModel]
|
|
133
133
|
database = models[DatabaseModel]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wry
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2.dev2
|
|
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
|
|
@@ -90,7 +90,7 @@ class AppArgs(AutoWryModel):
|
|
|
90
90
|
|
|
91
91
|
@click.command()
|
|
92
92
|
@AppArgs.generate_click_parameters()
|
|
93
|
-
def main(**kwargs):
|
|
93
|
+
def main(**kwargs: Any):
|
|
94
94
|
"""My simple CLI application."""
|
|
95
95
|
# Create the model instance from kwargs
|
|
96
96
|
config = AppArgs(**kwargs)
|
|
@@ -100,7 +100,10 @@ if __name__ == "__main__":
|
|
|
100
100
|
main()
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
**
|
|
103
|
+
**See comprehensive examples:**
|
|
104
|
+
- `examples/autowrymodel_comprehensive.py` - All AutoWryModel features including aliases
|
|
105
|
+
- `examples/wrymodel_comprehensive.py` - WryModel with source tracking
|
|
106
|
+
- `examples/multimodel_comprehensive.py` - Multi-model usage
|
|
104
107
|
|
|
105
108
|
Run it:
|
|
106
109
|
|
|
@@ -129,7 +132,7 @@ wry tracks where each configuration value came from, supporting all four sources
|
|
|
129
132
|
```python
|
|
130
133
|
@click.command()
|
|
131
134
|
@AppArgs.generate_click_parameters()
|
|
132
|
-
def main(**kwargs):
|
|
135
|
+
def main(**kwargs: Any):
|
|
133
136
|
# Simple instantiation - no source tracking
|
|
134
137
|
config = AppArgs(**kwargs)
|
|
135
138
|
# Works fine, but config.source.* will always show CLI
|
|
@@ -143,7 +146,7 @@ To enable accurate source tracking, use `@click.pass_context` and `from_click_co
|
|
|
143
146
|
@click.command()
|
|
144
147
|
@AppArgs.generate_click_parameters()
|
|
145
148
|
@click.pass_context
|
|
146
|
-
def main(ctx, **kwargs):
|
|
149
|
+
def main(ctx: click.Context, **kwargs: Any):
|
|
147
150
|
# Full source tracking with context
|
|
148
151
|
config = AppArgs.from_click_context(ctx, **kwargs)
|
|
149
152
|
|
|
@@ -181,7 +184,8 @@ python examples/source_tracking_comprehensive.py --config examples/sample_config
|
|
|
181
184
|
```
|
|
182
185
|
|
|
183
186
|
**Output shows source for each field:**
|
|
184
|
-
|
|
187
|
+
|
|
188
|
+
```text
|
|
185
189
|
host = json-server.com [from JSON]
|
|
186
190
|
port = 3000 [from CLI] ← CLI overrides JSON
|
|
187
191
|
debug = True [from ENV]
|
|
@@ -257,7 +261,7 @@ class DatabaseArgs(WryModel):
|
|
|
257
261
|
@click.command()
|
|
258
262
|
@multi_model(ServerConfig, DatabaseConfig)
|
|
259
263
|
@click.pass_context
|
|
260
|
-
def serve(ctx, **kwargs):
|
|
264
|
+
def serve(ctx: click.Context, **kwargs: Any):
|
|
261
265
|
# Create model instances
|
|
262
266
|
configs = create_models(ctx, kwargs, ServerConfig, DatabaseConfig)
|
|
263
267
|
server = configs[ServerConfig]
|
|
@@ -330,7 +334,7 @@ class AppArgs(WryModel):
|
|
|
330
334
|
@click.command()
|
|
331
335
|
@multi_model(DatabaseConfig, AppArgs)
|
|
332
336
|
@click.pass_context
|
|
333
|
-
def main(ctx, **kwargs):
|
|
337
|
+
def main(ctx: click.Context, **kwargs: Any):
|
|
334
338
|
# Automatically splits kwargs between models
|
|
335
339
|
configs = create_models(ctx, kwargs, DatabaseConfig, AppArgs)
|
|
336
340
|
|
|
@@ -349,7 +353,7 @@ By default, `generate_click_parameters` runs in strict mode to prevent common mi
|
|
|
349
353
|
@click.command()
|
|
350
354
|
@Config.generate_click_parameters() # strict=True by default
|
|
351
355
|
@Config.generate_click_parameters() # ERROR: Duplicate decorator detected!
|
|
352
|
-
def main(**kwargs):
|
|
356
|
+
def main(**kwargs: Any):
|
|
353
357
|
pass
|
|
354
358
|
```
|
|
355
359
|
|
|
@@ -391,6 +395,76 @@ class Config(WryModel):
|
|
|
391
395
|
)
|
|
392
396
|
```
|
|
393
397
|
|
|
398
|
+
### Using Pydantic Aliases for Custom CLI Names
|
|
399
|
+
|
|
400
|
+
**New in v0.3.2+**: Pydantic field aliases automatically control the generated CLI option names and environment variable names!
|
|
401
|
+
|
|
402
|
+
This allows you to have concise Python field names while exposing descriptive CLI options:
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
from pydantic import Field
|
|
406
|
+
from wry import AutoWryModel
|
|
407
|
+
|
|
408
|
+
class DatabaseConfig(AutoWryModel):
|
|
409
|
+
env_prefix = "DB_"
|
|
410
|
+
|
|
411
|
+
# Concise Python field name: db_url
|
|
412
|
+
# Alias controls CLI option: --database-url
|
|
413
|
+
# Environment variable: DB_DATABASE_URL
|
|
414
|
+
db_url: str = Field(
|
|
415
|
+
alias="database_url",
|
|
416
|
+
default="sqlite:///app.db",
|
|
417
|
+
description="Database connection URL"
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
pool_size: int = Field(
|
|
421
|
+
alias="connection_pool_size",
|
|
422
|
+
default=5,
|
|
423
|
+
description="Maximum connection pool size"
|
|
424
|
+
)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
**How it works:**
|
|
428
|
+
|
|
429
|
+
- **Python field**: `db_url` (concise, easy to type)
|
|
430
|
+
- **CLI option**: `--database-url` (descriptive, user-friendly)
|
|
431
|
+
- **Environment variable**: `DB_DATABASE_URL` (consistent with CLI)
|
|
432
|
+
- **JSON config**: Accepts both `db_url` and `database_url`
|
|
433
|
+
|
|
434
|
+
**Requirements:**
|
|
435
|
+
|
|
436
|
+
- **None!** `WryModel` automatically sets `validate_by_name=True` and `validate_by_alias=True`
|
|
437
|
+
- This tells Pydantic to accept both field names and aliases
|
|
438
|
+
- No need to configure anything - it just works!
|
|
439
|
+
- Aliases automatically control option names, env var names, and help text
|
|
440
|
+
|
|
441
|
+
**Full support (v0.3.2+):**
|
|
442
|
+
|
|
443
|
+
- ✅ Aliases automatically control auto-generated option names
|
|
444
|
+
- ✅ Environment variables use alias names (consistent with CLI)
|
|
445
|
+
- ✅ Source tracking works correctly
|
|
446
|
+
- ✅ JSON config accepts both field names and aliases
|
|
447
|
+
|
|
448
|
+
**Why this feature exists:**
|
|
449
|
+
|
|
450
|
+
Before v0.3.2, if you wanted custom CLI option names, you had to use explicit `click.option()` decorators for every field. The alias feature eliminates this boilerplate for the common case where you just want different names.
|
|
451
|
+
|
|
452
|
+
**For advanced use cases** (short options, custom Click types):
|
|
453
|
+
|
|
454
|
+
You can still combine aliases with explicit `click.option()` decorators:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
class Config(AutoWryModel):
|
|
458
|
+
# Explicit click.option for short option support
|
|
459
|
+
verbose: Annotated[int, click.option("-v", "--verbose", count=True)] = Field(default=0)
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
See `examples/autowrymodel_comprehensive.py` for examples of explicit Click decorators.
|
|
463
|
+
|
|
464
|
+
**See also:**
|
|
465
|
+
- `examples/autowrymodel_comprehensive.py` - Complete AutoWryModel example with aliases
|
|
466
|
+
- `examples/wrymodel_comprehensive.py` - WryModel with aliases and source tracking
|
|
467
|
+
|
|
394
468
|
## Development
|
|
395
469
|
|
|
396
470
|
### Prerequisites
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
wry/__init__.py,sha256=BI1sXr3d2q6wm4LdFSjgpatETyhv4RruVKN3auO7WH8,4203
|
|
2
|
+
wry/_version.py,sha256=ZmNNN5KY7zagAfK_ReGsNVBfLtZ1xBk0-Q-QqXvizhI,717
|
|
3
|
+
wry/auto_model.py,sha256=TC3I8r00Yv870bG3zZ5qa2Iet1rSCZ1cRWSkZSvZhco,6932
|
|
4
|
+
wry/click_integration.py,sha256=vo8Ajv-nD-YvTbBQsdW2txhTrUc_pdGXIghjswNtZaU,32554
|
|
5
|
+
wry/help_system.py,sha256=zSryjsOXAWz9HyCpybwuFWrlaP9gwbMNIexuAWTcCns,5731
|
|
6
|
+
wry/multi_model.py,sha256=-72Eder564guz5nwFW3te5MRVWbBmGUI8V6ig1axgtw,6023
|
|
7
|
+
wry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
wry/core/__init__.py,sha256=24Mu7qGzaFde8tAnyJOPPkD1ScZq-iSXNlDdAWSw_mg,894
|
|
9
|
+
wry/core/accessors.py,sha256=Kn5dziklgcMWClOjbeJSwWNHOCQkpAWV9b1uH9ZGUPI,3708
|
|
10
|
+
wry/core/env_utils.py,sha256=_yZDUPlzjIdyk-BbsX-sBfLCLmRsUlk8_la4zkg5mkI,4838
|
|
11
|
+
wry/core/field_utils.py,sha256=nDFkbzfIf7mNJm6T5BuqcZ1mYUVIKinXfw77xpK_FK0,4134
|
|
12
|
+
wry/core/model.py,sha256=tP0dyt0rsCDMxM9ULMLhHcaoOF3s9xd96iMkGbQ4uFE,27056
|
|
13
|
+
wry/core/sources.py,sha256=4uboTc23f_CXiD3ql767-TvrGfe6xXN5NhXEIfNxD2U,1171
|
|
14
|
+
wry-0.3.2.dev2.dist-info/licenses/LICENSE,sha256=0EfVapSdYZQRHpfqCVAO5OeyT2xMu8cxPKaRkWWdb2g,1068
|
|
15
|
+
wry-0.3.2.dev2.dist-info/METADATA,sha256=E_0Sux0uGD5MC0BExttbkPT0a9P8SMAO_PD0j6aaPE0,24030
|
|
16
|
+
wry-0.3.2.dev2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
+
wry-0.3.2.dev2.dist-info/top_level.txt,sha256=ytpYWd5uCeQpPCdTYaB1Vjzp4C5em03WpUKn3SiDDY8,4
|
|
18
|
+
wry-0.3.2.dev2.dist-info/RECORD,,
|
wry-0.3.1.dev0.dist-info/RECORD
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
wry/__init__.py,sha256=r42j1WbOhUjsB3lXyhX7H1vZATs2glLoeoxw69uAB_U,3496
|
|
2
|
-
wry/_version.py,sha256=Nn0powdlXw7bj95inepNhR1UFY-5VasWqYBebbxp5gk,717
|
|
3
|
-
wry/auto_model.py,sha256=YY71mkYr46P3O4cjY8fN0AmothtPcvsDqvk1eQM4VbE,6927
|
|
4
|
-
wry/click_integration.py,sha256=XDeNDHGh99Now77vrGMo151xp0KjWZYm7nTuIHh6CU4,31775
|
|
5
|
-
wry/help_system.py,sha256=zSryjsOXAWz9HyCpybwuFWrlaP9gwbMNIexuAWTcCns,5731
|
|
6
|
-
wry/multi_model.py,sha256=f6Hf5NMfIZtdXc6t00iTPtUEbiWnQZiC7NKVchQ7QU4,5978
|
|
7
|
-
wry/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
wry/core/__init__.py,sha256=24Mu7qGzaFde8tAnyJOPPkD1ScZq-iSXNlDdAWSw_mg,894
|
|
9
|
-
wry/core/accessors.py,sha256=Kn5dziklgcMWClOjbeJSwWNHOCQkpAWV9b1uH9ZGUPI,3708
|
|
10
|
-
wry/core/env_utils.py,sha256=8TCgVPr7n5-YNThEVC5MvAG6qB0TW5MJkO1YT5yHZYI,4051
|
|
11
|
-
wry/core/field_utils.py,sha256=nDFkbzfIf7mNJm6T5BuqcZ1mYUVIKinXfw77xpK_FK0,4134
|
|
12
|
-
wry/core/model.py,sha256=CP0zElttAqpFHj2yD8O8CgMSm9VpSAT46nXcnCHw38U,25150
|
|
13
|
-
wry/core/sources.py,sha256=4uboTc23f_CXiD3ql767-TvrGfe6xXN5NhXEIfNxD2U,1171
|
|
14
|
-
wry-0.3.1.dev0.dist-info/licenses/LICENSE,sha256=0EfVapSdYZQRHpfqCVAO5OeyT2xMu8cxPKaRkWWdb2g,1068
|
|
15
|
-
wry-0.3.1.dev0.dist-info/METADATA,sha256=9rd5Qav8ecAnFeQaS1ZgOevbt_s2ULm-BitNhf8pOaI,21429
|
|
16
|
-
wry-0.3.1.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
17
|
-
wry-0.3.1.dev0.dist-info/top_level.txt,sha256=ytpYWd5uCeQpPCdTYaB1Vjzp4C5em03WpUKn3SiDDY8,4
|
|
18
|
-
wry-0.3.1.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|