Coverage for /Users/davegaeddert/Development/dropseed/plain/plain/plain/utils/deconstruct.py: 64%
22 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-16 22:03 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-16 22:03 -0500
1from importlib import import_module
4def deconstructible(*args, path=None):
5 """
6 Class decorator that allows the decorated class to be serialized
7 by the migrations subsystem.
9 The `path` kwarg specifies the import path.
10 """
12 def decorator(klass):
13 def __new__(cls, *args, **kwargs):
14 # We capture the arguments to make returning them trivial
15 obj = super(klass, cls).__new__(cls)
16 obj._constructor_args = (args, kwargs)
17 return obj
19 def deconstruct(obj):
20 """
21 Return a 3-tuple of class import path, positional arguments,
22 and keyword arguments.
23 """
24 # Fallback version
25 if path and type(obj) is klass:
26 module_name, _, name = path.rpartition(".")
27 else:
28 module_name = obj.__module__
29 name = obj.__class__.__name__
30 # Make sure it's actually there and not an inner class
31 module = import_module(module_name)
32 if not hasattr(module, name):
33 raise ValueError(
34 f"Could not find object {name} in {module_name}.\n"
35 "Please note that you cannot serialize things like inner "
36 "classes. Please move the object into the main module "
37 "body to use migrations."
38 )
39 return (
40 path
41 if path and type(obj) is klass
42 else f"{obj.__class__.__module__}.{name}",
43 obj._constructor_args[0],
44 obj._constructor_args[1],
45 )
47 klass.__new__ = staticmethod(__new__)
48 klass.deconstruct = deconstruct
50 return klass
52 if not args:
53 return decorator
54 return decorator(*args)