Repository URL to install this package:
|
Version:
0.18.5 ▾
|
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.
'''
Project-wide **Python module getter** (i.e., callables dynamically retrieving
modules and/or attributes in modules) utilities.
This private submodule is *not* intended for importation by downstream callers.
'''
# ....................{ IMPORTS }....................
from beartype._cave._cavefast import ModuleType
from beartype.roar._roarexc import _BeartypeUtilModuleException
from beartype.typing import Optional
from inspect import findsource
from pathlib import Path
from sys import modules as sys_modules
# ....................{ GETTERS }....................
#FIXME: Unit test us up, please.
def get_module_imported_or_none(module_name: str) -> Optional[ModuleType]:
'''
Previously imported module, package, or C extension with the passed
fully-qualified name if previously imported *or* :data:`None` otherwise
(i.e., if that module, package, or C extension has yet to be imported).
Parameters
----------
module_name : str
Fully-qualified name of the previously imported module to be returned.
Returns
-------
Either:
* If a module, package, or C extension with this fully-qualified name has
already been imported, that module, package, or C extension.
* Else, :data:`None`.
'''
# Donkey One-liner Country: Codebase Freeze!
return sys_modules.get(module_name)
# ....................{ GETTERS ~ object }....................
def get_object_module_or_none(obj: object) -> Optional[ModuleType]:
'''
Module declaring the passed object if this object defines the ``__module__``
dunder instance variable *or* :data:`None` otherwise.
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
Optional[ModuleType]
Either:
* Module declaring this object if this object declares a ``__module__``
dunder attribute.
* :data:`None` otherwise.
'''
# Fully-qualified name of the module defining this object if any or "None".
module_name = get_object_module_name_or_none(obj)
# Return either:
# * If a module defines this object, that module.
# * Else, "None".
return get_module_imported_or_none(module_name) if module_name else None
def get_object_module(obj: object) -> ModuleType:
'''
Module declaring the passed object if this object defines the ``__module__``
dunder instance variable *or* raise an exception otherwise (i.e., if this
object does *not* define that variable).
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
ModuleType
Module declaring this object.
Raises
------
_BeartypeUtilModuleException
If this object does *not* define the ``__module__`` dunder attribute.
'''
# Fully-qualified name of the module defining this object if any *OR* raise
# an exception otherwise.
module_name = get_object_module_name(obj)
# Module defining this object if any *OR* "None" otherwise.
module = get_module_imported_or_none(module_name)
# If this module was *NOT* previously imported despite this object existing
# and thus having been imported from something, this object deceptively lies
# about its module. In this case, raise an exception.
if module is None:
raise _BeartypeUtilModuleException(
f'{repr(obj)} module "{module_name}" not found.')
# If this module was previously imported.
# Return this module.
return module
# ....................{ GETTERS ~ object : line }....................
def get_object_module_line_number_begin(obj: object) -> int:
'''
**Line number** (i.e., 1-based index) of the first line of the source code
of the module declaring the passed object if this object is either a
callable or class *or* raise an exception otherwise (i.e., if this object is
neither a callable nor class).
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
int
1-based index of the first line of the source code of the module
declaring the passed object.
Raises
------
_BeartypeUtilModuleException
If this object is neither a callable nor class.
'''
# If this object is a class, defer to the standard "inspect" module.
#
# Note that:
# * Deciding whether an object is a class is slightly faster than deciding
# whether an object is a callable. The former trivially reduces to a
# single isinstance() call against a single superclass; the latter is
# considerably less trivial. Ergo, this object is tested as a class first.
# * Deciding the line number of the first line declaring an arbitrary class
# in its underlying source code module file is highly non-trivial (and in
# fact requires extremely slow AST-based parsing). For maintainability and
# robustness, we defer to the well-tested standard "inspect" module
# despite the performance hit in doing so.
if isinstance(obj, type):
_, cls_source_line_number_start = findsource(obj)
return cls_source_line_number_start
# Else, this object is *NOT* a class.
# Avoid circular import dependencies.
from beartype._util.func.utilfunccodeobj import get_func_codeobj_or_none
# Code object underlying this object if this object is a pure-Python
# callable *OR* "None" otherwise.
#
# Note this is the canonical means of deciding whether an arbitrary object
# is a pure-Python callable, as our is_func_python() function demonstrates.
func_codeobj = get_func_codeobj_or_none(obj)
# If this object is a pure-Python callable, return the line number of the
# first line declaring this object in its underlying source code file.
if func_codeobj is not None:
return func_codeobj.co_firstlineno
# Else, this object is neither a pure-Python callable *NOR* a class.
# In this case, raise an exception.
raise _BeartypeUtilModuleException(
f'{repr(obj)} neither callable nor class.')
# ....................{ GETTERS ~ object : name }....................
#FIXME: Unit test us up, please.
def get_object_module_name(obj: object) -> str:
'''
**Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
declaring package) of the module declaring the passed object if this
object defines the ``__module__`` dunder instance variable *or* raise an
exception otherwise (i.e., if this object does *not* define that variable).
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
str
Fully-qualified name of the module declaring this object.
Raises
------
_BeartypeUtilModuleException
If this object does *not* define the ``__module__`` dunder attribute.
'''
# Fully-qualified name of the module declaring this object if this object
# defines the "__module__" dunder instance variable *OR* "None" otherwise.
module_name = get_object_module_name_or_none(obj)
# If this object defines *NO* "__module__" dunder instance variable, raise
# an exception.
if module_name is None:
raise _BeartypeUtilModuleException(
f'{repr(obj)} "__module__" dunder attribute undefined '
f'(e.g., due to being neither class nor callable).'
)
# Else, this fully-qualified module name exists.
# Return this name.
return module_name
#FIXME: Unit test us up, please.
def get_object_module_name_or_none(obj: object) -> Optional[str]:
'''
**Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
declaring package) of the module declaring the passed object if this object
defines the ``__module__`` dunder instance variable *or* :data:`None`
otherwise.
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
Optional[str]
Either:
* Fully-qualified name of the module declaring this object if this
object declares a ``__module__`` dunder attribute.
* :data:`None` otherwise.
'''
# Let it be, speaking one-liners of wisdom.
return getattr(obj, '__module__', None)
# ....................{ GETTERS ~ object : type : name }....................
#FIXME: Unit test us up, please.
def get_object_type_module_name_or_none(obj: object) -> Optional[str]:
'''
**Fully-qualified name** (i.e., ``.``-delimited name prefixed by the
declaring package) of the module declaring either the passed object if this
object is a class *or* the class of this object otherwise (i.e., if this
object is *not* a class) if this class declares the ``__module__`` dunder
instance variable *or* :data:`None` otherwise.
Parameters
----------
obj : object
Object to be inspected.
Returns
-------
Optional[str]
Either:
* Fully-qualified name of the module declaring the type of this object
if this type declares a ``__module__`` dunder attribute.
* :data:`None` otherwise.
'''
# Avoid circular import dependencies.
from beartype._util.utilobject import get_object_type_unless_type
# Make it so, ensign.
return get_object_module_name_or_none(get_object_type_unless_type(obj))
# ....................{ GETTERS ~ module : dir }....................
#FIXME: Unit test us up.
def get_module_dir(module: ModuleType) -> Path:
'''
High-level :class:`Path` object encapsulating the absolute dirname of the
parent directory containing the passed module if this module is physically
defined on-disk *or* raise an exception otherwise (i.e., if this module is
abstractly defined only in-memory).
Parameters
----------
module : ModuleType
Module to be inspected.
Returns
-------
Path
High-level :class:`Path` object encapsulating the absolute dirname of
the parent directory containing this on-disk module.
Raises
------
_BeartypeUtilModuleException
If this module *only* resides in memory.
'''
# Absolute filename of this module if this module is physically defined
# on-disk *OR* raise an exception otherwise (i.e., if this module is
# abstractly defined only in-memory).
module_filename = get_module_filename(module)
# High-level "Path" object encapsulating this file and the parent directory
# directly containing this file.
module_file = Path(module_filename)
module_dir = module_file.parent
# Return this "Path" object.
return module_dir
# ....................{ GETTERS ~ module : file }....................
#FIXME: Unit test us up.
def get_module_filename(module: ModuleType) -> str:
'''
Absolute filename of the passed module if this module is physically defined
on-disk *or* raise an exception otherwise (i.e., if this module is
abstractly defined only in-memory).
Parameters
----------
module : ModuleType
Module to be inspected.
Returns
-------
str
Absolute filename of this on-disk module.
Raises
------
_BeartypeUtilModuleException
If this module *only* resides in memory.
See Also
--------
:func:`get_module_filename_or_none`
Further details.
'''
# Absolute filename of this module if on-disk *OR* "None" otherwise.
module_filename = get_module_filename_or_none(module)
# If this module resides *ONLY* in memory, raise an exception.
if module_filename is None:
raise _BeartypeUtilModuleException(
f'Module {repr(module)} file not found '
f'(e.g., due to either being a namespace (sub)package or '
f'a dynamically defined in-memory module).'
)
# Else, this module resides on disk.
# Return this filename.
return module_filename
#FIXME: Unit test us up.
def get_module_filename_or_none(module: ModuleType) -> Optional[str]:
'''
Absolute filename of the passed module if this module is physically defined
on-disk *or* :data:`None` otherwise (i.e., if this module is abstractly
defined only in-memory).
Specifically, this getter returns either:
* If this module is actually a package, the absolute filename of the
``"__init__.py"`` submodule directly contained in this package.
* Else, the absolute filename of this module as provided by the `__file__`
dunder attribute of this in-memory module object.
In either case, the filename returned by this getter (if any) necessarily
refers to a file rather than a directory.
Parameters
----------
module : ModuleType
Module to be inspected.
Returns
-------
Optional[str]
Either:
* Absolute filename of this module if this module resides on disk.
* :data:`None` if this module *only* resides in memory.
'''
# Thus spake Onelinerthustra.
return getattr(module, '__file__', None)