Skip to content

Factory

coola.factory

Provide utilities to import and instantiate objects from configurations.

A configuration is a plain dict (for example loaded from JSON or YAML) that names an object by its fully qualified name, under the key OBJECT_TARGET ("_target_"), and gives its keyword arguments. The functions of the package:

  • import_object: import a class or a function from its fully qualified name.
  • instantiate_object: instantiate an object from a target and its arguments.
  • factory: import and instantiate an object from a fully qualified name, without any registration.
  • resolve_object: return an object as is, or build it from a configuration.
  • is_object_config: check if a configuration targets a given class.

Unlike coola.registry, an object does not need to be registered: any importable object can be built.

coola.factory.factory

factory(
    _target_: str,
    *args: Any,
    _init_: str = "__init__",
    _allowed_prefixes_: Sequence[str] | None = None,
    **kwargs: Any,
) -> Any

Instantiate dynamically an object given its configuration.

This function provides a universal factory that can instantiate any class or call any function by its fully qualified name. Unlike the AbstractFactory or Registry approaches, this function does not require prior registration of classes.

Parameters:

Name Type Description Default
_target_ str

The fully qualified name of the object (class or function) to instantiate, e.g., "collections.Counter" or "math.isclose".

required
*args Any

Positional arguments to pass to the class constructor or function.

()
_init_ str

The function or method used to create the object. If "__init__" (default), the object is created by calling the constructor. Can also be the name of a public class method or static method that returns an instance of the class (a name that starts with _, such as "__new__", is refused).

'__init__'
_allowed_prefixes_ Sequence[str] | None

An optional allow-list of module prefixes that _target_ must match (see import_object). It is keyword-only, and it is the choice of the code that calls this function: whoever unpacks untrusted data into factory owns it. When it is set, an argument that is a configuration is refused.

None
**kwargs Any

Keyword arguments to pass to the class constructor or function. Any name is accepted, including obj and cls.

{}

Returns:

Type Description
Any

The instantiated object with the given parameters.

Raises:

Type Description
ImportError

if the target cannot be found, is not allowed by _allowed_prefixes_, or if importing its module fails (the original error is then raised unchanged).

TypeError

if _target_ is not a string.

ValueError

if _allowed_prefixes_ is set and an argument is a dict with a _target_ key (a nested configuration): the target could resolve it without the allow-list. Resolve it first, with the same list.

Note

_target_ is resolved at the top level only: a nested dictionary with a _target_ key is passed to the target unchanged, it is not instantiated. Resolve the nested configurations with resolve_object and pass the results.

Security

_target_ is imported and called with no allowlist or restriction on which module or class it may reference: this function will import arbitrary module code and instantiate or call arbitrary objects from a dotted path string. Only pass a _target_ that comes from a trusted source (e.g. code you wrote, or a configuration file you control). Never resolve a _target_ derived from untrusted input, such as a third-party-uploaded config file or a network payload, as this is a remote-code-execution vector. _allowed_prefixes_ is a filter on a path, not a sandbox: it limits what is called, not the arguments. A prefix that contains a function that imports or resolves objects from its arguments (any resolve_*, import_object, importlib, pickle, eval, os.system, ...) makes the list worthless: never allow "coola" or "coola.factory". The path is checked, not the module that defines the object, so a re-export is allowed by the prefix of the re-exporting module.

Example
>>> from coola.factory import factory
>>> factory("collections.Counter", [1, 2, 1, 3])
Counter({1: 2, 2: 1, 3: 1})

coola.factory.import_object

import_object(
    object_path: object,
    *,
    allowed_prefixes: Sequence[str] | None = None,
) -> Any

Import an object given its path.

This function dynamically imports a class, function, or other Python object using its fully qualified name. The object path should have the structure module_path.object_name (e.g., "collections.Counter" or "math.isclose"). The object can also be an attribute of a class ("collections.Counter.fromkeys"): the longest prefix that is a module is imported and the rest of the path is read with getattr.

Parameters:

Name Type Description Default
object_path object

The fully qualified path of the object to import. Must be a string in the format "module.path.ObjectName".

required
allowed_prefixes Sequence[str] | None

An optional allow-list of module prefixes. If provided, object_path must be equal to a prefix or start with prefix + ".", otherwise an ImportError is raised before anything is imported. A string is treated as a single prefix.

None

Returns:

Type Description
Any

The imported object.

Raises:

Type Description
TypeError

if object_path is not a string.

ImportError

if object_path is empty or has an empty module name ("", ".Counter"), is not allowed by allowed_prefixes, or if its module or attribute does not exist ("The target object does not exist: ..."). A dependency of the target module that is not installed keeps its own ModuleNotFoundError.

Note

pkgutil.resolve_name is a different function: it takes a path with a colon ("package.module:name") and is used by the lazy registries. This function takes a dotted path ("package.module.name"), checks allowed_prefixes and reports a missing target with one ImportError.

Security

By default, this function imports arbitrary module code with no restriction on which module or attribute it may reference: importing a module executes its top-level code. Use allowed_prefixes to restrict it. Only pass an object_path that comes from a trusted source. Never resolve an object_path derived from untrusted input, such as a third-party-uploaded config file or a network payload, as this is a remote-code-execution vector.

Example
>>> from coola.factory import import_object
>>> cls = import_object("collections.Counter")
>>> cls()
Counter()
>>> fn = import_object("math.isclose")
>>> fn(1, 1)
True
>>> pi = import_object("math.pi")
>>> pi
3.141592653589793
>>> pkg = import_object("math")
>>> pkg
<module 'math' (built-in)>

coola.factory.instantiate_object

instantiate_object(
    obj: Callable[..., Any] | type,
    /,
    *args: Any,
    _init_: str = "__init__",
    **kwargs: Any,
) -> Any

Instantiate dynamically an object from its configuration.

This function creates an instance of a class or calls a callable with the provided arguments. For classes, it supports different instantiation methods (constructor or public class methods). For any other callable (function, builtin, functools.partial, or an object implementing __call__), it simply calls it with the given arguments.

Parameters:

Name Type Description Default
obj Callable[..., Any] | type

The class to instantiate or the callable to call. Must be a class or a callable object.

required
*args Any

Positional arguments to pass to the class constructor or callable.

()
_init_ str

The function or method to use to create the object. This parameter is ignored if obj is not a class. For classes, if "__init__" (default), the object is created by calling the constructor (__new__ and __init__). Can also be the name of a public class method or static method that returns an instance of the class. A name that starts with _ is refused.

'__init__'
**kwargs Any

Keyword arguments to pass to the class constructor or callable. Any name is accepted, including obj and cls: obj is positional-only.

{}

Returns:

Type Description
Any

The instantiated object if obj is a class, otherwise the returned value of the callable.

Raises:

Type Description
TypeError

if obj is not a class or a callable, if it is an abstract class, if _init_ is not a public method name (it is not a string or it starts with _), if the class does not have a callable _init_ attribute, or if _init_ does not return an instance of obj.

Security

obj is instantiated or called with the given arguments with no allowlist or restriction on what it may do: an arbitrary class or callable will be invoked as-is. Only pass an obj (and _init_) that come from a trusted source. Never instantiate an object whose class/callable or arguments are derived from untrusted input, as this is a remote-code-execution vector.

Example
>>> from collections import Counter
>>> from coola.factory import instantiate_object
>>> instantiate_object(Counter, [1, 2, 1])
Counter({1: 2, 2: 1})
>>> instantiate_object(list, [1, 2, 1])
[1, 2, 1]

coola.factory.is_object_config

is_object_config(
    config: dict[str, Any],
    cls: type,
    *,
    allowed_prefixes: Sequence[str] | None = None,
    import_target: bool = False,
) -> bool

Indicate if the input configuration is a configuration for a given class.

This function only checks if the value of the key _target_ is valid. It does not check the other values. If _target_ indicates a function, the returned type hint is used to check the class. A function whose return type hint cannot be resolved (e.g. a forward reference to an undefined name, or a string annotation that is not valid Python) is not a configuration for cls. A config that is not a dict, or whose _target_ is not a string, is not a configuration for cls.

The module of _target_ is not imported to answer: if it is not imported yet, the configuration is not a configuration for cls (importing a module runs its top-level code). Use allowed_prefixes to restrict the modules that can be imported, or import_target=True to import the module of any _target_.

Parameters:

Name Type Description Default
config dict[str, Any]

The configuration to check.

required
cls type

The object class.

required
allowed_prefixes Sequence[str] | None

An optional allow-list of module prefixes that _target_ must match (see import_object). If None (default), any _target_ is allowed, but its module is imported only if import_target is True. If it is set, the module of a _target_ that matches is imported when needed.

None
import_target bool

If True, the module of _target_ is imported if it is not imported yet, even without allowed_prefixes. If False (default), a _target_ whose module is not imported yet and that is not restricted by allowed_prefixes is not a configuration for cls.

False

Returns:

Type Description
bool

True if the input configuration is a configuration for the given class.

Raises:

Type Description
ImportError

if _target_ does not match allowed_prefixes, or if it cannot be imported.

Security

By default, the module of the _target_ is not imported, so a configuration from an untrusted source cannot run the top-level code of an arbitrary module. With allowed_prefixes or import_target=True, the module is imported to check the class, and importing a module runs its top-level code: do not use them with a _target_ that comes from an untrusted source, unless allowed_prefixes restricts the modules that can be imported.

Example
>>> from coola.factory import is_object_config
>>> from collections import Counter
>>> is_object_config({"_target_": "collections.Counter", "iterable": [1, 2, 1, 3]}, Counter)
True

coola.factory.resolve_object

resolve_object(
    obj: T | dict[str, Any],
    *,
    allowed_prefixes: Sequence[str] | None = None,
    strict: bool = False,
) -> T
resolve_object(
    obj: T | dict[str, Any],
    cls: type[T],
    *,
    allowed_prefixes: Sequence[str] | None = None,
    strict: bool = False,
) -> T
resolve_object(
    obj: Any,
    cls: tuple[type, ...],
    *,
    allowed_prefixes: Sequence[str] | None = None,
    strict: bool = False,
) -> Any
resolve_object(
    obj: Any,
    cls: type | tuple[type, ...] = object,
    *,
    allowed_prefixes: Sequence[str] | None = None,
    strict: bool = False,
) -> Any

Resolve an instance of cls from an existing object or a configuration dictionary.

A dict with the key "_target_" is always a factory configuration: it is built, and the result is checked against cls. Any other object, including a dict without "_target_", is returned as-is if it is an instance of cls. The key "_target_" is reserved: a dict that has it (for example Counter({"_target_": 2})) cannot be passed through as data.

Note

"_target_" is resolved at the top level only: a nested dictionary with a "_target_" key is passed to the target unchanged, it is not instantiated. With allowed_prefixes it is refused with a ValueError. Resolve the nested configurations with resolve_object (with the same allowed_prefixes) and pass the results.

Parameters:

Name Type Description Default
obj Any

Either a fully configured instance of cls, or a dict containing a factory specification (with a "_target_" key pointing to the fully-qualified class name). The other keys are passed to the target by keyword, except "_init_" and "_allowed_prefixes_".

required
cls type | tuple[type, ...]

The expected type, or a tuple of types (as accepted by isinstance). Used to validate the resolved object, whether obj was already an instance or was built from a configuration dictionary. Defaults to object, which accepts any resolved value without validation.

object
allowed_prefixes Sequence[str] | None

An optional allow-list of module prefixes that the "_target_" of the configuration must match (see import_object): "mypackage" allows "mypackage" and "mypackage.sub.Class" but not "mypackage2.Class". If None (default), any "_target_" is allowed. The "_allowed_prefixes_" key of a configuration is ignored: the data cannot set, widen or narrow the list. When it is set, an argument that is a configuration is refused.

None
strict bool

If True, a "_target_" that is not a class and has no usable return type hint (a function without annotation, a built-in function) is refused before it is called, because the type of its result cannot be checked beforehand. If False (default), it is called and the result is checked afterwards.

False

Returns:

Type Description
Any

A configured instance of cls.

Raises:

Type Description
TypeError

If the "_target_" is a class (or a function whose return type hint is a class) that is not a subclass of cls -- it is then not called --, or if the resolved object (including a dict without "_target_") is not an instance of cls.

ImportError

If the "_target_" does not match allowed_prefixes, or if it cannot be found.

ValueError

If allowed_prefixes is set and an argument of the configuration is itself a configuration (a dict with a "_target_" key).

Security

When obj is a dict with a "_target_" key, its value is imported and the target is called: this imports and instantiates arbitrary objects from a dotted path string with no allowlist by default. Only call this function with a configuration dictionary that comes from a trusted source; never resolve a _target_ derived from untrusted input, as this is a remote-code-execution vector. If the configuration cannot be fully trusted, pass allowed_prefixes to restrict the "_target_" to the modules you expect, and check the arguments of the allowed objects too: an allow-list limits what can be called, not the arguments it is called with, and a prefix that contains a function that resolves its arguments (any resolve_*, import_object, pickle, eval, ...) makes it worthless: never allow "coola" or "coola.factory".

Example
>>> from datetime import date
>>> from coola.factory import resolve_object
>>> # From an existing instance:
>>> d = resolve_object(date(2020, 1, 1), cls=date)
>>> # From a configuration dictionary:
>>> d = resolve_object(
...     {"_target_": "datetime.date", "year": 2020, "month": 1, "day": 1}, cls=date
... )