Skip to content
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

chore: ban no arguments in IsListOrTuple #106

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions dirty_equals/_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ class IsListOrTuple(DirtyEquals[T]):
Check that some object is a list or tuple and optionally its values match some constraints.
"""

allowed_type: Union[Type[T], Tuple[Type[List[Any]], Type[Tuple[Any, ...]]]] = (list, tuple)
allowed_type: Union[
Tuple[Type[List[Any]]], Tuple[Type[Tuple[Any, ...]]], Tuple[Type[List[Any]], Type[Tuple[Any, ...]]]
] = (list, tuple)
Comment on lines +90 to +92
Copy link
Contributor Author

@FBruzzesi FBruzzesi Sep 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a hack to be able to perform: ' or '.join(t.__name__ for t in self.allowed_type) and provide an error message specialized for each class.

The only other place in which allowed_type is if not isinstance(other, self.allowed_type): which would still be ok with the change:

- if not isinstance(other, list):
+ if not isinstance(other, (list, )):


@overload
def __init__(self, *items: Any, check_order: bool = True, length: 'LengthType' = None): ...
Expand Down Expand Up @@ -153,6 +155,15 @@ def __init__(
8. If you don't care about the first few values of a list or tuple,
you can use [`AnyThing`][dirty_equals.AnyThing] in your arguments.
"""
if not (items or positions or length is not None):
allowed_type_names = ' or '.join(t.__name__ for t in self.allowed_type)
msg = (
f'Instantiating `{self.__class__.__name__}` without any argument is ambiguous.\n'
f'- For an empty {allowed_type_names}, please specify {self.__class__.__name__}(length=0)\n'
f'- For a {allowed_type_names} of any given length, please specify {self.__class__.__name__}(length=...)'
)
raise TypeError(msg)

if positions is not None:
self.positions: Optional[Dict[int, Any]] = positions
if items:
Expand Down Expand Up @@ -226,7 +237,7 @@ class IsList(IsListOrTuple[List[Any]]):
```
"""

allowed_type = list
allowed_type = (list,)


class IsTuple(IsListOrTuple[Tuple[Any, ...]]):
Expand All @@ -247,7 +258,7 @@ class IsTuple(IsListOrTuple[Tuple[Any, ...]]):
```
"""

allowed_type = tuple
allowed_type = (tuple,)


def _length_repr(length: 'LengthType') -> Any:
Expand Down
32 changes: 25 additions & 7 deletions tests/test_list_tuple.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

import pytest

from dirty_equals import AnyThing, Contains, HasLen, IsInt, IsList, IsListOrTuple, IsNegative, IsTuple
Expand All @@ -6,11 +8,9 @@
@pytest.mark.parametrize(
'other,dirty',
[
([], IsList),
((), IsTuple),
([], IsList()),
([], IsList(length=0)),
((), IsTuple(length=0)),
([1], IsList(length=1)),
((), IsTuple()),
([1, 2, 3], IsList(1, 2, 3)),
((1, 2, 3), IsTuple(1, 2, 3)),
((1, 2, 3), IsListOrTuple(1, 2, 3)),
Expand Down Expand Up @@ -51,9 +51,9 @@ def test_dirty_equals(other, dirty):
@pytest.mark.parametrize(
'other,dirty',
[
([], IsTuple),
((), IsList),
([1], IsList),
([], IsTuple(length=0)),
((), IsList(length=0)),
([1], IsList(length=0)),
([1, 2, 3], IsTuple(1, 2, 3)),
((1, 2, 3), IsList(1, 2, 3)),
([1, 2, 3, 4], IsList(1, 2, 3)),
Expand Down Expand Up @@ -100,6 +100,24 @@ def test_wrong_length_length():
IsList(1, 2, length=(1, 2, 3))


@pytest.mark.parametrize(
'dirty,allowed_type_names',
[
(IsList, 'list'),
(IsTuple, 'tuple'),
(IsListOrTuple, 'list or tuple'),
],
)
def test_no_args(dirty: type, allowed_type_names: str):
err_msg = re.escape(
rf'Instantiating `{dirty.__name__}` without any argument is ambiguous.' + '\n'
rf'- For an empty {allowed_type_names}, please specify {dirty.__name__}(length=0)' + '\n'
rf'- For a {allowed_type_names} of any given length, please specify {dirty.__name__}(length=...)'
)
with pytest.raises(TypeError, match=err_msg):
dirty()


@pytest.mark.parametrize(
'dirty,repr_str',
[
Expand Down
Loading