Source code for steplib.core.params
"""Parameter types and the ``Param`` dataclass for step metadata."""
from __future__ import annotations
from dataclasses import dataclass, field
from types import NoneType
from typing import Any
[docs]
@dataclass(frozen=True, slots=True)
class Param:
"""Describes a single step parameter extracted from the pattern.
Attributes:
name: Placeholder name as it appears in the pattern (e.g. ``"method"``).
type: Python type or registered type name (e.g. ``int``, ``"Json"``).
required: Whether the parameter must be present.
default: Default value when the parameter is not in the pattern.
description: Human-readable description of the parameter.
choices: Allowed values after conversion.
"""
name: str
type: type[Any] | str = str
required: bool = False
default: Any = None
description: str | None = None
choices: list[Any] = field(default_factory=list)
# --- Built-in transformable types (names usable in patterns) ---
BUILTIN_TYPES: dict[str, type[Any]] = {
"str": str,
"int": int,
"float": float,
"bool": bool,
"None": NoneType,
"Json": str, # placeholder; real parsing happens via register_type
"Url": str,
"HttpMethod": str,
"Date": str,
"Regex": str,
}
[docs]
class TypeRegistry:
"""Registry of custom types registered via ``register_type``.
Encapsulates mutable state that was previously a module-level dict,
avoiding global mutable state.
"""
def __init__(self) -> None:
"""Initialize an empty type registry."""
self._types: dict[str, type[Any]] = {}
[docs]
def register(self, name: str, py_type: type[Any]) -> None:
"""Register a custom type usable in patterns as ``{value:Name}``.
Args:
name: The type name to use in patterns.
py_type: The Python type the converter returns.
"""
self._types[name] = py_type
[docs]
def resolve(self, type_ref: type[Any] | str) -> type[Any]:
"""Resolve a type reference (type object or name) to a Python type.
Args:
type_ref: A type object or a registered type name.
Returns:
The resolved Python type, falling back to ``str`` for unknown names.
"""
if isinstance(type_ref, str):
if type_ref in self._types:
return self._types[type_ref]
if type_ref in BUILTIN_TYPES:
return BUILTIN_TYPES[type_ref]
return str
return type_ref
[docs]
def get(self, name: str) -> type[Any] | None:
"""Return the registered type for *name*, or ``None`` if not registered."""
return self._types.get(name)
# Module-level singleton for backward compatibility.
_DEFAULT_REGISTRY = TypeRegistry()
[docs]
def register_type(name: str, py_type: type[Any]) -> None:
"""Register a custom type usable in patterns as ``{value:Name}``.
Delegates to the default :class:`TypeRegistry` instance and also
registers the type with behave's shared parse registry so that
``{value:Name}`` placeholders actually convert matched text at runtime.
Args:
name: The type name to use in patterns.
py_type: The Python type the converter returns; must be callable
with a single ``str`` argument.
"""
_DEFAULT_REGISTRY.register(name, py_type)
try:
from behave import register_type as _behave_register_type
except ImportError: # pragma: no cover
return
_behave_register_type(**{name: py_type})
[docs]
def resolve_type(type_ref: type[Any] | str) -> type[Any]:
"""Resolve a type reference (type object or name) to a Python type.
Delegates to the default :class:`TypeRegistry` instance.
Args:
type_ref: A type object or a registered type name.
Returns:
The resolved Python type, falling back to ``str`` for unknown names.
"""
return _DEFAULT_REGISTRY.resolve(type_ref)