Skip to content

fix: treat i2's NotSet signature default as required / no default - #19

Merged
thorwhalen merged 3 commits into
masterfrom
not-set-defaults-are-required
Sep 22, 2026
Merged

thorwhalen merged 3 commits into
masterfrom
not-set-defaults-are-required

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

What

Step 2 of the re-land plan in i2mint/i2#48, for py2http: treat i2's NotSet sentinel as a signature default the same way as inspect.Parameter.empty ("required, no default").

  • New py2http.schema_tools.param_default(param): returns param.default, or Parameter.empty when it is NotSet.
  • Used in mk_input_schema_from_func (OpenAPI request schema: default, required, type inference) and ParamsSpecifier.from_func. The latter also loses a dead loop whose result was immediately overwritten.

Why

i2mint/i2#88 gave FuncFactory signatures NotSet defaults and was reverted in i2mint/i2#89. One reason was that mk_routes_and_openapi_specs([factory]) then produced a spec that json.dumps refused ("Sentinel is not JSON serializable"), with the params no longer required. With this PR, the spec for a NotSet-defaulted signature is identical to the plain one.

Decision: NotSet means required. The plan left open whether such params should be required or optional in the schema. I chose required: it keeps py2http's output for a FuncFactory exactly as it is today, before and after #88. It also matches what NotSet stands for: the wrapped function will need the value by the time it is called. Making them optional would be a separate, deliberate API change.

No-op against the current i2: no current signature carries NotSet.

i2 compatibility (decision)

Uses i2.is_not_set (added in i2mint/i2#91, i2 0.1.74) when available, otherwise falls back on i2.deco.NotSet (present since 2023). The i2 requirement is not changed.

Tests

py2http/tests/test_not_set_defaults.py: the input schema and the whole OpenAPI spec for a NotSet-defaulted foo equal those of the plain foo, the spec JSON-serializes, and ParamsSpecifier uses its _dflt_default. There is also a doctest in mk_input_schema_from_func. 3 of the 4 tests fail without the fix (the 4th checks the fixture).

Gate

  • New tests + schema_tools/decorators doctests: 15 passed with i2 master (native is_not_set) and with i2 0.1.73 from PyPI (fallback path).
  • Full pytest --doctest-modules py2http (py3.12, scrap/examples ignored): 26 passed, 1 failed, 2 errors. Master gives 21 passed with the same 1 failure and 2 errors, all pre-existing: the service.mk_app doctest, and tests/simple_run_process_test.py calling http2py.mk_request_func_from_openapi_spec with a stale signature. Hosted CI on master has been red since 2025-08 for pre-existing reasons.
  • Dependents of py2http: extrude (tested below); peruse is not on this box.
  • Hosted CI: see checks.

🤖 Generated with Claude Code

thorwhalen and others added 2 commits September 22, 2026 15:24
`mk_input_schema_from_func` and `ParamsSpecifier.from_func` read defaults
through the new `schema_tools.param_default`, which maps i2's `NotSet`
sentinel to `Parameter.empty`. So a signature with NotSet defaults (as a
re-landed i2mint/i2#88 FuncFactory would show) keeps its params required
and yields a JSON-serializable OpenAPI spec, identical to the plain one.
No-op for current signatures.

Uses `i2.is_not_set` when available, else falls back on `i2.deco.NotSet`,
so the i2 requirement is unchanged.

Refs i2mint/i2#48

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…(review follow-up)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thorwhalen

Copy link
Copy Markdown
Member Author

Independent refute-review (sub-agent): no blocking defects. It exercised a real FuncFactory whose __signature__ carries NotSet defaults (what #88 does): the spec JSON-serializes, has no NotSet, equals the plain factory's spec and keeps required: ['a','b']. The same holds with i2 master and with i2 0.1.73. It also ran a bottle app built with mk_app through WSGI: responses are identical with and without the change. Nothing in py2http fills missing args from signature defaults, so NotSet cannot reach the function or a response.

Follow-ups applied:

  • Added a test with a real FuncFactory (not just a plain function). 4 of 5 tests fail against master's source.
  • Commented that x-py-param (only with include_func_params=True, which nothing in the fleet uses) is the raw Parameter and may still carry NotSet. It was never JSON-able anyway.

Noted, not changed:

  • Switching param.default != Parameter.empty to is not also fixes defaults with an exotic __eq__ (e.g. numpy arrays), which used to raise here.
  • required is a spec-level fact only. validate_input does not enforce top-level required, so a missing arg gives a 500, not a 400. This is pre-existing and unchanged.
  • A real FuncFactory has no __name__, so it cannot be routed without setting one. This is pre-existing and unchanged.

- tests: http2py's mk_request_func_from_openapi_spec takes method as
  keyword-only since 2025-08; the positional call broke collection of
  simple_run_process_test.py (master CI red since then).
- mk_app: multi-API routes like 'foo_api' (as in its own doctest) are now
  mounted at '/foo_api'; bottle >= 0.13 and aiohttp reject prefixes
  without a leading slash, so the documented usage raised ValueError.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@thorwhalen
thorwhalen merged commit 77fcb6f into master Sep 22, 2026
6 checks passed
@thorwhalen
thorwhalen deleted the not-set-defaults-are-required branch September 22, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant