Skip to content

Code Generator

FHIR resource code generation utilities.

CodeGenerator

Path: fhircraft.fhir.resources.generator.CodeGenerator

CodeGenerator()

Methods:

Name Description
generate_resource_model_code

Generate the source code for resource model(s) based on the input resources.

Source code in fhircraft/fhir/resources/generator.py
def __init__(self):
    # Prepare the templating engine environment
    file_loader = FileSystemLoader(os.path.dirname(os.path.abspath(__file__)))
    env = Environment(loader=file_loader, trim_blocks=True, lstrip_blocks=True)
    env.filters["escapequotes"] = lambda s: s.replace('"', '\\"')
    env.globals.update(ismodel=lambda obj: isinstance(obj, BaseModel))
    self.template = env.get_template("resource_template.py.j2")

generate_resource_model_code

generate_resource_model_code(resources: type[BaseModel] | List[type[BaseModel]], include_validators: bool = True) -> str

Generate the source code for resource model(s) based on the input resources.

Parameters:

Name Type Description Default
resources Union[BaseModel, List[BaseModel]]

The resource(s) to generate the model code for.

required
include_validators bool

Whether to include validators in the generated code (default: True). Recommended to be True for most use cases.

True

Returns:

Name Type Description
str str

The generated source code for the resource model(s).

Source code in fhircraft/fhir/resources/generator.py
def generate_resource_model_code(
    self,
    resources: type[BaseModel] | List[type[BaseModel]],
    include_validators: bool = True,
) -> str:
    """
    Generate the source code for resource model(s) based on the input resources.

    Args:
        resources (Union[BaseModel, List[BaseModel]]): The resource(s) to generate the model code for.
        include_validators (bool): Whether to include validators in the generated code (default: `True`). Recommended to be `True` for most use cases.

    Returns:
        str: The generated source code for the resource model(s).
    """
    # Reset the internal state of the generator
    self._reset_state()
    # Serialize the model information of the input resources
    for resource in ensure_list(resources):
        self._serialize_model(resource)
    # Group imports by common parent modules
    grouped_imports = self._group_imports_by_common_parent()

    # Render the source code using Jinja2
    source_code = self.template.render(
        data=self.data,
        imports=grouped_imports,
        include_validators=include_validators,
        metadata={
            "version": version("fhircraft"),
            "timestamp": datetime.now(),
        },
    )
    # Replace the full module specification for any modules imported
    # First, collect all imported objects for class name cleanup
    all_imported_objects = set()
    for objects in grouped_imports.values():
        all_imported_objects.update(objects)

    # Also add all serialized models (from factory) to the cleanup list
    for model in self.data.keys():
        all_imported_objects.add(model.__name__)

    for module, objects in grouped_imports.items():
        module_escaped = module.replace(".", r"\.")
        # Remove module prefixes for imported objects
        for match in re.finditer(
            rf"({module_escaped}\.)({'|'.join(objects)})", source_code
        ):
            source_code = source_code.replace(match.group(1), "")

    # Also handle original import statements for any remaining references
    for module, objects in self.import_statements.items():
        module_escaped = module.replace(".", r"\.")
        for match in re.finditer(
            rf"({module_escaped}\.)({'|'.join(objects)})", source_code
        ):
            source_code = source_code.replace(match.group(1), "")

    # Clean up class representations for all imported objects
    for obj_name in all_imported_objects:
        # Replace <class 'ObjectName'> with ObjectName
        source_code = re.sub(
            rf"<class '{re.escape(obj_name)}'>", obj_name, source_code
        )
        # Also handle cases with module prefixes
        source_code = re.sub(
            rf"<class '[\w.]*\.{re.escape(obj_name)}'>", obj_name, source_code
        )

    # Clean up built-in types that aren't in imports
    builtin_types = ["str", "int", "float", "bool", "list", "dict", "tuple", "set"]
    for builtin_type in builtin_types:
        source_code = re.sub(
            rf"<class '{re.escape(builtin_type)}'>", builtin_type, source_code
        )

    # Clean up any references to the factory module
    source_code = source_code.replace(f"{FACTORY_MODULE}.", "")
    # Also clean up module paths that might appear in repr() output
    # This handles patterns like "fhircraft.fhir.resources.factory.ClassName("
    factory_pattern = re.escape(FACTORY_MODULE) + r"\."
    source_code = re.sub(factory_pattern, "", source_code)

    # Clean up module prefixes for ALL imported objects from repr() output
    # For each module with imports, remove the module. prefix for its objects
    for module, objects in self.import_statements.items():
        if objects:
            module_parts = module.split(".")
            # Try both full module path and last part (e.g., both "typing" and "typing" for "typing")
            module_variants = [module]
            if len(module_parts) > 1:
                module_variants.append(module_parts[-1])

            for module_part in module_variants:
                for obj in objects:
                    # Replace module.ObjectName with ObjectName (word boundaries to avoid partial matches)
                    source_code = re.sub(
                        rf"\b{re.escape(module_part)}\.{re.escape(obj)}\b",
                        obj,
                        source_code,
                    )

    # Special cleanup for typing module - remove typing. prefix for common constructs
    # This handles Optional, Union, List, etc. which may appear in repr() but not all in imports
    source_code = re.sub(r"\btyping\.", "", source_code)

    source_code = source_code.replace(LEFT_TO_RIGHT_COMPLEX, LEFT_TO_RIGHT_SIMPLE)
    return source_code

generate_resource_model_code module-attribute

generate_resource_model_code = generate_resource_model_code

generator module-attribute

generator = CodeGenerator()