1 | """ |
||
2 | An abstraction for the ChEMBL REST API. |
||
3 | Designed to facilitate testing, but also improves static type checking. |
||
4 | """ |
||
5 | from __future__ import annotations |
||
6 | |||
7 | import abc |
||
8 | import logging |
||
9 | from typing import Any, Callable, Iterator, Mapping, Optional, Sequence, Set, List, Union, Iterable |
||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||
10 | |||
11 | from pocketutils.core.dot_dict import NestedDotDict |
||
0 ignored issues
–
show
|
|||
12 | |||
13 | logger = logging.getLogger("mandos") |
||
14 | |||
15 | from mandos import MandosResources |
||
0 ignored issues
–
show
|
|||
16 | |||
17 | |||
18 | class ChemblFilterQuery(metaclass=abc.ABCMeta): |
||
19 | """ |
||
20 | Wraps the result of calling ``filter`` on a ChEMBL query. |
||
21 | Supports iterating over results (``__iter__`), getting a single item (``__getitem__`), and calling ``only(lst)``. |
||
0 ignored issues
–
show
|
|||
22 | """ |
||
23 | |||
24 | def only(self, items: Sequence[str]) -> ChemblFilterQuery: |
||
25 | """ |
||
26 | Turns this into a query for a single record. |
||
27 | |||
28 | Args: |
||
29 | items: |
||
30 | |||
31 | Returns: |
||
32 | |||
33 | """ |
||
34 | raise NotImplementedError() |
||
35 | |||
36 | def __getitem__(self, item: int) -> NestedDotDict: |
||
37 | raise NotImplementedError() |
||
38 | |||
39 | def __len__(self) -> int: |
||
40 | raise NotImplementedError() |
||
41 | |||
42 | def __iter__(self) -> Iterator[NestedDotDict]: |
||
43 | raise NotImplementedError() |
||
44 | |||
45 | @classmethod |
||
46 | def mock(cls, items: Sequence[dict]): |
||
47 | """ |
||
48 | Mocks. |
||
49 | |||
50 | Args: |
||
51 | items: |
||
52 | |||
53 | Returns: |
||
54 | |||
55 | """ |
||
56 | |||
57 | class F(ChemblFilterQuery): |
||
0 ignored issues
–
show
Class name "F" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
58 | def only(self, _: Sequence[str]) -> ChemblFilterQuery: |
||
59 | return self |
||
60 | |||
61 | def __getitem__(self, item: int) -> NestedDotDict: |
||
62 | return NestedDotDict(items[item]) |
||
63 | |||
64 | def __len__(self) -> int: |
||
65 | return len(items) |
||
66 | |||
67 | def __iter__(self) -> Iterator[NestedDotDict]: |
||
68 | return iter([NestedDotDict(x) for x in items]) |
||
69 | |||
70 | return F() |
||
71 | |||
72 | @classmethod |
||
73 | def wrap(cls, query): |
||
74 | """ |
||
75 | Wraps. |
||
76 | |||
77 | Args: |
||
78 | query: |
||
79 | |||
80 | Returns: |
||
81 | |||
82 | """ |
||
83 | |||
84 | class F(ChemblFilterQuery): |
||
0 ignored issues
–
show
Class name "F" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
85 | def only(self, items: Sequence[str]) -> ChemblFilterQuery: |
||
86 | # TODO technically not returning this |
||
0 ignored issues
–
show
|
|||
87 | return getattr(query, "only")(items) |
||
88 | |||
89 | def __getitem__(self, item: int) -> NestedDotDict: |
||
90 | return NestedDotDict(query[item]) |
||
91 | |||
92 | def __len__(self) -> int: |
||
93 | return len(query) |
||
94 | |||
95 | def __iter__(self) -> Iterator[NestedDotDict]: |
||
96 | return iter([NestedDotDict(x) for x in query]) |
||
97 | |||
98 | return F() |
||
99 | |||
100 | |||
101 | class ChemblEntrypoint: |
||
102 | """ |
||
103 | Wraps just part of a node in the ChEMBL REST API. |
||
104 | Ex ``Chembl.target``. |
||
105 | """ |
||
106 | |||
107 | def filter(self, **kwargs) -> ChemblFilterQuery: |
||
0 ignored issues
–
show
|
|||
108 | raise NotImplementedError() |
||
109 | |||
110 | def get(self, arg: str) -> Optional[NestedDotDict]: |
||
0 ignored issues
–
show
|
|||
111 | raise NotImplementedError() |
||
112 | |||
113 | @classmethod |
||
114 | def mock( |
||
115 | cls, |
||
0 ignored issues
–
show
|
|||
116 | get_items: Mapping[str, dict], |
||
0 ignored issues
–
show
|
|||
117 | filter_items: Optional[Callable[[Mapping[str, Any]], Sequence[dict]]] = None, |
||
0 ignored issues
–
show
|
|||
118 | ) -> ChemblEntrypoint: |
||
119 | """ |
||
120 | |||
121 | Args: |
||
122 | get_items: Map from single arg for calling ``get`` to the item to return |
||
123 | filter_items: Map from kwarg-set for calling ``filter`` to the list of items to return; |
||
124 | If None, returns ``items`` in all cases |
||
125 | |||
126 | Returns: |
||
127 | |||
128 | """ |
||
129 | |||
130 | class X(ChemblEntrypoint): |
||
0 ignored issues
–
show
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
131 | def filter(self, **kwargs) -> ChemblFilterQuery: |
||
132 | if filter_items is None: |
||
133 | return ChemblFilterQuery.mock(list(get_items.values())) |
||
134 | items = filter_items(kwargs) |
||
135 | return ChemblFilterQuery.mock(items) |
||
136 | |||
137 | def get(self, arg: str) -> Optional[NestedDotDict]: |
||
138 | return NestedDotDict(get_items[arg]) |
||
139 | |||
140 | return X() |
||
141 | |||
142 | @classmethod |
||
143 | def wrap(cls, obj) -> ChemblEntrypoint: |
||
144 | """ |
||
145 | |||
146 | Args: |
||
147 | obj: |
||
148 | |||
149 | Returns: |
||
150 | |||
151 | """ |
||
152 | |||
153 | class X(ChemblEntrypoint): |
||
0 ignored issues
–
show
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
154 | def filter(self, **kwargs) -> ChemblFilterQuery: |
||
155 | query = getattr(obj, "filter")(**kwargs) |
||
156 | return ChemblFilterQuery.wrap(query) |
||
157 | |||
158 | def get(self, arg: str) -> Optional[NestedDotDict]: |
||
159 | return NestedDotDict(getattr(obj, "get")(arg)) |
||
160 | |||
161 | return X() |
||
162 | |||
163 | |||
164 | class ChemblApi(metaclass=abc.ABCMeta): |
||
165 | """ |
||
166 | Wraps the whole ChEMBL API. |
||
167 | """ |
||
168 | |||
169 | @property |
||
170 | def activity(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
171 | return self.__dict__["activity"] |
||
172 | |||
173 | @property |
||
174 | def assay(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
175 | return self.__dict__["assay"] |
||
176 | |||
177 | @property |
||
178 | def atc_class(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
179 | return self.__dict__["atc_class"] |
||
180 | |||
181 | @property |
||
182 | def drug(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
183 | return self.__dict__["drug"] |
||
184 | |||
185 | @property |
||
186 | def drug_indication(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
187 | return self.__dict__["drug_indication"] |
||
188 | |||
189 | @property |
||
190 | def mechanism(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
191 | return self.__dict__["mechanism"] |
||
192 | |||
193 | @property |
||
194 | def molecule(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
195 | return self.__dict__["molecule"] |
||
196 | |||
197 | @property |
||
198 | def molecule_form(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
199 | return self.__dict__["molecule_form"] |
||
200 | |||
201 | @property |
||
202 | def organism(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
203 | return self.__dict__["mechanism"] |
||
204 | |||
205 | @property |
||
206 | def go_slim(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
207 | return self.__dict__["go_slim"] |
||
208 | |||
209 | @property |
||
210 | def target(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
211 | return self.__dict__["target"] |
||
212 | |||
213 | @property |
||
214 | def target_relation(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
215 | return self.__dict__["target_relation"] |
||
216 | |||
217 | @property |
||
218 | def protein_class(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
219 | return self.__dict__["protein_class"] |
||
220 | |||
221 | @property |
||
222 | def target_component(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
223 | return self.__dict__["target_component"] |
||
224 | |||
225 | @property |
||
226 | def target_prediction(self) -> ChemblEntrypoint: |
||
0 ignored issues
–
show
|
|||
227 | return self.__dict__["target_prediction"] |
||
228 | |||
229 | def __getattribute__(self, item: str) -> ChemblEntrypoint: |
||
230 | raise NotImplementedError() |
||
231 | |||
232 | @classmethod |
||
233 | def mock(cls, entrypoints: Mapping[str, ChemblEntrypoint]) -> ChemblApi: |
||
234 | """ |
||
235 | Mocks. |
||
236 | |||
237 | Args: |
||
238 | entrypoints: |
||
239 | |||
240 | Returns: |
||
241 | |||
242 | """ |
||
243 | |||
244 | class X(ChemblApi): |
||
0 ignored issues
–
show
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
245 | def __getattribute__(self, item: str) -> ChemblEntrypoint: |
||
246 | return entrypoints[item] |
||
247 | |||
248 | return X() |
||
249 | |||
250 | @classmethod |
||
251 | def wrap(cls, obj) -> ChemblApi: |
||
252 | """ |
||
253 | Wraps. |
||
254 | |||
255 | Args: |
||
256 | obj: |
||
257 | |||
258 | Returns: |
||
259 | |||
260 | """ |
||
261 | |||
262 | class X(ChemblApi): |
||
0 ignored issues
–
show
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
263 | def __getattribute__(self, item: str) -> ChemblEntrypoint: |
||
264 | return ChemblEntrypoint.wrap(getattr(obj, item)) |
||
265 | |||
266 | return X() |
||
267 | |||
268 | |||
269 | __all__ = [ |
||
270 | "ChemblApi", |
||
271 | "ChemblEntrypoint", |
||
272 | "ChemblFilterQuery", |
||
273 | ] |
||
274 |