Skip to content

Add characterization tests for effective-key equivalence#238

Merged
timkpaine merged 2 commits into
nk/auto_deps_auto_callable_modelfrom
tkp/effective-key-characterization
Jun 9, 2026
Merged

Add characterization tests for effective-key equivalence#238
timkpaine merged 2 commits into
nk/auto_deps_auto_callable_modelfrom
tkp/effective-key-characterization

Conversation

@timkpaine

Copy link
Copy Markdown
Member

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.

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>
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Test Results

    1 files  ±0      1 suites  ±0   3m 3s ⏱️ +11s
1 168 tests +4  1 166 ✅ +4  2 💤 ±0  0 ❌ ±0 
1 174 runs  +4  1 172 ✅ +4  2 💤 ±0  0 ❌ ±0 

Results for commit 0cc4f41. ± Comparison against base commit 51396bc.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.03%. Comparing base (51396bc) to head (0cc4f41).

Additional details and impacted files
@@                         Coverage Diff                          @@
##           nk/auto_deps_auto_callable_model     #238      +/-   ##
====================================================================
+ Coverage                             93.01%   93.03%   +0.02%     
====================================================================
  Files                                   160      161       +1     
  Lines                                 17835    17892      +57     
  Branches                               1153     1160       +7     
====================================================================
+ Hits                                  16589    16646      +57     
  Misses                                 1020     1020              
  Partials                                226      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 88c5667 into nk/auto_deps_auto_callable_model Jun 9, 2026
20 checks passed
@timkpaine timkpaine deleted the tkp/effective-key-characterization branch June 9, 2026 22:57
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