Skip to content

ReflectionModule

ReflectionModule imports a Python module and classifies the objects in its namespace: classes, constants, functions and imported modules. Use it to inspect extensions or build a catalog of loaded components. Unlike ModuleInspector file analysis, construction can execute module code through Python’s normal import system.

from orionis.introspection import ReflectionModule
metadata = ReflectionModule("orionis.metadata.framework")
assert metadata.getConstant("NAME") == "orionis"

Save this file as app/plugins/catalog.py. The following examples reuse that module and the reflected wrapper.

import math
from pathlib import Path
PUBLIC_LIMIT = 10
_INTERNAL_LIMIT = 20
__PRIVATE_LIMIT = 30
class CatalogPlugin:
def run(self) -> str:
return "catalog"
class _InternalPlugin:
pass
class __PrivatePlugin:
pass
def list_items() -> list[str]:
return ["book"]
async def refresh_items() -> int:
return 1
def _prepare() -> bool:
return True
async def _prepare_async() -> bool:
return True
def __sign() -> str:
return "signed"
async def __sign_async() -> str:
return "signed"
from orionis.introspection import ReflectionModule
reflected = ReflectionModule("app.plugins.catalog")

def __init__(self, module: str) -> None: ...

module is a required string containing an importable name, not a file path or module object. An empty string, whitespace-only string or another type raises TypeError. Any exception raised during import is wrapped in TypeError, preserving its original cause. This does not reload a module already present in sys.modules.

reflected = ReflectionModule("app.plugins.catalog")

def getModule(self) -> object: ...

Take no arguments. Return the imported module object (object in the signature), shared with other consumers of that module.

import importlib
assert reflected.getModule() is importlib.import_module("app.plugins.catalog")

Any namespace value satisfying isinstance(value, type) is a class. Imported classes are included: value.__module__ need not match the inspected module. Keys are variable names or aliases in that module; they need not equal Class.__name__.

def getClasses(self) -> dict: ...

With no arguments, return a dict of all classes and aliases, including names beginning with _.

assert "CatalogPlugin" in reflected.getClasses()
assert "Path" in reflected.getClasses()

def getPublicClasses(self) -> dict: ...

With no arguments, return a dict whose names do not start with _.

assert "CatalogPlugin" in reflected.getPublicClasses()
assert "_InternalPlugin" not in reflected.getPublicClasses()

def getProtectedClasses(self) -> dict: ...

With no arguments, return a dict whose names start with _, but not __.

assert "_InternalPlugin" in reflected.getProtectedClasses()

def getPrivateClasses(self) -> dict: ...

With no arguments, return a dict whose names start with __ and do not end with __. These are module names, without class name mangling.

assert "__PrivatePlugin" in reflected.getPrivateClasses()

def hasClass(self, class_name: str) -> bool: ...

class_name is required. Return bool indicating whether that name is in getClasses().

assert reflected.hasClass("CatalogPlugin")
assert not reflected.hasClass("missing")

def getClass(self, class_name: str) -> type | None: ...

class_name is required. Return the class object, or None when absent. This does not construct an instance.

plugin_type = reflected.getClass("CatalogPlugin")
assert plugin_type().run() == "catalog"
assert reflected.getClass("missing") is None

Any name satisfying str.isupper() whose value is not callable is classified as a constant. This does not check Final or enforce immutability. Methods return actual values, not copies; a constant can contain a list or another mutable object.

def getConstant(self, constant_name: str) -> object | None: ...

constant_name is required. Return its value (object), or None when absent; a constant set to None yields the same result.

assert reflected.getConstant("PUBLIC_LIMIT") == 10
assert reflected.getConstant("missing") is None

def getConstants(self) -> dict: ...

With no arguments, return a dict of all recognized constants, including names beginning with underscores.

assert reflected.getConstants()["__PRIVATE_LIMIT"] == 30

def getPublicConstants(self) -> dict: ...

With no arguments, return a dict of constants without an _ prefix.

assert reflected.getPublicConstants()["PUBLIC_LIMIT"] == 10

def getProtectedConstants(self) -> dict: ...

With no arguments, return a dict of constants starting with _, but not __.

assert reflected.getProtectedConstants()["_INTERNAL_LIMIT"] == 20

def getPrivateConstants(self) -> dict: ...

With no arguments, return a dict of constants starting with __ but not ending with __.

assert reflected.getPrivateConstants()["__PRIVATE_LIMIT"] == 30

Filters include only types.FunctionType instances: locally defined or imported Python functions. Classes, bound methods, callable objects and built-in functions are excluded. Async classification uses inspect.iscoroutinefunction; it does not execute functions or inspect their potential return values. An async generator is not a coroutine function and remains in the sync filter.

All these methods take no arguments and return a dict mapping names to functions. To run an async function, invoke it and await its result in your asynchronous flow.

def getFunctions(self) -> dict: ...

Return all Python functions, of every visibility.

assert "list_items" in reflected.getFunctions()
assert "__sign" in reflected.getFunctions()

getPublicFunctions(self) -> dict — Return public functions, both sync and async.

assert "list_items" in reflected.getPublicFunctions()

getPublicSyncFunctions(self) -> dict — Return public functions that are synchronous.

assert "list_items" in reflected.getPublicSyncFunctions()

getPublicAsyncFunctions(self) -> dict — Return public functions that are asynchronous.

assert "refresh_items" in reflected.getPublicAsyncFunctions()

getProtectedFunctions(self) -> dict — Return protected functions, both sync and async.

assert "_prepare" in reflected.getProtectedFunctions()

getProtectedSyncFunctions(self) -> dict — Return protected functions that are synchronous.

assert "_prepare" in reflected.getProtectedSyncFunctions()

getProtectedAsyncFunctions(self) -> dict — Return protected functions that are asynchronous.

assert "_prepare_async" in reflected.getProtectedAsyncFunctions()

getPrivateFunctions(self) -> dict — Return private functions, both sync and async.

assert "__sign" in reflected.getPrivateFunctions()

getPrivateSyncFunctions(self) -> dict — Return private functions that are synchronous.

assert "__sign" in reflected.getPrivateSyncFunctions()

getPrivateAsyncFunctions(self) -> dict — Return private functions that are asynchronous.

assert "__sign_async" in reflected.getPrivateAsyncFunctions()
async def inspect_and_refresh() -> int:
functions = reflected.getPublicAsyncFunctions()
return await functions["refresh_items"]()

def getImports(self) -> dict: ...

With no arguments, return a dict of names whose values are ModuleType objects. import math appears as math; from pathlib import Path is not a module-object import, although Path appears among classes. This does not analyze file statements.

assert "math" in reflected.getImports()
assert "Path" not in reflected.getImports()

def getFile(self) -> str: ...

With no arguments, return str using inspect.getfile and memoize the path. TypeError propagates for modules without an inspectable file, such as some built-in or dynamically created modules.

assert reflected.getFile().endswith("catalog.py")

def getSourceCode(self) -> str: ...

With no arguments, open getFile() as UTF-8, return the complete contents (str) and memoize them. File, decoding and inspection errors are wrapped in ValueError, preserving the cause.

assert "class CatalogPlugin" in reflected.getSourceCode()

These operations modify the module object in memory for all its consumers. They do not edit the Python file or update references already imported into another module. Use them when an extension needs to register or remove a class alias during initialization.

def setClass(self, class_name: str, cls: type) -> bool: ...

class_name: str and cls: type are required. Require a class (TypeError) and a valid identifier that is not a keyword (ValueError). Assign or replace the alias and return True. Only the classes entry is invalidated; call clearCache() before reusing previously calculated visibility filters.

plugin_type = reflected.getClass("CatalogPlugin")
assert reflected.setClass("ExtraPlugin", plugin_type) is True
reflected.clearCache()
assert reflected.getPublicClasses()["ExtraPlugin"] is plugin_type

def removeClass(self, class_name: str) -> bool: ...

class_name is required. Delete an existing class alias and return True. Names absent from getClasses() raise ValueError. Like setClass, only classes is invalidated; clear the cache to refresh other filters.

assert reflected.removeClass("ExtraPlugin") is True
reflected.clearCache()
assert not reflected.hasClass("ExtraPlugin")

Result dictionaries are memoized and returned by reference. Do not modify them as if they were independent copies. Each wrapper owns its cache; external module changes do not invalidate it. The dictionary protocol operates on this cache, not on module attributes. No files are written.

def clearCache(self) -> None: ...

With no arguments, remove all cached results and return None. This neither reloads the module through importlib.reload nor clears the ModuleInspector class cache.

assert reflected.clearCache() is None
assert "CatalogPlugin" in reflected.getClasses()

def __setitem__(self, key: str, value: object) -> None: ...

Store value under key: str and return None.

reflected["custom:catalog"] = "loaded"

def __getitem__(self, key: str) -> object | None: ...

Look up key: str and return the value or None; this does not raise KeyError.

assert reflected["custom:catalog"] == "loaded"
assert reflected["custom:missing"] is None

def __contains__(self, key: str) -> bool: ...

Return whether key: str is present in the cache.

assert "custom:catalog" in reflected

def __delitem__(self, key: str) -> None: ...

Remove key: str if present; return None and do not fail for an absent key.

del reflected["custom:catalog"]
del reflected["custom:missing"]

Avoid writing internal keys such as classes or functions: they directly control wrapper reads.

The target module is application data. Encapsulate wrapper construction in a service and inject that service through Orionis. Do not register a generic ReflectionModule instance without a module name; reflection itself does not automatically register plugins.

from orionis.http import JSONResponse
from orionis.introspection import ReflectionModule
class PluginCatalog:
def names(self) -> list[str]:
module = ReflectionModule("app.plugins.catalog")
return sorted(module.getPublicClasses())
class PluginController:
async def __call__(self, catalog: PluginCatalog) -> JSONResponse:
return JSONResponse({"classes": catalog.names()})

The wrapper contract is IReflectionModule, importable from orionis.introspection.modules.contracts.reflection. To inject that contract, explicitly register an instance or factory constructing the wrapper for the chosen module name.

Discover modules and load classes.