Skip to content

Reflection: classification and selection

Reflection groups 31 static methods: five wrapper factories and 26 predicates. It does not automatically analyze an object through Reflection(obj), resolve dependencies or require a booted application.

from orionis.introspection import Reflection
def handler(value: int) -> int:
return value * 2
assert Reflection.isFunction(handler)
assert Reflection.callable(handler).getCallable() is handler

All arguments below are required and used directly. Any and Callable are signature annotations; IReflection* return types are contracts implemented by the concrete wrappers exported by the package.

instance(instance: Any) -> IReflectionInstance creates a ReflectionInstance over an existing object. It does not construct the target. The wrapper rejects classes and instances from builtins, abc and __main__ according to its rules.

abstract(abstract: type) -> IReflectionAbstract creates a ReflectionAbstract for a class recognized as abstract by inspect.isabstract(). It does not instantiate the class.

concrete(concrete: type) -> IReflectionConcrete creates a ReflectionConcrete, whose constructor validates input with isConcreteClass(). It does not create an instance of the reflected service.

module(module: str) -> IReflectionModule creates a ReflectionModule from an importable name such as "app.services.catalog". It imports the module; it does not accept a file path or a ModuleType object.

callable(fn: Callable) -> IReflectionCallable creates a ReflectionCallable over a function, method or lambda accepted by its constructor. It does not execute the function.

from abc import ABC, abstractmethod
from orionis.introspection import Reflection
from orionis.support.types import StdClass
class CatalogSource(ABC):
@abstractmethod
def load(self) -> list:
pass
class CatalogService:
def total(self, prices: list[int]) -> int:
return sum(prices)
assert Reflection.abstract(CatalogSource).getClass() is CatalogSource
assert Reflection.concrete(CatalogService).getClass() is CatalogService
record = StdClass(name="Ada")
assert Reflection.instance(record).getInstance() is record
assert Reflection.module("orionis.metadata.framework").getModule().__name__ == (
"orionis.metadata.framework"
)
assert Reflection.callable(CatalogService.total).getCallable() is CatalogService.total

Factories do not share a global wrapper registry: each call constructs a wrapper. They propagate the corresponding constructor’s errors. See those references for target validation, available members and source retrieval.

Each predicate takes obj: Any and returns bool; it does not call the inspected object.

isFunction(obj: Any) -> bool uses inspect.isfunction. Recognizes Python functions, lambdas and async def functions, but not builtin functions.

isMethod(obj: Any) -> bool uses inspect.ismethod. A method bound to an instance differs from the function obtained through its class.

isBuiltIn(obj: Any) -> bool uses inspect.isbuiltin: recognizes builtin functions or methods, not every class or instance in the builtins module.

isRoutine(obj: Any) -> bool uses inspect.isroutine for Python or builtin functions and methods. It does not mean “any callable object”.

isClass(obj: Any) -> bool uses inspect.isclass; includes builtin classes such as int.

isInstance(obj: Any) -> bool requires an object that is not a class and whose type does not belong to builtins or abc. It does not perform the full ReflectionInstance validation: an instance of a __main__ class can return True here and still be rejected by that wrapper.

from orionis.introspection import Reflection
class Action:
def run(self) -> None:
pass
action = Action()
assert Reflection.isFunction(Action.run)
assert Reflection.isMethod(action.run)
assert Reflection.isBuiltIn(len)
assert not Reflection.isBuiltIn(int)
assert Reflection.isRoutine(len)
assert not Reflection.isRoutine(Action)
assert Reflection.isClass(Action) and Reflection.isClass(int)
assert not Reflection.isClass(action)
assert Reflection.isInstance(action)
assert not Reflection.isInstance(42)

isAbstract(obj: Any) -> bool delegates to inspect.isabstract; inheriting from ABC without outstanding abstract methods does not necessarily make a class abstract.

isConcreteClass(obj: Any) -> bool requires a class with __init__. It excludes objects recognized as builtin by isBuiltIn, abstract classes, generics, Protocol subclasses, constructs recognized by isTypingConstruct, and classes with ABC among their direct bases.

This filter implements Orionis’s particular rules, not a general guarantee that a class is instantiable or user-defined. int, typing.Any and typing.Protocol itself can return True: builtin detection does not classify classes, and isProtocol excludes Protocol itself.

isGeneric(obj: Any) -> bool returns True if typing.get_origin(obj) is not None, the object has __origin__, or it is a TypeVar. A custom object with __origin__ also qualifies; a standard generic declaration is not required.

isProtocol(obj: Any) -> bool requires a class that subclasses typing.Protocol, excluding the Protocol object itself.

isTypingConstruct(obj: Any) -> bool compares type(obj).__name__ against an internal list: Any, Union, Optional, List, Dict, Set, Tuple, Callable, TypeVar, Generic, Protocol, Literal, Final, TypedDict, NewType, Deque, DefaultDict, Counter and ChainMap. It does not exhaustively recognize the typing module; typing.Any and typing.Literal[1] return False in the verified runtime, while a TypeVar returns True.

from abc import ABC, abstractmethod
from typing import Any, Literal, Protocol, TypeVar
from orionis.introspection import Reflection
class Source(ABC):
@abstractmethod
def read(self) -> str:
pass
class EmptyBase(ABC):
pass
class Readable(Protocol):
def read(self) -> str: ...
T = TypeVar("T")
assert Reflection.isAbstract(Source)
assert not Reflection.isAbstract(EmptyBase)
assert not Reflection.isConcreteClass(EmptyBase)
assert Reflection.isConcreteClass(int)
assert Reflection.isConcreteClass(Any)
assert Reflection.isGeneric(list[int]) and Reflection.isGeneric(T)
assert not Reflection.isGeneric(int)
assert Reflection.isProtocol(Readable)
assert not Reflection.isProtocol(Protocol)
assert Reflection.isTypingConstruct(T)
assert not Reflection.isTypingConstruct(Any)
assert not Reflection.isTypingConstruct(Literal[1])

These checks distinguish the function from its result. They do not await coroutines or consume generators.

isCoroutineFunction(obj: Any) -> bool delegates to inspect.iscoroutinefunction: checks the coroutine function.

isCoroutine(obj: Any) -> bool delegates to inspect.iscoroutine: checks an already-created coroutine object.

isAwaitable(obj: Any) -> bool delegates to inspect.isawaitable; an awaitable need not be a native coroutine.

isGeneratorFunction(obj: Any) -> bool uses inspect.isgeneratorfunction for functions that generate through yield.

isGenerator(obj: Any) -> bool uses inspect.isgenerator for the created generator object.

isAsyncGenFunction(obj: Any) -> bool uses inspect.isasyncgenfunction for asynchronous generator functions.

isAsyncGen(obj: Any) -> bool uses inspect.isasyncgen for asynchronous generator objects. An async def function containing yield is not an ordinary coroutine function.

from orionis.introspection import Reflection
async def fetch() -> int:
return 1
def rows():
yield 1
async def events():
yield "ready"
class Immediate:
def __await__(self):
if False:
yield
return 1
assert Reflection.isCoroutineFunction(fetch)
coroutine = fetch()
try:
assert Reflection.isCoroutine(coroutine)
assert Reflection.isAwaitable(coroutine)
assert not Reflection.isCoroutineFunction(coroutine)
finally:
coroutine.close()
generator = rows()
try:
assert Reflection.isGeneratorFunction(rows)
assert Reflection.isGenerator(generator)
finally:
generator.close()
async_generator = events()
assert Reflection.isAsyncGenFunction(events)
assert Reflection.isAsyncGen(async_generator)
assert not Reflection.isCoroutineFunction(events)
# The generator has not started and holds no active block resources.
assert Reflection.isAwaitable(Immediate())
assert not Reflection.isCoroutine(Immediate())

When creating coroutine objects solely to inspect them, close or execute them correctly. In an application you can analyze the function before invoking it and avoid creating results you will not consume.

isCode(obj: Any) -> bool uses inspect.iscode for code objects such as function.__code__.

isModule(obj: Any) -> bool uses inspect.ismodule. A module object differs from the string containing its name.

isFrame(obj: Any) -> bool uses inspect.isframe for execution frames.

isTraceback(obj: Any) -> bool uses inspect.istraceback for an exception’s traceback.

import inspect
from orionis.introspection import Reflection
def work() -> None:
pass
assert Reflection.isCode(work.__code__)
assert not Reflection.isCode(work)
assert Reflection.isModule(inspect)
assert not Reflection.isModule("inspect")
frame = inspect.currentframe()
try:
assert Reflection.isFrame(frame)
finally:
del frame
try:
raise ValueError("invalid example")
except ValueError as error:
assert Reflection.isTraceback(error.__traceback__)
assert not Reflection.isTraceback(None)

These predicates inspect the descriptor obtained through the class, which may differ from the value produced by accessing the attribute on an instance.

isDataDescriptor(obj: Any) -> bool uses inspect.isdatadescriptor; a Python property is an example.

isGetSetDescriptor(obj: Any) -> bool uses inspect.isgetsetdescriptor for interpreter-recognized get/set descriptors.

isMemberDescriptor(obj: Any) -> bool uses inspect.ismemberdescriptor; a __slots__ attribute is an example in the Orionis runtime.

isMethodDescriptor(obj: Any) -> bool uses inspect.ismethoddescriptor, such as the str.upper descriptor.

import types
from orionis.introspection import Reflection
class Slotted:
__slots__ = ("name",)
class Product:
@property
def price(self) -> int:
return 5
assert Reflection.isDataDescriptor(Product.price)
assert Reflection.isGetSetDescriptor(types.FunctionType.__dict__["__dict__"])
assert Reflection.isMemberDescriptor(Slotted.__dict__["name"])
assert Reflection.isMethodDescriptor(str.upper)
assert not Reflection.isMethodDescriptor(Product.price)

You do not need to inject Reflection to call its static methods. You can inject an application tool that decides what to inspect:

from orionis.http import JSONResponse
from orionis.introspection import Reflection
class HandlerInfo:
def describe(self, handler) -> dict:
reflected = Reflection.callable(handler)
return {
"name": reflected.getName(),
"async": Reflection.isCoroutineFunction(handler),
"parameters": list(reflected.getSignature().parameters),
}
class StatusController:
def __init__(self, info: HandlerInfo) -> None:
self.info = info
async def __call__(self) -> JSONResponse:
return JSONResponse(self.info.describe(self.__call__))

The container constructs HandlerInfo and the controller like any other concrete service requiring no additional configuration. Reflection still receives the handler explicitly. These utilities have no classification cache or request state; result caches belong to the wrappers and dependency analyzer.