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.5.0.tar.gz (71.6 kB view details)

Uploaded Source

Built Distributions

koerce-0.5.0-cp312-cp312-win_amd64.whl (496.9 kB view details)

Uploaded CPython 3.12 Windows x86-64

koerce-0.5.0-cp312-cp312-win32.whl (411.0 kB view details)

Uploaded CPython 3.12 Windows x86

koerce-0.5.0-cp312-cp312-musllinux_1_2_x86_64.whl (3.9 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ x86-64

koerce-0.5.0-cp312-cp312-musllinux_1_2_i686.whl (3.7 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

koerce-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.2 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

koerce-0.5.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.9 MB view details)

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

koerce-0.5.0-cp312-cp312-macosx_14_0_arm64.whl (561.8 kB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.5.0-cp312-cp312-macosx_13_0_x86_64.whl (614.3 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.5.0-cp311-cp311-win_amd64.whl (517.1 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.5.0-cp311-cp311-win32.whl (424.9 kB view details)

Uploaded CPython 3.11 Windows x86

koerce-0.5.0-cp311-cp311-musllinux_1_2_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ x86-64

koerce-0.5.0-cp311-cp311-musllinux_1_2_i686.whl (3.8 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

koerce-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.3 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

koerce-0.5.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (4.0 MB view details)

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

koerce-0.5.0-cp311-cp311-macosx_14_0_arm64.whl (559.9 kB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.5.0-cp311-cp311-macosx_13_0_x86_64.whl (630.4 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.5.0-cp310-cp310-win_amd64.whl (513.4 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.5.0-cp310-cp310-win32.whl (425.0 kB view details)

Uploaded CPython 3.10 Windows x86

koerce-0.5.0-cp310-cp310-musllinux_1_2_x86_64.whl (3.4 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ x86-64

koerce-0.5.0-cp310-cp310-musllinux_1_2_i686.whl (3.3 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

koerce-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.8 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

koerce-0.5.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.5 MB view details)

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

koerce-0.5.0-cp310-cp310-macosx_14_0_arm64.whl (559.2 kB view details)

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.5.0-cp310-cp310-macosx_13_0_x86_64.whl (628.2 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

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

File metadata

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

File hashes

Hashes for koerce-0.5.0.tar.gz
Algorithm Hash digest
SHA256 106125e857e86106bdfe91a6c750f3c1d000aae433b0a3d3c67ec3e51b3e6fd0
MD5 10498169e02a0b732bff2c13d1335a9a
BLAKE2b-256 147fd77a4685a4d4cbb26f0e04540abe19851e66ff03f5e953e3f1835b0c0b99

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 496.9 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.5.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 d6c572da665683dc071794300a6ee448a025608cbf78fe0453e814d1ab5cfc3e
MD5 1d943c1d4ee5080546f06a0819abbe30
BLAKE2b-256 2f9a5580cef9282d32dff3747b39eea0b562633bf40ca4d33bd7bc92efb8a230

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp312-cp312-win32.whl
  • Upload date:
  • Size: 411.0 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.5.0-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 1fe39fc6478d2ddf2e36cff61cc3978f6d012e6f107f6a0ecd1a71c7337783ca
MD5 398d62303dc995788235a33229eb31f3
BLAKE2b-256 15f83f477e0fa2f82bf07b68c3378f3215047ba131c99d842de24ce616657cbf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 a57474955742c11091edd4a516ddd904d0a0a802535fc193bc4863796e5bf22a
MD5 9cccbf674d379d1a06c99f8c4eab7075
BLAKE2b-256 cebbd2f6b8dc580466432718a9b5f6db4c57efdae9b5123e0ba92afebf4bed97

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 30533638f9682530606ecc4e66989f830ae23b204cfcd2310dfe2f8884a679d5
MD5 7ced2d18850a7a3e206d145c9fb4b6c1
BLAKE2b-256 b0a40c87b1c6fd3de52919835bb1751cd39cd988eff66a30a6c2efa31af4121a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9d94d5c9e98a02b5c2dc93cace1368c969666dd272408410d9369c00da4de1ad
MD5 f2dfa1e1f8f847abc182c84bfb9ae39c
BLAKE2b-256 21644ae3eb459d901721a73815a19bfe5d3feec0dff670064372671a165ef518

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 4b840dc03beffc2a969f98e34b8d0a1ac875dc6035ca9fc8b3284ed8ee2cef4a
MD5 667fe1e00b8f9110b8f2465bd80331f7
BLAKE2b-256 744549bd3fa821bbc100822e353a1510288366a5033e56f5734f5f4aad675df0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 c7dda537fc60db5750500ac062c2cceea353562b4a719bff1133bc2ec34cbe93
MD5 849cfad48de6fd18b9e1717a38cae644
BLAKE2b-256 9918d74b0a416f58a580322e7d18185a96321b0c8bcbbb92673fa467c6fc2c74

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 1fb390be86f7806cefefcc3884fb6ee60802069f8e229137555a36f531cb34f7
MD5 fe615e698549e32dd602d86f25246715
BLAKE2b-256 f22c8b7dfffb68c5490fc2583b5e6cdcc6cd804ee51fbdce62b212944c899442

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 517.1 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.5.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 c9aaf3b695c8c35780f8d6b836a9ed52059008e12b74373ba0b687b9eecc647c
MD5 dbf312259dd1e04bae9edc3811fcecd8
BLAKE2b-256 dc48b02189528dfb462c967167f97b5eff7971ddcaded798313dee59c3874526

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp311-cp311-win32.whl
  • Upload date:
  • Size: 424.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.5.0-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 2079a6c9e9a95cc0155613b7bc125825c02d014ec7cef50391fa03b4c652db45
MD5 35c32556b7c3e58ff08bcb2b2fce40e0
BLAKE2b-256 c0e2205b30d1c004026b6ae6b72226013a6c99c6b88581d1f0ac1ec4f46dd914

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 46b8283a8b76b14b5f156700e150210d3fbee1160e5512eaee7deaa2ef2537b2
MD5 a5113123b27df61883efb53d03cfed39
BLAKE2b-256 dc05b7e315831efa3470e85951d3a53151126282a899aa4dc2d9a853bec61bfc

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 68829536af351404a5515b983f343fb5b74c69d46ef23ae85e51c658c6f4360c
MD5 5dd1877613b36b72840b062f9d326ae6
BLAKE2b-256 673cc6f44f6886d60dfc61e3ca0fcbdc66545af062c20e6b8874d9d763748d1e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b854633c239b1778a6bd6bed3aa4cee8359a8ea6065caa7334aed2c0e836e9b0
MD5 022c93392089e12048af7fabce0ea216
BLAKE2b-256 2ba83ac68649f216aa1645f2d7b8826bb632af7b20bf0563f5f4df1890142979

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 ae5e8de40479fa65538907d36ceecbbb587a776bbf166a2118f3f562c4013903
MD5 d35eb5ca2b0484c4efe2931b52e029ea
BLAKE2b-256 e961156dc0f1545ec580be7b2cf9d09e4b5dfe9cd100199cc11360b70f837a3d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 95969f41d8cacbaa65c22bafee74798ed28dc871d29b16c5ac22588da4ab3110
MD5 59c2da74222a465342b22d217934ef52
BLAKE2b-256 c908883c666693fbfc479f78151dd7b0fc4bc685c32c9ca87141831502482d12

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 ea2090407c559273b4eb5dab18c36d614bd26139a6e1cc367d272cefa9b4348c
MD5 45544f62386557ecc3fd9d6e2f263a0a
BLAKE2b-256 fa273fd4d14e3fe47bd03af23bd9e95d68e566184ea4025abd7c02e7bd6ce0cd

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 513.4 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.5.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 d69b0677719e17788e30d996fb5558aabdd265555c41c92a2d468809328b9155
MD5 11acfa1230e2dc67ef471c188f78c80c
BLAKE2b-256 ce1560a9f883fedd6979d3caa595d41e04aa7eaae1aefbd573620581aafa0a46

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.0-cp310-cp310-win32.whl
  • Upload date:
  • Size: 425.0 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.5.0-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 a85531a709a6a4170534f0e9b5ab83b60ad0c0383938a33ded099f4fc793bee0
MD5 276b0b8f2725308b9631b9cf929fc8f5
BLAKE2b-256 679de52b5d9310b9a351f0e5e6b8066ed072987c0bd07cadcd740ce38d48eaaf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ab015be2c6acefd662712f3e645c90a7180ac717be044614d64fcb9367d066ee
MD5 5aead6798b64b395aaf9987bb1dd9f7e
BLAKE2b-256 3d589e8299ab4b986fab989fa69212040965a41366682b294a4e786d21db0f4c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 5b225e25bdbceb950872cffbbddf2f91f487a72a9d0ca1fdc24a9ce8937e6ddb
MD5 97a1aae2c3cdb1910ad98de228cbff8c
BLAKE2b-256 d7e1bdf0abb7e311a22360608dafd1f2a5eab75575454910de8c87509197653a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 59bf67c391e711ca5e0c70a97a22ebbb9a2724b8494b578d69cdcf005914e5a0
MD5 d9a4c99a057e29000175de7fbbab65d0
BLAKE2b-256 2bee222252bf22d0e15eaa46bf92ec3e2b1f29052a5b218b5009d4c7938b66df

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 8842211e6055706432c3e10eb570bea3d34989f98b111272cf132acdfa39137d
MD5 b820b02dc9ed7a87466e03c198e85030
BLAKE2b-256 16da12055884ab38d8626e532a20ad9cb4c25362d420864d44035c929b7f0524

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 fb87d34023dfe1c6aa9ad6c774c362e5c9161de3a2bc1af246fa55c9e22ce3a1
MD5 3792592f157776641c53f06cd7c6919a
BLAKE2b-256 b39de7bb8996dbd880cd3767566b5b33683409073604637710b825b922ba1be1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.0-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 35c21f86d7459c72eddd3f9550a895d4b0a3fa70358400cdeb9d947ce5c86367
MD5 f8bf752053f1b1e466745d20be517135
BLAKE2b-256 6ae1edc0c76ce44d04875150b6985128d240eaf7209aba9ff7a69e9684ac232f

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