Applying decorators#
When using decorators with providers, care must be taken to allow Sciline to recognize the argument and return types. This is done easiest with functools.wraps. The following decorator can be safely applied to providers:
[1]:
import functools
from typing import Any, TypeVar
from collections.abc import Callable
import sciline
R = TypeVar('R')
def deco(f: Callable[..., R]) -> Callable[..., R]:
@functools.wraps(f)
def impl(*args: Any, **kwargs: Any) -> R:
return f(*args, **kwargs)
return impl
@deco
def to_string(x: int) -> str:
return str(x)
pipeline = sciline.Pipeline([to_string], params={int: 3})
pipeline.compute(str)
[1]:
'3'
Omitting functools.wraps
results in an error when computing results:
[2]:
def bad_deco(f: Callable[..., R]) -> Callable[..., R]:
def impl(*args: Any, **kwargs: Any) -> R:
return f(*args, **kwargs)
return impl
@bad_deco
def to_string(x: int) -> str:
return str(x)
pipeline = sciline.Pipeline([to_string], params={int: 3})
pipeline.compute(str)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[2], line 13
8 @bad_deco
9 def to_string(x: int) -> str:
10 return str(x)
---> 13 pipeline = sciline.Pipeline([to_string], params={int: 3})
14 pipeline.compute(str)
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/pipeline.py:142, in Pipeline.__init__(self, providers, params, constraints)
117 def __init__(
118 self,
119 providers: Iterable[ToProvider | Provider] | None = None,
(...)
122 constraints: Mapping[TypeVar, Iterable[Key]] | None = None,
123 ):
124 """
125 Setup a Pipeline from a list providers
126
(...)
140 the type variable definition.
141 """
--> 142 super().__init__(providers, constraints=constraints)
143 for tp, param in (params or {}).items():
144 self[tp] = param
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:93, in DataGraph.__init__(self, providers, constraints)
91 self._cbgraph = cb.Graph(nx.DiGraph())
92 for provider in providers or []:
---> 93 self.insert(provider)
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:154, in DataGraph.insert(self, provider)
152 return_type = provider.deduce_key()
153 if typevars := _find_all_typevars(return_type):
--> 154 for bound in _mapping_to_constrained(typevars, self._constraints):
155 self.insert(provider.bind_type_vars(bound))
156 return
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:55, in _mapping_to_constrained(type_vars, over_constraints)
52 def _mapping_to_constrained(
53 type_vars: set[TypeVar], over_constraints: dict[TypeVar, frozenset[Key]]
54 ) -> Generator[dict[TypeVar, Key], None, None]:
---> 55 constraints = [_get_typevar_constraints(t, over_constraints) for t in type_vars]
56 for combination in itertools.product(*constraints):
57 yield dict(zip(type_vars, combination, strict=True))
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:55, in <listcomp>(.0)
52 def _mapping_to_constrained(
53 type_vars: set[TypeVar], over_constraints: dict[TypeVar, frozenset[Key]]
54 ) -> Generator[dict[TypeVar, Key], None, None]:
---> 55 constraints = [_get_typevar_constraints(t, over_constraints) for t in type_vars]
56 for combination in itertools.product(*constraints):
57 yield dict(zip(type_vars, combination, strict=True))
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:45, in _get_typevar_constraints(t, over_constraints)
43 return override
44 if not (constraints := t.__constraints__):
---> 45 raise ValueError(
46 f"Type variable {t!r} has no constraints. Either constrain the type "
47 f"variable in its definition or via the 'constraints' argument of Pipeline."
48 )
49 return frozenset(constraints)
ValueError: Type variable ~R has no constraints. Either constrain the type variable in its definition or via the 'constraints' argument of Pipeline.
Hint
For Python 3.10+, the decorator itself can be type-annoted like this:
from typing import ParamSpec, TypeVar
P = ParamSpec('P')
R = TypeVar('R')
def deco(f: Callable[P, R]) -> Callable[P, R]:
@functools.wraps(f)
def impl(*args: P.args, **kwargs: P.kwargs) -> R:
return f(*args, **kwargs)
return impl
This is good practice but not required by Sciline.