ReflectionCallable
ReflectionCallable envuelve una función sin ejecutarla. Úsalo para construir un catálogo de handlers, consultar sus argumentos o generar información de diagnóstico. Tiene diez métodos públicos y cinco métodos especiales propios, incluido el constructor.
Construcción e importación
Sección titulada «Construcción e importación»__init__(self, fn: callable) -> None recibe un objetivo obligatorio y devuelve None al inicializar el wrapper. Importa la clase desde orionis.introspection; su módulo de definición es orionis.introspection.callables.reflection.
Admite funciones Python, lambdas y métodos ligados. También admite un objeto callable con __code__, pero después lee __name__, __module__ y __doc__: esos atributos deben existir. No acepta cualquier objeto por el solo hecho de implementar __call__. Una clase ordinaria sin __code__, una función builtin o un callable sin __code__ produce TypeError; un objeto admitido por ese filtro pero sin sus metadatos puede producir AttributeError.
from orionis.introspection import Reflection, ReflectionCallable
def quote(units: int, *, currency: str = "USD") -> str: """Build a quote label.""" return f"{units} {currency}"
reflected = ReflectionCallable(quote)assert reflected.getCallable() is quoteassert Reflection.callable(quote).getCallable() is quoteassert ReflectionCallable(lambda item: item).getName() == "<lambda>"
class Action: def __call__(self, value: int) -> int: return value
action = Action()assert ReflectionCallable(action.__call__).getCallable() == action.__call__for invalid in (len, Action, action, 42): try: ReflectionCallable(invalid) except TypeError: pass else: raise AssertionError("El objetivo no debía aceptarse")Reflection.callable(fn) crea el mismo tipo de wrapper. No existe configuración global ni un proveedor propio del componente.
Identidad y documentación
Sección titulada «Identidad y documentación»Todos estos métodos carecen de argumentos adicionales. El constructor captura el nombre, el módulo y el docstring; esos tres valores son una instantánea de construcción.
getCallable(self) -> callable devuelve exactamente el objetivo original, conservando la identidad y el binding de un método ligado.
getName(self) -> str devuelve el __name__ capturado, no __qualname__.
getModuleName(self) -> str devuelve el __module__ capturado al construir el wrapper.
getModuleWithCallableName(self) -> str concatena módulo + "." + nombre. Para métodos no incorpora el nombre de su clase ni reconstruye una ruta importable completa; no lo uses como identificador global único de todos los handlers.
getDocstring(self) -> str devuelve el __doc__ original sin normalizar su indentación, o "" si no tenía docstring.
from orionis.introspection import ReflectionCallable
def label(value: str) -> str: """Format a label.""" return value.upper()
reflected = ReflectionCallable(label)assert reflected.getCallable() is labelassert reflected.getName() == "label"assert reflected.getModuleName() == label.__module__assert reflected.getModuleWithCallableName() == f"{label.__module__}.label"assert reflected.getDocstring() == "Format a label."
class Catalog: def update(self) -> None: pass
method = ReflectionCallable(Catalog().update)assert method.getModuleWithCallableName() == f"{Catalog.__module__}.update"assert method.getDocstring() == ""Archivo y código fuente
Sección titulada «Archivo y código fuente»getSourceCode(self) -> str obtiene inspect.getsource(fn) y cachea el texto, incluida su indentación. Convierte un OSError de recuperación en AttributeError, conservando la causa. Otros errores de inspección pueden propagarse.
getFile(self) -> str devuelve y cachea inspect.getfile(fn). Es la ruta que Python asocia al código; no realiza una normalización adicional ni garantiza que un archivo sintético exista. Propaga errores de inspección, como TypeError cuando no puede determinarse un archivo.
Guarda este ejemplo en un archivo .py y ejecútalo. Una consola interactiva o código generado dinámicamente puede tener una firma legible y aun así no disponer de fuente recuperable.
from orionis.introspection import ReflectionCallable
def calculate(value: int) -> int: return value * 2
reflected = ReflectionCallable(calculate)assert "def calculate(" in reflected.getSourceCode()assert reflected.getFile() == calculate.__code__.co_filenameassert reflected.getSourceCode() == reflected.getSourceCode()Firma Python y dependencias Orionis
Sección titulada «Firma Python y dependencias Orionis»getSignature(self) -> inspect.Signature devuelve y cachea inspect.signature(fn): parámetros, tipos, valores predeterminados y anotación de retorno. No elimina parámetros de una función no ligada manualmente; Python aplica el binding cuando el objetivo es un método ligado. Propaga errores de inspect.signature().
getDependencies(self) -> Signature delega en ReflectDependencies(fn).callableSignature() y cachea el resultado. Signature aquí es la entidad de Orionis, no inspect.Signature. Clasifica los argumentos resueltos/no resueltos y su orden; no construye servicios, valida payloads ni invoca el objetivo.
import inspectfrom orionis.introspection import ReflectionCallable
class TaxService: pass
def invoice(tax: TaxService, subtotal: float, *, currency: str = "USD") -> float: return subtotal
reflected = ReflectionCallable(invoice)python_signature = reflected.getSignature()assert isinstance(python_signature, inspect.Signature)assert python_signature.parameters["currency"].default == "USD"assert python_signature.return_annotation is float
dependencies = reflected.getDependencies()assert dependencies.getResolved()["tax"].type is TaxServiceassert dependencies.getResolved()["currency"].default == "USD"assert "subtotal" in dependencies.getUnresolved()assert list(dependencies.getAllOrdered()) == ["tax", "subtotal", "currency"]assert reflected.getSignature() is python_signatureassert reflected.getDependencies() is dependenciesLas referencias futuras, valores predeterminados, schemas, parámetros variádicos y errores se detallan en Dependencias y firmas. Para ejecutar un servicio con estos argumentos utiliza el contenedor.
Protocolo de caché y limpieza
Sección titulada «Protocolo de caché y limpieza»El wrapper tiene una caché compartida entre sus cálculos y los valores guardados con corchetes. Los argumentos key son claves de esa caché, no nombres de atributos de la función. Los métodos especiales no añaden validación sobre el diccionario subyacente.
__getitem__(self, key: str) -> object | None devuelve el valor almacenado o None si falta la clave; no lanza KeyError por ausencia.
__setitem__(self, key: str, value: object) -> None guarda cualquier valor en la caché del wrapper. No modifica la función.
__contains__(self, key: str) -> bool comprueba si existe una clave, aunque su valor almacenado sea None.
__delitem__(self, key: str) -> None elimina una entrada si existe; eliminar una clave ausente no produce error.
clearCache(self) -> None elimina todas las entradas, tanto calculadas como agregadas por la aplicación. Las lecturas diferidas se recalculan después; no modifica el objetivo, no renueva nombre/módulo/docstring capturados ni vacía las cachés LRU internas de ReflectDependencies.
from orionis.introspection import ReflectionCallable
def run(value: int = 1) -> int: """Original documentation.""" return value
reflected = ReflectionCallable(run)reflected["app.note"] = Noneassert "app.note" in reflectedassert reflected["app.note"] is Noneassert reflected["app.missing"] is Nonedel reflected["app.note"]del reflected["app.missing"]assert "app.note" not in reflected
before = reflected.getSignature()run.__name__ = "renamed"run.__doc__ = "Changed documentation."reflected["app.note"] = "reviewed"reflected.clearCache()assert "app.note" not in reflectedassert reflected.getSignature() is not beforeassert reflected.getName() == "run"assert reflected.getDocstring() == "Original documentation."fresh = ReflectionCallable(run)assert fresh.getName() == "renamed"assert fresh.getDocstring() == "Changed documentation."Usa claves propias como app.note. Las claves source_code, file, signature y dependencies pertenecen a los cálculos internos: sobrescribirlas puede sustituir su resultado por un valor incompatible. Los mapas y entidades devueltos por el análisis de dependencias pueden compartir referencias; consulta sus reglas de caché antes de modificarlos.
Integración e inyección de dependencias
Sección titulada «Integración e inyección de dependencias»No existe un objetivo implícito que el contenedor deba inspeccionar. Construye ReflectionCallable(handler) cuando conozcas el handler o inyecta un servicio propio que lo reciba explícitamente:
from orionis.http import JSONResponsefrom orionis.introspection import ReflectionCallable
class HandlerCatalog: def describe(self, handler) -> dict: reflected = ReflectionCallable(handler) return { "name": reflected.getName(), "parameters": list(reflected.getSignature().parameters), }
class DiagnosticsController: def __init__(self, catalog: HandlerCatalog) -> None: self.catalog = catalog
async def __call__(self) -> JSONResponse: return JSONResponse(self.catalog.describe(self.__call__))El contenedor puede construir HandlerCatalog e inyectarlo en el controlador. Si deliberadamente necesitas inyectar un wrapper preparado, registra esa instancia con app.instance(ReflectionCallable, wrapper); eso liga un objetivo concreto y comparte su caché según ese binding. El contrato IReflectionCallable requiere un binding explícito si eliges utilizarlo. No hay registro nativo que determine qué función debe envolver.