diff --git a/schema/__init__.py b/schema/__init__.py index 287d958..bf70a58 100644 --- a/schema/__init__.py +++ b/schema/__init__.py @@ -350,7 +350,15 @@ def _priority(s: Any) -> int: def _invoke_with_optional_kwargs(f: Callable[..., Any], **kwargs: Any) -> Any: - s = inspect.signature(f) + try: + s = inspect.signature(f) + except (ValueError, TypeError): + # Some callables -- notably C-implemented built-ins such as ``dict`` + # or ``int`` -- do not expose an introspectable signature. We cannot + # tell whether they accept keyword arguments, so fall back to calling + # them without any, matching how such defaults behaved before + # validate() keyword arguments were forwarded to them. + return f() if len(s.parameters) == 0: return f() return f(**kwargs) diff --git a/test_schema.py b/test_schema.py index e45d51f..476c961 100644 --- a/test_schema.py +++ b/test_schema.py @@ -838,6 +838,19 @@ def convert(data, increment): assert d["k"] == 1 and d["d"]["k"] == 42 and d["d"]["l"][0]["l"] == [3, 4, 5] +def test_optional_callable_default_builtin_c_callables(): + # ``inspect.signature`` raises for some C-implemented built-ins (e.g. + # ``dict`` and ``int``), which previously made them unusable as + # ``Optional`` defaults. See https://github.com/keleshev/schema/issues/272 + assert Schema({Optional("k", default=dict): dict}).validate({}) == {"k": {}} + assert Schema({Optional("k", default=int): int}).validate({}) == {"k": 0} + assert Schema({Optional("k", default=list): list}).validate({}) == {"k": []} + # validate() keyword arguments are still forwarded to callables that + # expose an introspectable signature accepting them. + s = Schema({Optional("k", default=lambda **kw: kw["increment"]): int}) + assert s.validate({}, increment=5) == {"k": 5} + + def test_inheritance_optional(): def convert(data, increment): if isinstance(data, int):