-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add method to get provider from type #135
Changes from all commits
596b517
f86bae4
5a3a900
2d48817
ded4f8a
7b4149b
86da6fc
81fba82
33c015b
49b088a
11002b7
bc53626
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -368,7 +368,6 @@ def __init__( | |
Dictionary of concrete values to provide for types. | ||
""" | ||
self._providers: Dict[Key, Provider] = {} | ||
self._subproviders: Dict[type, Dict[Tuple[Key | TypeVar, ...], Provider]] = {} | ||
self._param_tables: Dict[Key, ParamTable] = {} | ||
self._param_name_to_table_key: Dict[Key, Key] = {} | ||
for provider in providers or []: | ||
|
@@ -434,6 +433,26 @@ def __setitem__(self, key: Type[T], param: T) -> None: | |
) | ||
self._set_provider(key, Provider.parameter(param)) | ||
|
||
def get_provider( | ||
self, tp: Union[Type[T], Item[T]] | ||
) -> Union[Callable[[Any, ...], T], T]: | ||
'''Get the provider that produces the type. If the type is provided by a | ||
parameter the method returns the value of the parameter.''' | ||
provider = self._get_unique_provider(tp, HandleAsBuildTimeException())[0] | ||
if provider.kind == 'function': | ||
return provider.func | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does it return the function and not the provider object? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because I agreed with the concern that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not concerned about exposing it. This can be useful in its own right. Your solution makes it difficult to distinguish between providers and parameters. You can't check the type of the return value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My only concern about expose |
||
return provider.func() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about other kinds? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes that's something we need to talk about. What other cases are there, I looked at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, users might request There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's what I thought as well, so I don't think it makes sense to invest time to solve that now. |
||
|
||
def has_provider(self, tp: Union[Type[T], Item[T]]) -> bool: | ||
'''Determines if the pipeline has a provider that produces the type.''' | ||
try: | ||
self.get_provider(tp) | ||
return True | ||
except AmbiguousProvider: | ||
return True | ||
except UnsatisfiedRequirement: | ||
return False | ||
|
||
def set_param_table(self, params: ParamTable) -> None: | ||
""" | ||
Set a parameter table for a row dimension. | ||
|
@@ -532,72 +551,63 @@ def _set_provider( | |
'Series is a special container reserved for use in conjunction with ' | ||
'sciline.ParamTable and must not be provided directly.' | ||
) | ||
if (origin := get_origin(key)) is not None: | ||
subproviders = self._subproviders.setdefault(origin, {}) | ||
args = get_args(key) | ||
subproviders[args] = provider | ||
else: | ||
self._providers[key] = provider | ||
self._providers[key] = provider | ||
|
||
def _get_provider( | ||
self, tp: Union[Type[T], Item[T]], handler: Optional[ErrorHandler] = None | ||
) -> Tuple[Provider, Dict[TypeVar, Key]]: | ||
handler = handler or HandleAsBuildTimeException() | ||
explanation: List[str] = [] | ||
if (provider := self._providers.get(tp)) is not None: | ||
return provider, {} | ||
elif (origin := get_origin(tp)) is not None and ( | ||
subproviders := self._subproviders.get(origin) | ||
) is not None: | ||
requested = get_args(tp) | ||
|
||
if provider := self._providers.get(tp): | ||
# Optimization to quickly find non-generic providers | ||
matches = [(provider, {})] | ||
else: | ||
matches = [ | ||
(subprovider, bound) | ||
for args, subprovider in subproviders.items() | ||
if ( | ||
bound := _find_bounds_to_make_compatible_type_tuple(requested, args) | ||
) | ||
(provider, bound) | ||
for return_type, provider in self._providers.items() | ||
if (bound := _find_bounds_to_make_compatible_type(tp, return_type)) | ||
is not None | ||
] | ||
typevar_counts = [len(bound) for _, bound in matches] | ||
min_typevar_count = min(typevar_counts, default=0) | ||
matches = [ | ||
m | ||
for count, m in zip(typevar_counts, matches) | ||
if count == min_typevar_count | ||
] | ||
|
||
if len(matches) == 1: | ||
provider, bound = matches[0] | ||
return provider, bound | ||
elif len(matches) > 1: | ||
matching_providers = [provider.location.name for provider, _ in matches] | ||
raise AmbiguousProvider( | ||
f"Multiple providers found for type {tp}." | ||
f" Matching providers are: {matching_providers}." | ||
) | ||
else: | ||
typevars_in_expression = _extract_typevars_from_generic_type(origin) | ||
if typevars_in_expression: | ||
explanation = [ | ||
''.join( | ||
map( | ||
str, | ||
typevar_counts = [len(bound) for _, bound in matches] | ||
min_typevar_count = min(typevar_counts, default=0) | ||
matches = [ | ||
m for count, m in zip(typevar_counts, matches) if count == min_typevar_count | ||
] | ||
|
||
if len(matches) == 1: | ||
provider, bound = matches[0] | ||
return provider, bound | ||
elif len(matches) > 1: | ||
matching_providers = [provider.location.name for provider, _ in matches] | ||
raise AmbiguousProvider( | ||
f"Multiple providers found for type {tp}." | ||
f" Matching providers are: {matching_providers}." | ||
) | ||
else: | ||
origin = get_origin(tp) | ||
typevars_of_generic = _extract_typevars_from_generic_type(origin) | ||
if typevars_of_generic: | ||
explanation = [ | ||
''.join( | ||
map( | ||
str, | ||
( | ||
'Note that ', | ||
keyname(origin[typevars_of_generic]), | ||
' has constraints ', | ||
( | ||
'Note that ', | ||
keyname(origin[typevars_in_expression]), | ||
' has constraints ', | ||
( | ||
{ | ||
keyname(tv): tuple( | ||
map(keyname, tv.__constraints__) | ||
) | ||
for tv in typevars_in_expression | ||
} | ||
), | ||
{ | ||
keyname(tv): tuple( | ||
map(keyname, tv.__constraints__) | ||
) | ||
for tv in typevars_of_generic | ||
} | ||
), | ||
) | ||
), | ||
) | ||
] | ||
) | ||
] | ||
return handler.handle_unsatisfied_requirement(tp, *explanation), {} | ||
|
||
def _get_unique_provider( | ||
|
@@ -923,7 +933,6 @@ def copy(self) -> Pipeline: | |
""" | ||
out = Pipeline() | ||
out._providers = self._providers.copy() | ||
out._subproviders = {k: v.copy() for k, v in self._subproviders.items()} | ||
out._param_tables = self._param_tables.copy() | ||
out._param_name_to_table_key = self._param_name_to_table_key.copy() | ||
return out | ||
|
@@ -932,14 +941,8 @@ def __copy__(self) -> Pipeline: | |
return self.copy() | ||
|
||
def _repr_html_(self) -> str: | ||
providers_without_parameters = ( | ||
(origin, tuple(), value) for origin, value in self._providers.items() | ||
) # type: ignore[var-annotated] | ||
providers_with_parameters = ( | ||
(origin, args, value) | ||
for origin in self._subproviders | ||
for args, value in self._subproviders[origin].items() | ||
) | ||
return pipeline_html_repr( | ||
chain(providers_without_parameters, providers_with_parameters) | ||
) | ||
providers = [ | ||
(get_origin(tp), get_args(tp), value) | ||
for tp, value in self._providers.items() | ||
] | ||
return pipeline_html_repr(providers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About the conceptual issues, I was wrong stating that this makes Pipeline a Mapping. It does not because we would have to also implement
__iter__
and__len__
. And actually, since there is a__setitem__
, if anything, it should beMutableMapping
, thus we would also need__delitem__
.However, Pipeline implements part of the interface (and also the interface of
Sequence
). I'm not entirely sure how it fits in.More concretely, implementing
__getitem__
makes Python think that pipelines are iterable with integer indices:raises
This not great. So you would also need to implement
__iter__
. And since you would be going for a dict-like interface, you would also needkeys
,values
, anditems
.And that leaves us with the asymmetry between
__getitem__
and__setitem__
. For generic providers, the former currently returns different values and accepts different keys than the latter. Even if__setitem__
were extended to support providers on top of parameters.Also, what about parameter tables or sentinels? Would
__getitem__
return those special, internal providers? Or how would it represent them?With the proposed semantics, the new functions allow checking what a pipeline can produce and how. So it does not allow treating Pipeline like a container. So named methods seem more appropriate to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes a lot of sense.
How about adding a
get_provider
method instead of__getitem__
?I don't know, what do you think? I'm thinking that it's likely that a user wants to access the parameter tables that a pipeline uses, but they should preferably be returned in the same form that they were inserted, i.e. as
sl.ParamTable
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just like the discussion about using a graph data structure inside
Pipeline
, this shows that the implicit way in which we handle param-tables is problematic. We may need to re-think that (come up with a more explicit interface, or way the express the nesting?).