Skip to main content

python-crimes

PyPI CI

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.6.tar.gz (16.5 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.6-py3-none-any.whl (12.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: python_crimes-0.0.6.tar.gz
  • Upload date:
  • Size: 16.5 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.6.tar.gz
Algorithm Hash digest
SHA256 111a54a0af34e657416ef454685c46b649ce08728909fc73106c2ad4e157d921
MD5 68487ad44a9161982d18f22342090af6
BLAKE2b-256 3a23d745472b5f4ec044df1d240152f7531e93354dd2b6986aaa8747649a1471

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_crimes-0.0.6.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.6-py3-none-any.whl.

File metadata

  • Download URL: python_crimes-0.0.6-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.6-py3-none-any.whl
Algorithm Hash digest
SHA256 a4825f055c52d865f39f278bc0850bb31febd931024146f4bfc0e6f08b597335
MD5 839e160330753ad49c1d5d99d9d14d2b
BLAKE2b-256 b4af325ed0090010db18290f784983321a2cf4e01b90d359c15db6f169e7c718

See more details on using hashes here.

Provenance

The following attestation bundles were made for python_crimes-0.0.6-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