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>
4This file is part of jerrycan.
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.
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.
16You should have received a copy of the GNU General Public License
17along with jerrycan. If not, see <http://www.gnu.org/licenses/>.
18LICENSE"""
20from enum import Enum
21from typing import Dict, Any, Type, Optional, List
22from sqlalchemy.inspection import inspect
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 """
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 = []
47 json_dict = {}
49 relations: Dict[str, Type] = {
50 key: value.mapper.class_
51 for key, value in inspect(self.__class__).relationships.items()
52 }
54 for attribute in inspect(self).attrs:
55 key = attribute.key
56 value = attribute.value
57 relation_cls = relations.get(key)
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):
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
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
93 json_dict[attribute.key] = value
95 return json_dict
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))
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__()
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)
123 for key, val in self.__json__().items():
124 repr_arg = enums.get(key, repr(val))
125 params += "{}={}, ".format(key, repr_arg)
127 params = params.rsplit(",", 1)[0]
129 return "{}({})".format(self.__class__.__name__, params)
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
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__()))