Source code for bo4e.utils

"""
utils necessary for reflection/inspection and documentation runs
"""

import inspect
import re
from typing import TypeVar

from pydantic import BaseModel

from ..version import __gh_version__

T = TypeVar("T", bound=BaseModel)
REGEX_CLASS_START = re.compile(r"class \w+\(.*\):(?:\s*#[^\n]*)?\n\s+\"{3}(?:\"{0,2}[^\"])*\"{3}\n")
# https://regex101.com/r/dQPi06/1


[docs] def add_comments_to_description(cls: type[T]) -> type[T]: """ Add comments of fields to the description of the fields. This enables the generation of JSON-Schemas with proper descriptions. """ code = inspect.getsource(cls) split_result = REGEX_CLASS_START.split(code) assert len(split_result) == 2, "The class source code structure is not as expected." fields_code = split_result[1] regex_comment_above = r"#: ?(?P<comment>[^\n]*)\n\s+{field_name}:" # https://regex101.com/r/aJrvol/1 regex_comment_inline = r"{field_name}:[^:]*#: ?(?P<comment>[^\n]*)\n" # https://regex101.com/r/0PaUmw/1 for field_name, field_info in cls.model_fields.items(): if field_info.description is not None: field_info.description = field_info.description.strip() continue # search for (single line) comments above the field match = re.search(regex_comment_above.format(field_name=field_name), fields_code) if match is not None: field_info.description = match.group("comment").strip() continue # search for (single line) comments inline with the field match = re.search(regex_comment_inline.format(field_name=field_name), fields_code) if match is not None: field_info.description = match.group("comment").strip() continue # (multi line) comments below the field aka docstrings are now handled by pydantic # Try to find the comment in the bases description = None for base in cls.__bases__: if base == BaseModel: continue if not issubclass(base, BaseModel): continue if field_name in base.model_fields: if base.model_fields[field_name].description is None: add_comments_to_description(base) description = base.model_fields[field_name].description break if description is not None: field_info.description = description continue # cls.model_rebuild(force=True) # Unnecessary here since the models will be rebuilt in __init__.py. # Keeping this here as comment though. return cls
[docs] def postprocess_docstring(cls: type[T]) -> type[T]: """ Postprocess the docstring to inject the __gh_version__ for proper linking of the JSON-schemas. Note that doc-strings in Python have to be string literals, so we cannot use f-strings. Additionally, we add the comments to the description of the fields to enable the generation of JSON-Schemas with proper descriptions. """ if cls.__doc__ is not None: cls.__doc__ = cls.__doc__.format(__gh_version__=__gh_version__) add_comments_to_description(cls) return cls