Coverage for ghost/results.py: 92%
75 statements
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-17 17:19 +0200
« prev ^ index » next coverage.py v7.5.3, created at 2024-06-17 17:19 +0200
1from __future__ import annotations
3import hashlib
4import json
6# noinspection PyUnreachableCode
7if False:
8 # annotation only
9 from .abs_resources import GhostResource
12def dict_hash(dictionary: dict):
13 """
14 MD5 hash of a dictionary, used to compare dicts
15 """
16 dhash = hashlib.md5()
17 # We need to sort arguments so {'a': 1, 'b': 2} is
18 # the same as {'b': 2, 'a': 1}
19 encoded = json.dumps(dictionary, sort_keys=True).encode()
20 dhash.update(encoded)
21 return dhash.hexdigest()
24def is_admin_resource(obj):
25 """
26 GhostAdminResource cannot be imported globally due to circular referencing.
27 This method checks if an object is a Ghost Admin Resource
28 """
29 from .abs_resources import GhostAdminResource
31 return isinstance(obj, GhostAdminResource)
34class GhostResult:
35 """
36 Single resource value (e.g. one post, one tag)
37 """
39 def __init__(self, d: dict, resource: GhostResource):
40 if d.get("tags") and isinstance(d["tags"], list):
41 d["tags"] = {_["slug"]: _ for _ in d["tags"]} # list to dict
42 self.__data__ = d
43 self._resource: GhostResource = resource
45 def __getitem__(self, item):
46 """
47 Allows to access the result as a dict (result[item])
48 """
49 return self.__data__.get(item)
51 def __getattr__(self, item):
52 """
53 Allows to access the result as an object (result.item)
54 """
55 return self.__getitem__(item)
57 def __repr__(self):
58 """
59 human-friendlier representation of the resource
60 """
61 return f"<{self._resource.resource}: {self.slug}>"
63 def as_dict(self):
64 """
65 Get the Result's raw values as dict
66 """
67 return self.__data__
69 def delete(self):
70 """
71 Delete this response's item
72 """
73 rs = self._resource
74 if not is_admin_resource(rs):
75 raise PermissionError("Please use the admin API to delete resources.")
76 return rs.delete(self.id)
78 def update(self, **data):
79 """
80 Update this response's item
81 """
82 rs = self._resource
83 if not is_admin_resource(rs):
84 raise PermissionError("Please use the admin API to update resources.")
85 return rs.update(self.id, data, self)
87 def __eq__(self, other):
88 """
89 Check if this result's data matches the other's
90 """
91 return dict_hash(self.__data__) == dict_hash(other.__data__)
94class GhostResultSet:
95 """
96 List of resource objects (e.g. posts, tags)
97 """
99 def __init__(self, lst: list, resource: GhostResource, meta: dict, request: dict):
100 self.__list__ = [
101 (_ if isinstance(_, GhostResult) else GhostResult(_, resource)) for _ in lst
102 ]
103 self._resource = resource
104 meta["request"] = request
105 self._meta = meta
107 def __repr__(self):
108 return f"[{', '.join([repr(_) for _ in self.__list__])}]"
110 def __iter__(self):
111 """
112 Iterate the result list
113 """
114 for _ in self.__list__:
115 yield _
117 def __len__(self):
118 return len(self.__list__)
120 def __getitem__(self, idx):
121 """
122 Get an item by index (resultset[idx])
123 """
124 return self.__list__[idx]
126 def __or__(self, other: GhostResultSet):
127 """
128 Set | Set -> Bigger Set
130 Note: some metadata will be lost since other._meta is not returned in the new set
131 """
132 if self._resource != other._resource:
133 raise TypeError("Can only combine ResultSets of the same Resource Type")
135 combined_list = [*self.__list__, *other.__list__]
137 return GhostResultSet(
138 combined_list, self._resource, self._meta, self._meta["request"]
139 )
141 def as_dict(self):
142 return {_["id"]: _.as_dict() for _ in self.__list__}
144 def as_list(self):
145 return [_.as_dict() for _ in self.__list__]
147 def delete(self):
148 """
149 Delete all results in this set
150 """
151 return [i.delete() for i in self.__list__]
153 def update(self, **data):
154 """
155 Update all results in this set
156 """
157 return [i.update(**data) for i in self.__list__]
159 def next(self):
160 """
161 Get the next page for a resultset
162 """
163 pag = self._meta["pagination"]
164 request = self._meta["request"]
165 params = request["params"]
166 params["limit"] = pag["limit"]
167 params["page"] = pag["next"]
168 if not params["page"]:
169 return []
171 # request contains path, params, single
172 # used to keep the request params the same (so pagination makes sense)
173 # except the page number (and force limit received from server to be sure)
174 return self._resource._get(**request)