Connections & OAuth¶
A connection holds the credentials a source authenticates with; the OAuth variants renew them through a provider.
connection
¶
Create a Connection subclass from a decorated class.
Can be used bare or with arguments:
@connection
class MyConnection:
host: str = "localhost"
port: int = 5432
username: str
password: str
@connection(key="custom", name="Custom Connection")
class OtherConnection:
url: str
Class-level traits, identity (key, name, icon, tags) and behaviour (oauth), belong in the decorator; the class body declares fields:
@connection(
name="Amazon Ads",
oauth=OAuthConfig("amazon", scope="advertising::campaign_management"),
)
class AmazonAdsConnection(OAuthConnection):
location: str = SelectField(...)
The decorated class's annotations and attributes become the Connection subclass
body. Since Connection extends BaseSettings, fields can still be loaded from
environment variables.
relations maps a relation name to a Relation, a component class, or a list
of component classes narrowing the relation the decorated class declares under
that name. Every other keyword is one of the public ClassVars and field defaults
the decorated class declares, or Connection itself for a plain class (key,
name, icon, tags, auto_renew, and oauth on an
OAuthConnection). An unknown name is a TypeError at decoration, naming the
subclass that declares it when one does. Building the class fails with a
TypeError if oauth.fields maps token response keys to model fields the
class does not declare.
Connection
¶
Bases: interloper.resource.base.Resource, interloper.operation.base.Operation
A resource for database/service connection credentials.
Like every Resource, connection values can be loaded from
environment variables, .env files, or passed directly:
class MyConnection(Connection):
host: str = "localhost"
port: int = 5432
username: str
password: str
# Loads USERNAME, PASSWORD from environment if not passed explicitly
connection = MyConnection()
Connections that support OAuth subclass OAuthConnection (custom token
shape) or RefreshTokenOAuthConnection (standard refresh-token trio);
the oauth config lives there, not on this base.
billable
¶
bool(x) -> bool
Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
capture_traceback
¶
bool(x) -> bool
Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
enabled
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
internal_fields
¶
frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object
Build an immutable unordered collection of unique elements.
key
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
kind
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
model_config
¶
model_config = {'extra': 'forbid', 'arbitrary_types_allowed': True, 'validate_default': True, 'case_sensitive': False, 'env_prefix': '', 'env_prefix_target': 'variable', 'nested_model_default_partial_update': False, 'env_file': None, 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_none_str': None, 'env_parse_enums': None, 'cli_prog_name': None, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_hide_none_type': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_use_class_docs_for_groups': False, 'cli_show_env_vars': False, 'cli_exit_on_error': True, 'cli_prefix': '', 'cli_flag_prefix_char': '-', 'cli_implicit_flags': False, 'cli_ignore_unknown_args': False, 'cli_kebab_case': False, 'cli_shortcuts': None, 'json_file': None, 'json_file_encoding': None, 'yaml_file': None, 'yaml_file_encoding': None, 'yaml_config_section': None, 'toml_file': None, 'secrets_dir': None, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'enable_decoding': True, 'ignored_types': (<class 'interloper.serializable.base.IgnoredDescriptor'>, <class 'interloper.component.relation.Relation'>)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
relations
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
renewal_interval
¶
Difference between two datetime values.
timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
All arguments are optional and default to 0. Arguments may be integers or floats, and may be positive or negative.
retry
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
tags
¶
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
state_model
¶
Bases: pydantic.main.BaseModel
Machine-owned connection state (see Component.state_model).
Written by the renewal pipeline: the renewal controller stamps a
provisional next_renewal_at when it enqueues a renewal run, and the
run overwrites it with the real next due time on completion. Timestamps
are canonical timezone-aware ISO-8601 strings, compared lexicographically
in SQL like JobState's. last_run_at and last_run_status are
stamped by run completion on every run's component — plumbing, hidden
from state columns.
model_config
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
check
¶
Verify this connection with a lightweight authenticated call.
Override in a subclass (sync or async) to make the cheapest call
that proves the credentials work — often a one-line delegation to an
existing @fetch_field_provider method. Like fetch providers, it
may run inside the API process, so it must use lightweight HTTP
(httpx2), never a heavy provider SDK.
Returns True when the connection works, False when it
provably doesn't. Exceptions are also failures: httpx2 errors are
categorised by the caller (401/403 → bad credentials, timeouts →
network); raise ConnectionCheckError to
surface a curated message instead.
checkable
¶
Whether this connection class implements check.
Returns:
| Type | Description |
|---|---|
bool
|
True when the class overrides the base hook. |
definition
¶
definition() -> ResourceDefinition
Advertise check / renew support so UIs can offer them.
Returns:
| Type | Description |
|---|---|
ResourceDefinition
|
The resource definition with |
ResourceDefinition
|
|
ResourceDefinition
|
when the class has nothing to renew: both govern the renewal and |
ResourceDefinition
|
would be inert. |
execute
¶
execute(context: OperationContext) -> OperationResult
Renew this connection's credentials: the connection's operation.
The template over renew: caps the exchange at
_RENEWAL_TIMEOUT, returns any rotated credential fields as
config effects, and stamps the next due time — half the reported
validity when the provider gives one (floored at 15 minutes), the
class's renewal_interval otherwise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
OperationContext
|
The facts this execution is scoped to, unused — a renewal needs nothing beyond the connection itself. |
required |
Returns:
| Type | Description |
|---|---|
OperationResult
|
The renewal's effects. |
failure
¶
failure(error: Exception) -> OperationResult
Describe a failed renewal: a curated message plus a retry slot.
The message comes from renewal_failure_message, so raw
provider errors (which embed credentials in URLs) never reach the
connection's state or the run's failure event; the retry slot makes
the connection due again without waiting a full interval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error
|
Exception
|
The exception |
required |
Returns:
| Type | Description |
|---|---|
OperationResult
|
A failed result stamping the curated error and the retry slot. |
model_post_init
¶
Default id to a generated UUID if not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Any
|
The pydantic validation context, unused here. |
required |
renew
¶
renew() -> Renewal
Renew this connection's credentials before they age out.
Override in a subclass (sync or async) to exchange the stored
credential for a fresh one — same execution contract as
check: lightweight HTTP (httpx2), never a heavy provider
SDK. OAuth connections need no override: OAuthConnection derives
the whole flow from the provider and the oauth.fields mapping.
The renewal pipeline persists the returned Renewal.fields
into the stored config and schedules the next renewal; a raised
exception marks the renewal failed (surfaced on the connection's
state), typically meaning the credential is dead and a human must
re-consent.
renewable
¶
Whether this connection class implements renew.
Returns:
| Type | Description |
|---|---|
bool
|
True when the class overrides the base hook. |
renewal_failure_message
¶
Describe a failed renewal in terms that are safe to persist.
Provider token exchanges carry credentials in URLs and bodies, and
httpx2 error strings embed the request URL — so raw messages must
never reach the connection's state or the run's error event.
HTTP-layer failures collapse to their category; anything else formats
through format_exception (which already
strips pydantic input values).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error
|
Exception
|
The exception |
required |
Returns:
| Type | Description |
|---|---|
str
|
A short, curated message. |
ConnectionState
¶
Bases: pydantic.main.BaseModel
Machine-owned connection state (see Component.state_model).
Written by the renewal pipeline: the renewal controller stamps a
provisional next_renewal_at when it enqueues a renewal run, and the
run overwrites it with the real next due time on completion. Timestamps
are canonical timezone-aware ISO-8601 strings, compared lexicographically
in SQL like JobState's. last_run_at and last_run_status are
stamped by run completion on every run's component — plumbing, hidden
from state columns.
model_config
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
OAuthConnection
¶
Bases: interloper.connection.base.Connection
OAuth connection base — drives the "Sign in with X" form from oauth.
Carries the oauth config but no credential fields. Subclass this
directly when the connection's credential fields are named differently from
the standard trio, declaring its own fields and a matching
OAuthConfig.fields mapping:
@connection(oauth=OAuthConfig("tiktok", fields={"refresh_token": "access_token"}))
class TiktokAdsConnection(OAuthConnection):
access_token: str = SecretField()
OAuthConfig.fields maps the OAuth roles (client_id /
client_secret / refresh_token) to the connection's field names, and
drives the whole form: those fields are hidden in sign-in mode, the
client_id / client_secret fields are resolved from env, and the
refresh_token field receives the token from the sign-in response.
Most connections use the standard refresh-token flow and should subclass
RefreshTokenOAuthConnection instead, which declares the trio.
enabled
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
key
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
model_config
¶
model_config = {'extra': 'forbid', 'arbitrary_types_allowed': True, 'validate_default': True, 'case_sensitive': False, 'env_prefix': '', 'env_prefix_target': 'variable', 'nested_model_default_partial_update': False, 'env_file': None, 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_none_str': None, 'env_parse_enums': None, 'cli_prog_name': None, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_hide_none_type': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_use_class_docs_for_groups': False, 'cli_show_env_vars': False, 'cli_exit_on_error': True, 'cli_prefix': '', 'cli_flag_prefix_char': '-', 'cli_implicit_flags': False, 'cli_ignore_unknown_args': False, 'cli_kebab_case': False, 'cli_shortcuts': None, 'json_file': None, 'json_file_encoding': None, 'yaml_file': None, 'yaml_file_encoding': None, 'yaml_config_section': None, 'toml_file': None, 'secrets_dir': None, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'enable_decoding': True, 'ignored_types': (<class 'interloper.serializable.base.IgnoredDescriptor'>, <class 'interloper.component.relation.Relation'>)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
relations
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
retry
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
definition
¶
definition() -> ResourceDefinition
Inject the x-oauth extension so the form can render the button.
The extension carries the provider metadata and the fields mapping;
the form uses fields to hide the credential fields in sign-in mode
and to place the token returned by the flow.
Returns:
| Type | Description |
|---|---|
ResourceDefinition
|
The resource definition, enriched when |
env_credential
¶
The in-house OAuth credential for suffix (e.g. "CLIENT_ID").
Read from INTERLOPER_<PROVIDER>_<SUFFIX> — the same vars the
token-exchange endpoint uses. A mode="before" validator falls back to
this for its declared credential fields, so a required field can be
satisfied by the in-house app. Returns None when unset, so a field
left unfilled stays None and fails the required check rather than
passing as an empty string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
suffix
|
str
|
Credential field suffix — |
required |
Returns:
| Type | Description |
|---|---|
str | None
|
The env credential value, or |
model_post_init
¶
Default id to a generated UUID if not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Any
|
The pydantic validation context, unused here. |
required |
renew
¶
renew() -> Renewal
Renew the stored credential through the provider's refresh flow.
One implementation serves every OAuth connection, driven entirely by
what is already declared: the registered provider builds the grant
and parses its response (dialect included — Facebook's
fb_exchange_token, Microsoft's scoped refresh), and
oauth.fields names which connection fields play the grant's
credential roles, the rotated credential landing back in the field
playing the refresh-token role. Renewing keeps inactivity-expiring
credentials alive and surfaces a dead one as a failure instead of a
later run error.
Returns:
| Type | Description |
|---|---|
Renewal
|
The rotated credential field when the provider issued a new one, |
Renewal
|
and the credential's validity when reported. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
When the connection is not renewable (see
|
renewable
¶
Whether this connection can renew — derived, never hand-written.
A bespoke renew override always counts. Otherwise renewability
follows from the declarations alone: the registered provider must
have a refresh flow (supports_refresh) and oauth.fields must
name a connection field for each of the grant's credential roles.
A qualifying connection renews with zero renewal code, and one whose
provider has no refresh flow (TikTok: tokens do not expire) is
excluded automatically.
Returns:
| Type | Description |
|---|---|
bool
|
True when renewal is overridden or fully derivable. |
resolve_field
¶
Fill data[field] from value when the field is blank and value is set.
A mode="before" resolve_credentials validator uses this per field.
Leaves the field absent when there is no value, so a required field fails
with "Field required" rather than being satisfied by an empty value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
The raw pre-validation input, mutated in place. |
required |
field
|
str
|
Name of the model field to fill. |
required |
value
|
str | None
|
The value to place, or |
required |
RefreshTokenOAuthConnection
¶
Bases: interloper.connection.base.OAuthConnection
A connection using the standard OAuth2 refresh-token flow.
Declares the credential trio — client_id / client_secret (in-house
OAuth credentials resolved from env) and refresh_token (filled on
sign-in) — which the default OAuthConfig.fields mapping targets, so the
standard connections only add their own fields:
@connection(oauth=OAuthConfig("linkedin", scope="r_ads"))
class LinkedinAdsConnection(RefreshTokenOAuthConnection):
account_id: str
Connections whose credential fields are named differently subclass
OAuthConnection directly, declare their own fields, and inject them from
env with a mode="before" validator calling env_credential.
All three fields are required. client_id / client_secret may be
supplied by the in-house per-provider credentials
(INTERLOPER_<PROVIDER>_CLIENT_ID / INTERLOPER_<PROVIDER>_CLIENT_SECRET — the
same vars the token-exchange endpoint reads), injected before validation so
the sign-in flow can omit them and the in-house secret is never sent to the
browser or stored per connection. An explicit value overrides the in-house
app; when neither the caller nor env supplies one, the required check fails.
enabled
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
key
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
model_config
¶
model_config = {'extra': 'forbid', 'arbitrary_types_allowed': True, 'validate_default': True, 'case_sensitive': False, 'env_prefix': '', 'env_prefix_target': 'variable', 'nested_model_default_partial_update': False, 'env_file': None, 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_none_str': None, 'env_parse_enums': None, 'cli_prog_name': None, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_hide_none_type': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_use_class_docs_for_groups': False, 'cli_show_env_vars': False, 'cli_exit_on_error': True, 'cli_prefix': '', 'cli_flag_prefix_char': '-', 'cli_implicit_flags': False, 'cli_ignore_unknown_args': False, 'cli_kebab_case': False, 'cli_shortcuts': None, 'json_file': None, 'json_file_encoding': None, 'yaml_file': None, 'yaml_file_encoding': None, 'yaml_config_section': None, 'toml_file': None, 'secrets_dir': None, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'enable_decoding': True, 'ignored_types': (<class 'interloper.serializable.base.IgnoredDescriptor'>, <class 'interloper.component.relation.Relation'>)}
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
relations
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
retry
¶
The read-through descriptor installed over one inherited field.
A data descriptor, so it wins over the instance __dict__ on reads while
Pydantic keeps writing there: the stored value is the override, and the
read is the effective value.
model_post_init
¶
Default id to a generated UUID if not provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
Any
|
The pydantic validation context, unused here. |
required |
resolve_credentials
¶
Inject blank client_id / client_secret from the in-house env creds.
Runs before validation so the in-house app can satisfy these required fields when the caller omits them; an explicit value is left untouched.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The raw pre-validation input; only a |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The (possibly augmented) input data. |
Renewal
¶
Bases: pydantic.main.BaseModel
The outcome of a successful Connection.renew.
fields maps connection field names to their renewed values — the
platform persists them into the stored (encrypted) config, so a rotated
or re-issued credential replaces the aging one. Empty means the renewal
succeeded but nothing needs persisting (a pure keep-alive).
expires_in is the renewed credential's validity in seconds when the
provider reports one; the platform schedules the next renewal from it
(with a safety margin), falling back to the class's
renewal_interval when absent.
model_config
¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
OAuthProvider
¶
OAuthProvider(
key: str,
auth_url: str,
token_url: str,
label: str = "",
icon: str = "",
token_encoding: Literal["json", "form"] = "json",
)
Identity and token-flow dialect of an OAuth2 provider.
The base class speaks plain RFC 6749: the authorization-code exchange
and the refresh-token grant are POSTs to token_url with the standard
parameters. A provider whose dialect deviates — a different method,
parameter names, an extra parameter, another grant entirely, a response
envelope — overrides the request builders and response parsers on a
subclass; the provider is the single owner of its dialect, and the
sign-in exchange and connection renewal consume it identically.
token_encoding is the one wire knob a plain instance may set, and it
only parameterizes the default builders' body encoding. Any other
deviation is a method override, never a new field. supports_refresh
is a class trait of the dialect: a provider with no credential-refresh
flow at all (TikTok: tokens do not expire) sets it False on its
subclass, and connections on it derive as non-renewable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Provider key (e.g. |
required |
auth_url
|
str
|
Authorization endpoint the user is sent to. |
required |
token_url
|
str
|
Token endpoint the token requests are sent to. |
required |
label
|
str
|
Display label (defaults to the titlecased key). |
''
|
icon
|
str
|
Icon identifier (e.g. |
''
|
token_encoding
|
Literal['json', 'form']
|
Body encoding of the default token requests. |
'json'
|
icon
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
label
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
supports_refresh
¶
bool(x) -> bool
Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
token_encoding
¶
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.
authorization_code_request
¶
authorization_code_request(
*,
code: str,
redirect_uri: str,
client_id: str,
client_secret: str,
) -> Request
Build the authorization-code grant's token request (RFC 6749 §4.1.3).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
code
|
str
|
The authorization code from the provider's consent screen. |
required |
redirect_uri
|
str
|
The redirect URI the code was issued against. |
required |
client_id
|
str
|
The OAuth app's client id. |
required |
client_secret
|
str
|
The OAuth app's client secret. |
required |
Returns:
| Type | Description |
|---|---|
Request
|
The request to send; the caller owns the client and error handling. |
parse_authorization_code_response
¶
Read the authorization-code grant's response into the RFC token shape.
The default relays the body as is: RFC 6749 §5.1 already puts
access_token / refresh_token at the top level. A provider that
wraps them in an envelope, or reports rejections inside a 200 body,
overrides this to unwrap and to raise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
dict[str, Any]
|
The grant's JSON response body. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The token response with the credentials at the top level. |
parse_refresh_token_response
¶
parse_refresh_token_response(
payload: dict[str, Any],
) -> RefreshTokenResponse
Read the refresh grant's response at the credential-role level.
The default reads the RFC shape: refresh_token is the (possibly
rotated) credential when present, and refresh_token_expires_in
its validity — expires_in describes the access token, which
interloper does not store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
dict[str, Any]
|
The grant's JSON response body. |
required |
Returns:
| Type | Description |
|---|---|
RefreshTokenResponse
|
The issued refresh credential and its validity, when reported. |
refresh_token_request
¶
refresh_token_request(
*,
client_id: str,
client_secret: str,
refresh_token: str,
scope: str | None = None,
) -> Request
Build the refresh-token grant request (RFC 6749 §6).
The default grant omits scope even when given: it is optional per
the RFC and some providers reject parameters they do not document.
A provider that requires it overrides (Microsoft: AADSTS90023).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_id
|
str
|
The OAuth app's client id. |
required |
client_secret
|
str
|
The OAuth app's client secret. |
required |
refresh_token
|
str
|
The credential to exchange for a fresh one. |
required |
scope
|
str | None
|
The connection's declared scope; ignored by the default grant. |
None
|
Returns:
| Type | Description |
|---|---|
Request
|
The request to send; the caller owns the client and error handling. |
OAuthConfig
¶
OAuthConfig(
provider: str,
*,
scope: str = "",
fields: dict[str, str] | None = None,
auth_url: str = "",
label: str = "",
icon: str = "",
)
Class-level OAuth configuration for connection resources.
Declares that a connection supports OAuth sign-in: which provider to use and what scope to request. Provider identity (auth_url, label, icon) is resolved from the provider registry; pass them explicitly only to override the registry or to use an unregistered provider.
When present on a connection class (via the @connection(oauth=...)
decorator kwarg or an oauth ClassVar), definition() injects an
x-oauth extension into the root of the generated JSON Schema so the
frontend can render a "Sign in with X" button.
fields maps the canonical OAuth roles — client_id / client_secret
/ refresh_token — to the connection's actual field names. It drives the
whole form: the mapped fields are hidden in sign-in mode, the client_id
/ client_secret fields are resolved from INTERLOPER_<PROVIDER>_CLIENT_ID /
INTERLOPER_<PROVIDER>_CLIENT_SECRET env, and the refresh_token field receives
the token from the sign-in response. It defaults to the identity trio, so a
standard connection declares nothing; a connection with differently named
fields (or only some roles) passes its own mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
str
|
OAuth provider key (e.g. |
required |
scope
|
str
|
OAuth scope string to request. |
''
|
fields
|
dict[str, str] | None
|
|
None
|
auth_url
|
str
|
Authorization endpoint override. Required when the provider is not in the registry. |
''
|
label
|
str
|
Display label override. |
''
|
icon
|
str
|
Icon identifier override. |
''
|
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the provider is unregistered and no |
Example:
@connection(
name="Facebook Ads",
oauth=OAuthConfig(
"facebook",
scope="ads_read",
fields={"client_id": "app_id", "client_secret": "app_secret", "refresh_token": "access_token"},
),
)
class FacebookAdsConnection(OAuthConnection):
...
Initialise an OAuthConfig, resolving provider metadata from the registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
str
|
OAuth provider key (e.g. |
required |
scope
|
str
|
OAuth scope string. |
''
|
fields
|
dict[str, str] | None
|
Role → field-name mapping (see class docstring); defaults to the identity trio. |
None
|
auth_url
|
str
|
Authorization endpoint override (required for unregistered providers). |
''
|
label
|
str
|
Display label override. |
''
|
icon
|
str
|
Icon identifier override. |
''
|
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the provider is unregistered and no |
to_schema_ext
¶
Serialize to a JSON Schema x-oauth extension dict.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with provider, auth_url, scope, label, icon, and fields. |
RefreshTokenResponse
¶
Role-level outcome of a refresh-token grant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
refresh_token
|
str | None
|
The refresh credential the provider issued, or |
None
|
expires_in
|
int | None
|
Validity of the refresh credential in seconds, when the provider reports it. |
None
|