Skip to main content

Python Pattern Matching

Project description

Performant Python Pattern Matching and Object Validation

Reusable pattern matching for Python, implemented in Cython. I originally developed this system for the Ibis Project but hopefully it can be useful for others as well.

The implementation aims to be as quick as possible, the pure python implementation is already quite fast but taking advantage of Cython allows to mitigate the overhead of the Python interpreter. I have also tried to use PyO3 but it had higher overhead than Cython. The current implementation uses the pure python mode of cython allowing quick iteration and testing, and then it can be cythonized and compiled to an extension module giving a significant speedup. Benchmarks shows more than 2x speedup over pydantic's model validation which is written in Rust.

Installation

The package is published to PyPI, so it can be installed using pip:

pip install koerce

Library components

The library contains three main components which can be used independently or together:

1. Deferred object builders

These allow delayed evaluation of python expressions given a context:

In [1]: from koerce import var, resolve

In [2]: a, b = var("a"), var("b")

In [3]: expr = (a + 1) * b["field"]

In [4]: expr
Out[4]: (($a + 1) * $b['field'])

In [5]: resolve(expr, {"a": 2, "b": {"field": 3}})
Out[5]: 9

The syntax sugar provided by the deferred objects allows the definition of complex object transformations in a concise and natural way.

2. Pattern matchers which operate on various Python objects

Patterns are the heart of the library, they allow searching and replacing specific structures in Python objects. The library provides an extensible yet simple way to define patterns and match values against them.

In [1]: from koerce import match, NoMatch, Anything

In [2]: context = {}

In [3]: match([1, 2, 3, int, "a" @ Anything()], [1, 2, 3, 4, 5], context)
Out[3]: [1, 2, 3, 4, 5]

In [4]: context
Out[4]: {'a': 5}

Note that from koerce import koerce function can be used instead of match() to avoid confusion with the built-in python match.

from dataclasses import dataclass
from koerce import Object, match

@dataclass
class B:
    x: int
    y: int
    z: float

match(Object(B, y=1, z=2), B(1, 1, 2))
# B(x=1, y=1, z=2)

where the Object pattern checks whether the passed object is an instance of B and value.y == 1 and value.z == 2 ignoring the x field.

Patterns are also able to capture values as variables making the matching process more flexible:

from koerce import var

x = var("x")

# `+x` means to capture that object argument as variable `x`
# then the `z` argument must match that captured value
match(Object(B, +x, z=x), B(1, 2, 1))
# it is a match because x and z are equal: B(x=1, y=2, z=1)

match(Object(B, +x, z=x), B(1, 2, 0))
# is is a NoMatch because x and z are unequal

Patterns also suitable for match and replace tasks because they can produce new values:

# >> operator constructs a `Replace` pattern where the right
# hand side is a deferred object
match(Object(B, +x, z=x) >> (x, x + 1), B(1, 2, 1))
# result: (1, 2)

Patterns are also composable and can be freely combined using overloaded operators:

In [1]: from koerce import match, Is, Eq, NoMatch

In [2]: pattern = Is(int) | Is(str)
   ...: assert match(pattern, 1) == 1
   ...: assert match(pattern, "1") == "1"
   ...: assert match(pattern, 3.14) is NoMatch

In [3]: pattern = Is(int) | Eq(1)
   ...: assert match(pattern, 1) == 1
   ...: assert match(pattern, None) is NoMatch

Patterns can also be constructed from python typehints:

In [1]: from koerce import match

In [2]: class Ordinary:
   ...:     def __init__(self, x, y):
   ...:         self.x = x
   ...:         self.y = y
   ...:
   ...:
   ...: class Coercible(Ordinary):
   ...:
   ...:     @classmethod
   ...:     def __coerce__(cls, value):
   ...:         if isinstance(value, tuple):
   ...:             return Coercible(value[0], value[1])
   ...:         else:
   ...:             raise ValueError("Cannot coerce value to Coercible")
   ...:

In [3]: match(Ordinary, Ordinary(1, 2))
Out[3]: <__main__.Ordinary at 0x105194fe0>

In [4]: match(Ordinary, (1, 2))
Out[4]: koerce.patterns.NoMatch

In [5]: match(Coercible, (1, 2))
Out[5]: <__main__.Coercible at 0x109ebb320>

The pattern creation logic also handles generic types by doing lightweight type parameter inference. The implementation is quite compact, available under Pattern.from_typehint().

3. A high-level validation system for dataclass-like objects

This abstraction is similar to what attrs or pydantic provide but there are some differences (TODO listing them).

In [1]: from typing import Optional
   ...: from koerce import Annotable
   ...:
   ...:
   ...: class MyClass(Annotable):
   ...:     x: int
   ...:     y: float
   ...:     z: Optional[list[str]] = None
   ...:

In [2]: MyClass(1, 2.0, ["a", "b"])
Out[2]: MyClass(x=1, y=2.0, z=['a', 'b'])

In [3]: MyClass(1, 2, ["a", "b"])
Out[3]: MyClass(x=1, y=2.0, z=['a', 'b'])

In [4]: MyClass("invalid", 2, ["a", "b"])
Out[4]: # raises validation error

Annotable object are mutable by default, but can be made immutable by passing immutable=True to the Annotable base class. Often it is useful to make immutable objects hashable as well, which can be done by passing hashable=True to the Annotable base class, in this case the hash is precomputed during initialization and stored in the object making the dictionary lookups cheap.

In [1]: from typing import Optional
   ...: from koerce import Annotable
   ...:
   ...:
   ...: class MyClass(Annotable, immutable=True, hashable=True):
   ...:     x: int
   ...:     y: float
   ...:     z: Optional[tuple[str, ...]] = None
   ...:

In [2]: a = MyClass(1, 2.0, ["a", "b"])

In [3]: a
Out[3]: MyClass(x=1, y=2.0, z=('a', 'b'))

In [4]: a.x = 2
AttributeError: Attribute 'x' cannot be assigned to immutable instance of type <class '__main__.MyClass'>

In [5]: {a: 1}
Out[5]: {MyClass(x=1, y=2.0, z=('a', 'b')): 1}

Available Pattern matchers

It is an incompletee list of the matchers, for more details and examples see koerce/patterns.py and koerce/tests/test_patterns.py.

Anything and Nothing

In [1]: from koerce import match, Anything, Nothing

In [2]: match(Anything(), "a")
Out[2]: 'a'

In [3]: match(Anything(), 1)
Out[3]: 1

In [4]: match(Nothing(), 1)
Out[4]: koerce._internal.NoMatch

Eq for equality matching

In [1]: from koerce import Eq, match, var

In [2]: x = var("x")

In [3]: match(Eq(1), 1)
Out[3]: 1

In [4]: match(Eq(1), 2)
Out[4]: koerce._internal.NoMatch

In [5]: match(Eq(x), 2, context={"x": 2})
Out[5]: 2

In [6]: match(Eq(x), 2, context={"x": 3})
Out[6]: koerce._internal.NoMatch

Is for instance matching

Couple simple cases are below:

In [1]: from koerce import match, Is

In [2]: class A: pass

In [3]: match(Is(A), A())
Out[3]: <__main__.A at 0x1061070e0>

In [4]: match(Is(A), "A")
Out[4]: koerce._internal.NoMatch

In [5]: match(Is(int), 1)
Out[5]: 1

In [6]: match(Is(int), 3.14)
Out[6]: koerce._internal.NoMatch

In [7]: from typing import Optional

In [8]: match(Is(Optional[int]), 1)
Out[8]: 1

In [9]: match(Is(Optional[int]), None)

Generic types are also supported by checking types of attributes / properties:

from koerce import match, Is, NoMatch
from typing import Generic, TypeVar, Any
from dataclasses import dataclass


T = TypeVar("T", covariant=True)
S = TypeVar("S", covariant=True)

@dataclass
class My(Generic[T, S]):
    a: T
    b: S
    c: str


MyAlias = My[T, str]

b_int = My(1, 2, "3")
b_float = My(1, 2.0, "3")
b_str = My("1", "2", "3")

# b_int.a must be an instance of int
# b_int.b must be an instance of Any
assert match(My[int, Any], b_int) is b_int

# both b_int.a and b_int.b must be an instance of int
assert match(My[int, int], b_int) is b_int

# b_int.b should be an instance of a float but it isn't
assert match(My[int, float], b_int) is NoMatch

# now b_float.b is actually a float so it is a match
assert match(My[int, float], b_float) is b_float

# type aliases are also supported
assert match(MyAlias[str], b_str) is b_str

As patterns attempting to coerce the value as the given type

from koerce import match, As, NoMatch
from typing import Generic, TypeVar, Any
from dataclasses import dataclass

class MyClass:
    pass

class MyInt(int):
    @classmethod
    def __coerce__(cls, other):
        return MyInt(int(other))


class MyNumber(Generic[T]):
    value: T

    def __init__(self, value):
        self.value = value

    @classmethod
    def __coerce__(cls, other, T):
        return cls(T(other))


assert match(As(int), 1.0) == 1
assert match(As(str), 1.0) == "1.0"
assert match(As(float), 1.0) == 1.0
assert match(As(MyClass), "myclass") is NoMatch

# by implementing the coercible protocol objects can be transparently
# coerced to the given type
assert match(As(MyInt), 3.14) == MyInt(3)

# coercible protocol also supports generic types where the `__coerce__`
# method should be implemented on one of the base classes and the
# type parameters are passed as keyword arguments to `cls.__coerce__()`
assert match(As(MyNumber[float]), 8).value == 8.0

As and Is can be omitted because match() tries to convert its first argument to a pattern using the koerce.pattern() function:

from koerce import pattern, As, Is

assert pattern(int, allow_coercion=False) == Is(int)
assert pattern(int, allow_coercion=True) == As(int)

assert match(int, 1, allow_coercion=False) == 1
assert match(int, 1.1, allow_coercion=False) is NoMatch
# lossy coercion is not allowed
assert match(int, 1.1, allow_coercion=True) is NoMatch

# default is allow_coercion=False
assert match(int, 1.1) is NoMatch

As[typehint] and Is[typehint] can be used to create patterns:

from koerce import Pattern, As, Is

assert match(As[int], '1') == 1
assert match(Is[int], 1) == 1
assert match(Is[int], '1') is NoMatch

If patterns for conditionals

Allows conditional matching based on the value of the object, or other variables in the context:

from koerce import match, If, Is, var, NoMatch, Capture

x = var("x")

pattern = Capture(x) & If(x > 0)
assert match(pattern, 1) == 1
assert match(pattern, -1) is NoMatch

Custom for user defined matching logic

A function passed to either match() or pattern() is treated as a Custom pattern:

from koerce import match, Custom, NoMatch, NoMatchError

def is_even(value):
    if value % 2:
        raise NoMatchError("Value is not even")
    else:
        return value

assert match(is_even, 2) == 2
assert match(is_even, 3) is NoMatch

Capture to record values in the context

A capture pattern can be defined several ways:

from koerce import Capture, Is, var

x = var("x")

Capture("x")  # captures anything as "x" in the context
Capture(x)  # same as above but using a variable
Capture("x", Is(int))  # captures only integers as "x" in the context
Capture("x", Is(int) | Is(float))  # captures integers and floats as "x" in the context
"x" @ Is(int)  # syntax sugar for Capture("x", Is(int))
+x  # syntax sugar for Capture(x, Anything())
from koerce import match, Capture, var

# context is a mutable dictionary passed along the matching process
context = {}
assert match("x" @ Is(int), 1, context) == 1
assert context["x"] == 1

Replace for replacing matched values

Allows replacing matched values with new ones:

from koerce import match, Replace, var

x = var("x")

pattern = Replace(Capture(x), x + 1)
assert match(pattern, 1) == 2
assert match(pattern, 2) == 3

there is a syntax sugar for Replace patterns, the example above can be written as:

from koerce import match, Replace, var

x = var("x")

assert match(+x >> x + 1, 1) == 2
assert match(+x >> x + 1, 2) == 3

replace patterns are especially useful when matching objects:

from dataclasses import dataclass
from koerce import match, Replace, var, namespace

x = var("x")

@dataclass
class A:
    x: int
    y: int

@dataclass
class B:
    x: int
    y: int
    z: float


p, d = namespace(__name__)
x, y = var("x"), var("y")

# if value is an instance of A then capture A.0 as x and A.1 as y
# then construct a new B object with arguments x=x, y=1, z=y
pattern = p.A(+x, +y) >> d.B(x=x, y=1, z=y)
value = A(1, 2)
expected = B(x=1, y=1, z=2)
assert match(pattern, value) == expected

replacemenets can also be used in nested structures:

from koerce import match, Replace, var, namespace, NoMatch

@dataclass
class Foo:
    value: str

@dataclass
class Bar:
    foo: Foo
    value: int

p, d = namespace(__name__)

pattern = p.Bar(p.Foo("a") >> d.Foo("b"))
value = Bar(Foo("a"), 123)
expected = Bar(Foo("b"), 123)

assert match(pattern, value) == expected
assert match(pattern, Bar(Foo("c"), 123)) is NoMatch

SequenceOf / ListOf / TupleOf

from koerce import Is, NoMatch, match, ListOf, TupleOf

pattern = ListOf(str)
assert match(pattern, ["foo", "bar"]) == ["foo", "bar"]
assert match(pattern, [1, 2]) is NoMatch
assert match(pattern, 1) is NoMatch

MappingOf / DictOf / FrozenDictOf

from koerce import DictOf, Is, match

pattern = DictOf(Is(str), Is(int))
assert match(pattern, {"a": 1, "b": 2}) == {"a": 1, "b": 2}
assert match(pattern, {"a": 1, "b": "2"}) is NoMatch

PatternList

from koerce import match, NoMatch, SomeOf, ListOf, pattern

four = [1, 2, 3, 4]
three = [1, 2, 3]

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

integer = pattern(int, allow_coercion=False)
floating = pattern(float, allow_coercion=False)

assert match([1, 2, *floating], [1, 2, 3]) is NoMatch
assert match([1, 2, *floating], [1, 2, 3.0]) == [1, 2, 3.0]
assert match([1, 2, *floating], [1, 2, 3.0, 4.0]) == [1, 2, 3.0, 4.0]

PatternMap

from koerce import match, NoMatch, Is, As

pattern = {
    "a": Is(int),
    "b": As(int),
    "c": Is(str),
    "d": ListOf(As(int)),
}
value = {
    "a": 1,
    "b": 2.0,
    "c": "three",
    "d": (4.0, 5.0, 6.0),
}
assert match(pattern, value) == {
    "a": 1,
    "b": 2,
    "c": "three",
    "d": [4, 5, 6],
}
assert match(pattern, {"a": 1, "b": 2, "c": "three"}) is NoMatch

Annotable objects

Annotable objects are similar to dataclasses but with some differences:

  • Annotable objects are mutable by default, but can be made immutable by passing immutable=True to the Annotable base class.
  • Annotable objects can be made hashable by passing hashable=True to the Annotable base class, in this case the hash is precomputed during initialization and stored in the object making the dictionary lookups cheap.
  • Validation strictness can be controlled by passing allow_coercion=False. When allow_coercion=True the annotations are treated as As patterns allowing the values to be coerced to the given type. When allow_coercion=False the annotations are treated as Is patterns and the values must be exactly of the given type. The default is allow_coercion=True.
  • Annotable objects support inheritance, the annotations are inherited from the base classes and the signatures are merged providing a seamless experience.
  • Annotable objects can be called with either or both positional and keyword arguments, the positional arguments are matched to the annotations in order and the keyword arguments are matched to the annotations by name.
from typing import Optional
from koerce import Annotable

class MyBase(Annotable):
    x: int
    y: float
    z: Optional[str] = None

class MyClass(MyBase):
    a: str
    b: bytes
    c: tuple[str, ...] = ("a", "b")
    x: int = 1


print(MyClass.__signature__)
# (y: float, a: str, b: bytes, c: tuple = ('a', 'b'), x: int = 1, z: Optional[str] = None)

print(MyClass(2.0, "a", b"b"))
# MyClass(y=2.0, a='a', b=b'b', c=('a', 'b'), x=1, z=None)

print(MyClass(2.0, "a", b"b", c=("c", "d")))
# MyClass(y=2.0, a='a', b=b'b', c=('c', 'd'), x=1, z=None)

print(MyClass(2.0, "a", b"b", c=("c", "d"), x=2))
# MyClass(y=2.0, a='a', b=b'b', c=('c', 'd'), x=2, z=None)

print(MyClass(2.0, "a", b"b", c=("c", "d"), x=2, z="z"))
# MyClass(y=2.0, a='a', b=b'b', c=('c', 'd'), x=2, z='z')

MyClass()
# TypeError: missing a required argument: 'y'

MyClass(2.0, "a", b"b", c=("c", "d"), x=2, z="z", invalid="invalid")
# TypeError: got an unexpected keyword argument 'invalid'

MyClass(2.0, "a", b"b", c=("c", "d"), x=2, z="z", y=3.0)
# TypeError: multiple values for argument 'y'

MyClass("asd", "a", b"b")
# ValidationError

Performance

koerce's performance is at least comparable to pydantic's performance. pydantic-core is written in rust using the PyO3 bindings making it a pretty performant library. There is a quicker validation / serialization library from Jim Crist-Harif called msgspec implemented in hand-crafted C directly using python's C API.

koerce is not exactly like pydantic or msgpec but they are good candidates to benchmark against:

koerce/tests/test_y.py::test_pydantic PASSED
koerce/tests/test_y.py::test_msgspec PASSED
koerce/tests/test_y.py::test_annotated PASSED


------------------------------------------------------------------------------------------- benchmark: 3 tests ------------------------------------------------------------------------------------------
Name (time in ns)            Min                   Max                  Mean              StdDev                Median                IQR            Outliers  OPS (Kops/s)            Rounds  Iterations
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
test_msgspec            230.2801 (1.0)      6,481.4200 (1.60)       252.1706 (1.0)       97.0572 (1.0)        238.1600 (1.0)       5.0002 (1.0)      485;1616    3,965.5694 (1.0)       20000          50
test_annotated          525.6401 (2.28)     4,038.5600 (1.0)        577.7090 (2.29)     132.9966 (1.37)       553.9799 (2.33)     34.9300 (6.99)      662;671    1,730.9752 (0.44)      20000          50
test_pydantic         1,185.0201 (5.15)     6,027.9400 (1.49)     1,349.1259 (5.35)     320.3790 (3.30)     1,278.5601 (5.37)     75.5100 (15.10)   1071;1424      741.2206 (0.19)      20000          50

I tried to used the most performant API of both msgspec and pydantic receiving the arguments as a dictionary.

I am planning to make more thorough comparisons, but the model-like annotation API of koerce is roughly twice as fast as pydantic but half as fast as msgspec. Considering the implementations it also makes sense, PyO3 possible has a higher overhead than Cython has but neither of those can match the performance of hand crafted python C-API code.

This performance result could be slightly improved but has two huge advantage of the other two libraries:

  1. It is implemented in pure python with cython decorators, so it can be used even without compiling it. It could also enable JIT compilers like PyPy or the new copy and patch JIT compiler coming with CPython 3.13 to optimize hot paths better.
  2. Development an be done in pure python make it much easier to contribute to. No one needs to learn Rust or python's C API in order to fix bugs or contribute new features.

TODO:

The README is under construction, planning to improve it:

  • Example of validating functions by using @annotated decorator
  • Explain allow_coercible flag
  • Proper error messages for each pattern

Development

  • The project uses poetry for dependency management and packaging.
  • Python version support follows https://numpy.org/neps/nep-0029-deprecation_policy.html
  • The wheels are built using cibuildwheel project.
  • The implementation is in pure python with cython annotations.
  • The project uses ruff for code formatting.
  • The project uses pytest for testing.

More detailed developer guide is coming soon.

References

The project was mostly inspired by the following projects:

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

koerce-0.4.0.tar.gz (68.1 kB view details)

Uploaded Source

Built Distributions

koerce-0.4.0-cp312-cp312-win_amd64.whl (464.4 kB view details)

Uploaded CPython 3.12 Windows x86-64

koerce-0.4.0-cp312-cp312-win32.whl (383.8 kB view details)

Uploaded CPython 3.12 Windows x86

koerce-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl (3.6 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ x86-64

koerce-0.4.0-cp312-cp312-musllinux_1_2_i686.whl (3.4 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

koerce-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

koerce-0.4.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.6 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

koerce-0.4.0-cp312-cp312-macosx_14_0_arm64.whl (523.3 kB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.4.0-cp312-cp312-macosx_13_0_x86_64.whl (573.0 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.4.0-cp311-cp311-win_amd64.whl (485.4 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.4.0-cp311-cp311-win32.whl (398.9 kB view details)

Uploaded CPython 3.11 Windows x86

koerce-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ x86-64

koerce-0.4.0-cp311-cp311-musllinux_1_2_i686.whl (3.5 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

koerce-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.1 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

koerce-0.4.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.8 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

koerce-0.4.0-cp311-cp311-macosx_14_0_arm64.whl (521.8 kB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.4.0-cp311-cp311-macosx_13_0_x86_64.whl (588.1 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.4.0-cp310-cp310-win_amd64.whl (481.0 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.4.0-cp310-cp310-win32.whl (399.5 kB view details)

Uploaded CPython 3.10 Windows x86

koerce-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl (3.2 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ x86-64

koerce-0.4.0-cp310-cp310-musllinux_1_2_i686.whl (3.1 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

koerce-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.5 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

koerce-0.4.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.3 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ i686 manylinux: glibc 2.5+ i686

koerce-0.4.0-cp310-cp310-macosx_14_0_arm64.whl (521.3 kB view details)

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.4.0-cp310-cp310-macosx_13_0_x86_64.whl (586.8 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

Details for the file koerce-0.4.0.tar.gz.

File metadata

  • Download URL: koerce-0.4.0.tar.gz
  • Upload date:
  • Size: 68.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0.tar.gz
Algorithm Hash digest
SHA256 9e472814a714351521b31f781ec3e327dd64d0173b3cf131543c97076fc0f280
MD5 fcba21afeca8a3e8aabeef848a4dc33a
BLAKE2b-256 8b89caf33d37406cfcb663310501ea8575a13c39c3fef0f4d47bd3198ad5921c

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: koerce-0.4.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 464.4 kB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 37f99ae78a6530245c892696758f2bfe6ae4e4938055721689dee05135d1d4aa
MD5 6f2b22e107b726137f53f6677da93b97
BLAKE2b-256 3f8894d6766fbd503def2bda7054ae7fddcbf68c7a1662edc523e157b1cbc7b4

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-win32.whl.

File metadata

  • Download URL: koerce-0.4.0-cp312-cp312-win32.whl
  • Upload date:
  • Size: 383.8 kB
  • Tags: CPython 3.12, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 03d41ef1461ddf6a56e18c85265f857540f39a48f5e7acbee8ae7fc2515b4421
MD5 83c548b1cb8f900297c964b974273a5c
BLAKE2b-256 449106f162ff1dec06fa888a6106eba09fcb3a3fbf472aadfd2818bc7ff02fe2

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 3abed4896dbdf1a841251c4e75cfb7e5790e5151e7bef0a071e028f77886f50a
MD5 0e3e3d34e883677e24db1a8775b76101
BLAKE2b-256 1987c95a0e59ca5693e8dd225bb1e1e3872f0cea6f84f323075dca6acb4ed6e5

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 a649b903b3792e0a4b1eefa228fa10354c518b1d57de3bbe8d175c22ff23f157
MD5 2d33586c70d0e03e0ba65babee6f0f46
BLAKE2b-256 80ba8f60a0a711d7cc37d938bcccafb4dc9e992103f9c7b91d5b8555e32193e4

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e0a12fc489a4f0edd5a0bcea2b7bc29b2a9c14aa0fc43e215ced897bd6c63194
MD5 7a1e66b2046990719a1f799cf6a156f5
BLAKE2b-256 7d13b1b9cf06983656a8c708b066e3fab8124af55a025822862f43ccec670de6

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 89ead692742ed4f45b4d8d06c51eab6aaf91a805dab6dad4416e91e78aa92f2b
MD5 73564533ec1d7e4695baca6ca235c58e
BLAKE2b-256 9e0a3b05ee6ae0e53718b780c988ac05faccd069c2de36d928663acd8aaf9aa5

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 f85d04dbca1fbc2a9cd591767a899f355e73261d5694b11d093b77f631db4346
MD5 9fccc886833ff1d4327ea3e58a5b1cb9
BLAKE2b-256 67c5631b71c9d65d089f9359d1fe2e471fabf579b668343d612649a82003bc9e

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp312-cp312-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 a58aa0295c02a6e465f77a7b73c07735cdc01ac2a6b039ddb33c8028dcb5acd7
MD5 d502b2ae061b74d1276583f502e676fc
BLAKE2b-256 641fedc3813706979989038996b18edb7837ad08956ac29789ab2b5e87f56a07

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: koerce-0.4.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 485.4 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 7ea7f4d1e7ed16397fb3f491a344e596e20ffca8e56c9f2fcccb9ff33cea0e43
MD5 b7f8d40d07eef89f7fc431b80014d25f
BLAKE2b-256 f819ff7b035ca9cedc3be5f8b1862243a8b6930cb5e9b63e8b54bc1f2c2204b8

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-win32.whl.

File metadata

  • Download URL: koerce-0.4.0-cp311-cp311-win32.whl
  • Upload date:
  • Size: 398.9 kB
  • Tags: CPython 3.11, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 d49e951f160ed9cd8ac09d87add6b7ea56673fd5d3735776b03266f2dd91e24f
MD5 21a4d162ad3a423d2a2d25dc5522d752
BLAKE2b-256 f369d2386cbbf9784280ac5b3c7dcb2ef441cfe81a408f2cc814ee96d01fad3c

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 6882f3dcc2fb628c312c5289f08b5f8d0ab7a98aab9d93c008f7e40bb7aa5afe
MD5 c44dcbb75cb610f9a51dc36e4a912251
BLAKE2b-256 39d2eaeb6f8aaa3ad67d7ee69307064bd81bd949eb0085a7911e8d6dd937aa4a

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 5b0889a5aa5d0cc59a538927757f6c0d8a529e0bd221dc7a610e08d246620665
MD5 f6d9d8f58751c3c8f4a04ea7ad88aa3f
BLAKE2b-256 115999ad4fa3ad9fd83f7cb70f18a85bd6bee9209dd68ac32911c48398ee9925

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 036f913f9002a79064998d2516eaaf601f567613b8b726b4d5c728f6c2759806
MD5 431dc522041f86dc911098bb5156fc77
BLAKE2b-256 27b773030b2d4851f1f84ccb639a5e16ce40ac2de74b27a5e3d7ff8aa604d9f7

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 2e7c040f32c07ae7b1693b2685e1e8b9bc17dd44770860ac01fa310613b009ad
MD5 73b535f965cc1c47383e09db37d825ac
BLAKE2b-256 d43c74957fb43a002a47816a8e4efb8a1b84e3e6a790833a48a2f09017295112

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 b0cecc60f0b06f37446b49401f1bc61f73fc94037cc7c731cec05e3da437a6f3
MD5 645d17666bebd6276c5f7c1f5e4c4203
BLAKE2b-256 416e42e5c0b1b68a2b075d4c824e1645f8602939f1fc1e38d25d3009f95bae31

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp311-cp311-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 ecc909d8e0aee0719c6d53dc21c60a7645181ebd41dc9327fd15525b1e1a2ff4
MD5 2273fe84e3ee50d602b2631084678937
BLAKE2b-256 51a9b5772ae1942180b7439b3ac43d0e3c883dcba16ea39a1227ad684bd37b72

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: koerce-0.4.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 481.0 kB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 0235a5bd54a1c37ee98c1d650c3144672cf940724794d9bcc514a93b03ca7c55
MD5 a037055084a39c9f2ce85045bcb3936b
BLAKE2b-256 c14ab71736fcfeef6bc58bd414269b52255e30428c4d06f9d2e62555dd551399

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-win32.whl.

File metadata

  • Download URL: koerce-0.4.0-cp310-cp310-win32.whl
  • Upload date:
  • Size: 399.5 kB
  • Tags: CPython 3.10, Windows x86
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/5.1.1 CPython/3.12.6

File hashes

Hashes for koerce-0.4.0-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 4f3a91042f94ffee43bd1a87be19f6d1d964e8d78e3cd43c783afe0b7fb04bab
MD5 c06a79115cf5ba0b5c85d5fac71f86f5
BLAKE2b-256 f3b1f717c7606ca734a20c8214bc2f058bf2ac2219c160f1fa8d8c5f4567d43d

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 aabbc73929b24f80f7a24c991ccb132496686db30d866b801ce26e85bd787ab5
MD5 8946e51b90e76dd9e80f63782104757f
BLAKE2b-256 fdfaf5130254ae8869b6554db9f053c9d318728d58e5d04f378d18f1cc3ac853

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-musllinux_1_2_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 a568e7ef6f9ae73cf1bc506823bc6a24674c222d05e500fd73856b3d600af462
MD5 7b9159b67cb9e12343136292866023d0
BLAKE2b-256 d62c8c087ba6b956b1b26ac1fff96e43e1e606e4fd80174e687de370090a8f6b

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 234b0b8cc6659f52ebe13a74aba180b214627d3267ccb2d90ff916c10339f24b
MD5 6f80817e60fbc697874f42939b92e8ae
BLAKE2b-256 177e7c3617e86d63fce3881fb27c8fadf62dec333b1c560674d441e873b8ced1

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 798e031860a6c6938a14df35d97054c259c96906ebdff1c772262b680346daa9
MD5 2f381f74b709c6095dfba545f13080a8
BLAKE2b-256 c2c74090acdd04da378e698d227d1f35c89a6edd3692f437852ba2b5f3930bd9

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 b92a6ee617ca34dbe3e1e13c7f6a7f5f934bea31584ba1d34f1eff55cf692c32
MD5 43fcae12254fa3541bbd8bcc81f857fa
BLAKE2b-256 520679bcb662768723927e56ed8f2ae6f46f568b47c82dfe8adf614cde33f145

See more details on using hashes here.

File details

Details for the file koerce-0.4.0-cp310-cp310-macosx_13_0_x86_64.whl.

File metadata

File hashes

Hashes for koerce-0.4.0-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 2ef35b45bc783384e119551790480d5f183c5f86ac1266c4a0e01e6be89dc80f
MD5 602a3d48c531c15c33126f098dd0bf3d
BLAKE2b-256 63995116c0e7431bcf3d183973c95b584c366c249294a1fbd48fc4ad1b5150f4

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page