Jobs & hooks¶
Jobs schedule runs of their targets; hooks react to run outcomes; a retry policy decides when a failed attempt gets another.
Job
¶
Bases: interloper.component.base.Component, interloper.operation.base.Workload
A materialization workload: the anchor of the job kind.
A job declares what to materialize (targets); concrete job classes
add when: CronJob carries a cron trigger.
Trigger fields are inert declarative intent: the framework carries them,
and an operator (the scheduler) acts on them. The workload itself
compiles to the same DAG that every other
entry point executes.
A job also carries workload-level defaults: every relation the job holds
is the default for the same relation on any target that resolves none,
destinations included. The default applies where the job runs
(operations), on a copy of the target; the target itself is never changed.
destinations
¶
destinations = Relation(
kind="destination",
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="destinations",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
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'.
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 = {'arbitrary_types_allowed': 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
¶
relations = {
"targets": Relation(
kind=["source", "asset"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="targets",
),
"destinations": Relation(
kind="destination",
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="destinations",
),
}
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)
targets
¶
targets = Relation(
kind=["source", "asset"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="targets",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
state_model
¶
Bases: pydantic.main.BaseModel
Machine-owned job state (see Component.state_model).
Timestamps are canonical timezone-aware ISO-8601 strings; the scheduler
compares them lexicographically in SQL, so they are validated here but
never rewritten. last_run_status qualifies last_run_at — it reads
as that timestamp's badge rather than a column of its own.
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)
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 |
CronJob
¶
Bases: interloper.job.base.Job
A job triggered by a cron expression.
The trigger is declarative intent the scheduler acts on: cron sets the
cadence on the wall clock of timezone, and a job whose targets declare
time partitioning covers a trailing window of partitions on every tick.
Whether a job is partitioned is derived from its targets' catalog
definitions, never stored.
The window is counted in partitions, not days: offset is how many
partitions back from the current one it ends, and lookback how many it
spans. With daily targets, the defaults (offset=1, lookback=1) mean
"yesterday only" — the job timezone's yesterday. Hourly windows are always
UTC-derived regardless of timezone (hour partition keys are UTC labels,
see lookback).
Each firing is a backfill over that window, and concurrency is how many
of its partitions are in flight at once, newest first.
destinations
¶
destinations = Relation(
kind="destination",
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="destinations",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
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 = {'arbitrary_types_allowed': 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
¶
relations = {
"targets": Relation(
kind=["source", "asset"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="targets",
),
"destinations": Relation(
kind="destination",
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="destinations",
),
}
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)
targets
¶
targets = Relation(
kind=["source", "asset"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="targets",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
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 |
window
¶
window(
config: Mapping[str, Any],
*,
fires_at: datetime,
granularity: TimeGranularity | None,
) -> TimePartitionWindow | None
The trailing window of partitions a firing of a stored job config covers.
The window is read on the job timezone's clock at the firing instant,
so a daily job's "yesterday" is its timezone's yesterday; hourly
windows normalize back to UTC (see TimePartitionWindow.lookback).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Mapping[str, Any]
|
The job's stored config payload. A missing |
required |
fires_at
|
datetime
|
The firing instant, aware. |
required |
granularity
|
TimeGranularity | None
|
The granularity the job's targets share, from their
catalog definitions; |
required |
Returns:
| Type | Description |
|---|---|
TimePartitionWindow | None
|
The window, or |
TimePartitionWindow | None
|
lookback is null. |
zone
¶
The timezone a stored job config evaluates its schedule in.
Stored configs are read as raw payloads, without constructing the job: a name that no longer resolves (after a tzdata change) degrades to UTC rather than failing whoever reads it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
Mapping[str, Any]
|
The job's stored config payload. |
required |
Returns:
| Type | Description |
|---|---|
tzinfo
|
The configured zone, or UTC when it is unset or unknown. |
JobState
¶
Bases: pydantic.main.BaseModel
Machine-owned job state (see Component.state_model).
Timestamps are canonical timezone-aware ISO-8601 strings; the scheduler
compares them lexicographically in SQL, so they are validated here but
never rewritten. last_run_status qualifies last_run_at — it reads
as that timestamp's badge rather than a column of its own.
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)
RetryPolicy
¶
Bases: pydantic.main.BaseModel
How many times a unit of work is attempted, and how long between attempts.
Declared on the component whose unit it governs: an operation's policy retries that operation's execution, a source's is the default for its assets, a job's retries that job's runs. A component declaring none is attempted once; there is no instance-wide default.
The policy carries numbers only. Whether a given error is worth another
attempt is behaviour rather than configuration, and lives on
retryable.
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)
allows
¶
Whether the budget covers an attempt.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attempt
|
int
|
The attempt number, counting the first execution as 1. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
delay_before
¶
How long to wait before an attempt.
The growth is geometric from the second attempt (the first retry),
capped at max_delay, then spread by jitter so that operations
failing together do not retry in lockstep.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
attempt
|
int
|
The attempt number, counting the first execution as 1. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The delay in seconds, never negative; |
Hook
¶
Bases: interloper.component.base.Component
A reaction to events on watched components.
A hook declares what to observe (watches), which outcomes matter
(events), and, like an asset's partitioning or a job's cron, the
declaration is inert intent: the framework carries it, and an operator
(the scheduler) evaluates events and calls fire. Concrete hook
classes own the side effect:
class SlackHook(Hook):
channel: str = InputField(description="Channel to notify")
def fire(self, context: HookContext) -> None:
post_message(self.channel, context.metadata)
The base hook is only an observer. Hooks that act on other components
(TriggerHook) extend the vocabulary with a targets relation, so
each class's definition advertises exactly what it acts on.
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'.
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 = {'arbitrary_types_allowed': 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
¶
relations = {
"watches": Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
)
}
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)
watches
¶
watches = Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
state_model
¶
Bases: pydantic.main.BaseModel
Machine-owned hook state (see Component.state_model).
Stamped by the operator on every firing: last_error carries the latest
firing's error and a successful firing clears it, so the state alone tells
whether a hook is failing.
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)
fire
¶
fire(context: HookContext) -> None
Execute this hook's reaction to an event.
Subclasses must override this method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
HookContext
|
What the hook knows about the event that triggered it: its identity, metadata, and the operator-injected capabilities. |
required |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the subclass does not implement |
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 |
HookContext
¶
Bases: pydantic.main.BaseModel
What a firing hook knows about the event that triggered it.
Carries the event's identity and metadata, plus the capabilities the
operator injects: trigger creates a run for a component id, so
trigger-style hooks stay free of any persistence dependency. A run event
names its run and partition; a backfill event names its backfill and the
range it covered. url is the subject's page in the app, when the
deployment has a public URL, so a notification can link back to it.
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)
HookState
¶
Bases: pydantic.main.BaseModel
Machine-owned hook state (see Component.state_model).
Stamped by the operator on every firing: last_error carries the latest
firing's error and a successful firing clears it, so the state alone tells
whether a hook is failing.
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)
TriggerHook
¶
Bases: interloper.hook.base.Hook
Triggers a run of each target when a watched event matches.
The cascading-pipelines primitive: watch an upstream source or job,
target the downstream workload. Execution goes through the operator's
injected context.trigger capability, so the hook itself carries no
persistence dependency.
The targets relation lives here, not on the base hook: only
trigger-style hooks act on other components, so only their definitions
advertise it.
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 = {'arbitrary_types_allowed': 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
¶
relations = {
"watches": Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
),
"targets": Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="targets",
),
}
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)
targets
¶
targets = Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="block",
name="targets",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
watches
¶
watches = Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
fire
¶
fire(context: HookContext) -> None
Trigger a run for every target.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
HookContext
|
The event context, whose |
required |
Raises:
| Type | Description |
|---|---|
ConfigError
|
If the operator provided no trigger capability. |
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 |
WebhookHook
¶
Bases: interloper.hook.base.Hook
POSTs a JSON document describing the event to a URL.
The payload is a fixed metadata document — the event type, the watched component, the run — so receivers integrate against one stable shape.
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 = {'arbitrary_types_allowed': 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
¶
relations = {
"watches": Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
)
}
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)
watches
¶
watches = Relation(
kind=["source", "asset", "job"],
key="",
many=True,
optional=True,
default=None,
on_delete="detach",
name="watches",
)
One declared link from an owner component to the components that may fill it.
kind and key name the acceptable targets: kind names one or
several component kinds, key (when non-empty) narrows to specific
keys within those kinds, matched through ComponentIdentity.satisfies.
many marks a relation that binds several components at once;
optional marks one that may stay unbound or be left empty, and says
nothing else: on_delete alone decides what deleting a bound target
does, block refusing the deletion while this relation holds the
target and detach letting it through and dropping the binding.
default and has_fallback together describe a relation that can be
resolved without an explicit binding.
A relation is also its own descriptor: Component._collect installs
the stamped copy under the relation's name, so the class attribute reads
as the declaration and the instance attribute as what is bound to it, and
assignment rebinds it.
The declaration form the framework writes pairs the relation with the
annotation of what fills it
(destinations: list[Destination] = Relation("destination", many=True)).
The TYPE_CHECKING __new__ is what makes that form check: the type
checker reads a Pydantic field of the annotated type, so the constructor
kwarg and the instance attribute are both typed, while the runtime sees a
Relation (Pydantic ignores it, and Component._collect drops the
annotation before fields are collected).
Two attributes are not declared but derived. name is stamped from the
attribute the relation is declared under, and target is the class the
Relation(cls) shorthand was written with, the class a fallback is
built from; neither is a constructor argument. default (intended type
Callable[[], Component] | None) and target (intended type
type[Component] | None) are typed Any for the type checker, since
Component is only imported under TYPE_CHECKING; both stay out of
dumps.
fire
¶
fire(context: HookContext) -> None
POST the event payload to the configured URL.
An error status from the receiver raises httpx2.HTTPStatusError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
HookContext
|
The event context, serialized into the POST payload. |
required |
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 |
HOOK_EVENT_TYPES
¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.