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

Uploaded Python 3

File details

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

File metadata

  • Download URL: kain-1.1.6.tar.gz
  • Upload date:
  • Size: 87.6 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.6.tar.gz
Algorithm Hash digest
SHA256 270d4115f4b7d7c244853fcaaa6e8c481d1a98c2af8a0e86324e368f0384a329
MD5 cf2ff469bba00a2066240e3ab05e57a0
BLAKE2b-256 9685709be12ef05553034d6e01f8298f6ddcb88fb8dd2db8a4cabdf8473ad7b0

See more details on using hashes here.

File details

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

File metadata

  • Download URL: kain-1.1.6-py3-none-any.whl
  • Upload date:
  • Size: 48.9 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.6-py3-none-any.whl
Algorithm Hash digest
SHA256 70f744bca40474afebe5a7f092229ce9d799b64ef9dbd30b78d60f68e2ab2cc2
MD5 3be69cd41896ce0ee4b7709a9fca9761
BLAKE2b-256 54ceaaef99fee49e1708dc35fd180d3c7190cb903068860c64f30fcb7a473a0b

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