Skip to content

GraphBuilder->Graph with separate Workflow type#381

Merged
johnchildren merged 42 commits intomainfrom
acl/finished-graph
Mar 30, 2026
Merged

GraphBuilder->Graph with separate Workflow type#381
johnchildren merged 42 commits intomainfrom
acl/finished-graph

Conversation

@acl-cqc
Copy link
Copy Markdown
Contributor

@acl-cqc acl-cqc commented Mar 4, 2026

  • Add parametrized Workflow holding a GraphData and outputs_type.
  • GraphBuilder renamed to Graph, and .outputs renamed to .finish_with_outputs which returns a Workflow
    • TODO: Rename finish_with_outputs to build??
  • Change methods that take Graph(Builders) (e.g. loop, eval) to take Workflow's, removing check in embed that the Output node is present (this is now guaranteed if your python code is well-typed - I was surprised not to find more such checks in map/loop/eval/const/etc.)
  • Add a ModelConvertible protocol like DictConvertible/etc. but that works for parametrized types. (This was a bit of a can of worms, there are some slightly suspect bits in types.py.) Workflow implements this, and serializes as a GraphData. (Type erasure: Workflow is a build-time-only construct).
    • This was in commit 6ccf080, previously I included Workflow in the union of PType's which was a bit hacky (special-case, awkward circular import, etc.) but a lot shorter/easier (and DictConvertible makes me worry about robustness and parametrization). Thoughts?
  • Update stub generator to support TNamedModel's as well as PModel Structs (by adding an is_ptype field to GenericType - this is not super-neat, suggestions of better ways to do this welcome, but my previous attempt was even (much) worse]: 454c18f)
    • It'd be good to remove @runtime_checkable from the RestrictedNamedTuple subclasses/instantiations, but leaving that for Another PR
  • Update test functions, docs/notebooks, etc., and test worker (including tests from tests: more fun with workers making graphs #392) to take/return parametrized Workflow rather than GraphData. This was a bit tedious, and the bulk of changed files in the PR... I have tried to update the text of the docs a little but there is a lot of use of the word "graph" to mean either an unfinished one or a finished one, and trying to distinguish which of those should be workflows is...tricky. This may suggest calling the two classes GraphBuilder and Graph rather than Graph and Workflow???

The end result is that you need fewer TypedGraphRefs (but still some), and when you do need one, pyright should catch cases of providing the wrong outputs_type (because this is now just a runtime reification of a type that pyright knows statically).

Other possible TODOs (maybe too much for one PR??)

  • Remove output type from Graph (and provide when you set the outputs)? This would mean splitting the class hierarchy i.e. we'd need a parent _BaseGraph[Inputs] with all methods except finish_with_outputs and graph_ref, then class Graph[Inputs](_BaseGraph[Inputs]): ...fn finish_with_outputs[Outputs](self, outputs_type: type[Outputs], outputs: Outputs) and separately class RecursiveGraph[Inputs,Outputs](_BaseGraph[Inputs]): ...fn finish_with_outputs(self, outputs: Outputs) plus fn graph_ref(self). Not sure whether this is worth it so I think leave for another PR?
  • We have quite a few tests that build GraphData's directly (not using the builder)....if we want the builder to be the only supported way of building graphs, we'll need to update these too.
    • If we do that, then this would allow hiding GraphData (i.e. making private, this is python) where it is used in the builder, so GraphData is really part of the graph runtime, not part of the user-facing graph-building API.

@acl-cqc acl-cqc force-pushed the acl/finished-graph branch from e8d272b to 7e92fcb Compare March 4, 2026 21:41
@acl-cqc acl-cqc changed the base branch from main to acl/tidy_types March 4, 2026 21:54
@acl-cqc acl-cqc force-pushed the acl/finished-graph branch 4 times, most recently from dc8ba1f to e8ce026 Compare March 4, 2026 22:08
@acl-cqc acl-cqc changed the title Split FinishedGraph off from GraphBuilder Split FinishedGraph off from GraphBuilder, return from worker Mar 4, 2026
Comment thread tierkreis_visualization/tierkreis_visualization/visualize_graph.py Outdated
acl-cqc added a commit that referenced this pull request Mar 5, 2026
…puts_type before outputs_type (#391)

Also remove some overloads that seem pointless (??), and tidy some
imports

Removing generics_in_ptype as a preliminary to #381 because it's hard to
handle parametrized FinishedGraph.

`TypedGraphRef[Inputs, Outputs]` was constructed via
`TypedGraphRef(outputs_type, inputs_type)`, so reorder the latter.
(Although the `inputs_type` field is unused it helps `pyright` figure
out the `Inputs` type parameter when in strict mode or with
`reportUnknownVariableType` so keep it for now.)
Base automatically changed from acl/tidy_types to main March 5, 2026 17:49
@acl-cqc acl-cqc force-pushed the acl/finished-graph branch from e8ce026 to 969cd0f Compare March 5, 2026 22:31
Comment thread tierkreis/tests/workers/graph/stubs.py Outdated
acl-cqc added 2 commits March 5, 2026 22:45
…ke & stubs import FinGraph, regen stubs+import FinishedGraph...tests pass but pyright fails
Comment thread tierkreis/tierkreis/builder.py Outdated
Comment thread tierkreis/tierkreis/builder.py Outdated
graph: GraphBuilder[A, B],
graph: FinishedGraph[A, B],
) -> TypedGraphRef[A, B]:
# TODO @philipp-seitz: Turn this into a public method?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd +1 this, shall I do it in this PR?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go for it if it's a simple change

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was gonna leave this until we could unify TKR[Workflow...] and TypedGraphRef but now I realise that we can't - the former is a handle to a Workflow that will have outputs_type when it appears, whereas TypedGraphRef gives us that outputs_type right now.

So, yes, done, removing a #noqa

@acl-cqc acl-cqc requested a review from johnchildren March 9, 2026 14:37
@rossduncan
Copy link
Copy Markdown
Collaborator

Why do we need both FinishedGraph and GraphData ?

@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Mar 10, 2026

Why do we need both FinishedGraph and GraphData ?

As per last paragraph in the description (TODOs/future work):

  • We have quite a few tests that build GraphData's directly (not using the builder)....if we want the builder to be the only supported way of building graphs, we'll need to update these too.
    • If we do that, then this would allow hiding GraphData (i.e. making private, this is python) where it is used in the builder, so GraphData is really part of the graph runtime, not part of the user-facing graph-building API.

I.e. I think we need GraphData as an internal struct, for (a) serialization, and (b) passing from GraphBuilder into FinishedGraph, but we could hide it from the user with more updates to tests etc. Except this may restrict partial in the future, we should do a bit of investigation.

@acl-cqc acl-cqc changed the title Split FinishedGraph off from GraphBuilder, return from worker GraphBuilder->Graph with separate Workflow type Mar 16, 2026
@acl-cqc acl-cqc force-pushed the acl/finished-graph branch from 07f9b93 to 6ccf080 Compare March 16, 2026 12:03
Copy link
Copy Markdown
Collaborator

@johnchildren johnchildren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some small comments but I think we can likely proceed with merging with or/without them

from typing import NamedTuple

from tierkreis.builder import GraphBuilder
from tierkreis.builder import Graph
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entirely a vanity thing but do we want to change the module where we import this from as well? I don't think it matters too much though. Possibly we should at least re-export Graph from the root module.

Comment thread tierkreis/tierkreis/codegen.py Outdated
outs_str = "\n ".join(outs)

bases = ["NamedTuple"] if is_portmapping else ["Struct", "Protocol"]
def is_tmodel() -> bool:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth moving this out into a top level function? As far as I can tell it only captures model from the outer scope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or a method on tierkreis.idl.models.Model ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that could make sense as we know we have a Model at this point, I don't have a preference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to a function on Model, admittedly it is a little bit dubious because GenericType.is_ptype itself is a bit dubious (i.e. we only really know if we build the GenericType from a type)

Comment thread tierkreis/tierkreis/builder.py Outdated
Comment thread tierkreis/tierkreis/builder.py Outdated
graph: GraphBuilder[A, B],
graph: FinishedGraph[A, B],
) -> TypedGraphRef[A, B]:
# TODO @philipp-seitz: Turn this into a public method?
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go for it if it's a simple change

:rtype: TypedGraphRef[Inputs, Outputs]
"""
return TypedGraphRef((-1, "body"), self.inputs_type, self.outputs_type)
return TypedGraphRef(TKR(-1, "body"), self.outputs_type)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be the first time we are constructing TKR directly rather than using ValueRef. I don't really object to this but we might want to clarify what the distinction between the two is meant to be later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the first time - existing cases in ifelse, eifelse, and _map_fn_single_in in this file. I am not really sure what the difference is - is it just that TKR is parametrized (TKR[T: PModel]) whereas ValueRef is not? (in which case we might want to combine them?? one can always use x: TKR without specifying the type argument)

fwiw, TKR has a method for returning a ValueRef.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah possibly TKR carries type information while ValueRef doesn't then? Worth reviewing again at a later date.

…references from types.py (still defined at bottom)"

This reverts commit 6ccf080.

if issubclass(origin, Workflow):
_inputs, outputs = get_args(annotation)
return annotation(coerce_from_annotation(ser, GraphData), outputs) # type: ignore
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotation(output_type=outputs, **ser)

Could work if we want to do something minimal that allows type erasure.

@acl-cqc acl-cqc requested a review from johnchildren March 30, 2026 15:30
@acl-cqc
Copy link
Copy Markdown
Contributor Author

acl-cqc commented Mar 30, 2026

Reverted to listing Workflow in the big union of possible PTypes after pydantic BaseModel didn't like a field of type[Outputs] when Outputs = TKR[int]...boo :-(

@johnchildren johnchildren merged commit a82f490 into main Mar 30, 2026
13 checks passed
@johnchildren johnchildren deleted the acl/finished-graph branch March 30, 2026 15:47
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.

3 participants