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

Uploaded Source

Built Distributions

koerce-0.4.2-cp312-cp312-win_amd64.whl (473.8 kB view details)

Uploaded CPython 3.12 Windows x86-64

koerce-0.4.2-cp312-cp312-win32.whl (391.2 kB view details)

Uploaded CPython 3.12 Windows x86

koerce-0.4.2-cp312-cp312-musllinux_1_2_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ x86-64

koerce-0.4.2-cp312-cp312-musllinux_1_2_i686.whl (3.5 MB view details)

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

koerce-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

koerce-0.4.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl (3.7 MB view details)

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

koerce-0.4.2-cp312-cp312-macosx_14_0_arm64.whl (533.4 kB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.4.2-cp312-cp312-macosx_13_0_x86_64.whl (584.0 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.4.2-cp311-cp311-win_amd64.whl (493.0 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.4.2-cp311-cp311-win32.whl (406.2 kB view details)

Uploaded CPython 3.11 Windows x86

koerce-0.4.2-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.2-cp311-cp311-musllinux_1_2_i686.whl (3.6 MB view details)

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

koerce-0.4.2-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.2-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.2-cp311-cp311-macosx_14_0_arm64.whl (532.7 kB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.4.2-cp311-cp311-macosx_13_0_x86_64.whl (599.4 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.4.2-cp310-cp310-win_amd64.whl (488.6 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.4.2-cp310-cp310-win32.whl (407.0 kB view details)

Uploaded CPython 3.10 Windows x86

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

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

koerce-0.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.6 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

koerce-0.4.2-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.2-cp310-cp310-macosx_14_0_arm64.whl (531.8 kB view details)

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.4.2-cp310-cp310-macosx_13_0_x86_64.whl (596.3 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

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

File metadata

  • Download URL: koerce-0.4.2.tar.gz
  • Upload date:
  • Size: 69.3 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.2.tar.gz
Algorithm Hash digest
SHA256 3d9c0e36678a281b0a86eaa0f7a857fa9f402e7d03be7069d5eb04ce07796173
MD5 c6af5bcc553f3de62293bc456d2306d8
BLAKE2b-256 de38f2c372b0d54289127e95f16296a1a9188edca8035fb0e0fcda18464573ef

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 473.8 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.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 f0f3be531b5c2917bf5e31b376f12726dfb1bb48eb3668c25669f987bfb5e4aa
MD5 f279c454901f2dc7a493217e6ac852b1
BLAKE2b-256 34c392c090738ea03d16ca3c24054f4be426adbc4b731ca60f29cc76e2e444f6

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp312-cp312-win32.whl
  • Upload date:
  • Size: 391.2 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.2-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 e12af7dd9237b85f7c65987b811bac0ed9621aac0346ae181c765624d613683d
MD5 b9632de1e436879f5aed4fadfd04358a
BLAKE2b-256 382717b6ffbb293a15fef6bd94c23efb692aa512529e974c06f8e15e6fe4e75c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ab185ede4a3c37f3c0219b1678d554841b7f03ed2bec7cdf0af8962a2af311e7
MD5 4a62051a34446d63be81ef7d5a2a5d0c
BLAKE2b-256 f836a339260d2530b81d44c0750d5411c133b419f27f984711a7ee8a426902e7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 eb5002d1ab1eb55eac9527186a4e10eddc32d0eaa5f36a3bd1e481c1c59b7f32
MD5 f61d7d7bb57a44271d70bc32fdfe6c2d
BLAKE2b-256 f95cb5c4fc5a574b5e6fecf254c10a9a747d25e900bebad38dcfd1d9bb51a3d6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 28b275c1d6aba4df11b7c0cbe8bdce59795dad414470b7b6c0880bfa4a1238c5
MD5 731959e19931305fda83faa26218c515
BLAKE2b-256 cf8791e4e4492066a993cd4b6224eb750324eebeb3179e0633a4cf47c7a61e19

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 4376a9f79344f7db850f2de2e2bc66e9770349285c12530054669fefcb5e9691
MD5 3e1cba383301ab46fe28e0f2630dd677
BLAKE2b-256 2eb2e80feb587bbfeb450857ad134e65449fee5d1bdd6ae2b7bbfb86cee08339

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 e844e16ddfdb5dbfb09f6b8d3b73acd05d8014a66b726d290d22129fe36544e6
MD5 fac06aaf2d06d1a77cbfb7859ae640da
BLAKE2b-256 67453099fd45cf1a8b17578a3c143d980ccc3285d63c1ce7cc8f3ffeead02cc7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 f473f8652bee9c9cfb46307352a081eafe24ca37f79249aad17247d4058f2e10
MD5 ed3e5936a3e99bc8637015afe561f089
BLAKE2b-256 fef129d03dd6e4a7ed26a7fe20e2bf64a33f686a239eabcbbd83ff1090e3b5ee

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 493.0 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.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 3caeaccd14e0acdb113cbe9c6bd45cf4c2e31211bb9a3046baea774bd066adfb
MD5 4945c5882a368b1fdb51052b99b81f68
BLAKE2b-256 0e4e58a2193a56ec31341acf913b3ff15841d616d737c6bdd2f0c14adc69d51f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp311-cp311-win32.whl
  • Upload date:
  • Size: 406.2 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.2-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 a681800dcb4cb5016d6e3b781224783ff90f4e3db919d666f660dc6e9581ce12
MD5 61140efae48ec5f342100b1a483b5be5
BLAKE2b-256 88631e49aae2cff7fb5762941f7ca956fae664b7a14e8d8a714eca310c5f05ab

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 df55e649c210387c26ef1316fdfd215bb69967a5b88326b018c33133dbb77aa4
MD5 1942daaac22941119e870d8cf1908049
BLAKE2b-256 c3e2dbe6d97021e28ff4e03d1dd9889793c921fe88b9a71c3a2fba3e31328e81

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 12506a4865f2687f729317c15dc3e865115618bdfee8605aadac3f999e3d588d
MD5 9840253bf58e4e3b2588290498e23221
BLAKE2b-256 766bcc590fe7612ab30abd304a1a640294c5cac18c5929f5cbb44ac3a02c7b45

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 dbfd3a590a7769a1bed0c94c697d5b4062066a547af8ec4a400914dceccdb46a
MD5 3723d0c736f06b0f921316fec5a04531
BLAKE2b-256 486953fea74f101fe0327119e4015923050cf51f8787ce3a0aaf8cb06b023bd2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 7046dff1d978aed6eebe9bd5200d54c914c17e50cd294642843dd8daac7ff0e7
MD5 e54c7ebf322a5badcb4531b254563bbc
BLAKE2b-256 730cb58b03a4a1989256a4d027cd0ceec37e048079e44ca85bf6eedf2626d7f0

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 4782b0629c0356a06584ff256b6a48694a939875dbe3403e21bba7152b4d7807
MD5 085223e32defb3ecc0f4086bdb56b18f
BLAKE2b-256 0307a2c1c1d89e3059d8b1f05c2a195818bf54edac3545aad7c18d2c635308c2

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 5a23430ba19c19fa189eb1879b6ec74be488033222850ada2af737f706dc4915
MD5 500ea3bb920e31208673b1685da247df
BLAKE2b-256 f7eae82bba5f87dbcbc3c8ddc5add74b8916e085aa63cc566c0078563cf19f03

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 488.6 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.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 15dca6e03c2f3455193c73d4f647e29a9f87b8bf937d95af0efb6348aa00089c
MD5 b0ec0f73d26a023abcb1687d4a35bf03
BLAKE2b-256 eb41083bb3b465bf6ab8bc8909f01d7c8400f5200b8cd0e1a714adc5e3b35996

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.4.2-cp310-cp310-win32.whl
  • Upload date:
  • Size: 407.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.4.2-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 0a0e643a0de171c34158a7ab4948f762209d0f2b9a045fc5610193b4aae78eef
MD5 8cbc482ed72da20042477a366ca495a9
BLAKE2b-256 f9d562fe5d2d592186d0ca567dbf184f491d487df3d0833c9eab99922d3084bf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 b0d574a0ec5c847c778b57a77439d2a53135964c054510d70299bc95819d21db
MD5 d03f5fbce2d3804d3a5fcc2dd7259760
BLAKE2b-256 e976440c218ceca8e7cb6e636e6a4e8015437b6e8b0f949d2fa7822d83870498

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 04698d76373dcec9f66323c7fb155db31a02b4afc678c825d41ca6d0a2533a8e
MD5 5604dfd1443729fc329875d60920e11e
BLAKE2b-256 fc44b998168b4c6fa454395042df113b036ea3804eff79c4d79bba137c052968

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e84ba94db8852d4886bc7cfeff482ccb043de7eabc751887acee03e9d898db84
MD5 03d50cada6058e7bfe6466a5166b6121
BLAKE2b-256 74f9e0765af78670c7e7797ba7c329e1867f611e23ca8907da2b008e9e2a1c08

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 27be71825a4bb06eeee2bda8f1aea9819443cea37195ea71bbe5ed1a59dd5088
MD5 774212646c0c97ae2025962e2db40801
BLAKE2b-256 00b2f6763f30f3718da2d9315f910a1b7c4c8ba3c5c02a69511fa9d01d0eb499

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 92d4efcb52499d57e2b2f3fe4e3a02ce3eb1175a723a0e5d1b2f474c2a7a3007
MD5 549c27de7bf692f1661574a8a57ee199
BLAKE2b-256 26738525ef84db963f04f0f3f9528d3c23a4c8b6fc03ff61ce8ec1cf82c20be3

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.4.2-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 041012202c2fdb7259d3ace686d3806d52e52735f3dfbff1028b125b25a91410
MD5 c2fb8ba1b47b6ad863453d8de6d9c974
BLAKE2b-256 8840387ceab71f9a2276cd1bcbb3a3dcbc5e75381235038b85ca7aaf427db877

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