{"id":37065353,"url":"https://github.com/courage-tci/gekkota","last_synced_at":"2026-01-14T07:39:02.162Z","repository":{"id":49796142,"uuid":"454435604","full_name":"courage-tci/gekkota","owner":"courage-tci","description":"Extensible Python code generator","archived":false,"fork":false,"pushed_at":"2024-11-16T05:14:35.000Z","size":98,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"pub","last_synced_at":"2025-09-16T09:56:28.487Z","etag":null,"topics":["code-generation","codegen","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/courage-tci.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-01T15:13:02.000Z","updated_at":"2025-01-07T21:02:46.000Z","dependencies_parsed_at":"2023-01-31T05:30:24.569Z","dependency_job_id":null,"html_url":"https://github.com/courage-tci/gekkota","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/courage-tci/gekkota","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/courage-tci%2Fgekkota","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/courage-tci%2Fgekkota/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/courage-tci%2Fgekkota/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/courage-tci%2Fgekkota/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/courage-tci","download_url":"https://codeload.github.com/courage-tci/gekkota/tar.gz/refs/heads/pub","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/courage-tci%2Fgekkota/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28413463,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","codegen","python"],"created_at":"2026-01-14T07:39:01.484Z","updated_at":"2026-01-14T07:39:02.153Z","avatar_url":"https://github.com/courage-tci.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/courage-tci/gekkota/build.yml?branch=pub)](https://github.com/courage-tci/gekkota/actions/workflows/build.yml)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/courage-tci/gekkota/test.yml?branch=pub\u0026label=tests)](https://github.com/courage-tci/gekkota/actions/workflows/test.yml)\n[![PyPI](https://img.shields.io/pypi/v/gekkota)](https://pypi.org/project/gekkota/)\n![PyPI - Downloads](https://pepy.tech/badge/gekkota)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/gekkota)\n[![Coveralls](https://img.shields.io/coverallsCoverage/github/courage-tci/gekkota?label=test%20coverage)](https://coveralls.io/github/courage-tci/gekkota?branch=pub)\n![License](https://img.shields.io/github/license/courage-tci/gekkota)\n![Badge Count](https://img.shields.io/badge/badges-8-important)\n\nThis is a Python code-generation module.\n\n-   Generates any Python statement/expression\n-   Places parens to ensure expression priorities are unchanged\n-   Places extra newlines before/after class/function definitions to conform with PEP 8\n-   100% coverage of type hints, passing strict Pyright checks on every PR\n-   Meaningful type hierarchy inspired by Python grammar to guarantee syntax validity almost in every case.\n-   Covered with ~~diamonds~~ tests completely\n-   Compatible with [wordstreamer](https://github.com/evtn/wordstreamer) renderables via a [wrapper](#wordstreamer-compatibility)\n\n## Installation\n\nJust install `gekkota` package, e.g. with `python -m pip install gekkota` (or any other package manager of your choice)\n\n## Rendering and configuration\n\nTo render any `Renderable` into a string, you could use a few approaches:\n\n-   `str(renderable)`: renders a Renderable with default configuration (check below)\n-   `renderable.render_str()`: also default configuration\n-   `renderable.render_str(config)`: overrides default config options with provided in `config` mapping. Unspecified keys remain at default values\n\nHere is current default config:\n\n```python\ndefault_config: Config = {\n    \"tab_size\": 4,  # how much chars to use in indentation\n    \"compact\": False,  # if True, renders without redundant whitespace (e.g \"for [i, e] in enumerate(a)\" renders as \"for[i,e]in enumerate(a)\")\n    \"tab_char\": \" \",  # character used for indentation\n    # \"place_semicolons\" and \"inline_small_stmts\" options have some performance impact, since those require checking for newlines in token stream before re-streaming tokens.\n    # this impact is probably negligible, but be aware of it\n    \"place_semicolons\": False,  # if True, semicolons are placed after one-line statements\n    \"inline_small_stmts\": False,  # if True, one-line statements are inlined. Overrides \"place_semicolons\" if True.\n}\n```\n\n## Expressions\n\n### Basic expressions\n\nYour starting points would be `to_expression` and `Name`:\n\n```python\nfrom gekkota import Name, to_expression\n\n# Name(name: str, annotation: Optional[Expression] = None)\n# to_expression(value: int | float | complex | str | bytes | bool | None)\n\na = Name(\"a\")\nb = Name(\"b\")\nsix = to_expression(6)\n\n# prints 'a + b * 6'\nprint(\n    (a + b * six)\n)\n\n```\n\n`Name`, as many other classes in the module, is an `Expression` instance\n\nExpressions support most operations to combine with other expressions.  \nExceptions are:\n\n-   Attribute reference: for that you should use `Expression.getattr(other: str)`\n-   Indexing: `Expression.index(index: Expression)`\n-   Slicing: `Expression.index(index: Union[SliceExpr, Sequence[SliceExpr]])`\n-   Equality / Inequality: `Expression.eq(right_hand_side)` and `Expression.neq(right_hand_side)` respectively\n-   `is`: `Expression.is_(right_hand_side)`,\n-   `is not`: `Expression.is_not(right_hand_side)`\n-   `in`: `Expression.in_(right_hand_side)`\n-   `not in`: `Expression.not_in(right_hand_side)`\n-   `and`: `Expression.and_(right_hand_side)`\n-   `or`: `Expression.or_(right_hand_side)`\n-   `await`: `Expression.await_()`\n-   `:=` assignment: `Expression.assign(value)`\n-   Ternary operator: `Expression.if_(condition, alternative)`\n\nFor example:\n\n```python\nfrom gekkota import Name\n\na = Name(\"a\")\nb = Name(\"b\")\n\nexpression = a.await_().in_(b)\n\nprint(expression) # await a in b\n\n```\n\nFor any other operation on expressions you can just use familiar Python syntax:\n\n```python\nfrom gekkota import Name\n\na = Name(\"a\")\nb = Name(\"b\")\nc = Name(\"c\")\n\nprint(\n    (a + b * c / a(b, c)) # 'a + b * c / a(b, c)'\n)\n\n```\n\n### Sequences\n\nMost convenient way to create sequence literals is, again, `to_expression`:\n\n```python\nfrom gekkota import to_expression, Name\n\na = Name(\"a\")\nb = Name(\"b\")\n\nprint(\n    to_expression( (a, b, 6) ), # '(a, b, 6)' (notice that to_expression is recursive)\n    to_expression( (a, ) ),     # '(a, )'\n    to_expression([a, b]),      # '[a, b]'\n    to_expression([]),          # '[]'\n    to_expression({a: b}),      # '{a: b}'\n    to_expression(set()),       # 'set()'\n    to_expression([a, [a, b]]), # '[a, [a, b]]'\n)\n```\n\nIf you want to have more precise control, you can use `TupleExpr`, `ListExpr`, `SetExpr` and `DictExpr` for this.\nAll have same constructor signature: `(values: Sequence[Expression])` (except `DictExpr`, which has `KeyValue` values)\n\nTo create comprehensions:\n\n```python\nfrom gekkota import Name, GeneratorFor, GeneratorIf\nfrom gekkota import (\n    ListComprehension,\n    DictComprehension,\n    KeyValue,\n    SetComprehension, # same usage as ListComprehension\n)\n\na, b, c, d = map(Name, \"abcd\")\n\n# ListComprehension(generator_or_expr: GeneratorBase | Expression, parts: Sequence[GeneratorPart] = ())\nprint(\n    ListComprehension(\n        a,\n        [\n            # GeneratorFor(target: AssignmentTarget, iterator: Expression, *, is_async: bool = False)\n            GeneratorFor(b, c),\n\n            # GeneratorIf(condition: Expression)\n            GeneratorIf(b.eq(d))\n        ]\n    )\n) # [a for b in c if b == d]\n\n# DictComprehension(generator_or_expr: GeneratorBase | KeyValue, parts: Sequence[GeneratorPart] = ())\n# GeneratorPart == GeneratorFor | GeneratorIf\nprint(\n    DictComprehension(KeyValue(a, b), [GeneratorFor(c, d), GeneratorIf(b.eq(d))])\n) # {a: b for c in d if b == d}\n\n```\n\n### Keyword call args\n\nUse `CallArg` to provide keyword call args:\n\n```python\nfrom gekkota import Name, to_expression\n\nprint_ = Name(\"print\")\n\n# CallArg(name: str, value: Optional[Expression] = None)\nprint(\n    print_(\n        Name(\"a\"),\n        CallArg(\"b\"),\n        CallArg(\"sep\", to_expression(\", \"))\n    )\n) # print(a, b, sep=', ')\n\n```\n\n### Type hints\n\nTo annotate a name, just pass an additional parameter to `Name`:\n\n```python\nfrom gekkota import Name\n\na = Name(\"a\", Name(\"int\"))\n\nprint(a) # a: int\n\n```\n\nNew in version 0.6: `Name` is now generic, depending on the presence of the annotation.  \nSome constructors now accept only annotated `Name`, while some others accept only unannotated (depends on syntax features allowed).  \nThis allows to ensure type soundness without breaking changes in API.\n\n## Statements\n\nTo render program code (with multiple statements), use `Code`:\n\n```python\nfrom gekkota import Code, Assignment, Name\n\na = Name(\"a\")\n\nsix = Literal(6)\n\ncreate_variable = Assignment(\n    [Name(\"a\")],\n    six + six\n)\n\nprint_variable = Name(\"print\")(a)\n\nprint(\n    Code([\n        create_variable,\n        print_variable,\n    ])\n)\n# prints:\n# a = 6 + 6\n# print(a)\n\n```\n\nTo render a block of code, use `Block`:\n\n```python\nfrom gekkota import Block, IfStmt, Assignment, Name\n\na = Name(\"a\")\nb = Name(\"b\")\n\nsix = Literal(6)\n\ncreate_variable = Assignment(\n    [Name(\"a\")],\n    six + six\n)\n\nprint_variable = Name(\"print\")(a)\n\nprint(\n    IfStmt(\n        b,\n        Block([\n            create_variable,\n            print_variable,\n        ])\n    )\n)\n# prints:\n# if b:\n#     a = 6 + 6\n#     print(a)\n\n```\n\nIf the difference between two is not obvious: `Code` just renders statements on separate lines, while block also adds a newline before the first statement and indentation to every line.\nMoreover, `Code([])` renders into `\"\"`, while `Block([])` — into `\"\\n    pass\"`\n\n### Small statements\n\nHere is an example of a few small statements:\n\n```python\nfrom gekkota import Name, SequenceExpr\nfrom gekkota import (\n    ReturnStmt,\n    DelStmt,\n    AssertStmt,\n    BreakStmt,\n    ContinueStmt,\n    YieldStmt,\n    YieldFromStmt,\n    NonLocalStmt,\n    GlobalStmt,\n    PassStmt,\n    RaiseStmt,\n    AsyncStmt\n)\n\na, b, c = map(Name, \"abc\")\n\nprint(ReturnStmt(a)) # 'return a'\n\nprint(YieldStmt(a)) # 'yield a'\nprint(YieldFromStmt(b)) # 'yield from b'\n\nprint(DelStmt(a, b)) # 'del a, b'\n\nprint(AssertStmt(a)) # 'assert a'\n\nprint(BreakStmt()) # 'break'\n\nprint(ContinueStmt()) # 'continue'\n\nprint(GlobalStmt(a, b)) # 'global a, b'\nprint(NonLocalStmt(a, b)) # 'nonlocal a, b'\n\nprint(PassStmt()) # 'pass'\n\nprint(RaiseStmt()) # 'raise'\nprint(RaiseStmt(a)) # 'raise a'\nprint(RaiseStmt(a, b)) # 'raise a from b'\n```\n\n### Type statement\n\nGekkota 0.6 supports [PEP 695](https://docs.python.org/3.12/whatsnew/3.12.html#whatsnew312-pep695) which adds new syntax, including `type` statement:\n\n```python\ntype Point = tuple[float, float]\n```\n\nTo generate such statement in gekkota, use `gekkota.annotations.TypeStmt`:\n\n```python\nfrom gekkota import TypeStmt\n\n# type Point = tuple[float, float]\nprint(\n    TypeStmt(\n        \"Point\",\n        [],\n        TupleExpr([Name(\"float\"), Name(\"float\")])\n    )\n)\n```\n\nThe second argument is a sequence of type parameters (for genric types): `Sequence[TypeParam]`\n\nYou can use several types as TypeParam:\n\n```python\nfrom gekkota import TypeParam, TypeStmt, Name, TypeVarParam, TypeVarTupleParam, ParamSpecParam\n\ndef make_type(type_params: Sequence[TypeParam]):\n    return TypeStmt(\n        \"CoolAlias\",\n        type_params,\n        Name(\"int\")\n    )\n\n\nprint(make_type([])) # type CoolAlias = int\n\n# (both) type CoolAlias[T] = int\nprint(make_type([Name(\"T\")]))\nprint(\n    make_type([TypeVarParam(Name(\"T\"))])\n)\n\n# (both) type CoolAlias[T: float] = int\nprint(make_type([Name(\"T\", Name(\"float\"))]))\nprint(\n    make_type(\n        [\n            TypeVarParam(\n                name=Name(\"T\"),\n                value=Name(\"float\")\n            )\n        ]\n    )\n)\n\n# type CoolAlias[*T] = int\nprint(\n    make_type(\n        [\n            TypeVarTupleParam(\n                Name(\"T\"),\n            )\n        ]\n    )\n)\n\n# type CoolAlias[**T] = int\nprint(\n    make_type(\n        [\n            ParamSpecParam(\n                Name(\"T\"),\n            )\n        ]\n    )\n)\n```\n\n#### Generic Functions and Classes\n\n`FuncDef` and `ClassDef` now accept an optional keyword argument `type_params` accepting `Sequence[TypeParam]`:\n\n```python\nfrom gekkota import FuncDef, Name, PassStmt\n\ntypeparam = Name(\"T\")\n\nnewfunc = FuncDef(\n    name=\"newfunc\",\n    args=[Name(\"x\", typeparam)],\n    body=PassStmt(),\n    rtype=typeparam,\n    type_params=[typeparam],\n)\n\nprint(newfunc)\n\"\"\"\ndef newfunc[T](x: T) -\u003e T: pass\n\"\"\"\n```\n\n### Assignment\n\nFor common assigment use `Assignment`:\n\n```python\nfrom gekkota import Assignment, Name\n\na, b, c = map(Name, \"abc\")\n\n# Assignment(targets: Sequence[AssignmentTarget] | AnnotatedTarget, value: Expression)\n\nprint(\n    Assignment([a], b), # a = b\n    Assignment([a.index(b)], c) # a[b] = c\n    Assignment([a, b], c), # a = b = c\n)\n\n```\n\nTo annotate assignment (or just annotate a variable), use `AnnotatedTarget`:\n\n```python\n\nfrom gekkota import Assignment, AnnotatedTarget, Name\n\na, b, c = map(Name, \"abc\")\nD = Name(\"D\")\n\n# AnnotatedTarget(target: AssignmentTarget, annotation: Expression)\nprint(\n    Assignment(AnnotatedTarget(a, D), b), # a: D = b\n    Assignment(AnnotatedTarget(a.index(b), D), c) # a[b]: D = c\n    Assignment([a, b], c), # a = b = c\n)\n\n```\n\nFor augmented assignment (e.g. `+=`) use `AugmentedAssignment`:\n\n```python\nfrom gekkota import Assignment, Name\n\na, b, c = map(Name, \"abc\")\n\n# AugmentedAssignment(target: AugAssignmentTarget, op: str, expression: Expression)\n\nprint(\n    AugmentedAssignment(a, \"+=\", b), # a += b\n    AugmentedAssignment(a.index(b), \"*=\", c) # a *= c\n)\n\n```\n\n### Control flow\n\nFor control flow you can use `IfStmt`, `ElifStmt` and `ElseStmt`:\n\n```python\nfrom gekkota import Name, IfStmt, ElifStmt, ElseStmt, Code\n\na, b, c = map(Name, \"abc\")\n\n# IfStmt(condition: Expression, body: Statement)\n# ElifStmt(condition: Expression, body: Statement)\n# ElseStmt(body: Statement)\ncode = Code([\n    IfStmt(a, b),\n    ElifStmt(b, a),\n    ElseStmt(c)\n])\n\nprint(code)\n\"\"\"\nif a: b\nelif b: a\nelse: c\n\"\"\"\n```\n\n### Loops\n\nUse `ForStmt` and `WhileStmt` for loops:\n\n```python\nfrom gekkota import ForStmt, WhileStmt, Name\n\na, b, c = map(Name, \"abc\")\n\n# ForStmt(target: Expression, iterator: Expression, body: Statement, *, is_async: bool = False)\nprint(\n    ForStmt(a, b, c)\n) # for a in b: c\n\n# WhileStmt(condition: Expression, body: Statement)\nprint(\n    WhileStmt(a, b)\n) # while a: b\n```\n\n### Functions\n\nTo render a function definition, you will need a `FuncDef`:\n\n```python\nfrom gekkota import Name, FuncDef\n\na, b, c = map(Name, \"abc\")\n\n# FuncDef(\n#    name: str,\n#    args: Sequence[FuncArg],\n#    body: Statement,\n#    *,\n#    rtype: Optional[Expression] = None,\n#    is_async: bool = False,\n#    type_params: Sequence[TypeParam] = (),\n# )\n\nprint(\n    FuncDef(\n        \"cool_func\",\n        [a],\n        b,\n        rtype=c,\n    )\n) # def cool_func(a) -\u003e c: b\n```\n\n_New in 0.6:_ Now FuncDef accepts a `type_params` argument (check [Generic Functions and Classes](#generic-functions-and-classes))\n\nTo provide a default value and/or annotations to arguments, use `FuncArg`:\n\n```python\n\nfrom gekkota import Name, FuncDef, FuncArg, to_expression\n\na, b, c = map(Name, \"abc\")\n\n# FuncDef(name: str, args: Sequence[FuncArg], body: Statement, *, rtype: Optional[Expression] = None, is_async: bool = False)\n# FuncArg(\n#     name: str,\n#     annotation: Optional[Expression] = None,\n#     default_value: Optional[Expression] = None,\n#     *,\n#     late_bound_default: bool = False,\n# )\nprint(\n    FuncDef(\n        \"cool_func\",\n        [\n            FuncArg(\n                \"a\",\n                Name(\"int\"),\n                to_expression(0)\n            )\n        ],\n        b,\n        rtype=c,\n    )\n) # def cool_func(a: int = 0) -\u003e c: b\n\n```\n\n_New in version 0.6:_ support for [PEP 671 (Draft) – Syntax for late-bound function argument defaults](https://peps.python.org/pep-0671/)\n\nOther argument types are:\n\n-   `StarArg(value: T = None)`: generates `*value`, `*` by default\n-   `DoubleStarArg(value)`: same as `StarArg`, but with `**`\n-   `Slash()` is `/` (a mark of positional-only arguments in Python 3.8+)\n\nLambda functions are generated using `LambDef`:\n\n```python\nfrom gekkota import Name, LambDef\n\na, b, c = map(Name, \"abc\")\n\n# LambDef(args: Sequence[FuncArg], body: Expression)\nprint(\n    LambDef(\n        [a],\n        b,\n    )\n) # lambda a: b\n```\n\nTo decorate a function/class, use `Decorated`:\n\n```python\nfrom gekkota import Name, FuncDef, Decorated\n\ndecorator = Name(\"decorator\")\na, b, c = map(Name, \"abc\")\n\n# Decorated(decorator: Expression, statement: ClassDef | FuncDef)\n# FuncDef(name: str, args: Sequence[FuncArg], body: Statement, *, rtype: Optional[Expression] = None, is_async: bool = False)\nprint(\n    Decorated(\n        decorator,\n        FuncDef(\n            \"cool_func\",\n            [a],\n            b,\n            rtype=c,\n        )\n    )\n)\n# @decorator\n# def cool_func(a) -\u003e c: b\n```\n\n### Classes\n\nTo define a class, use `ClassDef`:\n\n```python\nfrom gekkota import Name, ClassDef\n\na, b, c = map(Name, \"abc\")\n\n# ClassDef(\n#     name: str,\n#     args: Sequence[CallArg | Expression],\n#     body: Statement,\n#     *,\n#     type_params: Sequence[TypeParam] = (),\n# )\nprint(\n    ClassDef(\"MyClass1\", [], a)\n) # class MyClass1: a\n\nprint(\n    ClassDef(\"MyClass2\", [b], c)\n) # class MyClass2(b): c\n\n```\n\n_New in 0.6:_ Now ClassDef accepts a `type_params` argument (check [Generic Functions and Classes](#generic-functions-and-classes))\n\n### Imports\n\nTo render imports, use `ImportStmt` and `FromImportStmt`:\n\n```python\nfrom gekkota import Name, StarArg, ImportDots, ImportSource, ImportStmt, FromImportStmt, ImportAlias\n\n\n# ImportStmt(names: Sequence[ImportAlias | Name | StarArg[None]])\nprint(\n    ImportStmt([Name(\"a\")])\n) # import a\n\nprint(\n    ImportStmt([Name(\"a\"), Name(\"b\")])\n) # import a, b\n\n# FromImportStmt(source: ImportSource | Name, names: Sequence[ImportAlias | Name | StarArg[None]])\n# ImportAlias(name: Name, alias: Name | None = None)\nprint(\n    FromImportStmt(\n        Name(\"math\"),\n        [\n            Name(\"cos\"),\n            ImportAlias(Name(\"sin\"), Name(\"tan\")) # we do a little trolling\n        ]\n    )\n) # from math import cos, sin as tan\n\nprint(\n    FromImportStmt(\n        Name(\"gekkota\"),\n        [StarArg()]\n    )\n) # from gekkota import *\n\n# ImportDots(length: int = 1)\nprint(\n    FromImportStmt(\n        ImportDots(),\n        [StarArg()]\n    )\n) # from . import *\n\n# ImportSource(parts: Sequence[str])\nprint(\n    FromImportStmt(\n        ImportSource([\"\", \"values\"]),\n        [Name(\"Name\")]\n    )\n) # from .values import Name\n\n```\n\n### Exceptions\n\n```python\nfrom gekkota import Name, TryStmt, ExceptStmt, FinallyStmt, Block\n\na, b, e = map(Name, \"abe\")\n\n# TryStmt(body: Statement)\nprint(\n    TryStmt(a)\n) # try: a\n\n# ExceptStmt(exceptions: Sequence[Expression] | None, alias: Name | None, body: Statement)\nprint(\n    ExceptStmt(None, None, a)\n) # except: a\n\nprint(\n    ExceptStmt(None, None, Block([]))\n)\n# except:\n#     pass\n\nprint(\n    ExceptStmt([a], None, b)\n) # except a: b\n\nprint(\n    ExceptStmt([a], e, b)\n) # except a as e: b\n\n# FinallyStmt(body: Statement)\nprint(\n    FinallyStmt(a)\n) # finally: a\n\n```\n\n### Context Managers\n\n```python\nfrom gekkota import Name, WithStmt, WithTarget\n\na, b, e = map(Name, \"abe\")\n\n# WithStmt(targets: Sequence[WithTarget | Expression], body: Statement, *, is_async: bool = False,)\nprint(\n    WithStmt([a], b)\n) # with a: b\n\n# WithTarget(expression: Expression, alias: str | None = None)\nprint(\n    WithStmt([WithTarget(a, \"aaaa\")], b)\n) # with a as aaaa: b\n\nprint(\n    WithStmt([a], b, is_async=True)\n) # async with a: b\n\n```\n\n### Pattern matching\n\nThis section is currently unfinished, check [pattern_matching.py](https://github.com/courage-tci/gekkota/blob/pub/gekkota/pattern_matching.py)\n\n## Custom rendering\n\nIf your custom element can be meaningfully represented as a combination of existing elements, you can use a function instead of a class:\n\n```python\nfrom gekkota import Expression\n\ndef Square(e: Expression) -\u003e Expression:\n    return e * e\n\n```\n\nThis is a pretty obvious approach, but often it works best.\n\n---\n\nWhile being aimed at Python code generation, `gekkota` is pretty extensible, and can be used to render different things.  \nYou can build custom renderables, statements, expressions, and so on.\n\nThe simplest example of a custom renderable would be:\n\n```python\nfrom gekkota import Renderable, StrGen, Config\n\n\nclass RenderString(Renderable):\n    \"\"\"It renders whatever is passed to it\"\"\"\n\n    def __init__(self, value: str):\n        self.value = value\n\n    def render(self, config: Config) -\u003e StrGen:\n        yield self.value\n```\n\nLet's suppose you want to render a custom expression: a custom sequence literal (obviously isn't valid in Python, but you need it for some reason).  \nSuppose your custom literal would be in form of `\u003c|value1, value2, ...|\u003e`.\n\nYou can extend `SequenceExpr` for that:\n\n```python\nfrom gekkota import SequenceExpr, Name\n\nclass MyCoolSequence(SequenceExpr):\n    parens = \"\u003c|\", \"|\u003e\"\n\n\nseq = MyCoolSequence([Name(\"a\"), Name(\"b\")])\n\nprint(seq) # \u003c|a,b|\u003e\n\n```\n\nThat's it, you're ready to render this literal (which, again, isn't valid in Python but anyway).\n\nOr you could go further and write rendering by yourself (it's easier than it sounds):\n\n```python\nfrom gekkota import Expression, Config\n\n\nclass MyCoolSequence(Expression):\n    def __init__(self, values: Sequence[Expression]):\n        self.values = values\n\n    # could be rewritten to be simpler, check `Useful utils` section below\n    def render(self, config: Config) -\u003e StrGen:\n        yield \"\u003c|\"\n\n        for i, item in enumerate(self.values):\n            yield from item.render(config)\n\n            if i + 1 \u003c len(self.values): # no comma after last element\n                yield \",\"\n                yield \" \"\n\n        yield \"|\u003e\"\n```\n\nIt's fairly easy, just render every part in the right order:\n\n-   To render a string, use `yield string`\n-   To render a `Renderable`, use `yield from renderable.render(config)`\n\n### Choosing a right base class\n\nTo choose a right base class, think in what context you want to use your renderable.  \nIf there is a similar context in Python (e.g. your renderable is a block statement, like `for` or `if`), extend that class.\n\nAfter choosing a right base class, check if it has a predefined render, maybe you won't need to write everything by yourself.  \nFor example, with `BlockStmt` you need to provide `render_head` instead:\n\n```python\n# that's the actual source from module, not an example\n\nclass BlockStmt(Statement):\n    body: Statement\n\n    def render_head(self, config: Config) -\u003e StrGen:\n        return NotImplemented\n\n    def render(self, config: Config) -\u003e StrGen:\n        yield from self.render_head(config)\n        yield \":\"\n        yield \" \"\n        yield from self.body.render(config)\n\n[...]\n\nclass ElseStmt(BlockStmt):\n    def __init__(self, body: Statement):\n        self.body = body\n\n    def render_head(self, config: config) -\u003e StrGen:\n        yield \"else\"\n```\n\n### Useful utils\n\n`gekkota.utils` provides `Utils` class which is useful for custom renderables. For example, custom `MyCoolSequence` could be implemented as:\n\n```python\nfrom gekkota import Expression, Utils\n\n\nclass MyCoolSequence(Expression):\n    def __init__(self, values: Sequence[Expression]):\n        self.values = values\n\n    def render(self, config: config) -\u003e StrGen:\n        yield from Utils.wrap(\n            [\"\u003c|\", \"|\u003e\"],\n            Utils.comma_separated(self.values, config)\n        )\n```\n\nMethods provided in `Utils`:\n\n-   `add_tab(generator: StrGen, config: Config) -\u003e StrGen`  \n     Adds indentation to a stream of tokens, using provided `config`  \n     For example, `Utils.add_tab(Name(\"a\").render(config), config)` -\u003e Iterable of [' ', 'a']\n\n-   `separated(separator: Sequence[str], renderables: Sequence[Renderable], config: Config) -\u003e StrGen`  \n     Inserts separator between renderables (and renders them in stream)  \n     For example: `Utils.separated([\",\", \" \"], self.values, config)` - inserts \", \" between elements of `self.values`\n\n-   `separated_str(separator: Sequence[str], strings: Sequence[str], config: Config)`  \n     Same as previous, but for `str` sequences\n\n-   `comma_separated(renderables: Sequence[Renderable], config: Config) -\u003e StrGen`  \n     Alias for `Utils.separated([\",\", \" \"], renderables, config)`\n\n-   `make_compact(generator: StrGen, config: Config) -\u003e StrGen`  \n     Filters all unneccessary whitespace from stream (doesn't respect `config[\"compact\"]`). Config is unused at the moment, but provided for compatibility with future updates\n\n-   `wrap(parens: Sequence[str], generator: StrGen) -\u003e StrGen`  \n     Wraps a token stream with strings from `parens` array (should have 2 elements).  \n     In other words, inserts `parens[0]` at the start of the stream, and `parens[1]` at the end\n\n## wordstreamer compatibility\n\ngekkota contains experimental [wordstreamer](https://github.com/evtn/wordstreamer) compatibility layer to use gekkota objects in wordstreamer and vice versa.\n\n### Use gekkota.Renderable where wordstreamer.Renderable can be used:\n\n```python\nfrom gekkota import WSRenderable, Literal\n\nws_compatible = WSRenderable(Literal(6)) # this is a wordstreamer.Renderable now\n```\n\n### Use wordstreamer.Renderable where gekkota.Renderable can be used:\n\n```python\nfrom gekkota import WStoG, Literal\n\nfrom wordstreamer.startkit import Stringify\n\n# create a class to define the type of renderable\nclass WSLiteral(WStoG, Literal):\n    pass\n\nsome_number = Stringify(6)\n\nprint(WSLiteral(some_number) + Literal(8)) # \"6 + 8\"\n```\n\n### Insert a wordstreamer.Renderable in a string (gekkota.Literal)\n\n```python\nfrom gekkota import WSString, Literal\n\nfrom wordstreamer.startkit import Stringify\n\nsome_number = Stringify(6)\n\nprint(WSString(some_number)) # '\"\"\"6\"\"\"'\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcourage-tci%2Fgekkota","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcourage-tci%2Fgekkota","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcourage-tci%2Fgekkota/lists"}