Skip to main content

python-crimes

PyPI

Small, typed-enough Python syntax crimes that form one deliberately coherent ecosystem with typed-errs: pipe values, defer cleanup, and match rich values without falling back to nullable match results.

uv add python-crimes

Examples

Runnable examples live in examples: pipes, deferred cleanup, structural matching and typed-errs variants, plus reusable dispatch.

python_crimes/
├── pipe.py       @pipe and value @ function
├── defer.py      DeferStack, with defer(), @deferred, terminate()
├── patterns.py   reusable and composable patterns
├── match.py      bound matching and reusable Dispatch
└── typed.py      Result and Option patterns backed by typed-errs

Pipes

@pipe preserves normal calls while allowing the left-to-right form.

from python_crimes import pipe


@pipe
def parse(raw: str) -> dict[str, object]: ...


@pipe
def validate(config: dict[str, object]) -> Config: ...


config = raw @ parse @ validate

For a function with configuration after its piped first argument, use the explicit partial form: score @ clamp.with_(lo=0, hi=100).

Deferred cleanup

DeferStack is the boring LIFO implementation. defer and @deferred are just convenient frontends over it.

from python_crimes import deferred, terminate
from typed_errs import Result, catch_bubble


@catch_bubble
@deferred
def write_report(d, path) -> Result[None, WriteError]:
    writer = write_text(path).q
    d << terminate(writer)
    writer("started\\n")
    return Ok(None)

The cleanup still runs when .q bubbles an Err, because normal Python stack unwinding reaches the deferred scope first.

Matching

Patterns return typed_errs.Option[Match]: successful matching is Some(Match(...)), and failure is Nothing(). The matcher itself therefore does not use Match | None as an internal failure protocol.

from python_crimes import capture, ge, gt, match_, type_

with match_(data) as m:
    m.case(200, 201, 204) << "success"
    m.case(type_(int)).when(gt(0)) << (lambda number: number * 2)
    m.regex(r"(\d+)x(\d+)") << (lambda width, height: (int(width), int(height)))
    m.case(
        {
            "type": "user",
            "payload": {"name": capture(str), "age": capture(int)},
        }
    ) << (lambda name, age: (name, age))
    m.default << "unknown"

result = m.value

Fluent matching

The context manager is convenient for a visually large decision tree. For a small match inside an expression or function return, use the exact same engine without with:

level = (
    match_(raw_level)
    .case(type_(int))
    .when(gt(0))
    .then(lambda value: value * 2)
    .regex(r"level:(\d+)")
    .then(int)
    .default.then(0)
    .value
)

<< and .then(...) are equivalent. A callable result is a handler; use const(callable_value) when a callable itself is the wanted result.

Patterns compose with &, |, and ~; helpers include eq, type_, when, gt/ge/lt/le, in_, contains, is_, regex, attr, length, ANY, and REST. Lists and mappings are structural patterns recursively, and capture(...) passes values to the selected handler in traversal order.

typed-errs is first class

Result and Option variants have dedicated arms with payload capture:

with match_(read(path)) as m:
    m.ok << process
    m.err << report

with match_(find_user()) as m:
    m.some << (lambda user: user.name)
    m.nothing << "anonymous"

No optional adapter is needed: python-crimes depends on typed-errs and ships these patterns as part of its public API.

The special arms deliberately unwrap only when their variant matched:

Ok(value)       -> m.ok       handler(value)
Err(error)      -> m.err      handler(error)
Some(value)     -> m.some     handler(value)
Nothing()       -> m.nothing  constant or handler(subject)

For ordinary structural matching, failed patterns are Nothing() and successful patterns are Some(Match(captures)); this is the same explicit absence vocabulary used everywhere else in the ecosystem.

Reusable dispatch

from python_crimes import ge, matcher, type_

status = (
    matcher()
    .case(200)
    .then("ok")
    .case(404)
    .then("missing")
    .case(type_(int) & ge(500))
    .then("server error")
    .default.then("unknown")
)

message = 503 @ status

Ecosystem

Dependencies

  • typed-errs

Use and contributions

This is a personal library, but it is not private or locked to my projects. You may use it in general Python work and in 42 projects under the MIT license; just follow the rules that apply to your campus and assignment.

Contributions are welcome: open an issue or send a pull request. I do not care whether a contribution is written by hand, AI-assisted, or generated another way; I care about whether it is correct, tested, understandable, and a good fit. Because this is opinionated personal infrastructure, pull requests are reviewed selectively and are likely to be rejected unless they clearly improve the library without making it harder to maintain.

Development and release

Run mise run check. Every push to master publishes a unique 0.0.<CI run> ZeroVer version through PyPI Trusted Publishing. mise run publish remains available.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

python_crimes-0.0.5.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

python_crimes-0.0.5-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

Details for the file python_crimes-0.0.5.tar.gz.

File metadata

  • Download URL: python_crimes-0.0.5.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for python_crimes-0.0.5.tar.gz
Algorithm Hash digest
SHA256 ad09b483d338937eca50be36645a053b0b8470a7736bf0380921f0dd420926e3
MD5 de52f276605a142d0520b4aa200349be
BLAKE2b-256 4205277813cdb6bfe9280c06d5df902651e9d9507d452f666f150a3f4e27c2e8

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_crimes-0.0.5.tar.gz:

Publisher: release.yml on 0xveya/python-crimes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file python_crimes-0.0.5-py3-none-any.whl.

File metadata

  • Download URL: python_crimes-0.0.5-py3-none-any.whl
  • Upload date:
  • Size: 12.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for python_crimes-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 794589fba385b7010579b4756eedc6ba400351f08e0a2eccf9d9c2d82dc5fbe0
MD5 e839abc99a8b3cd62c42f045cc388227
BLAKE2b-256 c1d04ab5e368fe000f47946ab24a2f5accae49c038b6d7751f2f2cbefc98eb99

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_crimes-0.0.5-py3-none-any.whl:

Publisher: release.yml on 0xveya/python-crimes

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page