Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""LICENSE 

2Copyright 2020 Hermann Krumrey <hermann@krumreyh.com> 

3 

4This file is part of jerrycan. 

5 

6jerrycan is free software: you can redistribute it and/or modify 

7it under the terms of the GNU General Public License as published by 

8the Free Software Foundation, either version 3 of the License, or 

9(at your option) any later version. 

10 

11jerrycan is distributed in the hope that it will be useful, 

12but WITHOUT ANY WARRANTY; without even the implied warranty of 

13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14GNU General Public License for more details. 

15 

16You should have received a copy of the GNU General Public License 

17along with jerrycan. If not, see <http://www.gnu.org/licenses/>. 

18LICENSE""" 

19 

20from enum import Enum 

21from typing import Dict, Any, Type, Optional, List 

22from sqlalchemy.inspection import inspect 

23 

24 

25class ModelMixin: 

26 """ 

27 A mixin class that specifies a couple of methods all database 

28 models should implement. 

29 Does not include an automatically incrementing ID. 

30 """ 

31 

32 def __json__( 

33 self, 

34 include_children: bool = False, 

35 ignore_keys: Optional[List[str]] = None 

36 ) -> Dict[str, Any]: 

37 """ 

38 Generates a dictionary containing the information of this model 

39 :param include_children: Specifies if children data models will be 

40 included or if they're limited to IDs 

41 :param ignore_keys: If provided, will not include any of these keys 

42 :return: A dictionary representing the model's values 

43 """ 

44 if ignore_keys is None: 

45 ignore_keys = [] 

46 

47 json_dict = {} 

48 

49 relations: Dict[str, Type] = { 

50 key: value.mapper.class_ 

51 for key, value in inspect(self.__class__).relationships.items() 

52 } 

53 

54 for attribute in inspect(self).attrs: 

55 key = attribute.key 

56 value = attribute.value 

57 relation_cls = relations.get(key) 

58 

59 if key in ignore_keys: 

60 continue 

61 elif key.endswith("_hash"): # Skip password hashes etc 

62 continue 

63 elif isinstance(value, Enum): 

64 value = value.name 

65 elif relation_cls is not None and \ 

66 issubclass(relation_cls, ModelMixin): 

67 

68 recursion_keys = [] 

69 other_relations = \ 

70 list(inspect(relation_cls).relationships.values()) 

71 for other_relation in other_relations: 

72 other_relation_cls = other_relation.mapper.class_ 

73 if other_relation_cls == self.__class__: 

74 recursion_keys.append(other_relation.key) 

75 recursion_keys += ignore_keys 

76 

77 if include_children and value is not None: 

78 if isinstance(value, list): 

79 value = [ 

80 x.__json__(include_children, recursion_keys) 

81 for x in value 

82 ] 

83 else: 

84 value = value.__json__( 

85 include_children, recursion_keys 

86 ) 

87 elif include_children and value is None: 

88 value = None 

89 else: 

90 assert not include_children 

91 continue # pragma: no cover 

92 

93 json_dict[attribute.key] = value 

94 

95 return json_dict 

96 

97 def __str__(self) -> str: 

98 """ 

99 :return: The string representation of this object 

100 """ 

101 data = self.__json__() 

102 if "id" in data: 102 ↛ 106line 102 didn't jump to line 106, because the condition on line 102 was never false

103 _id = data.pop("id") 

104 return "{}:{} <{}>".format(self.__class__.__name__, _id, str(data)) 

105 else: 

106 return "{} <{}>".format(self.__class__.__name__, str(data)) 

107 

108 def __repr__(self) -> str: 

109 """ 

110 :return: A string with which the object may be generated 

111 """ 

112 params = "" 

113 json_repr = self.__json__() 

114 

115 enums = {} 

116 for key in json_repr: 

117 attr = getattr(self, key) 

118 if isinstance(attr, Enum): 

119 enum_cls = attr.__class__.__name__ 

120 enum_val = attr.name 

121 enums[key] = "{}.{}".format(enum_cls, enum_val) 

122 

123 for key, val in self.__json__().items(): 

124 repr_arg = enums.get(key, repr(val)) 

125 params += "{}={}, ".format(key, repr_arg) 

126 

127 params = params.rsplit(",", 1)[0] 

128 

129 return "{}({})".format(self.__class__.__name__, params) 

130 

131 def __eq__(self, other: Any) -> bool: 

132 """ 

133 Checks the model object for equality with another object 

134 :param other: The other object 

135 :return: True if the objects are equal, False otherwise 

136 """ 

137 if "__json__" in dir(other): 

138 return other.__json__() == self.__json__() 

139 else: 

140 return False # pragma: no cover 

141 

142 def __hash__(self) -> int: 

143 """ 

144 Creates a hash so that the model objects can be used as keys 

145 :return: None 

146 """ 

147 return hash(str(self.__json__()))