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

22 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-23 11:16 -0600

1from importlib import import_module 

2 

3 

4def 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)