Utils
zenpyre.utils ¶
Shared helper utilities.
zenpyre.utils.config ¶
Contain configurations.
zenpyre.utils.config.BaseConfig ¶
Bases: ABC
Define the interface shared by all configuration classes.
Concrete subclasses only need to implement :meth:to_kwargs and
:meth:get_value; :meth:cache_key is derived from
:meth:to_kwargs automatically.
Example
>>> from zenpyre.utils.config import BaseConfig, MISSING
>>> class ChatModelConfig(BaseConfig):
... def __init__(self, model: str, temperature: float | None = None):
... self.model = model
... self.temperature = temperature
... def get_value(self, name: str, default: Any = MISSING) -> Any:
... kwargs = self.to_kwargs()
... if name in kwargs:
... return kwargs[name]
... if default is not MISSING:
... return default
... raise KeyError(name)
... def to_kwargs(self) -> dict:
... return {"model": self.model, "temperature": self.temperature}
...
>>> cfg = ChatModelConfig(model="gpt-4", temperature=0.2)
>>> isinstance(cfg, BaseConfig)
True
zenpyre.utils.config.BaseConfig.cache_key ¶
cache_key(length: int = 64) -> str
Return a stable hash string derived from the current configuration.
Walks the to_kwargs() output and replaces any nested
:class:BaseConfig instance with its own cache_key() string,
then hashes the result via :func:coola.hashing.hash_object. The
substitution step means two structurally-equal-but-distinct nested
configs (e.g. two separately constructed ChatModelConfig
objects with the same fields) yield the same key, since the nested
config contributes its content rather than its object identity.
:func:hash_object canonicalizes its input (e.g. via sorted keys)
before hashing, so two configs with identical (post-substitution)
to_kwargs() output always produce the same hash regardless of
field ordering.
Note that this only covers whatever to_kwargs() returns. A
subclass that adds a field must also include it in to_kwargs()
for it to affect the cache key.
Useful for cache keys, output filenames, or detecting configuration changes between runs without comparing each field manually.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
length
|
int
|
The desired length of the returned hash string. |
64
|
Returns:
| Type | Description |
|---|---|
str
|
A stable hash string, |
Example
>>> from zenpyre.utils.config import BaseConfig, MISSING
>>> class ChatModelConfig(BaseConfig):
... def __init__(self, model: str, temperature: float | None = None):
... self.model = model
... self.temperature = temperature
... def get_value(self, name: str, default: Any = MISSING) -> Any:
... kwargs = self.to_kwargs()
... if name in kwargs:
... return kwargs[name]
... if default is not MISSING:
... return default
... raise KeyError(name)
... def to_kwargs(self) -> dict:
... return {"model": self.model, "temperature": self.temperature}
...
>>> cfg = ChatModelConfig(model="gpt-4", temperature=0.2)
>>> key = cfg.cache_key()
>>> len(key)
64
>>> cfg.cache_key() == cfg.cache_key()
True
zenpyre.utils.config.BaseConfig.get_value
abstractmethod
¶
get_value(name: str, default: Any = MISSING) -> Any
Get the value of a configuration field by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the field to look up. |
required |
default
|
Any
|
The value to return if |
MISSING
|
Returns:
| Type | Description |
|---|---|
Any
|
The value associated with |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Example
>>> from zenpyre.utils.config import BaseConfig, MISSING
>>> class ChatModelConfig(BaseConfig):
... def __init__(self, model: str, temperature: float | None = None):
... self.model = model
... self.temperature = temperature
... def get_value(self, name: str, default: Any = MISSING) -> Any:
... kwargs = self.to_kwargs()
... if name in kwargs:
... return kwargs[name]
... if default is not MISSING:
... return default
... raise KeyError(name)
... def to_kwargs(self) -> dict:
... return {"model": self.model, "temperature": self.temperature}
...
>>> cfg = ChatModelConfig(model="gpt-4", temperature=0.2)
>>> cfg.get_value("model")
'gpt-4'
>>> cfg.get_value("missing_key", default=42)
42
zenpyre.utils.config.BaseConfig.to_kwargs
abstractmethod
¶
to_kwargs() -> dict[str, Any]
Return the configuration as a flat dict of keyword arguments.
This is the single source of truth for the configuration's
content: it is used both to construct/invoke the chat model
and, via :meth:cache_key, to derive a stable hash. Any field
that should affect caching or equality must be included here.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of keyword arguments representing this configuration. |
Example
>>> from zenpyre.utils.config import BaseConfig, MISSING
>>> class ChatModelConfig(BaseConfig):
... def __init__(self, model: str, temperature: float | None = None):
... self.model = model
... self.temperature = temperature
... def get_value(self, name: str, default: Any = MISSING) -> Any:
... kwargs = self.to_kwargs()
... if name in kwargs:
... return kwargs[name]
... if default is not MISSING:
... return default
... raise KeyError(name)
... def to_kwargs(self) -> dict:
... return {"model": self.model, "temperature": self.temperature}
...
>>> cfg = ChatModelConfig(model="gpt-4", temperature=0.2)
>>> cfg.to_kwargs()
{'model': 'gpt-4', 'temperature': 0.2}
zenpyre.utils.config.Config
dataclass
¶
zenpyre.utils.config.ExtraFieldsConfig
dataclass
¶
Bases: BaseConfig
Base for configs that merge arbitrary keyword arguments ("extra")
into :meth:to_kwargs, alongside whatever typed fields a subclass
declares.
Subclasses just need to be frozen dataclasses with their own typed
fields; :meth:to_kwargs, :meth:from_kwargs, the extra/field-name
collision check, and __hash__ are all inherited from here and
work automatically via introspection (:func:dataclasses.fields) —
no per-subclass overriding required.
extra is declared keyword-only (kw_only=True on this class)
specifically so that subclasses are free to add non-defaulted,
positional-or-keyword fields of their own without hitting
dataclass's "non-default argument follows default argument" error,
which would otherwise apply because extra (with its default)
is defined here in the base class.
Attributes:
| Name | Type | Description |
|---|---|---|
extra |
dict[str, Any]
|
Additional keyword arguments merged into
:meth: |
zenpyre.utils.config.ExtraFieldsConfig.from_kwargs
classmethod
¶
from_kwargs(**kwargs: Any) -> Self
Construct a config from arbitrary keyword arguments, routing
each one to a real field if it matches one, or to extra
otherwise.
A convenience alternative to the regular constructor's
extra={...} dict, letting callers pass extra fields
directly as keyword arguments instead of building the dict
themselves. Which keys count as "real fields" is determined by
introspection (:func:dataclasses.fields) on cls, so this
works automatically for any subclass without needing its own
override — including subclasses that add their own typed
fields.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Keyword arguments to route: keys matching one of
this class's own dataclass field names (other than
|
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
A new instance of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If, after routing, a key intended for |
zenpyre.utils.config.ExtraFieldsConfig.get_value ¶
get_value(name: str, default: Any = MISSING) -> Any
Get the value of a dataclass field (or extra entry) by
name.
Checks this config's own dataclass fields first (via getattr,
without needing to build the full :meth:to_kwargs dict just for
a single lookup), then falls back to extra for keys that only
live there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the field (or |
required |
default
|
Any
|
The value to return if |
MISSING
|
Returns:
| Type | Description |
|---|---|
Any
|
The value associated with |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
zenpyre.utils.config.ExtraFieldsConfig.to_kwargs ¶
to_kwargs() -> dict[str, Any]
Return every dataclass field (including ones declared by a
subclass) merged with extra, as a flat dict.
Fields are collected via introspection
(:func:dataclasses.fields) rather than hardcoded by name, so
a subclass that adds a new typed field gets it included here
automatically, with no need to override this method.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict of keyword arguments representing this configuration. |
zenpyre.utils.dataclass ¶
Contain dataclass utilities.
zenpyre.utils.dataclass.dataclasses_to_dataframe ¶
dataclasses_to_dataframe(items: list[Any]) -> DataFrame
Convert a list of dataclass instances into a Polars DataFrame.
Serialises each item to a dict via :func:dataclasses.asdict and
builds a :class:polars.DataFrame from the resulting list of
dicts. Works with any dataclass type, including frozen
dataclasses, as long as all field values are compatible with
Polars' type inference.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[Any]
|
The list of dataclass instances to convert. |
required |
Returns:
| Type | Description |
|---|---|
DataFrame
|
A |
DataFrame
|
per field. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If any item in |
Example
>>> from dataclasses import dataclass
>>> from zenpyre.utils.dataclass import dataclasses_to_dataframe
>>> @dataclass(frozen=True)
... class Point:
... x: int
... y: int
...
>>> frame = dataclasses_to_dataframe([Point(1, 2), Point(3, 4)])
>>> frame
shape: (2, 2)
┌─────┬─────┐
│ x ┆ y │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═════╪═════╡
│ 1 ┆ 2 │
│ 3 ┆ 4 │
└─────┴─────┘
zenpyre.utils.dataclass.load_dataclasses ¶
load_dataclasses(
path: Path | str, cls: type[T]
) -> list[T]
Load a list of dataclass instances from a JSON file.
Reads a JSON array of objects from path and converts each one
into an instance of cls by unpacking its keys as keyword
arguments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path | str
|
The source file path, as previously written by
:func: |
required |
cls
|
type[T]
|
The dataclass type to reconstruct each entry as. |
required |
Returns:
| Type | Description |
|---|---|
list[T]
|
A list of |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
TypeError
|
If the JSON content is not a list. |
ValueError
|
If an entry in the list is missing a required field
or has an unexpected field for |
Example
>>> from dataclasses import dataclass
>>> from zenpyre.utils.dataclass import load_dataclasses
>>> @dataclass(frozen=True)
... class Point:
... x: int
... y: int
...
>>> points = load_dataclasses("points.json", Point) # doctest: +SKIP
zenpyre.utils.dataclass.save_dataclasses ¶
save_dataclasses(
items: list[Any],
path: Path | str,
*,
exist_ok: bool = False
) -> None
Save a list of dataclass instances to a JSON file.
Serialises each item to a dict via :func:dataclasses.asdict and
writes the resulting list to path as a JSON array. Works with any
dataclass type, including frozen dataclasses, as long as all field
values are JSON-serialisable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items
|
list[Any]
|
The list of dataclass instances to save. |
required |
path
|
Path | str
|
The destination file path. |
required |
exist_ok
|
bool
|
If |
False
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If any item in |
FileExistsError
|
If |
OSError
|
If |
Example
>>> from dataclasses import dataclass
>>> from zenpyre.utils.dataclass import save_dataclasses
>>> @dataclass(frozen=True)
... class Point:
... x: int
... y: int
...
>>> points = save_dataclasses([Point(1, 2), Point(3, 4)]) # doctest: +SKIP
zenpyre.utils.duckdb ¶
Contain DuckDB utility functions.
zenpyre.utils.duckdb.prepare_duckdb_path ¶
prepare_duckdb_path(path: Path | str) -> Path | str
Prepare a path for use with duckdb.connect.
If path is the special in-memory sentinel (":memory:"), it is
returned unchanged. Otherwise, path is sanitized and its parent
directory is created if it does not already exist, so that DuckDB
can create the database file without failing on a missing directory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path | str
|
The DuckDB connection target -- either the in-memory
sentinel |
required |
Returns:
| Type | Description |
|---|---|
Path | str
|
|
Path | str
|
whose parent directory is guaranteed to exist. |
zenpyre.utils.fallback ¶
Fallback helpers used when optional dependencies are unavailable.
zenpyre.utils.hashing ¶
Provide UUID hashing utilities for Python dictionaries.
zenpyre.utils.hashing.hash_dict_uuid ¶
hash_dict_uuid(data: dict[str, Any]) -> str
Compute a stable, reproducible UUID for a Python dictionary.
Serialises data via :func:json.dumps with sort_keys=True
to guarantee a consistent ordering regardless of dict insertion
order, then derives a deterministic UUID using :func:uuid.uuid5
with a fixed project-specific namespace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
The dictionary to hash. All values must be JSON-serialisable. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A lowercase UUID string of the form |
str
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If any value in |
Example
>>> from zenpyre.utils.hashing import hash_dict_uuid
>>> hash_dict_uuid({"source": "cats.txt", "page": 1}) # doctest: +ELLIPSIS
'...'
>>> hash_dict_uuid({"page": 1, "source": "cats.txt"}) == hash_dict_uuid(
... {"source": "cats.txt", "page": 1}
... )
True
zenpyre.utils.imports ¶
Helpers to detect and validate optional dependencies.
zenpyre.utils.imports.check_duckdb ¶
check_duckdb() -> None
Check if the duckdb package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_duckdb
>>> check_duckdb()
zenpyre.utils.imports.check_faker ¶
check_faker() -> None
Check if the faker package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_faker
>>> check_faker()
zenpyre.utils.imports.check_langchain_anthropic ¶
check_langchain_anthropic() -> None
Check if the langchain_anthropic package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_anthropic
>>> check_langchain_anthropic()
zenpyre.utils.imports.check_langchain_chroma ¶
check_langchain_chroma() -> None
Check if the langchain_chroma package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_chroma
>>> check_langchain_chroma()
zenpyre.utils.imports.check_langchain_google_genai ¶
check_langchain_google_genai() -> None
Check if the langchain_google_genai package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_google_genai
>>> check_langchain_google_genai()
zenpyre.utils.imports.check_langchain_huggingface ¶
check_langchain_huggingface() -> None
Check if the langchain_huggingface package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_huggingface
>>> check_langchain_huggingface()
zenpyre.utils.imports.check_langchain_ollama ¶
check_langchain_ollama() -> None
Check if the langchain_ollama package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_ollama
>>> check_langchain_ollama()
zenpyre.utils.imports.check_langchain_openai ¶
check_langchain_openai() -> None
Check if the langchain_openai package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_openai
>>> check_langchain_openai()
zenpyre.utils.imports.check_langchain_text_splitters ¶
check_langchain_text_splitters() -> None
Check if the langchain_text_splitters package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_langchain_text_splitters
>>> check_langchain_text_splitters()
zenpyre.utils.imports.check_persista ¶
check_persista() -> None
Check if the persista package is installed.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the |
Example
>>> from zenpyre.utils.imports import check_persista
>>> check_persista()
zenpyre.utils.imports.duckdb_available ¶
duckdb_available(fn: F) -> F
Implement a decorator to execute a function only if duckdb
package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import duckdb_available
>>> @duckdb_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.faker_available ¶
faker_available(fn: F) -> F
Implement a decorator to execute a function only if faker
package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import faker_available
>>> @faker_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.is_duckdb_available
cached
¶
is_duckdb_available() -> bool
Indicate if the duckdb package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_duckdb_available
>>> is_duckdb_available()
zenpyre.utils.imports.is_faker_available
cached
¶
is_faker_available() -> bool
Indicate if the faker package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_faker_available
>>> is_faker_available()
zenpyre.utils.imports.is_langchain_anthropic_available
cached
¶
is_langchain_anthropic_available() -> bool
Indicate if the langchain_anthropic package is installed or
not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_anthropic_available
>>> is_langchain_anthropic_available()
zenpyre.utils.imports.is_langchain_chroma_available
cached
¶
is_langchain_chroma_available() -> bool
Indicate if the langchain_chroma package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_chroma_available
>>> is_langchain_chroma_available()
zenpyre.utils.imports.is_langchain_google_genai_available
cached
¶
is_langchain_google_genai_available() -> bool
Indicate if the langchain_google_genai package is installed
or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_google_genai_available
>>> is_langchain_google_genai_available()
zenpyre.utils.imports.is_langchain_huggingface_available
cached
¶
is_langchain_huggingface_available() -> bool
Indicate if the langchain_huggingface package is installed or
not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_huggingface_available
>>> is_langchain_huggingface_available()
zenpyre.utils.imports.is_langchain_ollama_available
cached
¶
is_langchain_ollama_available() -> bool
Indicate if the langchain_ollama package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_ollama_available
>>> is_langchain_ollama_available()
zenpyre.utils.imports.is_langchain_openai_available
cached
¶
is_langchain_openai_available() -> bool
Indicate if the langchain_openai package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_openai_available
>>> is_langchain_openai_available()
zenpyre.utils.imports.is_langchain_text_splitters_available
cached
¶
is_langchain_text_splitters_available() -> bool
Indicate if the langchain_text_splitters package is installed
or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_langchain_text_splitters_available
>>> is_langchain_text_splitters_available()
zenpyre.utils.imports.is_persista_available
cached
¶
is_persista_available() -> bool
Indicate if the persista package is installed or not.
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from zenpyre.utils.imports import is_persista_available
>>> is_persista_available()
zenpyre.utils.imports.langchain_anthropic_available ¶
langchain_anthropic_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_anthropic package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_anthropic_available
>>> @langchain_anthropic_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_chroma_available ¶
langchain_chroma_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_chroma package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_chroma_available
>>> @langchain_chroma_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_google_genai_available ¶
langchain_google_genai_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_google_genai package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_google_genai_available
>>> @langchain_google_genai_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_huggingface_available ¶
langchain_huggingface_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_huggingface package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_huggingface_available
>>> @langchain_huggingface_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_ollama_available ¶
langchain_ollama_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_ollama package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_ollama_available
>>> @langchain_ollama_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_openai_available ¶
langchain_openai_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_openai package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_openai_available
>>> @langchain_openai_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.langchain_text_splitters_available ¶
langchain_text_splitters_available(fn: F) -> F
Implement a decorator to execute a function only if
langchain_text_splitters package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import langchain_text_splitters_available
>>> @langchain_text_splitters_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.persista_available ¶
persista_available(fn: F) -> F
Implement a decorator to execute a function only if persista
package is installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
F
|
The function to execute. |
required |
Returns:
| Type | Description |
|---|---|
F
|
A wrapper around |
Example
>>> from zenpyre.utils.imports import persista_available
>>> @persista_available
... def my_function(n: int = 0) -> int:
... return 42 + n
...
>>> my_function()
zenpyre.utils.imports.raise_duckdb_missing_error ¶
raise_duckdb_missing_error() -> NoReturn
Raise a RuntimeError to indicate the duckdb package is
missing.
zenpyre.utils.imports.raise_faker_missing_error ¶
raise_faker_missing_error() -> NoReturn
Raise a RuntimeError to indicate the faker package is
missing.
zenpyre.utils.imports.raise_langchain_anthropic_missing_error ¶
raise_langchain_anthropic_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_anthropic
package is missing.
zenpyre.utils.imports.raise_langchain_chroma_missing_error ¶
raise_langchain_chroma_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_chroma
package is missing.
zenpyre.utils.imports.raise_langchain_google_genai_missing_error ¶
raise_langchain_google_genai_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_google_genai
package is missing.
zenpyre.utils.imports.raise_langchain_huggingface_missing_error ¶
raise_langchain_huggingface_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_huggingface
package is missing.
zenpyre.utils.imports.raise_langchain_ollama_missing_error ¶
raise_langchain_ollama_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_ollama
package is missing.
zenpyre.utils.imports.raise_langchain_openai_missing_error ¶
raise_langchain_openai_missing_error() -> NoReturn
Raise a RuntimeError to indicate the langchain_openai
package is missing.
zenpyre.utils.imports.raise_langchain_text_splitters_missing_error ¶
raise_langchain_text_splitters_missing_error() -> NoReturn
Raise a RuntimeError to indicate the
langchain_text_splitters package is missing.
zenpyre.utils.imports.raise_persista_missing_error ¶
raise_persista_missing_error() -> NoReturn
Raise a RuntimeError to indicate the persista package is
missing.
zenpyre.utils.json_to_structured ¶
Utilities for parsing raw JSON text emitted by a language model into a validated Pydantic model.
This module exists to work around language models (particularly small or local models, e.g. via Ollama) that do not reliably support native structured-output or tool-calling enforcement. Instead of relying on a framework's structured-output mechanism (which typically requires the model to emit a tool call), these utilities take the model's raw text response -- which may contain a JSON object wrapped in markdown code fences, surrounded by commentary, or with minor syntactic issues like trailing commas -- and attempt to extract, clean, parse, and validate it against a target Pydantic schema.
Typical usage:
from pydantic import BaseModel, Field
class WeatherReport(BaseModel):
city: str = Field(min_length=1)
temperature_celsius: float
condition: str
result = agent.invoke({"messages": [{"role": "user", "content": "..."}]})
raw_content = result["messages"][-1].content
report = parse_json_to_structured(raw_content, WeatherReport)
zenpyre.utils.json_to_structured.JsonStructuredOutputParseError ¶
Bases: Exception
Raised when a model's raw text output cannot be converted into the target schema.
This error covers two distinct failure modes, both surfaced through the same exception type for simplicity: 1. No valid JSON object could be extracted or decoded from the raw content. 2. A JSON object was successfully decoded, but it does not satisfy the target Pydantic schema (missing fields, wrong types, failed validators).
Attributes:
| Name | Type | Description |
|---|---|---|
raw_content |
The original, unmodified text that failed to parse or validate. Callers can use this for logging, debugging, or triggering a retry with a modified prompt. |
zenpyre.utils.json_to_structured.parse_json_to_structured ¶
parse_json_to_structured(
content: str, schema: type[T]
) -> T
Parse a model's raw text output into a validated instance of a Pydantic schema.
This is the core conversion function: given the free-form text content of
a language model's response, it attempts to locate a JSON object within
that text, decode it, and validate it against schema.
Parsing proceeds through the following stages, stopping at the first
stage that succeeds:
1. Strip any surrounding markdown code fence, then attempt json.loads
directly on the result.
2. Apply common JSON syntax fixes (e.g. trailing comma removal) to the
fence-stripped text, then attempt json.loads again.
3. Fall back to brace-matched extraction of the first top-level JSON
object anywhere in the original content, apply the same syntax
fixes, and attempt json.loads on the extracted substring.
If a JSON object is successfully decoded at any stage, it is then
validated against schema via schema.model_validate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
str
|
The raw text content of a model's response. May be plain JSON, JSON wrapped in a markdown code fence, or JSON embedded alongside other commentary text. |
required |
schema
|
type[T]
|
The Pydantic model class to validate the decoded JSON against. |
required |
Returns:
| Type | Description |
|---|---|
T
|
An instance of |
Raises:
| Type | Description |
|---|---|
JsonStructuredOutputParseError
|
If no valid JSON object can be decoded
from |
zenpyre.utils.json_to_structured.parse_json_to_structured_with_retry ¶
parse_json_to_structured_with_retry(
invoke_fn: Callable[[], str],
schema: type[T],
max_attempts: int = 3,
) -> T
Repeatedly invoke a model and parse its output until it matches a schema.
This provides a bounded retry loop around parse_json_to_structured for
cases where a single generation may occasionally produce invalid or
unparseable JSON (e.g. a non-deterministic small local model). Each
attempt performs a fresh model invocation via invoke_fn -- this function
does not reuse or repair a previous failed response, since re-generating
is typically more reliable than patching malformed output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
invoke_fn
|
Callable[[], str]
|
A zero-argument callable that performs one full model or
agent invocation and returns the raw text content of the response
(e.g. |
required |
schema
|
type[T]
|
The Pydantic model class each attempt's output is validated against. |
required |
max_attempts
|
int
|
The maximum number of invoke-and-parse attempts before giving up. Must be at least 1. |
3
|
Returns:
| Type | Description |
|---|---|
T
|
An instance of |
T
|
validates successfully. |
Raises:
| Type | Description |
|---|---|
JsonStructuredOutputParseError
|
If every attempt up to |
zenpyre.utils.rich ¶
Common utilities for rich.
zenpyre.utils.rich.configure_rich_logging ¶
configure_rich_logging(
level: int = INFO,
*,
fmt: str = "%(message)s",
datefmt: str = "[%Y-%m-%d %H:%M:%S]",
rich_tracebacks: bool = True,
markup: bool = True,
force: bool = False,
**kwargs: Any
) -> None
Configure the root logger to use
:class:rich.logging.RichHandler.
Sets up Python's standard :mod:logging module with a single
:class:~rich.logging.RichHandler so that all log output is rendered
through Rich's console, with coloured levels, timestamps, and optional
pretty-printed tracebacks.
After calling this function, obtain a logger for the current module with the standard idiom::
logger = logging.getLogger(__name__)
Using :func:logging.getLogger with __name__ gives the logger a
dotted name matching its import path (e.g. langchain_metrics.console),
which slots it correctly into the logging hierarchy and allows callers to
tune or silence it by name.
Note
:func:logging.basicConfig is a no-op if the root logger already
has handlers configured (e.g. if another library called
basicConfig first, or if you call this function more than once).
Pass force=True to remove any existing handlers and reconfigure
unconditionally.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
int
|
Minimum log level for the root logger. Accepts any constant
from :mod: |
INFO
|
fmt
|
str
|
Log record format string passed to :class: |
'%(message)s'
|
datefmt
|
str
|
Date/time format string for the |
'[%Y-%m-%d %H:%M:%S]'
|
rich_tracebacks
|
bool
|
When |
True
|
markup
|
bool
|
When |
True
|
force
|
bool
|
When |
False
|
**kwargs
|
Any
|
Additional keyword arguments forwarded verbatim to
:class: See the |
{}
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If an unrecognised keyword argument is passed that
:class: |
Example
>>> import logging
>>> from zenpyre.utils.rich import configure_rich_logging
>>> configure_rich_logging()
>>> logger = logging.getLogger(__name__)
>>> logger.info("Rich logging is active")
zenpyre.utils.rich.make_progressbar ¶
make_progressbar(*, transient: bool = False) -> Progress
Create a standardised Rich progress bar for use across the codebase.
Builds a Progress instance with a consistent column layout:
- A spinner indicating the task is active.
- Description text.
- A bar showing percentage complete.
- The percentage as a number (e.g. '42%').
- An M-of-N counter (e.g. '42/100').
- Elapsed time since the task started.
- Estimated time remaining.
Using this factory ensures all progress bars in the codebase share the same appearance. Use as a context manager to start and stop rendering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transient
|
bool
|
If True, the progress bar is cleared from the terminal when the context manager exits. Useful for short-lived tasks where the bar would clutter the output. Defaults to False. |
False
|
Returns:
| Type | Description |
|---|---|
Progress
|
A configured |
Example
>>> from zenpyre.utils.rich import make_progressbar
>>> rows = list(range(10))
>>> with make_progressbar() as progress:
... task = progress.add_task("Processing papers", total=len(rows))
... for row in rows:
... print(row)
... progress.advance(task)
...
zenpyre.utils.rich.make_spinner ¶
make_spinner(*, transient: bool = True) -> Progress
Create a Rich spinner for tasks where the total number of items is unknown.
Builds a :class:~rich.progress.Progress instance with a column
layout suited for indeterminate tasks:
- A spinner indicating the task is active.
- Description text.
- An M-of-N counter showing items processed so far (e.g.
42/?). - Processing speed (e.g.
12.3/s). - Elapsed time since the task started.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transient
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
Progress
|
A configured :class: |
Progress
|
use as a context manager. |
Example
>>> from zenpyre.utils.rich import make_spinner
>>> items = list(range(10))
>>> with make_spinner() as progress:
... task = progress.add_task("Processing items...", total=None)
... for item in items:
... progress.advance(task)
...
zenpyre.utils.rich.print_document ¶
print_document(
doc: Document,
max_length: int = 500,
console: Console | None = None,
compact_metadata: bool = False,
) -> None
Pretty-print a LangChain document to the terminal using rich.
Renders the document as a bordered panel titled with its id,
containing two nested panels: a content panel with the
document's page_content (truncated on a word boundary and
annotated with the omitted character count if it exceeds
max_length, with the total/truncated character count shown in
its subtitle), and, if metadata is non-empty, a metadata
panel listing entries sorted by key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
doc
|
Document
|
The document to display. |
required |
max_length
|
int
|
Maximum number of content characters to display
before truncating. Defaults to |
500
|
console
|
Console | None
|
An optional rich :class: |
None
|
compact_metadata
|
bool
|
If |
False
|
zenpyre.utils.rich.print_documents_metadata ¶
print_documents_metadata(
documents: Sequence[Document],
separator: str = "•",
console: Console | None = None,
) -> None
Pretty-print metadata for a sequence of documents, one line each.
Renders a bordered panel containing one line per document: the
document's id (if present) followed by its metadata entries,
sorted by key, rendered inline as key: value {separator} key: value.
Documents with no metadata show a dimmed placeholder instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
documents
|
Sequence[Document]
|
The documents whose metadata to display. |
required |
separator
|
str
|
The string used to separate metadata entries on
each line. Defaults to |
'•'
|
console
|
Console | None
|
An optional rich :class: |
None
|
zenpyre.utils.rich.print_markdown ¶
print_markdown(
msg: str,
*,
title: str | None = None,
title_align: AlignMethod = "left",
box: bool = True,
panel: bool = True,
max_length: int | None = None,
console: Console | None = None
) -> None
Render a Markdown string to the console, optionally inside a Rich panel.
Prints msg as rendered Markdown, wrapped in a
:class:~rich.panel.Panel unless panel is False. Uses the
provided console if given, otherwise falls back to the shared
instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
msg
|
str
|
The Markdown content to render. May contain any Markdown
syntax supported by :class: |
required |
title
|
str | None
|
Optional title displayed on the panel border. Pass
|
None
|
title_align
|
AlignMethod
|
Horizontal alignment of |
'left'
|
box
|
bool
|
If |
True
|
panel
|
bool
|
If |
True
|
max_length
|
int | None
|
Maximum number of characters of |
None
|
console
|
Console | None
|
Optional :class: |
None
|
Example
>>> from zenpyre.utils.rich import print_markdown
>>> print_markdown("**hello**", title="Demo")
>>> print_markdown("**hello**", title="Demo", box=False)
>>> print_markdown("**hello**", panel=False)
zenpyre.utils.rich.print_pretty ¶
print_pretty(
data: Any,
*,
title: str | None = None,
title_align: AlignMethod = "left",
box: bool = True,
panel: bool = True,
console: Console | None = None
) -> None
Render an arbitrary object to the console in a pretty format, optionally inside a Rich panel.
Prints data using :class:~rich.pretty.Pretty, wrapped in a
:class:~rich.panel.Panel unless panel is False. Uses the
provided console if given, otherwise falls back to the shared
instance. Works with any Python object — dicts, lists, dataclasses,
Pydantic models, and so on.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to render. Passed directly to
:class: |
required |
title
|
str | None
|
Optional title displayed on the panel border. Pass
|
None
|
title_align
|
AlignMethod
|
Horizontal alignment of |
'left'
|
box
|
bool
|
If |
True
|
panel
|
bool
|
If |
True
|
console
|
Console | None
|
Optional :class: |
None
|
Example
>>> from zenpyre.utils.rich import print_pretty
>>> print_pretty({"key": "value"}, title="Demo")
>>> print_pretty({"key": "value"}, title="Demo", box=False)
>>> print_pretty({"key": "value"}, panel=False)
zenpyre.utils.token_usage ¶
Contain utilities to compute token usage.
zenpyre.utils.token_usage.accumulate_token_usage ¶
accumulate_token_usage(
usage: dict[str, int], result: Any
) -> None
Add the token usage found in result to a running usage
total, in place.
This is a convenience wrapper around :func:get_token_usage for
callers that need to keep a running total across several calls
(e.g. one entry per iteration of a loop), instead of summing a
single batch upfront. result can be any shape accepted by
:func:get_token_usage, such as a single BaseMessage, the
dict returned by agent.invoke(...), or the list returned by
agent.batch(...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
usage
|
dict[str, int]
|
The running token usage totals to update, in place.
Missing keys are treated as |
required |
result
|
Any
|
Any object potentially containing |
required |
Example
>>> from langchain_core.messages import AIMessage, UsageMetadata
>>> from zenpyre.utils.token_usage import accumulate_token_usage
>>> usage = {}
>>> accumulate_token_usage(
... usage,
... AIMessage(
... content="hi",
... usage_metadata=UsageMetadata(input_tokens=10, output_tokens=5, total_tokens=15),
... ),
... )
>>> usage
{'input_tokens': 10, 'output_tokens': 5, 'total_tokens': 15}
zenpyre.utils.token_usage.format_token_usage ¶
format_token_usage(usage: UsageMetadata) -> str
Format token usage as a human-readable string for terminal display.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
usage
|
UsageMetadata
|
The token usage to format, as returned by
:func: |
required |
Returns:
| Type | Description |
|---|---|
str
|
A single-line string summarizing input, output, and total |
str
|
token counts, suitable for printing to a terminal. |
Example
>>> from langchain_core.messages import UsageMetadata
>>> from zenpyre.utils.token_usage import format_token_usage
>>> usage = UsageMetadata(input_tokens=1234, output_tokens=567, total_tokens=1801)
>>> print(format_token_usage(usage))
[tokens] in=1,234 | out=567 | total=1,801
zenpyre.utils.token_usage.get_token_usage ¶
get_token_usage(result: Any) -> UsageMetadata
Sum token usage across every AI message found anywhere within
result.
Unlike :func:get_invoke_token_usage and :func:get_batch_token_usage,
this function does not branch on the shape of result. Instead it
walks result depth-first (via coola's dfs_iterate) and
collects every AIMessage instance found, regardless of how deeply
it is nested. This makes it a convenient, shape-agnostic entry point
that works for a single BaseMessage, the dict returned by
agent.invoke(...), the list returned by agent.batch(...), or
any other structure that contains AIMessage objects.
If result contains no AIMessage instances (including cases
where result is of an unrecognized type, e.g. an int or a
plain str), this function returns all-zero usage rather than
raising an error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
Any
|
Any object potentially containing |
required |
Returns:
| Type | Description |
|---|---|
UsageMetadata
|
A |
zenpyre.utils.token_usage.log_token_usage ¶
log_token_usage(
result: Any, *, only_if_nonzero: bool = True
) -> None
Log the token usage for a single invocation or a batch of them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
Any
|
The dict returned by |
required |
only_if_nonzero
|
bool
|
If |
True
|