wry 0.2.1.dev1__tar.gz → 0.2.2__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.2.1.dev1 → wry-0.2.2}/PKG-INFO +1 -1
- {wry-0.2.1.dev1 → wry-0.2.2}/pyproject.toml +1 -1
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/__init__.py +1 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/_version.py +2 -2
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/click_integration.py +34 -14
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/model.py +2 -1
- {wry-0.2.1.dev1 → wry-0.2.2}/LICENSE +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/README.md +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/auto_model.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/__init__.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/accessors.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/env_utils.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/field_utils.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/core/sources.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/multi_model.py +0 -0
- {wry-0.2.1.dev1 → wry-0.2.2}/wry/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "wry"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2" # Placeholder - actual version comes from git tags via poetry-dynamic-versioning
|
|
4
4
|
description = "Why Repeat Yourself? - Define your CLI once with Pydantic models"
|
|
5
5
|
authors = ["Tyler House <26489166+tahouse@users.noreply.github.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# file generated by poetry-dynamic-versioning
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
|
-
__version__: str = "0.2.
|
|
5
|
-
__version_tuple__: tuple[int, ...] = (0, 2,
|
|
4
|
+
__version__: str = "0.2.2" # This will be replaced during build
|
|
5
|
+
__version_tuple__: tuple[int, ...] = (0, 2, 2) # This will be replaced during build
|
|
6
6
|
__commit_id__: str | None = None # This will be replaced during build
|
|
7
7
|
|
|
8
8
|
# For compatibility with setuptools-scm test expectations
|
|
@@ -336,6 +336,7 @@ def generate_click_parameters(
|
|
|
336
336
|
"""
|
|
337
337
|
arguments: list[ClickParameterDecorator[Any]] = [] # Arguments must come first
|
|
338
338
|
options: list[ClickParameterDecorator[Any]] = [] # Options come after arguments
|
|
339
|
+
argument_docs: list[tuple[str, str]] = [] # Track (arg_name, description) for docstring injection
|
|
339
340
|
type_hints = get_type_hints(model_class, include_extras=True)
|
|
340
341
|
|
|
341
342
|
for field_name, field_info in model_class.model_fields.items():
|
|
@@ -379,14 +380,22 @@ def generate_click_parameters(
|
|
|
379
380
|
# Auto-generate Click option from Field info
|
|
380
381
|
option_name = f"--{field_name.replace('_', '-')}"
|
|
381
382
|
|
|
383
|
+
# Check if field is required first (needed to decide on default handling)
|
|
384
|
+
is_required = field_info.is_required() or field_type == AutoClickParameter.REQUIRED_OPTION
|
|
385
|
+
|
|
382
386
|
click_kwargs: dict[str, Any] = {
|
|
383
|
-
"default": (field_info.default if field_info.default is not PydanticUndefined else None),
|
|
384
387
|
"help": field_info.description or f"{field_name.replace('_', ' ').title()}",
|
|
385
388
|
"show_default": True,
|
|
386
389
|
}
|
|
387
390
|
|
|
388
|
-
#
|
|
389
|
-
|
|
391
|
+
# Only set default if field has one, or if field is not required
|
|
392
|
+
# For required fields without a default, we must NOT set default=None
|
|
393
|
+
# or Click will think there IS a default and won't enforce the requirement
|
|
394
|
+
if field_info.default is not PydanticUndefined:
|
|
395
|
+
click_kwargs["default"] = field_info.default
|
|
396
|
+
elif not is_required:
|
|
397
|
+
# Optional field without explicit default gets None
|
|
398
|
+
click_kwargs["default"] = None
|
|
390
399
|
|
|
391
400
|
# Determine Click type from annotation
|
|
392
401
|
base_type = get_args(annotation)[0]
|
|
@@ -502,6 +511,10 @@ def generate_click_parameters(
|
|
|
502
511
|
# Note: required=False to allow --config to replace arg
|
|
503
512
|
arguments.append(click.argument(argument_name, **click_kwargs, required=False))
|
|
504
513
|
|
|
514
|
+
# Track argument description for docstring injection
|
|
515
|
+
if field_info.description:
|
|
516
|
+
argument_docs.append((argument_name.upper(), field_info.description))
|
|
517
|
+
|
|
505
518
|
elif click_parameter:
|
|
506
519
|
# Determine if it's an argument or option
|
|
507
520
|
if hasattr(click_parameter, "__name__") and "argument" in str(click_parameter):
|
|
@@ -573,6 +586,21 @@ def generate_click_parameters(
|
|
|
573
586
|
if config_and_env_options:
|
|
574
587
|
func._has_config_option = True # type: ignore
|
|
575
588
|
|
|
589
|
+
# Inject argument descriptions into docstring BEFORE applying decorators
|
|
590
|
+
if argument_docs:
|
|
591
|
+
original_doc = func.__doc__ or ""
|
|
592
|
+
# Build argument documentation section
|
|
593
|
+
# Use \b to prevent Click from rewrapping, and format like Options section
|
|
594
|
+
arg_doc_lines = ["\n\n\b"]
|
|
595
|
+
arg_doc_lines.append("\n\b\bArguments:")
|
|
596
|
+
for arg_name, description in argument_docs:
|
|
597
|
+
# Match Click's Options formatting: 2 space indent, left-aligned
|
|
598
|
+
arg_doc_lines.append(f"\n\b\b {arg_name.ljust(18)} {description}")
|
|
599
|
+
arg_doc_section = "".join(arg_doc_lines)
|
|
600
|
+
|
|
601
|
+
# Append to existing docstring
|
|
602
|
+
func.__doc__ = original_doc.rstrip() + arg_doc_section
|
|
603
|
+
|
|
576
604
|
# Apply arguments first, then options (Click requirement)
|
|
577
605
|
all_decorators = arguments + final_options
|
|
578
606
|
for dec in reversed(all_decorators):
|
|
@@ -720,23 +748,15 @@ def eager_json_config(ctx: click.Context, param: click.Parameter, value: Any) ->
|
|
|
720
748
|
with open(value) as f:
|
|
721
749
|
json_data = json.load(f)
|
|
722
750
|
|
|
723
|
-
#
|
|
724
|
-
# This prevents Click from throwing MissingParameter errors
|
|
725
|
-
for json_key, json_value in json_data.items():
|
|
726
|
-
param_key = json_key.replace("-", "_") # Handle kebab-case
|
|
727
|
-
if param_key not in ctx.params:
|
|
728
|
-
ctx.params[param_key] = json_value
|
|
729
|
-
|
|
730
|
-
# Store for later merging
|
|
751
|
+
# Store JSON data for later merging in from_click_context
|
|
731
752
|
ctx.ensure_object(dict)["json_data"] = json_data
|
|
732
753
|
|
|
733
|
-
#
|
|
734
|
-
# This allows JSON to satisfy required arguments
|
|
754
|
+
# Mark parameters from JSON as not required
|
|
755
|
+
# This allows JSON to satisfy required arguments without modifying defaults
|
|
735
756
|
if hasattr(ctx, "command") and ctx.command:
|
|
736
757
|
for p in ctx.command.params:
|
|
737
758
|
if (isinstance(p, click.Argument) or isinstance(p, click.Option)) and p.name in json_data:
|
|
738
759
|
p.required = False
|
|
739
|
-
p.default = json_data[p.name]
|
|
740
760
|
|
|
741
761
|
return value
|
|
742
762
|
|
|
@@ -66,7 +66,7 @@ class WryModel(BaseModel):
|
|
|
66
66
|
if "_value_sources" not in data:
|
|
67
67
|
# Only initialize sources if not already provided
|
|
68
68
|
self._value_sources = {}
|
|
69
|
-
for field_name in self.model_fields:
|
|
69
|
+
for field_name in self.__class__.model_fields:
|
|
70
70
|
# Mark non-default values as programmatic
|
|
71
71
|
if field_name in data:
|
|
72
72
|
self._value_sources[field_name] = ValueSource.CLI
|
|
@@ -526,6 +526,7 @@ class WryModel(BaseModel):
|
|
|
526
526
|
# Only override if it's actually from CLI
|
|
527
527
|
if "COMMANDLINE" in source_str:
|
|
528
528
|
config_data[field_name] = TrackedValue(value, ValueSource.CLI)
|
|
529
|
+
continue
|
|
529
530
|
# Skip if it's DEFAULT or ENVIRONMENT - already handled above
|
|
530
531
|
continue
|
|
531
532
|
except (AttributeError, RuntimeError):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|