85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
264 lines
10 KiB
Python
264 lines
10 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
"""Utility helpers for dynamic component initialization within the trainer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import inspect
|
|
from typing import Any, Callable, Dict, Optional, TypeVar, Union, cast, overload
|
|
|
|
OptionalDefaults = Dict[str, Callable[[], Any] | Any]
|
|
T = TypeVar("T")
|
|
|
|
|
|
def load_class(path: str) -> type[Any]:
|
|
"""Load a class from its fully qualified import path."""
|
|
module_name, class_name = path.rsplit(".", 1)
|
|
module = importlib.import_module(module_name)
|
|
return getattr(module, class_name)
|
|
|
|
|
|
def instantiate_component(
|
|
cls: type[Any],
|
|
provided_kwargs: Optional[Dict[str, Any]] = None,
|
|
optional_defaults: Optional[OptionalDefaults] = None,
|
|
) -> Any:
|
|
"""Instantiate `cls`, filling optional kwargs when the constructor accepts them."""
|
|
kwargs = dict(provided_kwargs or {})
|
|
if optional_defaults:
|
|
signature = inspect.signature(cls.__init__)
|
|
for name, value in optional_defaults.items():
|
|
if name in kwargs or name not in signature.parameters:
|
|
continue
|
|
kwargs[name] = value() if callable(value) else value
|
|
return cls(**kwargs)
|
|
|
|
|
|
def instantiate_from_spec(
|
|
spec: Union[str, Dict[str, Any]],
|
|
*,
|
|
spec_name: str,
|
|
optional_defaults: Optional[OptionalDefaults] = None,
|
|
dict_requires_type: bool = True,
|
|
dict_default_cls: type[Any] | None = None,
|
|
registry: Optional[Dict[str, str]] = None,
|
|
) -> Any:
|
|
"""Instantiate a component from a string or dict spec."""
|
|
if isinstance(spec, str):
|
|
type_path = registry.get(spec, spec) if registry else spec
|
|
cls = load_class(type_path)
|
|
return instantiate_component(cls, optional_defaults=optional_defaults)
|
|
|
|
if isinstance(spec, dict): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
spec_conf = dict(spec)
|
|
type_path = spec_conf.pop("type", None)
|
|
if type_path is None and registry and "name" in spec_conf:
|
|
type_path = registry.get(spec_conf.pop("name"))
|
|
elif registry and type_path is not None:
|
|
type_path = registry.get(type_path, type_path)
|
|
if type_path is None:
|
|
if dict_requires_type:
|
|
raise ValueError(f"{spec_name} dict must have a 'type' key with the class full name")
|
|
if dict_default_cls is None:
|
|
raise ValueError(f"{spec_name} dict missing 'type' and no default class provided")
|
|
cls = dict_default_cls
|
|
else:
|
|
cls = load_class(type_path)
|
|
return instantiate_component(cls, spec_conf, optional_defaults)
|
|
|
|
raise TypeError(f"{spec_name} spec must be a string or dict (got {type(spec)}).")
|
|
|
|
|
|
def _ensure_expected_type(
|
|
instance: Any,
|
|
expected_type: type[T],
|
|
spec_name: str,
|
|
type_error_fmt: str | None,
|
|
) -> T:
|
|
if not isinstance(instance, expected_type):
|
|
type_name = str(type(instance)) # type: ignore
|
|
if type_error_fmt:
|
|
raise TypeError(type_error_fmt.format(type_name=type_name, expected_type=expected_type.__name__))
|
|
raise TypeError(f"{spec_name} factory returned {type_name}, which is not a {expected_type.__name__} subclass.")
|
|
return instance
|
|
|
|
|
|
@overload
|
|
def build_component(
|
|
spec: Union[T, str, Dict[str, Any], type[T], Callable[[], T], None],
|
|
*,
|
|
expected_type: type[T],
|
|
spec_name: str,
|
|
default_factory: Callable[[], T],
|
|
allow_none: bool = ...,
|
|
optional_defaults: Optional[OptionalDefaults] = ...,
|
|
dict_requires_type: bool = ...,
|
|
dict_default_cls: type[T] | None = ...,
|
|
type_error_fmt: str | None = ...,
|
|
invalid_spec_error_fmt: str | None = ...,
|
|
registry: Optional[Dict[str, str]] = ...,
|
|
) -> T: ...
|
|
|
|
|
|
@overload
|
|
def build_component(
|
|
spec: Union[T, str, Dict[str, Any], type[T], Callable[[], T], None],
|
|
*,
|
|
expected_type: type[T],
|
|
spec_name: str,
|
|
default_factory: None = ...,
|
|
allow_none: bool,
|
|
optional_defaults: Optional[OptionalDefaults] = ...,
|
|
dict_requires_type: bool = ...,
|
|
dict_default_cls: type[T] | None = ...,
|
|
type_error_fmt: str | None = ...,
|
|
invalid_spec_error_fmt: str | None = ...,
|
|
registry: Optional[Dict[str, str]] = ...,
|
|
) -> T | None: ...
|
|
|
|
|
|
@overload
|
|
def build_component(
|
|
spec: Union[T, str, Dict[str, Any], type[T], Callable[[], T], None],
|
|
*,
|
|
expected_type: type[T],
|
|
spec_name: str,
|
|
default_factory: None = ...,
|
|
allow_none: bool = ...,
|
|
optional_defaults: Optional[OptionalDefaults] = ...,
|
|
dict_requires_type: bool = ...,
|
|
dict_default_cls: type[T] | None = ...,
|
|
type_error_fmt: str | None = ...,
|
|
invalid_spec_error_fmt: str | None = ...,
|
|
registry: Optional[Dict[str, str]] = ...,
|
|
) -> T | None: ...
|
|
|
|
|
|
def build_component(
|
|
spec: Union[T, str, Dict[str, Any], type[T], Callable[[], T], None],
|
|
*,
|
|
expected_type: type[T],
|
|
spec_name: str,
|
|
default_factory: Callable[[], T] | None = None,
|
|
allow_none: bool = False,
|
|
optional_defaults: Optional[OptionalDefaults] = None,
|
|
dict_requires_type: bool = True,
|
|
dict_default_cls: type[T] | None = None,
|
|
type_error_fmt: str | None = None,
|
|
invalid_spec_error_fmt: str | None = None,
|
|
registry: Optional[Dict[str, str]] = None,
|
|
) -> T | None:
|
|
"""Build and return a component instance from a flexible specification.
|
|
|
|
This function provides a flexible way to create component instances from various
|
|
input formats including direct instances, class types, factory functions, import
|
|
paths, or configuration dictionaries.
|
|
|
|
Args:
|
|
spec: The component specification. Can be:
|
|
- An instance of expected_type (returned as-is)
|
|
- A string import path (e.g., 'module.Class') or registry key
|
|
- A dict with 'type' key (import path or registry key) and constructor kwargs
|
|
- A class type (will be instantiated)
|
|
- A factory function (will be called)
|
|
- None (uses default_factory or returns None if allow_none=True)
|
|
expected_type: The type that the resulting instance must be or inherit from.
|
|
spec_name: Descriptive name for the spec, used in error messages.
|
|
default_factory: Optional factory function called when spec is None.
|
|
allow_none: If True, allows None to be returned when spec is None and
|
|
no default_factory is provided.
|
|
optional_defaults: Dict mapping parameter names to default values or factory
|
|
functions that will be injected if the constructor accepts them.
|
|
dict_requires_type: If True, dict specs must include a 'type' key.
|
|
dict_default_cls: Default class to use for dict specs without a 'type' key
|
|
(only used when dict_requires_type=False).
|
|
type_error_fmt: Custom format string for type validation errors. Should include
|
|
{type_name} and {expected_type} placeholders.
|
|
invalid_spec_error_fmt: Custom format string for invalid spec type errors.
|
|
Should include {actual_type} and {expected_type} placeholders.
|
|
registry: Optional mapping of short names to fully qualified import paths.
|
|
When provided, string specs or dict 'type'/'name' entries are first
|
|
resolved through this registry before attempting to import.
|
|
|
|
Returns:
|
|
An instance of expected_type, or None if allow_none=True and spec is None
|
|
without a default_factory.
|
|
|
|
Raises:
|
|
TypeError: If the instantiated object is not an instance of expected_type.
|
|
ValueError: If spec is None and neither default_factory nor allow_none is set,
|
|
or if spec type is invalid, or if dict spec is invalid.
|
|
|
|
Examples:
|
|
>>> # Direct instance
|
|
>>> optimizer = build_component(AdamW(), expected_type=Optimizer, spec_name='optimizer')
|
|
>>>
|
|
>>> # String import path
|
|
>>> optimizer = build_component('torch.optim.AdamW', expected_type=Optimizer, spec_name='optimizer')
|
|
>>>
|
|
>>> # Dict with type and kwargs
|
|
>>> spec = {'type': 'torch.optim.AdamW', 'lr': 0.001}
|
|
>>> optimizer = build_component(spec, expected_type=Optimizer, spec_name='optimizer')
|
|
>>>
|
|
>>> # Class type
|
|
>>> optimizer = build_component(AdamW, expected_type=Optimizer, spec_name='optimizer')
|
|
>>>
|
|
>>> # Factory function
|
|
>>> optimizer = build_component(lambda: AdamW(lr=0.001), expected_type=Optimizer,
|
|
... spec_name='optimizer')
|
|
"""
|
|
if isinstance(spec, expected_type):
|
|
return cast(T, spec)
|
|
|
|
if spec is None:
|
|
if default_factory is not None:
|
|
instance = default_factory()
|
|
return _ensure_expected_type(instance, expected_type, spec_name, type_error_fmt)
|
|
if allow_none:
|
|
return None
|
|
raise ValueError(
|
|
invalid_spec_error_fmt.format(actual_type=type(spec), expected_type=expected_type.__name__)
|
|
if invalid_spec_error_fmt
|
|
else f"{spec_name} cannot be None."
|
|
)
|
|
|
|
if isinstance(spec, type) and issubclass(spec, expected_type):
|
|
instance = instantiate_component(spec, optional_defaults=optional_defaults)
|
|
return _ensure_expected_type(instance, expected_type, spec_name, type_error_fmt)
|
|
|
|
if callable(spec) and not isinstance(spec, type): # type: ignore
|
|
instance = spec()
|
|
return _ensure_expected_type(instance, expected_type, spec_name, type_error_fmt)
|
|
|
|
if isinstance(spec, str):
|
|
instance = instantiate_from_spec(
|
|
spec,
|
|
spec_name=spec_name,
|
|
optional_defaults=optional_defaults,
|
|
dict_requires_type=dict_requires_type,
|
|
dict_default_cls=dict_default_cls,
|
|
registry=registry,
|
|
)
|
|
return _ensure_expected_type(instance, expected_type, spec_name, type_error_fmt)
|
|
|
|
if isinstance(spec, dict):
|
|
instance = instantiate_from_spec(
|
|
spec, # type: ignore
|
|
spec_name=spec_name,
|
|
optional_defaults=optional_defaults,
|
|
dict_requires_type=dict_requires_type,
|
|
dict_default_cls=dict_default_cls,
|
|
registry=registry,
|
|
)
|
|
return _ensure_expected_type(instance, expected_type, spec_name, type_error_fmt)
|
|
|
|
if invalid_spec_error_fmt:
|
|
raise ValueError(invalid_spec_error_fmt.format(actual_type=type(spec), expected_type=expected_type.__name__)) # type: ignore
|
|
|
|
type_name = str(type(spec)) # type: ignore
|
|
raise ValueError(f"Invalid {spec_name} type: {type_name}. Expected {expected_type.__name__}, str, dict, or None.")
|
|
|
|
|
|
__all__ = ["OptionalDefaults", "build_component", "instantiate_component", "instantiate_from_spec", "load_class"]
|