Files
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

380 lines
13 KiB
Python

"""Mode handler registry for v2.
Central registry mapping Mode enum values to their handler implementations.
Supports lazy loading, dynamic registration, and queryable API.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from instructor.v2.core.mode import Mode
from instructor.v2.core.providers import Provider
from instructor.v2.core.provider_specs import HANDLER_SPECS, PROVIDER_SPECS
from instructor.v2.core.protocols import (
AsyncStreamExtractor,
MessageConverter,
ReaskHandler,
RequestHandler,
ResponseParser,
StreamExtractor,
TemplateHandler,
)
_HANDLER_ATTRS = {
"request": "request_handler",
"reask": "reask_handler",
"response": "response_parser",
"stream": "stream_extractor",
"stream_async": "stream_extractor_async",
"message": "message_converter",
"template": "template_handler",
}
def normalize_mode(provider: Provider, mode: Mode) -> Mode:
"""Normalize backwards-compatible provider-specific aliases to core modes."""
spec = PROVIDER_SPECS.get(provider)
normalized = spec.legacy_modes.get(mode, mode) if spec is not None else mode
if normalized is not mode:
Mode.warn_deprecated_mode(mode)
return normalized
@dataclass
class ModeHandlers:
"""Collection of handlers for a specific mode."""
request_handler: RequestHandler
reask_handler: ReaskHandler
response_parser: ResponseParser
stream_extractor: StreamExtractor | None = None
stream_extractor_async: AsyncStreamExtractor | None = None
message_converter: MessageConverter | None = None
template_handler: TemplateHandler | None = None
class ModeRegistry:
"""Central registry for mode handlers.
Maps (Provider, Mode) tuples to their handler implementations.
Supports lazy loading and dynamic registration.
Example:
>>> registry.register(
... provider=Provider.ANTHROPIC,
... mode=Mode.TOOLS,
... request_handler=handle_request,
... reask_handler=handle_reask,
... response_parser=parse_response,
... )
>>> # Preferred: get all handlers at once
>>> handlers = registry.get_handlers(Provider.ANTHROPIC, Mode.TOOLS)
>>> handlers.request_handler(...)
>>> handlers.reask_handler(...)
>>> handlers.response_parser(...)
"""
def __init__(self) -> None:
"""Initialize empty registry."""
self._handlers: dict[tuple[Provider, Mode], ModeHandlers] = {}
self._lazy_loaders: dict[tuple[Provider, Mode], Callable[[], ModeHandlers]] = {}
def register(
self,
provider: Provider,
mode: Mode,
request_handler: RequestHandler,
reask_handler: ReaskHandler,
response_parser: ResponseParser,
stream_extractor: StreamExtractor | None = None,
stream_extractor_async: AsyncStreamExtractor | None = None,
message_converter: MessageConverter | None = None,
template_handler: TemplateHandler | None = None,
) -> None:
"""Register handlers for a mode.
Args:
provider: Provider enum value
mode: Mode enum value
request_handler: Handler to prepare request kwargs
reask_handler: Handler to handle validation failures
response_parser: Handler to parse responses
stream_extractor: Optional handler to extract streaming JSON chunks
stream_extractor_async: Optional handler to extract async streaming JSON
message_converter: Optional handler to convert multimodal messages
template_handler: Optional handler to apply template context
Raises:
ConfigurationError: If mode is already registered with different handlers
"""
mode_key = (provider, mode)
if mode_key in self._lazy_loaders:
self._lazy_loaders.pop(mode_key, None)
self._handlers[mode_key] = ModeHandlers(
request_handler=request_handler,
reask_handler=reask_handler,
response_parser=response_parser,
stream_extractor=stream_extractor,
stream_extractor_async=stream_extractor_async,
message_converter=message_converter,
template_handler=template_handler,
)
def register_lazy(
self,
provider: Provider,
mode: Mode,
loader: Callable[[], ModeHandlers],
) -> None:
"""Register a lazy loader for a mode.
The loader will be called on first access to load handlers.
Args:
provider: Provider enum value
mode: Mode enum value
loader: Callable that returns ModeHandlers when invoked
Raises:
ConfigurationError: If mode is already registered
"""
from instructor.v2.core.errors import ConfigurationError
mode_key = (provider, mode)
if mode_key in self._handlers or mode_key in self._lazy_loaders:
raise ConfigurationError(f"Mode {mode_key} is already registered")
self._lazy_loaders[mode_key] = loader
def get_handlers(self, provider: Provider, mode: Mode) -> ModeHandlers:
"""Get all handlers for a mode.
This is the preferred method for retrieving handlers. It performs
a single registry lookup and returns all handlers at once, which is
more efficient than calling get_handler() multiple times.
Args:
provider: Provider enum value
mode: Mode enum value (provider-specific modes will be converted)
Returns:
ModeHandlers with all handler functions (request_handler,
reask_handler, response_parser)
Raises:
KeyError: If mode is not registered
Example:
>>> handlers = registry.get_handlers(Provider.ANTHROPIC, Mode.TOOLS)
>>> handlers.request_handler(...)
>>> handlers.reask_handler(...)
>>> handlers.response_parser(...)
"""
# Convert provider-specific modes to generic modes
normalized_mode = normalize_mode(provider, mode)
mode_key = (provider, normalized_mode)
# Check if already loaded
if mode_key in self._handlers:
return self._handlers[mode_key]
# Try lazy loading
if mode_key in self._lazy_loaders:
loader = self._lazy_loaders.pop(mode_key)
handlers = loader()
self._handlers[mode_key] = handlers
return handlers
raise KeyError(
f"Mode {mode_key} is not registered. "
f"Available modes: {list(self._handlers.keys())}"
)
def get_handler(
self,
provider: Provider,
mode: Mode,
handler_type: str = "request",
) -> (
RequestHandler
| ReaskHandler
| ResponseParser
| StreamExtractor
| AsyncStreamExtractor
| MessageConverter
| TemplateHandler
):
"""Get a specific handler for a mode.
This is a convenience method that internally calls get_handlers().
For better performance when you need multiple handlers, use
get_handlers() instead and access handlers via the returned object.
Args:
provider: Provider enum value
mode: Mode enum value (provider-specific modes will be converted)
handler_type: One of 'request', 'reask', 'response', 'stream',
'stream_async', 'message', 'template'
Returns:
The requested handler function
Raises:
KeyError: If mode is not registered
ValueError: If handler_type is invalid
Example:
>>> # Prefer this when you need multiple handlers:
>>> handlers = registry.get_handlers(Provider.ANTHROPIC, Mode.TOOLS)
>>> handlers.request_handler(...)
>>> handlers.reask_handler(...)
>>> # Or use this convenience method for a single handler:
>>> handler = registry.get_handler(Provider.ANTHROPIC, Mode.TOOLS, "request")
"""
# get_handlers already handles normalization
handlers = self.get_handlers(provider, mode)
if handler_type not in _HANDLER_ATTRS:
raise ValueError(
f"Invalid handler_type: {handler_type}. "
"Must be 'request', 'reask', 'response', 'stream', "
"'stream_async', 'message', or 'template'"
)
attr_name = _HANDLER_ATTRS[handler_type]
handler = getattr(handlers, attr_name)
if handler is None:
raise KeyError(f"No {attr_name} registered for {provider}, {mode}")
return handler
def is_registered(self, provider: Provider, mode: Mode) -> bool:
"""Check if a mode is registered.
Args:
provider: Provider enum value
mode: Mode enum value (provider-specific modes will be converted)
Returns:
True if mode is registered (eagerly or lazily)
"""
# Convert provider-specific modes to generic modes
normalized_mode = normalize_mode(provider, mode)
mode_key = (provider, normalized_mode)
return mode_key in self._handlers or mode_key in self._lazy_loaders
def get_modes_for_provider(self, provider: Provider) -> list[Mode]:
"""Get all registered modes for a provider.
Args:
provider: Provider enum value
Returns:
List of Mode values supported by this provider
"""
modes = []
for p, mt in self._handlers.keys():
if p == provider:
modes.append(mt)
for p, mt in self._lazy_loaders.keys():
if p == provider:
modes.append(mt)
return sorted(set(modes), key=lambda m: m.value)
def get_providers_for_mode(self, mode: Mode) -> list[Provider]:
"""Get all providers that support a mode.
Args:
mode: Mode enum value
Returns:
List of Provider values that support this mode
"""
providers = []
for p, mt in self._handlers.keys():
if mt == mode:
providers.append(p)
for p, mt in self._lazy_loaders.keys():
if mt == mode:
providers.append(p)
return sorted(set(providers), key=lambda p: p.value)
def list_modes(self) -> list[tuple[Provider, Mode]]:
"""List all registered modes.
Returns:
List of (Provider, Mode) tuples
"""
all_modes = set(self._handlers.keys()) | set(self._lazy_loaders.keys())
return sorted(all_modes, key=lambda m: (m[0].value, m[1].value))
def get_handler_class(self, provider: Provider, mode: Mode) -> type | None:
"""Get the handler class for a mode.
This method looks up the handler class that was registered for the given
provider and mode. It's useful for testing and introspection.
Args:
provider: Provider enum value
mode: Mode enum value (provider-specific modes will be converted)
Returns:
Handler class type if found, None otherwise
"""
# Convert provider-specific modes to generic modes
normalized_mode = normalize_mode(provider, mode)
mode_key = (provider, normalized_mode)
# Check if handlers are registered
if mode_key not in self._handlers and mode_key not in self._lazy_loaders:
return None
# Try to get the handler class from the decorator registry
# The decorator stores the handler class, but we need to find it
# by looking at what was registered
handlers = self.get_handlers(provider, mode)
# The handlers are bound methods, so we need to get the class
# We can inspect the handler's __self__ to get the instance, then __class__
self_obj = getattr(handlers.request_handler, "__self__", None)
if self_obj is not None:
return self_obj.__class__
# If it's a function, we can't easily get the class
# Return None to indicate we couldn't determine it
return None
# Global registry instance
mode_registry = ModeRegistry()
_DEFAULT_HANDLER_SPECS = HANDLER_SPECS
def _lazy_handler_loader(
module_path: str, provider: Provider, mode: Mode
) -> ModeHandlers:
__import__(module_path, fromlist=["__name__"])
return mode_registry._handlers[(provider, mode)]
def _register_default_lazy_handlers() -> None:
"""Register built-in handlers without importing provider modules."""
for provider, (module_path, modes) in _DEFAULT_HANDLER_SPECS.items():
for mode in modes:
if mode_registry.is_registered(provider, mode):
continue
mode_registry.register_lazy(
provider,
mode,
lambda mp=module_path, p=provider, m=mode: _lazy_handler_loader(
mp, p, m
),
)
_register_default_lazy_handlers()