Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions ccflow/_flow_model_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __repr__(self) -> str:

class _DepMarker:
def __repr__(self) -> str:
return "Dep"
return "Dependency"


class FromContext:
Expand All @@ -81,11 +81,11 @@ def __class_getitem__(cls, item):
return Annotated[item, _LazyMarker()]


class Dep:
class Dependency:
"""Marker used in ``@Flow.model`` signatures for explicit dependency leaves."""

def __new__(cls, *args, **kwargs):
raise TypeError("Dep is an annotation marker; use Dep[T] in @Flow.model signatures.")
raise TypeError("Dependency is an annotation marker; use Dependency[T] in @Flow.model signatures.")

def __class_getitem__(cls, item):
return Annotated[item, _DepMarker()]
Expand Down Expand Up @@ -463,8 +463,8 @@ def _parse_annotation(annotation: Any) -> _ParsedAnnotation:
raise TypeError("FromContext is an annotation marker; use FromContext[T] in @Flow.model signatures.")
if annotation is Lazy:
raise TypeError("Lazy is an annotation marker; use Lazy[T] in @Flow.model signatures.")
if annotation is Dep:
raise TypeError("Dep is an annotation marker; use Dep[T] in @Flow.model signatures.")
if annotation is Dependency:
raise TypeError("Dependency is an annotation marker; use Dependency[T] in @Flow.model signatures.")

return _ParsedAnnotation(
base=annotation,
Expand All @@ -482,7 +482,7 @@ def _strip_annotated(annotation: Any) -> Any:


def _pop_dep_marker(annotation: Any) -> Tuple[Any, bool]:
"""Remove only the outer Dep marker while preserving other Annotated metadata."""
"""Remove only the outer Dependency marker while preserving other Annotated metadata."""

if get_origin(annotation) is not Annotated:
return annotation, False
Expand All @@ -493,7 +493,7 @@ def _pop_dep_marker(annotation: Any) -> Tuple[Any, bool]:
base = args[0]
if not metadata:
return base, has_dep
# Keep non-Dep metadata, such as pydantic Field constraints, on the annotation
# Keep non-Dependency metadata, such as pydantic Field constraints, on the annotation
# used to validate literals and resolved dependency results. Build the tuple
# first and subscript ``Annotated`` with it: ``Annotated[base, *metadata]`` is
# 3.11+-only syntax, and ``Annotated.__class_getitem__`` was removed in 3.14,
Expand All @@ -509,31 +509,31 @@ def _annotation_contains_dep(annotation: Any) -> bool:


def _validate_dep_annotation(annotation: Any, *, in_dep: bool = False, dep_allowed: bool = False) -> None:
"""Validate the deliberately small Dep marker language.
"""Validate the deliberately small Dependency marker language.

Dep marks exact substitution slots. It is allowed inside container values,
but not inside another Dep, not in dict keys, and not mixed with Lazy or
FromContext markers inside a Dep slot.
Dependency marks exact substitution slots. It is allowed inside container values,
but not inside another Dependency, not in dict keys, and not mixed with Lazy or
FromContext markers inside a Dependency slot.
"""

annotation, has_dep = _pop_dep_marker(annotation)
if has_dep:
if not dep_allowed:
raise TypeError("Dep[...] is only supported in regular parameter container values.")
raise TypeError("Dependency[...] is only supported in regular parameter container values.")
if in_dep:
raise TypeError("Dep[...] cannot contain another Dep[...] marker.")
raise TypeError("Dependency[...] cannot contain another Dependency[...] marker.")
_validate_dep_annotation(annotation, in_dep=True, dep_allowed=False)
return

if in_dep and get_origin(annotation) is Annotated:
metadata = get_args(annotation)[1:]
if any(isinstance(item, (_LazyMarker, _FromContextMarker)) for item in metadata):
raise TypeError("Dep[...] cannot contain Lazy[...] or FromContext[...] markers.")
raise TypeError("Dependency[...] cannot contain Lazy[...] or FromContext[...] markers.")

origin = get_origin(annotation)
args = get_args(annotation)
if origin in _UNION_ORIGINS and any(_annotation_contains_dep(arg) for arg in args):
raise TypeError("Dep[...] is not supported inside union annotations.")
raise TypeError("Dependency[...] is not supported inside union annotations.")
if origin is list and len(args) == 1:
_validate_dep_annotation(args[0], in_dep=in_dep, dep_allowed=True)
return
Expand All @@ -545,7 +545,7 @@ def _validate_dep_annotation(annotation: Any, *, in_dep: bool = False, dep_allow
if origin is dict and len(args) == 2:
key_annotation, value_annotation = args
if _annotation_contains_dep(key_annotation):
raise TypeError("Dep[...] is not supported in dict keys.")
raise TypeError("Dependency[...] is not supported in dict keys.")
_validate_dep_annotation(value_annotation, in_dep=in_dep, dep_allowed=True)
return

Expand Down Expand Up @@ -655,9 +655,9 @@ def _analyze_flow_function(
raise TypeError(f"Parameter '{param.name}' cannot combine Lazy[...] and FromContext[...].")
if (parsed.is_dep or _annotation_contains_dep(parsed.base)) and (parsed.is_lazy or parsed.is_from_context):
marker = "Lazy" if parsed.is_lazy else "FromContext"
raise TypeError(f"Parameter '{param.name}' cannot combine Dep[...] and {marker}[...].")
raise TypeError(f"Parameter '{param.name}' cannot combine Dependency[...] and {marker}[...].")
if parsed.is_dep:
raise TypeError("Dep[...] is only supported in regular parameter container values.")
raise TypeError("Dependency[...] is only supported in regular parameter container values.")
_validate_dep_annotation(parsed.base)
has_dep_slots = _annotation_contains_dep(parsed.base)
has_default = param.default is not inspect.Parameter.empty
Expand Down
6 changes: 3 additions & 3 deletions ccflow/examples/flow_model/flow_model_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
1. define stages as plain Python functions,
2. compose stages by passing upstream models as ordinary arguments,
3. rewrite contextual inputs on one dependency edge with `.flow.with_context(...)`,
4. use `Dep[...]` for model leaves inside regular container inputs,
4. use `Dependency[...]` for model leaves inside regular container inputs,
5. execute the configured graph with `model.flow.compute(...)`.

Run with:
Expand All @@ -15,7 +15,7 @@

from datetime import date, timedelta

from ccflow import DateRangeContext, Dep, Flow, FromContext
from ccflow import DateRangeContext, Dependency, Flow, FromContext


def _format_input_names(inputs: dict[str, object]) -> str:
Expand Down Expand Up @@ -54,7 +54,7 @@ def count_visitors(

@Flow.model(context_type=DateRangeContext)
def visitor_delta(
counts: list[Dep[int]],
counts: list[Dependency[int]],
label: str,
start_date: FromContext[date],
end_date: FromContext[date],
Expand Down
6 changes: 3 additions & 3 deletions ccflow/examples/flow_model/flow_model_hydra_builder_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

- keep runtime context (`start_date`, `end_date`) as runtime inputs,
- use a plain Python builder function for graph construction,
- use `Dep[...]` when a regular container input holds upstream models,
- use `Dependency[...]` when a regular container input holds upstream models,
- let Hydra instantiate that builder and register the returned model.

Run with:
Expand All @@ -20,7 +20,7 @@
from datetime import date, timedelta
from pathlib import Path

from ccflow import CallableModel, DateRangeContext, Dep, Flow, FromContext, ModelRegistry
from ccflow import CallableModel, DateRangeContext, Dependency, Flow, FromContext, ModelRegistry

CONFIG_PATH = Path(__file__).with_name("config") / "flow_model_hydra_builder_demo.yaml"

Expand Down Expand Up @@ -65,7 +65,7 @@ def count_visitors(location: str, start_date: FromContext[date], end_date: FromC

@Flow.model(context_type=DateRangeContext)
def visitor_delta(
counts: list[Dep[int]],
counts: list[Dependency[int]],
label: str,
start_date: FromContext[date],
end_date: FromContext[date],
Expand Down
26 changes: 16 additions & 10 deletions ccflow/flow_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
context instead of model construction.
* ``Lazy[T]`` marks a dependency that should be passed as a thunk and evaluated
only if user code calls it.
* ``Dep[T]`` marks a nested regular-parameter slot that can be a literal ``T``
* ``Dependency[T]`` marks a nested regular-parameter slot that can be a literal ``T``
or a ``CallableModel`` dependency returning ``T``.
* ``model.flow.compute(...)`` and ``model.flow.with_context(...)`` provide the
ergonomic execution and contextual binding API.
Expand Down Expand Up @@ -90,7 +90,7 @@
from ._flow_model_binding import (
_UNION_ORIGINS,
_UNSET,
Dep,
Dependency,
FromContext,
Lazy,
_analyze_flow_context_transform,
Expand All @@ -115,7 +115,7 @@
__all__ = (
"FlowAPI",
"BoundModel",
"Dep",
"Dependency",
"FlowInspection",
"InputSpec",
"FromContext",
Expand Down Expand Up @@ -435,7 +435,7 @@ def _is_model_dependency(value: Any) -> bool:


def _strip_outer_non_dep_annotated(annotation: Any) -> Any:
"""Strip outer Annotated layers only until an explicit Dep marker is found."""
"""Strip outer Annotated layers only until an explicit Dependency marker is found."""

while get_origin(annotation) is Annotated:
_, has_dep = _pop_dep_marker(annotation)
Expand Down Expand Up @@ -1081,7 +1081,7 @@ def _walk_dep_marked_value(
on_dict: Callable[[Any, List[Tuple[Any, Any]], Any, Any, Any, Tuple[Any, ...]], Any],
path: Tuple[Any, ...] = (),
) -> Any:
"""Walk only the container grammar where Dep markers are valid."""
"""Walk only the container grammar where Dependency markers are valid."""

annotation = _strip_outer_non_dep_annotated(annotation)
dep_base, is_dep_slot = _pop_dep_marker(annotation)
Expand Down Expand Up @@ -1183,7 +1183,7 @@ def _dep_slot_prefers_serialized_dependency(annotation: Any) -> bool:


def _validate_dep_marked_value(name: str, value: Any, annotation: Any, source: str, path: Tuple[Any, ...] = ()) -> Any:
"""Validate construction values that use explicit nested Dep markers."""
"""Validate construction values that use explicit nested Dependency markers."""

def validate_dep_slot(item: Any, dep_base: Any, item_path: Tuple[Any, ...]) -> Any:
if _is_model_dependency(item):
Expand Down Expand Up @@ -1233,7 +1233,7 @@ def validate_dict(


def _resolve_dep_marked_value(name: str, value: Any, annotation: Any, context: ContextBase, path: Tuple[Any, ...] = ()) -> Any:
"""Resolve CallableModel leaves that appear at explicit Dep marker slots."""
"""Resolve CallableModel leaves that appear at explicit Dependency marker slots."""

def resolve_dep_slot(item: Any, dep_base: Any, item_path: Tuple[Any, ...]) -> Any:
if _is_model_dependency(item):
Expand All @@ -1255,7 +1255,7 @@ def resolve_dep_slot(item: Any, dep_base: Any, item_path: Tuple[Any, ...]) -> An


def _dep_marked_dependency_entries(value: Any, annotation: Any, context: ContextBase) -> GraphDepList:
"""Collect dependency graph edges from explicit Dep marker slots."""
"""Collect dependency graph edges from explicit Dependency marker slots."""

def dep_slot_edges(item: Any, _dep_base: Any, _path: Tuple[Any, ...]) -> GraphDepList:
if not _is_model_dependency(item):
Expand Down Expand Up @@ -1288,7 +1288,7 @@ def _dep_marked_dependency_specs(
*,
trim_context: bool,
) -> Tuple[DependencySpec, ...]:
"""Collect inspect-visible dependency edges from explicit Dep marker slots."""
"""Collect inspect-visible dependency edges from explicit Dependency marker slots."""

def dep_slot_specs(item: Any, _dep_base: Any, item_path: Tuple[Any, ...]) -> Tuple[DependencySpec, ...]:
if not _is_model_dependency(item):
Expand Down Expand Up @@ -1323,7 +1323,7 @@ def merge_items(items: Any) -> Tuple[DependencySpec, ...]:


def _dep_marked_identity_value(value: Any, annotation: Any, context: ContextBase) -> Any:
"""Return an identity payload with explicit Dep leaves replaced by dependencies."""
"""Return an identity payload with explicit Dependency leaves replaced by dependencies."""

def dep_slot_identity(item: Any, _dep_base: Any, _path: Tuple[Any, ...]) -> Any:
if _is_model_dependency(item):
Expand Down Expand Up @@ -3289,6 +3289,12 @@ def _generated_field_annotation(param: _FlowModelParam) -> Any:
annotation = param.validation_annotation
elif param.is_lazy:
annotation = CallableModel
elif param.has_dep_slots:
# Dependency-marked containers may hold CallableModel leaves alongside
# literals. The leaf types are validated by ccflow, not pydantic, so use
# Any here to keep SkipValidation from emitting serializer warnings when a
# model leaf is serialized against the literal leaf type.
annotation = Any
elif param.annotation is Any or param.annotation is inspect.Parameter.empty:
annotation = Any
else:
Expand Down
Loading
Loading