v0.150.0
 1from __future__ import annotations
 2
 3from collections.abc import Callable
 4from importlib import import_module
 5from typing import Any, overload
 6
 7
 8@overload
 9def deconstructible[T](cls: type[T], /) -> type[T]: ...
10@overload
11def deconstructible[T](*, path: str | None = ...) -> Callable[[type[T]], type[T]]: ...
12def deconstructible[T](
13    *args: type[T], path: str | None = None
14) -> Callable[[type[T]], type[T]] | type[T]:
15    """
16    Class decorator that allows the decorated class to be serialized
17    by the migrations subsystem.
18
19    The `path` kwarg specifies the import path.
20    """
21
22    def decorator(klass: type[T]) -> type[T]:
23        def __new__(cls: type[T], *args: Any, **kwargs: Any) -> T:
24            # We capture the arguments to make returning them trivial
25            obj = super(klass, cls).__new__(cls)  # ty: ignore[unresolved-attribute]
26            obj._constructor_args = (args, kwargs)
27            return obj
28
29        def deconstruct(obj: Any) -> tuple[str, tuple[Any, ...], dict[str, Any]]:
30            """
31            Return a 3-tuple of class import path, positional arguments,
32            and keyword arguments.
33            """
34            # Fallback version
35            if path and type(obj) is klass:
36                module_name, _, name = path.rpartition(".")
37            else:
38                module_name = obj.__module__
39                name = obj.__class__.__name__
40            # Make sure it's actually there and not an inner class
41            module = import_module(module_name)
42            if not hasattr(module, name):
43                raise ValueError(
44                    f"Could not find object {name} in {module_name}.\n"
45                    "Please note that you cannot serialize things like inner "
46                    "classes. Please move the object into the main module "
47                    "body to use migrations."
48                )
49            return (
50                path
51                if path and type(obj) is klass
52                else f"{obj.__class__.__module__}.{name}",
53                obj._constructor_args[0],
54                obj._constructor_args[1],
55            )
56
57        setattr(klass, "__new__", staticmethod(__new__))
58        setattr(klass, "deconstruct", deconstruct)
59
60        return klass
61
62    if not args:
63        return decorator
64    return decorator(*args)