-
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
Better handling of typing.Optional
#50
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b9044a6
Add basics tests
SimonHeybrock 4e29695
Basic working version
SimonHeybrock 0484b61
Test transitive cases
SimonHeybrock 54cf2d4
Simplify and add more tests
SimonHeybrock c343058
Partially working param table with optional
SimonHeybrock 3f26689
Implement and test consistent behavior
SimonHeybrock 2f3fa84
Test that Union can be used instead of Optional
SimonHeybrock 1bd8742
Fix most typing
SimonHeybrock 4ca68b3
Ignore remaining mypy problems
SimonHeybrock 91d57f9
Add param so test makes more sense
SimonHeybrock eb1948a
Test optional with non- optional
SimonHeybrock a983adc
More tests
SimonHeybrock 54ab563
Fix minor mypy problem
SimonHeybrock File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# SPDX-License-Identifier: BSD-3-Clause | ||
# Copyright (c) 2023 Scipp contributors (https://github.com/scipp) | ||
from typing import NewType, Optional, Union | ||
|
||
import pytest | ||
|
||
import sciline as sl | ||
|
||
|
||
def test_provider_returning_optional_disallowed() -> None: | ||
def make_optional() -> Optional[int]: | ||
return 3 | ||
|
||
with pytest.raises(ValueError): | ||
sl.Pipeline([make_optional]) | ||
|
||
|
||
def test_provider_returning_union_disallowed() -> None: | ||
def make_union() -> Union[int, float]: | ||
return 3 | ||
|
||
with pytest.raises(ValueError): | ||
sl.Pipeline([make_union]) | ||
|
||
|
||
def test_parameter_type_union_or_optional_disallowed() -> None: | ||
pipeline = sl.Pipeline() | ||
with pytest.raises(ValueError): | ||
pipeline[Union[int, float]] = 3 # type: ignore[index] | ||
with pytest.raises(ValueError): | ||
pipeline[Optional[int]] = 3 # type: ignore[index] | ||
|
||
|
||
def test_union_requirement_leads_to_UnsatisfiedRequirement() -> None: | ||
def require_union(x: Union[int, float]) -> str: | ||
return f'{x}' | ||
|
||
pipeline = sl.Pipeline([require_union]) | ||
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. Can you add a provider for 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. Good point, see update! |
||
pipeline[int] = 1 | ||
with pytest.raises(sl.UnsatisfiedRequirement): | ||
pipeline.compute(str) | ||
|
||
|
||
def test_optional_dependency_can_be_filled_by_non_optional_param() -> None: | ||
def use_optional(x: Optional[int]) -> str: | ||
return f'{x or 123}' | ||
|
||
pipeline = sl.Pipeline([use_optional], params={int: 1}) | ||
assert pipeline.compute(str) == '1' | ||
|
||
|
||
def test_union_with_none_can_be_used_instead_of_Optional() -> None: | ||
def use_union1(x: Union[int, None]) -> str: | ||
return f'{x or 123}' | ||
|
||
def use_union2(x: Union[None, int]) -> str: | ||
return f'{x or 123}' | ||
|
||
pipeline = sl.Pipeline([use_union1], params={int: 1}) | ||
assert pipeline.compute(str) == '1' | ||
pipeline = sl.Pipeline([use_union2], params={int: 1}) | ||
assert pipeline.compute(str) == '1' | ||
|
||
|
||
def test_optional_requested_directly_can_be_filled_by_non_optional_param() -> None: | ||
pipeline = sl.Pipeline([], params={int: 1}) | ||
assert pipeline.compute(Optional[int]) == 1 # type: ignore[call-overload] | ||
|
||
|
||
def test_optional_dependency_can_be_filled_transitively() -> None: | ||
def use_optional(x: Optional[int]) -> str: | ||
return f'{x or 123}' | ||
|
||
def make_int(x: float) -> int: | ||
return int(x) | ||
|
||
pipeline = sl.Pipeline([use_optional, make_int], params={float: 2.2}) | ||
assert pipeline.compute(str) == '2' | ||
|
||
|
||
def test_optional_dependency_is_set_to_none_if_no_provider_found() -> None: | ||
def use_optional(x: Optional[int]) -> str: | ||
return f'{x or 123}' | ||
|
||
pipeline = sl.Pipeline([use_optional]) | ||
assert pipeline.compute(str) == '123' | ||
|
||
|
||
def test_optional_dependency_is_set_to_none_if_no_provider_found_transitively() -> None: | ||
def use_optional(x: Optional[int]) -> str: | ||
return f'{x or 123}' | ||
|
||
def make_int(x: float) -> int: | ||
return int(x) | ||
|
||
pipeline = sl.Pipeline([use_optional, make_int]) | ||
assert pipeline.compute(str) == '123' | ||
|
||
|
||
def test_can_have_both_optional_and_non_optional_path_to_param() -> None: | ||
Str1 = NewType('Str1', str) | ||
Str2 = NewType('Str2', str) | ||
Str12 = NewType('Str12', str) | ||
Str21 = NewType('Str21', str) | ||
|
||
def use_optional_int(x: Optional[int]) -> Str1: | ||
return Str1(f'{x or 123}') | ||
|
||
def use_int(x: int) -> Str2: | ||
return Str2(f'{x}') | ||
|
||
def combine12(x: Str1, y: Str2) -> Str12: | ||
return Str12(f'{x} {y}') | ||
|
||
def combine21(x: Str2, y: Str1) -> Str21: | ||
return Str21(f'{x} {y}') | ||
|
||
pipeline = sl.Pipeline( | ||
[use_optional_int, use_int, combine12, combine21], params={int: 1} | ||
) | ||
assert pipeline.compute(Str12) == '1 1' | ||
assert pipeline.compute(Str21) == '1 1' | ||
|
||
|
||
def test_presence_of_optional_does_not_affect_related_exception() -> None: | ||
Str1 = NewType('Str1', str) | ||
Str2 = NewType('Str2', str) | ||
Str12 = NewType('Str12', str) | ||
Str21 = NewType('Str21', str) | ||
|
||
def use_optional_int(x: Optional[int]) -> Str1: | ||
return Str1(f'{x or 123}') | ||
|
||
# Make sure the implementation does not unintentionally put "None" here, | ||
# triggered by the presence of the optional dependency on int in another provider. | ||
def use_int(x: int) -> Str2: | ||
return Str2(f'{x}') | ||
|
||
def combine12(x: Str1, y: Str2) -> Str12: | ||
return Str12(f'{x} {y}') | ||
|
||
def combine21(x: Str2, y: Str1) -> Str21: | ||
return Str21(f'{x} {y}') | ||
|
||
pipeline = sl.Pipeline([use_optional_int, use_int, combine12, combine21]) | ||
with pytest.raises(sl.UnsatisfiedRequirement): | ||
pipeline.compute(Str12) | ||
with pytest.raises(sl.UnsatisfiedRequirement): | ||
pipeline.compute(Str21) | ||
|
||
|
||
def test_optional_dependency_in_node_depending_on_param_table() -> None: | ||
def use_optional(x: float, y: Optional[int]) -> str: | ||
return f'{x} {y or 123}' | ||
|
||
pl = sl.Pipeline([use_optional]) | ||
pl.set_param_table(sl.ParamTable(int, {float: [1.0, 2.0, 3.0]})) | ||
assert pl.compute(sl.Series[int, str]) == sl.Series( | ||
int, {0: '1.0 123', 1: '2.0 123', 2: '3.0 123'} | ||
) | ||
pl[int] = 11 | ||
assert pl.compute(sl.Series[int, str]) == sl.Series( | ||
int, {0: '1.0 11', 1: '2.0 11', 2: '3.0 11'} | ||
) | ||
|
||
|
||
def test_optional_dependency_can_be_filled_from_param_table() -> None: | ||
def use_optional(x: Optional[float]) -> str: | ||
return f'{x or 4.0}' | ||
|
||
pl = sl.Pipeline([use_optional]) | ||
pl.set_param_table(sl.ParamTable(int, {float: [1.0, 2.0, 3.0]})) | ||
assert pl.compute(sl.Series[int, str]) == sl.Series( | ||
int, {0: '1.0', 1: '2.0', 2: '3.0'} | ||
) | ||
|
||
|
||
def test_optional_without_anchoring_param_raises_when_requesting_series() -> None: | ||
Param = NewType('Param', float) | ||
|
||
def use_optional(x: Optional[float]) -> str: | ||
return f'{x or 4.0}' | ||
|
||
pl = sl.Pipeline([use_optional]) | ||
pl.set_param_table(sl.ParamTable(int, {Param: [1.0, 2.0, 3.0]})) | ||
# It is a bit ambiguous what we would expect here: Above, we have another param | ||
# used from the table, defining the length of the series. Here, we could replicate | ||
# the output of use_optional(None) based on the `int` param table: | ||
# sl.Series(int, {0: '4.0', 1: '4.0', 2: '4.0'}) | ||
# However, we are not supporting this for non-optional dependencies either since | ||
# it is unclear whether that would bring conceptual issues or risk. | ||
with pytest.raises(sl.UnsatisfiedRequirement): | ||
pl.compute(sl.Series[int, str]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Please add tests where there is an optional and a required dependency on some value. And check that it works for any order or providers and whether the value can be provided or not.
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 added one test, not entirely sure what the other cases you are asking for are.