add the thing formally

This commit is contained in:
lda
2026-05-07 05:50:05 +07:00 Verified
parent d479af7e67
commit fa1aa58b3c
6 changed files with 67 additions and 36 deletions
+4
View File
@@ -43,10 +43,12 @@ from .nodes import (
AsyncRegistryHandler, AsyncRegistryHandler,
NodeReturn, NodeReturn,
NodeSpec, NodeSpec,
Nothing,
SyncRegistryHandler, SyncRegistryHandler,
build_async_registry, build_async_registry,
build_registry, build_registry,
node, node,
outcome,
) )
from .schemas import StateFieldMetadata, state_field from .schemas import StateFieldMetadata, state_field
from .subgraph import subgraph_node from .subgraph import subgraph_node
@@ -64,6 +66,7 @@ __all__ = [
"PickKeyInput", "PickKeyInput",
"NodeReturn", "NodeReturn",
"NodeSpec", "NodeSpec",
"Nothing",
"AsyncRegistryHandler", "AsyncRegistryHandler",
"SyncRegistryHandler", "SyncRegistryHandler",
"SequenceInput", "SequenceInput",
@@ -95,6 +98,7 @@ __all__ = [
"length", "length",
"pick_key", "pick_key",
"node", "node",
"outcome",
"state", "state",
"state_field", "state_field",
"state_path", "state_path",
+3 -1
View File
@@ -13,7 +13,7 @@ from .callables import (
from .inference import accepts_context, infer_models, is_basemodel_subclass from .inference import accepts_context, infer_models, is_basemodel_subclass
from .decorator import node from .decorator import node
from .registry import build_async_registry, build_registry from .registry import build_async_registry, build_registry
from .result import NodeReturn from .result import NodeReturn, Nothing, outcome
from .schema import schema_ref_for from .schema import schema_ref_for
from .spec import NodeSpec from .spec import NodeSpec
@@ -27,6 +27,7 @@ __all__ = [
"NodeCallable", "NodeCallable",
"NodeReturn", "NodeReturn",
"NodeSpec", "NodeSpec",
"Nothing",
"OutputT", "OutputT",
"PlainNodeCallable", "PlainNodeCallable",
"SyncRegistryHandler", "SyncRegistryHandler",
@@ -36,5 +37,6 @@ __all__ = [
"infer_models", "infer_models",
"is_basemodel_subclass", "is_basemodel_subclass",
"node", "node",
"outcome",
"schema_ref_for", "schema_ref_for",
] ]
+23 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from typing import Generic, TypeVar from typing import Generic, TypeVar, overload
from pydantic import BaseModel from pydantic import BaseModel
@@ -14,3 +14,25 @@ class NodeReturn(Generic[OutputT_co]):
outcome: str outcome: str
output: OutputT_co output: OutputT_co
class Nothing(BaseModel):
"""Empty output model for nodes that only choose an outcome."""
@overload
def outcome(name: str) -> NodeReturn[Nothing]: ...
@overload
def outcome(name: str, output: OutputT_co) -> NodeReturn[OutputT_co]: ...
def outcome(
name: str,
output: OutputT_co | None = None,
) -> NodeReturn[OutputT_co] | NodeReturn[Nothing]:
"""Create a node result with an optional output model."""
if output is None:
return NodeReturn(outcome=name, output=Nothing())
return NodeReturn(outcome=name, output=output)
+4
View File
@@ -8,12 +8,14 @@ from .nodes import (
NodeCallable, NodeCallable,
NodeReturn, NodeReturn,
NodeSpec, NodeSpec,
Nothing,
OutputT, OutputT,
PlainNodeCallable, PlainNodeCallable,
SyncRegistryHandler, SyncRegistryHandler,
build_async_registry, build_async_registry,
build_registry, build_registry,
node, node,
outcome,
) )
__all__ = [ __all__ = [
@@ -26,10 +28,12 @@ __all__ = [
"NodeCallable", "NodeCallable",
"NodeReturn", "NodeReturn",
"NodeSpec", "NodeSpec",
"Nothing",
"OutputT", "OutputT",
"PlainNodeCallable", "PlainNodeCallable",
"SyncRegistryHandler", "SyncRegistryHandler",
"build_async_registry", "build_async_registry",
"build_registry", "build_registry",
"node", "node",
"outcome",
] ]
+16 -33
View File
@@ -21,10 +21,9 @@ from pydantic import BaseModel, Field
from wf_authoring import node from wf_authoring import node
from wf_authoring import NodeReturn from wf_authoring import NodeReturn
from wf_authoring.dsl.conditions import expr, state from wf_authoring.dsl.conditions import expr, state
from wf_authoring.nodes.registry import build_registry from wf_authoring.nodes.result import Nothing, outcome
from wf_authoring.schemas import state_field from wf_authoring.schemas import state_field
from wf_core.run_state import RunStatus from wf_core.run_state import RunStatus
from wf_core.runtime.engine import execute_workflow
from wf_core.tokens import END from wf_core.tokens import END
# i copy things over, not the greatest design but im not doing deep fixes. # i copy things over, not the greatest design but im not doing deep fixes.
@@ -64,7 +63,7 @@ class ContextInput(BaseModel):
class how_do_i_explain_this(BaseModel): class how_do_i_explain_this(BaseModel):
pity_120_available: bool = True pity_120_available: bool = Field(default=True)
class Input(Counters, ContextInput, Countdown, how_do_i_explain_this): class Input(Counters, ContextInput, Countdown, how_do_i_explain_this):
@@ -160,9 +159,6 @@ class State(
# context: Context # context: Context
class Nothing(BaseModel): ... # variance shit IDC
# to the functions # to the functions
# @node(outcomes=("ok", "end")) # breaks because of input | nothing # @node(outcomes=("ok", "end")) # breaks because of input | nothing
@node @node
@@ -209,12 +205,8 @@ def rate_booster(c: Counters) -> NodeReturn[Nothing]:
rate_guarantee, which is no op. rate_guarantee, which is no op.
""" """
if c.counter["c_80"] >= 65: if c.counter["c_80"] >= 65:
return NodeReturn("65", Nothing()) return outcome("65")
return NodeReturn("0", Nothing()) return outcome("0")
def s(o: str) -> NodeReturn[Nothing]:
return NodeReturn(o, Nothing())
def _popped(storage: list[Entity]) -> bool: def _popped(storage: list[Entity]) -> bool:
@@ -223,7 +215,7 @@ def _popped(storage: list[Entity]) -> bool:
@node @node
def popped(s: Storage) -> how_do_i_explain_this: def popped(s: Storage) -> how_do_i_explain_this:
return how_do_i_explain_this(pity_120_available=_popped(s.storage)) return how_do_i_explain_this(pity_120_available=not _popped(s.storage))
@node(outcomes=("240", "80", "10", "1")) @node(outcomes=("240", "80", "10", "1"))
@@ -238,12 +230,12 @@ def pre_roll_router(c: CountersContextOutputInputAhhModelType) -> NodeReturn[Not
if c.context["type"] == "banner" and ( if c.context["type"] == "banner" and (
(sc == 120 and c.pity_120_available) or (sc > 0 and sc % 240 == 0) (sc == 120 and c.pity_120_available) or (sc > 0 and sc % 240 == 0)
): ):
return NodeReturn("240", Nothing()) return outcome("240")
if ct.get("c_80", 0) % 80 == 0: if ct.get("c_80", 0) % 80 == 0:
return s("80") return outcome("80")
if ct.get("c_10", 0) % 10 == 0: if ct.get("c_10", 0) % 10 == 0:
return s("10") return outcome("10")
return s("1") return outcome("1")
# now to the weeds of it. a Class! # now to the weeds of it. a Class!
@@ -398,7 +390,7 @@ def tick(state: Countdown) -> Countdown:
@node(outcomes=("tick", END)) @node(outcomes=("tick", END))
def keep_rolling(state: Countdown) -> NodeReturn[Nothing]: def keep_rolling(state: Countdown) -> NodeReturn[Nothing]:
return s("tick") if (state.countdown or 0) > 0 else s(END) return outcome("tick") if (state.countdown or 0) > 0 else outcome(END)
gacha = WorkflowBuilder( gacha = WorkflowBuilder(
@@ -549,34 +541,25 @@ def build_input(context: Context):
return dec return dec
def execute(graph: WorkflowBuilder, input: Input):
c = graph.compile()
r = build_registry(*(graph.node_specs.values()))
i = input.model_dump()
pprint(i)
pprint(c)
pprint(r)
return execute_workflow(c, i, r)
# twice in a row! it took 100+ and a miss tho # twice in a row! it took 100+ and a miss tho
def test(): def test():
assert 240 - 135 + 20 >= 120, "my math!" assert 240 - 135 + 20 >= 120, "my math!"
d = execute( d = gacha.execute(
gacha,
build_input(context)( build_input(context)(
20, 20, # lets be optimistic
rolled_previously=240 - 135, rolled_previously=240 - 135,
until_5=5, until_5=5,
until_6=73, until_6=73,
good_stuff=False, # lets pretend good_stuff=False, # lets pretend
), ).model_dump()
) )
assert d.status == RunStatus.COMPLETED, "oops" assert d.status == RunStatus.COMPLETED, "oops"
state = State.model_validate(d.state) state = State.model_validate(d.state)
assert any(i["name"] in context["pool"]["n_240"] for i in state.storage) assert any(
i["name"] in context["pool"]["n_240"] for i in state.storage
), "pity logic failed"
pprint(state.storage) pprint(state.storage)
+17 -1
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from pydantic import BaseModel from pydantic import BaseModel
from wf_authoring import build_registry, node from wf_authoring import Nothing, build_registry, node, outcome
from wf_core import RuntimeContext from wf_core import RuntimeContext
@@ -60,3 +60,19 @@ def test_node_can_alias_spec_with_direct_metadata_call() -> None:
assert alias.fn is echo.fn assert alias.fn is echo.fn
assert alias.input_model is AliasInput assert alias.input_model is AliasInput
assert alias.output_model is AliasOutput assert alias.output_model is AliasOutput
def test_outcome_returns_nothing_output_by_default() -> None:
result = outcome("skip")
assert result.outcome == "skip"
assert isinstance(result.output, Nothing)
def test_outcome_can_wrap_explicit_output() -> None:
output = AliasOutput(value="hello")
result = outcome("ok", output)
assert result.outcome == "ok"
assert result.output is output