symfonic.core.prompt.blocks.sources¶
sources ¶
Concrete :class:~symfonic.core.prompt.blocks.protocol.BlockSource adapters.
BlockComputer
module-attribute
¶
BlockComputer: TypeAlias = Callable[
[TenantScope, str],
ComputedBlockResult | Awaitable[ComputedBlockResult],
]
The host-supplied callable. Sync or async def; both are accepted.
ComputedBlockResult
module-attribute
¶
What a host callable returns: (content, revision), in that order.
BlockDatabaseUnavailableError ¶
Bases: StorageError
The backing database could not serve the block.
Typed as a :class:~symfonic.core.protocols.StorageError so the
resolver routes it through the block's on_source_failure policy
rather than letting a driver exception escape past that policy and
turn an optional block into a failed turn.
BlockFileNotFoundError ¶
Bases: BlockFileUnavailableError, NotFoundError
The backing file does not exist.
Derives from both :class:BlockFileUnavailableError (so a resolver
catching the source-failure family catches it) and
:class:~symfonic.core.protocols.NotFoundError (so "absent" stays
distinguishable from "present but unreadable" -- an operator who
mistyped a path and one whose deploy dropped read permissions need
different fixes).
BlockFileUnavailableError ¶
Bases: StorageError
The backing file exists but could not be read as block content.
A permission denial, a path that is a directory, or content that is
not decodable in the declared encoding. Typed as a
:class:~symfonic.core.protocols.StorageError so the resolver routes
it through the block's on_source_failure policy instead of
letting a raw :class:OSError escape past it.
BlockRevisionNotFoundError ¶
Bases: BlockDatabaseUnavailableError, NotFoundError
No such revision for this block in this scope.
Raised by :meth:DatabaseBlockSource.load when a block has never
been written, and by :meth:DatabaseBlockSource.load_revision when
the named revision does not exist for this scope -- a revision id
belonging to another tenant is not found here, which is the same
answer an id that never existed gets.
ComputedBlockContractError ¶
Bases: TypeError
The host callable returned something that is not (content, revision).
Deliberately a :class:TypeError and not a
:class:~symfonic.core.protocols.StorageError: this is a
deterministic bug in host code, not an outage. It must not be
swallowed by an omit failure policy, because a block that
vanishes silently from every prompt is discovered months later, if at
all. See the module docstring.
ComputedBlockSource ¶
ComputedBlockSource(
compute: BlockComputer,
*,
offline_safe: bool = False,
timeout: float | None = None,
)
Serves one prompt block by calling a host-supplied callable.
Satisfies :class:~symfonic.core.prompt.blocks.protocol.BlockSource
structurally and stops there -- see below for why history is not
presented.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compute
|
BlockComputer
|
Called as |
required |
offline_safe
|
bool
|
Host-declared, defaulting to |
False
|
timeout
|
float | None
|
Seconds to wait for |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
scope_aware |
bool
|
Always |
Why there is no history: a callable computes a value, it does not
retain the values it computed. list_revisions / load_revision
are therefore not presented, isinstance(src,
HistoryCapableBlockSource) is False, and
:func:~symfonic.core.prompt.blocks.validation.check_operator_editable
rejects operator_editable=True against it at construction. A host
whose backing store does keep history should ship an adapter that
presents it, not declare it through this one.
Source code in src/symfonic/core/prompt/blocks/sources/computed.py
current_revision
async
¶
Return the current revision id without building a revision object.
Not part of :class:BlockSource; offered for parity with
:class:~symfonic.core.prompt.blocks.sources.static.StaticBlockSource
and :class:~symfonic.core.prompt.blocks.sources.file.FileBlockSource
so a cache-validity check has one shape across adapters. Unlike
those two, this is not cheap here: it calls compute in
full, the same as :meth:load, because a computed value has no
separate metadata read -- the callable's return value is the
only source of the revision. A caller doing a cache-validity check
against a computed-backed block pays the full call, not a
shortcut.
Source code in src/symfonic/core/prompt/blocks/sources/computed.py
load
async
¶
Compute the current revision of block_id for scope.
Both arguments are passed through to the host callable verbatim, so content may differ per tenant and per block.
Raises:
| Type | Description |
|---|---|
ComputedBlockUnavailableError
|
The callable raised. Routed
through the block's |
ComputedBlockContractError
|
The callable returned something
other than a |
Source code in src/symfonic/core/prompt/blocks/sources/computed.py
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 | |
ComputedBlockUnavailableError ¶
Bases: StorageError
The host callable raised while computing the block.
Typed as a :class:~symfonic.core.protocols.StorageError so the
resolver routes it through the block's on_source_failure policy
instead of letting an arbitrary exception from host code escape past
it and fail a turn whose policy said to omit the block.
DatabaseBlockSource ¶
Serves prompt blocks from the append-only revision table.
One instance backs every block of every scope in one database: the
(scope_path, block_id) pair in each statement selects the rows,
so a deployment needs one of these rather than one per block.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pool
|
Any
|
Anything exposing |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
offline_safe |
bool
|
Always |
scope_aware |
bool
|
Always |
Source code in src/symfonic/core/prompt/blocks/sources/database.py
append_revision
async
¶
append_revision(
scope: TenantScope,
block_id: str,
content: str,
*,
author: str | None,
message: str | None,
expected_head: str | None,
) -> BlockRevision
Append a new revision of block_id for scope.
Operator-facing: called by the host application from its own admin surface, never registered as an agent tool. Authorizing the caller happens before this method is reached.
One INSERT, no ON CONFLICT clause. Existing revisions are
left exactly as they were, and the previous head stays reachable
through :meth:load_revision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
TenantScope
|
Isolation argument; the stored key is
|
required |
block_id
|
str
|
The block spec's name. |
required |
content
|
str
|
The new body. |
required |
author
|
str | None
|
Who asked for the write, or |
required |
message
|
str | None
|
Why, or |
required |
expected_head
|
str | None
|
The revision the caller believes is current,
or |
required |
Returns:
| Type | Description |
|---|---|
BlockRevision
|
The appended :class: |
Raises:
| Type | Description |
|---|---|
RevisionConflictError
|
|
Source code in src/symfonic/core/prompt/blocks/sources/database.py
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | |
ensure_schema
async
¶
Create the revision table if it is absent. Idempotent.
Never called from :meth:load or :meth:append_revision; see
the module docstring for why DDL stays off the hot paths.
CREATE TABLE IF NOT EXISTS is not race-safe on every engine:
two replicas booting at once can both attempt it, and the loser
may get a duplicate-key error on a system catalog index instead of
a silent no-op. That shape is indistinguishable from any other
unique-violation-shaped exception, so it is recognised with the
same :func:is_unique_violation the write path uses rather than a
second heuristic, and treated as success: the table exists either
way, which is everything this method promises.
Raises:
| Type | Description |
|---|---|
BlockDatabaseUnavailableError
|
The pool or the driver failed for a reason other than losing this race. |
Source code in src/symfonic/core/prompt/blocks/sources/database.py
list_revisions
async
¶
list_revisions(
scope: TenantScope,
block_id: str,
*,
limit: int | None = None,
) -> Sequence[BlockRevision]
Return revisions of block_id for scope, newest first.
Scoped and block-filtered like every other read. History is cumulative, so a history read that dropped either filter would leak strictly more than a current-value read.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
limit
|
int | None
|
Not part of :class: |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
BlockDatabaseUnavailableError
|
The pool or the driver failed. |
Source code in src/symfonic/core/prompt/blocks/sources/database.py
load
async
¶
Return the head revision of block_id for scope.
The head is the highest sequence for the pair, not the newest
created_at and not whichever row the table returns first.
Raises:
| Type | Description |
|---|---|
BlockRevisionNotFoundError
|
The block has never been written for this scope. |
Source code in src/symfonic/core/prompt/blocks/sources/database.py
load_revision
async
¶
Return one named prior revision of block_id for scope.
scope is part of the lookup, not inferred from revision:
revision ids are unique only within a scope's block, so trusting
the id alone would read across tenants.
Raises:
| Type | Description |
|---|---|
BlockRevisionNotFoundError
|
No such revision in this scope. |
Source code in src/symfonic/core/prompt/blocks/sources/database.py
FileBlockSource ¶
Serves one prompt block from one file on disk.
Satisfies :class:~symfonic.core.prompt.blocks.protocol.BlockSource
structurally and stops there -- see the module docstring for why
history is not presented.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
The file backing this block. Resolved once at construction
so a later |
required |
encoding
|
str
|
Text encoding of the file. Content is always hashed as UTF-8, so this affects decoding only, never the revision. |
'utf-8'
|
Attributes:
| Name | Type | Description |
|---|---|---|
offline_safe |
bool
|
Always |
scope_aware |
bool
|
Always |
Source code in src/symfonic/core/prompt/blocks/sources/file.py
current_revision
async
¶
Return the current revision id without building a revision.
Not part of :class:BlockSource; offered so a cache-validity
check has a cheap path that stays as offline as
:meth:load -- the check reads the same local file and hashes
it, and reaches no datastore either.
Source code in src/symfonic/core/prompt/blocks/sources/file.py
load
async
¶
Return the current revision of the backing file.
scope and block_id are accepted because the Protocol has
no overload that omits them, and ignored because one instance
backs one file: the path selects the content. block_id is
still used in failure messages so an unreadable file names the
block it was serving.
Raises:
| Type | Description |
|---|---|
BlockFileNotFoundError
|
The file does not exist. |
BlockFileUnavailableError
|
The file exists but cannot be read
as text in :attr: |
Source code in src/symfonic/core/prompt/blocks/sources/file.py
StaticBlockSource ¶
Serves one prompt block from one string fixed in config.
Satisfies :class:~symfonic.core.prompt.blocks.protocol.BlockSource
structurally and stops there -- see the module docstring for why
history is not presented.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
str
|
The block body. Hashed once here to produce the constant revision. Rejected when blank: an empty literal renders an empty labelled section, which reads to the model as a boundary section that exists and says nothing, and is in practice a half-finished config rather than an intent. |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
offline_safe |
bool
|
Always |
scope_aware |
bool
|
Always |
Source code in src/symfonic/core/prompt/blocks/sources/static.py
current_revision
async
¶
Return the constant revision without building a revision object.
Not part of :class:BlockSource; offered for parity with
:class:~symfonic.core.prompt.blocks.sources.file.FileBlockSource
so a cache-validity check has one shape across adapters. Here it
is a field read, so the check costs nothing.
Source code in src/symfonic/core/prompt/blocks/sources/static.py
load
async
¶
Return the configured literal and its constant revision.
scope and block_id are accepted because the Protocol has
no overload that omits them, and ignored because one instance
backs one literal.
Never raises: there is no backing system to be unreachable.
Source code in src/symfonic/core/prompt/blocks/sources/static.py
content_revision ¶
Return the revision id for content -- sha256:<hex>.
A pure function of the text, with no filesystem access at all, so the same content produces the same revision on every host and at every mtime. Exposed rather than inlined so a cache-invalidation check can compute the expected revision for content it already holds, without a second read.
The text is hashed as UTF-8 regardless of the encoding it was read in: the revision identifies the block's content, so re-saving the same words in a different on-disk encoding must not present itself as an edit.