tol-sdk 1.6.37__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tol/api_base/blueprint.py +29 -6
- tol/api_base/controller.py +14 -5
- tol/api_client/api_datasource.py +15 -7
- tol/api_client/client.py +12 -6
- tol/api_client/converter.py +22 -8
- tol/api_client/factory.py +5 -3
- tol/api_client/view.py +75 -205
- tol/cli/cli.py +1 -1
- tol/core/__init__.py +1 -0
- tol/core/http_client.py +4 -2
- tol/core/operator/cursor.py +5 -3
- tol/core/operator/detail_getter.py +7 -15
- tol/core/operator/list_getter.py +3 -1
- tol/core/operator/page_getter.py +3 -1
- tol/core/operator/relational.py +9 -4
- tol/core/requested_fields.py +189 -0
- tol/elastic/elastic_datasource.py +2 -1
- tol/flows/converters/benchling_extraction_to_elastic_extraction_converter.py +25 -6
- tol/flows/converters/benchling_extraction_to_elastic_sequencing_request_converter.py +28 -7
- tol/flows/converters/benchling_sequencing_request_to_elastic_sequencing_request_converter.py +30 -9
- tol/flows/converters/benchling_tissue_prep_to_elastic_tissue_prep_converter.py +14 -3
- tol/flows/converters/elastic_sample_to_benchling_tissue_update_converter.py +1 -1
- tol/flows/converters/elastic_sample_to_elastic_sequencing_request_update_converter.py +4 -1
- tol/flows/converters/elastic_tolid_to_elastic_genome_note_update_converter.py +4 -1
- tol/flows/converters/elastic_tolid_to_elastic_sample_update_converter.py +4 -1
- tol/sources/sts.py +6 -2
- tol/sql/database.py +80 -44
- tol/sql/factory.py +2 -2
- tol/sql/filter.py +22 -20
- tol/sql/model.py +43 -38
- tol/sql/relationship.py +1 -1
- tol/sql/sql_converter.py +49 -142
- tol/sql/sql_datasource.py +85 -180
- tol/sql/{board → standard}/__init__.py +1 -1
- tol/sql/standard/factory.py +549 -0
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/METADATA +1 -1
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/RECORD +41 -42
- tol/sql/board/factory.py +0 -341
- tol/sql/loader/__init__.py +0 -6
- tol/sql/loader/factory.py +0 -246
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/WHEEL +0 -0
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/entry_points.txt +0 -0
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {tol_sdk-1.6.37.dist-info → tol_sdk-1.7.0.dist-info}/top_level.txt +0 -0
tol/sql/filter.py
CHANGED
|
@@ -6,10 +6,10 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from collections import defaultdict
|
|
9
|
-
from collections.abc import MutableMapping
|
|
9
|
+
from collections.abc import Iterable, Iterator, MutableMapping
|
|
10
10
|
from functools import reduce
|
|
11
11
|
from itertools import chain
|
|
12
|
-
from typing import Any, Dict,
|
|
12
|
+
from typing import Any, Dict, Optional, Tuple, Type
|
|
13
13
|
|
|
14
14
|
from sqlalchemy import BinaryExpression, cast, inspect, not_
|
|
15
15
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
@@ -30,8 +30,8 @@ class AliasTrie(MutableMapping[str, 'AliasTrie']):
|
|
|
30
30
|
def alias(self) -> AliasedClass[Model]:
|
|
31
31
|
return self.__alias
|
|
32
32
|
|
|
33
|
-
def __getitem__(self,
|
|
34
|
-
return self.__dict[
|
|
33
|
+
def __getitem__(self, key: str) -> AliasTrie:
|
|
34
|
+
return self.__dict[key]
|
|
35
35
|
|
|
36
36
|
def __setitem__(self, key: str, value: AliasTrie) -> None:
|
|
37
37
|
self.__dict[key] = value
|
|
@@ -67,7 +67,9 @@ class DatabaseFilter(ABC):
|
|
|
67
67
|
"""Adds a relation field to the filter, for joining later"""
|
|
68
68
|
|
|
69
69
|
@abstractmethod
|
|
70
|
-
def get_query(
|
|
70
|
+
def get_query(
|
|
71
|
+
self, session: Session, base_model: type[Model]
|
|
72
|
+
) -> tuple[Query[Model], AliasedClass[Model]]:
|
|
71
73
|
"""Gets an aliased query"""
|
|
72
74
|
|
|
73
75
|
|
|
@@ -103,7 +105,7 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
103
105
|
query = self.__apply_joins(
|
|
104
106
|
query,
|
|
105
107
|
self.__alias_trie,
|
|
106
|
-
self.
|
|
108
|
+
self.__base_model,
|
|
107
109
|
)
|
|
108
110
|
|
|
109
111
|
if self.__filter is None:
|
|
@@ -117,13 +119,10 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
117
119
|
|
|
118
120
|
return query
|
|
119
121
|
|
|
120
|
-
def get_query(self, session: Session, base_model: Model) -> Query[Model]:
|
|
121
|
-
|
|
122
|
-
self.__base_alias: AliasedClass[Model] = aliased(
|
|
123
|
-
base_model,
|
|
124
|
-
)
|
|
122
|
+
def get_query(self, session: Session, base_model: Model) -> [Query[Model]]:
|
|
123
|
+
self.__base_model = base_model
|
|
125
124
|
|
|
126
|
-
return session.query(
|
|
125
|
+
return session.query(base_model)
|
|
127
126
|
|
|
128
127
|
def __apply_joins(
|
|
129
128
|
self,
|
|
@@ -135,6 +134,7 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
135
134
|
for part, trie in parent_trie.items():
|
|
136
135
|
alias = trie.alias
|
|
137
136
|
|
|
137
|
+
# Probably want an outerjoin() here
|
|
138
138
|
query = query.join(alias, getattr(parent_alias, part))
|
|
139
139
|
|
|
140
140
|
query = self.__apply_joins(
|
|
@@ -160,11 +160,11 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
160
160
|
self.__rel_keys.add(field)
|
|
161
161
|
|
|
162
162
|
def __build_alias_trie(self, paths: Iterable[str]) -> AliasTrie:
|
|
163
|
-
trie = AliasTrie(self.
|
|
163
|
+
trie = AliasTrie(self.__base_model)
|
|
164
164
|
|
|
165
165
|
for path in paths:
|
|
166
166
|
parts = path.split('.')
|
|
167
|
-
current_alias = self.
|
|
167
|
+
current_alias = self.__base_model
|
|
168
168
|
current = trie
|
|
169
169
|
for part in parts[:-1]:
|
|
170
170
|
if part not in current:
|
|
@@ -176,6 +176,8 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
176
176
|
)
|
|
177
177
|
current[part] = step
|
|
178
178
|
current = step
|
|
179
|
+
else:
|
|
180
|
+
current = current[part]
|
|
179
181
|
current_alias = current.alias
|
|
180
182
|
|
|
181
183
|
return trie
|
|
@@ -520,16 +522,16 @@ class DefaultDatabaseFilter(DatabaseFilter):
|
|
|
520
522
|
return self.__get_column_attr(model, key)
|
|
521
523
|
|
|
522
524
|
def __get_id_column(self, model: type[Model]) -> MappedColumn:
|
|
523
|
-
og_model: type[Model] = model.
|
|
525
|
+
og_model: type[Model] = inspect(model).mapper.class_
|
|
524
526
|
id_key = og_model.get_id_column_name()
|
|
525
527
|
return self.__get_column_attr(model, id_key)
|
|
526
528
|
|
|
527
529
|
def __get_column_attr(self, model: AliasedClass[Model], key: str) -> MappedColumn:
|
|
528
|
-
|
|
529
|
-
col.key:
|
|
530
|
-
|
|
531
|
-
}
|
|
532
|
-
|
|
530
|
+
for col in inspect(model).selectable.c:
|
|
531
|
+
if col.key == key:
|
|
532
|
+
return col
|
|
533
|
+
msg = f"Failed to find column '{key}' in '{model}'"
|
|
534
|
+
raise ValueError(msg)
|
|
533
535
|
|
|
534
536
|
def __get_relation_column(
|
|
535
537
|
self,
|
tol/sql/model.py
CHANGED
|
@@ -6,6 +6,8 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
from abc import ABCMeta
|
|
8
8
|
from datetime import datetime
|
|
9
|
+
from functools import cache
|
|
10
|
+
from types import MappingProxyType # A read-only dict
|
|
9
11
|
from typing import Any, Iterable, Optional, Type
|
|
10
12
|
|
|
11
13
|
from sqlalchemy import JSON, inspect
|
|
@@ -198,30 +200,32 @@ def model_base() -> Type[DefaultModel]:
|
|
|
198
200
|
return getattr(cls, name)
|
|
199
201
|
|
|
200
202
|
@classmethod
|
|
201
|
-
|
|
203
|
+
@cache
|
|
204
|
+
def get_to_many_relationship_config(cls) -> MappingProxyType[str, str]:
|
|
202
205
|
relationships = inspect(cls).relationships
|
|
203
206
|
all_ = {
|
|
204
|
-
|
|
207
|
+
r.key: cls.__get_relationship_target(r)
|
|
205
208
|
for r in relationships
|
|
206
209
|
if cls.__is_to_many_relationship(r)
|
|
207
210
|
}
|
|
208
|
-
return {
|
|
211
|
+
return MappingProxyType({
|
|
209
212
|
k: v for k, v in all_.items()
|
|
210
213
|
if not k.startswith('_')
|
|
211
|
-
}
|
|
214
|
+
})
|
|
212
215
|
|
|
213
216
|
@classmethod
|
|
214
|
-
|
|
217
|
+
@cache
|
|
218
|
+
def get_to_one_relationship_config(cls) -> MappingProxyType[str, str]:
|
|
215
219
|
relationships = inspect(cls).relationships
|
|
216
220
|
all_ = {
|
|
217
|
-
|
|
221
|
+
r.key: cls.__get_relationship_target(r)
|
|
218
222
|
for r in relationships
|
|
219
223
|
if cls.__is_to_one_relationship(r)
|
|
220
224
|
}
|
|
221
|
-
return {
|
|
225
|
+
return MappingProxyType({
|
|
222
226
|
k: v for k, v in all_.items()
|
|
223
227
|
if not k.startswith('_')
|
|
224
|
-
}
|
|
228
|
+
})
|
|
225
229
|
|
|
226
230
|
@classmethod
|
|
227
231
|
def get_foreign_key_name(cls, relationship_name: str) -> str:
|
|
@@ -230,12 +234,13 @@ def model_base() -> Type[DefaultModel]:
|
|
|
230
234
|
return foreign_key.name
|
|
231
235
|
|
|
232
236
|
@classmethod
|
|
233
|
-
|
|
237
|
+
@cache
|
|
238
|
+
def get_attribute_types(cls) -> MappingProxyType[str, type]:
|
|
234
239
|
names = cls.__get_attribute_names()
|
|
235
240
|
columns = inspect(cls).columns
|
|
236
|
-
return {
|
|
241
|
+
return MappingProxyType({
|
|
237
242
|
k: columns[k].type.python_type for k in names
|
|
238
|
-
}
|
|
243
|
+
})
|
|
239
244
|
|
|
240
245
|
@classmethod
|
|
241
246
|
def get_id_attribute_type(cls) -> dict[str, type]:
|
|
@@ -268,13 +273,15 @@ def model_base() -> Type[DefaultModel]:
|
|
|
268
273
|
cls,
|
|
269
274
|
relationship_name: str
|
|
270
275
|
) -> MappedColumn:
|
|
276
|
+
"""
|
|
277
|
+
Returns the local foreign key column given a relationship name
|
|
278
|
+
"""
|
|
271
279
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if len(
|
|
280
|
+
rel = inspect(cls).relationships[relationship_name]
|
|
281
|
+
all_cols = tuple(rel.local_columns)
|
|
282
|
+
if len(all_cols) != 1:
|
|
275
283
|
raise NotImplementedError('Composite keys are not supported.')
|
|
276
|
-
|
|
277
|
-
return foreign_key
|
|
284
|
+
return all_cols[0]
|
|
278
285
|
|
|
279
286
|
def __get_attributes_map(
|
|
280
287
|
self,
|
|
@@ -285,13 +292,9 @@ def model_base() -> Type[DefaultModel]:
|
|
|
285
292
|
name: getattr(self, name) for name in names
|
|
286
293
|
}
|
|
287
294
|
|
|
288
|
-
@classmethod
|
|
289
|
-
def __get_relationshship_name(cls, relationship) -> str:
|
|
290
|
-
return str(relationship).split('.')[-1]
|
|
291
|
-
|
|
292
295
|
@classmethod
|
|
293
296
|
def __get_relationship_target(cls, relationship) -> str:
|
|
294
|
-
return
|
|
297
|
+
return relationship.entity.tables[0].name
|
|
295
298
|
|
|
296
299
|
@classmethod
|
|
297
300
|
def __get_all_relationship_names(cls) -> list[str]:
|
|
@@ -310,47 +313,49 @@ def model_base() -> Type[DefaultModel]:
|
|
|
310
313
|
)
|
|
311
314
|
|
|
312
315
|
@classmethod
|
|
313
|
-
|
|
316
|
+
@cache
|
|
317
|
+
def get_all_foreign_key_names(cls) -> frozenset[str]:
|
|
314
318
|
"""
|
|
315
319
|
Returns only the names of columns which are foreign keys used in
|
|
316
|
-
relationsips.
|
|
320
|
+
relationsips. i.e. To-one relationships.
|
|
317
321
|
"""
|
|
318
322
|
|
|
323
|
+
col_to_attr = cls.__get_column_name_to_attribute_map()
|
|
324
|
+
|
|
319
325
|
foreign_keys = set()
|
|
320
326
|
for rel in inspect(cls).relationships:
|
|
321
327
|
for col in rel.local_columns:
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return foreign_keys
|
|
328
|
+
# If it is really a foreign key, get the name of the
|
|
329
|
+
# attribute in the model
|
|
330
|
+
if len(col.foreign_keys) > 0 and (attr_name := col_to_attr.get(col.name)):
|
|
331
|
+
foreign_keys.add(attr_name)
|
|
332
|
+
return frozenset(foreign_keys)
|
|
327
333
|
|
|
328
334
|
@classmethod
|
|
329
|
-
def
|
|
335
|
+
def __get_column_name_to_attribute_map(cls) -> dict[str, str]:
|
|
330
336
|
"""
|
|
331
|
-
|
|
332
|
-
|
|
337
|
+
Returns a dict mapping database column names to this model's
|
|
338
|
+
attribute names.
|
|
333
339
|
"""
|
|
334
340
|
|
|
335
|
-
|
|
341
|
+
return {
|
|
336
342
|
col_prop.columns[0].name: col_prop.key
|
|
337
343
|
for col_prop in inspect(cls).column_attrs
|
|
338
344
|
}
|
|
339
345
|
|
|
340
|
-
return col_to_attr.get(foreign_key_name)
|
|
341
|
-
|
|
342
346
|
@classmethod
|
|
343
|
-
|
|
347
|
+
@cache
|
|
348
|
+
def __get_attribute_names(cls) -> tuple[str]:
|
|
344
349
|
excluded = cls.get_excluded_column_names()
|
|
345
350
|
mapper = inspect(cls)
|
|
346
351
|
relationships = cls.__get_all_relationship_names()
|
|
347
352
|
foreign_keys = cls.get_all_foreign_key_names()
|
|
348
|
-
return
|
|
349
|
-
k for k in mapper.attrs.keys()
|
|
353
|
+
return tuple(
|
|
354
|
+
k for k in mapper.attrs.keys() # noqa: SIM118
|
|
350
355
|
if k not in excluded
|
|
351
356
|
and k not in relationships
|
|
352
357
|
and k not in foreign_keys
|
|
353
|
-
|
|
358
|
+
)
|
|
354
359
|
|
|
355
360
|
class LogBase(ModelBase):
|
|
356
361
|
"""
|
tol/sql/relationship.py
CHANGED
|
@@ -69,7 +69,7 @@ class DefaultSqlRelationshipConfig(ABC):
|
|
|
69
69
|
else:
|
|
70
70
|
return object_type, RelationshipConfig(
|
|
71
71
|
to_one=self.__map_config(to_one),
|
|
72
|
-
to_many=self.__map_config(to_many)
|
|
72
|
+
to_many=self.__map_config(to_many),
|
|
73
73
|
)
|
|
74
74
|
|
|
75
75
|
def __map_config(self, config: Dict[str, str]) -> Dict[str, str]:
|
tol/sql/sql_converter.py
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
# SPDX-License-Identifier: MIT
|
|
4
4
|
|
|
5
5
|
from abc import ABC
|
|
6
|
-
from typing import Any, Callable
|
|
6
|
+
from typing import Any, Callable
|
|
7
7
|
|
|
8
8
|
from .model import Model
|
|
9
|
-
from ..core import DataObject
|
|
9
|
+
from ..core import DataObject, ReqFieldsTree
|
|
10
10
|
from ..core.core_converter import Converter
|
|
11
11
|
from ..core.factory import DataObjectFactory
|
|
12
12
|
|
|
@@ -15,14 +15,6 @@ TypeFunction = Callable[[Model], str]
|
|
|
15
15
|
"""Takes a Model instance, and returns the corresponding DataObject type."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
In = TypeVar('In')
|
|
19
|
-
"""The input representation type"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Out = TypeVar('Out')
|
|
23
|
-
"""The output representation type"""
|
|
24
|
-
|
|
25
|
-
|
|
26
18
|
class ModelConverter(Converter[Model, DataObject], ABC):
|
|
27
19
|
"""
|
|
28
20
|
Converts Sqlalchemy model instances to DataObject instances.
|
|
@@ -30,13 +22,11 @@ class ModelConverter(Converter[Model, DataObject], ABC):
|
|
|
30
22
|
|
|
31
23
|
|
|
32
24
|
class DefaultModelConverter(ModelConverter):
|
|
33
|
-
|
|
34
25
|
def __init__(
|
|
35
26
|
self,
|
|
36
27
|
type_function: TypeFunction,
|
|
37
28
|
data_object_factory: DataObjectFactory,
|
|
38
|
-
|
|
39
|
-
requested_fields: list[str] | None = None,
|
|
29
|
+
requested_tree: ReqFieldsTree,
|
|
40
30
|
) -> None:
|
|
41
31
|
"""
|
|
42
32
|
Takes a type_function Callable, which determines the type of the
|
|
@@ -45,116 +35,55 @@ class DefaultModelConverter(ModelConverter):
|
|
|
45
35
|
|
|
46
36
|
self.__type_function = type_function
|
|
47
37
|
self.__data_object_factory = data_object_factory
|
|
48
|
-
|
|
49
|
-
self.__requested_fields = requested_fields
|
|
50
|
-
self.__max_depth = (
|
|
51
|
-
None
|
|
52
|
-
if self.__requested_fields
|
|
53
|
-
else max_depth
|
|
54
|
-
)
|
|
38
|
+
self.__requested_tree = requested_tree
|
|
55
39
|
|
|
56
40
|
def convert(self, model: Model) -> DataObject:
|
|
57
|
-
return self.__convert_to_max_depth(
|
|
58
|
-
model,
|
|
59
|
-
self.__initial_marker
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
def __convert_to_max_depth(
|
|
63
|
-
self,
|
|
64
|
-
model: Model | None,
|
|
65
|
-
marker: int
|
|
66
|
-
) -> DataObject:
|
|
67
|
-
|
|
68
41
|
if model is None:
|
|
69
42
|
return None
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
to_one=self.__convert_to_ones(
|
|
78
|
-
model,
|
|
79
|
-
marker
|
|
80
|
-
)
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def __initial_marker(self) -> int | str:
|
|
85
|
-
return (
|
|
86
|
-
''
|
|
87
|
-
if self.__requested_fields
|
|
88
|
-
else 0
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
def __convert_to_ones(
|
|
92
|
-
self,
|
|
93
|
-
model: Model,
|
|
94
|
-
marker: int | str
|
|
95
|
-
) -> dict[str, Optional[DataObject]]:
|
|
96
|
-
|
|
97
|
-
if self.__max_depth and marker >= self.__max_depth:
|
|
98
|
-
return {}
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
k: self.__convert_to_max_depth(
|
|
102
|
-
model.instance_to_one_relations[k],
|
|
103
|
-
self.__get_next_marker(
|
|
104
|
-
k,
|
|
105
|
-
marker,
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
for k in self.__get_requested_to_ones(
|
|
109
|
-
model,
|
|
110
|
-
marker,
|
|
43
|
+
if tree := self.__requested_tree:
|
|
44
|
+
return self.__convert_requested(model, tree)
|
|
45
|
+
else:
|
|
46
|
+
return self.__data_object_factory(
|
|
47
|
+
self.__type_function(model),
|
|
48
|
+
id_=model.instance_id,
|
|
49
|
+
attributes=model.instance_attributes,
|
|
111
50
|
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
def __get_next_marker(
|
|
115
|
-
self,
|
|
116
|
-
k: str,
|
|
117
|
-
marker: int | str
|
|
118
|
-
) -> int | str:
|
|
119
51
|
|
|
120
|
-
|
|
121
|
-
|
|
52
|
+
def __convert_requested(self, model, tree):
|
|
53
|
+
if attr_names := tree.attribute_names:
|
|
54
|
+
attributes = {x: getattr(model, x) for x in attr_names if x != 'id'}
|
|
122
55
|
else:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def __get_requested_to_ones(
|
|
126
|
-
self,
|
|
127
|
-
model_instance: Model,
|
|
128
|
-
marker: int | str
|
|
129
|
-
) -> list[str]:
|
|
130
|
-
|
|
131
|
-
all_keys = list(
|
|
132
|
-
model_instance.get_to_one_relationship_config().keys()
|
|
133
|
-
)
|
|
56
|
+
attributes = model.instance_attributes
|
|
134
57
|
|
|
135
|
-
|
|
136
|
-
|
|
58
|
+
req_to_ones = self.__convert_to_ones_requested(model, tree)
|
|
59
|
+
req_to_many = self.__convert_to_many_requested(model, tree)
|
|
137
60
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
self,
|
|
145
|
-
k: str,
|
|
146
|
-
marker: int | str
|
|
147
|
-
) -> bool:
|
|
148
|
-
|
|
149
|
-
next_marker = self.__get_next_marker(
|
|
150
|
-
k,
|
|
151
|
-
marker,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
return any(
|
|
155
|
-
r.startswith(next_marker)
|
|
156
|
-
for r in self.__requested_fields
|
|
61
|
+
obj = self.__data_object_factory(
|
|
62
|
+
self.__type_function(model),
|
|
63
|
+
id_=model.instance_id,
|
|
64
|
+
attributes=attributes,
|
|
65
|
+
to_one=req_to_ones,
|
|
66
|
+
to_many=req_to_many,
|
|
157
67
|
)
|
|
68
|
+
return obj
|
|
69
|
+
|
|
70
|
+
def __convert_to_ones_requested(self, model, tree):
|
|
71
|
+
to_ones = {}
|
|
72
|
+
for rel_name, remote in model.get_to_one_relationship_config().items():
|
|
73
|
+
one = None
|
|
74
|
+
if sub_tree := tree.get_sub_tree(rel_name):
|
|
75
|
+
if sub_model := getattr(model, rel_name):
|
|
76
|
+
one = self.__convert_requested(sub_model, sub_tree)
|
|
77
|
+
else:
|
|
78
|
+
# Create a stub DataObject
|
|
79
|
+
rel_col = model.get_foreign_key_name(rel_name)
|
|
80
|
+
if rel_id := getattr(model, rel_col):
|
|
81
|
+
one = self.__data_object_factory(remote, id_=rel_id)
|
|
82
|
+
to_ones[rel_name] = one
|
|
83
|
+
return to_ones if to_ones else None
|
|
84
|
+
|
|
85
|
+
def __convert_to_many_requested(self, model, tree):
|
|
86
|
+
return None
|
|
158
87
|
|
|
159
88
|
|
|
160
89
|
class DataObjectConverter(Converter[DataObject, Model], ABC):
|
|
@@ -164,11 +93,7 @@ class DataObjectConverter(Converter[DataObject, Model], ABC):
|
|
|
164
93
|
|
|
165
94
|
|
|
166
95
|
class DefaultDataObjectConverter(DataObjectConverter):
|
|
167
|
-
|
|
168
|
-
def __init__(
|
|
169
|
-
self,
|
|
170
|
-
type_models_dict: dict[str, type[Model]]
|
|
171
|
-
) -> None:
|
|
96
|
+
def __init__(self, type_models_dict: dict[str, type[Model]]) -> None:
|
|
172
97
|
"""
|
|
173
98
|
`type_models_dict` maps object type to the
|
|
174
99
|
corresponding `type[Model]` class.
|
|
@@ -182,40 +107,22 @@ class DefaultDataObjectConverter(DataObjectConverter):
|
|
|
182
107
|
return model_class(
|
|
183
108
|
**self.__get_id_dict(input_.id, model_class),
|
|
184
109
|
**input_.attributes,
|
|
185
|
-
**self.__get_relation_dict(
|
|
186
|
-
model_class,
|
|
187
|
-
input_._to_one_objects
|
|
188
|
-
)
|
|
110
|
+
**self.__get_relation_dict(model_class, input_._to_one_objects),
|
|
189
111
|
)
|
|
190
112
|
|
|
191
|
-
def __get_id_dict(
|
|
192
|
-
self,
|
|
193
|
-
id_: str,
|
|
194
|
-
model_class: type[Model]
|
|
195
|
-
) -> dict[str, str]:
|
|
196
|
-
|
|
113
|
+
def __get_id_dict(self, id_: str, model_class: type[Model]) -> dict[str, str]:
|
|
197
114
|
id_column_name = model_class.get_id_column_name()
|
|
198
115
|
return {id_column_name: id_}
|
|
199
116
|
|
|
200
117
|
def __get_relation_dict(
|
|
201
|
-
self,
|
|
202
|
-
model_class: type[Model],
|
|
203
|
-
ones: dict[str, DataObject]
|
|
118
|
+
self, model_class: type[Model], ones: dict[str, DataObject]
|
|
204
119
|
) -> dict[str, str]:
|
|
205
120
|
# TODO validation - relationship names and their types
|
|
206
121
|
|
|
207
122
|
return {
|
|
208
|
-
model_class.get_foreign_key_name(
|
|
209
|
-
rel_name
|
|
210
|
-
): self.__map_to_foreign_key(rel_obj)
|
|
123
|
+
model_class.get_foreign_key_name(rel_name): self.__map_to_foreign_key(rel_obj)
|
|
211
124
|
for rel_name, rel_obj in ones.items()
|
|
212
125
|
}
|
|
213
126
|
|
|
214
|
-
def __map_to_foreign_key(
|
|
215
|
-
|
|
216
|
-
rel_obj: DataObject | None
|
|
217
|
-
) -> Any | None:
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
None if rel_obj is None else rel_obj.id
|
|
221
|
-
)
|
|
127
|
+
def __map_to_foreign_key(self, rel_obj: DataObject | None) -> Any | None:
|
|
128
|
+
return None if rel_obj is None else rel_obj.id
|