Skip to main content

Python syntax generator based on Object-Oriented Programing, type hints, and simplicity.

Project description

CodeMate

Test

Python package, syntax generator, easy to use, OOP API

Goals

Easily generating python code without the need to care for styling and typo's.

Use Cases

The use cases for using this pack may be one of the following:

  • Generate Python clients by protocols:

    • OpenAPI

    • AsyncAPI

    • ProtoBuf

  • Generate adapters between the code to services I/O.

Documentation

Here will be described the usage of the pack features to generate Python code, by a simple and convenient API:

  • Block
  • Function
  • Class
  • Method
  • File

All of the components inherit from the Block component, from that we receive the simplicity of the API.

Block component

The Block component is the base of all of the components, it generates a python syntax in a new context block.

The Block structure (each section is optional):

  1. Docs
  2. Imports
  3. General Syntax

Initialization

In the initiation of the Block, the default indentation may be changed, it will set how many spaces to add before each line multiplied by the indentation.

Each method that inserts/parse lines support indentation as input.

from codemate import Block

block = Block(indentation=4)

To generate the code use the following line:

print(block.syntax())

Adding syntax

Syntax lines are referred as general Python code lines, they are inserted as is, in the given order.

from codemate import Block

block = Block()

# Adding multiple line as block
block.add_syntax_block("""
import math

x = math.sqrt(9)
if x > 3:
""")

# Adding a single line
block.add_syntax_line("print(x)", indent=1)

# Adding a variable
block.add_variable(
    name="y",
    type="float",
    value="x * 3"
)

# Adding multiple lines
block.add_syntax_lines(
    "z = x * y",
    "print(z)"
)

Generating the syntax using print(block.syntax()), we will receive:

import math

x = math.sqrt(9)
if x > 3:
    print(x)
y: float = x * 3
z = x * y
print(z)

Adding imports

Specifying syntax lines as imports line provide syntax typo safety, sorting with isort and inserting after the docs section and before the syntax. Order and content are modified.

from codemate import Block

block = Block()

# Adding a single import
block.add_import("codemate")

# Adding specific import from a module
block.add_specific_import(
    "codemate",
    "Block",
    "Function",
    "Method",
    "ClassMethod",
    "StaticMethod",
    "Class",
    "File",
)

# Adding multiple imports
block.add_imports(
    "typing",
    "codemate",
    "typing"
)

Generating the syntax using print(block.syntax()), we will receive:

import typing

import codemate
from codemate import (Block, Class, ClassMethod, File, Function, Method,
                      StaticMethod)

Adding docs

Specifying syntax lines as doc line inserts it before the imports section and the syntax section.

from codemate import Block

block = Block()

# Adding a doc line
block.add_doc_line("Adding a single documentation line to the block")

# Adding multiple doc lines
block.add_doc_lines(
    "",
    "Adding multiple,",
    "documentation lines,",
    "each one of them is in a new line.",
    "",
)

# Adding multiple imports
block.add_doc_block("""
    Adding a block of documentation,
    for adding paragraphs easily.
""")

Generating the syntax using print(block.syntax()), we will receive:

"""
Adding a single documentation line to the block

Adding multiple,
documentation lines,
each one of them is in a new line.

Adding a block of documentation,
for adding paragraphs easily.
"""

Extension

Adds other Python block syntax to the current Python block syntax. Ignoring the other's docs, copying the imports and adding the syntax.

from codemate import Block

other = Block()

other.add_import("math")

other.add_doc_line("Other doc")

other.add_syntax_block("""
# other syntax
x = math.sqrt(9)
if x > 3:
""")

other.add_syntax_line("print(x)")

block = Block()

block.add_doc_line("Extend example")

block.add_syntax_line("pass")

block.extend(other)

Generating the syntax using print(block.syntax()), we will receive:

"""
Extend example
"""

import math

pass
# other syntax
x = math.sqrt(9)
if x > 3:
print(x)

Insertion

Inserts as is other Python block syntax to the current Python block syntax. Inserting the docs and syntax as is and copying the imports.

from codemate import Block

other = Block()

other.add_import("math")

other.add_doc_line("Other doc")

other.add_syntax_block("""
# other syntax
x = math.sqrt(9)
if x > 3:
""")

other.add_syntax_line("print(x)")

block = Block()

block.add_doc_line("Insert example")

block.add_syntax_line("pass")

block.insert(other)

Generating the syntax using print(block.syntax()), we will receive:

"""
Insert example
"""

import math

pass
"""
Other doc
"""

# other syntax
x = math.sqrt(9)
if x > 3:
print(x)

Using Black

Will use the black linter to format the Python syntax.

** Note it modifies the content.

from codemate import Block

block = Block()

# complex syntax
block.add_syntax_block("""
    data = [{'userId':'3998e9a3dsdsa', 'related': {'tmp':'example'},
            'emails':['gopo@fake.com', 'tmp@tmp.io', 'ab@ab.io']}]
    print(data)
""")

Generating the syntax using print(block.use_black()), we will receive:

data = [
    {
        "userId": "3998e9a3dsdsa",
        "related": {"tmp": "example"},
        "emails": ["gopo@fake.com", "tmp@tmp.io", "ab@ab.io"],
    }
]
print(data)

Validation

Checks if the generated syntax structure is valid.

from codemate import Block

block = Block()

# complex syntax
block.add_syntax_block("""
    data = ["structure error here -> missing ']'"
    print(data)
""")

# Will raise an exception, the reason is that ']' is missing in the 'data' variable set.
block.validate()

Function component

The Function generates a python function syntax, moreover this is the base of the methods objects.

Initialization

from codemate import Function

# Simple function
simple_func = Function(name="simple")
simple_func.add_syntax_line("pass")

# Function with arguments
with_arguments_func = Function(name="with_arguments", arguments=("foo:int", "bar"))
with_arguments_func.add_syntax_line("pass")

# Async function
async_func = Function(name="async", arguments=("foo:int", "bar"))
async_func.add_syntax_line("pass")

# Function with return value type hint
with_rv_func = Function(name="with_rv", return_value="str")
with_rv_func.add_syntax_line("pass")

Generating the syntax using print({function instance}.syntax()), we will receive:

def simple():
    pass

# --------------------------------------------
def with_arguments(foo:int, bar):
    pass

# --------------------------------------------
def async(foo:int, bar):
    pass

# --------------------------------------------
def with_rv() -> str:
    pass

Decorators

from codemate import Function, Block

block = Block()
block.add_specific_import("functools", "lru_cache")

function = Function(name="foo")
function.add_syntax_line("pass")
function.add_decorator("lru_cache()")

block.insert(function)

Generating the syntax using print(block.syntax()), we will receive:

from functools import lru_cache

@lru_cache()
def foo():
    pass

Class component

The Class generates a python class syntax.

Initialization

from codemate import Class

# Simple class
simple_class = Class(name="Simple")
simple_class.add_syntax_line("pass")

# Class with metaclass
with_meta_class = Class(name="WithMeta", metaclass="Simple")
with_meta_class.add_syntax_line("pass")

# Class with inheritance
with_inheritance_class = Class(name="WithInheritance", inherit=("str", "WithMeta"))
with_inheritance_class.add_syntax_line("pass")

Generating the syntax using print({class instance}.syntax()), we will receive:

class Simple:
    pass

# --------------------------------------------
class WithMeta(metaclass=Simple):
    pass

# --------------------------------------------
class WithInheritance(str, WithMeta,):
    pass

Decorators

from codemate import Class, Function, Block

block = Block()

decorator = Function(name="class_decorator", arguments=("cls",))
decorator.add_syntax_line("pass")
block.insert(decorator)

class_ = Class(name="Bar")
class_.add_syntax_line("pass")
class_.add_decorator("class_decorator")
block.insert(class_)

Generating the syntax using print(block.syntax()), we will receive:

def class_decorator(cls):
    pass

@class_decorator
class Bar:
    pass

Method component

The Method, ClassMethod, and StaticMethod components inherit from Function and already contain the relevant adaptation.

File component

File component is a Block component with relevant capabilities to make Python files generation simple.

Initialization

from codemate import File

# A file with default header
default = File()

# A file with customized header
customized = File(header="-------- Customized Header --------")

Generating the syntax using print({file instance}.syntax()), we will receive:

"""
--------------------------------- Warning generated file ---------------------------------
Generated at: 2021-06-22T15:30:42.222423
------------------------------------------------------------------------------------------
"""

# --------------------------------------------
"""
-------- Customized Header --------
"""

Saving the file

We can save the generated file and specific whether to use black linter on the content or not.

from codemate import Class, Function, Block

from codemate import File

file = File()

# Without black linter
file.save(path="without_black.py")

# With black linter
file.save(path="with_black.py", use_black=True)

Use case example

An example of how to use the components in this Python package to generate a client by API.

from typing import Tuple

from codemate import Class, ClassMethod, File, Function, Method, StaticMethod

DECORATOR_NAME = "timer"

API_STRUCTURE = [
    {"operation_name": "get_x", "return_value": "List[int]"},
    {"operation_name": "get_y", "return_value": "str"},
    {"operation_name": "post_x", "return_value": "bool"},
    {"operation_name": "post_y", "return_value": "bool"},
]


def _set_file_docs(file: File) -> None:
    file.add_doc_lines(
        " Example ".center(90, "-"),
        "This is an example of how to use this Python package to generate easily and safely",
        "Python syntax.",
    )
    file.add_doc_line("")
    file.add_doc_block(
        """
        The use cases for using this pack may be one of the following:
        * Generate Python clients by protocols:
            * OpenAPI
            * AsyncAPI
            * ProtoBuf
        * Generate adapters between the code to systems I/O.

        Easily generating python code without the need to care for styling and indentation.
    """.strip(
            "\n"
        )
    )
    file.add_doc_line("")
    file.add_doc_line("".center(90, "-"))


def _create_file_base() -> File:
    file = File()
    _set_file_docs(file)
    file.add_specific_import("logging", "getLogger")
    file.add_specific_import("logging", "INFO, StreamHandler")
    file.add_specific_import("logging", "DEBUG", "Logger")
    file.add_variable("LOGGER", type="Logger", value="getLogger(__name__)")
    file.add_syntax_block(
        """
        LOGGER.setLevel(DEBUG)
        _channel = StreamHandler()
        _channel.setLevel(INFO)
        LOGGER.addHandler(_channel)
        LOGGER.debug("✨So far so good✨")
    """.strip(
            "\n"
        )
    )
    return file


def _create_inner_function() -> Function:
    function = Function(name="decorator", arguments=("*args", "**kwargs"))
    function.add_import("time")
    function.add_syntax_block(
        """
        start = time.perf_counter()
        return_value = func(*args, **kwargs)
        end = time.perf_counter()
        name = getattr(func, "__name__", "UnKnown")
        LOGGER.info(f"The execution of '{name}' took {end - start:0.4f} seconds")
        return return_value
    """.strip(
            "\n"
        )
    )
    return function


def _create_decorator():
    function = Function(
        name=DECORATOR_NAME, arguments=("func:Callable",), return_value="Callable"
    )
    function.add_doc_block(
        """
        A decorator that times the execution of the wrapped function.

        Args:
            func (Callable): The wrapped function.
    """.strip(
            "\n"
        )
    )
    function.add_specific_import("typing", "Callable")
    inner_function = _create_inner_function()
    function.insert(inner_function)
    function.add_syntax_line("return decorator")
    return function


def _create_init() -> Method:
    method = Method(
        "__init__", arguments=("logger:Logger=LOGGER",), return_value="None"
    )
    method.add_specific_import("logging", "Logger")
    method.add_specific_import("typing", "List")
    method.add_variable("self._logger", value="logger")
    method.add_variable("self._size", value="0")
    return method


def _create_operations() -> Tuple[Method, ...]:
    methods = []
    for _operation in API_STRUCTURE:
        _name, _return_value = _operation.values()
        _method = Method(_name, arguments=("item_id:str",), return_value=_return_value)
        _method.add_decorator("timer")
        _method.add_syntax_line("pass")
        methods.append(_method)
    return tuple(methods)


def _create_calc_method() -> StaticMethod:
    method = StaticMethod(
        "calc",
        arguments=(
            "key:str",
            "value:int",
        ),
        return_value="int",
    )
    method.add_decorator("timer")
    method.add_syntax_line("pass")
    return method


def _create_methods() -> Tuple[Function, ...]:
    methods = []
    methods.append(_create_init())
    methods.extend(_create_operations())
    len_ = Method("__len__", return_value="int")
    len_.add_syntax_line("return self._size")
    methods.append(len_)
    set_base_method = ClassMethod("set_base", return_value="int")
    set_base_method.add_syntax_line("pass")
    methods.append(set_base_method)
    methods.append(_create_calc_method())
    return tuple(methods)


def _create_class() -> Class:
    class_ = Class(name="APIWrapper", inherit=("Sized",))
    class_.add_doc_line("A class that represents a raper for a defined API structure.")
    class_.add_specific_import("collections", "Sized")
    class_.add_specific_import("logging", "Logger")
    for method in _create_methods():
        class_.insert(method)
    return class_


def main():
    """
    An example of how to use the components in this Python package to generate a client
    by API.
    """
    file = _create_file_base()
    file.insert(_create_decorator())
    file.insert(_create_class())
    return file

Generating the syntax using print(block.syntax()), we will receive:

"""
--------------------------------- Warning generated file ---------------------------------
Generated at: 2021-06-22T17:46:00.593553
------------------------------------------------------------------------------------------
---------------------------------------- Example -----------------------------------------
This is an example of how to use this Python package to generate easily and safely
Python syntax.

The use cases for using this pack may be one of the following:
* Generate Python clients by protocols:
    * OpenAPI
    * AsyncAPI
    * ProtoBuf
* Generate adapters between the code to systems I/O.

Easily generating python code without the need to care for styling and indentation.

------------------------------------------------------------------------------------------
"""

import time
from collections import Sized
from logging import DEBUG, INFO, Logger, StreamHandler, getLogger
from typing import Callable, List

LOGGER: Logger = getLogger(__name__)
LOGGER.setLevel(DEBUG)
_channel = StreamHandler()
_channel.setLevel(INFO)
LOGGER.addHandler(_channel)
LOGGER.debug("✨So far so good✨")


def timer(func: Callable) -> Callable:
    """
    A decorator that times the execution of the wrapped function.

    Args:
        func (Callable): The wrapped function.
    """

    def decorator(*args, **kwargs):
        start = time.perf_counter()
        return_value = func(*args, **kwargs)
        end = time.perf_counter()
        name = getattr(func, "__name__", "UnKnown")
        LOGGER.info(f"The execution of '{name}' took {end - start:0.4f} seconds")
        return return_value

    return decorator


class APIWrapper(
    Sized,
):
    """
    A class that represents a raper for a defined API structure.
    """

    def __init__(self, logger: Logger = LOGGER) -> None:
        self._logger = logger
        self._size = 0

    @timer
    def get_x(self, item_id: str) -> List[int]:
        pass

    @timer
    def get_y(self, item_id: str) -> str:
        pass

    @timer
    def post_x(self, item_id: str) -> bool:
        pass

    @timer
    def post_y(self, item_id: str) -> bool:
        pass

    def __len__(self) -> int:
        return self._size

    @classmethod
    def set_base(cls) -> int:
        pass

    @staticmethod
    @timer
    def calc(key: str, value: int) -> int:
        pass

Development

Use the following command to execute the linters and the unit tests.

Make sure that the linters and the unit tests pass and that the unit tests coverage is above 90%:

scripts/test.sh

Use "pytest-cov" to only execute the unit tests:

pytest --cov=codemate --cov=tests

Use the following command to make sure that the linters are passing:

scripts/lint.sh

Use "pre-commit" to run the active and passive linters:

  • pre-commit install - run on every commit.
  • pre-commit run --all-files - run manually on the repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

codemate-0.1.0.tar.gz (24.6 kB view hashes)

Uploaded Source

Built Distribution

codemate-0.1.0-py3-none-any.whl (13.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page