FuncWire
Portable contracts for Python callables.
FuncWire is a zero-dependency contract runtime for typed Python callables. It converts ordinary functions, bound methods, and callable objects into deterministic, machine-readable contracts with JSON Schema, strict recursive validation, annotation-directed codecs, exact Python signature binding, synchronous and asynchronous invocation, return contracts, and provider adapters.
Designed as infrastructure rather than framework glue, FuncWire gives RPC systems, workflow engines, plugin platforms, remote workers, interface generators, MCP servers, and LLM tool-calling stacks one stable intermediate representation for Python callable boundaries.
Engineering guarantees
- Provider-neutral core: no OpenAI, Anthropic, Gemini, MCP, or orchestration assumptions in the IR.
- Exact signature semantics: positional-only, keyword-only, defaults,
*args, and**kwargs. - Strict by default: external strings are not silently coerced into numbers or booleans.
- Structured codecs: dataclasses, TypedDicts, enums, UUIDs, paths, dates, times, and decimals.
- Deterministic contracts: modern JSON Schema, recursive
$defs/$ref, and versioned serialization. - Small dependency surface: no runtime dependencies and no provider SDK imports.
- Operational clarity: structured errors, sync/async separation, security guidance, and typed APIs.
FuncWire is an alpha. The v1 serialized contract is versioned, but the Python API may evolve before 1.0.
Install
pip install funcwire
Python 3.10 or newer is required.
First contract
from typing import Literal
from funcwire import spec
def weather(city: str, units: Literal["c", "f"] = "c") -> dict[str, float]:
"""Get current weather.
Args:
city: City to look up.
units: Temperature unit.
"""
return {"temperature": 21.0}
tool = spec(weather)
print(tool.name)
print(tool.input_schema)
values = tool.validate({"city": "Toronto"})
result = tool.call({"city": "Toronto", "units": "c"})
openai_tool = tool.export("openai")
mcp_tool = tool.export("mcp")
validate() is strict: "21" is not accepted for int or float. Decoding only happens when
the annotation justifies it; for example, a UUID string becomes uuid.UUID, and an object matching
a dataclass becomes that dataclass.
Annotated constraints
from typing import Annotated
from funcwire import Description, Max, Min, spec
def set_speed(
speed: Annotated[float, Description("Motor speed"), Min(0), Max(1)],
) -> None: ...
speed = spec(set_speed)
speed.validate({"speed": 0.5})
Description, Min, Max, MinLength, MaxLength, Pattern, and Examples map directly to
JSON Schema concepts and are enforced locally where applicable.
Signatures and invocation
FuncWire supports positional-only parameters, positional-or-keyword parameters, keyword-only
parameters, defaults, *args, and **kwargs. External calls are always objects. The keys for
variadic parameters contain an array and object respectively:
def invoke(a: int, /, *items: str, enabled: bool, **labels: float):
return a, items, enabled, labels
tool = spec(invoke)
tool.call(
{
"a": 1,
"items": ["x", "y"],
"enabled": True,
"labels": {"score": 0.8},
}
)
Calling .call() on an async function raises InvocationError. Use await tool.acall(data).
acall() can also invoke a synchronous callable without using a thread or executor.
Supported annotations
str,int,float,bool,None, andAnylist,tuple,set,frozenset, and string-keyeddictSequence,Iterable,Mapping,Optional,Union,Literal, andAnnotated- modern
|unions, resolvable aliases, bounded/constrainedTypeVar - dataclasses, nested/recursive dataclasses,
TypedDict, andEnum datetime,date,time,Path,UUID, andDecimal
Unresolvable forward references, non-string mapping keys, unsupported classes, non-scalar enum or
literal values, and unknown Annotated metadata fail explicitly. Arbitrary classes are not guessed.
Custom types
from funcwire import register_type
register_type(
MyType,
schema={"type": "string", "format": "my-type"},
validator=lambda value: isinstance(value, str),
decoder=MyType.parse,
encoder=str,
)
Registration is atomic and concurrent reads use a lock-protected registry. A newly built contract
captures schema metadata, while validation and codecs consult the current registry. Register types
during application startup for stable behavior. unregister_type(MyType) is intended primarily for
tests and plugin lifecycle management.
Provider adapters
tool.export("openai"), tool.export("anthropic"), tool.export("gemini"), and
tool.export("mcp") return plain dictionaries. No provider SDK is imported or required. These are
metadata transformations; the universal FuncSpec remains provider-neutral.
Results, serialization, and compatibility
Use validate_result() for external result data and encode_result() for Python results. to_dict()
and to_json() serialize only the contract description, tagged with funcwire_version: 1; executable
callables are deliberately not serialized. diff(old, new) reports conservative compatibility
changes to parameters and returns.
Validation is not a sandbox
tool.call(data) executes the wrapped Python callable normally with the process's permissions.
Validation establishes data shape and type; it does not make untrusted code safe, authorize an
operation, limit resource use, or isolate side effects. Provider exports provide no security boundary.
Limitations
- Callable reconstruction from serialized contracts is intentionally unsupported.
- Generator functions, overloaded dispatch semantics, protocols, arbitrary user classes, and provider-specific strict-schema subsets do not receive special handling yet.
Decimaluses a JSON string to avoid precision loss.- Abstract sequences decode to
list; abstract mappings decode todict. - Compatibility analysis is conservative; type changes are classified as potentially breaking rather than attempting formal schema subsumption.
See the architecture, competitive analysis, and release audit.
Development
python -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest --cov=funcwire --cov-branch
.venv/bin/ruff check .
.venv/bin/mypy src
.venv/bin/python -m build
Contributions are welcome under the Apache License 2.0. See CONTRIBUTING.md.
Metadata
Release files for funcwire 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| funcwire-0.1.0.tar.gz | 31.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| funcwire-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 56.0 kB
Release files / funcwire-0.1.0.tar.gz
| Download URL | funcwire-0.1.0.tar.gz |
|---|---|
| Size | 31.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a813a703670f6f70d1c1c06aa54116259df6bb308f8534d591fb1a5431bb1436
|
|
BLAKE2b-256 checksum How to use checksums |
d5a45f8f3a9883293d358684d6f27fff801b1cd34fbf8ca4978ebd90be5a3226
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / funcwire-0.1.0-py3-none-any.whl
| Download URL | funcwire-0.1.0-py3-none-any.whl |
|---|---|
| Size | 24.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5599a077b668e4e897c277309ce0d6b99e7fd4b0a46bf25c590c775241e7633
|
|
BLAKE2b-256 checksum How to use checksums |
6672afb71ea3747cf1641e1e94238c9f3e4fb5f5dd37301f5365e6a3ba0a11a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|