contracts.rules_sse

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

SSE contract cross-checks.

SSE contract cross-checks.

contracts.rules_sse

Name Type Default Description
type
qualified_name
element_type
description
source_file
line_number
is_autodoc
autodoc_element
_autodoc_template
_autodoc_url_path
_autodoc_page_type
title
doc_content_hash

Symbols on this page

function strip_template_comments

Remove Kida + HTML comments so element scans ignore illustrative markup.

Jump to symbol
function normalize_sse_url

Replace Kida expressions so route-pattern matching still works.

Jump to symbol
function extract_sse_swap_values

Extract all sse-swap event names from source (comments stripped).

Jump to symbol
function _string_kwarg

Return(confident, value)for a string keyword argument.

Jump to symbol
class _UserReadFinder

Find user-accessor calls and long-lived loops inside a generator body.

found records whether get_user() / current_user()is called anywhere in the walked tree;in_long_lived_loop…

Jump to symbol
function _eventstream_generator_arg

Return the single positional argument of the firstEventStream(...)call.

EventStream(generate()) -> the generate()call node; EventStream(gen) -> the gen Name node. Returns None…

Jump to symbol
function _generator_callable_name

Resolve the generator-producing name from anEventStream(...)arg.

generate() -> "generate"; gen -> "gen". Nonefor any other shape (e.g. an inline…

Jump to symbol
function _find_nested_funcdef

Find a nestedasync def/defnamed name inside tree.

Jump to symbol
function _resolve_module_level_generator

Resolve a MODULE-LEVEL generator function by name via__globals__.

The handler-source AST walk only sees generators defined as NESTED functions inside the handler.…

Jump to symbol
function _analyze_eventstream_generator

Return a populated_UserReadFinderfor an EventStream route's generator.

ReturnsNone when the route is NOT an EventStreamroute, or its source/generator cannot be…

Jump to symbol
function check_sse_auth_gate

Flag an EventStream generator that reads the user with noAuthMiddleware.

AnEventStream route whose generator calls get_user()/ current_user()resolves the connect-time-pinned…

Jump to symbol
function check_sse_context

Nudge that SSE user identity is connect-time-pinned in a long-lived stream.

POST-FIX residual guard, NOT a broken-pattern flag: reading the user inside anEventStream…

Jump to symbol
function _infer_emitted_events

Infer literal SSE event names emitted by a route handler.

ReturnsNonewhen source is unavailable or a relevant event name is dynamic. Dynamic…

Jump to symbol
function check_sse_self_swap

Error when sse-swap appears on same element as sse-connect.

Jump to symbol
function check_sse_connect_scope

Warn when sse-connect is inside broad hx-target scope without mitigation.

Jump to symbol
function check_sse_event_crossref

Cross-reference sse-swap values against declared and inferred events.

Jump to symbol
strip_template_comments
function
def strip_template_comments(source: str) -> str

Remove Kida + HTML comments so element scans ignore illustrative markup.

Parameters

Name Type Default Description
source str
normalize_sse_url
function
def normalize_sse_url(url: str) -> str

Replace Kida expressions so route-pattern matching still works.

Parameters

Name Type Default Description
url str
extract_sse_swap_values
function
def extract_sse_swap_values(source: str) -> set[str]

Extract all sse-swap event names from source (comments stripped).

Parameters

Name Type Default Description
source str
_string_kwarg
function
def _string_kwarg(node: ast.Call, name: str) -> tuple[bool, str | None]

Return(confident, value)for a string keyword argument.

Parameters

Name Type Default Description
node ast.Call
name str
_UserReadFinder
class

Find user-accessor calls and long-lived loops inside a generator body.

found records whether get_user() / current_user()is called anywhere in the walked tree;in_long_lived_looprecords whether any such call sits inside awhile / for / async for(the long-lived SSE pump where connect-time identity pinning becomes a staleness caveat). A bare user read at generator top-level (outside any loop) is treated as short-lived — it resolves once and the stream ends.

_eventstream_generator_arg
function
def _eventstream_generator_arg(handler_tree: ast.AST) -> ast.expr | None

Return the single positional argument of the firstEventStream(...)call.

EventStream(generate()) -> the generate()call node; EventStream(gen) -> the gen Name node. Returns Nonewhen no EventStream(...)call with a first positional arg is found.

Parameters

Name Type Default Description
handler_tree ast.AST
_generator_callable_name
function
def _generator_callable_name(gen_arg: ast.expr) -> str | None

Resolve the generator-producing name from anEventStream(...)arg.

generate() -> "generate"; gen -> "gen". Nonefor any other shape (e.g. an inline comprehension or a method call) — the caller falls back to walking the whole handler tree.

Parameters

Name Type Default Description
gen_arg ast.expr
_find_nested_funcdef
function
def _find_nested_funcdef(tree: ast.AST, name: str) -> ast.AST | None

Find a nestedasync def/defnamed name inside tree.

Parameters

Name Type Default Description
tree ast.AST
name str
_resolve_module_level_generator
function
def _resolve_module_level_generator(handler: Any, name: str) -> ast.AST | None

Resolve a MODULE-LEVEL generator function by name via__globals__.

The handler-source AST walk only sees generators defined as NESTED functions inside the handler. A module-levelasync def gen()passed as EventStream(gen())is NOT in the handler source — so we resolve the bare name against the handler's__globals__and parse THAT function's source. ReturnsNonewhen the name is not a module-level function, or its source is unavailable.

Parameters

Name Type Default Description
handler Any
name str
_analyze_eventstream_generator
function
def _analyze_eventstream_generator(handler: Any) -> _UserReadFinder | None

Return a populated_UserReadFinderfor an EventStream route's generator.

ReturnsNone when the route is NOT an EventStreamroute, or its source/generator cannot be statically resolved (errs toward silence). The generator is resolved in two scopes:

  1. NESTED — an inlineasync def generate()inside the handler (the common case). Walked directly from the handler tree.
  2. MODULE-LEVEL — a bare name passed asEventStream(gen())resolved via the handler's__globals__and parsed separately (see _resolve_module_level_generator()).

Falls back to walking the WHOLE handler tree when the generator argument's shape is not a plain name/call (e.g. an inline comprehension) so an inline user read is still seen.

Parameters

Name Type Default Description
handler Any
check_sse_auth_gate
function
def check_sse_auth_gate(router: Router, config: Any, middleware_list: list[Any]) -> list[ContractIssue]

Flag an EventStream generator that reads the user with noAuthMiddleware.

AnEventStream route whose generator calls get_user()/ current_user()resolves the connect-time-pinned user — but only when AuthMiddleware is wired. Without it the captured user is AnonymousUser for the entire stream, so an auth-sensitive SSE feed silently serves the anonymous view to everyone. Parallelsauth_middleware: env-aware (ERROR production / WARNING staging / silent development — the dev behavior surfaces locally), detected by middleware class NAME.

SCOPE: the static AST walk resolves the generator in two scopes — an inline nestedasync def inside the handler, AND a module-level async def passed asEventStream(gen()) (resolved via the handler __globals__). A generator built by any other indirection (a factory, a method, a value threaded through another call) is not statically resolvable and is silently skipped — errs toward silence, never a false ERROR.

Parameters

Name Type Default Description
router Router
config Any
middleware_list list[Any]
check_sse_context
function
def check_sse_context(router: Router, config: Any) -> list[ContractIssue]

Nudge that SSE user identity is connect-time-pinned in a long-lived stream.

POST-FIX residual guard, NOT a broken-pattern flag: reading the user inside anEventStreamgenerator now WORKS (connect-time capture). This only surfaces the semantic caveat — when the user is read inside a LONG-LIVED loop (while / async for / for), the identity is pinned at connect and is NOT refreshed on a mid-stream logout / permission change for the life of the connection. WARNING (env-aware: silent development, WARNING staging / production); never ERROR — the pattern is correct, this is a known-semantic nudge for auth-sensitive long-lived streams.

Same two-scope resolution (and the same single-indirection blind spot) as check_sse_auth_gate(). A short-lived top-level user read (outside any loop) is NOT nudged — it resolves once and the stream ends.

Parameters

Name Type Default Description
router Router
config Any
_infer_emitted_events
function
def _infer_emitted_events(handler: Any) -> set[str] | None

Infer literal SSE event names emitted by a route handler.

ReturnsNonewhen source is unavailable or a relevant event name is dynamic. Dynamic cases are skipped by the cross-reference check so the contract errs toward silence instead of false positives.

Parameters

Name Type Default Description
handler Any
check_sse_self_swap
function
def check_sse_self_swap(template_sources: dict[str, str]) -> list[ContractIssue]

Error when sse-swap appears on same element as sse-connect.

Parameters

Name Type Default Description
template_sources dict[str, str]
check_sse_connect_scope
function
def check_sse_connect_scope(template_sources: dict[str, str], broad_targets: set[str]) -> list[ContractIssue]

Warn when sse-connect is inside broad hx-target scope without mitigation.

Parameters

Name Type Default Description
template_sources dict[str, str]
broad_targets set[str]
check_sse_event_crossref
function
def check_sse_event_crossref(template_sources: dict[str, str], router: Router) -> list[ContractIssue]

Cross-reference sse-swap values against declared and inferred events.

Parameters

Name Type Default Description
template_sources dict[str, str]
router Router

View source · /home/runner/work/chirp/chirp/site/../src/chirp/contracts/rules_sse.py:1