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

Uploaded Source

Built Distributions

koerce-0.4.1-cp312-cp312-win_amd64.whl (464.5 kB view details)

Uploaded CPython 3.12 Windows x86-64

koerce-0.4.1-cp312-cp312-win32.whl (384.0 kB view details)

Uploaded CPython 3.12 Windows x86

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

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

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

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.4.1-cp312-cp312-macosx_13_0_x86_64.whl (573.1 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.4.1-cp311-cp311-win_amd64.whl (485.5 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.4.1-cp311-cp311-win32.whl (398.8 kB view details)

Uploaded CPython 3.11 Windows x86

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

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

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

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.4.1-cp311-cp311-macosx_13_0_x86_64.whl (588.2 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.4.1-cp310-cp310-win_amd64.whl (481.1 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.4.1-cp310-cp310-win32.whl (399.4 kB view details)

Uploaded CPython 3.10 Windows x86

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

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

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

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.4.1-cp310-cp310-macosx_13_0_x86_64.whl (587.0 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

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

File metadata

  • Download URL: koerce-0.4.1.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.1.tar.gz
Algorithm Hash digest
SHA256 0327932d4283dc1c937644379522cc3727bf6651abb8b52dc2f85dcf5ff46719
MD5 ff655b3d3640cb205150898a02fe78a3
BLAKE2b-256 d14244bb1482d7f557994afbac9e206bc78525199f2d7eb4dac4490dcc833739

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 464.5 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.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 50da7d3fec2284b93fa2f2497fa5e21e2aee4c5c8436e51c54e84ec81045ddd8
MD5 eefb2252f534f63314589eff90e63bff
BLAKE2b-256 575e316eb5b125cf4a7dd95d0bd8fdb987a621bc015e7e0ef4057a335813639a

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp312-cp312-win32.whl
  • Upload date:
  • Size: 384.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.4.1-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 a2f753f7a8bd462e70dc1085082be0af4c3396d1c2c4dbafc6fd25b7d363f075
MD5 cf808dc0919911935ae78f9c17a45ec8
BLAKE2b-256 48ad595f22c9f2c49330ac80c6d2c97537337011ab1d6562768b33b43397ea76

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 41dad4c43f83830be20e9e8b094ace2b4d9d42eb5b6a6d09f339da6b75f38187
MD5 43b4b17a78f98e0b23949ec523ab96dc
BLAKE2b-256 fdf58be578190e78196d3c72b6fe062aee194df3ec336c62d41fde0afc4dbe5a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 98eb66eb705ac08012ebabbc68cf0d76a594d9d61067e056a5f32d0c466cb8dd
MD5 19b916583b2f40581f46c3f09cdf28d7
BLAKE2b-256 20ecbda48dce09244b10eea8ea49fee8b4191475dfded04749da39a2726796f4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 458225d66859db9858397a7d80312feb6fa75fb5ceb2192026cb8787631845e9
MD5 b6b52b8fb90f75193845450490f5f78d
BLAKE2b-256 a79295f586a36bbefd717f4bcef84bdd97ac072e4bc5460022690aa932c7d0cf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 691e612d9b210ce147313de51c800455c1e7f8fd31a1d7dc6691f56739276635
MD5 ef7818b3933425ed4d040465ed8eae4f
BLAKE2b-256 8aa352ec403750a425c1f6932e0b4260fdb17c403417bf4378caedf11ac291b5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 38fd7a8ed9dd71b3c6331a5a21f467b7c35e1a3a164d3abaeac6ce4c755b39c9
MD5 cf1bb1b4b5dac5b35b932f60004cbed4
BLAKE2b-256 b895c2494dd262a4cd66f5914a83063830c21e7aa6badb2545e19232f4f0b0b2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 aa32056bcd622d210939db5acc4bf50804cca6c59ef541aa998f964ae8864078
MD5 43a3c8d541684a986ec93c5e86cb8be7
BLAKE2b-256 d24a6d84225333cd53f07c439e0031f094e683f17b7d8682350dc5874ccdbb7b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 485.5 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.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 18437f9bc6272e433550c90457e1f585bc4694fdc3841a9e7ec0b0ba637bec83
MD5 dc7386b179f29614837a0c4acd110db0
BLAKE2b-256 83684a93b887e2ccb121a0cb4cf7ca376caaf789ec3e2d7bcbe6ad985a2f4e89

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp311-cp311-win32.whl
  • Upload date:
  • Size: 398.8 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.1-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 0cad05b1727388f41263609277abbccab01271414d76c3b306a87a1e9421bd1c
MD5 3174185576256fd85a023e6bbdf7cc0d
BLAKE2b-256 8fe53201248249c23b42ea0516bf88c093e96d86827fffecd6ea71ba1ddf0057

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 979e6a74c8477333405c51f65117f235dc275e02391acb619dedaae092acc0b5
MD5 e8a19e73cf681b20f40ac6bb817e9049
BLAKE2b-256 ea2044ab82acc2ff1a7bb3c52322c09f6246dde8edaf709ba89970b0455d03ba

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 80ef02a1a3dbd0c6d09b7e4ab01c6d9dc0f520ea8ca0ec0d0b0164dcec540731
MD5 086f064f4fecc0107b0505e98dca5539
BLAKE2b-256 b77beae780b842e34dcdc65f462f6a0283ad71d5ec9a9da7a8544492ca5c3523

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8041f76e66ed98b1c032e0e0a9c2b2ee99a177bfc8ee632cd99c941d3b8ae897
MD5 85157f57cb5c69e54ef87426d611e2fe
BLAKE2b-256 b976032f408318a575f6929950fae57085a9bd16fddd3cd98a180cb041b56733

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 684017c502659339f8e6f7b1f089878e2b6ef9f342406c714c2b2240eec0a710
MD5 31dc8a89dbf0b29a1e441ea1db7d36c7
BLAKE2b-256 7e03f356ef1070979b607a84fb78b7b21d6519aca18241345ab7358f4d4eefd4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 0ced1c7d5f20b6389d1175a8a1a424a0c1871fed85918753baf99e95fb8bd0a8
MD5 d8eb466219fef24a82c5012223b3d0ad
BLAKE2b-256 2c9c01a322069959551a47ab7b5897bb105bb34f77eb8a257a91c29901f6a21d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 346adc728db37d4f7e82d2c1821d2679b5854a6a64bd295357ef520d82603083
MD5 a31c9506eca8e6741431e239fa7bea63
BLAKE2b-256 c2bc8250281cec91f99622bd606163c26ce74fe6dee3a2470cccfb4c6048db08

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 481.1 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.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 4e87da9e2b50a38f20d66fbb125d12eebfece267bdd81b8512a427fa0984369d
MD5 3d8c1ecda4136fb2d644c65688cfa34c
BLAKE2b-256 2d4ebd20e67c2dea2d8377fbae5a0f222ced23970ba832c75918b892e70088f4

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.1-cp310-cp310-win32.whl
  • Upload date:
  • Size: 399.4 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.1-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 a6ebb97f2edda5e4c2cf6d11de96141521c73629542ca5cac38787ac11eef96e
MD5 9226d00f5cbaa964a6559bd8af6c0efb
BLAKE2b-256 7b91eb2b3f07eb056335191a2d8d528c188a899154cb79775c5b1b01a4e5322d

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 b1c1bf8812f6554d58d8798c9573b75f1ac7fafeb1fc3b103d7dc7a496c6a2a5
MD5 c6943971be96628cc64c25af8dee0416
BLAKE2b-256 be44c3e140b2a8528c3d8ce46e349df0ed2809ce31346ca76eecb21425497b09

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 c8ecde709125fb66cc8cb40db4ffe35cc8226cc22cf0a1a683201ebdbf71ca81
MD5 e0418090c3dd68cee77410c59171de16
BLAKE2b-256 9e8d30da213c4328af04b5d01747b562055293d506aac6f045407ab2da533ae9

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 97feba816f157ccf0b27d5f94ee07f3cb3162664babc439cacad55361b204f80
MD5 bc0552afdb057d30d7a4d0448ca0508c
BLAKE2b-256 94154f4ba744c9df001b2e3fe3494eba068f8cbdfb4054f963761b8cf85e7ad4

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 a4690863c8ed6c4cad8fda0d3230289e895e8b44c2108ce715b3238fb51e5be3
MD5 da180b4233f47c86b3a88687b8249c8a
BLAKE2b-256 a50ae842fb46cf1a3843745dfb0bce612ca256caa2b86990426d0273ede2da9a

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 4757d841d19ec00941d28adc1c00dbcb825cce4da92e882a143f5313d81ae90d
MD5 0554893875e914bbb80e271626c9dc79
BLAKE2b-256 cf9166f65e5c0b5c3f9682c964b555659df6fec4aa06fa192ca41269b3b6ddb6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.1-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 55c2fbf8c2d4d19fc885d3b8cfe49cc18e8ed270bcf1d1262982980ac28b802b
MD5 83975d7dac837c33882993161d86cd08
BLAKE2b-256 b398b9cb118331b1982aefa61cfa14b4a10eae4da4a99d474b67503a4457d23f

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