Source code for hbllmutils.meta.code.object

"""
This module provides utilities for inspecting Python objects and retrieving their source code information.

It includes functionality to extract source file paths, line numbers, and source code for various Python objects
such as functions, classes, and methods. The module is particularly useful for code analysis, documentation
generation, and debugging purposes.
"""

import inspect
import os
import pathlib
from dataclasses import dataclass
from typing import Any, List, Optional


[docs] @dataclass class ObjectInspect: """ A dataclass that encapsulates inspection information about a Python object. This class stores metadata about a Python object including its source file location, line numbers, and source code. It provides convenient properties to access and manipulate this information. :param object: The Python object being inspected. :type object: Any :param source_file: The absolute path to the source file containing the object, or None if unavailable. :type source_file: Optional[str] :param start_line: The starting line number of the object in the source file, or None if unavailable. :type start_line: Optional[int] :param end_line: The ending line number of the object in the source file, or None if unavailable. :type end_line: Optional[int] :param source_lines: A list of source code lines for the object, or None if unavailable. :type source_lines: Optional[List[str]] """ object: Any source_file: Optional[str] start_line: Optional[int] end_line: Optional[int] source_lines: Optional[List[str]]
[docs] def __post_init__(self): """ Normalize and convert the source file path to an absolute path after initialization. This method is automatically called after the dataclass is initialized. It ensures that the source_file path is normalized, case-normalized, and converted to an absolute path for consistency across different platforms. """ if self.source_file is not None: self.source_file = os.path.normpath(os.path.normcase(os.path.abspath(self.source_file)))
@property def name(self) -> Optional[str]: """ Get the name of the inspected object. :return: The name of the object if it has a '__name__' attribute, otherwise None. :rtype: Optional[str] Example:: >>> def example_func(): ... pass >>> info = get_object_info(example_func) >>> info.name 'example_func' """ return getattr(self.object, '__name__', None) @property def source_code(self) -> Optional[str]: """ Get the source code of the inspected object. :return: The complete source code as a string if available, otherwise None. :rtype: Optional[str] Example:: >>> def example_func(): ... return 42 >>> info = get_object_info(example_func) >>> print(info.source_code) def example_func(): return 42 """ if self.has_source: return ''.join(self.source_lines) else: return None @property def source_file_code(self) -> Optional[str]: """ Get the complete source code of the file containing the inspected object. :return: The entire file content as a string if the source file is available, otherwise None. :rtype: Optional[str] Example:: >>> info = get_object_info(some_function) >>> file_content = info.source_file_code # Gets entire file content """ if self.source_file is not None: return pathlib.Path(self.source_file).read_text() else: return None @property def has_source(self) -> bool: """ Check whether source code lines are available for the inspected object. :return: True if source lines are available, False otherwise. :rtype: bool Example:: >>> info = get_object_info(print) # Built-in function >>> info.has_source False >>> def custom_func(): ... pass >>> info = get_object_info(custom_func) >>> info.has_source True """ return self.source_lines is not None @property def package_name(self) -> Optional[str]: """ Get the package name containing the inspected object. This property retrieves the package name by analyzing the source file path. It relies on the get_package_name function from the module utility. :return: The package name if the source file is available, otherwise None. :rtype: Optional[str] Example:: >>> # Assuming the object is from package 'mypackage' >>> info = get_object_info(some_function) >>> info.package_name 'mypackage' """ from .module import get_package_name if self.source_file is not None: return get_package_name(self.source_file) else: return None
[docs] def get_object_info(obj: Any) -> ObjectInspect: """ Retrieve comprehensive inspection information about a Python object. This function attempts to extract source file location, line numbers, and source code for the given object. If any information is unavailable (e.g., for built-in objects), the corresponding fields will be set to None. :param obj: The Python object to inspect (function, class, method, etc.). :type obj: Any :return: An ObjectInspect instance containing all available inspection information. :rtype: ObjectInspect Example:: >>> def example_function(): ... '''A simple example function.''' ... return "Hello, World!" >>> info = get_object_info(example_function) >>> info.name 'example_function' >>> info.has_source True >>> print(info.source_code) # doctest: +SKIP def example_function(): '''A simple example function.''' return "Hello, World!" >>> # Built-in objects have limited information >>> info = get_object_info(print) >>> info.has_source False >>> info.source_file is None True """ try: source_file = inspect.getfile(obj) except TypeError: source_file = None try: source_lines, start_line = inspect.getsourcelines(obj) end_line = start_line + len(source_lines) - 1 except TypeError: source_lines, start_line, end_line = None, None, None return ObjectInspect( object=obj, source_file=source_file, start_line=start_line, end_line=end_line, source_lines=source_lines, )