Skip to content

Commit

Permalink
feat: add PatternMap matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
kszucs committed Aug 3, 2024
1 parent 9e2e07f commit 26a13b8
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 3 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ Examples
========

```python
from koerce.sugar import match, Namespace
from koerce.patterns import SomeOf, NoMatch, ListOf

from koerce import match, NoMatch
from koerce.sugar import Namespace
from koerce.patterns import SomeOf, ListOf

assert match([1, 2, 3, SomeOf(int, at_least=1)], four) == four
assert match([1, 2, 3, SomeOf(int, at_least=1)], three) is NoMatch
Expand Down
5 changes: 5 additions & 0 deletions koerce/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
from __future__ import annotations

from .patterns import NoMatch, Pattern
from .sugar import match, var

__all__ = ["NoMatch", "Pattern", "match", "var"]
135 changes: 135 additions & 0 deletions koerce/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,141 @@ def match(self, value, ctx: Context):

return self.type_(result)

@cython.ccall
def PatternMap(fields) -> Pattern:
if len(fields) == 1:
return PatternMap1(fields)
elif len(fields) == 2:
return PatternMap2(fields)
else:
return PatternMapN(fields)


@cython.final
@cython.cclass
class PatternMap1(Pattern):
field1: str
pattern1: Pattern

def __init__(self, fields):
(self.field1, pattern1), = fields.items()
self.pattern1 = pattern(pattern1)

def __repr__(self) -> str:
return f"PatternMap1({self.field1!r}={self.pattern1!r})"

def equals(self, other: PatternMap1) -> bool:
return self.field1 == other.field1 and self.pattern1 == other.pattern1

@cython.cfunc
def match(self, value, ctx: Context):
if not isinstance(value, Mapping):
return NoMatch

try:
item1 = value[self.field1]
except KeyError:
return NoMatch
result1 = self.pattern1.match(item1, ctx)
if result1 is NoMatch:
return NoMatch
elif result1 is not item1:
return type(value)({**value, self.field1: result1})
else:
return value

@cython.final
@cython.cclass
class PatternMap2(Pattern):
field1: str
field2: str
pattern1: Pattern
pattern2: Pattern

def __init__(self, fields):
(self.field1, pattern1), (self.field2, pattern2) = fields.items()
self.pattern1 = pattern(pattern1)
self.pattern2 = pattern(pattern2)

def __repr__(self) -> str:
return f"PatternMap2({self.field1!r}={self.pattern1!r}, {self.field2!r}={self.pattern2!r})"

def equals(self, other: PatternMap2) -> bool:
return (
self.field1 == other.field1
and self.pattern1 == other.pattern1
and self.field2 == other.field2
and self.pattern2 == other.pattern2
)

@cython.cfunc
def match(self, value, ctx: Context):
if not isinstance(value, Mapping):
return NoMatch

try:
item1 = value[self.field1]
except KeyError:
return NoMatch
result1 = self.pattern1.match(item1, ctx)
if result1 is NoMatch:
return NoMatch

try:
item2 = value[self.field2]
except KeyError:
return NoMatch
result2 = self.pattern2.match(item2, ctx)
if result2 is NoMatch:
return NoMatch

if result1 is not item1 or result2 is not item2:
return type(value)({**value, self.field1: result1, self.field2: result2})
else:
return value



@cython.final
@cython.cclass
class PatternMapN(Pattern):
fields: dict[str, Pattern]

def __init__(self, fields):
self.fields = {k: pattern(v) for k, v in fields.items()}

def __repr__(self) -> str:
return f"PatternMapN({self.fields!r})"

def equals(self, other: PatternMapN) -> bool:
return self.fields == other.fields

@cython.cfunc
def match(self, value, ctx: Context):
if not isinstance(value, Mapping):
return NoMatch

name: str
pattern: Pattern
changed: dict[str, Any] = {}
for name, pattern in self.fields.items():
try:
item = value[name]
except KeyError:
return NoMatch
result = pattern.match(item, ctx)
if result is NoMatch:
return NoMatch
elif result is not item:
changed[name] = result

if changed:
return type(value)({**value, **changed})
else:
return value




@cython.ccall
def pattern(obj: Any, allow_custom: bool = True) -> Pattern:
Expand Down
65 changes: 65 additions & 0 deletions koerce/tests/test_y.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from __future__ import annotations

from dataclasses import dataclass

# from koerce.matchers import InstanceOf as InstanceOf2
from ibis.common.patterns import Object
from pydantic_core import SchemaValidator, core_schema

from koerce.patterns import InstanceOf, ObjectOf


@dataclass
class Person:
name: str
age: int
is_developer: bool = True
has_children: bool = False

schema = core_schema.dataclass_schema(
Person,
core_schema.dataclass_args_schema(
'Person',
[
core_schema.dataclass_field(name='name', schema=core_schema.str_schema()),
core_schema.dataclass_field(name='age', schema=core_schema.int_schema()),
core_schema.dataclass_field(name='is_developer', schema=core_schema.bool_schema()),
core_schema.dataclass_field(name='has_children', schema=core_schema.bool_schema())
],
),
['name', 'age', 'is_developer', 'has_children'],
)
#s = SchemaSerializer(schema)

v = SchemaValidator(schema)

p = Person(name='Samuel', age=35, is_developer=True, has_children=False)


def test_pydantic(benchmark):
r1 = benchmark(v.validate_python, p)
assert r1 == p


def test_koerce(benchmark):
pat = ObjectOf(Person, str, int, bool, bool)
r2 = benchmark(pat.apply, p, {})
assert r2 == p

def test_ibis(benchmark):
pat = Object(Person, str, int, bool, bool)
r3 = benchmark(pat.match, p, {})
assert r3 == p


# def test_patterns_instanceof(benchmark):
# pat = InstanceOf(str)
# assert pat.apply('hello') == 'hello'
# benchmark(pat.apply, "hello")


# def test_patterns_instanceof2(benchmark):
# pat = InstanceOf2(str)
# assert pat.apply('hello') == 'hello'
# benchmark(pat.apply, "hello")

0 comments on commit 26a13b8

Please sign in to comment.