[![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)
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/courage-tci/gekkota/test.yml?branch=pub&label=tests)](https://github.com/courage-tci/gekkota/actions/workflows/test.yml)
[![PyPI](https://img.shields.io/pypi/v/gekkota)](https://pypi.org/project/gekkota/)
![PyPI - Downloads](https://pepy.tech/badge/gekkota)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/gekkota)
[![Coveralls](https://img.shields.io/coverallsCoverage/github/courage-tci/gekkota?label=test%20coverage)](https://coveralls.io/github/courage-tci/gekkota?branch=pub)
![License](https://img.shields.io/github/license/courage-tci/gekkota)
![Badge Count](https://img.shields.io/badge/badges-8-important)
This is a Python code-generation module.
- Generates any Python statement/expression
- Places parens to ensure expression priorities are unchanged
- Places extra newlines before/after class/function definitions to conform with PEP 8
- 100% coverage of type hints, passing strict Pyright checks on every PR
- Meaningful type hierarchy inspired by Python grammar to guarantee syntax validity almost in every case.
- Covered with ~~diamonds~~ tests completely
- Compatible with [wordstreamer](https://github.com/evtn/wordstreamer) renderables via a [wrapper](#wordstreamer-compatibility)
## Installation
Just install `gekkota` package, e.g. with `python -m pip install gekkota` (or any other package manager of your choice)
## Rendering and configuration
To render any `Renderable` into a string, you could use a few approaches:
- `str(renderable)`: renders a Renderable with default configuration (check below)
- `renderable.render_str()`: also default configuration
- `renderable.render_str(config)`: overrides default config options with provided in `config` mapping. Unspecified keys remain at default values
Here is current default config:
```python
default_config: Config = {
"tab_size": 4, # how much chars to use in indentation
"compact": False, # if True, renders without redundant whitespace (e.g "for [i, e] in enumerate(a)" renders as "for[i,e]in enumerate(a)")
"tab_char": " ", # character used for indentation
# "place_semicolons" and "inline_small_stmts" options have some performance impact, since those require checking for newlines in token stream before re-streaming tokens.
# this impact is probably negligible, but be aware of it
"place_semicolons": False, # if True, semicolons are placed after one-line statements
"inline_small_stmts": False, # if True, one-line statements are inlined. Overrides "place_semicolons" if True.
}
```
## Expressions
### Basic expressions
Your starting points would be `to_expression` and `Name`:
```python
from gekkota import Name, to_expression
# Name(name: str, annotation: Optional[Expression] = None)
# to_expression(value: int | float | complex | str | bytes | bool | None)
a = Name("a")
b = Name("b")
six = to_expression(6)
# prints 'a + b * 6'
print(
(a + b * six)
)
```
`Name`, as many other classes in the module, is an `Expression` instance
Expressions support most operations to combine with other expressions.
Exceptions are:
- Attribute reference: for that you should use `Expression.getattr(other: str)`
- Indexing: `Expression.index(index: Expression)`
- Slicing: `Expression.index(index: Union[SliceExpr, Sequence[SliceExpr]])`
- Equality / Inequality: `Expression.eq(right_hand_side)` and `Expression.neq(right_hand_side)` respectively
- `is`: `Expression.is_(right_hand_side)`,
- `is not`: `Expression.is_not(right_hand_side)`
- `in`: `Expression.in_(right_hand_side)`
- `not in`: `Expression.not_in(right_hand_side)`
- `and`: `Expression.and_(right_hand_side)`
- `or`: `Expression.or_(right_hand_side)`
- `await`: `Expression.await_()`
- `:=` assignment: `Expression.assign(value)`
- Ternary operator: `Expression.if_(condition, alternative)`
For example:
```python
from gekkota import Name
a = Name("a")
b = Name("b")
expression = a.await_().in_(b)
print(expression) # await a in b
```
For any other operation on expressions you can just use familiar Python syntax:
```python
from gekkota import Name
a = Name("a")
b = Name("b")
c = Name("c")
print(
(a + b * c / a(b, c)) # 'a + b * c / a(b, c)'
)
```
### Sequences
Most convenient way to create sequence literals is, again, `to_expression`:
```python
from gekkota import to_expression, Name
a = Name("a")
b = Name("b")
print(
to_expression( (a, b, 6) ), # '(a, b, 6)' (notice that to_expression is recursive)
to_expression( (a, ) ), # '(a, )'
to_expression([a, b]), # '[a, b]'
to_expression([]), # '[]'
to_expression({a: b}), # '{a: b}'
to_expression(set()), # 'set()'
to_expression([a, [a, b]]), # '[a, [a, b]]'
)
```
If you want to have more precise control, you can use `TupleExpr`, `ListExpr`, `SetExpr` and `DictExpr` for this.
All have same constructor signature: `(values: Sequence[Expression])` (except `DictExpr`, which has `KeyValue` values)
To create comprehensions:
```python
from gekkota import Name, GeneratorFor, GeneratorIf
from gekkota import (
ListComprehension,
DictComprehension,
KeyValue,
SetComprehension, # same usage as ListComprehension
)
a, b, c, d = map(Name, "abcd")
# ListComprehension(generator_or_expr: GeneratorBase | Expression, parts: Sequence[GeneratorPart] = ())
print(
ListComprehension(
a,
[
# GeneratorFor(target: AssignmentTarget, iterator: Expression, *, is_async: bool = False)
GeneratorFor(b, c),
# GeneratorIf(condition: Expression)
GeneratorIf(b.eq(d))
]
)
) # [a for b in c if b == d]
# DictComprehension(generator_or_expr: GeneratorBase | KeyValue, parts: Sequence[GeneratorPart] = ())
# GeneratorPart == GeneratorFor | GeneratorIf
print(
DictComprehension(KeyValue(a, b), [GeneratorFor(c, d), GeneratorIf(b.eq(d))])
) # {a: b for c in d if b == d}
```
### Keyword call args
Use `CallArg` to provide keyword call args:
```python
from gekkota import Name, to_expression
print_ = Name("print")
# CallArg(name: str, value: Optional[Expression] = None)
print(
print_(
Name("a"),
CallArg("b"),
CallArg("sep", to_expression(", "))
)
) # print(a, b, sep=', ')
```
### Type hints
To annotate a name, just pass an additional parameter to `Name`:
```python
from gekkota import Name
a = Name("a", Name("int"))
print(a) # a: int
```
New in version 0.6: `Name` is now generic, depending on the presence of the annotation.
Some constructors now accept only annotated `Name`, while some others accept only unannotated (depends on syntax features allowed).
This allows to ensure type soundness without breaking changes in API.
## Statements
To render program code (with multiple statements), use `Code`:
```python
from gekkota import Code, Assignment, Name
a = Name("a")
six = Literal(6)
create_variable = Assignment(
[Name("a")],
six + six
)
print_variable = Name("print")(a)
print(
Code([
create_variable,
print_variable,
])
)
# prints:
# a = 6 + 6
# print(a)
```
To render a block of code, use `Block`:
```python
from gekkota import Block, IfStmt, Assignment, Name
a = Name("a")
b = Name("b")
six = Literal(6)
create_variable = Assignment(
[Name("a")],
six + six
)
print_variable = Name("print")(a)
print(
IfStmt(
b,
Block([
create_variable,
print_variable,
])
)
)
# prints:
# if b:
# a = 6 + 6
# print(a)
```
If 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.
Moreover, `Code([])` renders into `""`, while `Block([])` — into `"\n pass"`
### Small statements
Here is an example of a few small statements:
```python
from gekkota import Name, SequenceExpr
from gekkota import (
ReturnStmt,
DelStmt,
AssertStmt,
BreakStmt,
ContinueStmt,
YieldStmt,
YieldFromStmt,
NonLocalStmt,
GlobalStmt,
PassStmt,
RaiseStmt,
AsyncStmt
)
a, b, c = map(Name, "abc")
print(ReturnStmt(a)) # 'return a'
print(YieldStmt(a)) # 'yield a'
print(YieldFromStmt(b)) # 'yield from b'
print(DelStmt(a, b)) # 'del a, b'
print(AssertStmt(a)) # 'assert a'
print(BreakStmt()) # 'break'
print(ContinueStmt()) # 'continue'
print(GlobalStmt(a, b)) # 'global a, b'
print(NonLocalStmt(a, b)) # 'nonlocal a, b'
print(PassStmt()) # 'pass'
print(RaiseStmt()) # 'raise'
print(RaiseStmt(a)) # 'raise a'
print(RaiseStmt(a, b)) # 'raise a from b'
```
### Type statement
Gekkota 0.6 supports [PEP 695](https://docs.python.org/3.12/whatsnew/3.12.html#whatsnew312-pep695) which adds new syntax, including `type` statement:
```python
type Point = tuple[float, float]
```
To generate such statement in gekkota, use `gekkota.annotations.TypeStmt`:
```python
from gekkota import TypeStmt
# type Point = tuple[float, float]
print(
TypeStmt(
"Point",
[],
TupleExpr([Name("float"), Name("float")])
)
)
```
The second argument is a sequence of type parameters (for genric types): `Sequence[TypeParam]`
You can use several types as TypeParam:
```python
from gekkota import TypeParam, TypeStmt, Name, TypeVarParam, TypeVarTupleParam, ParamSpecParam
def make_type(type_params: Sequence[TypeParam]):
return TypeStmt(
"CoolAlias",
type_params,
Name("int")
)
print(make_type([])) # type CoolAlias = int
# (both) type CoolAlias[T] = int
print(make_type([Name("T")]))
print(
make_type([TypeVarParam(Name("T"))])
)
# (both) type CoolAlias[T: float] = int
print(make_type([Name("T", Name("float"))]))
print(
make_type(
[
TypeVarParam(
name=Name("T"),
value=Name("float")
)
]
)
)
# type CoolAlias[*T] = int
print(
make_type(
[
TypeVarTupleParam(
Name("T"),
)
]
)
)
# type CoolAlias[**T] = int
print(
make_type(
[
ParamSpecParam(
Name("T"),
)
]
)
)
```
#### Generic Functions and Classes
`FuncDef` and `ClassDef` now accept an optional keyword argument `type_params` accepting `Sequence[TypeParam]`:
```python
from gekkota import FuncDef, Name, PassStmt
typeparam = Name("T")
newfunc = FuncDef(
name="newfunc",
args=[Name("x", typeparam)],
body=PassStmt(),
rtype=typeparam,
type_params=[typeparam],
)
print(newfunc)
"""
def newfunc[T](x: T) -> T: pass
"""
```
### Assignment
For common assigment use `Assignment`:
```python
from gekkota import Assignment, Name
a, b, c = map(Name, "abc")
# Assignment(targets: Sequence[AssignmentTarget] | AnnotatedTarget, value: Expression)
print(
Assignment([a], b), # a = b
Assignment([a.index(b)], c) # a[b] = c
Assignment([a, b], c), # a = b = c
)
```
To annotate assignment (or just annotate a variable), use `AnnotatedTarget`:
```python
from gekkota import Assignment, AnnotatedTarget, Name
a, b, c = map(Name, "abc")
D = Name("D")
# AnnotatedTarget(target: AssignmentTarget, annotation: Expression)
print(
Assignment(AnnotatedTarget(a, D), b), # a: D = b
Assignment(AnnotatedTarget(a.index(b), D), c) # a[b]: D = c
Assignment([a, b], c), # a = b = c
)
```
For augmented assignment (e.g. `+=`) use `AugmentedAssignment`:
```python
from gekkota import Assignment, Name
a, b, c = map(Name, "abc")
# AugmentedAssignment(target: AugAssignmentTarget, op: str, expression: Expression)
print(
AugmentedAssignment(a, "+=", b), # a += b
AugmentedAssignment(a.index(b), "*=", c) # a *= c
)
```
### Control flow
For control flow you can use `IfStmt`, `ElifStmt` and `ElseStmt`:
```python
from gekkota import Name, IfStmt, ElifStmt, ElseStmt, Code
a, b, c = map(Name, "abc")
# IfStmt(condition: Expression, body: Statement)
# ElifStmt(condition: Expression, body: Statement)
# ElseStmt(body: Statement)
code = Code([
IfStmt(a, b),
ElifStmt(b, a),
ElseStmt(c)
])
print(code)
"""
if a: b
elif b: a
else: c
"""
```
### Loops
Use `ForStmt` and `WhileStmt` for loops:
```python
from gekkota import ForStmt, WhileStmt, Name
a, b, c = map(Name, "abc")
# ForStmt(target: Expression, iterator: Expression, body: Statement, *, is_async: bool = False)
print(
ForStmt(a, b, c)
) # for a in b: c
# WhileStmt(condition: Expression, body: Statement)
print(
WhileStmt(a, b)
) # while a: b
```
### Functions
To render a function definition, you will need a `FuncDef`:
```python
from gekkota import Name, FuncDef
a, b, c = map(Name, "abc")
# FuncDef(
# name: str,
# args: Sequence[FuncArg],
# body: Statement,
# *,
# rtype: Optional[Expression] = None,
# is_async: bool = False,
# type_params: Sequence[TypeParam] = (),
# )
print(
FuncDef(
"cool_func",
[a],
b,
rtype=c,
)
) # def cool_func(a) -> c: b
```
_New in 0.6:_ Now FuncDef accepts a `type_params` argument (check [Generic Functions and Classes](#generic-functions-and-classes))
To provide a default value and/or annotations to arguments, use `FuncArg`:
```python
from gekkota import Name, FuncDef, FuncArg, to_expression
a, b, c = map(Name, "abc")
# FuncDef(name: str, args: Sequence[FuncArg], body: Statement, *, rtype: Optional[Expression] = None, is_async: bool = False)
# FuncArg(
# name: str,
# annotation: Optional[Expression] = None,
# default_value: Optional[Expression] = None,
# *,
# late_bound_default: bool = False,
# )
print(
FuncDef(
"cool_func",
[
FuncArg(
"a",
Name("int"),
to_expression(0)
)
],
b,
rtype=c,
)
) # def cool_func(a: int = 0) -> c: b
```
_New in version 0.6:_ support for [PEP 671 (Draft) – Syntax for late-bound function argument defaults](https://peps.python.org/pep-0671/)
Other argument types are:
- `StarArg(value: T = None)`: generates `*value`, `*` by default
- `DoubleStarArg(value)`: same as `StarArg`, but with `**`
- `Slash()` is `/` (a mark of positional-only arguments in Python 3.8+)
Lambda functions are generated using `LambDef`:
```python
from gekkota import Name, LambDef
a, b, c = map(Name, "abc")
# LambDef(args: Sequence[FuncArg], body: Expression)
print(
LambDef(
[a],
b,
)
) # lambda a: b
```
To decorate a function/class, use `Decorated`:
```python
from gekkota import Name, FuncDef, Decorated
decorator = Name("decorator")
a, b, c = map(Name, "abc")
# Decorated(decorator: Expression, statement: ClassDef | FuncDef)
# FuncDef(name: str, args: Sequence[FuncArg], body: Statement, *, rtype: Optional[Expression] = None, is_async: bool = False)
print(
Decorated(
decorator,
FuncDef(
"cool_func",
[a],
b,
rtype=c,
)
)
)
# @decorator
# def cool_func(a) -> c: b
```
### Classes
To define a class, use `ClassDef`:
```python
from gekkota import Name, ClassDef
a, b, c = map(Name, "abc")
# ClassDef(
# name: str,
# args: Sequence[CallArg | Expression],
# body: Statement,
# *,
# type_params: Sequence[TypeParam] = (),
# )
print(
ClassDef("MyClass1", [], a)
) # class MyClass1: a
print(
ClassDef("MyClass2", [b], c)
) # class MyClass2(b): c
```
_New in 0.6:_ Now ClassDef accepts a `type_params` argument (check [Generic Functions and Classes](#generic-functions-and-classes))
### Imports
To render imports, use `ImportStmt` and `FromImportStmt`:
```python
from gekkota import Name, StarArg, ImportDots, ImportSource, ImportStmt, FromImportStmt, ImportAlias
# ImportStmt(names: Sequence[ImportAlias | Name | StarArg[None]])
print(
ImportStmt([Name("a")])
) # import a
print(
ImportStmt([Name("a"), Name("b")])
) # import a, b
# FromImportStmt(source: ImportSource | Name, names: Sequence[ImportAlias | Name | StarArg[None]])
# ImportAlias(name: Name, alias: Name | None = None)
print(
FromImportStmt(
Name("math"),
[
Name("cos"),
ImportAlias(Name("sin"), Name("tan")) # we do a little trolling
]
)
) # from math import cos, sin as tan
print(
FromImportStmt(
Name("gekkota"),
[StarArg()]
)
) # from gekkota import *
# ImportDots(length: int = 1)
print(
FromImportStmt(
ImportDots(),
[StarArg()]
)
) # from . import *
# ImportSource(parts: Sequence[str])
print(
FromImportStmt(
ImportSource(["", "values"]),
[Name("Name")]
)
) # from .values import Name
```
### Exceptions
```python
from gekkota import Name, TryStmt, ExceptStmt, FinallyStmt, Block
a, b, e = map(Name, "abe")
# TryStmt(body: Statement)
print(
TryStmt(a)
) # try: a
# ExceptStmt(exceptions: Sequence[Expression] | None, alias: Name | None, body: Statement)
print(
ExceptStmt(None, None, a)
) # except: a
print(
ExceptStmt(None, None, Block([]))
)
# except:
# pass
print(
ExceptStmt([a], None, b)
) # except a: b
print(
ExceptStmt([a], e, b)
) # except a as e: b
# FinallyStmt(body: Statement)
print(
FinallyStmt(a)
) # finally: a
```
### Context Managers
```python
from gekkota import Name, WithStmt, WithTarget
a, b, e = map(Name, "abe")
# WithStmt(targets: Sequence[WithTarget | Expression], body: Statement, *, is_async: bool = False,)
print(
WithStmt([a], b)
) # with a: b
# WithTarget(expression: Expression, alias: str | None = None)
print(
WithStmt([WithTarget(a, "aaaa")], b)
) # with a as aaaa: b
print(
WithStmt([a], b, is_async=True)
) # async with a: b
```
### Pattern matching
This section is currently unfinished, check [pattern_matching.py](https://github.com/courage-tci/gekkota/blob/pub/gekkota/pattern_matching.py)
## Custom rendering
If your custom element can be meaningfully represented as a combination of existing elements, you can use a function instead of a class:
```python
from gekkota import Expression
def Square(e: Expression) -> Expression:
return e * e
```
This is a pretty obvious approach, but often it works best.
---
While being aimed at Python code generation, `gekkota` is pretty extensible, and can be used to render different things.
You can build custom renderables, statements, expressions, and so on.
The simplest example of a custom renderable would be:
```python
from gekkota import Renderable, StrGen, Config
class RenderString(Renderable):
"""It renders whatever is passed to it"""
def __init__(self, value: str):
self.value = value
def render(self, config: Config) -> StrGen:
yield self.value
```
Let'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).
Suppose your custom literal would be in form of `<|value1, value2, ...|>`.
You can extend `SequenceExpr` for that:
```python
from gekkota import SequenceExpr, Name
class MyCoolSequence(SequenceExpr):
parens = "<|", "|>"
seq = MyCoolSequence([Name("a"), Name("b")])
print(seq) # <|a,b|>
```
That's it, you're ready to render this literal (which, again, isn't valid in Python but anyway).
Or you could go further and write rendering by yourself (it's easier than it sounds):
```python
from gekkota import Expression, Config
class MyCoolSequence(Expression):
def __init__(self, values: Sequence[Expression]):
self.values = values
# could be rewritten to be simpler, check `Useful utils` section below
def render(self, config: Config) -> StrGen:
yield "<|"
for i, item in enumerate(self.values):
yield from item.render(config)
if i + 1 < len(self.values): # no comma after last element
yield ","
yield " "
yield "|>"
```
It's fairly easy, just render every part in the right order:
- To render a string, use `yield string`
- To render a `Renderable`, use `yield from renderable.render(config)`
### Choosing a right base class
To choose a right base class, think in what context you want to use your renderable.
If there is a similar context in Python (e.g. your renderable is a block statement, like `for` or `if`), extend that class.
After choosing a right base class, check if it has a predefined render, maybe you won't need to write everything by yourself.
For example, with `BlockStmt` you need to provide `render_head` instead:
```python
# that's the actual source from module, not an example
class BlockStmt(Statement):
body: Statement
def render_head(self, config: Config) -> StrGen:
return NotImplemented
def render(self, config: Config) -> StrGen:
yield from self.render_head(config)
yield ":"
yield " "
yield from self.body.render(config)
[...]
class ElseStmt(BlockStmt):
def __init__(self, body: Statement):
self.body = body
def render_head(self, config: config) -> StrGen:
yield "else"
```
### Useful utils
`gekkota.utils` provides `Utils` class which is useful for custom renderables. For example, custom `MyCoolSequence` could be implemented as:
```python
from gekkota import Expression, Utils
class MyCoolSequence(Expression):
def __init__(self, values: Sequence[Expression]):
self.values = values
def render(self, config: config) -> StrGen:
yield from Utils.wrap(
["<|", "|>"],
Utils.comma_separated(self.values, config)
)
```
Methods provided in `Utils`:
- `add_tab(generator: StrGen, config: Config) -> StrGen`
Adds indentation to a stream of tokens, using provided `config`
For example, `Utils.add_tab(Name("a").render(config), config)` -> Iterable of [' ', 'a']
- `separated(separator: Sequence[str], renderables: Sequence[Renderable], config: Config) -> StrGen`
Inserts separator between renderables (and renders them in stream)
For example: `Utils.separated([",", " "], self.values, config)` - inserts ", " between elements of `self.values`
- `separated_str(separator: Sequence[str], strings: Sequence[str], config: Config)`
Same as previous, but for `str` sequences
- `comma_separated(renderables: Sequence[Renderable], config: Config) -> StrGen`
Alias for `Utils.separated([",", " "], renderables, config)`
- `make_compact(generator: StrGen, config: Config) -> StrGen`
Filters all unneccessary whitespace from stream (doesn't respect `config["compact"]`). Config is unused at the moment, but provided for compatibility with future updates
- `wrap(parens: Sequence[str], generator: StrGen) -> StrGen`
Wraps a token stream with strings from `parens` array (should have 2 elements).
In other words, inserts `parens[0]` at the start of the stream, and `parens[1]` at the end
## wordstreamer compatibility
gekkota contains experimental [wordstreamer](https://github.com/evtn/wordstreamer) compatibility layer to use gekkota objects in wordstreamer and vice versa.
### Use gekkota.Renderable where wordstreamer.Renderable can be used:
```python
from gekkota import WSRenderable, Literal
ws_compatible = WSRenderable(Literal(6)) # this is a wordstreamer.Renderable now
```
### Use wordstreamer.Renderable where gekkota.Renderable can be used:
```python
from gekkota import WStoG, Literal
from wordstreamer.startkit import Stringify
# create a class to define the type of renderable
class WSLiteral(WStoG, Literal):
pass
some_number = Stringify(6)
print(WSLiteral(some_number) + Literal(8)) # "6 + 8"
```
### Insert a wordstreamer.Renderable in a string (gekkota.Literal)
```python
from gekkota import WSString, Literal
from wordstreamer.startkit import Stringify
some_number = Stringify(6)
print(WSString(some_number)) # '"""6"""'
```
Raw data
{
"_id": null,
"home_page": "https://github.com/courage-tci/gekkota",
"name": "gekkota",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": "codegen",
"author": "Dmitry Gritsenko",
"author_email": "k01419q45@ya.ru",
"download_url": "https://files.pythonhosted.org/packages/3e/43/fd935738bccb33fb824ad9882267682530e3f135f1d2d5d871339315074e/gekkota-0.7.0.tar.gz",
"platform": null,
"description": "[![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&label=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([])` \u2014 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) -> 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) -> 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) -> c: b\n\n```\n\n_New in version 0.6:_ support for [PEP 671 (Draft) \u2013 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) -> 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) -> 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) -> 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 `<|value1, value2, ...|>`.\n\nYou can extend `SequenceExpr` for that:\n\n```python\nfrom gekkota import SequenceExpr, Name\n\nclass MyCoolSequence(SequenceExpr):\n parens = \"<|\", \"|>\"\n\n\nseq = MyCoolSequence([Name(\"a\"), Name(\"b\")])\n\nprint(seq) # <|a,b|>\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) -> StrGen:\n yield \"<|\"\n\n for i, item in enumerate(self.values):\n yield from item.render(config)\n\n if i + 1 < len(self.values): # no comma after last element\n yield \",\"\n yield \" \"\n\n yield \"|>\"\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) -> StrGen:\n return NotImplemented\n\n def render(self, config: Config) -> 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) -> 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) -> StrGen:\n yield from Utils.wrap(\n [\"<|\", \"|>\"],\n Utils.comma_separated(self.values, config)\n )\n```\n\nMethods provided in `Utils`:\n\n- `add_tab(generator: StrGen, config: Config) -> StrGen` \n Adds indentation to a stream of tokens, using provided `config` \n For example, `Utils.add_tab(Name(\"a\").render(config), config)` -> Iterable of [' ', 'a']\n\n- `separated(separator: Sequence[str], renderables: Sequence[Renderable], config: Config) -> 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) -> StrGen` \n Alias for `Utils.separated([\",\", \" \"], renderables, config)`\n\n- `make_compact(generator: StrGen, config: Config) -> 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) -> 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",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python code-generation for Python",
"version": "0.7.0",
"project_urls": {
"Homepage": "https://github.com/courage-tci/gekkota",
"Repository": "https://github.com/courage-tci/gekkota"
},
"split_keywords": [
"codegen"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "9e1b7a3d93df7d632f631013a7c9ad5faa86ddab320805d09a251a2554cdb9a7",
"md5": "b479524d900b8b4b2c3a63493813e642",
"sha256": "fef8b487e5f9411f4551726a076b8a2b438c66a4fc4d6905e49c144ff96dd72e"
},
"downloads": -1,
"filename": "gekkota-0.7.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b479524d900b8b4b2c3a63493813e642",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 27945,
"upload_time": "2024-05-02T08:58:19",
"upload_time_iso_8601": "2024-05-02T08:58:19.043152Z",
"url": "https://files.pythonhosted.org/packages/9e/1b/7a3d93df7d632f631013a7c9ad5faa86ddab320805d09a251a2554cdb9a7/gekkota-0.7.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "3e43fd935738bccb33fb824ad9882267682530e3f135f1d2d5d871339315074e",
"md5": "a7b7e7b9912c1952f9f8219892fe95f2",
"sha256": "146ed86f7c593c56e1a92c28faf7dac04d1afaddcdd0a45f031f05f5e582eab7"
},
"downloads": -1,
"filename": "gekkota-0.7.0.tar.gz",
"has_sig": false,
"md5_digest": "a7b7e7b9912c1952f9f8219892fe95f2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 26582,
"upload_time": "2024-05-02T08:58:22",
"upload_time_iso_8601": "2024-05-02T08:58:22.405117Z",
"url": "https://files.pythonhosted.org/packages/3e/43/fd935738bccb33fb824ad9882267682530e3f135f1d2d5d871339315074e/gekkota-0.7.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-05-02 08:58:22",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "courage-tci",
"github_project": "gekkota",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"lcname": "gekkota"
}