|
20 | 20 | T = TypeVar("T") |
21 | 21 |
|
22 | 22 |
|
| 23 | +class _RequiredValueSentinel: |
| 24 | + """ |
| 25 | + Sentinel type to mark parameters as required but eligible for apply_defaults. |
| 26 | +
|
| 27 | + This allows parameters to have no default value in the function signature |
| 28 | + (making them appear required), while still allowing apply_defaults to provide |
| 29 | + a value if one is registered globally. |
| 30 | +
|
| 31 | + Usage: |
| 32 | + @apply_defaults |
| 33 | + def __init__(self, *, objective_target: PromptTarget = REQUIRED_VALUE): |
| 34 | + # If apply_defaults finds a default for objective_target, it will be used. |
| 35 | + # Otherwise, an error will be raised for missing required parameter. |
| 36 | + pass |
| 37 | + """ |
| 38 | + |
| 39 | + def __repr__(self) -> str: |
| 40 | + return "REQUIRED_VALUE" |
| 41 | + |
| 42 | + def __bool__(self) -> bool: |
| 43 | + # Ensure this evaluates to False in boolean context |
| 44 | + return False |
| 45 | + |
| 46 | + |
| 47 | +# Global sentinel instance |
| 48 | +REQUIRED_VALUE = _RequiredValueSentinel() |
| 49 | + |
| 50 | + |
23 | 51 | @dataclass(frozen=True) |
24 | 52 | class DefaultValueScope: |
25 | 53 | """ |
@@ -218,20 +246,35 @@ def wrapper(self, *args, **kwargs): |
218 | 246 | bound_args = sig.bind(self, *args, **kwargs) |
219 | 247 | bound_args.apply_defaults() |
220 | 248 |
|
221 | | - # Apply default values for parameters that are None |
| 249 | + # Apply default values for parameters that are None or REQUIRED_VALUE |
222 | 250 | for param_name, param_value in bound_args.arguments.items(): |
223 | 251 | if param_name == "self": |
224 | 252 | continue |
225 | 253 |
|
226 | | - # Only apply defaults if the parameter is None |
227 | | - if param_value is None: |
| 254 | + # Apply defaults if parameter is None or REQUIRED_VALUE sentinel |
| 255 | + if param_value is None or isinstance(param_value, _RequiredValueSentinel): |
228 | 256 | found, default_value = _global_default_values.get_default_value( |
229 | 257 | class_type=cls, |
230 | 258 | parameter_name=param_name, |
231 | 259 | ) |
232 | 260 | if found: |
233 | 261 | bound_args.arguments[param_name] = default_value |
234 | 262 | logger.debug(f"Applied default value for {cls.__name__}.{param_name} = {default_value}") |
| 263 | + elif isinstance(param_value, _RequiredValueSentinel): |
| 264 | + # REQUIRED_VALUE was used but no default found - raise clear error |
| 265 | + raise ValueError( |
| 266 | + f"{param_name} is required for {cls.__name__}. " |
| 267 | + f"Either pass the parameter explicitly or register a default using set_default_value()." |
| 268 | + ) |
| 269 | + # If None was explicitly passed and parameter has REQUIRED_VALUE as default, also raise |
| 270 | + elif param_value is None: |
| 271 | + # Check if the parameter's default in the signature is REQUIRED_VALUE |
| 272 | + param_obj = sig.parameters.get(param_name) |
| 273 | + if param_obj and isinstance(param_obj.default, _RequiredValueSentinel): |
| 274 | + raise ValueError( |
| 275 | + f"{param_name} is required for {cls.__name__}. " |
| 276 | + f"Either pass a valid value or register a default using set_default_value()." |
| 277 | + ) |
235 | 278 |
|
236 | 279 | # Call the original method with updated arguments |
237 | 280 | return method(*bound_args.args, **bound_args.kwargs) |
|
0 commit comments