Module: class_info
Expand source code
# Copyright (C) 2023-present The Project Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from abc import ABC
from importlib import import_module
from typing import List
from typing import Tuple
from typing import Type
from memoization import cached # TODO: Replace cached package by custom caching for better performance
class ClassInfo(ABC):
"""Helper methods for Record."""
@classmethod
def get_class_path(cls, class_: Type) -> str:
"""Returns the concatenation of module path and class name using dot delimiter.
- The argument cls is either a literal class type, for example StubClass,
or a type variable obtained from a class instance, for example type(stub_class_instance).
- This method is also used to calculate key for LRU caching of functions taking class
as their argument. This method is itself not cached because caching would involve
calling the same method, resulting in no performance gain.
"""
return f"{class_.__module__}.{class_.__name__}"
@classmethod
def split_class_path(cls, class_path: str) -> Tuple[str, str]:
"""Split dot-delimited class path into module path and class name.
Returns:
Tuple of module_name, class_name
"""
result = class_path.rsplit(".", 1)
return result[0], result[1]
@classmethod
@cached
def get_class_type(cls, class_path: str) -> Type:
"""
Get class type from string in 'module.ClassName' format, importing the module if necessary.
Notes:
Return value is cached to increase performance.
Args:
class_path: String in module.ClassName format.
"""
module_name, class_name = cls.split_class_path(class_path)
# Check that the module exists and is fully initialized
module = sys.modules.get(module_name)
module_spec = getattr(module, "__spec__", None) if module is not None else None
module_initializing = getattr(module_spec, "_initializing", False) if module_spec is not None else None
module_imported = module_initializing is False # To ensure it is not another value evaluating to False
# Import dynamically if not already imported, report error if not found
if not module_imported:
try:
module = import_module(module_name)
except ModuleNotFoundError:
raise RuntimeError(f"Module {module_name} is not found when loading class {class_name}.")
# Get class from module, report error if not found
try:
result = getattr(module, class_name)
return result
except AttributeError:
raise RuntimeError(f"Module {module_name} does not contain top-level class {class_name}.")
@classmethod
@cached(custom_key_maker=lambda cls, record_type: f"{record_type.__module__}.{record_type.__name__}")
def get_inheritance_chain(cls, record_type: Type) -> List[str]:
"""
Returns the list of fully qualified class names in MRO order starting from this class
and ending with the class that has suffix Key. Exactly one class with suffix Key should
be present in MRO, error otherwise.
"""
# Get the list of classes in MRO
fully_qualified_names = [
f"{c.__module__}.{c.__name__}"
for c in record_type.mro()
if hasattr(c, "get_key") and not c.__name__.endswith("Mixin")
]
# Make sure there is only one such class in the inheritance chain
if len(fully_qualified_names) == 0:
raise RuntimeError(
f"Class {record_type.__module__}.{record_type.__name__} does not implement get_key(self) method."
)
# TODO: Add package aliases
# Remove module from fully qualified names
result = [name.split(".")[-1] for name in fully_qualified_names]
return result
Classes
class ClassInfo
-
Helper methods for Record.
Expand source code
class ClassInfo(ABC): """Helper methods for Record.""" @classmethod def get_class_path(cls, class_: Type) -> str: """Returns the concatenation of module path and class name using dot delimiter. - The argument cls is either a literal class type, for example StubClass, or a type variable obtained from a class instance, for example type(stub_class_instance). - This method is also used to calculate key for LRU caching of functions taking class as their argument. This method is itself not cached because caching would involve calling the same method, resulting in no performance gain. """ return f"{class_.__module__}.{class_.__name__}" @classmethod def split_class_path(cls, class_path: str) -> Tuple[str, str]: """Split dot-delimited class path into module path and class name. Returns: Tuple of module_name, class_name """ result = class_path.rsplit(".", 1) return result[0], result[1] @classmethod @cached def get_class_type(cls, class_path: str) -> Type: """ Get class type from string in 'module.ClassName' format, importing the module if necessary. Notes: Return value is cached to increase performance. Args: class_path: String in module.ClassName format. """ module_name, class_name = cls.split_class_path(class_path) # Check that the module exists and is fully initialized module = sys.modules.get(module_name) module_spec = getattr(module, "__spec__", None) if module is not None else None module_initializing = getattr(module_spec, "_initializing", False) if module_spec is not None else None module_imported = module_initializing is False # To ensure it is not another value evaluating to False # Import dynamically if not already imported, report error if not found if not module_imported: try: module = import_module(module_name) except ModuleNotFoundError: raise RuntimeError(f"Module {module_name} is not found when loading class {class_name}.") # Get class from module, report error if not found try: result = getattr(module, class_name) return result except AttributeError: raise RuntimeError(f"Module {module_name} does not contain top-level class {class_name}.") @classmethod @cached(custom_key_maker=lambda cls, record_type: f"{record_type.__module__}.{record_type.__name__}") def get_inheritance_chain(cls, record_type: Type) -> List[str]: """ Returns the list of fully qualified class names in MRO order starting from this class and ending with the class that has suffix Key. Exactly one class with suffix Key should be present in MRO, error otherwise. """ # Get the list of classes in MRO fully_qualified_names = [ f"{c.__module__}.{c.__name__}" for c in record_type.mro() if hasattr(c, "get_key") and not c.__name__.endswith("Mixin") ] # Make sure there is only one such class in the inheritance chain if len(fully_qualified_names) == 0: raise RuntimeError( f"Class {record_type.__module__}.{record_type.__name__} does not implement get_key(self) method." ) # TODO: Add package aliases # Remove module from fully qualified names result = [name.split(".")[-1] for name in fully_qualified_names] return result
Ancestors
- abc.ABC
Static methods
def get_class_path(class_: Type) -> str
-
Returns the concatenation of module path and class name using dot delimiter.
- The argument cls is either a literal class type, for example StubClass, or a type variable obtained from a class instance, for example type(stub_class_instance).
- This method is also used to calculate key for LRU caching of functions taking class as their argument. This method is itself not cached because caching would involve calling the same method, resulting in no performance gain.
def get_class_type(cls, class_path: str) -> Type
-
Get class type from string in ‘module.ClassName’ format, importing the module if necessary.
Notes
Return value is cached to increase performance.
Args
class_path
- String in module.ClassName format.
def get_inheritance_chain(cls, record_type: Type) -> List[str]
-
Returns the list of fully qualified class names in MRO order starting from this class and ending with the class that has suffix Key. Exactly one class with suffix Key should be present in MRO, error otherwise.
def split_class_path(class_path: str) -> Tuple[str, str]
-
Split dot-delimited class path into module path and class name.
Returns
Tuple of module_name, class_name