You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
12 KiB
Python
416 lines
12 KiB
Python
"""Helper functionality that is used in other modules of this package."""
|
|
from __future__ import annotations
|
|
|
|
from copy import copy
|
|
from dataclasses import dataclass
|
|
from dataclasses import field
|
|
from typing import Any
|
|
|
|
BUILDER_NAME = "Builder"
|
|
READER_NAME = "Reader"
|
|
|
|
|
|
def new_builder(type_name: str) -> str:
|
|
"""Converts a type name to its builder variant.
|
|
|
|
E.g. `MyClass` becomes `MyClassBuilder`.
|
|
|
|
Args:
|
|
type_name (str): The original type name.
|
|
|
|
Returns:
|
|
str: The builder variant.
|
|
"""
|
|
return f"{type_name}{BUILDER_NAME}"
|
|
|
|
|
|
def new_reader(type_name: str) -> str:
|
|
"""Converts a type name to its reader variant.
|
|
|
|
E.g. `MyClass` becomes `MyClassTeader`.
|
|
|
|
Args:
|
|
type_name (str): The original type name.
|
|
|
|
Returns:
|
|
str: The reader variant.
|
|
"""
|
|
return f"{type_name}{READER_NAME}"
|
|
|
|
|
|
@dataclass
|
|
class TypeHint:
|
|
"""A class that captures a type hint."""
|
|
|
|
name: str
|
|
scopes: list[str] = field(default_factory=list)
|
|
affix: str = ""
|
|
primary: bool = False
|
|
|
|
def __str__(self) -> str:
|
|
"""The string representation of the type hint.
|
|
|
|
This is composed of the scopes (if any), the name of the hint, and the affix (if any).
|
|
"""
|
|
if not self.scopes:
|
|
return f"{self.name}{self.affix}"
|
|
|
|
else:
|
|
return f"{'.'.join(self.scopes)}.{self.name}{self.affix}"
|
|
|
|
|
|
@dataclass
|
|
class TypeHintedVariable:
|
|
"""A class that represents a type hinted variable."""
|
|
|
|
name: str
|
|
type_hints: list[TypeHint]
|
|
default: str = ""
|
|
nesting_depth: int = 0
|
|
|
|
def __post_init__(self):
|
|
"""Sanity check for provided type hints."""
|
|
primary_type_hint_count = 0
|
|
|
|
for type_hint in self.type_hints:
|
|
if type_hint.primary:
|
|
primary_type_hint_count += 1
|
|
|
|
if primary_type_hint_count != 1:
|
|
raise ValueError(f"There can only be exactly one primary type hint. Found {primary_type_hint_count}")
|
|
|
|
def __str__(self) -> str:
|
|
"""String representation of this object.
|
|
|
|
Returns:
|
|
str: The string representation.
|
|
"""
|
|
return self.typed_variable_with_full_hints
|
|
|
|
def _nest(self, unnested_type_name: str) -> str:
|
|
if self.nesting_depth > 0:
|
|
return f'{self.nesting_depth * "Sequence["}{unnested_type_name}{self.nesting_depth * "]"}'
|
|
|
|
else:
|
|
return unnested_type_name
|
|
|
|
@property
|
|
def typed_variable_with_full_hints(self) -> str:
|
|
"""Returns the typed variable string, hinted will all available type hints."""
|
|
return self._generate_typed_variable(self.full_type)
|
|
|
|
@property
|
|
def typed_variable_with_primary_hint(self) -> str:
|
|
"""Returns the typed variable string, hinted only with the primary type hint."""
|
|
if self.primary_type_hint is None:
|
|
raise ValueError("No primary type hint available.")
|
|
|
|
return self._generate_typed_variable(str(self.primary_type_hint))
|
|
|
|
@property
|
|
def primary_type_hint(self) -> TypeHint:
|
|
"""Returns the primary type hint."""
|
|
for type_hint in self.type_hints:
|
|
if type_hint.primary:
|
|
return type_hint
|
|
|
|
raise RuntimeError("Primary type hint not found.")
|
|
|
|
def _generate_typed_variable(self, type_name: str) -> str:
|
|
"""Generate the typed variable string for a chosen type name.
|
|
|
|
Args:
|
|
type_name (str): The type name to use.
|
|
|
|
Returns:
|
|
str: The typed variable string.
|
|
"""
|
|
nested_type_name = self._nest(type_name)
|
|
typed_variable = f"{self.name}: {nested_type_name}"
|
|
|
|
if self.default:
|
|
return f"{typed_variable} = {self.default}"
|
|
|
|
else:
|
|
return typed_variable
|
|
|
|
def _get_type_hints_for_affixes(self, affixes: list[str]) -> list[TypeHint]:
|
|
return [self.get_type_hint_for_affix(affix) for affix in affixes]
|
|
|
|
def get_typed_variable_with_affixes(self, affixes: list[str] | None) -> str:
|
|
"""Gets a typed version of this variable with the selected type hint affixes.
|
|
|
|
For example, when selecting the affixes "Reader" and "Builder" for the primary type "Something" and the variable
|
|
named "VariableName", this will return "VariableName: SomethingReader | SomethingBuilder".
|
|
|
|
Args:
|
|
affixes (list[str] | None): The affixes to select for type hints.
|
|
|
|
Returns:
|
|
str: The typed variable in string format.
|
|
"""
|
|
type_hints: list[TypeHint] = []
|
|
|
|
if affixes is not None:
|
|
type_hints_for_affixes = self._get_type_hints_for_affixes(affixes)
|
|
|
|
if type_hints_for_affixes is not None:
|
|
type_hints.extend(type_hints_for_affixes)
|
|
|
|
return self._generate_typed_variable(self._join_type_hints(type_hints))
|
|
|
|
def _join_type_hints(self, type_hints: list[TypeHint]) -> str:
|
|
return " | ".join(str(type_hint) for type_hint in type_hints)
|
|
|
|
@property
|
|
def full_type(self) -> str:
|
|
"""The full type string of the hinted variable."""
|
|
return self._join_type_hints(self.type_hints)
|
|
|
|
def add_type_hint(self, new_type_hint: TypeHint):
|
|
"""Add a type hint to the hinted variable.
|
|
|
|
Args:
|
|
new_type_hint (TypeHint): The type hint to add.
|
|
"""
|
|
for type_hint in self.type_hints:
|
|
if type_hint == new_type_hint:
|
|
raise ValueError("Type hint already exists.")
|
|
|
|
if new_type_hint.primary:
|
|
raise ValueError("There can only be one primary type.")
|
|
|
|
self.type_hints.append(new_type_hint)
|
|
|
|
def add_type_scope(self, scope: str):
|
|
"""Add a scope to the type name.
|
|
|
|
Args:
|
|
scope (str): The scope name to prefix to the type name.
|
|
"""
|
|
for type_hint in self.type_hints:
|
|
type_hint.scopes.append(scope)
|
|
|
|
def get_type_hint_for_affix(self, affix: str) -> TypeHint:
|
|
"""Looks for a type hint that has the provided affix and returns it.
|
|
|
|
Args:
|
|
affix (str | None): The affix to look for.
|
|
|
|
Returns:
|
|
TypeHint: The type hint that was found.
|
|
"""
|
|
for type_hint in self.type_hints:
|
|
if type_hint.affix == affix:
|
|
return type_hint
|
|
|
|
raise KeyError(f"Affix '{affix}' is not present in any recorded type hint.")
|
|
|
|
def has_type_hint_with_affix(self, affix: str) -> bool:
|
|
"""Assess, whether or not the variable has a type hint with the provided affix."""
|
|
try:
|
|
self.get_type_hint_for_affix(affix)
|
|
|
|
except KeyError:
|
|
return False
|
|
|
|
else:
|
|
return True
|
|
|
|
@property
|
|
def has_type_hint_with_builder_affix(self) -> bool:
|
|
"""Whether the variable holds a type hint with a builder affix."""
|
|
return self.has_type_hint_with_affix(BUILDER_NAME)
|
|
|
|
@property
|
|
def has_type_hint_with_reader_affix(self) -> bool:
|
|
"""Whether the variable holds a type hint with a reader affix."""
|
|
return self.has_type_hint_with_affix(READER_NAME)
|
|
|
|
def add_builder_from_primary_type(self):
|
|
"""Add a type hint with builder affix, based on the primary type."""
|
|
self.add_type_hint(TypeHint(self.primary_type_hint.name, copy(self.primary_type_hint.scopes), BUILDER_NAME))
|
|
|
|
def add_reader_from_primary_type(self):
|
|
"""Add a type hint with builder affix, based on the primary type."""
|
|
self.add_type_hint(
|
|
TypeHint(
|
|
self.primary_type_hint.name,
|
|
copy(self.primary_type_hint.scopes),
|
|
READER_NAME,
|
|
)
|
|
)
|
|
|
|
|
|
def replace_capnp_suffix(original: str) -> str:
|
|
"""If found, replaces the .capnp suffix in a string with _capnp.
|
|
|
|
For example, `some_module.capnp` becomes `some_module_capnp`.
|
|
|
|
Args:
|
|
original (str): The string to replace the suffix in.
|
|
|
|
Returns:
|
|
str: The string with the replaced suffix.
|
|
"""
|
|
if original.endswith(".capnp"):
|
|
return original.replace(".capnp", "_capnp")
|
|
|
|
else:
|
|
return original
|
|
|
|
|
|
def join_parameters(parameters: list[TypeHintedVariable] | list[str] | None) -> str:
|
|
"""Joins parameters by means of ', '.
|
|
|
|
Args:
|
|
parameters (list[HintedVariable] | list[str] | None): The parameters to join.
|
|
|
|
Returns:
|
|
str: The joined parameters.
|
|
"""
|
|
if parameters:
|
|
return ", ".join(str(p) for p in parameters if p)
|
|
|
|
else:
|
|
return ""
|
|
|
|
|
|
def new_type_alias(alias: str, type_name: str) -> str:
|
|
"""Generate a string for a type-alias.
|
|
|
|
For example, for a type alias `SomeType` with a type `TheType` this gives `SomeType = TheType`.
|
|
|
|
Args:
|
|
alias (str): The alias name.
|
|
type_name (str): The aliased type.
|
|
|
|
Returns:
|
|
str: The type alias.
|
|
"""
|
|
return f"{alias} = {type_name}"
|
|
|
|
|
|
def new_group(name: str, members: list[str]) -> str:
|
|
"""Create a string for a group name and its members.
|
|
|
|
For example, when the group name is 'Type', and the parameters are 'str', and 'int',
|
|
the output will be 'Type[str, int]'.
|
|
|
|
Args:
|
|
name (str): The name of the group.
|
|
members (list[str]): The members of the group
|
|
|
|
Returns:
|
|
str: The resulting group string.
|
|
"""
|
|
return f"{name}[{join_parameters(members)}]"
|
|
|
|
|
|
def new_type_group(name: str, types: list[str]) -> str:
|
|
"""Create a string for a parameter with types.
|
|
|
|
Uses `new_group` internally.
|
|
|
|
Args:
|
|
name (str): The name of the parameter.
|
|
types (list[str]): The list of types to that this parameter can have.
|
|
|
|
Returns:
|
|
str: The resulting parameter string.
|
|
"""
|
|
return new_group(name, types)
|
|
|
|
|
|
def new_function(
|
|
name: str, parameters: list[TypeHintedVariable] | list[str] | None = None, return_type: str | None = None
|
|
) -> str:
|
|
"""Create a string for a function.
|
|
|
|
Args:
|
|
name (str): The function name.
|
|
parameters (list[HintedVariable] | list[str] | None, optional): The function parameters, if any. Defaults to None.
|
|
return_type (str | None, optional): The function's return type. Defaults to None.
|
|
|
|
Returns:
|
|
str: The function string.
|
|
"""
|
|
if return_type is None:
|
|
return_type = "None"
|
|
|
|
arguments = join_parameters(parameters)
|
|
return f"def {name}({arguments}) -> {return_type}: ..."
|
|
|
|
|
|
def new_decorator(name: str, parameters: list[TypeHintedVariable] | list[str] | None = None) -> str:
|
|
"""Create a new decorator.
|
|
|
|
Args:
|
|
name (str): The name of the decorator.
|
|
parameters (list[HintedVariable] | list[str] | None, optional): The parameters (args, kwargs) of the decorator,
|
|
if any. Defaults to None.
|
|
|
|
Returns:
|
|
str: The decorator string.
|
|
"""
|
|
if parameters:
|
|
return f"@{name}({join_parameters(parameters)})"
|
|
|
|
else:
|
|
return f"@{name}"
|
|
|
|
|
|
def new_constructor(kwargs: list[str] | None = None) -> str:
|
|
"""Creates a new constructor.
|
|
|
|
Args:
|
|
kwargs (list[str] | None, optional): The keyword arguments for the constructor. Defaults to None.
|
|
|
|
Returns:
|
|
str: The constructor string.
|
|
"""
|
|
constructor_args = "self"
|
|
|
|
kwargs_ = ""
|
|
|
|
if kwargs:
|
|
# Prepend '*' for catching any positional arguments, before the keyword arguments.
|
|
kwargs_ = join_parameters(["*"] + [f"{kwarg} = ..." for kwarg in kwargs])
|
|
|
|
return new_function("__init__", parameters=[constructor_args, kwargs_])
|
|
|
|
|
|
def new_class_declaration(name: str, parameters: list[str] | None = None) -> str:
|
|
"""Creates a string for declaring a class.
|
|
|
|
For example, for a name of 'SomeClass' and a list of parameters that is 'str, Type[str, int]', the output
|
|
will be 'SomeClass (str, Type[str, int]):'.
|
|
|
|
If no parameters are provided, the output is just 'SomeClass:'.
|
|
|
|
Args:
|
|
name (str): The class name.
|
|
parameters (list[str] | None, optional):
|
|
A list of parameters that are part of the class declaration. Defaults to None.
|
|
|
|
Returns:
|
|
str: The class declaration.
|
|
"""
|
|
if parameters:
|
|
return f"class {name}({join_parameters(parameters)}):"
|
|
|
|
else:
|
|
return f"class {name}:"
|
|
|
|
|
|
def get_display_name(schema: Any) -> str:
|
|
"""Extract the display name from a schema.
|
|
|
|
Args:
|
|
schema (Any): The schema to get the display name from.
|
|
|
|
Returns:
|
|
str: The display name of the schema.
|
|
"""
|
|
return schema.node.displayName[schema.node.displayNamePrefixLength :]
|