Skip to content

Astichi reference guide (snippets)

Condensed map: each topic is a directory under snippets/. Authored sources are *.py (optional # astichi-snippet: {…} metadata), builder bundles (snippet.json + parts), or recipe.py (multi-stage / non-JSON wiring). Materialized output is *_generated.py in the same directory as the authored file: flat foo.pyfoo_generated.py; bundle directory bar/bar/bar_generated.py. Do not edit by hand.

Regenerate: from the astichi/ tree, run
uv run python scripts/generate_reference_snippet_outputs.py

API: astichi.compile → optional .bind / .bind_identifier.describe() / .materialize(); builder bundles use astichi.build(), add, target, assign, bind_identifier, build(unroll=…).

Topic Forms covered Authored Materialized (sibling *_generated.py)
bind_external tuple binding; mapping-style bind kwargs tuple_binding.py, mapping_binding.py tuple_binding_generated.py, mapping_binding_generated.py
ref string literal path; external= + bind; f-string + bound prefix; subscript into bound tuple; LHS/delete sentinel astichi_v / _ ref/ e.g. literal_generated.py; same suffix pattern for other ref/*.py
unroll astichi_for + ref in loop body (unroll=True) unroll/for_literal_ref/ for_literal_ref_generated.py
keep call-form astichi_keep call_keep.py call_keep_generated.py
keep_identifier __astichi_keep__ class/def suffix class_suffix.py class_suffix_generated.py
arg __astichi_arg__ resolved via bind_identifier resolved.py resolved_generated.py
export astichi_export (supply port; marker stripped) export_line.py export_line_generated.py
import astichi_import; explicit builder / arg_names threading; same-name immediate outer bind via outer_bind=True import/accumulator_step/ accumulator_step_generated.py
pass value-form only: walrus RHS (x := astichi_pass(y)), direct value / call / attribute use, LHS/delete sentinel astichi_v / _, and same-name immediate outer bind via outer_bind=True; bare statement-form astichi_pass(...) rejects pass/if_walrus_pass/ if_walrus_pass_generated.py
funcargs * hole: ordered positional payloads; ** hole: keyword payloads + edge order; call hole + fixed keywords: func(hole, fixed=…) with payload first, named=…, **extra; walrus + boundary markers: (out := seed), _=astichi_import(seed), _=astichi_export(out) with assign wiring; same with compile(..., arg_names=…) on the fragment instead of assign; astichi_pass as the walrus RHS; astichi_bind_external in the carrier + .bind() before materialize; parameterized keyword name + value identifier via astichi_funcargs(param__astichi_arg__=astichi_pass(value__astichi_arg__, outer_bind=True)) reused with per-instance arg_names={...} overlays funcargs/ (ordered_variadic/, named_variadic_kwargs/, plain_fixed_keywords_star_extra/, walrus_import_export_assign/, payload_compile_arg_names/, pass_walrus_export/, bind_external_arg/) e.g. ordered_variadic_generated.py; each subfolder has a matching *_generated.py
params function signature holes via name__astichi_param_hole__; payload carrier def astichi_params(...): pass; defaults/annotations; variadic parameters; body snippets binding to inserted parameter names params/function_signature/ function_signature_generated.py
comment statement-only astichi_comment("..."); emit_commented() output with same-indentation multi-line comments, {__file__} / {__line__} expansion, and pass for comment-only suites comment/generated_comment/ generated_comment_generated.py
expr scalar expression hole + single insert; tuple/list starred * expansion with ordered expression entries; dict initializer ** expansion with dict payloads; scalar dict-key replacement expr/scalar_hole/, expr/tuple_list_expansion/, expr/dict_expansion/ scalar_hole_generated.py, tuple_list_expansion_generated.py, dict_expansion_generated.py
statement block hole + builder-generated insert synthesis; defaulted block hole fallback selected when unfilled; defaulted block hole replaced when filled statement/block_hole/, statement/defaulted_block_hole_empty/, statement/defaulted_block_hole_filled/ block_hole_generated.py, defaulted_block_hole_empty_generated.py, defaulted_block_hole_filled_generated.py
composition Staged graphs: first build() produces a composable embedded in a later build(); unroll on a subsequent build(unroll=True) after indexed edges onto a loop carried in from the prior merge; indexed instance families: builder.add.Step[i](...), builder.Root.body.add.Step[i](...), and later reach-in via builder.Step[i]...; data-driven named builder API: builder.add("Root", ...), builder.instance("Root").target("body").add("Step"), direct builder.target(...), and named builder.assign(...) composition/staged_unroll_indexed_edges/ (recipe.py), composition/nested_three_stage_trace/, composition/indexed_instance_family/, composition/data_driven_builder_basic/, composition/data_driven_builder_nested/ staged_unroll_indexed_edges_generated.py, nested_three_stage_trace_generated.py, indexed_instance_family_generated.py, data_driven_builder_basic_generated.py, data_driven_builder_nested_generated.py
descriptor_api Self-description: discover external binds from describe() and bind them before materialize; descriptor-driven staged targets: use ComposableHole.with_root_instance(...) with builder.target(...) and descriptor identifier surfaces with bind_identifier(...); indexed unroll: resolve a descriptor target once, then use target[i] and indexed Step[i] sources for loop-expanded holes descriptor_api/external_bind_single/, descriptor_api/staged_descriptor_targets/, descriptor_api/unrolled_indexed_descriptor_targets/ external_bind_single_generated.py, staged_descriptor_targets_generated.py, unrolled_indexed_descriptor_targets_generated.py
assembler_scope Scope-driven candidate lookup: wrap composables, external values, and identifier names as resources; filter by demand name, build-path selectors such as ("Root", "GetterBody[1]"), and code-owner selectors; apply the selected candidate to an AssemblyScope assembler_scope/resource_candidates/ resource_candidates_generated.py
scope Hygiene / names: colliding locals across two inserts (total -> total__astichi_scoped_*); two named holes (setup then body); builder insert + astichi_keep (inner value renamed, outer name kept) scope/ colliding_locals_two_inserts_generated.py, two_holes_ordered_generated.py, outer_hole_inner_insert_keep_generated.py

Metadata line (optional, first line of a flat snippet):

# astichi-snippet: {"bind": {...}, "bind_identifier": {...}, "arg_names": {...}}

JSON lists in bind are normalized to tuples where needed.

Builder bundle (snippet.json):

  • instances: map of builder instance name → fragment filename or { "file": "impl.py", "arg_names": { … } } (any astichi.compile kwargs).
  • wires: Python expressions evaluated with builder in scope (e.g. builder.Root.body.add.Step1(...)).
  • unroll: passed to build(unroll=…).

Recipe bundle (recipe.py only, no snippet.json):

  • Defines def run() -> str: returning final generated source.
  • Use when the example needs multiple build() calls, assign chains, indexed Pipeline.Root.Loop.step[i] wiring, or an emission surface such as emit_commented() that is awkward to encode as JSON.

Long-form narrative docs: marker-overview.md, marker-comment.md, scoping-hygiene.md, using-the-api.md, builder-api.md (fluent and data-driven builder surfaces), descriptor-api.md (self-description and descriptor-driven composition), inventory-api.md (record/map discovery), assembler-scope.md (candidate lookup/application), addressing.md (paths + indexed edges).