Skip to main content

kain

Project description

kain

Python License

Minimalist Python utility library. Zero runtime dependencies.

kain is a set of helper tools: introspection, descriptors, dynamic imports, monkey-patching, signal handling. Everything is built on typing and the standard library — no external packages.


Installation

Via uv:

uv add kain

Contents


Feature overview

Module Purpose
kain.Is Type predicates: Is.Class, Is.callable, Is.collection, Is.mapping, Is.subclass, ...
kain.Who Object name formatting: Who.Name, Who.Addr, Who.Is, Who.Module, Who.Args
kain.importer Dynamic imports: required(), optional(), add_path(), get_path()
kain.properties Descriptors: class_property, mixed_property, pin, cached variants
kain.signals Graceful shutdown: on_quit, quit_at
kain.classes Sentinels: Missing, Nothing, Singleton
kain.internals Utilities: to_ascii, to_bytes, unique, iter_inheritance

How to use

Is — type predicates

Is is a namespace module of predicates. All members are callables returning bool (or a type for Is.classOf).

from kain import Is

Is.Class(str)           # True
Is.callable(print)      # True
Is.collection([1, 2])   # True
Is.mapping({"a": 1})    # True
Is.primitive(42)        # True
Is.subclass(int, object)  # True

# Special
Is.tty                  # True if stdin/stdout/stderr are TTYs
Is.Builtin              # alias for is_from_builtin

Who — introspection and names

Who builds human-readable strings for objects — fully-qualified names, memory addresses, call arguments.

from kain import Who

Who.Name(str)           # 'builtins.str'
Who.Addr(str)           # 'builtins.str#7f8b2c4d1e00'  # with hex id
Who.Is(str)             # 'builtins.str (type)'
Who.Module(str)         # 'builtins'

# Call arguments
Who.Args(1, "hello", flag=True)  # "1, 'hello', flag=True"

# Short name
Who.Name(str, full=False)        # 'str'

required / optional — dynamic imports

Import by string name with different strictness levels.

from kain import required, optional

# Strict — raises ImportError if not found
os_path = required("os.path")
join = required("os.path.join")

# Optional — falls back to default if package is missing
natsorted = optional("natsort.natsorted", default=sorted)
# If natsort is not installed, returns built-in sorted

Adding paths to sys.path:

from kain import add_path

add_path("../src")      # Path("../src").resolve() is added to sys.path

class_property / mixed_property — descriptors

Analogues of property, but for the class (class_property) and both levels (mixed_property).

from kain import class_property, mixed_property

class Config:
    _name = "default"

    @class_property
    def name(cls):
        return cls._name

    @mixed_property
    def greet(cls_or_self):
        who = "class" if isinstance(cls_or_self, type) else "instance"
        return f"hello from {who}"

Config.name        # 'default'
Config().greet     # 'hello from instance'
Config.greet       # 'hello from class'

pin — cached properties

pin is the unified entry point for all cached descriptors. It exposes aliases for different variants:

from kain import pin

class Data:
    @pin
    def expensive(self):
        return sum(range(10_000_000))

    @pin.cls
    def class_level(cls):
        return f"cache on {cls.__name__}"

    @pin.any
    def mixed(cls_or_self):
        return "works on both"

d = Data()
d.expensive        # computed once, then read from __dict__
Data.class_level   # cache on class

Under the hood, pin uses the kain.properties.cached descriptor family:

Descriptor Cache on Access level
cached_property instance instance only
class_cached_property class class only
mixed_cached_property class class + instance
pre_cached_property class instance (value stored on class)
post_cached_property class instance (lazy evaluation)

All support inheritance via with_parent:

from kain.properties import class_property

class Base:
    @class_property
    def label(cls):
        return "base"

class Derived(Base):
    @class_property.with_parent
    def label(cls, parent_value):
        return f"derived:{parent_value}"

Derived.label      # 'derived:base'

proxy_to — attribute proxying

Class decorator that forwards method calls to a pivot attribute.

from kain import proxy_to

@proxy_to("_inner", "read", "write", "close")
class Wrapper:
    def __init__(self, inner):
        self._inner = inner

# Wrapper.read / Wrapper.write / Wrapper.close now proxy
# to _inner.read / _inner.write / _inner.close

Monkey — monkey-patch

from kain import Monkey

# Replace an attribute on a module or object
Monkey.patch((os.path, "join"), lambda *a: "patched!")

# Wrap an existing callable
original = Monkey.wrap(len, lambda orig, x: orig(x) * 2)

# Bind a new method to a class
Monkey.bind(SomeClass, "new_method", lambda self: 42)

# Decorator: suppress specified exceptions
class Parser:
    @Monkey.expect(ValueError)
    def parse_int(cls, s):
        return int(s)

Parser.parse_int("not-a-number")   # None, exception swallowed

on_quit / quit_at — signals

from kain import on_quit, quit_at

# Register a callback for graceful shutdown
@on_quit().schedule
def cleanup():
    print("Cleaning up before exit...")

# Auto-reload on file change (dev mode)
checker = quit_at()
while checker.sleep(2.5):
    pass  # main loop; exits when script mtime changes

on_quit is a singleton that intercepts SIGINT, SIGTERM, SIGQUIT, and atexit. All registered callbacks are executed exactly once.

Utilities

from kain import to_ascii, to_bytes, unique

# Encoding / decoding with default charset
to_bytes("hello")              # b'hello'
to_ascii(b"hello")             # 'hello'

# Unique elements with key and filtering
items = [1, 2, 2, 3, 1]
list(unique(items))            # [1, 2, 3]

# MRO iteration with filters
from kain.internals import iter_inheritance
list(iter_inheritance("hello", exclude_stdlib=True))

License

BSD License. See LICENSE.

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

kain-1.1.4.tar.gz (86.1 kB view details)

Uploaded Source

Built Distribution

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

kain-1.1.4-py3-none-any.whl (46.8 kB view details)

Uploaded Python 3

File details

Details for the file kain-1.1.4.tar.gz.

File metadata

  • Download URL: kain-1.1.4.tar.gz
  • Upload date:
  • Size: 86.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kain-1.1.4.tar.gz
Algorithm Hash digest
SHA256 e1fe97f9c1d5644f3d1314e0b2f2518aae038527751df36e854e9bd6af761889
MD5 85473cad9162a856e8bd33014c46cb46
BLAKE2b-256 051283a46876d916a3c3f0364dbd1a23b4299dd881bc5641886ceb0c8fff6ca7

See more details on using hashes here.

File details

Details for the file kain-1.1.4-py3-none-any.whl.

File metadata

  • Download URL: kain-1.1.4-py3-none-any.whl
  • Upload date:
  • Size: 46.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for kain-1.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 989254d23d691dd7bb1ed8d81a602fc48e2444c3bc3b5065bc8156057a3d8eee
MD5 acce5d0f66a907bf4c222b449d650c0e
BLAKE2b-256 e5399622c5ab68eb9889dd9d5590f54131c925ca1449a7aa7f9081fe27104443

See more details on using hashes here.

Supported by

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