The connector model¶
A connector is a small Python callable plus metadata. You write a
def that fetches data and returns a plain pandas object (or a scalar
or dict); the @connector decorator turns it into a frozen Connector, and the
framework wraps whatever you return into a Result
with framework-built Provenance. The function's parameters are
the connector's parameters — there is no wrapper request object — which is what
makes connectors directly callable by agents.
This page is the map for the connector subsection. It covers what a connector
is, the @connector decorator at a glance, the rules every connector obeys, and
one compact end-to-end example. The deeper pages drill into each piece.
What a connector is¶
Connector is a frozen dataclass that pairs a name, a description, the
wrapped fn, its inspect.Signature, and optional metadata (tags,
properties, an output schema, declared secrets, namespace hints). You do
not construct it directly — you decorate a function:
import pandas as pd
from parsimony.connector import connector
@connector
def demo_search(query: str) -> pd.DataFrame:
"""Search demo series by keyword."""
return pd.DataFrame(
{"id": ["A", "B"], "title": [f"Series about {query}", "Another"]}
)
result = demo_search(query="GDP")
print(result.provenance.source) # demo_search
print(result.provenance.params) # {'query': 'GDP'}
print(len(result.raw)) # 2
Calling a connector returns its result directly. The return value is always a
Result. When the connector returns a DataFrame or Series the result is
tabular (result.is_tabular is True); otherwise raw carries the scalar
or dict unchanged.
Import path
connector, loader, enumerator, Connector, and Connectors are
re-exported at the package root, so from parsimony import connector also
works.
The decorator at a glance¶
@connector works bare or called with options:
from parsimony.connector import connector
from parsimony.result import Column, ColumnRole, OutputSpec
OUTPUT = OutputSpec(
columns=[
Column(name="date", role=ColumnRole.KEY, namespace="demo"),
Column(name="value", role=ColumnRole.DATA),
]
)
@connector(
name="demo_fetch",
output=OUTPUT,
tags=["demo"],
secrets=("api_key",),
)
def fetch(series_id: str, api_key: str) -> pd.DataFrame:
"""Fetch demo observations for a series."""
return pd.DataFrame({"date": ["2020-01-01"], "value": [1.0]})
| Argument | Default | Meaning |
|---|---|---|
name |
fn.__name__ |
Connector identity; becomes provenance.source. |
description |
stripped fn.__doc__ |
Required. Human/agent-facing summary. |
output |
None |
An OutputSpec — a passive declaration of column roles attached to the result, never applied to the data. |
tags |
() |
Free-form labels (loader/enumerator set them automatically). |
properties |
{} |
Read-only metadata you can filter the collection on. |
secrets |
() |
Parameter names stripped from provenance; validated against the signature. |
requires |
() |
Env-var names a call needs to succeed; surfaced in the cards, never read by core. |
loader and enumerator are stricter factories
built on the same machinery — they add output-schema contracts and a verb tag.
The rules every connector follows¶
These invariants are enforced by the framework — most at decoration time, so a malformed connector fails as soon as the module imports.
Parameters are the call surface¶
A connector exposes flat, top-level parameters. There is no bundled
params: SomeModel object — the conformance suite (see
conformance testing) actively forbids annotating a
public params parameter as a Pydantic BaseModel. Scalar parameters keep the
call surface legible to an agent and let the framework record exactly which
arguments produced a result.
The function must be synchronous¶
The decorator rejects an async def at decoration time:
from parsimony.connector import connector
try:
@connector
async def not_sync(q: str) -> str: # async is rejected
"""Async functions are rejected."""
return q
except TypeError as exc:
print(exc) # not_sync: connector function must be synchronous; ...
A description is mandatory¶
description defaults to the stripped docstring; pass description= to override
it. If both are empty the decorator raises ValueError:
try:
@connector
def undocumented(q: str) -> str:
return q
except ValueError as exc:
print(exc) # undocumented: add a docstring or pass description= ...
Return raw data — never a Result¶
A connector returns raw data: a pandas.DataFrame/Series, a scalar, or a
dict. The framework builds the output envelope (the Result and its
Provenance); a connector that tries to build it itself fails at call time.
Returning the wrong shape raises TypeError
Returning a Result, or a (data, properties) tuple, raises TypeError
from inside the call — put provider facts in DataFrame columns and let the
framework wrap them.
Why raw data? It keeps the connector author's job to one thing — fetch and shape
the data — while the framework owns provenance, secret stripping, and attaching
the declarative output schema. It also means a connector cannot
fabricate provenance: Provenance.source, fetched_at, and params are always
framework-built. The output schema, if set, is attached to the result as-is —
the framework never coerces, renames, reorders, or drops a column based on it; any
type conversion the data needs is your connector's job, in the function body.
Secrets are declared, not exposed¶
Names listed in secrets= must be real parameters (an unknown name raises
ValueError at decoration time). Their values are stripped from
provenance.params whether they were supplied at call time or fixed with
bind. Combined with bind, this is how you inject an API key without it
appearing in agent-facing cards or recorded provenance — see
calling, binding, and composing.
A compact end-to-end example¶
Define a connector, attach an output schema, bind a secret, collect it into a
Connectors bundle, and call it by name. This runs with only parsimony-core
installed — no network, no plugins, no optional extras.
import pandas as pd
from parsimony.connector import Connectors, connector
from parsimony.result import Column, ColumnRole, OutputSpec
OUTPUT = OutputSpec(
columns=[
Column(name="date", role=ColumnRole.KEY, namespace="demo"),
Column(name="value", role=ColumnRole.DATA),
]
)
@connector(output=OUTPUT, tags=["demo"], secrets=("api_key",))
def demo_fetch(series_id: str, api_key: str) -> pd.DataFrame:
"""Fetch demo observations for a series id."""
# A real connector would call an HTTP API here using api_key.
return pd.DataFrame({"date": ["2020-01-01", "2020-02-01"], "value": [1.0, 2.0]})
# Bind the secret once; it disappears from the call surface and provenance.
wired = demo_fetch.bind(api_key="s3cr3t")
bundle = Connectors([wired])
print(bundle.names()) # ['demo_fetch']
result = bundle["demo_fetch"](series_id="X1")
print(result.provenance.source) # demo_fetch
print(result.provenance.params) # {'series_id': 'X1'} — api_key stripped
print(result.raw["value"].tolist()) # [1.0, 2.0]
A few things to notice:
bundle["demo_fetch"](...)is the canonical execution pattern:connectors[name](**kwargs).Connectors.__getitem__takes a connector name, not an integer index.bindreturns a new connector —Connectoris frozen — withapi_keyfixed and removed from the public signature.result.raw["value"]is exactly thefloatvalues the function returned —OutputSpecdeclaresvalueasDATAbut never touches the values themselves.
You merge bundles with the + operator (bundle_a + bundle_b); there is no
.merge method. The collection also gives you filter, search,
collection-wide bind, and the describe() / to_llm() prompt projections.
The connector subsection¶
| Page | What it covers |
|---|---|
| Defining connectors | The @connector decorator in depth: defaults, validation timings, namespace hints, and the describe() / to_llm() projections. |
| Loaders and enumerators | The two stricter verbs — fetching observation values vs discovering entities — and their output-schema contracts. |
| Calling, binding, and composing | Invoking connectors, bind, and the immutable Connectors collection. |
| Results and output schemas | Result (tabular when data is a DataFrame), OutputSpec / Column / ColumnRole, Provenance, and the entity projection. |
| Errors | The typed, agent-facing exception taxonomy connector authors raise. |
| HTTP transport | The HTTP layer (HttpClient, retry policy, redaction) that connector authors build on. |
See also¶
- Defining connectors — the decorator and its rules in full
- Calling, binding, and composing —
bindand theConnectorscollection - Results and output schemas — what a connector's return value becomes
- Plugins and providers — how connectors ship as
parsimony-<name>distributions