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

Uploaded Source

Built Distributions

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

Uploaded CPython 3.12 Windows x86-64

koerce-0.5.1-cp312-cp312-win32.whl (411.1 kB view details)

Uploaded CPython 3.12 Windows x86

koerce-0.5.1-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.1-cp312-cp312-musllinux_1_2_i686.whl (3.7 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

koerce-0.5.1-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.1-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.1-cp312-cp312-macosx_14_0_arm64.whl (561.7 kB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.5.1-cp312-cp312-macosx_13_0_x86_64.whl (614.4 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.5.1-cp311-cp311-win_amd64.whl (517.2 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.5.1-cp311-cp311-win32.whl (425.0 kB view details)

Uploaded CPython 3.11 Windows x86

koerce-0.5.1-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.1-cp311-cp311-musllinux_1_2_i686.whl (3.8 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

koerce-0.5.1-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.1-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.1-cp311-cp311-macosx_14_0_arm64.whl (559.7 kB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.5.1-cp311-cp311-macosx_13_0_x86_64.whl (630.5 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.5.1-cp310-cp310-win_amd64.whl (513.3 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.5.1-cp310-cp310-win32.whl (425.1 kB view details)

Uploaded CPython 3.10 Windows x86

koerce-0.5.1-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.1-cp310-cp310-musllinux_1_2_i686.whl (3.3 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

koerce-0.5.1-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.1-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.1-cp310-cp310-macosx_14_0_arm64.whl (558.8 kB view details)

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.5.1-cp310-cp310-macosx_13_0_x86_64.whl (628.3 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

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

File metadata

  • Download URL: koerce-0.5.1.tar.gz
  • Upload date:
  • Size: 71.7 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.1.tar.gz
Algorithm Hash digest
SHA256 20db6412e7e2aed1b7ce893307152333f88d0313ba3bc91e0582756acea45bbe
MD5 93204e46af4011368c0800a8be842bde
BLAKE2b-256 53003d1a70f4cbdb51f91c1a01295e0889e992ca67b35bca8c2fc030c4a61a8b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-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.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 91d00ab89656a32cf669c2394595c89c8b147a2bc87d30ccc983e4c9cfa54a52
MD5 7a42870f47a42d159e74fd8532ed13cd
BLAKE2b-256 4391a438626128a0bb3a88d77bab60fa1e8bdd0ecaec8e6e6a658b8c2989a52e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-cp312-cp312-win32.whl
  • Upload date:
  • Size: 411.1 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.1-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 de99f796f11490f9c900c1cc7b952e45973719007300e8a2de6f8149252bfb77
MD5 b16caae6531052f747a83cf2391013a5
BLAKE2b-256 197334866663ddf39d6b3ea5db4f822dc950894c9eb2764ee7eb14b423fd6359

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 64e94be035ec3b115576ed258ed293a7a81f074eea23108e8c2b2bc1482501da
MD5 c21e21dd1d27bc261bbab78345c5d4b3
BLAKE2b-256 01d909a876e909a479345530ab40724bb0907fcfc21548a2841543ac321ea82e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 03f0b03fa961c8f49d687ad351ea16b60878d09f5b3f36fc56b5f196f8d40db8
MD5 be0984582f352c62c0f8ba7752db4427
BLAKE2b-256 7aa01eba9dfb0c9f02199ab44ca5224b7291fa5759a27b5fb6f5e97b554340fb

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d924d9df9e8de3129bb8b4083d63ac246a4cd4fb4eb5af5c181c324fac9d50c3
MD5 50d88a73f85b1e81df015cd166fcc81c
BLAKE2b-256 d3f5c18db790fafb964982bca5914f3b4b840b837fd3e073340e8dd63da9152b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 d545eb05a33544e56df4c9ba1e780cba1eca9fcf5f7e4300d02922ce7ebadaab
MD5 642279b88078b9f247966de184dd83f3
BLAKE2b-256 e88ea2365ccceaea10afeb0b5d3bccfa6646595ee377ef78ead0acf023018544

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 813aa591e1b2dbfacef7f3311243b58a7be26115922d437e4c303d80aec56dcc
MD5 76e91482554127ea92a2c280945aa56b
BLAKE2b-256 ed4d4c2a05f0066639ccccb68e9a0e20d8e30f70de76d752fd80b8e9ef997f30

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 95b12440c2e00c90563fa327d70781f290d17602b9c71150f4d2541aae1b9744
MD5 e4a4e1e0331b16a1c269c92a3cd6b100
BLAKE2b-256 21b6c1baa93b22fbf10d46deaa1019b4438191fe3da757b7f697a255f3720740

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 517.2 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.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 38cc10a6e3480a51e1557788de570cdc42ff8c5cf83de4bc856062827f1e087c
MD5 60751dc68417e5a5278fcb0dd83daf9b
BLAKE2b-256 bd370e33e9c868750da2df80e6a2a1fe22b3cea5324b9c85d27c6f05f1451b47

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-cp311-cp311-win32.whl
  • Upload date:
  • Size: 425.0 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.1-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 5653f30ab9ecd4f55bd06aa2bf588d0c2a5604119d0677131751adb7a6ce635a
MD5 f7a8336496f68e81a2eaa84fefc5cc5b
BLAKE2b-256 a976a051272d187ca4b49f8e29f02b0b313c4911a9524c70ae14680fd119a57d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 942ab409d7c4930519310544f31f1446e2f4ac059415feffe2a811a52435b7c3
MD5 29f88d46c6aba42a9564b2439c36fba5
BLAKE2b-256 9b89b4b95a755fb79ca9bb7c684e03d979f341b0294c770ba23be9b08a893a69

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 03a133958b669c5a3dd7c08de6dc3b1c72d204e633957ce3c40e49357e36406e
MD5 13a3eb3dd6db5ea3a5967490f088288b
BLAKE2b-256 fe9c9f1ea6a7d2bda9383124bcf702593688f7140c2a38b658826dc7e07e6e8d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a8ffbf5c66c0208a6b08cfd758102ec39fb89c0b1ff82c91ccde20396e013e18
MD5 60a402a54bfc5d391c9c23b19b046d30
BLAKE2b-256 867dd10201436b13f7b440c707889a24e85921bcd5c4f5434ec137a3029040c5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 e00c4aaebc35c0c7c5e65a0f6ab76428ede0eda291baa5146512da505feb1397
MD5 00b3000ff1f9557f0b75544c0b657db8
BLAKE2b-256 19d880c12e9142a029fc34e261665a77410350212b0a0d57c89b435aaa46a9f2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 3bf30dd4b8875b69c2322a370fbe52267ff0f3291250ba56f26f39903a378d4d
MD5 5aef3c66f297add3f45ab5ebabfa1183
BLAKE2b-256 75bc34fa6fd0a10b82fe2430edbc4ecfe8702732cd09e92e80d8af79168b84e8

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 a05d31e8fed9451dedc4125736fdcfbe7e8f4ee6a42697ffb65dff791180a00a
MD5 cf1648b08c20af24aa60d004fc903bdf
BLAKE2b-256 a7217d08a92a16676c4007ab71bde6c7672a74b5cc6a0dfa2d389c4840e4e265

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 513.3 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.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 e4bb4729f7bb28e06c2ee0cf61ee505d487e14144d4471e1483e2897cbdfd76b
MD5 50cd6e7afe4a6a78ebc40e1dbe2efea0
BLAKE2b-256 44a28621066c663a4e36d9173358982ee07aaffe24a52ef7a1c28ec9cd3d3ab6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.5.1-cp310-cp310-win32.whl
  • Upload date:
  • Size: 425.1 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.1-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 0214febd4032d9a635a5a49ecad99d2ef8f6b3cb345e6ecfb444f0ad09356d27
MD5 341f8ee4125ce647d7e1b4fed5fa329b
BLAKE2b-256 97f746eb1283643ece7de523fc15a344beab5f68576705c945b65b2527a69344

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 f0e58cf2c40df250ea7daf5bc8e0acdb022c0ce6eaaa7de6b3075cbaab1bd7b7
MD5 68e9b888a92e1af0e255ee0653999a9f
BLAKE2b-256 0bc5a50ba13d42dadf265401974759e9e7fdafc3e5870c76c67ded322f08be1c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 050238a030dd8726bec33890d79a2cbc1e5733f01438a7d37d7bf3566608ba63
MD5 4f2e0bc3a90ace01d52992d297d4d838
BLAKE2b-256 824f7c3aff513d58d085e63abd327fccefec5c423d3572268b1a47b98e8136ad

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b6742b840ab1feb80a31a086a3f24d64899ba9fb73466782c98b0df74231dfdf
MD5 f0aeb435548362291bf0b9a07db02396
BLAKE2b-256 55a1c5f42f82fc4235aa6b1e3ecd6c4dc86166799ab7860be6616e9ed698ab7b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 bac1a35126533320f569459b9d47e651b3053fcf8be4d2a8f0215b9aaaa54df6
MD5 76ea7bdd803975345bd0265d5099b244
BLAKE2b-256 c47e65e5ee84df381a8d884d0d792a51c078fdbd48573f990271b9faa287343a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 3c4cd9d54194ef7692c893be44e4aac1f471cf8bc952004a68016f9303b2ecc4
MD5 77a5bc860297e2556cdff1b41819a771
BLAKE2b-256 7042d45aa82dc3767601466e532524ee28c5115838f43d4e5379d26ad1ed787e

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.5.1-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 53ca3c48efe1848c68b6a0ff71d6b962873fae32d017ac73ad2d717ff65b8a7a
MD5 21e6357675ff1dfad3f80ebdc659140a
BLAKE2b-256 2a3b701d9aa4245f8839e42d839b1804e25ffcc1a240044158851995300b64e6

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