Skip to main content

Genro-Toolbox Logo

PyPI version Python 3.10+ License Tests codecov Documentation LLM Docs

Genro-Toolbox

Essential utilities for the Genro ecosystem

Part of Genro Kyō ecosystem.

A lightweight, zero-dependency Python library providing core utilities that can be used across all Genro projects.

📚 Full Documentation

Installation

pip install genro-toolbox

Features

  • SmartOptions - Multi-source config with merge via + operator
  • TreeDict - Hierarchical dict with dot notation and path access
  • DictObj - Dict subclass with dot-access for attribute-style read/write
  • extract_kwargs - Decorator to group kwargs by prefix
  • metadata - Decorator to stamp keyword arguments as attributes on functions or classes
  • dictExtract - Extract dict items by key prefix
  • smartsplit - Split strings honoring escaped separators
  • get_uuid - Sortable 22-char unique identifiers for distributed systems
  • sign / verify - HMAC-signed payloads with optional expiry, for data that leaves the server and comes back
  • smartasync - Unified sync/async API with automatic context detection
  • smarttimer - Non-blocking timers (setTimeout/setInterval) with sync/async detection
  • safe_is_instance - isinstance() without importing the class
  • render_ascii_table - ASCII table rendering with formatting
  • render_markdown_table - Markdown table rendering
  • tags_match - Boolean expression matcher for tag-based filtering

Examples

SmartOptions

Load config from multiple sources with type conversion:

from genro_toolbox import SmartOptions
import sys

def serve(host: str = '127.0.0.1', port: int = 8000, debug: bool = False):
    pass

# Load from env and argv with automatic type conversion
# Priority: defaults < env < argv
config = SmartOptions(serve, env='MYAPP', argv=sys.argv[1:])

config["host"]   # from env (MYAPP_HOST) or default
config["port"]   # int from env (MYAPP_PORT) or argv
config["debug"]  # True if --debug passed

Compose with + for file configs:

config = (
    SmartOptions('config.yaml') +      # file config
    SmartOptions(serve, env='MYAPP', argv=sys.argv[1:])  # defaults < env < argv
)

Load from files (YAML, JSON, TOML, INI):

opts = SmartOptions('config.yaml')
opts["server.host"]  # nested dicts become SmartOptions
opts["middleware.cors"]  # string lists become feature flags (True)
opts["apps.shop.module"]  # list of dicts indexed by first key

Basic merge with filtering:

opts = SmartOptions(
    {"timeout": 30},                    # runtime values
    {"timeout": 10, "retries": 3},      # defaults
    ignore_none=True,
    ignore_empty=True,
)

opts["timeout"]   # 30 (runtime wins)
opts["retries"]   # 3 (from defaults)

TreeDict

Hierarchical dictionary with path access:

from genro_toolbox import TreeDict

# Create from nested dict
td = TreeDict({"user": {"name": "Alice", "prefs": {"theme": "dark"}}})

# Or from JSON string
td = TreeDict('{"user": {"name": "Alice"}}')

# Or from config file (JSON, YAML, TOML, INI)
td = TreeDict.from_file("config.yaml")

# Path string access
td["user.name"]        # "Alice"
td["user.prefs.theme"] # "dark"
td["missing"]          # None (no KeyError)

# Auto-create intermediate dicts on write
td["settings.db.host"] = "localhost"
td["settings.db.host"] # "localhost"

# List access with #N syntax
td = TreeDict({"users": [{"name": "Alice"}, {"name": "Bob"}]})
td["users.#0.name"]    # "Alice"
td["users.#1.name"]    # "Bob"

# Walk all paths
for path, value in td.walk():
    print(f"{path} = {value}")
# users.#0.name = Alice
# users.#1.name = Bob

# Thread-safe access (sync)
with td:
    td["counter"] = td["counter"] + 1

# Async-safe access
async with td:
    td["counter"] = td["counter"] + 1

# Convert back to dict
td.as_dict()  # {"user": {"name": "Alice", ...}}

extract_kwargs Decorator

from genro_toolbox import extract_kwargs

@extract_kwargs(logging=True, cache=True)
def my_function(name, logging_kwargs=None, cache_kwargs=None, **kwargs):
    print(f"logging: {logging_kwargs}")
    print(f"cache: {cache_kwargs}")

my_function(
    "test",
    logging_level="INFO",
    logging_format="json",
    cache_ttl=300,
)
# logging: {'level': 'INFO', 'format': 'json'}
# cache: {'ttl': 300}

metadata Decorator

Stamp keyword arguments as attributes on a function or class:

from genro_toolbox import metadata

@metadata(mixin_order=10)
class Core:
    pass

Core.mixin_order        # 10

@metadata(prefix="rpc", public=True)
def handler():
    pass

handler.rpc_public      # True

safe_is_instance

from genro_toolbox import safe_is_instance

# Check type without importing
safe_is_instance(42, "builtins.int")              # True
safe_is_instance(my_obj, "mypackage.BaseClass")   # True (includes subclasses)

ASCII & Markdown Tables

from genro_toolbox import render_ascii_table, render_markdown_table

data = {
    "headers": [
        {"name": "Name", "type": "str"},
        {"name": "Active", "type": "bool"},
    ],
    "rows": [
        ["Alice", "yes"],
        ["Bob", "no"],
    ],
}

print(render_ascii_table(data))
# +-------+--------+
# |Name   |Active  |
# +-------+--------+
# |Alice  |true    |
# +-------+--------+
# |Bob    |false   |
# +-------+--------+

tags_match

from genro_toolbox import tags_match

# Simple tag check
tags_match("admin", {"admin", "user"})  # True

# OR (comma, pipe, or keyword)
tags_match("admin,public", {"public"})  # True
tags_match("admin or public", {"admin"})  # True

# AND (ampersand or keyword)
tags_match("admin&internal", {"admin", "internal"})  # True
tags_match("admin and internal", {"admin"})  # False

# NOT (exclamation or keyword)
tags_match("!admin", {"public"})  # True
tags_match("not admin", {"admin"})  # False

# Complex expressions
tags_match("(admin|public)&!internal", {"admin"})  # True
tags_match("(admin or public) and not internal", {"admin", "internal"})  # False

get_uuid

from genro_toolbox import get_uuid

# Generate sortable unique identifiers
uid = get_uuid()  # e.g., "Z00005KmLxHj7F9aGbCd3e"

len(uid)      # 22 characters
uid[0]        # 'Z' (version marker, sorts after legacy UUIDs)
uid.isalnum() # True (URL-safe)

# IDs are lexicographically sortable by creation time
ids = [get_uuid() for _ in range(3)]
sorted(ids) == ids  # True (already sorted)

sign / verify

Authenticate data that leaves the server and comes back — a payload handed to a client, put in a URL or a cookie, then returned to be acted upon. The key stays server-side: sign on the way out, verify on the way in.

from genro_toolbox import sign, verify, SignatureError, SignatureExpired

token = sign("/srv/data", key=SECRET, expires_in=300)
# 'L3Nydi9kYXRh.MTc4NTMyMzIyMQ.FmMftICRb9MQVrFUwoo9qPCg-R5SYVpKmXA2oXQdmLg'

verify(token, key=SECRET)   # '/srv/data'

Tampering and expiry are both rejected:

try:
    path = verify(token_from_client, key=SECRET)
except SignatureExpired:
    ...   # signature was valid, the token is just too old
except SignatureError:
    ...   # forged, altered, or signed with another key

The token is three base64url fields joined by ., so the payload round-trips byte for byte whatever it contains — separators, unicode, XML-unsafe characters:

payload = '{"exclude": "*.tmp;*.bak"}'
verify(sign(payload, key=SECRET), key=SECRET) == payload  # True

The expiry sits inside the signed area, so it cannot be stripped or extended. Omit expires_in for a token that never expires.

smartasync

from genro_toolbox import smartasync

class DataManager:
    @smartasync
    async def fetch_data(self, url: str):
        async with httpx.AsyncClient() as client:
            return await client.get(url).json()

manager = DataManager()

# Sync context - no await needed!
data = manager.fetch_data("https://api.example.com")

# Async context - use await
async def main():
    data = await manager.fetch_data("https://api.example.com")

# Also works with sync methods in async context (offloaded to thread)
class LegacyProcessor:
    @smartasync
    def cpu_intensive(self, data):
        return process(data)  # Blocking operation

async def main():
    proc = LegacyProcessor()
    result = await proc.cpu_intensive(data)  # Won't block event loop

DictObj

from genro_toolbox import DictObj

ctx = DictObj()
ctx.db = connection
ctx.session = session_obj
ctx.db.execute(...)   # dot-access
"db" in ctx           # True (dict-access)
del ctx.session       # works too

smartsplit

from genro_toolbox import smartsplit

smartsplit("a.b.c", ".")          # ['a', 'b', 'c']
smartsplit(r"a\.b.c", ".")        # ['a\\.b', 'c']  (escaped separator preserved)
smartsplit("one , two , three", ",")  # ['one', 'two', 'three']  (strips whitespace)

dictExtract

from genro_toolbox import dictExtract

kwargs = {"logging_level": "INFO", "logging_format": "json", "cache_ttl": 300}
dictExtract(kwargs, "logging_")  # {'level': 'INFO', 'format': 'json'}

smarttimer

Non-blocking timers with automatic sync/async detection:

from genro_toolbox import set_timeout, set_interval, cancel_timer

# Token refresh: renew 5 min before expiry (inside a server)
set_timeout(expires_in - 300, refresh_token)

# Heartbeat: ping every 30s, start after 1s (works in both sync and async)
hb = set_interval(30.0, ws.send_json, {"type": "ping"}, initial_delay=1)

# Cancel when done
cancel_timer(hb)

# Async callbacks work transparently
async def check_job(job_id):
    status = await api.get_status(job_id)
    if status == "completed":
        cancel_timer(pollers[job_id])

pollers = {}
pollers["j1"] = set_interval(5.0, check_job, "j1")

Philosophy

If you write a generic helper that could be useful elsewhere, put it in genro-toolbox.

This library serves as the foundation for utilities shared across:

  • genro-asgi
  • genro-routes
  • genro-api
  • Other Genro Kyō projects

License

Apache License 2.0 - See LICENSE for details.

Copyright 2025 Softwell S.r.l.

Download files

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

Source Distribution

genro_toolbox-0.14.0.tar.gz (1.7 MB view details)

Uploaded Source

Built Distribution

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

genro_toolbox-0.14.0-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

Details for the file genro_toolbox-0.14.0.tar.gz.

File metadata

  • Download URL: genro_toolbox-0.14.0.tar.gz
  • Upload date:
  • Size: 1.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for genro_toolbox-0.14.0.tar.gz
Algorithm Hash digest
SHA256 fc98c4e3312df29f2b47ad2987a33013f6b3ee0e890c5ad928561af9652177e8
MD5 17ce721c9aa73a92af293edeb2572329
BLAKE2b-256 74d9cf4913475c0fe75ba789f1e5f652176459c26d4fadb318407b880922085e

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_toolbox-0.14.0.tar.gz:

Publisher: publish.yml on genropy/genro-toolbox

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

File details

Details for the file genro_toolbox-0.14.0-py3-none-any.whl.

File metadata

  • Download URL: genro_toolbox-0.14.0-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for genro_toolbox-0.14.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18eb42ee3741dcc1259fea492dcc96db17a8e238c84d5ff4ef259d33e60b3497
MD5 95d67f122c392766e49953d650790839
BLAKE2b-256 d2573008a58410555fc4b68e47ef94249fd3b6e8df0fce8e6ac438457f7ed8c5

See more details on using hashes here.

Provenance

The following attestation bundles were made for genro_toolbox-0.14.0-py3-none-any.whl:

Publisher: publish.yml on genropy/genro-toolbox

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

Release history Release notifications | RSS feed

This release

0.14.0 This release

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.1

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page