less dot-separated string in the Machinery

This commit is contained in:
lda
2026-05-21 03:41:47 +07:00 Verified
parent 6e768435d8
commit 92d0e366c4
8 changed files with 877 additions and 124 deletions
+30 -1
View File
@@ -1,6 +1,11 @@
from __future__ import annotations
from wf_authoring import WorkflowBuilder
from typing import Annotated
from pydantic import BaseModel, Field
from wf_authoring import WorkflowBuilder, state_field
from wf_core.paths import StatePath
from tests.authoring.helpers import (
AppendState,
@@ -13,6 +18,14 @@ from tests.authoring.helpers import (
)
class DotAliasState(BaseModel):
person_tags: Annotated[
list[str],
Field(alias="person.name"),
state_field(reducer="wf.std.append"),
]
def test_builder_accepts_basemodel_classes_for_workflow_schemas() -> None:
builder = WorkflowBuilder(
name="model_schema_demo",
@@ -102,3 +115,19 @@ def test_nested_state_basemodel_projects_parent_and_child_paths() -> None:
assert fields["person.tags"].type == "array"
assert fields["person"].reducer.name == "wf.std.replace"
assert fields["person.tags"].reducer.name == "wf.std.append"
def test_state_schema_from_preserves_literal_dotted_alias_paths() -> None:
builder = WorkflowBuilder(
name="dotted_alias_state_schema_demo",
input_schema=WorkflowInput,
state_schema=DotAliasState,
output_schema=WorkflowOutput,
start="start",
)
workflow = builder.compile()
fields = workflow.state_schema.field_index()
assert StatePath(("person.name",)) in fields
assert fields[StatePath(("person.name",))].reducer.name == "wf.std.append"
+131 -78
View File
@@ -31,16 +31,18 @@ def test_exact_nested_state_path_uses_declared_reducer() -> None:
def test_state_schema_accepts_legacy_field_list_and_dumps_json_schema() -> None:
schema = StateSchema.model_validate({
"fields": [
{"path": "state.person", "type": "object"},
{
"path": "state.person.name",
"type": "string",
"reducer": "wf.std.replace",
},
]
})
schema = StateSchema.model_validate(
{
"fields": [
{"path": "state.person", "type": "object"},
{
"path": "state.person.name",
"type": "string",
"reducer": "wf.std.replace",
},
]
}
)
assert schema.fields[0].path == StatePath.of("person")
assert schema.field_map()["person.name"].type == "string"
@@ -50,22 +52,24 @@ def test_state_schema_accepts_legacy_field_list_and_dumps_json_schema() -> None:
def test_state_schema_uses_json_schema_properties_as_canonical_shape() -> None:
schema = StateSchema.model_validate({
"type": "object",
"properties": {
"person": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Display name",
"reducer": "wf.std.replace",
}
schema = StateSchema.model_validate(
{
"type": "object",
"properties": {
"person": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Display name",
"reducer": "wf.std.replace",
}
},
},
"count": {"type": "integer", "reducer": "wf.std.add"},
},
"count": {"type": "integer", "reducer": "wf.std.add"},
},
})
}
)
fields = schema.field_map()
assert fields["person.name"].validation_schema.type == "string"
@@ -73,14 +77,48 @@ def test_state_schema_uses_json_schema_properties_as_canonical_shape() -> None:
assert fields["count"].reducer == ReducerRef(name="wf.std.add")
def test_state_schema_rejects_invalid_reducer_extension_keyword() -> None:
try:
StateSchema.model_validate({
def test_state_schema_preserves_literal_dotted_property_names() -> None:
schema = StateSchema.model_validate(
{
"type": "object",
"properties": {
"count": {"type": "integer", "reducer": {"bad": True}},
"person.name": {"type": "string", "reducer": "wf.std.replace"}
},
})
}
)
fields = schema.field_index()
assert set(fields) == {StatePath(("person.name",))}
assert fields[StatePath(("person.name",))].path == StatePath(("person.name",))
def test_state_schema_field_map_keeps_display_key_for_literal_dotted_property() -> None:
schema = StateSchema.model_validate(
{
"type": "object",
"properties": {
"person.name": {"type": "string", "reducer": "wf.std.replace"}
},
}
)
fields = schema.field_map()
assert set(fields) == {"person.name"}
assert fields["person.name"].path == StatePath(("person.name",))
def test_state_schema_rejects_invalid_reducer_extension_keyword() -> None:
try:
StateSchema.model_validate(
{
"type": "object",
"properties": {
"count": {"type": "integer", "reducer": {"bad": True}},
},
}
)
except ValueError as exc:
assert "invalid reducer for state field 'count'" in str(exc)
else:
@@ -88,14 +126,16 @@ def test_state_schema_rejects_invalid_reducer_extension_keyword() -> None:
def test_state_schema_accepts_canonical_schema_field() -> None:
schema = StateSchema.model_validate({
"fields": [
{
"path": "state.person.name",
"schema": {"type": "string", "title": "Person Name"},
}
]
})
schema = StateSchema.model_validate(
{
"fields": [
{
"path": "state.person.name",
"schema": {"type": "string", "title": "Person Name"},
}
]
}
)
field = schema.field_map()["person.name"]
assert field.validation_schema.type == "string"
@@ -111,13 +151,15 @@ def test_state_schema_accepts_deprecated_dict_shape_and_dumps_list() -> None:
def test_state_schema_accepts_deprecated_dict_value_with_schema_key() -> None:
schema = StateSchema.model_validate({
"fields": {
"person.name": {
"schema": {"type": "string", "description": "Display name"},
schema = StateSchema.model_validate(
{
"fields": {
"person.name": {
"schema": {"type": "string", "description": "Display name"},
}
}
}
})
)
assert schema.field_map()["person.name"].validation_schema.type == "string"
@@ -129,27 +171,32 @@ def test_state_schema_accepts_json_schema_field_without_type() -> None:
def test_state_schema_accepts_deprecated_state_prefixed_dict_keys() -> None:
schema = StateSchema.model_validate({
"fields": {"state.person.name": {"type": "string"}}
})
schema = StateSchema.model_validate(
{"fields": {"state.person.name": {"type": "string"}}}
)
assert schema.field_map()["person.name"].path == StatePath.of("person.name")
def test_state_field_decl_model_dump_serializes_path_as_string() -> None:
field = StateFieldDecl.model_validate({
"path": "state.person.name",
"type": "string",
})
def test_state_field_decl_model_dump_serializes_path_structurally() -> None:
field = StateFieldDecl.model_validate(
{
"path": "state.person.name",
"type": "string",
}
)
assert field.model_dump()["path"] == "state.person.name"
assert field.model_dump(mode="json")["path"] == "state.person.name"
assert field.model_dump()["path"] == {"root": "state", "parts": ["person", "name"]}
assert field.model_dump(mode="json")["path"] == {
"root": "state",
"parts": ["person", "name"],
}
def test_state_schema_model_dump_serializes_paths_as_strings() -> None:
schema = StateSchema.model_validate({
"fields": [{"path": "state.person.name", "type": "string"}]
})
schema = StateSchema.model_validate(
{"fields": [{"path": "state.person.name", "type": "string"}]}
)
dumped = schema.model_dump(mode="json")
assert dumped["properties"]["person"]["properties"]["name"]["type"] == "string"
@@ -158,12 +205,14 @@ def test_state_schema_model_dump_serializes_paths_as_strings() -> None:
def test_state_schema_rejects_duplicate_field_paths() -> None:
try:
StateSchema.model_validate({
"fields": [
{"path": "state.person.name", "type": "string"},
{"path": "state.person.name", "type": "string"},
]
})
StateSchema.model_validate(
{
"fields": [
{"path": "state.person.name", "type": "string"},
{"path": "state.person.name", "type": "string"},
]
}
)
except ValueError as exc:
assert "duplicate state field path 'person.name'" in str(exc)
else:
@@ -172,17 +221,19 @@ def test_state_schema_rejects_duplicate_field_paths() -> None:
def test_exact_nested_state_path_uses_reducer_from_json_schema_property() -> None:
workflow = _workflow_from_state_schema(
StateSchema.model_validate({
"type": "object",
"properties": {
"person": {
"type": "object",
"properties": {
"tags": {"type": "array", "reducer": "wf.std.append"}
},
}
},
})
StateSchema.model_validate(
{
"type": "object",
"properties": {
"person": {
"type": "object",
"properties": {
"tags": {"type": "array", "reducer": "wf.std.append"}
},
}
},
}
)
)
state = {"person": {"tags": ["seed"]}}
@@ -192,12 +243,14 @@ def test_exact_nested_state_path_uses_reducer_from_json_schema_property() -> Non
def test_state_schema_field_map_uses_rootless_keys() -> None:
schema = StateSchema.model_validate({
"fields": [
{"path": "state.person.name", "type": "string"},
{"path": "state.person.tags", "type": "array"},
]
})
schema = StateSchema.model_validate(
{
"fields": [
{"path": "state.person.name", "type": "string"},
{"path": "state.person.tags", "type": "array"},
]
}
)
fields = schema.field_map()
assert fields["person.name"].path == StatePath.of("person.name")