Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/utils/deconstruct.py: 64%

1 from importlib import import_module

2

3

4 def deconstructible(*args, path=None):

5 """

6 Class decorator that allows the decorated class to be serialized

7 by the migrations subsystem.

8

9 The `path` kwarg specifies the import path.

10 """

11

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

18

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 )

46

47 klass.__new__ = staticmethod(__new__)

48 klass.deconstruct = deconstruct

49

50 return klass

51

52 if not args:

53 return decorator

54 return decorator(*args)