Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

guatiao for Python

PyPI version Python versions License: MPL 2.0 CI

Python bindings for the guatiao ABI: one value model for passing data between languages, a JSON Schema that describes a value, and a registry that loads plugin libraries. Pure Python over ctypes, with no dependencies and nothing to compile. It works with any shared library that exports the ABI, whether that is guatiao itself or an application that carries it.

Status: alpha. A pure Python package: it carries no native library, you point it at one.

Features

  • Value trees as Python objects — build, read and change a tree in place, or convert it to and from plain dict/list data.
  • Numbers keep their exact text — 1.10 stays 1.10 and an integer of any size survives; you choose int/float, Decimal or str when reading.
  • Be a plugin host — scan a directory, filter on what a library declares before loading it, list providers best first, build an instance from a configuration, call a provider through its table.
  • JSON, TOML and YAML, and form checks against a schema, through the guatiao-serde and guatiao-intake libraries when they are present.
  • Importing never fails — a native library is looked for on first use, and a missing one says every place that was searched.

Installation

pip install guatiao

The package drives a shared library that exports the guatiao ABI. Say where it is:

export GUATIAO_LIBRARY=/opt/myapp/lib             # a directory holding guatiao.dll / libguatiao.so / libguatiao.dylib
export GUATIAO_LIBRARY=/opt/myapp/myapp.dll       # or the file itself, whatever it is called

Without the variable the package tries the system's library search (ctypes.util.find_library("guatiao")). Nothing is loaded at import time: import guatiao always succeeds, and the first native call raises guatiao.LibraryNotFound, naming every place it looked. A library that exports only part of the ABI is fine: calling a function it lacks raises guatiao.MissingSymbol naming it, and everything else works.

Exports from Needed for
guatiao values, the registry, provider tables
guatiao_serde guatiao.serde: JSON, and TOML and YAML when the library has them
guatiao_intake guatiao.intake

guatiao_serde and guatiao_intake are looked for the same way, in the same directory, only when guatiao.serde or guatiao.intake is first used.

Quick start

Values

from guatiao import Value

with Value(host="10.0.0.1", port=5900, tags=["a", "b"]) as v:
    m = v.as_map()
    m["port"] = 5901
    m["tags"].as_list().append("c")

    m["port"].to_python()        # 5901
    list(m)                      # ['host', 'port', 'tags'], insertion order
    "missing" in m               # False; m["missing"] raises KeyError
    m["port"].int_or(22)         # 5901, or 22 if it were not an integer
    v.to_python()                # back to a plain dict

A Value owns its tree and frees it on close() or at the end of the with block. m["tags"] is a Ref: a path into the tree that is walked again on every access, so it stays valid while the tree grows around it.

Numbers keep their exact text. 1.10 stays 1.10, and an integer of any size survives. Choose what comes back when reading:

v = Value.number("1.10")
v.to_python()                    # 1.1   ("auto": int when it looks like one, else float)
v.to_python(numbers="decimal")   # Decimal('1.10')
v.to_python(numbers="str")       # '1.10'

Value(1), Value(True), Value("x"), Value([1, 2]) and Value({"host": "h"}, port=1) all work, as the builtins would. None becomes null, bytes the bytes kind, a dict with str keys a map. A missing value reads back as guatiao.ABSENT, which is falsy and is not None. A float that is nan or infinite raises ValueError.

Hosting plugins

from guatiao import Registry

with Registry("my-host", "1.0") as reg:
    report = reg.scan_dir("/opt/myapp/plugins", rules="kind=greeter")
    for p in reg.providers("greeter"):           # best first
        print(p["key"], p["version"], p["from"])

    reg.why_not("codec")                         # why nothing serves a kind

    schema = reg.provider_config("acme_shouter")   # borrowed; do not close
    with reg.create("acme_shouter", {"prefix": "hey"}) as instance:
        ...

    reg.retire("acme")                           # out of the registry, still mapped
    reg.collect()                                # ["acme"]: closed, once nothing of it is alive
    reg.unload("acme2")                          # out and unmapped at once, when it agrees

Every answer that is data comes back as a plain dict or list. rules is one [!]KEY=VALUE per line and filters on what a library declares about itself, before it is loaded.

To call a provider, describe its table with the ctypes.Structure the kind's C header declares and let the package check it:

from guatiao import kinds

table_ptr, size, ctx = reg.provider_table("acme_greeter", kind="greeter")
greeter = kinds.table(table_ptr, size, GreeterVtable, floor_hash=FLOOR_HASH)
greeter.greet(ctx, name, out_map, err)

tests/test_greeter.py is a complete worked example.

JSON, TOML, YAML and forms

from guatiao import intake, serde

value = serde.loads('{"port": 5900}')                  # format="json" by default
text = serde.dumps(value, format="yaml")
pretty = serde.dumps(value, pretty=True)

problem = intake.check(schema, form_doc)               # None when the form fits the schema
sections = intake.layout(schema, form_doc)
shown = intake.is_visible(schema, form_doc, "tls.verify", values)

# A front end that keeps text under flat keys (`agent[0].name`, `env[PATH]`):
field = intake.resolve(schema, "auth.password")        # the schema field, or None
keys = intake.flat_keys(schema, "auth")                # ["auth", "auth.username", ...]
store = intake.flatten(schema, "auth", auth_value)     # {"auth": "userpass", ...}
back = intake.unflatten(schema, "auth", store)         # a Value, or None

A document that does not parse raises guatiao.errors.ParseError, a ValueError carrying format, line and column (from 1; 0 when the parser did not say where). A format the loaded library does not export raises NotImplementedError naming it.

Errors

A failed native call raises guatiao.GuatiaoError, as one of BadValue, AllocFailed, WrongKind, NotFound, NullArgument, Gone, Unsupported, Busy or Internal. .status is the C status code, and .message carries a provider's own words when it gave any.

Known limits

  • Reading JSON writes an exponent's sign: 1e400 parses as 1e+400. Everything else about a number's text survives a round trip.
  • List.insert and list[i] = x rebuild the list, because the C ABI only appends and removes. append and del list[i] are direct.
  • Runs where CPython can load a native library, on 64-bit targets only: every pointer and size_t in the ABI is eight bytes. It does not run in a browser (Pyodide has no way to load the library) or on a 32-bit interpreter.
  • A Registry is used from one thread at a time, and a Value is not shared between threads without a lock of your own.
  • Registry.unload unmaps a library once its own unload function agrees, and collect a retired one; neither can see a table or ctx you took with provider_table. Let those go first: they are valid until the library is closed, by unload, or once retired by collect or the registry's next load, retire or unload.

API overview

Module Purpose
guatiao Value, Ref, Map, List, ABSENT, Registry, Instance, the error classes
guatiao.registry the plugin host: load, scan, list, rank, configure, instantiate
guatiao.kinds check and cast a provider's function table
guatiao.serde loads / dumps for JSON, TOML, YAML
guatiao.intake check, layout, is_visible, at, for_schema, form_for, resolve, flat_keys, flatten, unflatten

AGENTS.md is the full API reference and ships inside the wheel. The C ABI is described by guatiao.h.

Development

pip install -e "bindings/python[dev]"     # from the repository root, in a virtual environment
pytest bindings/python/tests -rs

The tests need the three libraries and the repository's example plugins in one directory. conftest.py points GUATIAO_LIBRARY at the repository's build output when the variable is unset; the repository's own AGENTS.md says how to produce it. With everything present nothing skips; a test that needs a missing library skips and says which. CI runs the suite on Python 3.9 and 3.14.

License

MPL 2.0 — see LICENSE.

Metadata

Release files for guatiao 0.0.0a1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for guatiao 0.0.0a1
File Size Uploaded Provenance
guatiao-0.0.0a1.tar.gz 42.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for guatiao 0.0.0a1
File Interpreter ABI Platform
guatiao-0.0.0a1-py3-none-any.whl Python 3 none any Details

Total release size: 82.1 kB

Release files / guatiao-0.0.0a1.tar.gz

Download URL guatiao-0.0.0a1.tar.gz
Size 42.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e8d6ad40e3aec742625ff3397fe131ff5fd8e904242f312838856a3678884895
BLAKE2b-256 checksum
How to use checksums
1fd155d29898e51a4c595e0deaa33d673e2695f069fc1880864c3f8ea069180e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Release files / guatiao-0.0.0a1-py3-none-any.whl

Download URL guatiao-0.0.0a1-py3-none-any.whl
Size 39.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
73585e6056c943050f140a4cf1282afab6bd37b01d03c81effab3fca46f9d6ae
BLAKE2b-256 checksum
How to use checksums
ab65f4003952db72a99d83407679852cca627661382e5fac54d8b03382a6ce4a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

Security for guatiao 0.0.0a1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for guatiao-0.0.0a1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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