ReflectionCallable
ReflectionCallable wraps a function without executing it. Use it to build a handler catalog, read arguments or generate diagnostic information. It has ten public methods and five special methods of its own, including its constructor.
Construction and imports
Section titled “Construction and imports”__init__(self, fn: callable) -> None takes a required target and returns None when initializing the wrapper. Import the class from orionis.introspection; its defining module is orionis.introspection.callables.reflection.
Accepts Python functions, lambdas and bound methods. It also accepts a callable object with __code__, but then reads __name__, __module__ and __doc__: those attributes must exist. Implementing __call__ alone is insufficient. An ordinary class without __code__, a builtin function or a callable without __code__ raises TypeError; an object accepted by that filter but lacking the required metadata can raise 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("The target should not have been accepted")Reflection.callable(fn) constructs the same wrapper type. The component has no global configuration or provider of its own.
Identity and documentation
Section titled “Identity and documentation”These methods take no additional arguments. The constructor captures the name, module and docstring; those three values are construction-time snapshots.
getCallable(self) -> callable returns the exact original target, retaining identity and the binding of a bound method.
getName(self) -> str returns the captured __name__, not __qualname__.
getModuleName(self) -> str returns __module__ captured at construction.
getModuleWithCallableName(self) -> str concatenates module + "." + name. For methods it does not include the class name or reconstruct a complete import path; do not treat it as a globally unique identifier for every handler.
getDocstring(self) -> str returns the original __doc__ without normalizing indentation, or "" if there was no 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() == ""File and source code
Section titled “File and source code”getSourceCode(self) -> str reads inspect.getsource(fn) and caches the text, including indentation. Converts a retrieval OSError into AttributeError, preserving the cause. Other inspection errors can propagate.
getFile(self) -> str returns and caches inspect.getfile(fn). This is the path Python associates with the code; the method does not normalize it further or guarantee a synthetic file exists. It propagates inspection errors, such as TypeError when a file cannot be determined.
Save and execute this example in a .py file. Interactive sessions or dynamically generated code may have readable signatures without retrievable source.
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()Python signature and Orionis dependencies
Section titled “Python signature and Orionis dependencies”getSignature(self) -> inspect.Signature returns and caches inspect.signature(fn): parameters, types, defaults and return annotation. It does not manually remove parameters from an unbound function; Python applies binding when the target is a bound method. Propagates inspect.signature() errors.
getDependencies(self) -> Signature delegates to ReflectDependencies(fn).callableSignature() and caches the result. Signature here is the Orionis entity, not inspect.Signature. It classifies resolved/unresolved arguments and their order; it does not construct services, validate payloads or invoke the target.
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 dependenciesForward references, defaults, schemas, variadic parameters and errors are detailed in Dependencies and signatures. Use the container to execute a service with those arguments.
Cache protocol and clearing
Section titled “Cache protocol and clearing”The wrapper has a cache shared by its calculations and values stored through brackets. key arguments address that cache, not function attributes. The special methods add no validation beyond the underlying dictionary.
__getitem__(self, key: str) -> object | None returns the stored value or None for an absent key; absence does not raise KeyError.
__setitem__(self, key: str, value: object) -> None stores any value in the wrapper cache. Does not modify the function.
__contains__(self, key: str) -> bool checks key existence even when its stored value is None.
__delitem__(self, key: str) -> None removes an entry if present; deleting an absent key is harmless.
clearCache(self) -> None removes every entry, whether calculated or added by the application. Deferred reads recompute afterward; it does not modify the target, refresh the captured name/module/docstring or clear ReflectDependencies internal LRU caches.
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."Use application keys such as app.note. The source_code, file, signature and dependencies keys belong to internal calculations: overwriting them can replace a result with an incompatible value. Maps and entities returned by dependency analysis can share references; check their cache rules before modifying them.
Integration and dependency injection
Section titled “Integration and dependency injection”There is no implicit target for the container to inspect. Construct ReflectionCallable(handler) where the handler is known, or inject an application service that receives it explicitly:
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__))The container can construct HandlerCatalog and inject it into the controller. To deliberately inject a prepared wrapper, bind that instance with app.instance(ReflectionCallable, wrapper); this binds a particular target and shares its cache according to the binding. IReflectionCallable requires an explicit binding if you choose that contract. No native registration determines which function to wrap.