From 7cfb8d787a0d041a9f5db8a88af6a32b59c5eb8a Mon Sep 17 00:00:00 2001 From: RF-Tar-Railt Date: Mon, 14 Oct 2024 02:26:40 +0800 Subject: [PATCH] :art: format --- nepattern/base.py | 37 ++++--- nepattern/context.pyi | 4 +- nepattern/core.py | 84 +++++++++++---- nepattern/main.py | 11 +- nepattern/util.py | 3 +- test.py | 240 ++++-------------------------------------- 6 files changed, 113 insertions(+), 266 deletions(-) diff --git a/nepattern/base.py b/nepattern/base.py index b0fae89..b31a076 100644 --- a/nepattern/base.py +++ b/nepattern/base.py @@ -5,16 +5,7 @@ from pathlib import Path import re import sys -from typing import ( - Any, - ForwardRef, - Generic, - Match, - TypeVar, - Union, - final, - Final -) +from typing import Any, Callable, Final, ForwardRef, Generic, Match, TypeVar, Union, final from tarina import DateParser, lang @@ -154,6 +145,7 @@ def __eq__(self, other): # pragma: no cover class SwitchPattern(Pattern[_TCase], Generic[_TCase, _TSwtich]): """匹配多种情况的表达式""" + switch: dict[_TSwtich | ellipsis, _TCase] __slots__ = ("switch",) @@ -426,10 +418,10 @@ def __eq__(self, other): # pragma: no cover WIDE_BOOLEAN = WideBoolPattern() """宽松布尔表达式,可以接受更多的布尔样式的量""" -LIST: Final[Pattern[list]] = Pattern.regex_convert(r"(\[.+?\])",list, lambda m: eval(m[1]), alias="list") -TUPLE: Final[Pattern[tuple]] = Pattern.regex_convert(r"(\(.+?\))",tuple, lambda m: eval(m[1]), alias="tuple") -SET: Final[Pattern[set]] = Pattern.regex_convert(r"(\{.+?\})",set, lambda m: eval(m[1]), alias="set") -DICT: Final[Pattern[dict]] = Pattern.regex_convert(r"(\{.+?\})",dict, lambda m: eval(m[1]), alias="dict") +LIST: Final[Pattern[list]] = Pattern.regex_convert(r"(\[.+?\])", list, lambda m: eval(m[1]), alias="list") +TUPLE: Final[Pattern[tuple]] = Pattern.regex_convert(r"(\(.+?\))", tuple, lambda m: eval(m[1]), alias="tuple") +SET: Final[Pattern[set]] = Pattern.regex_convert(r"(\{.+?\})", set, lambda m: eval(m[1]), alias="set") +DICT: Final[Pattern[dict]] = Pattern.regex_convert(r"(\{.+?\})", dict, lambda m: eval(m[1]), alias="dict") EMAIL: Final = Pattern.regex_match(r"(?:[\w\.+-]+)@(?:[\w\.-]+)\.(?:[\w\.-]+)", alias="email") """匹配邮箱地址的表达式""" @@ -527,14 +519,15 @@ def __eq__(self, other): # pragma: no cover Pattern(bytes) .accept(Union[str, Path, bytes]) .pre_validate(lambda x: isinstance(x, bytes) or (isinstance(x, (str, Path)) and Path(x).is_file())) - .convert(lambda _, x: x if isinstance(x, bytes) else x.read_bytes()) + .convert(lambda _, x: x if isinstance(x, bytes) else Path(x).read_bytes()) ) def combine( current: Pattern[_T], - previous: Pattern[Any], + previous: Pattern[Any] | None = None, alias: str | None = None, + validator: Callable[[_T], bool] | None = None, ) -> Pattern[_T]: _new = current.copy() if previous: @@ -546,6 +539,18 @@ def match(self, input_): _new.match = match.__get__(_new) if alias: _new.alias = alias + if validator: + _match = _new.match + + def match(self, input_): + res = _match(input_) + if not validator(res): + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=input_, expected=alias) + ) + return res + + _new.match = match.__get__(_new) return _new diff --git a/nepattern/context.pyi b/nepattern/context.pyi index c7c4261..0eebd78 100644 --- a/nepattern/context.pyi +++ b/nepattern/context.pyi @@ -7,9 +7,7 @@ from .core import Pattern class Patterns(UserDict[Any, Pattern]): name: str def __init__(self, name: str): ... - def set( - self, target: Pattern[Any], alias: str | None = None, cover: bool = True, no_alias=False - ): + def set(self, target: Pattern[Any], alias: str | None = None, cover: bool = True, no_alias=False): """ 增加可使用的类型转换器 diff --git a/nepattern/core.py b/nepattern/core.py index 467a6b2..74f6bcf 100644 --- a/nepattern/core.py +++ b/nepattern/core.py @@ -2,7 +2,7 @@ from copy import deepcopy import re -from typing import Any, Callable, Generic, TypeVar, overload +from typing import Any, Callable, Generic, TypeVar, Union, overload from typing_extensions import Self from tarina import Empty, generic_isinstance @@ -10,7 +10,6 @@ from .exception import MatchFailed - T = TypeVar("T") @@ -49,7 +48,11 @@ def __bool__(self): return self._value != Empty def __repr__(self): - return f"ValidateResult(value={self._value!r})" if self._value is not Empty else f"ValidateResult(error={self._error!r})" + return ( + f"ValidateResult(value={self._value!r})" + if self._value is not Empty + else f"ValidateResult(error={self._error!r})" + ) class Pattern(Generic[T]): @@ -61,21 +64,47 @@ def regex_match(pattern: str, alias: str | None = None): def _(self, x: str): mat = re.match(pattern, x) if not mat: - raise MatchFailed(lang.require("nepattern", "error.content").format(target=x, expected=pattern)) + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=x, expected=pattern) + ) return x return pat @staticmethod - def regex_convert(pattern: str, origin: type[T], fn: Callable[[re.Match], T], alias: str | None = None): - pat = Pattern(origin, alias).accept(str) - - @pat.convert - def _(self, x: str): - mat = re.match(pattern, x) - if not mat: - raise MatchFailed(lang.require("nepattern", "error.content").format(target=x, expected=pattern)) - return fn(mat) + def regex_convert( + pattern: str, + origin: type[T], + fn: Callable[[re.Match], T], + alias: str | None = None, + allow_origin: bool = False, + ): + pat = Pattern(origin, alias) + if allow_origin: + pat.accept(Union[str, origin]) + + @pat.convert + def _(self, x): + if isinstance(x, origin): + return x + mat = re.match(pattern, x) + if not mat: + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=x, expected=pattern) + ) + return fn(mat) + + else: + pat.accept(str) + + @pat.convert + def _(self, x: str): + mat = re.match(pattern, x) + if not mat: + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=x, expected=pattern) + ) + return fn(mat) return pat @@ -87,12 +116,10 @@ def on(obj: T): return DirectPattern(obj) @overload - def __init__(self, origin: type[T], alias: str | None = None): - ... + def __init__(self, origin: type[T], alias: str | None = None): ... @overload - def __init__(self: Pattern[Any], *, alias: str | None = None): - ... + def __init__(self: Pattern[Any], *, alias: str | None = None): ... def __init__(self, origin: type[T] | None = None, alias: str | None = None): self.origin: type[T] = origin or Any # type: ignore @@ -103,6 +130,9 @@ def __init__(self, origin: type[T] | None = None, alias: str | None = None): self._pre_validator = None self._converter = None + def __init_subclass__(cls, **kwargs): + cls.__hash__ = Pattern.__hash__ + def accept(self, input_type: Any): self._accepts = input_type return self @@ -121,13 +151,19 @@ def convert(self, func: Callable[[Self, Any], T]): def match(self, input_: Any) -> T: if not generic_isinstance(input_, self._accepts): - raise MatchFailed(lang.require("nepattern", "error.type").format(target=input_, expected=self._accepts)) + raise MatchFailed( + lang.require("nepattern", "error.type").format(target=input_, expected=self._accepts) + ) if self._pre_validator and not self._pre_validator(input_): - raise MatchFailed(lang.require("nepattern", "error.content").format(target=input_, expected=self.origin)) + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=input_, expected=self.origin) + ) if self._converter: input_ = self._converter(self, input_) if self._post_validator and not self._post_validator(input_): - raise MatchFailed(lang.require("nepattern", "error.content").format(target=input_, expected=self.origin)) + raise MatchFailed( + lang.require("nepattern", "error.content").format(target=input_, expected=self.origin) + ) return input_ def execute(self, input_: Any) -> ValidateResult[T]: @@ -144,7 +180,7 @@ def __str__(self): return f"{self._accepts.__name__} -> {self.origin.__name__}" def __repr__(self): - return f"Pattern({self.origin.__name__}, {self.alias})" + return f"{self.__class__.__name__}({self.origin.__name__}, {self.alias})" def copy(self) -> Self: return deepcopy(self) @@ -161,3 +197,9 @@ def __matmul__(self, other) -> Self: # pragma: no cover if isinstance(other, str): self.alias = other return self + + def __hash__(self): + return id((self.origin, self.alias, self._accepts, self._converter)) + + def __eq__(self, other): + return isinstance(other, Pattern) and self.__hash__() == other.__hash__() diff --git a/nepattern/main.py b/nepattern/main.py index f0c866e..20a5acc 100644 --- a/nepattern/main.py +++ b/nepattern/main.py @@ -24,6 +24,7 @@ RegexPattern, SwitchPattern, UnionPattern, + combine, ) from .context import all_patterns from .core import Pattern @@ -38,12 +39,12 @@ def _generic_parser(item: GenericAlias, extra: str) -> Pattern: # type: ignore org, *meta = get_args(item) if not isinstance(_o := parser(org, extra), Pattern): # type: ignore # pragma: no cover raise TypeError(_o) - _arg = deepcopy(_o) - _arg.alias = al[-1] if (al := [i for i in meta if isinstance(i, str)]) else _arg.alias validators = [i for i in meta if callable(i)] - if validators: - _arg.post_validate(lambda x: all(i(x) for i in validators)) - return _arg + return combine( + _o, + alias=al[-1] if (al := [i for i in meta if isinstance(i, str)]) else _o.alias, + validator=(lambda x: all(i(x) for i in validators)) if validators else None, + ) if origin in _Contents: _args = {parser(t, extra) for t in get_args(item)} return (_args.pop() if len(_args) == 1 else UnionPattern(*_args)) if _args else ANY diff --git a/nepattern/util.py b/nepattern/util.py index ce7c5ba..6859037 100644 --- a/nepattern/util.py +++ b/nepattern/util.py @@ -2,13 +2,12 @@ import dataclasses import sys +from types import GenericAlias as CGenericAlias # noqa: F401 from typing import TYPE_CHECKING, List, Pattern, Union from typing_extensions import TypeAlias from .i18n import lang as lang # noqa: F401 -from types import GenericAlias as CGenericAlias # noqa: F401 - if sys.version_info >= (3, 10): # pragma: no cover from types import UnionType as CUnionType # noqa: F401 else: # pragma: no cover diff --git a/test.py b/test.py index e506b2b..c6910dc 100644 --- a/test.py +++ b/test.py @@ -134,9 +134,7 @@ def test_pattern_regex(): def test_pattern_regex_convert(): """测试 BasePattern 的正则转换模式, 正则匹配成功后再进行类型转换""" pat4 = Pattern.regex_convert( - r"\[at:(\d+)\]", - int, - lambda m: res if (res := int(x[1])) < 1000000 else None, + r"\[at:(\d+)\]", int, lambda m: res if (res := int(m[1])) < 1000000 else None, allow_origin=True ) assert pat4.execute("[at:123456]").value() == 123456 assert pat4.execute("[at:abcdef]").failed @@ -170,56 +168,14 @@ def convert(self, content): def test_pattern_accepts(): """测试 BasePattern 的输入类型筛选, 不在范围内的类型视为非法""" - pat6 = BasePattern( - mode=MatchMode.TYPE_CONVERT, - origin=str, - converter=lambda _, x: x.decode(), - accepts=bytes, - ) + + pat6 = Pattern(str).accept(bytes).convert(lambda _, x: x.decode()) assert pat6.execute(b"123").value() == "123" assert pat6.execute(123).failed - pat6_1 = BasePattern(mode=MatchMode.KEEP, accepts=Union[int, float]) + pat6_1 = Pattern().accept(Union[int, float]) assert pat6_1.execute(123).value() == 123 assert pat6_1.execute("123").failed print(pat6, pat6_1) - pat6_2 = BasePattern(mode=MatchMode.KEEP, accepts=bytes, addition_accepts=NUMBER) - assert pat6_2.execute(123).value() == 123 - assert pat6_2.execute(123.123).value() == 123.123 - assert pat6_2.execute(b"123").value() == b"123" - print(pat6_2) - pat6_3 = BasePattern(mode=MatchMode.KEEP, addition_accepts=INTEGER | BOOLEAN) - assert pat6_3.execute(123).value() == 123 - assert pat6_3.execute(True).value() is True - assert pat6_3.execute(b"123").value() == b"123" - assert pat6_3.execute([]).failed - - -def test_pattern_previous(): - """测试 BasePattern 的前置表达式, 在传入的对象类型不正确时会尝试用前置表达式进行预处理""" - - class A: - def __repr__(self): - return "123" - - pat7 = BasePattern(mode=MatchMode.TYPE_CONVERT, origin=str, converter=lambda _, x: f"abc[{x}]", accepts=A) - pat7_1 = BasePattern( - r"abc\[(\d+)\]", - mode=MatchMode.REGEX_CONVERT, - origin=int, - converter=lambda self, x: self.origin(x[1]), - previous=pat7, - ) - assert pat7_1.execute("abc[123]").value() == 123 - assert pat7_1.execute(A()).value() == 123 - pat7_2 = BasePattern(mode=MatchMode.TYPE_CONVERT, origin=str) - pat7_3 = BasePattern( - mode=MatchMode.TYPE_CONVERT, - origin=int, - accepts=Union[int, float], # type: ignore - previous=pat7_2, # type: ignore - ) - assert pat7_3.execute("123").failed - print(pat7, pat7_1) def test_pattern_anti(): @@ -234,19 +190,12 @@ def test_pattern_anti(): def test_pattern_validator(): """测试 BasePattern 的匹配后验证器, 会对匹配结果进行验证""" - pat9 = BasePattern(mode=MatchMode.KEEP, accepts=int, validators=[lambda x: x > 0]) + pat9 = Pattern(int).accept(int).post_validate(lambda x: x > 0) assert pat9.execute(23).value() == 23 assert pat9.execute(-23).failed print(pat9) -def test_pattern_default(): - pat10 = Pattern(int) - assert pat10.execute("123", 123).or_default - assert pat10.execute("123", 123).value() == 123 - assert AntiPattern(pat10).execute(123, "123").value() == "123" - - def test_parser(): from typing import Literal, Protocol, Type, TypeVar from typing_extensions import Annotated @@ -255,13 +204,13 @@ def test_parser(): assert pat11.execute(-321).success pat11_1 = parser(123) print(pat11, pat11_1) - pat11_2 = BasePattern.to(int) + pat11_2 = parser(int) assert pat11_2 == pat11 assert isinstance(parser(Literal["a", "b"]), UnionPattern) assert parser(Type[int]).origin is type - assert parser(complex) == Pattern(complex) + assert parser(complex) != Pattern(complex) assert isinstance(parser("a|b|c"), UnionPattern) - assert isinstance(parser("re:a|b|c"), BasePattern) + assert isinstance(parser("re:a|b|c"), Pattern) assert parser([1, 2, 3]).execute(1).success assert parser({"a": 1, "b": 2}).execute("a").value() == 1 @@ -275,7 +224,7 @@ def my_func(x: int) -> str: pat11_3 = parser(my_func) assert pat11_3.origin == str - assert pat11_3._accepts == (int,) + assert pat11_3._accepts == int assert parser(complex, extra="ignore") == ANY @@ -313,57 +262,12 @@ def test_union_pattern(): pat12_1 = parser(Optional[str]) assert pat12_1.execute("123").success assert pat12_1.execute(None).success - pat12_2 = UnionPattern(["abc", "efg"]) + pat12_2 = UnionPattern("abc", "efg") assert pat12_2.execute("abc").success assert pat12_2.execute("bca").failed print(pat12, pat12_1, pat12_2) - pat12_3 = UnionPattern._(List[bool], int) - pat12_4 = pat12_2 | pat12_3 - print(pat12_3, pat12_4) - - -def test_seq_pattern(): - from typing import List, Set, Tuple - - pat13 = parser(List[int]) - pat13_1 = parser(Tuple[int, int]) - pat13_2 = parser(Set[int]) - assert pat13.execute("[1,2,3]").value() == [1, 2, 3] - assert pat13.execute([1, 2, 3]).success - assert pat13_1.execute("(1,2,3)").value() == (1, 2, 3) - assert pat13_2.execute("{1,2,a}").failed - print(pat13, pat13_1, pat13_2) - try: - SequencePattern(dict, Pattern(int)) # type: ignore - except ValueError as e: - print(e) - pat13_3 = SequencePattern(list, INTEGER, IterMode.PRE) - assert pat13_3.execute([1, 2, 3]).success - assert pat13_3.execute("[1, 2, a]").value() == [1, 2] - pat13_4 = SequencePattern(list, INTEGER, IterMode.SUF) - assert pat13_4.execute([1, 2, 3]).success - assert pat13_4.execute("[1, 2, a]").failed - assert pat13_4.execute("[a, 2, 3]").value() == [2, 3] - - -def test_map_pattern(): - from typing import Dict - - pat14 = parser(Dict[str, int]) - assert pat14.execute("{a:1,b:2}").value() == {"a": 1, "b": 2} - assert pat14.execute("{a:a, b:2}").failed - assert pat14.execute({"a": 1, "b": 2}).success - pat14_1 = parser(Dict[int, int]) - assert pat14_1.execute({"a": 1, "b": 2}).failed - print(pat14) - pat14_2 = MappingPattern(INTEGER, BOOLEAN, IterMode.PRE) - assert pat14_2.execute({1: True, 2: False}).success - assert pat14_2.execute({1: True, 2: None}).value() == {1: True} - assert pat14_2.execute({0: None, 1: True, 2: False}).failed - pat14_3 = MappingPattern(INTEGER, BOOLEAN, IterMode.SUF) - assert pat14_3.execute({1: True, 2: False}).success - assert pat14_3.execute({0: None, 1: False, 2: True}).value() == {1: False, 2: True} - assert pat14_3.execute({0: False, 1: True, 2: None}).failed + pat12_3 = UnionPattern.of(List[bool], int) + print(pat12_3) def test_converters(): @@ -395,7 +299,7 @@ def test_converters(): def test_converter_method(): temp = create_local_patterns("test", set_current=False) - temp.set(Pattern(complex)) + temp.set(Pattern(complex, "complex")) assert temp["complex"] temp.set(Pattern(complex), alias="abc") assert temp["abc"] @@ -406,8 +310,8 @@ def test_converter_method(): assert temp["c"] temp.remove(complex, alias="complex") assert not temp.get("complex") - temp.set(BasePattern(mode=MatchMode.KEEP, alias="a")) - temp.set(BasePattern(mode=MatchMode.KEEP, accepts=int, alias="b"), alias="a", cover=False) + temp.set(Pattern(alias="a")) + temp.set(Pattern(alias="b").accept(int), alias="a", cover=False) temp.remove(int, alias="a") assert temp["a"] temp.remove(int) @@ -416,19 +320,6 @@ def test_converter_method(): assert not temp.get(int) -def test_dunder(): - pat17 = Pattern(float) - assert ("test_float" @ pat17).alias == "test_float" - assert pat17.execute(1.33).step(str) == pat17.execute(1.33) >> str == "1.33" - assert (pat17.execute(1.33) >> 1).value() == 1.33 - assert not "1.33" >> pat17 - assert pat17.execute(1.33) >> bool - assert Pattern(int).execute(1).step(lambda x: x + 2) == 3 - pat17_1 = BasePattern(r"@(\d+)", MatchMode.REGEX_CONVERT, str, lambda _, x: x[0][1:]) - pat17_2: BasePattern[int, Any, Any] = parser(int) - assert ("@123456" >> pat17_1 >> pat17_2).value() == 123456 - - def test_regex_pattern(): from re import Match, compile @@ -492,11 +383,9 @@ def test_direct(): assert pat20.execute("abc").value() == "abc" assert pat20.execute("abcd").failed assert pat20.execute(123).failed - assert pat20.execute("123", 123).value() == 123 pat20_1 = DirectPattern(123) assert pat20_1.execute(123).value() == 123 assert pat20_1.execute("123").failed - assert pat20_1.execute(123, "123").value() == 123 assert pat20_1.match(123) == 123 try: pat20_1.match("123") @@ -505,12 +394,11 @@ def test_direct(): pat21 = DirectTypePattern(int) assert pat21.execute(123).value() == 123 assert pat21.execute("123").failed - assert pat21.execute(123, "123").value() == 123 assert pat21.match(123) == 123 assert pat21.match(456) == 456 -def test_forward_red(): +def test_forward_ref(): from typing import ForwardRef pat21 = parser(ForwardRef("int")) @@ -520,119 +408,33 @@ def test_forward_red(): def test_value_operate(): - pat22 = BasePattern( - mode=MatchMode.VALUE_OPERATE, - origin=int, - converter=lambda _, x: x + 1, + pat22 = Pattern(origin=int).convert( + lambda _, x: x + 1, ) assert pat22.execute(123).value() == 124 assert pat22.execute("123").failed assert pat22.execute(123.0).failed - pat22_1p = BasePattern( - mode=MatchMode.TYPE_CONVERT, - origin=int, - accepts=Union[str, float], - converter=lambda _, x: int(x), - ) - - pat22_1 = BasePattern( - mode=MatchMode.VALUE_OPERATE, - origin=int, - converter=lambda _, x: x + 1, - previous=pat22_1p, - ) - assert pat22_1.execute(123).value() == 124 - assert pat22_1.execute("123").value() == 124 - assert pat22_1.execute(123.0).value() == 124 - assert pat22_1.execute("123.0").failed - assert pat22_1.execute([]).failed - def test_eq(): assert parser(123) == Pattern.on(123) - assert BasePattern.to(None) == NONE + assert parser(None) == NONE assert parser(Pattern(int)) == Pattern(int) assert parser(str) == STRING def test_combine(): - pre = BasePattern(mode=MatchMode.VALUE_OPERATE, origin=str, converter=lambda _, x: x.replace(",", "_")) + pre = Pattern(origin=str).convert(lambda _, x: x.replace(",", "_")) pat23 = combine(INTEGER, pre) assert pat23.execute("123,456").value() == 123456 assert pat23.execute("1,000,000").value() == 1_000_000 - pat23_1 = combine(INTEGER, alias="0~10", validators=[lambda x: 0 <= x <= 10]) + pat23_1 = combine(INTEGER, alias="0~10", validator=lambda x: 0 <= x <= 10) assert pat23_1.execute(5).value() == 5 assert pat23_1.execute(11).failed assert str(pat23_1) == "0~10" -def test_funcs(): - from dataclasses import dataclass - - pat = BasePattern( - mode=MatchMode.TYPE_CONVERT, - accepts=str, - origin=list[str], - alias="chars", - converter=lambda _, x: list(x), - ) - assert pat.execute("abcde").value() == ["a", "b", "c", "d", "e"] - - pat24 = Index(pat, 2) - assert pat24.execute("abcde").value() == "c" - - pat24_1 = Slice(pat, 1, 3) - assert pat24_1.execute("abcde").value() == ["b", "c"] - - pat24_2 = Map(pat, lambda x: x.upper(), "str.upper") - assert pat24_2.execute("abcde").value() == ["A", "B", "C", "D", "E"] - - pat24_3 = Filter(pat, lambda x: x in "aeiou", "vowels") - assert pat24_3.execute("abcde").value() == ["a", "e"] - - pat24_4 = Filter(Map(pat, lambda x: x.upper(), "str.upper"), lambda x: x in "AEIOU", "vowels") - assert pat24_4.execute("abcde").value() == ["A", "E"] - - pat24_5 = Reduce(pat24_2, lambda x, y: x + y, funcname="add") - assert pat24_5.execute("abcde").value() == "ABCDE" - - pat24_6 = Join(pat, sep="-") - assert pat24_6.execute("abcde").value() == "a-b-c-d-e" - - pat24_7 = Upper(pat24_6) - assert pat24_7.execute("abcde").value() == "A-B-C-D-E" - - pat24_8 = Lower(pat24_7) - assert pat24_8.execute("abcde").value() == "a-b-c-d-e" - - pat24_9 = Sum(Map(pat, ord)) - assert pat24_9.execute("abcde").value() == 495 - - pat24_10 = Step(pat, len) - assert pat24_10.execute("abcde").value() == 5 - - pat24_11 = Step(pat, lambda x: x.count("a"), funcname="count_a") - assert pat24_11.execute("abcde").value() == 1 - - @dataclass - class Test: - a: int - b: str - - pat1 = Pattern(Test) - obj = Test(123, "abc") - assert pat1.execute(obj).value() == obj - - pat24_12 = Dot(pat1, int, "a") - assert pat24_12.execute(obj).value() == 123 - - pat2 = Pattern.on({"a": 123, "b": "abc"}) - pat24_13 = GetItem(pat2, int, "a") - assert pat24_13.execute({"a": 123, "b": "abc"}).value() == 123 - - if __name__ == "__main__": import pytest