Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/migrations/operations/base.py: 62%

40 statements  

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

1from plain.models.db import router 

2 

3 

4class Operation: 

5 """ 

6 Base class for migration operations. 

7 

8 It's responsible for both mutating the in-memory model state 

9 (see db/migrations/state.py) to represent what it performs, as well 

10 as actually performing it against a live database. 

11 

12 Note that some operations won't modify memory state at all (e.g. data 

13 copying operations), and some will need their modifications to be 

14 optionally specified by the user (e.g. custom Python code snippets) 

15 

16 Due to the way this class deals with deconstruction, it should be 

17 considered immutable. 

18 """ 

19 

20 # If this migration can be run in reverse. 

21 # Some operations are impossible to reverse, like deleting data. 

22 reversible = True 

23 

24 # Can this migration be represented as SQL? (things like RunPython cannot) 

25 reduces_to_sql = True 

26 

27 # Should this operation be forced as atomic even on backends with no 

28 # DDL transaction support (i.e., does it have no DDL, like RunPython) 

29 atomic = False 

30 

31 # Should this operation be considered safe to elide and optimize across? 

32 elidable = False 

33 

34 serialization_expand_args = [] 

35 

36 def __new__(cls, *args, **kwargs): 

37 # We capture the arguments to make returning them trivial 

38 self = object.__new__(cls) 

39 self._constructor_args = (args, kwargs) 

40 return self 

41 

42 def deconstruct(self): 

43 """ 

44 Return a 3-tuple of class import path (or just name if it lives 

45 under plain.models.migrations), positional arguments, and keyword 

46 arguments. 

47 """ 

48 return ( 

49 self.__class__.__name__, 

50 self._constructor_args[0], 

51 self._constructor_args[1], 

52 ) 

53 

54 def state_forwards(self, package_label, state): 

55 """ 

56 Take the state from the previous migration, and mutate it 

57 so that it matches what this migration would perform. 

58 """ 

59 raise NotImplementedError( 

60 "subclasses of Operation must provide a state_forwards() method" 

61 ) 

62 

63 def database_forwards(self, package_label, schema_editor, from_state, to_state): 

64 """ 

65 Perform the mutation on the database schema in the normal 

66 (forwards) direction. 

67 """ 

68 raise NotImplementedError( 

69 "subclasses of Operation must provide a database_forwards() method" 

70 ) 

71 

72 def database_backwards(self, package_label, schema_editor, from_state, to_state): 

73 """ 

74 Perform the mutation on the database schema in the reverse 

75 direction - e.g. if this were CreateModel, it would in fact 

76 drop the model's table. 

77 """ 

78 raise NotImplementedError( 

79 "subclasses of Operation must provide a database_backwards() method" 

80 ) 

81 

82 def describe(self): 

83 """ 

84 Output a brief summary of what the action does. 

85 """ 

86 return f"{self.__class__.__name__}: {self._constructor_args}" 

87 

88 @property 

89 def migration_name_fragment(self): 

90 """ 

91 A filename part suitable for automatically naming a migration 

92 containing this operation, or None if not applicable. 

93 """ 

94 return None 

95 

96 def references_model(self, name, package_label): 

97 """ 

98 Return True if there is a chance this operation references the given 

99 model name (as a string), with an app label for accuracy. 

100 

101 Used for optimization. If in doubt, return True; 

102 returning a false positive will merely make the optimizer a little 

103 less efficient, while returning a false negative may result in an 

104 unusable optimized migration. 

105 """ 

106 return True 

107 

108 def references_field(self, model_name, name, package_label): 

109 """ 

110 Return True if there is a chance this operation references the given 

111 field name, with an app label for accuracy. 

112 

113 Used for optimization. If in doubt, return True. 

114 """ 

115 return self.references_model(model_name, package_label) 

116 

117 def allow_migrate_model(self, connection_alias, model): 

118 """ 

119 Return whether or not a model may be migrated. 

120 

121 This is a thin wrapper around router.allow_migrate_model() that 

122 preemptively rejects any swapped out or unmanaged model. 

123 """ 

124 if not model._meta.can_migrate(connection_alias): 

125 return False 

126 

127 return router.allow_migrate_model(connection_alias, model) 

128 

129 def reduce(self, operation, package_label): 

130 """ 

131 Return either a list of operations the actual operation should be 

132 replaced with or a boolean that indicates whether or not the specified 

133 operation can be optimized across. 

134 """ 

135 if self.elidable: 

136 return [operation] 

137 elif operation.elidable: 

138 return [self] 

139 return False 

140 

141 def __repr__(self): 

142 return "<{} {}{}>".format( 

143 self.__class__.__name__, 

144 ", ".join(map(repr, self._constructor_args[0])), 

145 ",".join(" {}={!r}".format(*x) for x in self._constructor_args[1].items()), 

146 )