datajunction-server 0.0.2.dev2__py3-none-any.whl → 0.0.2.dev4__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.
- datajunction_server/__about__.py +1 -1
- datajunction_server/alembic/versions/2025_09_16_1513-b55add7e1ebc_add_cascade_delete_on_tag_node_and_.py +2 -3
- datajunction_server/alembic/versions/{2025_09_12_2107-b6398ba852b3_deployments.py → 2025_09_17_2107-b6398ba852b3_deployments.py} +2 -2
- datajunction_server/api/namespaces.py +1 -2
- datajunction_server/database/node.py +9 -0
- datajunction_server/internal/deployment.py +219 -117
- datajunction_server/internal/nodes.py +10 -5
- datajunction_server/models/deployment.py +116 -60
- {datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/METADATA +1 -1
- {datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/RECORD +12 -12
- {datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/WHEEL +0 -0
- {datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/entry_points.txt +0 -0
datajunction_server/__about__.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Add cascade delete on tag-node and column-attr foreign keys
|
|
3
|
-
|
|
4
3
|
Revision ID: b55add7e1ebc
|
|
5
|
-
Revises:
|
|
4
|
+
Revises: 759c4d50cb8d
|
|
6
5
|
Create Date: 2025-09-16 15:13:39.963297+00:00
|
|
7
6
|
"""
|
|
8
7
|
# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module
|
|
@@ -11,7 +10,7 @@ from alembic import op
|
|
|
11
10
|
|
|
12
11
|
# revision identifiers, used by Alembic.
|
|
13
12
|
revision = "b55add7e1ebc"
|
|
14
|
-
down_revision = "
|
|
13
|
+
down_revision = "759c4d50cb8d"
|
|
15
14
|
branch_labels = None
|
|
16
15
|
depends_on = None
|
|
17
16
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Deployments
|
|
3
3
|
|
|
4
4
|
Revision ID: b6398ba852b3
|
|
5
|
-
Revises:
|
|
5
|
+
Revises: b55add7e1ebc
|
|
6
6
|
Create Date: 2025-09-12 00:07:30.531304+00:00
|
|
7
7
|
"""
|
|
8
8
|
# pylint: disable=no-member, invalid-name, missing-function-docstring, unused-import, no-name-in-module
|
|
@@ -13,7 +13,7 @@ import sqlalchemy_utils
|
|
|
13
13
|
|
|
14
14
|
# revision identifiers, used by Alembic.
|
|
15
15
|
revision = "b6398ba852b3"
|
|
16
|
-
down_revision = "
|
|
16
|
+
down_revision = "b55add7e1ebc"
|
|
17
17
|
branch_labels = None
|
|
18
18
|
depends_on = None
|
|
19
19
|
|
|
@@ -403,8 +403,7 @@ async def export_namespace_spec(
|
|
|
403
403
|
session: AsyncSession = Depends(get_session),
|
|
404
404
|
) -> DeploymentSpec:
|
|
405
405
|
"""
|
|
406
|
-
Generates a
|
|
407
|
-
as well as a project definition file.
|
|
406
|
+
Generates a deployment spec for a namespace
|
|
408
407
|
"""
|
|
409
408
|
nodes = await NodeNamespace.list_all_nodes(
|
|
410
409
|
session,
|
|
@@ -438,6 +438,15 @@ class Node(Base):
|
|
|
438
438
|
node_spec_cls = node_spec_class_map[self.type]
|
|
439
439
|
return node_spec_cls(**base_kwargs, **extra_kwargs)
|
|
440
440
|
|
|
441
|
+
@classmethod
|
|
442
|
+
def default_load_options(cls) -> List[ExecutableOption]:
|
|
443
|
+
return [
|
|
444
|
+
joinedload(Node.current).options(*NodeRevision.default_load_options()),
|
|
445
|
+
selectinload(Node.tags),
|
|
446
|
+
selectinload(Node.created_by),
|
|
447
|
+
selectinload(Node.owners),
|
|
448
|
+
]
|
|
449
|
+
|
|
441
450
|
@classmethod
|
|
442
451
|
def cube_load_options(cls) -> List[ExecutableOption]:
|
|
443
452
|
return [
|
|
@@ -10,7 +10,10 @@ from datajunction_server.database import Node
|
|
|
10
10
|
from datajunction_server.models import access
|
|
11
11
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
12
|
from datajunction_server.api.tags import get_tags_by_name
|
|
13
|
+
from datajunction_server.models.base import labelize
|
|
13
14
|
|
|
15
|
+
from datajunction_server.database.partition import Partition
|
|
16
|
+
from datajunction_server.database.namespace import NodeNamespace
|
|
14
17
|
from datajunction_server.models.attribute import AttributeTypeIdentifier
|
|
15
18
|
from datajunction_server.models.deployment import (
|
|
16
19
|
CubeSpec,
|
|
@@ -139,9 +142,38 @@ async def deploy(
|
|
|
139
142
|
deployed_results: list[DeploymentResult] = []
|
|
140
143
|
|
|
141
144
|
async with session_context(request) as session:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
current_user = cast(User, await User.get_by_username(session, current_username))
|
|
146
|
+
await create_deployment_namespaces(
|
|
147
|
+
deployment,
|
|
148
|
+
session,
|
|
149
|
+
current_user,
|
|
150
|
+
save_history,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# async with session_context(request) as session:
|
|
154
|
+
all_nodes = await NodeNamespace.list_all_nodes(
|
|
155
|
+
session,
|
|
156
|
+
deployment.namespace,
|
|
157
|
+
options=Node.cube_load_options(),
|
|
158
|
+
)
|
|
159
|
+
existing = {node.name: await node.to_spec(session) for node in all_nodes}
|
|
160
|
+
to_deploy, to_skip, to_delete = filter_nodes_to_deploy(
|
|
161
|
+
deployment.nodes,
|
|
162
|
+
existing,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
deployed_results.extend(
|
|
166
|
+
[
|
|
167
|
+
DeploymentResult(
|
|
168
|
+
name=node_spec.rendered_name,
|
|
169
|
+
deploy_type=DeploymentResult.Type.NODE,
|
|
170
|
+
status=DeploymentResult.Status.SKIPPED,
|
|
171
|
+
operation=DeploymentResult.Operation.NOOP,
|
|
172
|
+
message=f"Node {node_spec.rendered_name} is unchanged.",
|
|
173
|
+
)
|
|
174
|
+
for node_spec in to_skip
|
|
175
|
+
],
|
|
176
|
+
)
|
|
145
177
|
if not to_deploy:
|
|
146
178
|
logger.info(
|
|
147
179
|
"No changes detected, skipping deployment. Total elapsed: %.3fs",
|
|
@@ -155,15 +187,6 @@ async def deploy(
|
|
|
155
187
|
len(to_skip),
|
|
156
188
|
)
|
|
157
189
|
|
|
158
|
-
async with session_context(request) as session:
|
|
159
|
-
current_user = cast(User, await User.get_by_username(session, current_username))
|
|
160
|
-
await create_deployment_namespaces(
|
|
161
|
-
deployment,
|
|
162
|
-
session,
|
|
163
|
-
current_user,
|
|
164
|
-
save_history,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
190
|
node_graph = extract_node_graph(
|
|
168
191
|
[node for node in to_deploy if not isinstance(node, CubeSpec)],
|
|
169
192
|
)
|
|
@@ -232,6 +255,9 @@ async def deploy(
|
|
|
232
255
|
DeploymentStatus.RUNNING,
|
|
233
256
|
deployed_results,
|
|
234
257
|
)
|
|
258
|
+
logger.info("Starting deletion of %d nodes", len(to_delete))
|
|
259
|
+
# for node in to_delete:
|
|
260
|
+
# await deactivate_node(session, node.name, current_user, save_history)
|
|
235
261
|
logger.info("Finished deploying namespace %s", deployment.namespace)
|
|
236
262
|
return deployed_results
|
|
237
263
|
|
|
@@ -248,7 +274,15 @@ async def create_deployment_namespaces(
|
|
|
248
274
|
if SEPARATOR in node.rendered_name
|
|
249
275
|
]
|
|
250
276
|
namespace_set = set(namespaces)
|
|
251
|
-
|
|
277
|
+
pruned = {
|
|
278
|
+
ns
|
|
279
|
+
for ns in namespace_set
|
|
280
|
+
if not any(
|
|
281
|
+
other != ns and other.startswith(f"{ns}{SEPARATOR}")
|
|
282
|
+
for other in namespace_set
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
for nspace in pruned:
|
|
252
286
|
await create_namespace(
|
|
253
287
|
session=session,
|
|
254
288
|
namespace=nspace,
|
|
@@ -351,7 +385,7 @@ def filter_nodes_to_deploy(
|
|
|
351
385
|
):
|
|
352
386
|
to_create: list[NodeSpec] = []
|
|
353
387
|
to_update: list[NodeSpec] = []
|
|
354
|
-
to_skip: list[
|
|
388
|
+
to_skip: list[NodeSpec] = []
|
|
355
389
|
for node_spec in node_specs:
|
|
356
390
|
existing_spec = existing_nodes_map.get(node_spec.rendered_name)
|
|
357
391
|
if not existing_spec:
|
|
@@ -359,15 +393,14 @@ def filter_nodes_to_deploy(
|
|
|
359
393
|
elif node_spec != existing_spec:
|
|
360
394
|
to_update.append(node_spec)
|
|
361
395
|
else:
|
|
362
|
-
to_skip.append(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
)
|
|
396
|
+
to_skip.append(node_spec)
|
|
397
|
+
|
|
398
|
+
desired_node_names = {n.rendered_name for n in node_specs}
|
|
399
|
+
to_delete = [
|
|
400
|
+
existing
|
|
401
|
+
for name, existing in existing_nodes_map.items()
|
|
402
|
+
if name not in desired_node_names
|
|
403
|
+
]
|
|
371
404
|
|
|
372
405
|
logger.info(
|
|
373
406
|
"Creating %d new nodes: %s",
|
|
@@ -382,9 +415,9 @@ def filter_nodes_to_deploy(
|
|
|
382
415
|
logger.info(
|
|
383
416
|
"Skipping %d nodes as they are unchanged: %s",
|
|
384
417
|
len(to_skip),
|
|
385
|
-
[result.
|
|
418
|
+
[result.rendered_name for result in to_skip],
|
|
386
419
|
)
|
|
387
|
-
return to_create + to_update, to_skip
|
|
420
|
+
return to_create + to_update, to_skip, to_delete
|
|
388
421
|
|
|
389
422
|
|
|
390
423
|
async def check_external_deps(
|
|
@@ -475,8 +508,7 @@ async def deploy_nodes_in_levels(
|
|
|
475
508
|
background_tasks=background_tasks,
|
|
476
509
|
save_history=save_history,
|
|
477
510
|
cache=cache,
|
|
478
|
-
existing=existing_nodes_map.get(node_spec.rendered_name)
|
|
479
|
-
is not None,
|
|
511
|
+
existing=existing_nodes_map.get(node_spec.rendered_name),
|
|
480
512
|
),
|
|
481
513
|
)
|
|
482
514
|
|
|
@@ -505,16 +537,17 @@ async def deploy_links_for_node(
|
|
|
505
537
|
existing_nodes_map.get(node_spec.rendered_name),
|
|
506
538
|
)
|
|
507
539
|
existing_node_links = {
|
|
508
|
-
link.rendered_dimension_node: link
|
|
540
|
+
(link.rendered_dimension_node, link.role): link
|
|
509
541
|
for link in (existing_node_spec.dimension_links if existing_node_spec else [])
|
|
510
542
|
}
|
|
511
543
|
desired_node_links = {
|
|
512
|
-
link.rendered_dimension_node
|
|
544
|
+
(link.rendered_dimension_node, link.role): link
|
|
545
|
+
for link in node_spec.dimension_links
|
|
513
546
|
}
|
|
514
547
|
to_delete = {
|
|
515
|
-
existing_node_links[dim]
|
|
516
|
-
for dim in existing_node_links
|
|
517
|
-
if dim not in desired_node_links
|
|
548
|
+
existing_node_links[(dim, role)]
|
|
549
|
+
for (dim, role) in existing_node_links
|
|
550
|
+
if (dim, role) not in desired_node_links
|
|
518
551
|
}
|
|
519
552
|
async with session_context(request) as session:
|
|
520
553
|
for link in to_delete:
|
|
@@ -591,7 +624,7 @@ async def deploy_cubes(
|
|
|
591
624
|
background_tasks=background_tasks,
|
|
592
625
|
save_history=save_history,
|
|
593
626
|
cache=cache,
|
|
594
|
-
existing=existing_nodes_map.get(cube_spec.rendered_name)
|
|
627
|
+
existing=existing_nodes_map.get(cube_spec.rendered_name),
|
|
595
628
|
),
|
|
596
629
|
)
|
|
597
630
|
return await run_tasks_with_semaphore(
|
|
@@ -644,21 +677,61 @@ async def deploy_column_attributes(
|
|
|
644
677
|
node_spec: NodeSpec,
|
|
645
678
|
current_username: str,
|
|
646
679
|
save_history: Callable,
|
|
647
|
-
):
|
|
680
|
+
) -> set[str]:
|
|
681
|
+
changed_columns = set()
|
|
648
682
|
async with session_context() as session:
|
|
649
683
|
node = await Node.get_by_name(session=session, name=node_name)
|
|
650
684
|
current_user = cast(User, await User.get_by_username(session, current_username))
|
|
651
|
-
for col in node_spec.columns or []
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
685
|
+
desired_column_state = {col.name: col for col in node_spec.columns or []}
|
|
686
|
+
for col in node.current.columns: # type: ignore
|
|
687
|
+
if desired_col := desired_column_state.get(col.name):
|
|
688
|
+
# If the column is explicitly defined, update its properties to match
|
|
689
|
+
if col.display_name != desired_col.display_name:
|
|
690
|
+
col.display_name = desired_col.display_name
|
|
691
|
+
changed_columns.add(col.name)
|
|
692
|
+
if col.description != desired_col.description:
|
|
693
|
+
col.description = desired_col.description
|
|
694
|
+
changed_columns.add(col.name)
|
|
695
|
+
if desired_col.partition != (
|
|
696
|
+
col.partition.to_spec() if col.partition else None
|
|
697
|
+
):
|
|
698
|
+
partition = (
|
|
699
|
+
Partition(
|
|
700
|
+
column_id=col.id,
|
|
701
|
+
type_=desired_col.partition.type,
|
|
702
|
+
format=desired_col.partition.format,
|
|
703
|
+
granularity=desired_col.partition.granularity,
|
|
704
|
+
)
|
|
705
|
+
if desired_col.partition
|
|
706
|
+
else None
|
|
707
|
+
)
|
|
708
|
+
if partition:
|
|
709
|
+
session.add(partition)
|
|
710
|
+
col.partition = partition
|
|
711
|
+
changed_columns.add(col.name)
|
|
712
|
+
if set(desired_col.attributes) != set(col.attribute_names()):
|
|
713
|
+
await set_node_column_attributes(
|
|
714
|
+
session=session,
|
|
715
|
+
node=node, # type: ignore
|
|
716
|
+
column_name=col.name,
|
|
717
|
+
attributes=[
|
|
718
|
+
AttributeTypeIdentifier(name=attr)
|
|
719
|
+
for attr in desired_col.attributes
|
|
720
|
+
],
|
|
721
|
+
current_user=current_user,
|
|
722
|
+
save_history=save_history,
|
|
723
|
+
)
|
|
724
|
+
changed_columns.add(col.name)
|
|
725
|
+
else:
|
|
726
|
+
# If the column is not explicitly defined, reset it to default
|
|
727
|
+
col.display_name = labelize(col.name)
|
|
728
|
+
col.description = ""
|
|
729
|
+
col.partition = None
|
|
730
|
+
col.attributes = []
|
|
731
|
+
|
|
732
|
+
session.add(col)
|
|
733
|
+
await session.commit()
|
|
734
|
+
return changed_columns
|
|
662
735
|
|
|
663
736
|
|
|
664
737
|
async def deploy_node_from_spec(
|
|
@@ -671,7 +744,7 @@ async def deploy_node_from_spec(
|
|
|
671
744
|
*,
|
|
672
745
|
save_history: Callable,
|
|
673
746
|
cache: Cache,
|
|
674
|
-
existing:
|
|
747
|
+
existing: NodeSpec | None = None,
|
|
675
748
|
) -> DeploymentResult:
|
|
676
749
|
"""
|
|
677
750
|
Deploy a node from its specification.
|
|
@@ -690,6 +763,7 @@ async def deploy_node_from_spec(
|
|
|
690
763
|
if not existing
|
|
691
764
|
else DeploymentResult.Operation.UPDATE
|
|
692
765
|
)
|
|
766
|
+
changelog = []
|
|
693
767
|
if not deploy_fn: # pragma: no cover
|
|
694
768
|
raise DJInvalidDeploymentConfig(f"Unknown node type: {node_spec.node_type}")
|
|
695
769
|
try:
|
|
@@ -704,20 +778,40 @@ async def deploy_node_from_spec(
|
|
|
704
778
|
cache=cache,
|
|
705
779
|
existing=existing,
|
|
706
780
|
)
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
781
|
+
changed_fields = existing.diff(node_spec) if existing else []
|
|
782
|
+
changelog.append(
|
|
783
|
+
f"{operation.capitalize()}d {node_spec.node_type} ({node.current_version})",
|
|
784
|
+
)
|
|
785
|
+
changelog.append(
|
|
786
|
+
("└─ Updated " + ", ".join(changed_fields)),
|
|
787
|
+
) if changed_fields else ""
|
|
788
|
+
|
|
789
|
+
if set(node_spec.tags) != set([tag.name for tag in node.tags]):
|
|
790
|
+
await deploy_node_tags(node_name=node.name, node_spec=node_spec)
|
|
791
|
+
tags_list = ", ".join([f"`{tag}`" for tag in node_spec.tags])
|
|
792
|
+
changelog.append(f"└─ Set tags to {tags_list}.")
|
|
793
|
+
if node.type in (
|
|
794
|
+
NodeType.SOURCE,
|
|
795
|
+
NodeType.TRANSFORM,
|
|
796
|
+
NodeType.DIMENSION,
|
|
797
|
+
NodeType.CUBE,
|
|
798
|
+
):
|
|
799
|
+
changed_columns = await deploy_column_attributes(
|
|
710
800
|
node_name=node.name,
|
|
711
801
|
node_spec=node_spec,
|
|
712
802
|
current_username=current_username,
|
|
713
803
|
save_history=save_history,
|
|
714
804
|
)
|
|
805
|
+
if changed_columns and operation == DeploymentResult.Operation.UPDATE:
|
|
806
|
+
changelog.append(
|
|
807
|
+
f"└─ Set properties for {len(changed_columns)} columns",
|
|
808
|
+
)
|
|
715
809
|
except DJException as exc:
|
|
716
810
|
return DeploymentResult(
|
|
717
811
|
deploy_type=DeploymentResult.Type.NODE,
|
|
718
812
|
name=node_spec.rendered_name,
|
|
719
813
|
status=DeploymentResult.Status.FAILED,
|
|
720
|
-
message=str(exc),
|
|
814
|
+
message="\n".join(changelog + [str(exc)]),
|
|
721
815
|
operation=operation,
|
|
722
816
|
)
|
|
723
817
|
|
|
@@ -728,6 +822,7 @@ async def deploy_node_from_spec(
|
|
|
728
822
|
if isinstance(node, Node)
|
|
729
823
|
else DeploymentResult.Status.FAILED,
|
|
730
824
|
operation=operation,
|
|
825
|
+
message="\n".join(changelog),
|
|
731
826
|
)
|
|
732
827
|
|
|
733
828
|
|
|
@@ -810,7 +905,7 @@ async def deploy_transform_dimension_node_from_spec(
|
|
|
810
905
|
*,
|
|
811
906
|
save_history: Callable,
|
|
812
907
|
cache: Cache,
|
|
813
|
-
existing:
|
|
908
|
+
existing: NodeSpec | None = None,
|
|
814
909
|
) -> Node:
|
|
815
910
|
"""
|
|
816
911
|
Deploy a transform or dimension node from its spec.
|
|
@@ -886,20 +981,12 @@ async def deploy_metric_node_from_spec(
|
|
|
886
981
|
"""
|
|
887
982
|
Deploy a metric node from its spec.
|
|
888
983
|
"""
|
|
889
|
-
metric_metadata_input = (
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
max_decimal_exponent=node_spec.max_decimal_exponent,
|
|
896
|
-
)
|
|
897
|
-
if node_spec.direction
|
|
898
|
-
or node_spec.unit
|
|
899
|
-
or node_spec.significant_digits
|
|
900
|
-
or node_spec.min_decimal_exponent
|
|
901
|
-
or node_spec.max_decimal_exponent
|
|
902
|
-
else None
|
|
984
|
+
metric_metadata_input = MetricMetadataInput(
|
|
985
|
+
direction=node_spec.direction,
|
|
986
|
+
unit=node_spec.unit,
|
|
987
|
+
significant_digits=node_spec.significant_digits,
|
|
988
|
+
min_decimal_exponent=node_spec.min_decimal_exponent,
|
|
989
|
+
max_decimal_exponent=node_spec.max_decimal_exponent,
|
|
903
990
|
)
|
|
904
991
|
async with session_context(request) as session:
|
|
905
992
|
current_user = cast(User, await User.get_by_username(session, current_username))
|
|
@@ -911,10 +998,10 @@ async def deploy_metric_node_from_spec(
|
|
|
911
998
|
display_name=node_spec.display_name,
|
|
912
999
|
description=node_spec.description,
|
|
913
1000
|
mode=node_spec.mode,
|
|
914
|
-
custom_metadata=node_spec.custom_metadata,
|
|
1001
|
+
custom_metadata=node_spec.custom_metadata or {},
|
|
915
1002
|
owners=node_spec.owners,
|
|
916
1003
|
query=node_spec.rendered_query,
|
|
917
|
-
required_dimensions=node_spec.required_dimensions,
|
|
1004
|
+
required_dimensions=node_spec.required_dimensions or [],
|
|
918
1005
|
metric_metadata=metric_metadata_input,
|
|
919
1006
|
),
|
|
920
1007
|
session=session,
|
|
@@ -1002,32 +1089,34 @@ async def deploy_cube_node_from_spec(
|
|
|
1002
1089
|
refresh_materialization=True,
|
|
1003
1090
|
cache=cache,
|
|
1004
1091
|
)
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1092
|
+
else:
|
|
1093
|
+
logger.info("Creating cube node %s", node_spec.rendered_name)
|
|
1094
|
+
await create_a_cube(
|
|
1095
|
+
data=CreateCubeNode(
|
|
1096
|
+
name=node_spec.rendered_name,
|
|
1097
|
+
display_name=node_spec.display_name,
|
|
1098
|
+
description=node_spec.description,
|
|
1099
|
+
mode=node_spec.mode,
|
|
1100
|
+
custom_metadata=node_spec.custom_metadata,
|
|
1101
|
+
owners=node_spec.owners,
|
|
1102
|
+
metrics=node_spec.rendered_metrics,
|
|
1103
|
+
dimensions=node_spec.rendered_dimensions,
|
|
1104
|
+
filters=node_spec.rendered_filters,
|
|
1105
|
+
),
|
|
1106
|
+
request=request,
|
|
1107
|
+
session=session,
|
|
1108
|
+
current_user=current_user,
|
|
1109
|
+
query_service_client=query_service_client,
|
|
1110
|
+
background_tasks=background_tasks,
|
|
1111
|
+
validate_access=validate_access,
|
|
1112
|
+
save_history=save_history,
|
|
1010
1113
|
)
|
|
1011
|
-
|
|
1012
|
-
return await
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
mode=node_spec.mode,
|
|
1018
|
-
custom_metadata=node_spec.custom_metadata,
|
|
1019
|
-
owners=node_spec.owners,
|
|
1020
|
-
metrics=node_spec.rendered_metrics,
|
|
1021
|
-
dimensions=node_spec.rendered_dimensions,
|
|
1022
|
-
filters=node_spec.rendered_filters,
|
|
1023
|
-
),
|
|
1024
|
-
request=request,
|
|
1025
|
-
session=session,
|
|
1026
|
-
current_user=current_user,
|
|
1027
|
-
query_service_client=query_service_client,
|
|
1028
|
-
background_tasks=background_tasks,
|
|
1029
|
-
validate_access=validate_access,
|
|
1030
|
-
save_history=save_history,
|
|
1114
|
+
|
|
1115
|
+
return await Node.get_by_name( # type: ignore
|
|
1116
|
+
session,
|
|
1117
|
+
node_spec.rendered_name,
|
|
1118
|
+
options=NodeOutput.load_options(),
|
|
1119
|
+
raise_if_not_exists=True,
|
|
1031
1120
|
)
|
|
1032
1121
|
|
|
1033
1122
|
|
|
@@ -1037,7 +1126,10 @@ async def deploy_dimension_link_from_spec(
|
|
|
1037
1126
|
request: Request,
|
|
1038
1127
|
current_username: str,
|
|
1039
1128
|
save_history: Callable,
|
|
1040
|
-
existing_node_links: dict[
|
|
1129
|
+
existing_node_links: dict[
|
|
1130
|
+
tuple[str, str | None],
|
|
1131
|
+
DimensionJoinLinkSpec | DimensionReferenceLinkSpec,
|
|
1132
|
+
],
|
|
1041
1133
|
) -> DeploymentResult:
|
|
1042
1134
|
try:
|
|
1043
1135
|
link_name = f"{node_spec.rendered_name} -> {link_spec.rendered_dimension_node}"
|
|
@@ -1052,36 +1144,46 @@ async def deploy_dimension_link_from_spec(
|
|
|
1052
1144
|
await User.get_by_username(session, current_username),
|
|
1053
1145
|
)
|
|
1054
1146
|
if link_spec.type == LinkType.JOIN:
|
|
1147
|
+
existing = existing_node_links.get(link_spec.rendered_dimension_node)
|
|
1055
1148
|
join_link = cast(DimensionJoinLinkSpec, link_spec)
|
|
1056
|
-
if join_link
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1149
|
+
if existing != join_link:
|
|
1150
|
+
if join_link.node_column:
|
|
1151
|
+
await upsert_simple_dimension_link( # pragma: no cover
|
|
1152
|
+
session,
|
|
1153
|
+
node_spec.rendered_name,
|
|
1154
|
+
join_link.rendered_dimension_node,
|
|
1155
|
+
join_link.node_column,
|
|
1156
|
+
None,
|
|
1157
|
+
current_user,
|
|
1158
|
+
save_history,
|
|
1159
|
+
)
|
|
1160
|
+
else:
|
|
1161
|
+
link_input = JoinLinkInput(
|
|
1162
|
+
dimension_node=join_link.rendered_dimension_node,
|
|
1163
|
+
join_type=join_link.join_type,
|
|
1164
|
+
join_on=join_link.rendered_join_on,
|
|
1165
|
+
role=join_link.role,
|
|
1166
|
+
)
|
|
1167
|
+
await upsert_complex_dimension_link(
|
|
1168
|
+
session,
|
|
1169
|
+
node_spec.rendered_name,
|
|
1170
|
+
link_input,
|
|
1171
|
+
current_user,
|
|
1172
|
+
save_history,
|
|
1173
|
+
)
|
|
1174
|
+
return DeploymentResult(
|
|
1175
|
+
deploy_type=DeploymentResult.Type.LINK,
|
|
1176
|
+
operation=operation,
|
|
1177
|
+
name=link_name,
|
|
1178
|
+
status=DeploymentResult.Status.SUCCESS,
|
|
1179
|
+
message="Join link successfully deployed",
|
|
1065
1180
|
)
|
|
1066
|
-
link_input = JoinLinkInput(
|
|
1067
|
-
dimension_node=join_link.rendered_dimension_node,
|
|
1068
|
-
join_type=join_link.join_type,
|
|
1069
|
-
join_on=join_link.rendered_join_on,
|
|
1070
|
-
role=join_link.role,
|
|
1071
|
-
)
|
|
1072
|
-
await upsert_complex_dimension_link(
|
|
1073
|
-
session,
|
|
1074
|
-
node_spec.rendered_name,
|
|
1075
|
-
link_input,
|
|
1076
|
-
current_user,
|
|
1077
|
-
save_history,
|
|
1078
|
-
)
|
|
1079
1181
|
return DeploymentResult(
|
|
1080
1182
|
deploy_type=DeploymentResult.Type.LINK,
|
|
1081
1183
|
operation=operation,
|
|
1082
1184
|
name=link_name,
|
|
1083
|
-
status=DeploymentResult.Status.
|
|
1084
|
-
message="
|
|
1185
|
+
status=DeploymentResult.Status.SKIPPED,
|
|
1186
|
+
message="No change to dimension link",
|
|
1085
1187
|
)
|
|
1086
1188
|
else:
|
|
1087
1189
|
reference_link = cast(DimensionReferenceLinkSpec, link_spec)
|
|
@@ -1137,7 +1137,12 @@ def has_minor_changes(
|
|
|
1137
1137
|
Whether the node has minor changes
|
|
1138
1138
|
"""
|
|
1139
1139
|
return (
|
|
1140
|
-
(
|
|
1140
|
+
(
|
|
1141
|
+
data
|
|
1142
|
+
and data.description
|
|
1143
|
+
and old_revision.description
|
|
1144
|
+
and (old_revision.description != data.description)
|
|
1145
|
+
)
|
|
1141
1146
|
or (data and data.mode and old_revision.mode != data.mode)
|
|
1142
1147
|
or (
|
|
1143
1148
|
data
|
|
@@ -1595,7 +1600,7 @@ async def create_new_revision_from_existing(
|
|
|
1595
1600
|
)
|
|
1596
1601
|
or (
|
|
1597
1602
|
data
|
|
1598
|
-
and data.custom_metadata
|
|
1603
|
+
and data.custom_metadata is not None
|
|
1599
1604
|
and old_revision.custom_metadata != data.custom_metadata
|
|
1600
1605
|
)
|
|
1601
1606
|
)
|
|
@@ -1621,7 +1626,7 @@ async def create_new_revision_from_existing(
|
|
|
1621
1626
|
)
|
|
1622
1627
|
required_dim_changes = (
|
|
1623
1628
|
data
|
|
1624
|
-
and data.required_dimensions
|
|
1629
|
+
and isinstance(data.required_dimensions, list)
|
|
1625
1630
|
and {col.name for col in old_revision.required_dimensions}
|
|
1626
1631
|
!= set(data.required_dimensions)
|
|
1627
1632
|
)
|
|
@@ -1692,10 +1697,10 @@ async def create_new_revision_from_existing(
|
|
|
1692
1697
|
created_by_id=current_user.id,
|
|
1693
1698
|
custom_metadata=old_revision.custom_metadata,
|
|
1694
1699
|
)
|
|
1695
|
-
if data and data.required_dimensions: # type: ignore
|
|
1700
|
+
if data and data.required_dimensions is not None: # type: ignore
|
|
1696
1701
|
new_revision.required_dimensions = data.required_dimensions # type: ignore
|
|
1697
1702
|
|
|
1698
|
-
if data and data.custom_metadata: # type: ignore
|
|
1703
|
+
if data and data.custom_metadata is not None: # type: ignore
|
|
1699
1704
|
new_revision.custom_metadata = data.custom_metadata # type: ignore
|
|
1700
1705
|
|
|
1701
1706
|
# Link the new revision to its parents if a new revision was created and update its status
|
|
@@ -58,14 +58,13 @@ class ColumnSpec(BaseModel):
|
|
|
58
58
|
def __eq__(self, other: Any) -> bool:
|
|
59
59
|
if not isinstance(other, ColumnSpec):
|
|
60
60
|
return False
|
|
61
|
-
|
|
62
61
|
return (
|
|
63
62
|
self.name == other.name
|
|
64
63
|
and self.type == other.type
|
|
65
64
|
and (self.display_name == other.display_name or self.display_name is None)
|
|
66
|
-
and
|
|
65
|
+
and self.description == other.description
|
|
67
66
|
and set(self.attributes) == set(other.attributes)
|
|
68
|
-
and
|
|
67
|
+
and self.partition == other.partition
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
|
|
@@ -126,15 +125,14 @@ class DimensionJoinLinkSpec(DimensionLinkSpec):
|
|
|
126
125
|
)
|
|
127
126
|
|
|
128
127
|
def __eq__(self, other: Any) -> bool:
|
|
128
|
+
if not isinstance(other, DimensionJoinLinkSpec):
|
|
129
|
+
return False
|
|
129
130
|
return (
|
|
130
131
|
super().__eq__(other)
|
|
131
132
|
and self.rendered_dimension_node == other.rendered_dimension_node
|
|
132
133
|
and self.join_type == other.join_type
|
|
133
134
|
and self.rendered_join_on == other.rendered_join_on
|
|
134
|
-
and
|
|
135
|
-
self.node_column == other.node_column
|
|
136
|
-
or (self.node_column is None and other.node_column is None)
|
|
137
|
-
)
|
|
135
|
+
and self.node_column == other.node_column
|
|
138
136
|
)
|
|
139
137
|
|
|
140
138
|
|
|
@@ -233,7 +231,7 @@ class NodeSpec(BaseModel):
|
|
|
233
231
|
|
|
234
232
|
def __eq__(self, other: Any) -> bool:
|
|
235
233
|
if not isinstance(other, NodeSpec):
|
|
236
|
-
return False
|
|
234
|
+
return False # pragma: no cover
|
|
237
235
|
return (
|
|
238
236
|
self.rendered_name == other.rendered_name
|
|
239
237
|
and self.node_type == other.node_type
|
|
@@ -242,11 +240,17 @@ class NodeSpec(BaseModel):
|
|
|
242
240
|
and set(self.owners) == set(other.owners)
|
|
243
241
|
and set(self.tags) == set(other.tags)
|
|
244
242
|
and self.mode == other.mode
|
|
245
|
-
and (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
243
|
+
and eq_or_fallback(self.custom_metadata, other.custom_metadata, {})
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def diff(self, other: "NodeSpec") -> list[str]:
|
|
247
|
+
"""
|
|
248
|
+
Return a list of fields that differ between this and another NodeSpec.
|
|
249
|
+
"""
|
|
250
|
+
return diff(
|
|
251
|
+
self,
|
|
252
|
+
other,
|
|
253
|
+
ignore_fields=["name", "namespace", "query", "columns"],
|
|
250
254
|
)
|
|
251
255
|
|
|
252
256
|
|
|
@@ -278,27 +282,24 @@ class LinkableNodeSpec(NodeSpec):
|
|
|
278
282
|
return value
|
|
279
283
|
|
|
280
284
|
def __eq__(self, other: Any) -> bool:
|
|
285
|
+
if not isinstance(other, LinkableNodeSpec):
|
|
286
|
+
return False
|
|
287
|
+
dimension_links_equal = sorted(
|
|
288
|
+
self.dimension_links or [],
|
|
289
|
+
key=lambda link: link.rendered_dimension_node,
|
|
290
|
+
) == sorted(
|
|
291
|
+
other.dimension_links or [],
|
|
292
|
+
key=lambda link: link.rendered_dimension_node,
|
|
293
|
+
)
|
|
294
|
+
print(
|
|
295
|
+
"Comparing LinkableNodeSpec",
|
|
296
|
+
self.rendered_name,
|
|
297
|
+
eq_columns(self.columns, other.columns),
|
|
298
|
+
)
|
|
281
299
|
return (
|
|
282
300
|
super().__eq__(other)
|
|
283
|
-
and (
|
|
284
|
-
|
|
285
|
-
or (
|
|
286
|
-
self.columns is None
|
|
287
|
-
and not any(
|
|
288
|
-
[attr for attr in col.attributes if attr != "primary_key"]
|
|
289
|
-
for col in other.columns
|
|
290
|
-
)
|
|
291
|
-
and not any(col.partition for col in other.columns)
|
|
292
|
-
)
|
|
293
|
-
)
|
|
294
|
-
and sorted(
|
|
295
|
-
self.dimension_links or [],
|
|
296
|
-
key=lambda link: link.rendered_dimension_node,
|
|
297
|
-
)
|
|
298
|
-
== sorted(
|
|
299
|
-
other.dimension_links or [],
|
|
300
|
-
key=lambda link: link.rendered_dimension_node,
|
|
301
|
-
)
|
|
301
|
+
and eq_columns(self.columns, other.columns)
|
|
302
|
+
and dimension_links_equal
|
|
302
303
|
)
|
|
303
304
|
|
|
304
305
|
|
|
@@ -368,6 +369,18 @@ class MetricSpec(NodeSpec):
|
|
|
368
369
|
min_decimal_exponent: int | None
|
|
369
370
|
max_decimal_exponent: int | None
|
|
370
371
|
|
|
372
|
+
def __init__(self, **data: Any):
|
|
373
|
+
unit = data.pop("unit", None)
|
|
374
|
+
if unit:
|
|
375
|
+
try:
|
|
376
|
+
if isinstance(unit, MetricUnit):
|
|
377
|
+
data["unit_enum"] = unit
|
|
378
|
+
else:
|
|
379
|
+
data["unit_enum"] = MetricUnit[unit.strip().upper()]
|
|
380
|
+
except KeyError: # pragma: no cover
|
|
381
|
+
raise DJInvalidInputException(f"Invalid metric unit: {unit}")
|
|
382
|
+
super().__init__(**data)
|
|
383
|
+
|
|
371
384
|
@property
|
|
372
385
|
def unit(self) -> str | None:
|
|
373
386
|
"""Return lowercased unit name for JSON serialization."""
|
|
@@ -387,11 +400,11 @@ class MetricSpec(NodeSpec):
|
|
|
387
400
|
super().__eq__(other)
|
|
388
401
|
and self.query_ast.compare(other.query_ast)
|
|
389
402
|
and (self.required_dimensions or []) == (other.required_dimensions or [])
|
|
390
|
-
and (self.direction
|
|
391
|
-
and (self.unit
|
|
392
|
-
and
|
|
393
|
-
and
|
|
394
|
-
and
|
|
403
|
+
and eq_or_fallback(self.direction, other.direction, MetricDirection.NEUTRAL)
|
|
404
|
+
and eq_or_fallback(self.unit, other.unit, MetricUnit.UNKNOWN.value.name)
|
|
405
|
+
and self.significant_digits == other.significant_digits
|
|
406
|
+
and self.min_decimal_exponent == other.min_decimal_exponent
|
|
407
|
+
and self.max_decimal_exponent == other.max_decimal_exponent
|
|
395
408
|
)
|
|
396
409
|
|
|
397
410
|
|
|
@@ -421,32 +434,14 @@ class CubeSpec(NodeSpec):
|
|
|
421
434
|
]
|
|
422
435
|
|
|
423
436
|
def __eq__(self, other: Any) -> bool:
|
|
424
|
-
print(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
[attr for attr in col.attributes if attr != "primary_key"]
|
|
429
|
-
for col in other.columns
|
|
430
|
-
],
|
|
431
|
-
not any(col.partition for col in other.columns)
|
|
432
|
-
),
|
|
433
|
-
"metrics:", set(self.rendered_metrics) == set(other.rendered_metrics),
|
|
434
|
-
"dimensions:", set(self.rendered_dimensions) == set(other.rendered_dimensions),
|
|
435
|
-
"filters:", (self.rendered_filters or []) == (other.rendered_filters or [])
|
|
437
|
+
print(
|
|
438
|
+
"Comparing CubeSpec",
|
|
439
|
+
self.rendered_name,
|
|
440
|
+
eq_columns(self.columns, other.columns),
|
|
436
441
|
)
|
|
437
442
|
return (
|
|
438
443
|
super().__eq__(other)
|
|
439
|
-
and (
|
|
440
|
-
(self.columns or []) == (other.columns or [])
|
|
441
|
-
or (
|
|
442
|
-
not self.columns
|
|
443
|
-
and not any(
|
|
444
|
-
[attr for attr in col.attributes if attr != "primary_key"]
|
|
445
|
-
for col in other.columns
|
|
446
|
-
)
|
|
447
|
-
and not any(col.partition for col in other.columns)
|
|
448
|
-
)
|
|
449
|
-
)
|
|
444
|
+
and eq_columns(self.columns, other.columns)
|
|
450
445
|
and set(self.rendered_metrics) == set(other.rendered_metrics)
|
|
451
446
|
and set(self.rendered_dimensions) == set(other.rendered_dimensions)
|
|
452
447
|
and (self.rendered_filters or []) == (other.rendered_filters or [])
|
|
@@ -462,6 +457,30 @@ NodeUnion = Union[
|
|
|
462
457
|
]
|
|
463
458
|
|
|
464
459
|
|
|
460
|
+
def diff(one: BaseModel, two: BaseModel, ignore_fields: list[str] = None) -> list[str]:
|
|
461
|
+
"""
|
|
462
|
+
Compare two Pydantic models and return a list of fields that have changed.
|
|
463
|
+
"""
|
|
464
|
+
changed_fields = [
|
|
465
|
+
field
|
|
466
|
+
for field in one.__fields__.keys()
|
|
467
|
+
if field not in (ignore_fields or [])
|
|
468
|
+
and hasattr(one, field)
|
|
469
|
+
and hasattr(two, field)
|
|
470
|
+
and (
|
|
471
|
+
(
|
|
472
|
+
isinstance(getattr(one, field), (list, dict))
|
|
473
|
+
and set(getattr(one, field) or []) != set(getattr(two, field) or [])
|
|
474
|
+
)
|
|
475
|
+
or (
|
|
476
|
+
not isinstance(getattr(one, field), (list, dict))
|
|
477
|
+
and getattr(one, field) != getattr(two, field)
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
]
|
|
481
|
+
return changed_fields
|
|
482
|
+
|
|
483
|
+
|
|
465
484
|
class DeploymentSpec(BaseModel):
|
|
466
485
|
"""
|
|
467
486
|
Specification of a full deployment (namespace, nodes, tags, and add'l metadata).
|
|
@@ -541,3 +560,40 @@ class DeploymentInfo(BaseModel):
|
|
|
541
560
|
namespace: str
|
|
542
561
|
status: DeploymentStatus
|
|
543
562
|
results: list[DeploymentResult] = Field(default_factory=list)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def eq_or_fallback(a, b, fallback):
|
|
566
|
+
"""
|
|
567
|
+
Helper to compare two values that may be None, with a fallback value
|
|
568
|
+
"""
|
|
569
|
+
return a == b or (a is None and b == fallback)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def eq_columns(a: list[ColumnSpec] | None, b: list[ColumnSpec] | None) -> bool:
|
|
573
|
+
"""
|
|
574
|
+
Compare two lists of ColumnSpec objects (or None) with special rules:
|
|
575
|
+
- None or [] is considered equivalent to a list where every column only has 'primary_key'
|
|
576
|
+
in attributes and partition is None.
|
|
577
|
+
"""
|
|
578
|
+
a_list = a or []
|
|
579
|
+
b_list = b or []
|
|
580
|
+
|
|
581
|
+
a_map = {col.name: col for col in a_list}
|
|
582
|
+
b_map = {col.name: col for col in b_list}
|
|
583
|
+
for col_name, col_a in a_map.items():
|
|
584
|
+
col_b = b_map.get(col_name)
|
|
585
|
+
if (set(col_a.attributes if col_a else []) - {"primary_key"}) != ( # type: ignore
|
|
586
|
+
set(col_b.attributes if col_b else []) - {"primary_key"} # type: ignore
|
|
587
|
+
) or (col_a.partition if col_a else None) != (
|
|
588
|
+
col_b.partition if col_b else None
|
|
589
|
+
): # type: ignore
|
|
590
|
+
return False
|
|
591
|
+
for col_name, col_b in b_map.items():
|
|
592
|
+
col_a = a_map.get(col_name) # type: ignore
|
|
593
|
+
if (set(col_b.attributes if col_b else []) - {"primary_key"}) != ( # type: ignore
|
|
594
|
+
set(col_a.attributes if col_a else []) - {"primary_key"} # type: ignore
|
|
595
|
+
) or (col_b.partition if col_b else None) != (
|
|
596
|
+
col_a.partition if col_a else None
|
|
597
|
+
): # type: ignore
|
|
598
|
+
return False
|
|
599
|
+
return True
|
{datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: datajunction-server
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.dev4
|
|
4
4
|
Summary: DataJunction server library for running to a DataJunction server
|
|
5
5
|
Project-URL: Homepage, https://datajunction.io
|
|
6
6
|
Project-URL: Repository, https://github.com/DataJunction/dj
|
{datajunction_server-0.0.2.dev2.dist-info → datajunction_server-0.0.2.dev4.dist-info}/RECORD
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
datajunction_server/__about__.py,sha256=
|
|
1
|
+
datajunction_server/__about__.py,sha256=7_8nP1iwEx_vfBpO-HcgSpLMpKEmeBk1LgGxMXjgACM,54
|
|
2
2
|
datajunction_server/__init__.py,sha256=nN5-uJoSVEwuc8n-wMygqeF0Xhxi_zqqbCgutZvAt3E,384
|
|
3
3
|
datajunction_server/alembic.ini,sha256=mclJ_xx8pHfRyZ69SA9ZPqUwZaaQCTyxZ6wBmbrf1bo,3024
|
|
4
4
|
datajunction_server/config.py,sha256=L1zkaiF82S-ciR-wVeILx7CWKSOPPJ90a9zooXVNHEc,5641
|
|
@@ -44,8 +44,8 @@ datajunction_server/alembic/versions/2025_07_06_0746-634fdac051c3_unique_contrai
|
|
|
44
44
|
datajunction_server/alembic/versions/2025_08_02_1543-8eab64955a49_add_concrete_measures_for_node_revisions.py,sha256=wN7UtCLjrXsMyDIb1DKJv7p0QULBSQGA0gILgSn-CXw,2394
|
|
45
45
|
datajunction_server/alembic/versions/2025_09_02_0102-5b00137c69f9_add_service_accounts.py,sha256=rv9j0VDoi3bQNTjcPFphwAsvQMOmL2j35PlcYiGKYJM,2055
|
|
46
46
|
datajunction_server/alembic/versions/2025_09_12_0555-759c4d50cb8d_add_cascade_delete.py,sha256=hULra07uoqeUVLKmeOXiznx7FKPBowvOn8e3EWMb8Xo,7236
|
|
47
|
-
datajunction_server/alembic/versions/
|
|
48
|
-
datajunction_server/alembic/versions/
|
|
47
|
+
datajunction_server/alembic/versions/2025_09_16_1513-b55add7e1ebc_add_cascade_delete_on_tag_node_and_.py,sha256=06vQjjaskVQI1EaztJ_jNUObseRWWuLzWqQymZiEKRA,3316
|
|
48
|
+
datajunction_server/alembic/versions/2025_09_17_2107-b6398ba852b3_deployments.py,sha256=NfPEHgvrSsxfCotC_fVKv-A-g5PH5QaLtAEhszqvFVk,1421
|
|
49
49
|
datajunction_server/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
50
|
datajunction_server/api/attributes.py,sha256=zSTHqdf1el7pWX76Eu-sPrt6uzWFwGN0mXiNJFz9vBE,4553
|
|
51
51
|
datajunction_server/api/catalogs.py,sha256=D-avfvhUdgTAnOvMSxMN2I8dXebDuovEZr-dQSB-nk8,4454
|
|
@@ -65,7 +65,7 @@ datajunction_server/api/main.py,sha256=s1gWNs5wGXzCtlqf4faeq-rE6uf_RLn4O7WJnauWr
|
|
|
65
65
|
datajunction_server/api/materializations.py,sha256=buhYWTUGBbRI0fWvREaK1D1F809y5eSxHDWqTQtBewc,21796
|
|
66
66
|
datajunction_server/api/measures.py,sha256=MdNpB4iNqEcU5-hWno5X4QnMTbijc2ISrprS8nyY2WQ,5729
|
|
67
67
|
datajunction_server/api/metrics.py,sha256=X5By7oNWa9b0l65Qf8Bcz--aClngc30y4G3PMdJhTCs,4917
|
|
68
|
-
datajunction_server/api/namespaces.py,sha256=
|
|
68
|
+
datajunction_server/api/namespaces.py,sha256=220PNifdq_DShw9I3OH-VNDd4zJtP54OYRXUTdzSUfA,14781
|
|
69
69
|
datajunction_server/api/nodes.py,sha256=0nOPEKD0OQ8Tq5FLElKI2ODnXRZS25jM9yUywURKfsc,43728
|
|
70
70
|
datajunction_server/api/notifications.py,sha256=0jHnWD6leBxazVgZtfTlL1e8-sZ3Y2PmU8FI-TWepkU,5403
|
|
71
71
|
datajunction_server/api/setup_logging.py,sha256=_1LlR2KMqjjBff1gqJ8Kmyvza1dAYjzGGK6kak1GdcU,174
|
|
@@ -128,7 +128,7 @@ datajunction_server/database/materialization.py,sha256=7_w5ay2RXukTaRS03UILsMFrN
|
|
|
128
128
|
datajunction_server/database/measure.py,sha256=iwZQIhM-_OlZ4v2a3BmZrhpC4ej-MGh6rLqGB70XGYo,6387
|
|
129
129
|
datajunction_server/database/metricmetadata.py,sha256=jKh58lOf5_MoFyDTVtpWhK9-Ox22ryWK2XtBKjPApc4,2084
|
|
130
130
|
datajunction_server/database/namespace.py,sha256=T5GF4eDLgw51xdjWC0T5G_mcAgL4E7OrYuk6x552dhM,5685
|
|
131
|
-
datajunction_server/database/node.py,sha256=
|
|
131
|
+
datajunction_server/database/node.py,sha256=4BJPUaoYQGz-CtBHPcSB8-I1S4yzkd8-Q2NOvRAtYpk,44951
|
|
132
132
|
datajunction_server/database/nodeowner.py,sha256=YydIO1F-S-J6qyNW8ui71H-l6aENbW7RhdX5koIBq8E,1196
|
|
133
133
|
datajunction_server/database/notification_preference.py,sha256=uLy60fWLWLJE940Qj7bY0Rq5b5flEkgcq2uxx4pRvPE,1892
|
|
134
134
|
datajunction_server/database/partition.py,sha256=312KyT0IbaKk-lXxMRvAphJDfA83Wm1RsGp7-dT-U0g,5242
|
|
@@ -138,12 +138,12 @@ datajunction_server/database/user.py,sha256=-zW5ssf2IWgSjvZbOljOzVoqynBtNp3gQxHm
|
|
|
138
138
|
datajunction_server/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
139
139
|
datajunction_server/internal/client.py,sha256=hpManY1PTGQxAzx4S-1aUAoMvlWfPPU1wgL9iLlAg7o,11442
|
|
140
140
|
datajunction_server/internal/cube_materializations.py,sha256=SJYIZfjAQr8n-pgECKoroGfOhoR_J0vnMmpCQ0mbLlY,10695
|
|
141
|
-
datajunction_server/internal/deployment.py,sha256=
|
|
141
|
+
datajunction_server/internal/deployment.py,sha256=N9p5kVllXC61yLX6JXiXRV2P_mjeIt-4gO8fWXm_cVE,45154
|
|
142
142
|
datajunction_server/internal/engines.py,sha256=RKIYIYJQMjRIFS7YXIW-q6vLUxilc3sd3eWHwTmDDu0,858
|
|
143
143
|
datajunction_server/internal/history.py,sha256=MAj2ZTUCGgSLCaS1uB5dWtTqvb2SE_5HHLo0RReCRAc,816
|
|
144
144
|
datajunction_server/internal/materializations.py,sha256=HVc1RPiarOBksvzyLP8Mt5GFvyEPlOqgIe151I_ZZMY,18184
|
|
145
145
|
datajunction_server/internal/namespaces.py,sha256=Ff020CvpzWeypqReU5sPl3kNbHj1aQpw_6MI2DJhFSw,20713
|
|
146
|
-
datajunction_server/internal/nodes.py,sha256=
|
|
146
|
+
datajunction_server/internal/nodes.py,sha256=BbXzMg5G2Tk-xXcBD73UhWR7MQh3B_UeZ9r83LkK8tg,103715
|
|
147
147
|
datajunction_server/internal/notifications.py,sha256=8aP8KBs8k4kKXnQXIUPWyHa4uKsyV0oERipyP0LR0bs,1724
|
|
148
148
|
datajunction_server/internal/seed.py,sha256=mc2TQP4dMwAyWTfGKj5b6wK6pvVyU6azHfxSozPElY0,2363
|
|
149
149
|
datajunction_server/internal/sql.py,sha256=bboANhvtKufVYOOI6RF719GXQFWv-toVXIQQtKIFCtA,18475
|
|
@@ -185,7 +185,7 @@ datajunction_server/models/column.py,sha256=3TCa9dDAb9Q2WEAzpcdqAjKSX3PutbwlS8qG
|
|
|
185
185
|
datajunction_server/models/cube.py,sha256=p6KmqoOVGlziH5k0wXs6qBvgWWQs984Ho6SGplDbtAQ,4459
|
|
186
186
|
datajunction_server/models/cube_materialization.py,sha256=ydMRepDM95b4BBVay_nOYlRtK7OFG3pCDDTRdHe2awU,15787
|
|
187
187
|
datajunction_server/models/database.py,sha256=xhCllbq5ikFNnrPvzuchxQUC9RWogzh73Eqz7Jj0i38,499
|
|
188
|
-
datajunction_server/models/deployment.py,sha256=
|
|
188
|
+
datajunction_server/models/deployment.py,sha256=ZCRgGVy_FLHeoLc98-zRz_n79IZAPsr073Y8k4SOhqA,18051
|
|
189
189
|
datajunction_server/models/dialect.py,sha256=uifriawtKR4vi-GG2rdp4eeQkAkD6iLNuarCLQanTxY,3825
|
|
190
190
|
datajunction_server/models/dimensionlink.py,sha256=WaCarxhawOiQqtH1EVS0RQRu9D8Bx6GFonLsdSVgXSs,1551
|
|
191
191
|
datajunction_server/models/engine.py,sha256=Ebuy4HLkURr7mj0pxTj_Y4fEmF0oDen393rnj9fBnR0,466
|
|
@@ -226,7 +226,7 @@ datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.py,sha2
|
|
|
226
226
|
datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParser.tokens,sha256=JDrzbaKDwIaimAZPYIUzCgzkOEgq0X5-a6_lz78lqgs,8131
|
|
227
227
|
datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserListener.py,sha256=vp8wduYkB-T5Xr6HZCSdzAxTHHPrDI5UJZXRJSVhAGA,102464
|
|
228
228
|
datajunction_server/sql/parsing/backends/grammar/generated/SqlBaseParserVisitor.py,sha256=w3V03LgPIHCiqojNyuekBDYqskjOKlKrd0sczQAB_WQ,60290
|
|
229
|
-
datajunction_server-0.0.2.
|
|
230
|
-
datajunction_server-0.0.2.
|
|
231
|
-
datajunction_server-0.0.2.
|
|
232
|
-
datajunction_server-0.0.2.
|
|
229
|
+
datajunction_server-0.0.2.dev4.dist-info/METADATA,sha256=h0bsPkDT6voeOPRcczUx6yrmvivISRd09p51Kq40Qq4,3769
|
|
230
|
+
datajunction_server-0.0.2.dev4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
231
|
+
datajunction_server-0.0.2.dev4.dist-info/entry_points.txt,sha256=MOInJGdcQ10bDEl-XW4UMokEgx-ypINqBhObeDI8KiQ,74
|
|
232
|
+
datajunction_server-0.0.2.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|