Source code for fgen.data_models.package
"""
Package data model
"""
from __future__ import annotations
from collections import defaultdict
from typing import Any, Optional, Union
import attr
from attrs import define, field
from fgen.data_models.module import Module
from fgen.data_models.module_enum_defining import ModuleEnumDefining
from fgen.data_models.multi_return import MultiReturn
from fgen.data_models.value import Value
[docs]class NoProvidingModuleError(ValueError):
"""
Exception raised when no module provides a given type
"""
def __init__(
self, sought_type: str, value: Union[MultiReturn, Value], package: Package
):
error_msg = (
f"No module provides {sought_type}, which is the type of {value}. "
f"We searched in {package=}."
)
super().__init__(error_msg)
[docs]@define
class Package:
"""
Data model of a package
It isn't clear that our package is exactly like a typical Python/Fortran package,
so it is best to think of it exactly as it is for now:
a Package is a collection of :obj:~`fgen.data_models.Module`'s.
While the naming is similar to the Package > Module hierarchy used by Python,
they should be treated as different concepts.
"""
modules: tuple[Module, ...] = field()
"""
Collection of modules that define derived types used within the package
"""
modules_enum_defining: tuple[ModuleEnumDefining, ...] = field(factory=tuple)
"""
Collection of modules that define enums used within the package
"""
@modules.validator
def _modules_provide_unique(
self, attribute: attr.Attribute[Any], value: tuple[Module, ...]
) -> None:
provided_derived_types_modules = defaultdict(list)
# Multiple loops as mypy being silly
for module in value:
provided_derived_types_modules[module.provides.name].append(module.name)
for module_enum_defining in self.modules_enum_defining:
provided_derived_types_modules[module_enum_defining.provides.name].append(
module_enum_defining.name
)
not_unique = tuple(
(k, sorted(v))
for k, v in provided_derived_types_modules.items()
if len(v) > 1
)
if not_unique:
duplicate_type_info = "\n".join(
(
f"Provided type `{provided_type}` is provided by: {provided_by}"
for provided_type, provided_by in not_unique
)
)
msg = (
"The following derived types are provided by more than one module: "
f"{duplicate_type_info}"
)
raise ValueError(msg)
# Can cache this if we need speed
[docs] def get_module_that_provides_values_type(
self, value: Union[MultiReturn, Value]
) -> Module:
"""
Get the module that provides a value's data type
Parameters
----------
value
Value of which to find the data type provider
Returns
-------
Module that provides ``value``
Raises
------
NoProvidingModuleError
No module that provides ``value``'s data type is part of ``self.modules``.
"""
fdt = value.definition.as_fortran_data_type()
sought_type = fdt.equivalent_python_type
if "tuple" in sought_type:
sought_type = fdt.base_python_type
for module in self.modules:
if module.provides.name == sought_type:
return module
raise NoProvidingModuleError(sought_type=sought_type, value=value, package=self)
[docs] def find_providing_module(
self, value: Union[MultiReturn, Value]
) -> Optional[Module]:
"""
Find the module that provides a value's data type
If no module provides the value's data type, ``None`` is returned.
Parameters
----------
value
Value of which to find the data type provider
Returns
-------
Module that provides ``value``.
If no module provides the value, ``None`` is returned.
"""
try:
return self.get_module_that_provides_values_type(value)
except NoProvidingModuleError:
return None