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

Uploaded Source

Built Distributions

koerce-0.3.0-cp312-cp312-win_amd64.whl (463.9 kB view details)

Uploaded CPython 3.12 Windows x86-64

koerce-0.3.0-cp312-cp312-win32.whl (383.7 kB view details)

Uploaded CPython 3.12 Windows x86

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

Uploaded CPython 3.12 musllinux: musl 1.2+ x86-64

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

Uploaded CPython 3.12 musllinux: musl 1.2+ i686

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

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

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

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

koerce-0.3.0-cp312-cp312-macosx_14_0_arm64.whl (522.8 kB view details)

Uploaded CPython 3.12 macOS 14.0+ ARM64

koerce-0.3.0-cp312-cp312-macosx_13_0_x86_64.whl (572.4 kB view details)

Uploaded CPython 3.12 macOS 13.0+ x86-64

koerce-0.3.0-cp311-cp311-win_amd64.whl (485.0 kB view details)

Uploaded CPython 3.11 Windows x86-64

koerce-0.3.0-cp311-cp311-win32.whl (398.6 kB view details)

Uploaded CPython 3.11 Windows x86

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

Uploaded CPython 3.11 musllinux: musl 1.2+ x86-64

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

Uploaded CPython 3.11 musllinux: musl 1.2+ i686

koerce-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.0 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

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

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

koerce-0.3.0-cp311-cp311-macosx_14_0_arm64.whl (521.5 kB view details)

Uploaded CPython 3.11 macOS 14.0+ ARM64

koerce-0.3.0-cp311-cp311-macosx_13_0_x86_64.whl (587.9 kB view details)

Uploaded CPython 3.11 macOS 13.0+ x86-64

koerce-0.3.0-cp310-cp310-win_amd64.whl (480.5 kB view details)

Uploaded CPython 3.10 Windows x86-64

koerce-0.3.0-cp310-cp310-win32.whl (399.0 kB view details)

Uploaded CPython 3.10 Windows x86

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

Uploaded CPython 3.10 musllinux: musl 1.2+ x86-64

koerce-0.3.0-cp310-cp310-musllinux_1_2_i686.whl (3.0 MB view details)

Uploaded CPython 3.10 musllinux: musl 1.2+ i686

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

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

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

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

koerce-0.3.0-cp310-cp310-macosx_14_0_arm64.whl (520.8 kB view details)

Uploaded CPython 3.10 macOS 14.0+ ARM64

koerce-0.3.0-cp310-cp310-macosx_13_0_x86_64.whl (586.4 kB view details)

Uploaded CPython 3.10 macOS 13.0+ x86-64

File details

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

File metadata

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

File hashes

Hashes for koerce-0.3.0.tar.gz
Algorithm Hash digest
SHA256 132dd84b84a06e88aa1039139f90bd8bf9de2d88b6c9e09de887d37aa469cac1
MD5 bd7b0dda09ac13b629dc1348ee65adb3
BLAKE2b-256 dc9fecb4266cc74e3868b7da8858dcdae3ee87e559fd304fe20f56b5da4b8481

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 463.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.3.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 95ccc47c25cf59373d551d6e01a3000975aa2246c7776edfc0d54df914c56bc3
MD5 5b098ef8e075d25c2062107b6d5d15cd
BLAKE2b-256 744becf330ca0bc8761e3fd90d237aa2026debe4a7c3fd38d48a6bbd4eb69f28

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp312-cp312-win32.whl
  • Upload date:
  • Size: 383.7 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.3.0-cp312-cp312-win32.whl
Algorithm Hash digest
SHA256 d95eb9737d142cc80fb0903d0797c65ea22f5c45dcc819414eddc8f71cc1154f
MD5 7895e55ca9a08011714aefbae5112642
BLAKE2b-256 5c517739452ccea7e9f90597ae8f1751161c985242f060369ec9b93c269aad28

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 fe50b33dd156e42301cd1a81f567ebc29a724b7584df7600afa11e744705c0c1
MD5 1d1938a1c641327583eed22675ba66ee
BLAKE2b-256 90820aad22cf13f66599fabfe4492b531a87644b3bb52b3dbc2802e7f549f8ed

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 1f298ff01890229870ae8d27933e4e5d120bfe69c43b3efd26757f46a463dd6a
MD5 73057b6f0f513aef9d662a858e40da35
BLAKE2b-256 c2c4bad2a1e053fe11f73f669d2891a3fa228ea6800e6759f312b8584d3385cd

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 672f5c42b1242a18ad0dcf826d848763734fcc7ec5b314911ccaca1a3d3f4eac
MD5 d4d0ea6ec2ae1e66bbe41da496af17cd
BLAKE2b-256 7796e1f156d0567a716b090a2081b4452cdc71dcc8329bdecd937fd2e8a3afaf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 7e20387a500ff79af1b26170246c4ed6cd0b4b923e14b728847d2429818d3498
MD5 c532fb215ecc0a619823b6490d4263da
BLAKE2b-256 263daa86f7344a69cc01c0996e9cacfa8517f27be614228f399633a10060db9b

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 fe0ff62971d1054f9061d666aa5a0136bbeaa6adfb7445375f38f26ff248badb
MD5 f82dbf1bb18d6ee2363453cccbc5a242
BLAKE2b-256 3f69777ee5728bbda487dc35fdfff7316a3b0f2c7cc643f85c324a876deb9087

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp312-cp312-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 a8684ad4dbd15e8f4847f5fd2cfa19528af44c548cbe740d18b0d58e5535a795
MD5 893e778c1a3602590cd33b4828d92852
BLAKE2b-256 dfd63b1c597781e5645140b9e9d3a027b97ef6e9f4664b407712fcfe29702ab3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 485.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.3.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 b0e4090c021067f4ec9f65f33261a74135c8c3941bd5dd2e5af7444cd909ff8d
MD5 db02babd17b28f70260a8012522e7d7f
BLAKE2b-256 40ecce1678ee072438c517b60c3a46deacbd9ad9bcc0dfc8e5800bf1e60fed9e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp311-cp311-win32.whl
  • Upload date:
  • Size: 398.6 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.3.0-cp311-cp311-win32.whl
Algorithm Hash digest
SHA256 7e458d238fe0394e83fb932d25f7093a3135c02accb5249d7203c61ac3eab414
MD5 239e9d4b5af177d0a64860f2700aac7a
BLAKE2b-256 10b46178f82af80ac63f1317ba598ba15418139e737a8073bc4fd236d1faa449

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 b68749e9c61afbd10fb9ffc53fc7fe590271c2bb03de265ff89e759c81142a19
MD5 f271b2bcc1fd5cd8ae360f0614fdb1ad
BLAKE2b-256 084f7286c00b9e7e043b6e6a8ef8f4e1ffc16d974cc3bec112637af168243850

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 663cb72d32bd1d0f1f566b4646a66b5baeab10da7d87d91f855abd4a0383afa9
MD5 3827ce65161890b74604da20d2de0481
BLAKE2b-256 ba3318bcce2e5f7b3209297c55081b08c946207b7b7b342e58168a4dfcbb7336

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 267bbc5ee212777b23570d173cf649b56ca4bbe49054204b4b165cca6e8a5915
MD5 3320b4159ce380fe522cbd44e4dd411c
BLAKE2b-256 d7715aaef45e7a92439ba19a2d83dbcc1d224fea0554ecb0437b0884ee6706f7

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 e77602027b2c7fbb8ae3a0df4633c68897385560fead395d7c97537ebdcd572f
MD5 27fb3f375d28ab03261d4102f3b5eb51
BLAKE2b-256 1c6c07a550f7ee6603cec11b506b853eec63fbae7da62f08b3f4bf1a0ae3bac5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 a09d168586dff977d001a2cfcb1f4fde33a4d42ce57666567f7ad33a47b9f6e8
MD5 e5cac06aa7c63d1014946421d4e9d4ed
BLAKE2b-256 df39856f040d3e7f6b4c2c38d0f90ad1b48a24108c73e3b9700e9a6451408fd5

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp311-cp311-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 3c8fa07b6df3fd427eff456761647280e8973f7565e815d41ae06cef2dad290f
MD5 1fea8449d3b9bf15aa0ba8704b3deeca
BLAKE2b-256 02d4977e7278bf901fd78788a84369d33c8560f2c7d8409d7a312f6839e642ec

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 480.5 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.3.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 2b3e0ad7ab0833eaed77a0e2f61db1235a48c01626f6f14e8550ddecdc87e4e1
MD5 35e476f1bd12bb346b76874b44b83487
BLAKE2b-256 3be4d9dc1b05bcfc08909961f970a57f67b5581b49a61c0a2d880b349e3bda27

See more details on using hashes here.

File details

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

File metadata

  • Download URL: koerce-0.3.0-cp310-cp310-win32.whl
  • Upload date:
  • Size: 399.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.3.0-cp310-cp310-win32.whl
Algorithm Hash digest
SHA256 9127e3a309a5ceb1deef7391388f6530f9ab47f91fc13375f5aa6284cf81908f
MD5 09b2599931ec901ec7f9adc6d565175a
BLAKE2b-256 9ba30c56b441098d603873446616dc665bbbc1145ec2c56462816bb9d35dbdb1

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ad44a20b8d997c7935c3cf162670d6be5a8255366c60c3b92d1e9300ac2ea83a
MD5 996ad0d18e1e616f05c66ee31b287933
BLAKE2b-256 720d1fefafa396e0bd4578df41cd945b533d8a3f48cae45550daf7a8f82ec9a6

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-musllinux_1_2_i686.whl
Algorithm Hash digest
SHA256 18c7d695342e9693b6e516b3e896926971ddd758357b45ad794e4ea9f76f9c7a
MD5 d504118e33bc7415e1674006150579ec
BLAKE2b-256 bad5076c8f09b52a59085441f2d085f33eaa012e8432b27bdf655fe6c819a618

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7fbf6df7c3732dae0e8b7a050637b54d1df3d7f85f792c305cedd1529560798b
MD5 9d5f85d0b4aae065e33aca342f9d61de
BLAKE2b-256 8dfa2010e7404efdf814d2ff7a3f83c31120662f0418710ee74cefbc8e4ffd11

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl
Algorithm Hash digest
SHA256 9c617cdb4cc23457457da93ab5cf6179430ee83c95aa2304d0cb175f6c39626e
MD5 4ff16d4bb32f2b577a232621c17f4331
BLAKE2b-256 16d3c8a65debf0f0fc61785e6e98d1d7948d056eb14f6d82d7de5b6d45816d08

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 c99061527474b7c1fa0e0ad81c859c9c7b72cf45c532e1a728fd9c089a18ac6a
MD5 7b41cceaded30f774d5c0e023e84dafb
BLAKE2b-256 c0ee349484ac85ab03f37a33e950876bd07824f4797996a035a4e8f75844775c

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for koerce-0.3.0-cp310-cp310-macosx_13_0_x86_64.whl
Algorithm Hash digest
SHA256 8342b5f325214e96736060e2d5567ca24875e1819c94c9c80a5b11effcfff76a
MD5 c4629b123e5912caaaff8fe382c40959
BLAKE2b-256 a75f9fecd0ba991675018bf6d34a628ffa90f9c095470bfb6bccc15aa81e92e7

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