Ir al contenido

ReflectionModule

ReflectionModule importa un módulo Python y clasifica los objetos presentes en su espacio de nombres: clases, constantes, funciones y módulos importados. Úsalo para inspeccionar extensiones o construir un catálogo de componentes cargados. A diferencia del análisis de archivos de ModuleInspector, su construcción puede ejecutar el código del módulo mediante el sistema normal de importación de Python.

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

Guarda este archivo como app/plugins/catalog.py. Todos los ejemplos siguientes reutilizan este módulo y el wrapper reflected.

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 es una cadena obligatoria con el nombre importable, no una ruta de archivo ni un objeto módulo. Una cadena vacía, solo espacios o un valor de otro tipo genera TypeError. Cualquier excepción producida durante la importación se envuelve en TypeError, conservando la causa original. No recarga un módulo ya presente en sys.modules.

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

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

No recibe argumentos. Devuelve el objeto módulo importado (object en la firma), compartido con otros consumidores de ese módulo.

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

Se considera clase cualquier valor isinstance(value, type) presente en el módulo. Incluye clases importadas: no exige que value.__module__ coincida con el módulo inspeccionado. Las claves son los nombres de sus variables o alias en ese módulo; no necesariamente Class.__name__.

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

Sin argumentos devuelve dict de todas las clases y sus alias, incluidos nombres con _.

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

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

Sin argumentos devuelve dict con nombres que no comienzan con _.

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

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

Sin argumentos devuelve dict con nombres que empiezan con _, pero no con __.

assert "_InternalPlugin" in reflected.getProtectedClasses()

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

Sin argumentos devuelve dict con nombres que empiezan con __ y no terminan con __. Son nombres de módulo, sin mangling de clase.

assert "__PrivatePlugin" in reflected.getPrivateClasses()

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

class_name es obligatorio. Devuelve bool indicando si ese nombre está en getClasses().

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

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

class_name es obligatorio. Devuelve el objeto clase o None si no existe. No construye una instancia.

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

La clasificación considera constante cualquier nombre que cumpla str.isupper() y cuyo valor no sea callable. No verifica Final ni impide modificarlo. Los métodos devuelven los valores reales, no copias; una constante puede contener una lista u otro objeto mutable.

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

constant_name es obligatorio. Devuelve el valor (object) o None si falta; una constante cuyo valor sea None produce el mismo resultado.

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

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

Sin argumentos devuelve dict con todas las constantes reconocidas, incluidas las que empiezan con guiones bajos.

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

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

Sin argumentos devuelve dict de constantes sin prefijo _.

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

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

Sin argumentos devuelve dict de constantes con un _ inicial y sin __ inicial.

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

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

Sin argumentos devuelve dict de constantes con __ inicial y sin __ final.

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

Los filtros incluyen solamente instancias de types.FunctionType: funciones Python propias o importadas. No incluyen clases, métodos ligados, callables de objeto ni funciones builtin. La separación async usa inspect.iscoroutinefunction; no ejecuta las funciones ni inspecciona el valor que devolverían. Un generador asíncrono no es una función de corrutina y queda en el filtro sync.

Todos estos métodos no reciben argumentos y devuelven dict de nombre a función. Para ejecutar una función async debes invocarla y esperar su resultado en tu flujo asíncrono.

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

Devuelve todas las funciones Python, de cualquier visibilidad.

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

getPublicFunctions(self) -> dict — Devuelve funciones públicas, sync y async.

assert "list_items" in reflected.getPublicFunctions()

getPublicSyncFunctions(self) -> dict — Devuelve funciones públicas síncronas.

assert "list_items" in reflected.getPublicSyncFunctions()

getPublicAsyncFunctions(self) -> dict — Devuelve funciones públicas asíncronas.

assert "refresh_items" in reflected.getPublicAsyncFunctions()

getProtectedFunctions(self) -> dict — Devuelve funciones protegidas, sync y async.

assert "_prepare" in reflected.getProtectedFunctions()

getProtectedSyncFunctions(self) -> dict — Devuelve funciones protegidas síncronas.

assert "_prepare" in reflected.getProtectedSyncFunctions()

getProtectedAsyncFunctions(self) -> dict — Devuelve funciones protegidas asíncronas.

assert "_prepare_async" in reflected.getProtectedAsyncFunctions()

getPrivateFunctions(self) -> dict — Devuelve funciones privadas, sync y async.

assert "__sign" in reflected.getPrivateFunctions()

getPrivateSyncFunctions(self) -> dict — Devuelve funciones privadas síncronas.

assert "__sign" in reflected.getPrivateSyncFunctions()

getPrivateAsyncFunctions(self) -> dict — Devuelve funciones privadas asíncronas.

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

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

Sin argumentos devuelve dict de nombres cuyo valor es un objeto ModuleType. import math aparece como math; from pathlib import Path no aparece como import de módulo, aunque Path sí aparece en las clases. No analiza sentencias del archivo.

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

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

Sin argumentos devuelve str mediante inspect.getfile y memoiza la ruta. Propaga TypeError para módulos sin archivo inspeccionable, como algunos módulos builtin o creados dinámicamente.

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

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

Sin argumentos abre getFile() como UTF-8, devuelve el contenido completo (str) y lo memoiza. Los errores de archivo, decodificación o inspección se envuelven en ValueError conservando la causa.

assert "class CatalogPlugin" in reflected.getSourceCode()

Estas operaciones modifican el objeto módulo en memoria para todos sus consumidores. No editan el archivo Python ni actualizan referencias que otro módulo ya haya importado. Úsalas cuando una extensión realmente necesite registrar o retirar un alias de clase durante su inicialización.

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

class_name: str y cls: type son obligatorios. Exige una clase (TypeError) y un identificador válido que no sea palabra reservada (ValueError). Asigna o reemplaza el alias y devuelve True. Solo invalida la entrada classes; llama a clearCache() antes de volver a consultar filtros de visibilidad ya calculados.

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 es obligatorio. Elimina un alias de clase existente y devuelve True. Lanza ValueError si no está en getClasses(). Igual que setClass, solo invalida classes; vacía la caché para refrescar otros filtros.

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

Los diccionarios de resultados se memoizan y se devuelven por referencia. No los modifiques como si fueran copias independientes. Cada wrapper tiene su caché; los cambios externos del módulo no la invalidan. El protocolo de diccionario maneja esta caché, no los atributos del módulo. No escribe archivos.

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

Sin argumentos elimina todos los resultados cacheados y devuelve None. No recarga el módulo con importlib.reload ni vacía la caché de clases de ModuleInspector.

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

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

Guarda value bajo key: str y devuelve None.

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

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

Consulta key: str y devuelve el valor o None; no lanza KeyError.

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

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

Devuelve si key: str está en la caché.

assert "custom:catalog" in reflected

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

Elimina key: str si existe; devuelve None y no falla para una clave ausente.

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

Evita escribir claves internas como classes o functions: intervienen directamente en las lecturas del wrapper.

El módulo objetivo es un dato de tu aplicación. Puedes encapsular la construcción del wrapper en un servicio e inyectar ese servicio mediante Orionis. No hay que registrar una instancia genérica de ReflectionModule sin un nombre de módulo, ni la reflexión constituye un registro automático de 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()})

El contrato del wrapper es IReflectionModule, importable desde orionis.introspection.modules.contracts.reflection. Si deseas inyectar ese contrato, registra explícitamente una instancia o fábrica que construya el wrapper para el nombre de módulo elegido.

Descubrir módulos y cargar clases.