diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 1a8a31c..40fe808 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -2,7 +2,7 @@ name: Question or consultation about: Ask anything about this project title: '' -labels: guestion +labels: question assignees: pomponchik --- diff --git a/.ruff.toml b/.ruff.toml deleted file mode 100644 index 353bb74..0000000 --- a/.ruff.toml +++ /dev/null @@ -1,4 +0,0 @@ -lint.ignore = ['E501', 'E712'] - -[format] -quote-style = "single" diff --git a/README.md b/README.md index 813726c..4022b5b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ Python type checking tools are usually very complex. In this case, we have throw - [**String deserialization**](#string-deserialization) - ## Why? It's been a long time since static type checking tools like [`mypy`](https://github.com/python/mypy) for `Python` have been available, and they've become very complex. The typing system has also become noticeably more complicated, providing us with more and more new types of annotations, new syntax and other tools. It seems that `Python` devs procrastinate endlessly, postponing all the really important [`CPyhton`](https://github.com/python/cpython) improvements in order to add more garbage to [`typing`](https://docs.python.org/3/library/typing.html). diff --git a/pyproject.toml b/pyproject.toml index a10df27..545e148 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "simtypes" -version = "0.0.11" +version = "0.0.12" authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }] description = 'Type checking in runtime without stupid games' readme = "README.md" diff --git a/simtypes/__init__.py b/simtypes/__init__.py index 036c6dc..ff84386 100644 --- a/simtypes/__init__.py +++ b/simtypes/__init__.py @@ -1,4 +1,6 @@ -from simtypes.check import check as check # noqa: F401 -from simtypes.from_string import from_string as from_string # noqa: F401 -from simtypes.types.ints.natural import NaturalNumber as NaturalNumber # noqa: F401 -from simtypes.types.ints.non_negative import NonNegativeInt as NonNegativeInt # noqa: F401 +from simtypes.check import check as check +from simtypes.from_string import from_string as from_string +from simtypes.types.ints.natural import NaturalNumber as NaturalNumber +from simtypes.types.ints.non_negative import ( + NonNegativeInt as NonNegativeInt, +) diff --git a/simtypes/check.py b/simtypes/check.py index 16f5929..c74ab26 100644 --- a/simtypes/check.py +++ b/simtypes/check.py @@ -1,5 +1,5 @@ from inspect import isclass -from unittest.mock import Mock, MagicMock +from unittest.mock import MagicMock, Mock try: from types import UnionType # type: ignore[attr-defined, unused-ignore] @@ -11,24 +11,21 @@ except ImportError: # pragma: no cover from typing_extensions import TypeIs -from typing import List, Type, Union, Any, get_args, get_origin +from typing import Any, List, Type, Union, get_args, get_origin from denial import InnerNoneType from simtypes.typing import ExpectedType -def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists_are_tuples: bool = False, pass_mocks: bool = True) -> TypeIs[ExpectedType]: - if type_hint is Any: # type: ignore[comparison-overlap] +def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists_are_tuples: bool = False, pass_mocks: bool = True) -> TypeIs[ExpectedType]: # noqa: C901, PLR0911, PLR0912 + if type_hint is Any or (isinstance(value, (Mock, MagicMock)) and pass_mocks): # type: ignore[comparison-overlap] return True - elif (isinstance(value, Mock) or isinstance(value, MagicMock)) and pass_mocks: - return True - - elif type_hint is None: + if type_hint is None: return value is None - elif isinstance(type_hint, InnerNoneType): + if isinstance(type_hint, InnerNoneType): return type_hint == value origin_type = get_origin(type_hint) @@ -36,7 +33,7 @@ def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists if origin_type is Union or origin_type is UnionType: return any(check(value, argument, strict=strict, lists_are_tuples=lists_are_tuples) for argument in get_args(type_hint)) - elif origin_type is list and strict: + if origin_type is list and strict: if not isinstance(value, list): return False arguments = get_args(type_hint) @@ -44,7 +41,7 @@ def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists return True return all(check(subvalue, arguments[0], strict=strict, lists_are_tuples=lists_are_tuples) for subvalue in value) - elif origin_type is dict and strict: + if origin_type is dict and strict: if not isinstance(value, dict): return False arguments = get_args(type_hint) @@ -52,7 +49,7 @@ def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists return True return all(check(key, arguments[0], strict=strict, lists_are_tuples=lists_are_tuples) and check(subvalue, arguments[1], strict=strict, lists_are_tuples=lists_are_tuples) for key, subvalue in value.items()) - elif origin_type is tuple and strict: + if origin_type is tuple and strict: types_to_check: List[Union[Type[list], Type[tuple]]] = [tuple] if not lists_are_tuples else [tuple, list] # type: ignore[type-arg] if all(not isinstance(value, x) for x in types_to_check): return False @@ -70,14 +67,13 @@ def check(value: Any, type_hint: Type[ExpectedType], strict: bool = False, lists return all(check(subvalue, expected_subtype, strict=strict, lists_are_tuples=lists_are_tuples) for subvalue, expected_subtype in zip(value, arguments)) - else: - if origin_type is not None: - return isinstance(value, origin_type) + if origin_type is not None: + return isinstance(value, origin_type) - if not isclass(type_hint): - raise ValueError('Type must be a valid type object.') + if not isclass(type_hint): + raise ValueError('Type must be a valid type object.') - if type_hint is tuple and lists_are_tuples: - return isinstance(value, tuple) or isinstance(value, list) # pragma: no cover + if type_hint is tuple and lists_are_tuples: + return isinstance(value, (tuple, list)) # pragma: no cover - return isinstance(value, type_hint) + return isinstance(value, type_hint) diff --git a/simtypes/from_string.py b/simtypes/from_string.py index a015cf0..0b81991 100644 --- a/simtypes/from_string.py +++ b/simtypes/from_string.py @@ -1,33 +1,32 @@ -from typing import List, Tuple, Dict, Type, Optional, Union, Any, get_origin, get_args -from json import loads, JSONDecodeError -from inspect import isclass -from datetime import datetime, date from collections.abc import Hashable +from datetime import date, datetime +from inspect import isclass +from json import JSONDecodeError, loads +from typing import Any, Dict, List, Optional, Tuple, Type, Union, get_args, get_origin from simtypes import check from simtypes.typing import ExpectedType -def convert_single_value(value: str, expected_type: Type[ExpectedType]) -> ExpectedType: +def convert_single_value(value: str, expected_type: Type[ExpectedType]) -> ExpectedType: # noqa: PLR0912, PLR0911, C901 if expected_type is str: return value # type: ignore[return-value] - elif expected_type is bool: + if expected_type is bool: if value in ('True', 'true', 'yes'): return True # type: ignore[return-value] - elif value in ('False', 'false', 'no'): + if value in ('False', 'false', 'no'): return False # type: ignore[return-value] - else: - raise TypeError(f'The string "{value}" cannot be interpreted as a boolean value.') + raise TypeError(f'The string "{value}" cannot be interpreted as a boolean value.') - elif expected_type is int: + if expected_type is int: try: return int(value) # type: ignore[return-value] except ValueError as e: raise TypeError(f'The string "{value}" cannot be interpreted as an integer.') from e elif expected_type is float: - if value == '∞' or value == '+∞': + if value in {'∞', '+∞'}: value = 'inf' elif value == '-∞': value = '-inf' @@ -87,7 +86,7 @@ def fix_lists(collection: List[Any], type_hint_arguments: Tuple[Any, ...]) -> Op return result -def fix_tuples(collection: List[Any], type_hint_arguments: Tuple[Any, ...]) -> Optional[Tuple[Any, ...]]: +def fix_tuples(collection: List[Any], type_hint_arguments: Tuple[Any, ...]) -> Optional[Tuple[Any, ...]]: # noqa: PLR0912, PLR0911, C901 if not isinstance(collection, list): return None @@ -158,7 +157,7 @@ def fix_dicts(collection: List[Any], type_hint_arguments: Tuple[Any, ...]) -> Op pair_result = {} for name, meta in pair.items(): - element, type_hint = meta + element, type_hint = meta # noqa: PLW2901 origin_type = get_origin(type_hint) type_hint_arguments = get_args(type_hint) if any(x in (dict, list, tuple) for x in (type_hint, origin_type)): @@ -221,7 +220,6 @@ def from_string(value: str, expected_type: Type[ExpectedType]) -> ExpectedType: if check(result, expected_type, strict=True): # type: ignore[operator] return result # type: ignore[no-any-return] - else: - raise error + raise error return convert_single_value(value, expected_type) diff --git a/simtypes/types/strings/email.py b/simtypes/types/strings/email.py deleted file mode 100644 index e69de29..0000000 diff --git a/simtypes/typing.py b/simtypes/typing.py index 38611ec..61b7519 100644 --- a/simtypes/typing.py +++ b/simtypes/typing.py @@ -1,4 +1,3 @@ from typing import TypeVar - ExpectedType = TypeVar('ExpectedType') diff --git a/tests/units/conftest.py b/tests/units/conftest.py index 1969121..398703f 100644 --- a/tests/units/conftest.py +++ b/tests/units/conftest.py @@ -1,14 +1,9 @@ import sys -from typing import Tuple, List, Set, Dict, Union, Optional +from typing import Dict, List, Optional, Set, Tuple, Union import pytest -@pytest.fixture(params=[1, 2]) -def new_style(request): - return request.param - - @pytest.fixture(params=[Dict, dict]) def dict_type(request): return request.param diff --git a/tests/units/test_check.py b/tests/units/test_check.py index 141bb5c..08e9b3a 100644 --- a/tests/units/test_check.py +++ b/tests/units/test_check.py @@ -1,17 +1,17 @@ import sys -from unittest.mock import Mock, MagicMock +from unittest.mock import MagicMock, Mock try: from types import NoneType # type: ignore[attr-defined] except ImportError: NoneType = type(None) # type: ignore[misc] -from typing import Optional, Any, Union from collections.abc import Sequence +from typing import Any, Optional, Union import pytest -from full_match import match from denial import InnerNone, InnerNoneType, SentinelType +from full_match import match from simtypes import check @@ -132,7 +132,7 @@ def test_bool_is_int(make_optional, make_union): assert check(False, make_optional(make_union(int, str))) -def test_optional(new_style, tuple_type, list_type, make_optional): +def test_optional(tuple_type, list_type, make_optional): assert check(None, make_optional(int)) assert check(1, make_optional(int)) assert check(0, make_optional(int)) @@ -185,11 +185,11 @@ def test_optional_union(make_union, make_optional, tuple_type): @pytest.mark.parametrize( - ['addictional_parameters'], + 'addictional_parameters', [ - ({},), - ({'strict': True},), - ({'strict': False},), + {}, + {'strict': True}, + {'strict': False}, ], ) def test_list_without_arguments(list_type, addictional_parameters): @@ -210,11 +210,11 @@ def test_list_without_arguments(list_type, addictional_parameters): @pytest.mark.parametrize( - ['addictional_parameters'], + 'addictional_parameters', [ - ({},), - ({'strict': True},), - ({'strict': False},), + {}, + {'strict': True}, + {'strict': False}, ], ) def test_tuple_without_arguments(tuple_type, addictional_parameters): @@ -240,11 +240,11 @@ def test_tuple_without_arguments(tuple_type, addictional_parameters): @pytest.mark.parametrize( - ['addictional_parameters'], + 'addictional_parameters', [ - ({},), - ({'strict': True},), - ({'strict': False},), + {}, + {'strict': True}, + {'strict': False}, ], ) def test_set_without_arguments(set_type, addictional_parameters): @@ -265,11 +265,11 @@ def test_set_without_arguments(set_type, addictional_parameters): @pytest.mark.parametrize( - ['addictional_parameters'], + 'addictional_parameters', [ - ({},), - ({'strict': True},), - ({'strict': False},), + {}, + {'strict': True}, + {'strict': False}, ], ) def test_dict_without_arguments(dict_type, addictional_parameters): @@ -449,17 +449,17 @@ def test_lists_are_tuples_flag_is_true_in_strict_mode(subscribable_tuple_type, s @pytest.mark.parametrize( - ['strict_mode'], + 'strict_mode', [ - (False,), - (True,), + False, + True, ], ) @pytest.mark.parametrize( - ['addictional_parameters'], + 'addictional_parameters', [ - ({'pass_mocks': True},), - ({},), + {'pass_mocks': True}, + {}, ], ) def test_pass_mocks_when_its_on(strict_mode, list_type, addictional_parameters): @@ -476,10 +476,10 @@ def test_pass_mocks_when_its_on(strict_mode, list_type, addictional_parameters): @pytest.mark.parametrize( - ['strict_mode'], + 'strict_mode', [ - (False,), - (True,), + False, + True, ], ) def test_pass_mocks_when_its_off(strict_mode, list_type): @@ -496,10 +496,10 @@ def test_pass_mocks_when_its_off(strict_mode, list_type): @pytest.mark.parametrize( - ['strict_mode'], + 'strict_mode', [ - (False,), - (True,), + False, + True, ], ) def test_denial_sentinel(strict_mode): @@ -514,10 +514,10 @@ def test_denial_sentinel(strict_mode): @pytest.mark.parametrize( - ['strict_mode'], + 'strict_mode', [ - (False,), - (True,), + False, + True, ], ) def test_denial_innernonetype(strict_mode): @@ -532,10 +532,10 @@ def test_denial_innernonetype(strict_mode): @pytest.mark.parametrize( - ['strict_mode'], + 'strict_mode', [ - (False,), - (True,), + False, + True, ], ) def test_denial_innernone(strict_mode): diff --git a/tests/units/test_from_string.py b/tests/units/test_from_string.py index 72d9544..820fe6c 100644 --- a/tests/units/test_from_string.py +++ b/tests/units/test_from_string.py @@ -1,7 +1,7 @@ -from math import inf, isnan -from typing import Any from datetime import date, datetime from json import dumps +from math import inf, isnan +from typing import Any import pytest from full_match import match @@ -299,11 +299,11 @@ def test_get_dict_value(dict_type, subscribable_list_type, subscribable_dict_typ @pytest.mark.parametrize( - ['string'], + 'string', [ - ('{"lol": "kek"}',), - ('1',), - ('kek',), + '{"lol": "kek"}', + '1', + 'kek', ], ) def test_get_any(string): @@ -350,7 +350,7 @@ def test_deserialize_subscribable_collections_with_dates(subscribable_list_type, assert from_string(dumps({isoformatted_date: isoformatted_date}), subscribable_dict_type[str, date]) == {isoformatted_date: date.fromisoformat(isoformatted_date)} -def test_wrong_collection_content(subscribable_list_type, subscribable_tuple_type, subscribable_dict_type, dict_type, list_type, tuple_type): +def test_wrong_collection_content(subscribable_list_type, subscribable_tuple_type, subscribable_dict_type, dict_type, list_type, tuple_type): # noqa: PLR0915, PLR0913 with pytest.raises(TypeError, match=match('The string "[123]" cannot be interpreted as a list of the specified format.')): from_string(dumps([123]), subscribable_list_type[date])