Source code for scm.pisa.block_subclass_string

from __future__ import annotations

from copy import copy
from dataclasses import dataclass
from typing import Literal, Sequence

from scm.pisa.utils import to_valid_identifier

INDENT = " " * 4


[docs]@dataclass class BlockSubclassAttribute: """Simple dataclass describing an attribute to generate valid python code for. :attr:`name` is the under which to store the attribute, :attr:`repr_string` is a string that when interpreted as python code will result in the proper object (`Key` or `Block` subclass) and :attr:`type_hint` is the string to be inserted as type hint. """ name: str repr_string: str type_hint: str | None = None
[docs]class BlockSubclassString:
[docs] def __init__( self, name: str, parent: Literal["DriverBlock", "EngineBlock", "FreeBlock", "InputBlock", "FixedBlock"], attributes: Sequence[BlockSubclassAttribute], doc_str: str | None = "", ): """Class that is able to produce valid python code that defines a subclass of :class:`scm.pisa.block.Block` :param name: Name of the class :type name: str :param parent: Parent class to derive from. One of the ``Block`` classes :type parent: Literal['DriverBlock', 'EngineBlock', 'FreeBlock', 'InputBlock', 'FixedBlock'] :param attributes: List of attributes (that represent keys or blocks) to insert in :meth:`__post_init__` method :type attributes: Sequence[ClassStringAttribute] :param doc_str: Class docstring, usually set to `_comment` metadata of the block, defaults to "" :type doc_str: str | None, optional """ self._nested_class_strings: list[BlockSubclassString] = [] self._lines: list[str] = [] self._lines.append(f"class {name}({parent}):") self.doc_str = doc_str if self.doc_str: self.doc_str = self.doc_str.replace("'", '"') self._lines.append(f"{INDENT}'''{self.doc_str}'''") # TODO define method as argument self._lines.append(f"{INDENT}def __post_init__(self):") if len(attributes): for attr in attributes: left_side = f"{INDENT * 2}self.{to_valid_identifier(attr.name)}" if attr.type_hint: left_side += f": {attr.type_hint}" self._lines.append(f"{left_side} = {attr.repr_string}") else: self._lines.append(f"{INDENT * 2}pass")
[docs] def insert(self, cls_str: BlockSubclassString) -> None: """Insert a class definitions into this class. Nested classes are used to prevent name clashes between different blocks sharing the same name in the AMS universe. :param cls_str: Class string to insert. :type cls_str: BlockSubclassString """ self._nested_class_strings.append(cls_str)
@property def lines(self) -> list[str]: """All the lines defining this class. Property recursively inserted nested class definitions with the proper indentation. :return: List of lines of python code. :rtype: list[str] """ if len(self._nested_class_strings): # line containing 'class' statement' idx = 2 if self.doc_str else 1 lines = self._lines[:idx] after = self._lines[idx:] for nested_cls in self._nested_class_strings: lines += [INDENT + line for line in nested_cls.lines] lines += after else: lines = self._lines return copy(lines)
[docs] def __str__(self) -> str: """Convert this class to a string of valid python code defining a subclass of :class:`scm.pisa.block.Block` with the given attributes. Import statements need to added to this string separately :return: String of valid python code :rtype: str """ return "\n".join(self.lines) + "\n"