Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion py2http/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ def from_func(cls, func, _dflt_default=None):
}

def dflt_of(param):
dflt = param_default(param) # i2's NotSet sentinel counts as "no default"
dflt = param_default(param) # i2 placeholder defaults (e.g. NotSet) count as "no default"
return _dflt_default if dflt is Parameter.empty else dflt

_name_and_dflts = {x.name: dflt_of(x) for x in params}
Expand Down
27 changes: 16 additions & 11 deletions py2http/schema_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,25 @@
from i2.errors import InputError

try:
from i2 import is_not_set
except ImportError: # older i2: same sentinel, not exported from the root yet
from i2.deco import NotSet as _NotSet
from i2 import is_placeholder_default
except ImportError: # older i2: no DefaultPlaceholder yet, so only NotSet is a placeholder
try:
from i2 import is_not_set as is_placeholder_default
except ImportError: # older still: same sentinel, not exported from the root yet
from i2.deco import NotSet as _NotSet

def is_not_set(x) -> bool:
"""Return True iff ``x`` is ``i2``'s ``NotSet`` sentinel."""
return x is _NotSet
def is_placeholder_default(x) -> bool:
"""Return True iff ``x`` is ``i2``'s ``NotSet`` sentinel."""
return x is _NotSet


def param_default(param: Parameter):
"""Return ``param.default``, or ``Parameter.empty`` if it is ``i2``'s ``NotSet``.
"""Return ``param.default``, or ``Parameter.empty`` if it is a placeholder default.

``NotSet`` in a signature means "no value given", not a real default, so schema
builders treat that param as required, with no default (it is not JSON
A placeholder default is ``i2``'s ``NotSet`` sentinel or any
``i2.DefaultPlaceholder`` (such as the marker ``methodize(..., default_from=...)``
puts in a signature). It means "no concrete value here", not a real default, so
schema builders treat that param as required, with no default (it is not JSON
serializable either).

>>> from i2.deco import NotSet
Expand All @@ -34,7 +39,7 @@ def param_default(param: Parameter):
<class 'inspect._empty'>
"""
default = param.default
return Parameter.empty if is_not_set(default) else default
return Parameter.empty if is_placeholder_default(default) else default


COMPLEX_TYPE_MAPPING = {}
Expand Down Expand Up @@ -164,7 +169,7 @@ def mk_input_schema_from_func(func, exclude_keys=None, include_func_params=False

default_type = Any
p = {}
default = param_default(param) # i2's NotSet sentinel counts as "no default"
default = param_default(param) # i2 placeholder defaults (e.g. NotSet) count as "no default"
if default is not Parameter.empty:
if type(default) in JSON_TYPES:
default_type = type(default)
Expand Down
39 changes: 39 additions & 0 deletions py2http/tests/test_not_set_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"""

import json
from inspect import Parameter

import pytest
from i2 import Sig
from i2.deco import NotSet

Expand Down Expand Up @@ -71,3 +73,40 @@ def mk_factory():
_, spec = mk_routes_and_openapi_specs([with_not_set])
assert "NotSet" not in json.dumps(spec)
assert spec == mk_routes_and_openapi_specs([plain])[1]


try:
from i2 import is_placeholder_default as _ # noqa: F401

_HAS_PLACEHOLDERS = True
except ImportError:
_HAS_PLACEHOLDERS = False


@pytest.mark.skipif(not _HAS_PLACEHOLDERS, reason="i2 without DefaultPlaceholder")
def test_methodize_default_from_placeholder_is_no_default():
"""i2mint/py2http#49: a ``methodize(..., default_from=...)`` default is a marker,
not a value: the schema keeps that param required, with no default.
"""
from dataclasses import dataclass

from i2.wrapper import methodize

def baz(x, y: int, z: int, *, d: float = 1.5):
return x + y * z + d

@dataclass
class K:
x: int
zoo: int = 2
bar = methodize(baz, bind="x", default_from=[("z", "zoo")])

method = K(1).bar
assert Sig(method).parameters["z"].default is not Parameter.empty # marker is there

schema = mk_input_schema_from_func(method)
assert schema["required"] == ["y", "z"]
assert "default" not in schema["properties"]["z"]

specifier = ParamsSpecifier.from_func(method, _dflt_default="dflt")
assert specifier._name_and_dflts == {"y": "dflt", "z": "dflt", "d": 1.5}
Loading