Skip to content

Commit

Permalink
Partial implementation of variable wiring
Browse files Browse the repository at this point in the history
  • Loading branch information
ashuping committed Oct 11, 2024
1 parent 736d778 commit 1cf7210
Show file tree
Hide file tree
Showing 10 changed files with 689 additions and 9 deletions.
2 changes: 2 additions & 0 deletions exseos/types/Option.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ def __str__(self):
class Some[A](Option):
"""Represents an ``Option`` that contains a concrete value."""

__match_args__ = ("val",)

def __init__(self, val: A):
self.__val = val

Expand Down
12 changes: 9 additions & 3 deletions exseos/types/Result.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@

from exseos.types.ComparableError import ComparableError

from typing import TypeVar, Callable, List
from abc import ABC, abstractmethod
from typing import TypeVar, Callable, List

A = TypeVar("A")
B = TypeVar("B")
Expand Down Expand Up @@ -211,6 +211,8 @@ class Okay[C](Result):
Has a value, but no warnings or errors
"""

__match_args__ = ("val",)

def __init__(self, val: C):
self.__val = val

Expand Down Expand Up @@ -258,8 +260,10 @@ class Warn[B, C](Result):
Has a value and warnings, but no errors
"""

def __init__(self, warn: List[B], val: C):
self.__warn = warn
__match_args__ = ("warnings", "val")

def __init__(self, warnings: List[B], val: C):
self.__warn = warnings
self.__val = val

@property
Expand Down Expand Up @@ -312,6 +316,8 @@ class Fail[A, B](Result):
Has warnings and errors, but no value.
"""

__match_args__ = ("errors", "warnings")

def __init__(self, errors: List[A], warnings: List[B] = []):
self.__warn = warnings
self.__err = errors
Expand Down
20 changes: 20 additions & 0 deletions exseos/types/Variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,26 @@ def __getattr__(self, name: str) -> any:
"""
return self.get_var(name)

def __eq__(self, other: "VariableSet") -> bool:
if not issubclass(type(other), VariableSet):
return False

return self.vars == other.vars

def __str__(self) -> str:
return (
"VariableSet {\n"
+ "".join([f" {k}: {v}\n" for (k, v) in self.vars.items()])
+ "}"
)

def __repr__(self) -> str:
return (
"VariableSet("
+ ", ".join([f"{repr(v)}" for (_, v) in self.vars.items()])
+ ")"
)


def ensure_from_name(x: Variable | str) -> Variable:
"""
Expand Down
3 changes: 3 additions & 0 deletions exseos/workflow/Workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,8 @@ def __call__(self, *args, **kwargs):
"""
Instantiate the ``Workflow`` and bind the provided arguments to its
inputs.
TODO: We should validate the ``Stage``'s, to make sure that their inputs
and outputs are well-formed and avoid issues down the line.
"""
raise NotImplementedError
54 changes: 48 additions & 6 deletions exseos/workflow/stage/Stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,20 @@ def __init__(
"""
self.__inputs = tuple(
[
_process_stage_io(dex, i, args, kwargs)
(i, _process_stage_io(dex, i, args, kwargs))
for dex, i in enumerate(self.input_vars)
]
)

self.__outputs = tuple(
[
_process_stage_io(dex, i, _to[0], _to[1])
for dex, i in enumerate(self.output_vars)
(o, _process_stage_io(dex, o, _to[0], _to[1]))
for dex, o in enumerate(self.output_vars)
]
)

self.__args = tuple(args)
self.__kwargs = tuple(kwargs)
self.__kwargs = kwargs
self.__to = _to

@abstractmethod
Expand All @@ -115,19 +115,37 @@ def run(self, inputs: VariableSet) -> Result[Exception, Exception, tuple[Variabl
... # pragma: no cover

@property
def _input_bindings(self) -> tuple[Variable]:
def _input_bindings(self) -> tuple[Variable, Option[Variable]]:
"""
A tuple of input bindings for this stage. Used for internal wiring.
Bindings take the form ``(internal_var, Option[bound_var])``.
:meta private:
"""
return self.__inputs

@property
def _output_bindings(self) -> tuple[Variable]:
def _output_bindings(self) -> tuple[Variable, Option[Variable]]:
"""
A tuple of output bindings for this stage. Used for internal wiring.
Bindings take the form ``(internal_var, Option[bound_var])``.
:meta private:
"""
return self.__outputs

@property
def _is_implicit(self) -> bool:
"""
``True`` iff the stage is marked as implicit-binding. Used for internal
wiring.
:meta private:
"""
...

def to(self, *args, **kwargs) -> "Stage":
"""
Bind the outputs of the ``Stage`` to a ``Variable`` or name.
Expand Down Expand Up @@ -172,3 +190,27 @@ def provides(self, *args: tuple[str]) -> "Stage":
:returns: A ``Stage`` with the requested dependencies added.
"""
...

def bind_implicitly(self) -> "Stage":
"""
Mark this stage as implicit-binding. This allows its inputs to be
matched to previous stages' outputs automatically, without needing to
explicitly use ``to`` and constructor params.
This is not recommended, as it can easily lead to ambiguity and highly
dependent on the stage's input names.
:returns: A ``Stage`` marked as implicit-binding
"""
...

def always(self) -> "Stage":
"""
Mark this stage as 'always-run.' Normally, the result of a ``Stage`` is
cached for future stages unless its inputs change. This directive
indicates that this stage has a side-effect that requires it to be
re-run every time.
:returns: This ``Stage`` marked as always-run
"""
...
Loading

0 comments on commit 1cf7210

Please sign in to comment.