astichi 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- astichi-0.1.0/.gitignore +14 -0
- astichi-0.1.0/LICENSE +21 -0
- astichi-0.1.0/PKG-INFO +310 -0
- astichi-0.1.0/README.md +267 -0
- astichi-0.1.0/astichi/__init__.py +17 -0
- astichi-0.1.0/astichi/ast_provenance.py +131 -0
- astichi-0.1.0/astichi/asttools/__init__.py +40 -0
- astichi-0.1.0/astichi/asttools/imports.py +47 -0
- astichi-0.1.0/astichi/asttools/inserts.py +33 -0
- astichi-0.1.0/astichi/asttools/scopes.py +157 -0
- astichi-0.1.0/astichi/asttools/shapes.py +86 -0
- astichi-0.1.0/astichi/builder/__init__.py +35 -0
- astichi-0.1.0/astichi/builder/api.py +10 -0
- astichi-0.1.0/astichi/builder/graph.py +452 -0
- astichi-0.1.0/astichi/builder/handles.py +1257 -0
- astichi-0.1.0/astichi/diagnostics/__init__.py +13 -0
- astichi-0.1.0/astichi/diagnostics/formatting.py +48 -0
- astichi-0.1.0/astichi/emit/__init__.py +19 -0
- astichi-0.1.0/astichi/emit/api.py +87 -0
- astichi-0.1.0/astichi/frontend/__init__.py +18 -0
- astichi-0.1.0/astichi/frontend/api.py +239 -0
- astichi-0.1.0/astichi/frontend/compiled.py +9 -0
- astichi-0.1.0/astichi/frontend/source_kind.py +51 -0
- astichi-0.1.0/astichi/hygiene/__init__.py +29 -0
- astichi-0.1.0/astichi/hygiene/api.py +1316 -0
- astichi-0.1.0/astichi/lowering/__init__.py +129 -0
- astichi-0.1.0/astichi/lowering/boundaries.py +324 -0
- astichi-0.1.0/astichi/lowering/call_argument_payloads.py +375 -0
- astichi-0.1.0/astichi/lowering/external_bind.py +492 -0
- astichi-0.1.0/astichi/lowering/external_ref.py +517 -0
- astichi-0.1.0/astichi/lowering/marker_contexts.py +61 -0
- astichi-0.1.0/astichi/lowering/markers.py +1260 -0
- astichi-0.1.0/astichi/lowering/parameters.py +138 -0
- astichi-0.1.0/astichi/lowering/pyimport.py +371 -0
- astichi-0.1.0/astichi/lowering/sentinel_attrs.py +41 -0
- astichi-0.1.0/astichi/lowering/unroll.py +565 -0
- astichi-0.1.0/astichi/lowering/unroll_domain.py +147 -0
- astichi-0.1.0/astichi/materialize/__init__.py +9 -0
- astichi-0.1.0/astichi/materialize/api.py +4227 -0
- astichi-0.1.0/astichi/materialize/pyimport.py +217 -0
- astichi-0.1.0/astichi/model/__init__.py +99 -0
- astichi-0.1.0/astichi/model/basic.py +639 -0
- astichi-0.1.0/astichi/model/composable.py +29 -0
- astichi-0.1.0/astichi/model/descriptors.py +398 -0
- astichi-0.1.0/astichi/model/external_values.py +224 -0
- astichi-0.1.0/astichi/model/origin.py +14 -0
- astichi-0.1.0/astichi/model/ports.py +295 -0
- astichi-0.1.0/astichi/model/semantics.py +352 -0
- astichi-0.1.0/astichi/path_resolution.py +710 -0
- astichi-0.1.0/astichi/shell_refs.py +340 -0
- astichi-0.1.0/dev-docs/AstichiBindBuilderDesign.md +659 -0
- astichi-0.1.0/dev-docs/AstichiCodingRules.md +263 -0
- astichi-0.1.0/dev-docs/AstichiCommentsProposal.md +471 -0
- astichi-0.1.0/dev-docs/AstichiImportProposedEnhancements.md +84 -0
- astichi-0.1.0/dev-docs/AstichiSingleSourceSummary.md +837 -0
- astichi-0.1.0/dev-docs/AtichiBuildResolverProposal.md +253 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignComposed.md +409 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignProposal.md +483 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1-BindExternal.md +1033 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1-CompositionUnification.md +642 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1-InsertExpression.md +798 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1-MarkerPreservingEmit.md +339 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1-UnrollRevision.md +627 -0
- astichi-0.1.0/dev-docs/historical/AstichiApiDesignV1.md +746 -0
- astichi-0.1.0/dev-docs/historical/AstichiComposableSelfDescription.md +1087 -0
- astichi-0.1.0/dev-docs/historical/AstichiCompositionModel.md +35 -0
- astichi-0.1.0/dev-docs/historical/AstichiDataDrivenBuilderApi.md +394 -0
- astichi-0.1.0/dev-docs/historical/AstichiDecriptorAPIProposal.md +790 -0
- astichi-0.1.0/dev-docs/historical/AstichiFixNoEnumViolations.md +994 -0
- astichi-0.1.0/dev-docs/historical/AstichiImplementationBoundaries.md +277 -0
- astichi-0.1.0/dev-docs/historical/AstichiInternalsDesignV1.md +359 -0
- astichi-0.1.0/dev-docs/historical/AstichiMultiBindFixPlan.md +372 -0
- astichi-0.1.0/dev-docs/historical/AstichiUserDocumentationPlan.md +138 -0
- astichi-0.1.0/dev-docs/historical/AstichiV1Milestones.md +77 -0
- astichi-0.1.0/dev-docs/historical/AstichiV2TestAugments.md +347 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3CallArgumentAddendum.md +284 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3CallArgumentImplementationPlan.md +328 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3ExternalRefBind.m4 +280 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3ParameterHoleImplementationPlan.md +108 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3ParameterHoleSpec.md +359 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3TargetAdditionalHoleShapes.md +707 -0
- astichi-0.1.0/dev-docs/historical/AstichiV3TestPlan.md +744 -0
- astichi-0.1.0/dev-docs/historical/BoundaryScopePolicyProposal.md +289 -0
- astichi-0.1.0/dev-docs/historical/BuilderIdentityAndAliasesDesign.md +115 -0
- astichi-0.1.0/dev-docs/historical/IdentifierHygieneRequirements.md +67 -0
- astichi-0.1.0/dev-docs/historical/README.md +11 -0
- astichi-0.1.0/dev-docs/historical/V1DeferredFeatures.md +201 -0
- astichi-0.1.0/dev-docs/historical/V1Plan.md +1069 -0
- astichi-0.1.0/dev-docs/historical/V1ProgressRegister.md +284 -0
- astichi-0.1.0/dev-docs/historical/V1StartHere.md +82 -0
- astichi-0.1.0/dev-docs/historical/V2DeferredFeatures.md +193 -0
- astichi-0.1.0/dev-docs/historical/V2Plan.md +419 -0
- astichi-0.1.0/dev-docs/historical/V2ProgressRegister.md +276 -0
- astichi-0.1.0/dev-docs/historical/V2StartHere.md +117 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/README.md +27 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/resolved/001-expression-insert-materialization-gap.md +72 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/resolved/002-expression-port-shape-validation-gap.md +64 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/resolved/004-expression-insert-end-to-end-test-gap.md +54 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/resolved/005-expression-shape-rejection-test-gap.md +51 -0
- astichi-0.1.0/dev-docs/historical/v1_issues/resolved/006-provenance-contract-test-gap.md +45 -0
- astichi-0.1.0/dev-docs/historical/v2_issues/003-provenance-format-drift.md +77 -0
- astichi-0.1.0/dev-docs/historical/v2_issues/004-materialize-free-name-soundness.md +174 -0
- astichi-0.1.0/dev-docs/historical/v2_issues/005-definitional-name-replacement.md +300 -0
- astichi-0.1.0/dev-docs/historical/v2_issues/006-cross-scope-identifier-threading.md +95 -0
- astichi-0.1.0/dev-docs/historical/v2_issues/README.md +27 -0
- astichi-0.1.0/dev-docs/historical/v3_issues/001-deep-descendant-cross-composable-addressing.md +853 -0
- astichi-0.1.0/dev-docs/historical/v3_issues/README.md +14 -0
- astichi-0.1.0/dev-docs/history/AstichiImportProposal.md +726 -0
- astichi-0.1.0/dev-docs/history/AstichiImportProposalDetailedCodingPlan.md +1199 -0
- astichi-0.1.0/dev-docs/history/AstichiImportProposalDetailedReccomendations.md +293 -0
- astichi-0.1.0/dev-docs/history/AstichiImportProposalPlan.md +792 -0
- astichi-0.1.0/dev-docs/history/AstichiImportRefactorPrep.md +719 -0
- astichi-0.1.0/docs/README.md +38 -0
- astichi-0.1.0/docs/reference/README.md +33 -0
- astichi-0.1.0/pyproject.toml +51 -0
- astichi-0.1.0/tests/README.md +74 -0
- astichi-0.1.0/tests/data/gold_src/arg_identifier_kwarg.py +46 -0
- astichi-0.1.0/tests/data/gold_src/bind_external_literal.py +25 -0
- astichi-0.1.0/tests/data/gold_src/boundary_pass_export.py +67 -0
- astichi-0.1.0/tests/data/gold_src/call_argument_payload.py +271 -0
- astichi-0.1.0/tests/data/gold_src/comment_marker.py +62 -0
- astichi-0.1.0/tests/data/gold_src/compile_basic.py +29 -0
- astichi-0.1.0/tests/data/gold_src/descriptor_assign_and_bind_identifier.py +122 -0
- astichi-0.1.0/tests/data/gold_src/descriptor_bind_identifier.py +86 -0
- astichi-0.1.0/tests/data/gold_src/descriptor_bind_identifier_same_name_collision.py +131 -0
- astichi-0.1.0/tests/data/gold_src/dict_expansion_hygiene.py +52 -0
- astichi-0.1.0/tests/data/gold_src/edge_multibind.py +86 -0
- astichi-0.1.0/tests/data/gold_src/edge_multibind_staged.py +68 -0
- astichi-0.1.0/tests/data/gold_src/function_parameter_scope_hygiene.py +77 -0
- astichi-0.1.0/tests/data/gold_src/hygiene_boundary_isolation.py +104 -0
- astichi-0.1.0/tests/data/gold_src/hygiene_scope_collision.py +46 -0
- astichi-0.1.0/tests/data/gold_src/identifier_bind.py +41 -0
- astichi-0.1.0/tests/data/gold_src/import_arg_identifier.py +34 -0
- astichi-0.1.0/tests/data/gold_src/indexed_instance_family.py +78 -0
- astichi-0.1.0/tests/data/gold_src/inline_insert_block.py +54 -0
- astichi-0.1.0/tests/data/gold_src/param_hole_staged_compose.py +70 -0
- astichi-0.1.0/tests/data/gold_src/parameter_holes.py +163 -0
- astichi-0.1.0/tests/data/gold_src/provenance_absorb_roundtrip.py +42 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_dotted_module.py +21 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_dynamic_module_ref.py +22 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_expression_multi_stage.py +73 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_from_basic.py +21 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_hygiene_collision.py +26 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_multi_stage_multi_hole.py +65 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_plain_alias.py +22 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_staged_composition.py +38 -0
- astichi-0.1.0/tests/data/gold_src/pyimport_with_docstring_and_future.py +22 -0
- astichi-0.1.0/tests/data/gold_src/ref_param_hygiene.py +90 -0
- astichi-0.1.0/tests/data/gold_src/staged_build_trace.py +87 -0
- astichi-0.1.0/tests/data/gold_src/support/__init__.py +1 -0
- astichi-0.1.0/tests/data/gold_src/support/golden_case.py +61 -0
- astichi-0.1.0/tests/data/gold_src/tuple_list_expression_expansion.py +60 -0
- astichi-0.1.0/tests/data/gold_src/unroll_bind_domain.py +55 -0
- astichi-0.1.0/tests/data/gold_src/unroll_literal.py +85 -0
- astichi-0.1.0/tests/data/goldens/materialized/arg_identifier_kwarg.py +4 -0
- astichi-0.1.0/tests/data/goldens/materialized/bind_external_literal.py +1 -0
- astichi-0.1.0/tests/data/goldens/materialized/boundary_pass_export.py +9 -0
- astichi-0.1.0/tests/data/goldens/materialized/call_argument_payload.py +31 -0
- astichi-0.1.0/tests/data/goldens/materialized/comment_marker.py +9 -0
- astichi-0.1.0/tests/data/goldens/materialized/compile_basic.py +8 -0
- astichi-0.1.0/tests/data/goldens/materialized/descriptor_assign_and_bind_identifier.py +7 -0
- astichi-0.1.0/tests/data/goldens/materialized/descriptor_bind_identifier.py +4 -0
- astichi-0.1.0/tests/data/goldens/materialized/descriptor_bind_identifier_same_name_collision.py +6 -0
- astichi-0.1.0/tests/data/goldens/materialized/dict_expansion_hygiene.py +1 -0
- astichi-0.1.0/tests/data/goldens/materialized/edge_multibind.py +12 -0
- astichi-0.1.0/tests/data/goldens/materialized/edge_multibind_staged.py +6 -0
- astichi-0.1.0/tests/data/goldens/materialized/function_parameter_scope_hygiene.py +12 -0
- astichi-0.1.0/tests/data/goldens/materialized/hygiene_boundary_isolation.py +8 -0
- astichi-0.1.0/tests/data/goldens/materialized/hygiene_scope_collision.py +4 -0
- astichi-0.1.0/tests/data/goldens/materialized/identifier_bind.py +8 -0
- astichi-0.1.0/tests/data/goldens/materialized/import_arg_identifier.py +3 -0
- astichi-0.1.0/tests/data/goldens/materialized/indexed_instance_family.py +6 -0
- astichi-0.1.0/tests/data/goldens/materialized/inline_insert_block.py +4 -0
- astichi-0.1.0/tests/data/goldens/materialized/param_hole_staged_compose.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/parameter_holes.py +18 -0
- astichi-0.1.0/tests/data/goldens/materialized/provenance_absorb_roundtrip.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_dotted_module.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_dynamic_module_ref.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_expression_multi_stage.py +5 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_from_basic.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_hygiene_collision.py +3 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_multi_stage_multi_hole.py +11 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_plain_alias.py +3 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_staged_composition.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/pyimport_with_docstring_and_future.py +4 -0
- astichi-0.1.0/tests/data/goldens/materialized/ref_param_hygiene.py +11 -0
- astichi-0.1.0/tests/data/goldens/materialized/staged_build_trace.py +10 -0
- astichi-0.1.0/tests/data/goldens/materialized/tuple_list_expression_expansion.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/unroll_bind_domain.py +2 -0
- astichi-0.1.0/tests/data/goldens/materialized/unroll_literal.py +7 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/arg_identifier_kwarg.py +5 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/bind_external_literal.py +2 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/boundary_pass_export.py +29 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/call_argument_payload.py +37 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/comment_marker.py +19 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/compile_basic.py +9 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/descriptor_assign_and_bind_identifier.py +34 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/descriptor_bind_identifier.py +21 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/descriptor_bind_identifier_same_name_collision.py +33 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/dict_expansion_hygiene.py +6 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/edge_multibind.py +34 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/edge_multibind_staged.py +34 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/function_parameter_scope_hygiene.py +29 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/hygiene_boundary_isolation.py +35 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/hygiene_scope_collision.py +13 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/identifier_bind.py +9 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/import_arg_identifier.py +4 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/indexed_instance_family.py +25 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/inline_insert_block.py +16 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/param_hole_staged_compose.py +16 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/parameter_holes.py +55 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/provenance_absorb_roundtrip.py +3 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_dotted_module.py +3 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_dynamic_module_ref.py +3 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_expression_multi_stage.py +8 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_from_basic.py +3 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_hygiene_collision.py +8 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_multi_stage_multi_hole.py +29 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_plain_alias.py +4 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_staged_composition.py +12 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/pyimport_with_docstring_and_future.py +5 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/ref_param_hygiene.py +20 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/staged_build_trace.py +53 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/tuple_list_expression_expansion.py +7 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/unroll_bind_domain.py +15 -0
- astichi-0.1.0/tests/data/goldens/pre_materialized/unroll_literal.py +33 -0
- astichi-0.1.0/tests/test_arg_identifier_kwarg.py +150 -0
- astichi-0.1.0/tests/test_ast_goldens.py +121 -0
- astichi-0.1.0/tests/test_ast_provenance.py +132 -0
- astichi-0.1.0/tests/test_asttools.py +131 -0
- astichi-0.1.0/tests/test_bind_external.py +132 -0
- astichi-0.1.0/tests/test_boundaries.py +1008 -0
- astichi-0.1.0/tests/test_build_merge.py +561 -0
- astichi-0.1.0/tests/test_builder_handles.py +856 -0
- astichi-0.1.0/tests/test_builder_raw.py +105 -0
- astichi-0.1.0/tests/test_call_arg_hole_anchor.py +124 -0
- astichi-0.1.0/tests/test_call_argument_payload_materialize.py +360 -0
- astichi-0.1.0/tests/test_call_argument_payload_model.py +85 -0
- astichi-0.1.0/tests/test_call_argument_payload_recognition.py +141 -0
- astichi-0.1.0/tests/test_comments.py +77 -0
- astichi-0.1.0/tests/test_descriptors.py +219 -0
- astichi-0.1.0/tests/test_diagnostics_formatting.py +48 -0
- astichi-0.1.0/tests/test_emit.py +151 -0
- astichi-0.1.0/tests/test_expression_insert_pipeline.py +272 -0
- astichi-0.1.0/tests/test_external_bind.py +216 -0
- astichi-0.1.0/tests/test_external_ref.py +397 -0
- astichi-0.1.0/tests/test_external_values.py +164 -0
- astichi-0.1.0/tests/test_frontend_compile.py +93 -0
- astichi-0.1.0/tests/test_hygiene.py +409 -0
- astichi-0.1.0/tests/test_lowering_markers.py +334 -0
- astichi-0.1.0/tests/test_lowering_shapes.py +165 -0
- astichi-0.1.0/tests/test_materialize.py +802 -0
- astichi-0.1.0/tests/test_model.py +269 -0
- astichi-0.1.0/tests/test_parameter_holes.py +406 -0
- astichi-0.1.0/tests/test_pyimport_phase1.py +218 -0
- astichi-0.1.0/tests/test_staged_build_refs_and_bindings.py +572 -0
- astichi-0.1.0/tests/test_unroll.py +375 -0
- astichi-0.1.0/tests/test_unroll_domain.py +171 -0
- astichi-0.1.0/tests/test_versioned_test_harness.py +175 -0
- astichi-0.1.0/tests/versioned_test_harness.py +481 -0
astichi-0.1.0/.gitignore
ADDED
astichi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 astichi contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
astichi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: astichi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AST composition helpers: markers, hygiene, lifting, and lowering for composable Python code generation
|
|
5
|
+
Author: astichi contributors
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 astichi contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Keywords: ast,codegen,compiler,composition
|
|
29
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
37
|
+
Requires-Python: >=3.12
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
40
|
+
Provides-Extra: test
|
|
41
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# astichi
|
|
45
|
+
|
|
46
|
+
AST-level composition and stitching for Python code generation.
|
|
47
|
+
|
|
48
|
+
Astichi takes small, marker-bearing Python snippets, composes them into one
|
|
49
|
+
coherent program, and emits runnable Python. It is built for generators that
|
|
50
|
+
need low-overhead output without falling back to brittle string templates or
|
|
51
|
+
runtime abstraction in hot paths.
|
|
52
|
+
|
|
53
|
+
Astichi is a fit when you want to:
|
|
54
|
+
|
|
55
|
+
- describe codegen intent in Python-shaped snippets
|
|
56
|
+
- stitch block and expression fragments at named composition sites
|
|
57
|
+
- bind compile-time values into source before final lowering
|
|
58
|
+
- unroll compile-time loops into straight-line Python
|
|
59
|
+
- synthesize managed imports that participate in hygiene
|
|
60
|
+
- inspect composables with descriptors before wiring them
|
|
61
|
+
- emit inspectable source with provenance instead of opaque runtime machinery
|
|
62
|
+
|
|
63
|
+
It is **not** a general macro system and **not** a generic codemod framework.
|
|
64
|
+
It is a focused library for generators that need a reliable AST stitcher.
|
|
65
|
+
|
|
66
|
+
## Why Astichi
|
|
67
|
+
|
|
68
|
+
Code generators often hit the same wall: the desired output is simple,
|
|
69
|
+
specialized Python, but the implementation ends up split between string
|
|
70
|
+
concatenation, ad hoc templates, and fragile scope management.
|
|
71
|
+
|
|
72
|
+
Astichi handles the parts that usually go wrong:
|
|
73
|
+
|
|
74
|
+
- valid Python ASTs instead of fragile template fragments
|
|
75
|
+
- deterministic insertion order for stitched code
|
|
76
|
+
- compile-time binding and loop unrolling before emission
|
|
77
|
+
- specialized straight-line Python instead of runtime dispatch layers
|
|
78
|
+
- emitted source you can diff, test, and round-trip
|
|
79
|
+
|
|
80
|
+
## Marker mental model
|
|
81
|
+
|
|
82
|
+
Astichi is marker-bearing Python source plus a small build pipeline.
|
|
83
|
+
|
|
84
|
+
- Markers are recognized from authored Python source.
|
|
85
|
+
- Marker meaning comes from AST position, not string matching alone.
|
|
86
|
+
- `compile(...)` parses marker-bearing source into a `Composable`.
|
|
87
|
+
- `build()` wires composables together.
|
|
88
|
+
- `describe()` exposes holes, binds, ports, and builder target addresses for
|
|
89
|
+
data-driven composition.
|
|
90
|
+
- `materialize()` resolves inserts, bindings, and hygiene, then produces real
|
|
91
|
+
Python.
|
|
92
|
+
|
|
93
|
+
The core markers are:
|
|
94
|
+
|
|
95
|
+
- `astichi_hole(name)` -> insertion site
|
|
96
|
+
- `astichi_keep(name)` -> hygiene-preserved name in expression / statement source
|
|
97
|
+
- `name__astichi_keep__` -> hygiene-preserved name in identifier position
|
|
98
|
+
- `name__astichi_arg__` -> identifier demand
|
|
99
|
+
- `name__astichi_param_hole__` -> function-parameter insertion target
|
|
100
|
+
- `astichi_funcargs(...)` -> call-argument payload
|
|
101
|
+
- `astichi_bind_external(name)` -> external/literal value slot
|
|
102
|
+
- `astichi_ref(path)` -> compile-time reducible identifier / attribute path
|
|
103
|
+
- `astichi_pyimport(module=..., names=(...))` -> managed Python import
|
|
104
|
+
- `astichi_comment("...")` -> final-output source comment
|
|
105
|
+
- `astichi_pass(name, outer_bind=True)` -> explicit same-name boundary read
|
|
106
|
+
- `astichi_import(name)` -> explicit whole-scope boundary import
|
|
107
|
+
- `astichi_export(name)` -> explicit outward supply
|
|
108
|
+
- `astichi_insert(...)` -> internal emitted metadata, not general authored API
|
|
109
|
+
|
|
110
|
+
Comment marker note:
|
|
111
|
+
|
|
112
|
+
- `astichi_comment("...")` is statement-only. Ordinary `materialize()` strips
|
|
113
|
+
it for executable output; `emit_commented()` renders it as real `#` comments.
|
|
114
|
+
- Multi-line payloads keep the marker statement's indentation, and only exact
|
|
115
|
+
`{__file__}` / `{__line__}` substrings are expanded.
|
|
116
|
+
|
|
117
|
+
Value-form target note:
|
|
118
|
+
|
|
119
|
+
- `astichi_ref(...)` and `astichi_pass(...)` are ordinary value-form surfaces in
|
|
120
|
+
expressions.
|
|
121
|
+
- If the marker result itself must occupy an `Assign` / `AugAssign` / `Delete`
|
|
122
|
+
target position, append `._` or `.astichi_v`:
|
|
123
|
+
`astichi_ref("self.f0")._ = 1`,
|
|
124
|
+
`astichi_pass(counter).astichi_v = 1`.
|
|
125
|
+
- If you immediately continue to a real attribute, plain Python target syntax
|
|
126
|
+
already works:
|
|
127
|
+
`astichi_pass(obj).field = 1`.
|
|
128
|
+
|
|
129
|
+
The one rule that matters most is scope:
|
|
130
|
+
|
|
131
|
+
- `astichi_insert` is the basic Astichi boundary.
|
|
132
|
+
- Each inserted composable lives in its own Astichi scope.
|
|
133
|
+
- There is no implicit capture across that boundary.
|
|
134
|
+
- If a name crosses the boundary, make it explicit with `keep`, `pass`,
|
|
135
|
+
`import`, or `export`.
|
|
136
|
+
- Function parameters are the pinned exception: parameter names and uses in the
|
|
137
|
+
function scope stay attached to that parameter binding.
|
|
138
|
+
|
|
139
|
+
Small example:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import astichi
|
|
143
|
+
|
|
144
|
+
builder = astichi.build()
|
|
145
|
+
builder.add.Root(
|
|
146
|
+
astichi.compile(
|
|
147
|
+
"""
|
|
148
|
+
items = []
|
|
149
|
+
astichi_hole(body)
|
|
150
|
+
result = tuple(items)
|
|
151
|
+
"""
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
builder.add.Step(
|
|
155
|
+
astichi.compile(
|
|
156
|
+
"""
|
|
157
|
+
astichi_pass(items, outer_bind=True).append("x")
|
|
158
|
+
"""
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
builder.Root.body.add.Step(order=0)
|
|
162
|
+
|
|
163
|
+
materialized = builder.build().materialize()
|
|
164
|
+
print(materialized.emit(provenance=False))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Emitted Python:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
items = []
|
|
171
|
+
items.append("x")
|
|
172
|
+
result = tuple(items)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Without `astichi_pass(items, outer_bind=True)`, the inner snippet does not get
|
|
176
|
+
to reuse `items` just because the spelling matches. That is deliberate. Astichi
|
|
177
|
+
defaults to isolated scopes and only crosses them when the source says so.
|
|
178
|
+
|
|
179
|
+
The fluent builder is also available as a data-driven named API. Descriptor
|
|
180
|
+
target data can feed that API directly:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
hole = root.describe().single_hole_named("body")
|
|
184
|
+
|
|
185
|
+
builder = astichi.build()
|
|
186
|
+
builder.add("Root", root)
|
|
187
|
+
builder.add("Step", astichi.compile("value = 1\n"))
|
|
188
|
+
builder.target(hole.with_root_instance("Root")).add("Step")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
That `builder.target(...)` call uses the same target address as
|
|
192
|
+
`builder.Root.body.add.Step()`, but the address came from `describe()` instead
|
|
193
|
+
of a Python attribute chain.
|
|
194
|
+
|
|
195
|
+
## Example: schema-specialized row projector
|
|
196
|
+
|
|
197
|
+
Suppose an ingestion pipeline knows its event schema at build time, and each
|
|
198
|
+
field needs its own normalization step. A runtime loop or dispatch table adds
|
|
199
|
+
overhead to every row. String templating works until ordering, scope, and
|
|
200
|
+
correctness start fighting each other.
|
|
201
|
+
|
|
202
|
+
Astichi lets you define the skeleton once, stitch in field-specific steps, and
|
|
203
|
+
emit the straight-line Python you actually want to run.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import astichi
|
|
207
|
+
|
|
208
|
+
root = astichi.compile(
|
|
209
|
+
"""
|
|
210
|
+
astichi_bind_external(FIELDS)
|
|
211
|
+
|
|
212
|
+
def project_row(row):
|
|
213
|
+
out = {}
|
|
214
|
+
for field in astichi_for(FIELDS):
|
|
215
|
+
astichi_hole(step)
|
|
216
|
+
return out
|
|
217
|
+
"""
|
|
218
|
+
).bind(FIELDS=("user_id", "total_cents", "created_at"))
|
|
219
|
+
|
|
220
|
+
builder = astichi.build()
|
|
221
|
+
builder.add.Root(root)
|
|
222
|
+
|
|
223
|
+
builder.add.UserId(
|
|
224
|
+
astichi.compile("out['user_id'] = int(row['user_id'])\n")
|
|
225
|
+
)
|
|
226
|
+
builder.add.TotalCents(
|
|
227
|
+
astichi.compile("out['total_cents'] = int(row['total_cents'])\n")
|
|
228
|
+
)
|
|
229
|
+
builder.add.CreatedAt(
|
|
230
|
+
astichi.compile("out['created_at'] = row['created_at'][:10]\n")
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
builder.Root.step[0].add.UserId()
|
|
234
|
+
builder.Root.step[1].add.TotalCents()
|
|
235
|
+
builder.Root.step[2].add.CreatedAt()
|
|
236
|
+
|
|
237
|
+
projector = builder.build().materialize()
|
|
238
|
+
print(projector.emit(provenance=False))
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Emitted Python:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
def project_row(row):
|
|
245
|
+
out = {}
|
|
246
|
+
out["user_id"] = int(row["user_id"])
|
|
247
|
+
out["total_cents"] = int(row["total_cents"])
|
|
248
|
+
out["created_at"] = row["created_at"][:10]
|
|
249
|
+
return out
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
That is the point: no runtime field loop, no dispatch registry, no handwritten
|
|
253
|
+
template surgery. The generated function is plain Python, specialized to the
|
|
254
|
+
known schema, and suitable for hot-path use.
|
|
255
|
+
|
|
256
|
+
This is exactly the class of problem where a reliable AST stitcher matters:
|
|
257
|
+
|
|
258
|
+
- block fragments must land in the right lexical scope
|
|
259
|
+
- per-field steps must keep deterministic order
|
|
260
|
+
- compile-time schema data must become literal Python
|
|
261
|
+
- the final output must still be valid, inspectable source
|
|
262
|
+
|
|
263
|
+
## Current surface
|
|
264
|
+
|
|
265
|
+
Astichi currently provides:
|
|
266
|
+
|
|
267
|
+
- `astichi.compile(source, file_name=None, line_number=1, offset=0)`
|
|
268
|
+
- `astichi.build()` for builder-based composition
|
|
269
|
+
- concrete composables with `.bind(...)`, `.describe()`, `.materialize()`, and
|
|
270
|
+
`.emit(...)` / `.emit_commented()`
|
|
271
|
+
- data-driven builder calls such as `builder.add("Root", root)` and
|
|
272
|
+
`builder.target(hole.with_root_instance("Root")).add("Step")`
|
|
273
|
+
- provenance helpers in `astichi.emit`
|
|
274
|
+
|
|
275
|
+
Supported pieces today include block holes, expression inserts, external
|
|
276
|
+
binding, managed Python imports, materialization, emission, and builder-driven
|
|
277
|
+
loop unrolling.
|
|
278
|
+
|
|
279
|
+
## Layout
|
|
280
|
+
|
|
281
|
+
| Path | Role |
|
|
282
|
+
|------|------|
|
|
283
|
+
| `src/astichi/` | Library code |
|
|
284
|
+
| `docs/` | User-facing docs |
|
|
285
|
+
| `tests/` | Pytest suite |
|
|
286
|
+
| `dev-docs/` | Design notes, active summary, and requirements |
|
|
287
|
+
| `scratch/` | Throwaway experiments (not shipped) |
|
|
288
|
+
|
|
289
|
+
## Development
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
python -m venv .venv && source .venv/bin/activate
|
|
293
|
+
pip install -e ".[dev]"
|
|
294
|
+
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Status
|
|
298
|
+
|
|
299
|
+
Early development (`0.1.0`), but already useful for controlled codegen
|
|
300
|
+
pipelines.
|
|
301
|
+
|
|
302
|
+
Start with:
|
|
303
|
+
|
|
304
|
+
- `docs/` for the user-facing surface
|
|
305
|
+
- `dev-docs/AstichiSingleSourceSummary.md` for the current implementation
|
|
306
|
+
snapshot and known gaps
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT. See [LICENSE](LICENSE).
|
astichi-0.1.0/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# astichi
|
|
2
|
+
|
|
3
|
+
AST-level composition and stitching for Python code generation.
|
|
4
|
+
|
|
5
|
+
Astichi takes small, marker-bearing Python snippets, composes them into one
|
|
6
|
+
coherent program, and emits runnable Python. It is built for generators that
|
|
7
|
+
need low-overhead output without falling back to brittle string templates or
|
|
8
|
+
runtime abstraction in hot paths.
|
|
9
|
+
|
|
10
|
+
Astichi is a fit when you want to:
|
|
11
|
+
|
|
12
|
+
- describe codegen intent in Python-shaped snippets
|
|
13
|
+
- stitch block and expression fragments at named composition sites
|
|
14
|
+
- bind compile-time values into source before final lowering
|
|
15
|
+
- unroll compile-time loops into straight-line Python
|
|
16
|
+
- synthesize managed imports that participate in hygiene
|
|
17
|
+
- inspect composables with descriptors before wiring them
|
|
18
|
+
- emit inspectable source with provenance instead of opaque runtime machinery
|
|
19
|
+
|
|
20
|
+
It is **not** a general macro system and **not** a generic codemod framework.
|
|
21
|
+
It is a focused library for generators that need a reliable AST stitcher.
|
|
22
|
+
|
|
23
|
+
## Why Astichi
|
|
24
|
+
|
|
25
|
+
Code generators often hit the same wall: the desired output is simple,
|
|
26
|
+
specialized Python, but the implementation ends up split between string
|
|
27
|
+
concatenation, ad hoc templates, and fragile scope management.
|
|
28
|
+
|
|
29
|
+
Astichi handles the parts that usually go wrong:
|
|
30
|
+
|
|
31
|
+
- valid Python ASTs instead of fragile template fragments
|
|
32
|
+
- deterministic insertion order for stitched code
|
|
33
|
+
- compile-time binding and loop unrolling before emission
|
|
34
|
+
- specialized straight-line Python instead of runtime dispatch layers
|
|
35
|
+
- emitted source you can diff, test, and round-trip
|
|
36
|
+
|
|
37
|
+
## Marker mental model
|
|
38
|
+
|
|
39
|
+
Astichi is marker-bearing Python source plus a small build pipeline.
|
|
40
|
+
|
|
41
|
+
- Markers are recognized from authored Python source.
|
|
42
|
+
- Marker meaning comes from AST position, not string matching alone.
|
|
43
|
+
- `compile(...)` parses marker-bearing source into a `Composable`.
|
|
44
|
+
- `build()` wires composables together.
|
|
45
|
+
- `describe()` exposes holes, binds, ports, and builder target addresses for
|
|
46
|
+
data-driven composition.
|
|
47
|
+
- `materialize()` resolves inserts, bindings, and hygiene, then produces real
|
|
48
|
+
Python.
|
|
49
|
+
|
|
50
|
+
The core markers are:
|
|
51
|
+
|
|
52
|
+
- `astichi_hole(name)` -> insertion site
|
|
53
|
+
- `astichi_keep(name)` -> hygiene-preserved name in expression / statement source
|
|
54
|
+
- `name__astichi_keep__` -> hygiene-preserved name in identifier position
|
|
55
|
+
- `name__astichi_arg__` -> identifier demand
|
|
56
|
+
- `name__astichi_param_hole__` -> function-parameter insertion target
|
|
57
|
+
- `astichi_funcargs(...)` -> call-argument payload
|
|
58
|
+
- `astichi_bind_external(name)` -> external/literal value slot
|
|
59
|
+
- `astichi_ref(path)` -> compile-time reducible identifier / attribute path
|
|
60
|
+
- `astichi_pyimport(module=..., names=(...))` -> managed Python import
|
|
61
|
+
- `astichi_comment("...")` -> final-output source comment
|
|
62
|
+
- `astichi_pass(name, outer_bind=True)` -> explicit same-name boundary read
|
|
63
|
+
- `astichi_import(name)` -> explicit whole-scope boundary import
|
|
64
|
+
- `astichi_export(name)` -> explicit outward supply
|
|
65
|
+
- `astichi_insert(...)` -> internal emitted metadata, not general authored API
|
|
66
|
+
|
|
67
|
+
Comment marker note:
|
|
68
|
+
|
|
69
|
+
- `astichi_comment("...")` is statement-only. Ordinary `materialize()` strips
|
|
70
|
+
it for executable output; `emit_commented()` renders it as real `#` comments.
|
|
71
|
+
- Multi-line payloads keep the marker statement's indentation, and only exact
|
|
72
|
+
`{__file__}` / `{__line__}` substrings are expanded.
|
|
73
|
+
|
|
74
|
+
Value-form target note:
|
|
75
|
+
|
|
76
|
+
- `astichi_ref(...)` and `astichi_pass(...)` are ordinary value-form surfaces in
|
|
77
|
+
expressions.
|
|
78
|
+
- If the marker result itself must occupy an `Assign` / `AugAssign` / `Delete`
|
|
79
|
+
target position, append `._` or `.astichi_v`:
|
|
80
|
+
`astichi_ref("self.f0")._ = 1`,
|
|
81
|
+
`astichi_pass(counter).astichi_v = 1`.
|
|
82
|
+
- If you immediately continue to a real attribute, plain Python target syntax
|
|
83
|
+
already works:
|
|
84
|
+
`astichi_pass(obj).field = 1`.
|
|
85
|
+
|
|
86
|
+
The one rule that matters most is scope:
|
|
87
|
+
|
|
88
|
+
- `astichi_insert` is the basic Astichi boundary.
|
|
89
|
+
- Each inserted composable lives in its own Astichi scope.
|
|
90
|
+
- There is no implicit capture across that boundary.
|
|
91
|
+
- If a name crosses the boundary, make it explicit with `keep`, `pass`,
|
|
92
|
+
`import`, or `export`.
|
|
93
|
+
- Function parameters are the pinned exception: parameter names and uses in the
|
|
94
|
+
function scope stay attached to that parameter binding.
|
|
95
|
+
|
|
96
|
+
Small example:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import astichi
|
|
100
|
+
|
|
101
|
+
builder = astichi.build()
|
|
102
|
+
builder.add.Root(
|
|
103
|
+
astichi.compile(
|
|
104
|
+
"""
|
|
105
|
+
items = []
|
|
106
|
+
astichi_hole(body)
|
|
107
|
+
result = tuple(items)
|
|
108
|
+
"""
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
builder.add.Step(
|
|
112
|
+
astichi.compile(
|
|
113
|
+
"""
|
|
114
|
+
astichi_pass(items, outer_bind=True).append("x")
|
|
115
|
+
"""
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
builder.Root.body.add.Step(order=0)
|
|
119
|
+
|
|
120
|
+
materialized = builder.build().materialize()
|
|
121
|
+
print(materialized.emit(provenance=False))
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Emitted Python:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
items = []
|
|
128
|
+
items.append("x")
|
|
129
|
+
result = tuple(items)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Without `astichi_pass(items, outer_bind=True)`, the inner snippet does not get
|
|
133
|
+
to reuse `items` just because the spelling matches. That is deliberate. Astichi
|
|
134
|
+
defaults to isolated scopes and only crosses them when the source says so.
|
|
135
|
+
|
|
136
|
+
The fluent builder is also available as a data-driven named API. Descriptor
|
|
137
|
+
target data can feed that API directly:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
hole = root.describe().single_hole_named("body")
|
|
141
|
+
|
|
142
|
+
builder = astichi.build()
|
|
143
|
+
builder.add("Root", root)
|
|
144
|
+
builder.add("Step", astichi.compile("value = 1\n"))
|
|
145
|
+
builder.target(hole.with_root_instance("Root")).add("Step")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
That `builder.target(...)` call uses the same target address as
|
|
149
|
+
`builder.Root.body.add.Step()`, but the address came from `describe()` instead
|
|
150
|
+
of a Python attribute chain.
|
|
151
|
+
|
|
152
|
+
## Example: schema-specialized row projector
|
|
153
|
+
|
|
154
|
+
Suppose an ingestion pipeline knows its event schema at build time, and each
|
|
155
|
+
field needs its own normalization step. A runtime loop or dispatch table adds
|
|
156
|
+
overhead to every row. String templating works until ordering, scope, and
|
|
157
|
+
correctness start fighting each other.
|
|
158
|
+
|
|
159
|
+
Astichi lets you define the skeleton once, stitch in field-specific steps, and
|
|
160
|
+
emit the straight-line Python you actually want to run.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
import astichi
|
|
164
|
+
|
|
165
|
+
root = astichi.compile(
|
|
166
|
+
"""
|
|
167
|
+
astichi_bind_external(FIELDS)
|
|
168
|
+
|
|
169
|
+
def project_row(row):
|
|
170
|
+
out = {}
|
|
171
|
+
for field in astichi_for(FIELDS):
|
|
172
|
+
astichi_hole(step)
|
|
173
|
+
return out
|
|
174
|
+
"""
|
|
175
|
+
).bind(FIELDS=("user_id", "total_cents", "created_at"))
|
|
176
|
+
|
|
177
|
+
builder = astichi.build()
|
|
178
|
+
builder.add.Root(root)
|
|
179
|
+
|
|
180
|
+
builder.add.UserId(
|
|
181
|
+
astichi.compile("out['user_id'] = int(row['user_id'])\n")
|
|
182
|
+
)
|
|
183
|
+
builder.add.TotalCents(
|
|
184
|
+
astichi.compile("out['total_cents'] = int(row['total_cents'])\n")
|
|
185
|
+
)
|
|
186
|
+
builder.add.CreatedAt(
|
|
187
|
+
astichi.compile("out['created_at'] = row['created_at'][:10]\n")
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
builder.Root.step[0].add.UserId()
|
|
191
|
+
builder.Root.step[1].add.TotalCents()
|
|
192
|
+
builder.Root.step[2].add.CreatedAt()
|
|
193
|
+
|
|
194
|
+
projector = builder.build().materialize()
|
|
195
|
+
print(projector.emit(provenance=False))
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Emitted Python:
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
def project_row(row):
|
|
202
|
+
out = {}
|
|
203
|
+
out["user_id"] = int(row["user_id"])
|
|
204
|
+
out["total_cents"] = int(row["total_cents"])
|
|
205
|
+
out["created_at"] = row["created_at"][:10]
|
|
206
|
+
return out
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
That is the point: no runtime field loop, no dispatch registry, no handwritten
|
|
210
|
+
template surgery. The generated function is plain Python, specialized to the
|
|
211
|
+
known schema, and suitable for hot-path use.
|
|
212
|
+
|
|
213
|
+
This is exactly the class of problem where a reliable AST stitcher matters:
|
|
214
|
+
|
|
215
|
+
- block fragments must land in the right lexical scope
|
|
216
|
+
- per-field steps must keep deterministic order
|
|
217
|
+
- compile-time schema data must become literal Python
|
|
218
|
+
- the final output must still be valid, inspectable source
|
|
219
|
+
|
|
220
|
+
## Current surface
|
|
221
|
+
|
|
222
|
+
Astichi currently provides:
|
|
223
|
+
|
|
224
|
+
- `astichi.compile(source, file_name=None, line_number=1, offset=0)`
|
|
225
|
+
- `astichi.build()` for builder-based composition
|
|
226
|
+
- concrete composables with `.bind(...)`, `.describe()`, `.materialize()`, and
|
|
227
|
+
`.emit(...)` / `.emit_commented()`
|
|
228
|
+
- data-driven builder calls such as `builder.add("Root", root)` and
|
|
229
|
+
`builder.target(hole.with_root_instance("Root")).add("Step")`
|
|
230
|
+
- provenance helpers in `astichi.emit`
|
|
231
|
+
|
|
232
|
+
Supported pieces today include block holes, expression inserts, external
|
|
233
|
+
binding, managed Python imports, materialization, emission, and builder-driven
|
|
234
|
+
loop unrolling.
|
|
235
|
+
|
|
236
|
+
## Layout
|
|
237
|
+
|
|
238
|
+
| Path | Role |
|
|
239
|
+
|------|------|
|
|
240
|
+
| `src/astichi/` | Library code |
|
|
241
|
+
| `docs/` | User-facing docs |
|
|
242
|
+
| `tests/` | Pytest suite |
|
|
243
|
+
| `dev-docs/` | Design notes, active summary, and requirements |
|
|
244
|
+
| `scratch/` | Throwaway experiments (not shipped) |
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
python -m venv .venv && source .venv/bin/activate
|
|
250
|
+
pip install -e ".[dev]"
|
|
251
|
+
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Status
|
|
255
|
+
|
|
256
|
+
Early development (`0.1.0`), but already useful for controlled codegen
|
|
257
|
+
pipelines.
|
|
258
|
+
|
|
259
|
+
Start with:
|
|
260
|
+
|
|
261
|
+
- `docs/` for the user-facing surface
|
|
262
|
+
- `dev-docs/AstichiSingleSourceSummary.md` for the current implementation
|
|
263
|
+
snapshot and known gaps
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""astichi — AST composition for ahead-of-time Python codegen."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from astichi.builder import build
|
|
6
|
+
from astichi.frontend import compile
|
|
7
|
+
from astichi.model import Composable, ComposableDescription, ComposableHole, TargetAddress
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"__version__",
|
|
11
|
+
"Composable",
|
|
12
|
+
"ComposableDescription",
|
|
13
|
+
"ComposableHole",
|
|
14
|
+
"TargetAddress",
|
|
15
|
+
"build",
|
|
16
|
+
"compile",
|
|
17
|
+
]
|