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:72, in Pipeline.__init__(self, providers, params)
55 def __init__(
56 self,
57 providers: Iterable[ToProvider | Provider] | None = None,
58 *,
59 params: dict[type[Any], Any] | None = None,
60 ):
61 """
62 Setup a Pipeline from a list providers
63
(...)
70 Dictionary of concrete values to provide for types.
71 """
---> 72 super().__init__(providers)
73 for tp, param in (params or {}).items():
74 self[tp] = param
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:55, in DataGraph.__init__(self, providers)
53 self._cbgraph = cb.Graph(nx.DiGraph())
54 for provider in providers or []:
---> 55 self.insert(provider)
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:114, in DataGraph.insert(self, provider)
112 return_type = provider.deduce_key()
113 if typevars := _find_all_typevars(return_type):
--> 114 for bound in _mapping_to_constrained(typevars):
115 self.insert(provider.bind_type_vars(bound))
116 return
File ~/work/sciline/sciline/.tox/docs/lib/python3.10/site-packages/sciline/data_graph.py:43, in _mapping_to_constrained(type_vars)
41 constraints = [_get_typevar_constraints(t) for t in type_vars]
42 if any(len(c) == 0 for c in constraints):
---> 43 raise ValueError('Typevars must have constraints')
44 for combination in itertools.product(*constraints):
45 yield dict(zip(type_vars, combination, strict=True))
ValueError: Typevars must have constraints
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.