Coverage for /Users/davegaeddert/Development/dropseed/plain/plain-models/plain/models/migrations/utils.py: 25%

55 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-16 22:04 -0500

1import datetime 

2import re 

3from collections import namedtuple 

4 

5from plain.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT 

6 

7FieldReference = namedtuple("FieldReference", "to through") 

8 

9COMPILED_REGEX_TYPE = type(re.compile("")) 

10 

11 

12class RegexObject: 

13 def __init__(self, obj): 

14 self.pattern = obj.pattern 

15 self.flags = obj.flags 

16 

17 def __eq__(self, other): 

18 if not isinstance(other, RegexObject): 

19 return NotImplemented 

20 return self.pattern == other.pattern and self.flags == other.flags 

21 

22 

23def get_migration_name_timestamp(): 

24 return datetime.datetime.now().strftime("%Y%m%d_%H%M") 

25 

26 

27def resolve_relation(model, package_label=None, model_name=None): 

28 """ 

29 Turn a model class or model reference string and return a model tuple. 

30 

31 package_label and model_name are used to resolve the scope of recursive and 

32 unscoped model relationship. 

33 """ 

34 if isinstance(model, str): 

35 if model == RECURSIVE_RELATIONSHIP_CONSTANT: 

36 if package_label is None or model_name is None: 

37 raise TypeError( 

38 "package_label and model_name must be provided to resolve " 

39 "recursive relationships." 

40 ) 

41 return package_label, model_name 

42 if "." in model: 

43 package_label, model_name = model.split(".", 1) 

44 return package_label, model_name.lower() 

45 if package_label is None: 

46 raise TypeError( 

47 "package_label must be provided to resolve unscoped model relationships." 

48 ) 

49 return package_label, model.lower() 

50 return model._meta.package_label, model._meta.model_name 

51 

52 

53def field_references( 

54 model_tuple, 

55 field, 

56 reference_model_tuple, 

57 reference_field_name=None, 

58 reference_field=None, 

59): 

60 """ 

61 Return either False or a FieldReference if `field` references provided 

62 context. 

63 

64 False positives can be returned if `reference_field_name` is provided 

65 without `reference_field` because of the introspection limitation it 

66 incurs. This should not be an issue when this function is used to determine 

67 whether or not an optimization can take place. 

68 """ 

69 remote_field = field.remote_field 

70 if not remote_field: 

71 return False 

72 references_to = None 

73 references_through = None 

74 if resolve_relation(remote_field.model, *model_tuple) == reference_model_tuple: 

75 to_fields = getattr(field, "to_fields", None) 

76 if ( 

77 reference_field_name is None 

78 or 

79 # Unspecified to_field(s). 

80 to_fields is None 

81 or 

82 # Reference to primary key. 

83 ( 

84 None in to_fields 

85 and (reference_field is None or reference_field.primary_key) 

86 ) 

87 or 

88 # Reference to field. 

89 reference_field_name in to_fields 

90 ): 

91 references_to = (remote_field, to_fields) 

92 through = getattr(remote_field, "through", None) 

93 if through and resolve_relation(through, *model_tuple) == reference_model_tuple: 

94 through_fields = remote_field.through_fields 

95 if ( 

96 reference_field_name is None 

97 or 

98 # Unspecified through_fields. 

99 through_fields is None 

100 or 

101 # Reference to field. 

102 reference_field_name in through_fields 

103 ): 

104 references_through = (remote_field, through_fields) 

105 if not (references_to or references_through): 

106 return False 

107 return FieldReference(references_to, references_through) 

108 

109 

110def get_references(state, model_tuple, field_tuple=()): 

111 """ 

112 Generator of (model_state, name, field, reference) referencing 

113 provided context. 

114 

115 If field_tuple is provided only references to this particular field of 

116 model_tuple will be generated. 

117 """ 

118 for state_model_tuple, model_state in state.models.items(): 

119 for name, field in model_state.fields.items(): 

120 reference = field_references( 

121 state_model_tuple, field, model_tuple, *field_tuple 

122 ) 

123 if reference: 

124 yield model_state, name, field, reference 

125 

126 

127def field_is_referenced(state, model_tuple, field_tuple): 

128 """Return whether `field_tuple` is referenced by any state models.""" 

129 return next(get_references(state, model_tuple, field_tuple), None) is not None