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.
Inicio rápido
Sección titulada «Inicio rápido»from orionis.introspection import ReflectionModule
metadata = ReflectionModule("orionis.metadata.framework")assert metadata.getConstant("NAME") == "orionis"Módulo de ejemplo
Sección titulada «Módulo de ejemplo»Guarda este archivo como app/plugins/catalog.py. Todos los ejemplos siguientes reutilizan este módulo y el wrapper reflected.
import mathfrom 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")__init__
Sección titulada «__init__»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")getModule
Sección titulada «getModule»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")Clases y alias de clases
Sección titulada «Clases y alias de clases»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__.
getClasses
Sección titulada «getClasses»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()getPublicClasses
Sección titulada «getPublicClasses»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()getProtectedClasses
Sección titulada «getProtectedClasses»def getProtectedClasses(self) -> dict: ...Sin argumentos devuelve dict con nombres que empiezan con _, pero no con __.
assert "_InternalPlugin" in reflected.getProtectedClasses()getPrivateClasses
Sección titulada «getPrivateClasses»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()hasClass
Sección titulada «hasClass»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")getClass
Sección titulada «getClass»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 NoneConstantes
Sección titulada «Constantes»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.
getConstant
Sección titulada «getConstant»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") == 10assert reflected.getConstant("missing") is NonegetConstants
Sección titulada «getConstants»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"] == 30getPublicConstants
Sección titulada «getPublicConstants»def getPublicConstants(self) -> dict: ...Sin argumentos devuelve dict de constantes sin prefijo _.
assert reflected.getPublicConstants()["PUBLIC_LIMIT"] == 10getProtectedConstants
Sección titulada «getProtectedConstants»def getProtectedConstants(self) -> dict: ...Sin argumentos devuelve dict de constantes con un _ inicial y sin __ inicial.
assert reflected.getProtectedConstants()["_INTERNAL_LIMIT"] == 20getPrivateConstants
Sección titulada «getPrivateConstants»def getPrivateConstants(self) -> dict: ...Sin argumentos devuelve dict de constantes con __ inicial y sin __ final.
assert reflected.getPrivateConstants()["__PRIVATE_LIMIT"] == 30Funciones sync y async
Sección titulada «Funciones sync y async»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.
getFunctions
Sección titulada «getFunctions»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"]()Imports y código fuente
Sección titulada «Imports y código fuente»getImports
Sección titulada «getImports»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()getFile
Sección titulada «getFile»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")getSourceCode
Sección titulada «getSourceCode»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()Modificar el espacio de nombres
Sección titulada «Modificar el espacio de nombres»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.
setClass
Sección titulada «setClass»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 Truereflected.clearCache()assert reflected.getPublicClasses()["ExtraPlugin"] is plugin_typeremoveClass
Sección titulada «removeClass»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 Truereflected.clearCache()assert not reflected.hasClass("ExtraPlugin")Caché del wrapper
Sección titulada «Caché del wrapper»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.
clearCache
Sección titulada «clearCache»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 Noneassert "CatalogPlugin" in reflected.getClasses()__setitem__
Sección titulada «__setitem__»def __setitem__(self, key: str, value: object) -> None: ...Guarda value bajo key: str y devuelve None.
reflected["custom:catalog"] = "loaded"__getitem__
Sección titulada «__getitem__»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__contains__
Sección titulada «__contains__»def __contains__(self, key: str) -> bool: ...Devuelve si key: str está en la caché.
assert "custom:catalog" in reflected__delitem__
Sección titulada «__delitem__»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.
Uso desde componentes con inyección
Sección titulada «Uso desde componentes con inyección»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 JSONResponsefrom 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.