Skip to content

Commit

Permalink
Regexp functions support (#13)
Browse files Browse the repository at this point in the history
* feat!: other regexp functions support

* feat: add non-breakable regex

* chore: add tests for regex

* Expand iterable input types in magic_filter methods

Modified the 'in_' and 'not_in' methods in 'magic_filter/magic.py' to accept both 'Container' and 'MagicT' types as iterable. This change provides more flexibility in specifying iterables in these methods and broadens their potential usage.

* fix: regexp typo

---------

Co-authored-by: JRoot Junior <[email protected]>
  • Loading branch information
Olegt0rr and JrooTJunior authored Aug 17, 2023
1 parent 1808f90 commit 9592bbc
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 8 deletions.
3 changes: 2 additions & 1 deletion magic_filter/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from . import operations
from .attrdict import AttrDict
from .magic import MagicFilter, MagicT
from .magic import MagicFilter, MagicT, RegexpMode

__all__ = (
"__version__",
"operations",
"MagicFilter",
"MagicT",
"RegexpMode",
"F",
"AttrDict",
)
Expand Down
4 changes: 4 additions & 0 deletions magic_filter/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ class SwitchModeToAny(SwitchMode):

class RejectOperations(MagicFilterException):
pass


class ParamsConflict(MagicFilterException):
pass
40 changes: 36 additions & 4 deletions magic_filter/magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
import re
from functools import wraps
from typing import Any, Callable, Container, Optional, Pattern, Tuple, Type, TypeVar, Union
from warnings import warn

from magic_filter.exceptions import RejectOperations, SwitchModeToAll, SwitchModeToAny
from magic_filter.exceptions import (
ParamsConflict,
RejectOperations,
SwitchModeToAll,
SwitchModeToAny,
)
from magic_filter.operations import (
BaseOperation,
CallOperation,
Expand All @@ -24,6 +30,14 @@
MagicT = TypeVar("MagicT", bound="MagicFilter")


class RegexpMode:
SEARCH = "search"
MATCH = "match"
FINDALL = "findall"
FINDITER = "finditer"
FULLMATCH = "fullmatch"


class MagicFilter:
__slots__ = ("_operations",)

Expand Down Expand Up @@ -240,13 +254,31 @@ def regexp(
self: MagicT,
pattern: Union[str, Pattern[str]],
*,
search: bool = False,
mode: Optional[str] = None,
search: Optional[bool] = None,
flags: Union[int, re.RegexFlag] = 0,
) -> MagicT:

if search is not None:
warn(
"Param 'search' is deprecated, use 'mode' instead.",
DeprecationWarning,
)

if mode is not None:
msg = "Can't pass both 'search' and 'mode' params."
raise ParamsConflict(msg)

mode = RegexpMode.SEARCH if search else RegexpMode.MATCH

if mode is None:
mode = RegexpMode.MATCH

if isinstance(pattern, str):
pattern = re.compile(pattern, flags=flags)
regexp_mode = pattern.search if search else pattern.match
return self._extend(FunctionOperation(regexp_mode))

regexp_func = getattr(pattern, mode)
return self._extend(FunctionOperation(regexp_func))

def func(self: MagicT, func: Callable[[Any], Any], *args: Any, **kwargs: Any) -> MagicT:
return self._extend(FunctionOperation(func, *args, **kwargs))
Expand Down
84 changes: 81 additions & 3 deletions tests/test_magic.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import warnings
from collections import namedtuple
from typing import Any, NamedTuple, Optional

import pytest

from magic_filter import F, MagicFilter
from magic_filter import F, MagicFilter, RegexpMode
from magic_filter.exceptions import ParamsConflict

Job = namedtuple("Job", ["place", "salary", "position"])

Expand Down Expand Up @@ -84,8 +86,6 @@ class TestMagicFilter:
F.age.in_(range(15, 40)),
F.job.place.in_({"New York", "WDC"}),
F.age.not_in(range(40, 100)),
F.about.regexp(r"Gonna .+"),
F.about.regexp(r".+"),
F.about.contains("Factory"),
F.job.place.lower().contains("n"),
F.job.place.upper().contains("N"),
Expand Down Expand Up @@ -159,3 +159,81 @@ def test_extract_operation(self):
def test_bool(self):
case = F.foo.bar.baz
assert bool(case) is True


class TestMagicRegexpFilter:
@pytest.mark.parametrize(
"case,result",
[
(F.about.regexp(r"Gonna .+"), True),
(F.about.regexp(r".+"), True),
(F.about.regexp(r"Gonna .+", mode=RegexpMode.MATCH), True),
(F.about.regexp(r"fly"), False),
(F.about.regexp(r"fly", search=False), False),
],
)
def test_match(self, case: MagicFilter, user: User, result: bool):
assert bool(case.resolve(user)) is result

@pytest.mark.parametrize(
"case,result",
[
(F.about.regexp(r"fly", search=True), True),
(F.about.regexp(r"fly", mode=RegexpMode.SEARCH), True),
(F.about.regexp(r"run", mode=RegexpMode.SEARCH), False),
],
)
def test_search(self, case: MagicFilter, user: User, result: bool):
assert bool(case.resolve(user)) is result

@pytest.mark.parametrize(
"case,result",
[
(F.job.place.regexp(r"[A-Z]", mode=RegexpMode.FINDALL), ['N', 'Y']),
],
)
def test_findall(self, case: MagicFilter, user: User, result: bool):
assert case.resolve(user) == result

@pytest.mark.parametrize(
"case,result",
[
(F.about.regexp(r"(\w{5,})", mode=RegexpMode.FINDITER),
['Gonna', 'Factory']),
],
)
def test_finditer(self, case: MagicFilter, user: User, result: bool):
assert [m.group() for m in case.resolve(user)] == result

@pytest.mark.parametrize(
"case,result",
[
(F.job.place.regexp(r"New York", mode=RegexpMode.FULLMATCH), True),
(F.job.place.regexp(r"Old York", mode=RegexpMode.FULLMATCH), False),
],
)
def test_full_match(self, case: MagicFilter, user: User, result: bool):
assert bool(case.resolve(user)) is result

@pytest.mark.parametrize("search", [True, False])
def test_search_deprecation(self, search):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
F.about.regexp(r"test deprecation", search=search)
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)

@pytest.mark.parametrize(
"mode",
[
RegexpMode.SEARCH,
RegexpMode.MATCH,
RegexpMode.FULLMATCH,
RegexpMode.FINDALL,
RegexpMode.FINDITER,
]
)
@pytest.mark.parametrize("search", [True, False])
def test_params_conflict(self, search, mode):
with pytest.raises(ParamsConflict):
F.about.regexp(r"", search=search, mode=mode)

0 comments on commit 9592bbc

Please sign in to comment.