title: kain description: Runtime introspection, dynamic imports, and monkey-patching utilities for Python
[ref: #kain]
kain
kain is a small Python toolbox for runtime introspection, dynamic imports, and controlled monkey-patching.
It gives you several public entry points:
Is— type and shape predicates.Who— introspection helpers for names, modules, and inheritance.required,optional,add_path— dynamic imports andsys.pathmanagement.Monkey— runtime patching, binding, and exception suppression.to_ascii,to_bytes,unique— small text and iteration helpers.
[ref: #installation]
Installation
Install with uv:
uv add kain
Or with any PEP 517-compatible tool:
pip install kain
[ref: #is]
Type and shape checks with Is
Is is a namespace of predicates for classes, collections, primitives, mappings, and more.
from kain import Is
assert Is.Class(int) is True
assert Is.Class(42) is False
assert Is.collection([1, 2, 3]) is True
assert Is.mapping({"a": 1}) is True
assert Is.primitive(42) is True
assert Is.subclass(42, int | str) is True
[ref: #who]
Introspection with Who
Who gives you readable names, source files, argument lists, and inheritance chains.
from kain import Who
assert Who.Is(42) == "int"
assert Who.Name(str) == "str"
assert Who.Cast(42) == "(int)42"
assert Who.Args(1, 2, a=3) == "'1', '2', a=3"
class A:
pass
class B(A):
pass
assert A in Who.Inheritance(B)
assert Who.Inheritance(B, glue=" -> ") == "__main__.A"
[ref: #importer]
Dynamic imports with required, optional, and add_path
required imports by dotted path and raises on failure; optional returns a default instead.
add_path resolves a directory or file path and appends it to sys.path.
from kain import add_path, optional, required
assert required("os.path.join") is __import__("os").path.join
assert optional("nonexistent_package_xyz") is None
assert optional("nonexistent_package_xyz", default="fallback") == "fallback"
# Add a directory or a file's parent directory to sys.path.
add_path(".")
[ref: #monkey]
Runtime patching with Monkey
Monkey provides helpers for suppressing exceptions, attaching functions to objects, and replacing attributes while preserving the original in Monkey.mapping.
from kain import Monkey
class Parser:
@Monkey.expect(ValueError)
def parse(cls, text: str) -> int | None:
return int(text)
assert Parser.parse("not-a-number") is None
assert Parser.parse("42") == 42
node = type("Node", (), {})()
@Monkey.bind(node)
def greet(name: str) -> str:
return f"hi {name}"
assert node.greet("world") == "hi world"
[ref: #text-and-unique]
Text coercion and deduplication with to_ascii, to_bytes, and unique
to_ascii and to_bytes coerce between str and bytes with an optional charset.
unique yields distinct items from an iterable, optionally by a key function.
from kain import to_ascii, to_bytes, unique
assert to_ascii(b"hello") == "hello"
assert to_bytes("hello") == b"hello"
assert to_ascii("é".encode("utf-8"), charset="utf-8") == "é"
assert list(unique([1, 2, 2, 3, 1])) == [1, 2, 3]
assert list(unique(["a", "A", "b"], key=str.lower)) == ["a", "b"]
[ref: #full-example]
Full example
from kain import Is, Monkey, Who, add_path, optional, required, to_ascii, unique
class Config:
@Monkey.expect(ValueError)
def port(cls, raw: str) -> int | None:
return int(raw)
assert Config.port("8080") == 8080
assert Config.port("oops") is None
assert Is.primitive("hello") is True
assert Who.Is(Config.port).endswith("Config.port")
join = required("os.path.join")
assert callable(join)
parser = optional("nonexistent_parser", default=str)
assert parser is str
add_path(".")
assert to_ascii(b"kain") == "kain"
assert list(unique(["x", "x", "y"])) == ["x", "y"]
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kain-1.2.0.tar.gz.
File metadata
- Download URL: kain-1.2.0.tar.gz
- Upload date:
- Size: 47.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5b9f3f9b97e6e3e7654d228958a1732b8dd4b9b59fbb549093d66353f66075b
|
|
| MD5 |
113a6680fbd0c9e796df17ce538ccf3c
|
|
| BLAKE2b-256 |
acf7ee6a8d24eee19bad807dba1876fc2dfccdd8008ca36413890439647ef4ac
|
File details
Details for the file kain-1.2.0-py3-none-any.whl.
File metadata
- Download URL: kain-1.2.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4aa12c6dffe12f913f18d971367ab09a7573b72347520e729f37d14bd42172c0
|
|
| MD5 |
41a963aa4ce161104caeb045ce6637e0
|
|
| BLAKE2b-256 |
dcc89a06b5d6f077cf963cbdd1341246d97b647e20c84aaf6d5108742aa4d898
|