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"