Skip to content

Fix Optional[FromContext[T]] classification and define its semantics#235

Merged
timkpaine merged 2 commits into
nk/auto_deps_auto_callable_modelfrom
tkp/flow-model-optional-fromcontext
Jun 9, 2026
Merged

Fix Optional[FromContext[T]] classification and define its semantics#235
timkpaine merged 2 commits into
nk/auto_deps_auto_callable_modelfrom
tkp/flow-model-optional-fromcontext

Conversation

@timkpaine

Copy link
Copy Markdown
Member

_parse_annotation only peeled the top-level Annotated, so Optional[FromContext[int]] (a Union on the outside) was silently misclassified as a regular parameter and every call failed with a misleading "cannot satisfy unbound regular parameter" error.

Detect FromContext/Lazy markers nested inside a top-level Optional and define the two spellings precisely:

  • FromContext[Optional[int]]: contextual, required-in-context, value may be None.
  • Optional[FromContext[int]]: contextual, optional; absent -> bound to None (an implicit None default synthesized in _analyze_flow_function).

FromContext[Optional[int]] = None is therefore equivalent to Optional[FromContext[int]], and an explicit default still wins. Distinct required-ness yields distinct config/cache identities via the existing has_function_default/function_default identity terms.

Reject nested Lazy (Optional[Lazy[int]]) and non-Optional unions carrying FromContext (e.g. Union[FromContext[int], str]) with clear messages.

Adds focused regression tests covering all call shapes, the consistency equivalences, and the rejection cases.

`_parse_annotation` only peeled the top-level `Annotated`, so
`Optional[FromContext[int]]` (a Union on the outside) was silently
misclassified as a regular parameter and every call failed with a
misleading "cannot satisfy unbound regular parameter" error.

Detect `FromContext`/`Lazy` markers nested inside a top-level Optional and
define the two spellings precisely:

- `FromContext[Optional[int]]`: contextual, required-in-context, value may be None.
- `Optional[FromContext[int]]`: contextual, optional; absent -> bound to None
  (an implicit None default synthesized in `_analyze_flow_function`).

`FromContext[Optional[int]] = None` is therefore equivalent to
`Optional[FromContext[int]]`, and an explicit default still wins. Distinct
required-ness yields distinct config/cache identities via the existing
has_function_default/function_default identity terms.

Reject nested `Lazy` (`Optional[Lazy[int]]`) and non-Optional unions carrying
`FromContext` (e.g. `Union[FromContext[int], str]`) with clear messages.

Adds focused regression tests covering all call shapes, the consistency
equivalences, and the rejection cases.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Test Results

    1 files  ± 0      1 suites  ±0   2m 50s ⏱️ -5s
1 129 tests  - 20  1 127 ✅  - 20  2 💤 ±0  0 ❌ ±0 
1 135 runs   - 20  1 133 ✅  - 20  2 💤 ±0  0 ❌ ±0 

Results for commit 2147313. ± Comparison against base commit d35c55d.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.64706% with 2 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (nk/auto_deps_auto_callable_model@d11a398). Learn more about missing BASE report.

Files with missing lines Patch % Lines
ccflow/tests/test_flow_model_optional_context.py 96.61% 2 Missing ⚠️
Additional details and impacted files
@@                         Coverage Diff                         @@
##             nk/auto_deps_auto_callable_model     #235   +/-   ##
===================================================================
  Coverage                                    ?   93.01%           
===================================================================
  Files                                       ?      160           
  Lines                                       ?    17835           
  Branches                                    ?     1153           
===================================================================
  Hits                                        ?    16589           
  Misses                                      ?     1020           
  Partials                                    ?      226           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@timkpaine timkpaine merged commit 51396bc into nk/auto_deps_auto_callable_model Jun 9, 2026
17 checks passed
@timkpaine timkpaine deleted the tkp/flow-model-optional-fromcontext branch June 9, 2026 22:46
timkpaine added a commit that referenced this pull request Jun 23, 2026
* Add Flow.model generated callable models

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Handle FlowContext and existing context case and clean up code

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Further simplify Flow.model internals

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Trim public export

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Bug fixes

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Simplify tests, harden detecting nested/local @Flow.model

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Move examples inside ccflow, not top level

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Add 'inspect' to consolidate introspection for @Flow.model

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Remove uneeded ray tests, add comment pointing to issue 221 for helpers defining local classes

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>

* Split _build_compute_context into focused per-path helpers (#233)

`_build_compute_context` mixed argument validation, the explicit-context path,
plain-CallableModel defaults, and generated-model handling across one long
function with several exit branches. Extract the three paths into
`_compute_context_from_explicit`, `_compute_context_for_plain_model`, and
`_compute_context_for_generated_model`, leaving `_build_compute_context` as a
thin dispatcher.

Behavior-preserving refactor; no functional change. Verified against the full
flow_model, flow_context, hydra, and evaluator suites.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Add parity tests for model_alias vs bare-string registry aliases (#234)

In ccflow a bare string field value already resolves from the model registry,
so `ccflow.compose.model_alias`, a bare-string alias, and a root-relative
`/name` alias should all dereference to the same registered model instance.
`model_alias` is a Hydra convenience for the existing bare-string convention,
not a separate mechanism.

Adds a small Hydra config wiring one registered source three equivalent ways
and tests asserting all three resolve to the same instance (not a literal
string) and compute identically.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Fix Optional[FromContext[T]] classification and define its semantics (#235)

`_parse_annotation` only peeled the top-level `Annotated`, so
`Optional[FromContext[int]]` (a Union on the outside) was silently
misclassified as a regular parameter and every call failed with a
misleading "cannot satisfy unbound regular parameter" error.

Detect `FromContext`/`Lazy` markers nested inside a top-level Optional and
define the two spellings precisely:

- `FromContext[Optional[int]]`: contextual, required-in-context, value may be None.
- `Optional[FromContext[int]]`: contextual, optional; absent -> bound to None
  (an implicit None default synthesized in `_analyze_flow_function`).

`FromContext[Optional[int]] = None` is therefore equivalent to
`Optional[FromContext[int]]`, and an explicit default still wins. Distinct
required-ness yields distinct config/cache identities via the existing
has_function_default/function_default identity terms.

Reject nested `Lazy` (`Optional[Lazy[int]]`) and non-Optional unions carrying
`FromContext` (e.g. `Union[FromContext[int], str]`) with clear messages.

Adds focused regression tests covering all call shapes, the consistency
equivalences, and the rejection cases.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Accept positional/string context shorthand for Flow.model(context_type=) (#237)

Class-based CallableModel execution already accepts positional/string context
shorthand via ContextBase's ordered `zip(model_fields, v)` mapping (as used by
Hydra `+context=[...]`). Generated @Flow.model instances expose the open
FlowContext bag as their runtime context type, which has no declared fields to
zip against, so positional shorthand was silently dropped.

When a generated model declares a `context_type`, `compute()` now validates
non-mapping shorthand (list/tuple/str) through that declared type first, then
forwards the named values into the FlowContext bag. Mapping and named-kwarg
inputs keep their existing paths.

Scope: this covers the `compute()` entry point only. The direct-call form
(`model([...])`) is intentionally not supported, since `Flow.call` validates
against FlowContext before the generated body runs; supporting it would require
reverting the bag-of-types design.

Adds `_declared_context_type_for_model` and focused tests for list/tuple/string
shorthand, parity with named inputs, and that bag-only models are unaffected.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Add characterization tests for effective-key equivalence (#238)

The dependency-graph builder and cache-key path route every node through
`_effective_evaluation_key()`. For models that do not opt into effective
identity (everything except generated @Flow.model / BoundModel), the result
must stay byte-for-byte identical to the structural `cache_key()`.

Pin that equivalence so future changes to the effective path cannot silently
shift cache or graph identity for ordinary CallableModel graphs:

- effective cache_key == structural cache_key for simple, chain, and diamond graphs;
- the dependency graph (root_id, node keys, edges) built via the effective path
  equals an independently-computed structural graph;
- shared diamond leaves still dedupe to one node;
- `_build_dependency_graph` returns the structural root key.

Test-only; no library changes.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Add Flow.model(context_type=, strict=False) subset/omnibus context (#236)

By default a declared `context_type` may now be an "omnibus" superset: every
`FromContext[...]` parameter must exist on the context with a compatible type,
but the context may carry extra fields the model does not consume. This matches
the otherwise-permissive bag-of-types design and lets multiple models share one
broad context.

- `_validate_declared_context_type(..., strict=False)` keeps the missing-field
  and type-compatibility checks but only enforces the "every required context
  field is a FromContext param" bijection when `strict=True`.
- Runtime: `_validate_declared_context_values` validates the consumed fields
  individually when the declared context has unconsumed required fields (subset
  mode); otherwise it constructs the whole declared context so its cross-field
  validators still run. No config/serialization change needed.
- Thread `strict` through `flow_model` and document it on `Flow.model`.

Updates the existing extra-required-field test to reflect the new default
(allowed by default, rejected under strict=True) and adds focused tests for
subset execution, strict rejection/acceptance, and the shared missing/type
checks that apply in both modes.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

* Allow `flow` as a @Flow.model parameter name

`model.flow` was a property (data descriptor), so a same-named pydantic
field was silently unreadable; `@Flow.model` therefore rejected params
named `flow`. Replace the property with a non-data descriptor so a `flow`
field shadows it through the instance __dict__, and add `Flow.of(model)`
(backed by `_flow_api`) as the escape hatch to reach the API in that case.

`meta`, `context_type`, `result_type`, and `type_` stay reserved: the
runtime reads them directly, so shadowing them would silently break
execution rather than raise.

Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>

---------

Signed-off-by: Nijat K <nijat.khanbabayev@gmail.com>
Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com>
Co-authored-by: Nijat K <nijat.khanbabayev@gmail.com>
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