spinta 0.2.dev28__py3-none-any.whl → 0.2.dev29__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.
- spinta/api/__init__.py +1 -0
- spinta/auth.py +1 -2
- spinta/backends/__init__.py +26 -1
- spinta/backends/components.py +3 -0
- spinta/backends/postgresql/helpers/exceptions.py +1 -3
- spinta/backends/postgresql/ufuncs/query/components.py +5 -7
- spinta/backends/postgresql/ufuncs/query/ufuncs.py +4 -4
- spinta/cli/helpers/admin/scripts/add_local_ids.py +1 -1
- spinta/cli/helpers/admin/scripts/remove_local_ids.py +1 -1
- spinta/cli/helpers/push/read.py +2 -2
- spinta/cli/helpers/store.py +1 -1
- spinta/cli/pii.py +1 -1
- spinta/components.py +6 -2
- spinta/datasets/backends/dataframe/backends/csv/components.py +3 -0
- spinta/datasets/backends/dataframe/backends/memory/components.py +3 -0
- spinta/datasets/backends/dataframe/commands/read.py +3 -15
- spinta/datasets/backends/dataframe/ufuncs/query/components.py +2 -3
- spinta/datasets/backends/dataframe/ufuncs/query/helpers.py +76 -0
- spinta/datasets/backends/dataframe/ufuncs/query/ufuncs.py +82 -23
- spinta/datasets/backends/helpers.py +52 -154
- spinta/datasets/backends/sql/commands/cast.py +2 -230
- spinta/datasets/backends/sql/commands/read.py +3 -5
- spinta/datasets/backends/sql/helpers.py +2 -7
- spinta/datasets/backends/sql/ufuncs/query/ufuncs.py +19 -11
- spinta/datasets/commands/cast.py +308 -0
- spinta/datasets/commands/load.py +1 -1
- spinta/datasets/components.py +3 -0
- spinta/datasets/enums.py +41 -0
- spinta/datasets/helpers.py +96 -3
- spinta/datasets/keymaps/components.py +4 -0
- spinta/datasets/keymaps/helpers.py +19 -0
- spinta/datasets/keymaps/redis.py +7 -0
- spinta/datasets/keymaps/sqlalchemy.py +13 -1
- spinta/formats/helpers.py +1 -1
- spinta/formats/html/commands.py +3 -2
- spinta/formats/html/helpers.py +1 -1
- spinta/testing/client.py +2 -2
- spinta/testing/data.py +10 -10
- spinta/testing/utils.py +2 -2
- spinta/types/model.py +18 -5
- spinta/types/namespace.py +3 -1
- spinta/types/ref/link.py +1 -1
- spinta/utils/nestedstruct.py +2 -2
- spinta/utils/response.py +2 -2
- {spinta-0.2.dev28.dist-info → spinta-0.2.dev29.dist-info}/METADATA +2 -2
- {spinta-0.2.dev28.dist-info → spinta-0.2.dev29.dist-info}/RECORD +49 -49
- spinta/datasets/backends/sql/backends/oracle/types/__init__.py +0 -0
- spinta/datasets/backends/sql/backends/oracle/types/geometry/__init__.py +0 -0
- spinta/datasets/backends/sql/backends/oracle/types/geometry/encode.py +0 -41
- {spinta-0.2.dev28.dist-info → spinta-0.2.dev29.dist-info}/WHEEL +0 -0
- {spinta-0.2.dev28.dist-info → spinta-0.2.dev29.dist-info}/entry_points.txt +0 -0
- {spinta-0.2.dev28.dist-info → spinta-0.2.dev29.dist-info}/licenses/LICENSE +0 -0
spinta/api/__init__.py
CHANGED
spinta/auth.py
CHANGED
|
@@ -538,7 +538,7 @@ class AdminToken(rfc6749.TokenMixin):
|
|
|
538
538
|
return True
|
|
539
539
|
|
|
540
540
|
def check_scope(self, scope: SCOPE_TYPE, **kwargs) -> bool:
|
|
541
|
-
|
|
541
|
+
return True
|
|
542
542
|
|
|
543
543
|
def get_sub(self) -> str: # User.
|
|
544
544
|
return "admin"
|
|
@@ -864,7 +864,6 @@ def authorized(
|
|
|
864
864
|
|
|
865
865
|
# Unauthenticated clients can only access nodes if spinta config.access is open.
|
|
866
866
|
unauthenticated = token.get_client_id() == get_default_auth_client_id(context)
|
|
867
|
-
|
|
868
867
|
if unauthenticated and config.access < Access.open:
|
|
869
868
|
if throw:
|
|
870
869
|
raise AuthorizedClientsOnly()
|
spinta/backends/__init__.py
CHANGED
|
@@ -590,7 +590,7 @@ def is_object_id(context: Context, value: str):
|
|
|
590
590
|
|
|
591
591
|
@is_object_id.register(Context, Backend, Model, str)
|
|
592
592
|
def is_object_id(context: Context, backend: Backend, model: Model, value: str):
|
|
593
|
-
return is_object_id(context, backend, model.
|
|
593
|
+
return is_object_id(context, backend, model.id_prop.dtype, value)
|
|
594
594
|
|
|
595
595
|
|
|
596
596
|
@is_object_id.register(Context, Backend, PrimaryKey, str)
|
|
@@ -1940,6 +1940,31 @@ def cast_backend_to_python(context: Context, dtype: Ref, backend: Backend, data:
|
|
|
1940
1940
|
return processed_data
|
|
1941
1941
|
|
|
1942
1942
|
|
|
1943
|
+
@commands.cast_backend_to_python.register(Context, ExternalRef, Backend, dict)
|
|
1944
|
+
def cast_backend_to_python(
|
|
1945
|
+
context: Context, dtype: ExternalRef, backend: Backend, data: Dict[str, Any], **kwargs
|
|
1946
|
+
) -> Any:
|
|
1947
|
+
if not data:
|
|
1948
|
+
return data
|
|
1949
|
+
|
|
1950
|
+
processed_data = {}
|
|
1951
|
+
for key in data:
|
|
1952
|
+
prop = commands.resolve_property(dtype.prop.model, f"{dtype.prop.place}.{key}")
|
|
1953
|
+
if prop is not None:
|
|
1954
|
+
processed_data[key] = commands.cast_backend_to_python(context, prop, backend, data[key], **kwargs)
|
|
1955
|
+
|
|
1956
|
+
for prop in dtype.refprops:
|
|
1957
|
+
if prop.name not in processed_data and prop.name in data:
|
|
1958
|
+
processed_data[prop.name] = commands.cast_backend_to_python(
|
|
1959
|
+
context, prop, backend, data[prop.name], **kwargs
|
|
1960
|
+
)
|
|
1961
|
+
|
|
1962
|
+
if not processed_data or all(value is None for value in processed_data.values()):
|
|
1963
|
+
return None
|
|
1964
|
+
|
|
1965
|
+
return processed_data
|
|
1966
|
+
|
|
1967
|
+
|
|
1943
1968
|
@commands.cast_backend_to_python.register(Context, Object, Backend, dict)
|
|
1944
1969
|
def cast_backend_to_python(
|
|
1945
1970
|
context: Context, dtype: Object, backend: Backend, data: Dict[str, Any], **kwargs
|
spinta/backends/components.py
CHANGED
|
@@ -36,6 +36,9 @@ class Backend:
|
|
|
36
36
|
result_builder_type: str = ""
|
|
37
37
|
result_builder_class: Type[ResultBuilder]
|
|
38
38
|
|
|
39
|
+
# Some backends might only work with source given to models and some might not need it
|
|
40
|
+
model_requires_source: bool = False
|
|
41
|
+
|
|
39
42
|
def __repr__(self):
|
|
40
43
|
return f"<{self.__class__.__module__}.{self.__class__.__name__}(name={self.name!r}) at 0x{id(self):02x}>"
|
|
41
44
|
|
|
@@ -19,9 +19,7 @@ def create_exception(item: DataItem, error: psy_errors.ForeignKeyViolation):
|
|
|
19
19
|
error_message = error.diag.message_detail
|
|
20
20
|
if item.action is Action.DELETE:
|
|
21
21
|
error_model = extractor.extract_error_model(error_message)
|
|
22
|
-
return exceptions.ReferringObjectFound(
|
|
23
|
-
item.model.properties.get("_id"), model=error_model, id=item.saved.get("_id")
|
|
24
|
-
)
|
|
22
|
+
return exceptions.ReferringObjectFound(item.model.id_prop, model=error_model, id=item.saved.get("_id"))
|
|
25
23
|
else:
|
|
26
24
|
error_property_names = extractor.extract_error_property_names(error_message)
|
|
27
25
|
error_ref_id = extractor.extract_error_ref_id(error_message)
|
|
@@ -57,11 +57,9 @@ class PgQueryBuilder(QueryBuilder):
|
|
|
57
57
|
self.call("select", Expr("select"))
|
|
58
58
|
|
|
59
59
|
if not self.aggregate:
|
|
60
|
-
self.selected["_id"] = Selected(
|
|
61
|
-
item=self.add_column(self.table.c["_id"]), prop=self.model.properties["_id"]
|
|
62
|
-
)
|
|
60
|
+
self.selected["_id"] = Selected(item=self.add_column(self.table.c["_id"]), prop=self.model.id_prop)
|
|
63
61
|
self.selected["_revision"] = Selected(
|
|
64
|
-
item=self.add_column(self.table.c["_revision"]), prop=self.model.
|
|
62
|
+
item=self.add_column(self.table.c["_revision"]), prop=self.model.revision_prop
|
|
65
63
|
)
|
|
66
64
|
merged_selected = merge_with_page_selected_list(self.columns, self.page)
|
|
67
65
|
merged_sorted = merge_with_page_sort(self.sort, self.page)
|
|
@@ -116,7 +114,7 @@ class PgQueryBuilder(QueryBuilder):
|
|
|
116
114
|
else:
|
|
117
115
|
rtable = self.backend.get_table(rmodel).alias()
|
|
118
116
|
|
|
119
|
-
rpkey = self.backend.get_column(rtable, rmodel.
|
|
117
|
+
rpkey = self.backend.get_column(rtable, rmodel.id_prop)
|
|
120
118
|
|
|
121
119
|
if isinstance(lrkey, list) and not isinstance(rpkey, list):
|
|
122
120
|
if len(lrkey) == 1:
|
|
@@ -199,10 +197,10 @@ class PgQueryBuilder(QueryBuilder):
|
|
|
199
197
|
raise PropertyNotFound(model, property=prop)
|
|
200
198
|
|
|
201
199
|
ltable = self.backend.get_table(inherit_model)
|
|
202
|
-
lrkey = self.backend.get_column(ltable, inherit_model.
|
|
200
|
+
lrkey = self.backend.get_column(ltable, inherit_model.id_prop)
|
|
203
201
|
|
|
204
202
|
rtable = self.backend.get_table(base_model).alias()
|
|
205
|
-
rpkey = self.backend.get_column(rtable, base_model.
|
|
203
|
+
rpkey = self.backend.get_column(rtable, base_model.id_prop)
|
|
206
204
|
|
|
207
205
|
if base_model.name in self.joins:
|
|
208
206
|
if self.joins[base_model.name] == rtable:
|
|
@@ -287,7 +287,7 @@ def select(env, dtype: Ref):
|
|
|
287
287
|
if not dtype.inherited:
|
|
288
288
|
name = "_id"
|
|
289
289
|
if env.query_params.prioritize_uri and uri is not None:
|
|
290
|
-
fpr = ForeignProperty(None, dtype, dtype.model.
|
|
290
|
+
fpr = ForeignProperty(None, dtype, dtype.model.id_prop.dtype)
|
|
291
291
|
table = env.get_joined_table(fpr)
|
|
292
292
|
column = table.c[uri.place]
|
|
293
293
|
name = "_uri"
|
|
@@ -311,7 +311,7 @@ def select(env, dtype: ExternalRef):
|
|
|
311
311
|
if dtype.model.given.pkeys or dtype.explicit:
|
|
312
312
|
props = dtype.refprops
|
|
313
313
|
else:
|
|
314
|
-
props = [dtype.model.
|
|
314
|
+
props = [dtype.model.id_prop]
|
|
315
315
|
for prop in props:
|
|
316
316
|
name = f"{dtype.prop.place}.{prop.place}"
|
|
317
317
|
column = table.c[name]
|
|
@@ -350,7 +350,7 @@ def _select_backref(env, dtype, is_array=False):
|
|
|
350
350
|
return_columns = {}
|
|
351
351
|
|
|
352
352
|
if commands.identifiable(refprop):
|
|
353
|
-
id_ = fpr.right.prop.model.
|
|
353
|
+
id_ = fpr.right.prop.model.id_prop
|
|
354
354
|
column_name = id_.name
|
|
355
355
|
label = f"{dtype.prop.name}.{column_name}"
|
|
356
356
|
required_columns.append((column_name, label))
|
|
@@ -431,7 +431,7 @@ def _denorm_to_foreign_property(env: PgQueryBuilder, dtype: Denorm):
|
|
|
431
431
|
and isinstance(root_ref_parent, Property)
|
|
432
432
|
and isinstance(root_ref_parent.dtype, (Ref, BackRef))
|
|
433
433
|
):
|
|
434
|
-
fpr = ForeignProperty(fpr, root_ref_parent.dtype, root_ref_parent.dtype.model.
|
|
434
|
+
fpr = ForeignProperty(fpr, root_ref_parent.dtype, root_ref_parent.dtype.model.id_prop.dtype)
|
|
435
435
|
|
|
436
436
|
if not root_ref_parent.dtype.inherited:
|
|
437
437
|
break
|
|
@@ -25,7 +25,7 @@ def add_explicit_id_properties(context: Context, manifest: str, verbose: bool =
|
|
|
25
25
|
if verbose:
|
|
26
26
|
echo(f'Model "{model.model_type()}" has no `ref`, skipping.', err=True)
|
|
27
27
|
continue
|
|
28
|
-
prop = model.
|
|
28
|
+
prop = model.id_prop
|
|
29
29
|
if prop is None or prop.explicitly_given:
|
|
30
30
|
continue
|
|
31
31
|
|
|
@@ -19,7 +19,7 @@ def remove_explicit_id_properties(context: Context, manifest: str) -> int:
|
|
|
19
19
|
for model in commands.get_models(context, manifest).values():
|
|
20
20
|
if model.model_type().startswith(RESERVED_MODEL_PREFIX):
|
|
21
21
|
continue
|
|
22
|
-
prop = model.
|
|
22
|
+
prop = model.id_prop
|
|
23
23
|
if prop is None or not prop.explicitly_given:
|
|
24
24
|
continue
|
|
25
25
|
|
spinta/cli/helpers/push/read.py
CHANGED
|
@@ -55,9 +55,9 @@ def _iter_model_rows(
|
|
|
55
55
|
else:
|
|
56
56
|
stream = read_model_data(context, model, limit, stop_on_error, params=params)
|
|
57
57
|
for item in stream:
|
|
58
|
-
if push_counter:
|
|
58
|
+
if push_counter is not None:
|
|
59
59
|
push_counter.update(1)
|
|
60
|
-
if model_push_counter:
|
|
60
|
+
if model_push_counter is not None:
|
|
61
61
|
model_push_counter.update(1)
|
|
62
62
|
yield PushRow(model, item)
|
|
63
63
|
|
spinta/cli/helpers/store.py
CHANGED
|
@@ -166,4 +166,4 @@ def attach_backends(context: Context, store: Store, manifest: Manifest) -> None:
|
|
|
166
166
|
|
|
167
167
|
def attach_keymaps(context: Context, store: Store) -> None:
|
|
168
168
|
for keymap in store.keymaps.values():
|
|
169
|
-
context.attach(f"keymap.{keymap.name}", lambda: keymap)
|
|
169
|
+
context.attach(f"keymap.{keymap.name}", lambda: keymap.copy())
|
spinta/cli/pii.py
CHANGED
|
@@ -220,7 +220,7 @@ def detect(
|
|
|
220
220
|
f"transaction.{resource.backend.name}", validate_and_return_begin, context, resource.backend
|
|
221
221
|
)
|
|
222
222
|
for keymap in store.keymaps.values():
|
|
223
|
-
context.attach(f"keymap.{keymap.name}", lambda: keymap)
|
|
223
|
+
context.attach(f"keymap.{keymap.name}", lambda: keymap.copy())
|
|
224
224
|
|
|
225
225
|
ns = commands.get_namespace(context, manifest, "")
|
|
226
226
|
models = commands.traverse_ns_models(context, ns, manifest, Action.SEARCH)
|
spinta/components.py
CHANGED
|
@@ -662,7 +662,6 @@ class Model(MetaData):
|
|
|
662
662
|
comments: List[Comment] = None
|
|
663
663
|
base: Base = None
|
|
664
664
|
uri: str = None
|
|
665
|
-
uri_prop: Property = None
|
|
666
665
|
page: PageInfo = None
|
|
667
666
|
features: str = None
|
|
668
667
|
status: Status | None = None
|
|
@@ -675,6 +674,11 @@ class Model(MetaData):
|
|
|
675
674
|
distribution_strategy: DistributionStrategy | None = None
|
|
676
675
|
required_keymap_properties = None
|
|
677
676
|
|
|
677
|
+
# Quick access properties
|
|
678
|
+
uri_prop: Property | None = None
|
|
679
|
+
id_prop: Property
|
|
680
|
+
revision_prop: Property
|
|
681
|
+
|
|
678
682
|
schema = {
|
|
679
683
|
"keymap": {"type": "string"},
|
|
680
684
|
"backend": {"type": "string"},
|
|
@@ -741,7 +745,7 @@ class Model(MetaData):
|
|
|
741
745
|
# return self.name.split('/')[-1]
|
|
742
746
|
|
|
743
747
|
def add_keymap_property_combination(self, given_props: List[Property]):
|
|
744
|
-
extract_names =
|
|
748
|
+
extract_names = tuple([prop.place for prop in given_props])
|
|
745
749
|
if extract_names not in self.required_keymap_properties:
|
|
746
750
|
self.required_keymap_properties.append(extract_names)
|
|
747
751
|
|
|
@@ -6,6 +6,9 @@ from spinta.datasets.backends.dataframe.components import DaskBackend
|
|
|
6
6
|
class Csv(DaskBackend):
|
|
7
7
|
type: str = "dask/csv"
|
|
8
8
|
|
|
9
|
+
# Csv data is mainly one table, meaning model.source is usually not given
|
|
10
|
+
model_requires_source: bool = False
|
|
11
|
+
|
|
9
12
|
@contextlib.contextmanager
|
|
10
13
|
def begin(self):
|
|
11
14
|
yield
|
|
@@ -6,6 +6,9 @@ from spinta.datasets.backends.dataframe.components import DaskBackend
|
|
|
6
6
|
class MemoryDaskBackend(DaskBackend):
|
|
7
7
|
type: str = "dask/memory"
|
|
8
8
|
|
|
9
|
+
# Memory backends can set model.source, but it can also leave it open and still work
|
|
10
|
+
model_requires_source: bool = False
|
|
11
|
+
|
|
9
12
|
@contextlib.contextmanager
|
|
10
13
|
def begin(self):
|
|
11
14
|
yield
|
|
@@ -16,16 +16,14 @@ from spinta.core.ufuncs import Env, Expr
|
|
|
16
16
|
from spinta.datasets.backends.dataframe.backends.memory.components import MemoryDaskBackend
|
|
17
17
|
from spinta.datasets.backends.dataframe.components import DaskBackend
|
|
18
18
|
from spinta.datasets.backends.dataframe.ufuncs.query.components import DaskDataFrameQueryBuilder
|
|
19
|
-
from spinta.datasets.backends.helpers import handle_ref_key_assignment
|
|
20
19
|
from spinta.datasets.components import Resource
|
|
21
20
|
from spinta.datasets.helpers import encode_composite_string_id, get_enum_filters, get_ref_filters
|
|
22
|
-
from spinta.datasets.keymaps.components import KeyMap
|
|
23
21
|
from spinta.datasets.utils import iterparams
|
|
24
22
|
from spinta.dimensions.enum.helpers import get_prop_enum
|
|
25
23
|
from spinta.dimensions.param.components import ResolvedParams
|
|
26
24
|
from spinta.exceptions import NoExternalName, PropertyNotFound, ValueNotInEnum
|
|
27
25
|
from spinta.manifests.components import Manifest
|
|
28
|
-
from spinta.types.datatype import Base32, Boolean, DataType, DateTime, Integer, Number,
|
|
26
|
+
from spinta.types.datatype import Base32, Boolean, DataType, DateTime, Integer, Number, Ref
|
|
29
27
|
from spinta.typing import ObjectData
|
|
30
28
|
from spinta.ufuncs.helpers import merge_formulas
|
|
31
29
|
from spinta.ufuncs.querybuilder.components import Selected
|
|
@@ -182,7 +180,7 @@ def _select_primary_key(model: Model, df: DataFrame):
|
|
|
182
180
|
result = _select(prop, df)
|
|
183
181
|
else:
|
|
184
182
|
result = [_select(prop, df) for prop in pkeys]
|
|
185
|
-
return Selected(prop=model.
|
|
183
|
+
return Selected(prop=model.id_prop, prep=result)
|
|
186
184
|
|
|
187
185
|
|
|
188
186
|
def get_pkeys_if_ref(prop: Property) -> list[str]:
|
|
@@ -258,7 +256,6 @@ def dask_get_all(
|
|
|
258
256
|
params: dict | None = None,
|
|
259
257
|
):
|
|
260
258
|
params = params or {}
|
|
261
|
-
keymap: KeyMap = context.get(f"keymap.{model.keymap.name}")
|
|
262
259
|
|
|
263
260
|
query = merge_formulas(model.external.prepare, query)
|
|
264
261
|
query = merge_formulas(query, get_enum_filters(context, model))
|
|
@@ -279,16 +276,7 @@ def dask_get_all(
|
|
|
279
276
|
for key, sel in env_selected.items():
|
|
280
277
|
val = _get_row_value(context, row, sel, env.params)
|
|
281
278
|
if sel.prop:
|
|
282
|
-
if isinstance(sel.prop.dtype,
|
|
283
|
-
if isinstance(val, list):
|
|
284
|
-
val = [
|
|
285
|
-
list_value.get("_id", list_value) if isinstance(list_value, dict) else list_value
|
|
286
|
-
for list_value in val
|
|
287
|
-
]
|
|
288
|
-
val = keymap.encode(sel.prop.model.model_type(), val)
|
|
289
|
-
elif isinstance(sel.prop.dtype, Ref):
|
|
290
|
-
val = handle_ref_key_assignment(context, keymap, env, val, sel.prop.dtype)
|
|
291
|
-
elif is_custom_id_prop(sel.prop) and isinstance(val, list) and not isinstance(sel.prop.dtype, Base32):
|
|
279
|
+
if is_custom_id_prop(sel.prop) and isinstance(val, list) and not isinstance(sel.prop.dtype, Base32):
|
|
292
280
|
val = encode_composite_string_id(val, model.external.pkeys)
|
|
293
281
|
res[key] = val
|
|
294
282
|
res = flat_dicts_to_nested(res, list_keys=list_keys)
|
|
@@ -15,6 +15,8 @@ from spinta.ufuncs.propertyresolver.components import PropertyResolver
|
|
|
15
15
|
from spinta.ufuncs.querybuilder.components import Func, Selected
|
|
16
16
|
from spinta.utils.schema import NA
|
|
17
17
|
|
|
18
|
+
RESERVED_COUNT_PROP = "__dask_count"
|
|
19
|
+
|
|
18
20
|
|
|
19
21
|
class DaskDataFrameQueryBuilder(Env):
|
|
20
22
|
backend: DaskBackend
|
|
@@ -90,9 +92,6 @@ class DaskSelected(Selected):
|
|
|
90
92
|
prep: Any = NA
|
|
91
93
|
|
|
92
94
|
|
|
93
|
-
RESERVED_COUNT_PROP = "__dask_count"
|
|
94
|
-
|
|
95
|
-
|
|
96
95
|
@dataclasses.dataclass
|
|
97
96
|
class Count(Func):
|
|
98
97
|
pass
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from spinta.datasets.backends.dataframe.ufuncs.query.components import (
|
|
4
|
+
DaskDataFrameQueryBuilder,
|
|
5
|
+
)
|
|
6
|
+
from spinta.datasets.backends.dataframe.ufuncs.query.components import (
|
|
7
|
+
DaskSelected as Selected,
|
|
8
|
+
)
|
|
9
|
+
from spinta.exceptions import (
|
|
10
|
+
GivenValueCountMissmatch,
|
|
11
|
+
PropertyNotPartOfRefProps,
|
|
12
|
+
)
|
|
13
|
+
from spinta.types.datatype import ExternalRef, Ref
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def select_ref_foreign_key_properties(
|
|
17
|
+
env: DaskDataFrameQueryBuilder, dtype: Ref, *, properties: list = None
|
|
18
|
+
) -> dict[str, Selected]:
|
|
19
|
+
if properties is None:
|
|
20
|
+
if dtype.prop.external and dtype.prop.external.prepare:
|
|
21
|
+
properties = env(this=dtype.prop).resolve(dtype.prop.external.prepare)
|
|
22
|
+
|
|
23
|
+
prep = {}
|
|
24
|
+
given_mapping_count = len(properties) if properties else 1
|
|
25
|
+
if len(dtype.refprops) != given_mapping_count:
|
|
26
|
+
raise GivenValueCountMissmatch(dtype, given_count=given_mapping_count, expected_count=len(dtype.refprops))
|
|
27
|
+
|
|
28
|
+
if properties is None:
|
|
29
|
+
refprop = dtype.refprops[0]
|
|
30
|
+
# Currently, dask can only access its own dataframe columns, so we we have to use dtype.prop.external.name as source.
|
|
31
|
+
prep[refprop.name] = Selected(item=dtype.prop.external.name, prop=refprop)
|
|
32
|
+
else:
|
|
33
|
+
for i, prop in enumerate(dtype.refprops):
|
|
34
|
+
sel = env.call("select", properties[i])
|
|
35
|
+
if sel.prop is None:
|
|
36
|
+
sel.prop = prop
|
|
37
|
+
prep[prop.name] = sel
|
|
38
|
+
return prep
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def select_external_ref_foreign_key_properties(
|
|
42
|
+
env: DaskDataFrameQueryBuilder, dtype: ExternalRef, *, properties: list = None, target: str = None
|
|
43
|
+
) -> dict[str, Selected]:
|
|
44
|
+
if properties is None:
|
|
45
|
+
if dtype.prop.external and dtype.prop.external.prepare:
|
|
46
|
+
properties = env(this=dtype.prop).resolve(dtype.prop.external.prepare)
|
|
47
|
+
|
|
48
|
+
if target is not None:
|
|
49
|
+
value = next((prop for prop in dtype.refprops if prop.name == target), None)
|
|
50
|
+
if value is None:
|
|
51
|
+
raise PropertyNotPartOfRefProps(dtype, prop=target, refprops=list(prop_.name for prop_ in dtype.refprops))
|
|
52
|
+
target = value
|
|
53
|
+
|
|
54
|
+
prep = {}
|
|
55
|
+
given_mapping_count = len(properties) if properties else 1
|
|
56
|
+
if len(dtype.refprops) != given_mapping_count:
|
|
57
|
+
raise GivenValueCountMissmatch(dtype, given_count=given_mapping_count, expected_count=len(dtype.refprops))
|
|
58
|
+
|
|
59
|
+
if properties is None:
|
|
60
|
+
refprop = dtype.refprops[0]
|
|
61
|
+
# Currently, dask can only access its own dataframe columns, so we we have to use dtype.prop.external.name as source.
|
|
62
|
+
prep[refprop.name] = Selected(item=dtype.prop.external.name, prop=refprop)
|
|
63
|
+
else:
|
|
64
|
+
if target is not None:
|
|
65
|
+
pos = dtype.refprops.index(target)
|
|
66
|
+
sel = env.call("select", properties[pos])
|
|
67
|
+
if sel.prop is None:
|
|
68
|
+
sel.prop = target
|
|
69
|
+
prep[target.name] = sel
|
|
70
|
+
else:
|
|
71
|
+
for i, prop in enumerate(dtype.refprops):
|
|
72
|
+
sel = env.call("select", properties[i])
|
|
73
|
+
if sel.prop is None:
|
|
74
|
+
sel.prop = prop
|
|
75
|
+
prep[prop.name] = sel
|
|
76
|
+
return prep
|
|
@@ -5,7 +5,6 @@ from typing import Any, Dict, Iterator, List, Tuple
|
|
|
5
5
|
|
|
6
6
|
from dask.dataframe import Series
|
|
7
7
|
|
|
8
|
-
from spinta.auth import authorized
|
|
9
8
|
from spinta.backends.helpers import is_custom_id_prop, is_custom_revision_prop
|
|
10
9
|
from spinta.components import Property
|
|
11
10
|
from spinta.core.enums import Action
|
|
@@ -18,7 +17,13 @@ from spinta.datasets.backends.dataframe.ufuncs.query.components import (
|
|
|
18
17
|
from spinta.datasets.backends.dataframe.ufuncs.query.components import (
|
|
19
18
|
DaskSelected as Selected,
|
|
20
19
|
)
|
|
20
|
+
from spinta.datasets.backends.dataframe.ufuncs.query.helpers import (
|
|
21
|
+
select_external_ref_foreign_key_properties,
|
|
22
|
+
select_ref_foreign_key_properties,
|
|
23
|
+
)
|
|
21
24
|
from spinta.datasets.components import Param
|
|
25
|
+
from spinta.datasets.enums import ExternalIdPattern
|
|
26
|
+
from spinta.datasets.helpers import authorized_or_system_request
|
|
22
27
|
from spinta.datasets.utils import iterparams
|
|
23
28
|
from spinta.exceptions import (
|
|
24
29
|
InvalidArgumentInExpression,
|
|
@@ -26,13 +31,23 @@ from spinta.exceptions import (
|
|
|
26
31
|
SourceCannotBeList,
|
|
27
32
|
SourceOrPrepareNotAllowed,
|
|
28
33
|
)
|
|
29
|
-
from spinta.types.datatype import Boolean, DataType, Integer, Number, PrimaryKey, Ref
|
|
34
|
+
from spinta.types.datatype import Boolean, DataType, ExternalRef, Integer, Number, PrimaryKey, Ref
|
|
30
35
|
from spinta.types.text.components import Text
|
|
31
36
|
from spinta.ufuncs.components import ForeignProperty
|
|
37
|
+
from spinta.ufuncs.querybuilder.helpers import process_literal_value
|
|
32
38
|
from spinta.utils.data import take
|
|
33
39
|
from spinta.utils.schema import NA
|
|
34
40
|
|
|
35
41
|
|
|
42
|
+
@ufunc.resolver(DaskDataFrameQueryBuilder, Expr)
|
|
43
|
+
def testlist(env: DaskDataFrameQueryBuilder, expr: Expr) -> tuple:
|
|
44
|
+
args, kwargs = expr.resolve(env)
|
|
45
|
+
result = []
|
|
46
|
+
for arg in args:
|
|
47
|
+
result.append(process_literal_value(arg))
|
|
48
|
+
return tuple(result)
|
|
49
|
+
|
|
50
|
+
|
|
36
51
|
@ufunc.resolver(DaskDataFrameQueryBuilder, Expr, name="and")
|
|
37
52
|
def and_(env, expr):
|
|
38
53
|
args, kwargs = expr.resolve(env)
|
|
@@ -152,7 +167,7 @@ def select(env: DaskDataFrameQueryBuilder, expr: Expr):
|
|
|
152
167
|
for prop in take(["_id", "_revision", all], env.model.properties).values():
|
|
153
168
|
if prop.name == "_revision" and not is_custom_revision_prop(prop):
|
|
154
169
|
continue
|
|
155
|
-
if
|
|
170
|
+
if authorized_or_system_request(env.context, prop, Action.GETALL):
|
|
156
171
|
env.selected[prop.place] = env.call("select", prop)
|
|
157
172
|
|
|
158
173
|
|
|
@@ -182,7 +197,7 @@ def _get_property_for_select(
|
|
|
182
197
|
# then how prepare context should be defined? Probably resolvers
|
|
183
198
|
# should be called with a different env class?
|
|
184
199
|
# tag:resolving_private_properties_in_prepare_context
|
|
185
|
-
nested or
|
|
200
|
+
nested or authorized_or_system_request(env.context, prop, Action.SEARCH)
|
|
186
201
|
):
|
|
187
202
|
return prop
|
|
188
203
|
else:
|
|
@@ -199,7 +214,7 @@ def select(env: DaskDataFrameQueryBuilder, prop: Property) -> Selected:
|
|
|
199
214
|
if prop.place not in env.resolved:
|
|
200
215
|
if isinstance(prop.external, list):
|
|
201
216
|
raise SourceCannotBeList(prop)
|
|
202
|
-
if prop.external
|
|
217
|
+
if prop.external and prop.external.prepare is not NA:
|
|
203
218
|
# If property doesn't have external name - it describes query parameter
|
|
204
219
|
# If `prepare` formula is given, evaluate formula.
|
|
205
220
|
if isinstance(prop.external.prepare, Expr):
|
|
@@ -213,13 +228,6 @@ def select(env: DaskDataFrameQueryBuilder, prop: Property) -> Selected:
|
|
|
213
228
|
# properties.
|
|
214
229
|
# tag:resolving_private_properties_in_prepare_context
|
|
215
230
|
result = env.call("select", prop.dtype, result)
|
|
216
|
-
elif prop.external.prepare is not NA:
|
|
217
|
-
# property without external name may be evaluated already, if it is use the value
|
|
218
|
-
if isinstance(prop.external.prepare, Expr):
|
|
219
|
-
result = env(this=prop).resolve(prop.external.prepare)
|
|
220
|
-
result = env.call("select", prop.dtype, result)
|
|
221
|
-
else:
|
|
222
|
-
result = Selected(prop=prop, prep=prop.external.prepare)
|
|
223
231
|
elif prop.external and prop.external.name:
|
|
224
232
|
# If prepare is not given, then take value from `source`.
|
|
225
233
|
result = env.call("select", prop.dtype)
|
|
@@ -306,18 +314,22 @@ def select(
|
|
|
306
314
|
dtype: PrimaryKey,
|
|
307
315
|
) -> Selected:
|
|
308
316
|
model = dtype.prop.model
|
|
309
|
-
pkeys = model.external.pkeys
|
|
310
|
-
|
|
317
|
+
pkeys = (model.base and model.base.pk) or model.external.pkeys
|
|
311
318
|
if not pkeys:
|
|
312
319
|
# If primary key is not specified use all properties to uniquely
|
|
313
320
|
# identify row.
|
|
314
321
|
pkeys = take(model.properties).values()
|
|
315
322
|
|
|
316
|
-
|
|
317
|
-
prop
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
result = {
|
|
324
|
+
ExternalIdPattern.ID_KEY.value: {prop.name: env.call("select", prop) for prop in pkeys},
|
|
325
|
+
ExternalIdPattern.COMBINATIONS_KEY.value: {},
|
|
326
|
+
}
|
|
327
|
+
for required_pk_combination in model.required_keymap_properties:
|
|
328
|
+
combination_result = {}
|
|
329
|
+
for prop_name in required_pk_combination:
|
|
330
|
+
prop = env.call("_resolve_property", prop_name)
|
|
331
|
+
combination_result[prop.name] = env.call("select", prop)
|
|
332
|
+
result[ExternalIdPattern.COMBINATIONS_KEY.value][required_pk_combination] = combination_result
|
|
321
333
|
return Selected(prop=dtype.prop, prep=result)
|
|
322
334
|
|
|
323
335
|
|
|
@@ -331,7 +343,10 @@ def select(env: DaskDataFrameQueryBuilder, dtype: Ref, prep: GetAttr) -> Selecte
|
|
|
331
343
|
resolved_prep = env.call("select", prep)
|
|
332
344
|
|
|
333
345
|
result = {}
|
|
334
|
-
|
|
346
|
+
if not dtype.inherited:
|
|
347
|
+
refprop = dtype.refprops[0]
|
|
348
|
+
result[ExternalIdPattern.ID_KEY.value] = Selected(prop=dtype.prop, prep={refprop.name: resolved_prep})
|
|
349
|
+
|
|
335
350
|
for prop in dtype.properties.values():
|
|
336
351
|
sel = env.call("select", prop)
|
|
337
352
|
result[prop.name] = sel
|
|
@@ -341,7 +356,7 @@ def select(env: DaskDataFrameQueryBuilder, dtype: Ref, prep: GetAttr) -> Selecte
|
|
|
341
356
|
|
|
342
357
|
@ufunc.resolver(DaskDataFrameQueryBuilder, Ref, object)
|
|
343
358
|
def select(env: DaskDataFrameQueryBuilder, dtype: Ref, prep: Any) -> Selected:
|
|
344
|
-
fpr = ForeignProperty(None, dtype, dtype.model.
|
|
359
|
+
fpr = ForeignProperty(None, dtype, dtype.model.id_prop.dtype)
|
|
345
360
|
return Selected(
|
|
346
361
|
prop=dtype.prop,
|
|
347
362
|
prep=env.call("select", fpr, fpr.right.prop),
|
|
@@ -351,7 +366,51 @@ def select(env: DaskDataFrameQueryBuilder, dtype: Ref, prep: Any) -> Selected:
|
|
|
351
366
|
@ufunc.resolver(DaskDataFrameQueryBuilder, Ref)
|
|
352
367
|
def select(env: DaskDataFrameQueryBuilder, dtype: Ref) -> Selected:
|
|
353
368
|
prep = {}
|
|
354
|
-
|
|
369
|
+
if not dtype.inherited:
|
|
370
|
+
prep[ExternalIdPattern.ID_KEY.value] = Selected(
|
|
371
|
+
prop=dtype.prop, prep=select_ref_foreign_key_properties(env, dtype)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
for prop in dtype.properties.values():
|
|
375
|
+
sel = env.call("select", prop)
|
|
376
|
+
prep[prop.name] = sel
|
|
377
|
+
|
|
378
|
+
return Selected(prop=dtype.prop, prep=prep)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@ufunc.resolver(DaskDataFrameQueryBuilder, Ref, (list, tuple))
|
|
382
|
+
def select(env: DaskDataFrameQueryBuilder, dtype: Ref, data: list | tuple) -> Selected:
|
|
383
|
+
prep = {}
|
|
384
|
+
if not dtype.inherited:
|
|
385
|
+
prep[ExternalIdPattern.ID_KEY.value] = Selected(
|
|
386
|
+
prop=dtype.prop, prep=select_ref_foreign_key_properties(env, dtype, properties=data)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
for prop in dtype.properties.values():
|
|
390
|
+
sel = env.call("select", prop)
|
|
391
|
+
prep[prop.name] = sel
|
|
392
|
+
|
|
393
|
+
return Selected(prop=dtype.prop, prep=prep)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@ufunc.resolver(DaskDataFrameQueryBuilder, ExternalRef)
|
|
397
|
+
def select(env: DaskDataFrameQueryBuilder, dtype: ExternalRef) -> Selected:
|
|
398
|
+
prep = {}
|
|
399
|
+
if not dtype.inherited:
|
|
400
|
+
prep.update(select_external_ref_foreign_key_properties(env, dtype))
|
|
401
|
+
|
|
402
|
+
for prop in dtype.properties.values():
|
|
403
|
+
sel = env.call("select", prop)
|
|
404
|
+
prep[prop.name] = sel
|
|
405
|
+
|
|
406
|
+
return Selected(prop=dtype.prop, prep=prep)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
@ufunc.resolver(DaskDataFrameQueryBuilder, ExternalRef, (list, tuple))
|
|
410
|
+
def select(env: DaskDataFrameQueryBuilder, dtype: ExternalRef, data: list | tuple) -> Selected:
|
|
411
|
+
prep = {}
|
|
412
|
+
if not dtype.inherited:
|
|
413
|
+
prep.update(select_external_ref_foreign_key_properties(env, dtype, properties=data))
|
|
355
414
|
|
|
356
415
|
for prop in dtype.properties.values():
|
|
357
416
|
sel = env.call("select", prop)
|
|
@@ -434,7 +493,7 @@ def select(
|
|
|
434
493
|
def select(env: DaskDataFrameQueryBuilder, fpr: ForeignProperty, item: Bind):
|
|
435
494
|
model = fpr.right.prop.model
|
|
436
495
|
prop = env.call("_resolve_property", item)
|
|
437
|
-
if
|
|
496
|
+
if authorized_or_system_request(env.context, prop, Action.SEARCH):
|
|
438
497
|
return env.call("select", fpr, prop)
|
|
439
498
|
else:
|
|
440
499
|
raise PropertyNotFound(model, property=item.name)
|