dagster-omni 0.27.9__tar.gz → 0.27.11__tar.gz
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.
Potentially problematic release.
This version of dagster-omni might be problematic. Click here for more details.
- {dagster_omni-0.27.9/dagster_omni.egg-info → dagster_omni-0.27.11}/PKG-INFO +2 -2
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni/component.py +37 -60
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni/objects.py +50 -0
- dagster_omni-0.27.11/dagster_omni/translation.py +143 -0
- dagster_omni-0.27.11/dagster_omni/version.py +1 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni/workspace.py +35 -8
- {dagster_omni-0.27.9 → dagster_omni-0.27.11/dagster_omni.egg-info}/PKG-INFO +2 -2
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni.egg-info/SOURCES.txt +1 -0
- dagster_omni-0.27.11/dagster_omni.egg-info/requires.txt +2 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/setup.py +1 -1
- dagster_omni-0.27.9/dagster_omni/version.py +0 -1
- dagster_omni-0.27.9/dagster_omni.egg-info/requires.txt +0 -2
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/LICENSE +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/MANIFEST.in +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/README.md +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni/__init__.py +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni/py.typed +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni.egg-info/dependency_links.txt +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni.egg-info/not-zip-safe +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/dagster_omni.egg-info/top_level.txt +0 -0
- {dagster_omni-0.27.9 → dagster_omni-0.27.11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dagster_omni
|
|
3
|
-
Version: 0.27.
|
|
3
|
+
Version: 0.27.11
|
|
4
4
|
Summary: Package for integrating Omni with Dagster.
|
|
5
5
|
Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-omni
|
|
6
6
|
Author: Dagster Labs
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: dagster==1.11.
|
|
17
|
+
Requires-Dist: dagster==1.11.11
|
|
18
18
|
Requires-Dist: aiohttp
|
|
19
19
|
Dynamic: author
|
|
20
20
|
Dynamic: author-email
|
|
@@ -1,64 +1,30 @@
|
|
|
1
1
|
import itertools
|
|
2
2
|
from collections import defaultdict
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
4
|
+
from typing import Optional
|
|
5
5
|
|
|
6
6
|
import dagster as dg
|
|
7
7
|
from dagster._annotations import preview
|
|
8
|
-
from dagster._core.definitions.metadata.metadata_set import NamespacedMetadataSet
|
|
9
|
-
from dagster._core.definitions.metadata.metadata_value import UrlMetadataValue
|
|
10
8
|
from dagster._core.errors import DagsterInvalidDefinitionError
|
|
11
9
|
from dagster.components.component.state_backed_component import StateBackedComponent
|
|
12
|
-
from dagster.components.utils.translation import ResolvedTranslationFn
|
|
13
|
-
from dagster_shared.record import record
|
|
14
10
|
from pydantic import Field
|
|
15
|
-
from typing_extensions import Self
|
|
16
11
|
|
|
17
12
|
from dagster_omni.objects import OmniDocument, OmniQuery, OmniWorkspaceData
|
|
13
|
+
from dagster_omni.translation import (
|
|
14
|
+
TRANSLATOR_DATA_METADATA_KEY,
|
|
15
|
+
OmniDocumentMetadataSet,
|
|
16
|
+
OmniTranslatorData,
|
|
17
|
+
ResolvedOmniTranslationFn,
|
|
18
|
+
)
|
|
18
19
|
from dagster_omni.workspace import OmniWorkspace
|
|
19
20
|
|
|
20
|
-
_TRANSLATOR_DATA_METADATA_KEY = ".dagster-omni/translator_data"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class OmniDocumentMetadataSet(NamespacedMetadataSet):
|
|
24
|
-
url: Optional[UrlMetadataValue] = None
|
|
25
|
-
document_name: str
|
|
26
|
-
document_type: str
|
|
27
|
-
|
|
28
|
-
@classmethod
|
|
29
|
-
def from_document(cls, workspace: OmniWorkspace, document: OmniDocument) -> Self:
|
|
30
|
-
url_str = f"{workspace.base_url.rstrip('/')}/dashboards/{document.identifier}"
|
|
31
|
-
return cls(
|
|
32
|
-
url=UrlMetadataValue(url_str) if document.has_dashboard else None,
|
|
33
|
-
document_name=document.name,
|
|
34
|
-
document_type=document.type,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
def namespace(cls) -> str:
|
|
39
|
-
return "dagster-omni"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@record
|
|
43
|
-
class OmniTranslatorData:
|
|
44
|
-
"""Container class for data required to translate an object in an
|
|
45
|
-
Omni workspace into a Dagster definition.
|
|
46
|
-
|
|
47
|
-
Properties:
|
|
48
|
-
obj (Union[OmniDocument, OmniQuery]): The object to translate.
|
|
49
|
-
workspace_data (OmniWorkspaceData): Global workspace data.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
obj: Union[OmniDocument, OmniQuery]
|
|
53
|
-
workspace_data: OmniWorkspaceData
|
|
54
|
-
|
|
55
21
|
|
|
56
22
|
@preview
|
|
57
23
|
class OmniComponent(StateBackedComponent, dg.Model, dg.Resolvable):
|
|
58
24
|
workspace: OmniWorkspace = Field(
|
|
59
25
|
description="Defines configuration for interacting with an Omni instance.",
|
|
60
26
|
)
|
|
61
|
-
translation: Optional[
|
|
27
|
+
translation: Optional[ResolvedOmniTranslationFn] = Field(
|
|
62
28
|
default=None,
|
|
63
29
|
description="Defines how to translate an Omni object into an AssetSpec object.",
|
|
64
30
|
)
|
|
@@ -72,49 +38,58 @@ class OmniComponent(StateBackedComponent, dg.Model, dg.Resolvable):
|
|
|
72
38
|
"""Load state from path using Dagster's deserialization system."""
|
|
73
39
|
return dg.deserialize_value(state_path.read_text(), OmniWorkspaceData)
|
|
74
40
|
|
|
75
|
-
def
|
|
41
|
+
def _get_default_omni_spec(
|
|
42
|
+
self, context: dg.ComponentLoadContext, data: OmniTranslatorData, workspace: OmniWorkspace
|
|
43
|
+
) -> Optional[dg.AssetSpec]:
|
|
76
44
|
"""Core function for converting an Omni document into an AssetSpec object."""
|
|
77
45
|
if isinstance(data.obj, OmniDocument):
|
|
78
46
|
doc = data.obj
|
|
79
|
-
|
|
47
|
+
maybe_deps = [
|
|
80
48
|
self.get_asset_spec(
|
|
81
|
-
OmniTranslatorData(obj=query, workspace_data=data.workspace_data)
|
|
49
|
+
context, OmniTranslatorData(obj=query, workspace_data=data.workspace_data)
|
|
82
50
|
)
|
|
83
|
-
for query in
|
|
51
|
+
for query in data.obj.queries
|
|
84
52
|
]
|
|
85
53
|
|
|
86
54
|
prefix = doc.folder.path.split("/") if doc.folder else []
|
|
55
|
+
user = data.workspace_data.get_user(doc.owner.id)
|
|
56
|
+
owner_email = user.primary_email if user else None
|
|
57
|
+
|
|
87
58
|
return dg.AssetSpec(
|
|
88
59
|
key=dg.AssetKey([*prefix, doc.name]),
|
|
89
60
|
group_name=prefix[0].replace("-", "_") if prefix else None,
|
|
90
61
|
tags={label.name: "" for label in doc.labels},
|
|
91
|
-
deps=
|
|
62
|
+
deps=list(filter(None, maybe_deps)),
|
|
92
63
|
metadata={
|
|
93
|
-
**OmniDocumentMetadataSet.from_document(
|
|
94
|
-
|
|
64
|
+
**OmniDocumentMetadataSet.from_document(workspace, doc),
|
|
65
|
+
TRANSLATOR_DATA_METADATA_KEY: data,
|
|
95
66
|
},
|
|
96
67
|
kinds={"omni"},
|
|
68
|
+
owners=[owner_email] if owner_email else None,
|
|
97
69
|
)
|
|
98
|
-
|
|
70
|
+
if isinstance(data.obj, OmniQuery):
|
|
99
71
|
return dg.AssetSpec(key=dg.AssetKey([data.obj.query_config.table]))
|
|
100
|
-
|
|
101
|
-
raise ValueError(f"Unsupported object type: {type(data.obj)}")
|
|
72
|
+
return None
|
|
102
73
|
|
|
103
|
-
def get_asset_spec(
|
|
74
|
+
def get_asset_spec(
|
|
75
|
+
self, context: dg.ComponentLoadContext, data: OmniTranslatorData
|
|
76
|
+
) -> Optional[dg.AssetSpec]:
|
|
104
77
|
"""Core function for converting an Omni document into an AssetSpec object."""
|
|
105
|
-
base_asset_spec = self.
|
|
106
|
-
if self.translation:
|
|
78
|
+
base_asset_spec = self._get_default_omni_spec(context, data, self.workspace)
|
|
79
|
+
if self.translation and base_asset_spec:
|
|
107
80
|
return self.translation(base_asset_spec, data)
|
|
108
81
|
else:
|
|
109
82
|
return base_asset_spec
|
|
110
83
|
|
|
111
|
-
def _build_asset_specs(
|
|
84
|
+
def _build_asset_specs(
|
|
85
|
+
self, context: dg.ComponentLoadContext, workspace_data: OmniWorkspaceData
|
|
86
|
+
) -> list[dg.AssetSpec]:
|
|
112
87
|
"""Invokes the `get_asset_spec` method on all objects in the provided `workspace_data`.
|
|
113
88
|
Filters out any cases where the asset_spec is `None`, and provides a helpful error
|
|
114
89
|
message in cases where keys overlap between different documents.
|
|
115
90
|
"""
|
|
116
91
|
maybe_specs = [
|
|
117
|
-
self.get_asset_spec(OmniTranslatorData(obj=doc, workspace_data=workspace_data))
|
|
92
|
+
self.get_asset_spec(context, OmniTranslatorData(obj=doc, workspace_data=workspace_data))
|
|
118
93
|
for doc in workspace_data.documents
|
|
119
94
|
]
|
|
120
95
|
|
|
@@ -136,8 +111,10 @@ class OmniComponent(StateBackedComponent, dg.Model, dg.Resolvable):
|
|
|
136
111
|
|
|
137
112
|
return list(itertools.chain.from_iterable(specs_by_key.values()))
|
|
138
113
|
|
|
139
|
-
def build_defs_from_workspace_data(
|
|
140
|
-
|
|
114
|
+
def build_defs_from_workspace_data(
|
|
115
|
+
self, context: dg.ComponentLoadContext, workspace_data: OmniWorkspaceData
|
|
116
|
+
) -> dg.Definitions:
|
|
117
|
+
return dg.Definitions(assets=self._build_asset_specs(context, workspace_data))
|
|
141
118
|
|
|
142
119
|
def build_defs_from_state(
|
|
143
120
|
self, context: dg.ComponentLoadContext, state_path: Optional[Path]
|
|
@@ -146,4 +123,4 @@ class OmniComponent(StateBackedComponent, dg.Model, dg.Resolvable):
|
|
|
146
123
|
return dg.Definitions()
|
|
147
124
|
|
|
148
125
|
state = self.load_state_from_path(state_path)
|
|
149
|
-
return self.build_defs_from_workspace_data(state)
|
|
126
|
+
return self.build_defs_from_workspace_data(context, state)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from functools import cached_property
|
|
1
2
|
from typing import Any, Optional
|
|
2
3
|
|
|
3
4
|
from dagster_shared.record import record
|
|
@@ -57,6 +58,8 @@ class OmniDocument:
|
|
|
57
58
|
folder: Optional[OmniFolder]
|
|
58
59
|
labels: list[OmniLabel]
|
|
59
60
|
queries: list["OmniQuery"]
|
|
61
|
+
favorites: Optional[int] = None
|
|
62
|
+
views: Optional[int] = None
|
|
60
63
|
|
|
61
64
|
@classmethod
|
|
62
65
|
def from_json(cls, data: dict[str, Any], queries: list["OmniQuery"]) -> "OmniDocument":
|
|
@@ -81,6 +84,8 @@ class OmniDocument:
|
|
|
81
84
|
folder=folder,
|
|
82
85
|
labels=labels,
|
|
83
86
|
queries=queries,
|
|
87
|
+
favorites=data.get("_count", {}).get("favorites", None),
|
|
88
|
+
views=data.get("_count", {}).get("views", None),
|
|
84
89
|
)
|
|
85
90
|
|
|
86
91
|
|
|
@@ -118,6 +123,42 @@ class OmniQuery:
|
|
|
118
123
|
)
|
|
119
124
|
|
|
120
125
|
|
|
126
|
+
@whitelist_for_serdes
|
|
127
|
+
@record
|
|
128
|
+
class OmniUser:
|
|
129
|
+
"""Represents an Omni user with all user information in a single class."""
|
|
130
|
+
|
|
131
|
+
id: str
|
|
132
|
+
name: Optional[str]
|
|
133
|
+
display_name: str
|
|
134
|
+
user_name: str
|
|
135
|
+
active: bool
|
|
136
|
+
primary_email: Optional[str]
|
|
137
|
+
groups: list[str]
|
|
138
|
+
created: str
|
|
139
|
+
last_modified: str
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def from_json(cls, data: dict[str, Any]) -> "OmniUser":
|
|
143
|
+
"""Create OmniUser from JSON response data."""
|
|
144
|
+
primary_email = next(
|
|
145
|
+
(email["value"] for email in data.get("emails", []) if email["primary"]), None
|
|
146
|
+
)
|
|
147
|
+
groups = [group["display"] for group in data.get("groups", [])]
|
|
148
|
+
|
|
149
|
+
return cls(
|
|
150
|
+
id=data["id"],
|
|
151
|
+
name=data.get("displayName") or data.get("userName") or "",
|
|
152
|
+
display_name=data.get("displayName", ""),
|
|
153
|
+
user_name=data.get("userName", ""),
|
|
154
|
+
active=data.get("active", True),
|
|
155
|
+
primary_email=primary_email,
|
|
156
|
+
groups=groups,
|
|
157
|
+
created=data.get("meta", {}).get("created", ""),
|
|
158
|
+
last_modified=data.get("meta", {}).get("lastModified", ""),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
121
162
|
@whitelist_for_serdes
|
|
122
163
|
@record
|
|
123
164
|
class OmniWorkspaceData:
|
|
@@ -125,6 +166,15 @@ class OmniWorkspaceData:
|
|
|
125
166
|
|
|
126
167
|
Properties:
|
|
127
168
|
documents: list[OmniDocument]
|
|
169
|
+
users: list[OmniUser]
|
|
128
170
|
"""
|
|
129
171
|
|
|
130
172
|
documents: list[OmniDocument]
|
|
173
|
+
users: list[OmniUser]
|
|
174
|
+
|
|
175
|
+
@cached_property
|
|
176
|
+
def _users_by_id(self) -> dict[str, OmniUser]:
|
|
177
|
+
return {user.id: user for user in self.users}
|
|
178
|
+
|
|
179
|
+
def get_user(self, user_id: str) -> Optional[OmniUser]:
|
|
180
|
+
return self._users_by_id.get(user_id)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from typing import Annotated, Optional, Union
|
|
2
|
+
|
|
3
|
+
import dateutil
|
|
4
|
+
from dagster._core.definitions.assets.definition.asset_spec import AssetSpec
|
|
5
|
+
from dagster._core.definitions.metadata.metadata_set import NamespacedMetadataSet
|
|
6
|
+
from dagster._core.definitions.metadata.metadata_value import (
|
|
7
|
+
TimestampMetadataValue,
|
|
8
|
+
UrlMetadataValue,
|
|
9
|
+
)
|
|
10
|
+
from dagster.components import Resolvable, Resolver
|
|
11
|
+
from dagster.components.resolved.base import resolve_fields
|
|
12
|
+
from dagster.components.resolved.context import ResolutionContext
|
|
13
|
+
from dagster.components.resolved.core_models import AssetSpecKeyUpdateKwargs, AssetSpecUpdateKwargs
|
|
14
|
+
from dagster.components.utils import TranslatorResolvingInfo
|
|
15
|
+
from dagster.components.utils.translation import TranslationFn, TranslationFnResolver
|
|
16
|
+
from dagster_shared.record import record
|
|
17
|
+
from typing_extensions import Self, TypeAlias
|
|
18
|
+
|
|
19
|
+
from dagster_omni.objects import OmniDocument, OmniQuery, OmniWorkspaceData
|
|
20
|
+
from dagster_omni.workspace import OmniWorkspace
|
|
21
|
+
|
|
22
|
+
TRANSLATOR_DATA_METADATA_KEY = ".dagster-omni/translator_data"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OmniDocumentMetadataSet(NamespacedMetadataSet):
|
|
26
|
+
"""Represents metadata that is captured from an Omni document."""
|
|
27
|
+
|
|
28
|
+
url: Optional[UrlMetadataValue] = None
|
|
29
|
+
owner_name: str
|
|
30
|
+
document_name: str
|
|
31
|
+
document_type: str
|
|
32
|
+
updated_at: TimestampMetadataValue
|
|
33
|
+
favorites: Optional[int] = None
|
|
34
|
+
views: Optional[int] = None
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def from_document(cls, workspace: OmniWorkspace, document: OmniDocument) -> Self:
|
|
38
|
+
url_str = f"{workspace.base_url.rstrip('/')}/dashboards/{document.identifier}"
|
|
39
|
+
|
|
40
|
+
return cls(
|
|
41
|
+
url=UrlMetadataValue(url_str) if document.has_dashboard else None,
|
|
42
|
+
document_name=document.name,
|
|
43
|
+
document_type=document.type,
|
|
44
|
+
updated_at=TimestampMetadataValue(
|
|
45
|
+
dateutil.parser.parse(document.updated_at).timestamp()
|
|
46
|
+
),
|
|
47
|
+
owner_name=document.owner.name,
|
|
48
|
+
favorites=document.favorites,
|
|
49
|
+
views=document.views,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def namespace(cls) -> str:
|
|
54
|
+
return "dagster-omni"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@record
|
|
58
|
+
class OmniTranslatorData:
|
|
59
|
+
"""Container class for data required to translate an object in an
|
|
60
|
+
Omni workspace into a Dagster definition.
|
|
61
|
+
|
|
62
|
+
Properties:
|
|
63
|
+
obj (Union[OmniDocument, OmniQuery]): The object to translate.
|
|
64
|
+
workspace_data (OmniWorkspaceData): Global workspace data.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
obj: Union[OmniDocument, OmniQuery]
|
|
68
|
+
workspace_data: OmniWorkspaceData
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve_multilayer_translation(context: ResolutionContext, model):
|
|
72
|
+
"""The Omni translation schema supports defining global transforms
|
|
73
|
+
as well as per-object-type transforms. This resolver composes the
|
|
74
|
+
per-object-type transforms with the global transforms.
|
|
75
|
+
"""
|
|
76
|
+
info = TranslatorResolvingInfo(
|
|
77
|
+
asset_attributes=model,
|
|
78
|
+
resolution_context=context,
|
|
79
|
+
model_key="translation",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _translation_fn(base_asset_spec: AssetSpec, data: OmniTranslatorData):
|
|
83
|
+
processed_spec = info.get_asset_spec(
|
|
84
|
+
base_asset_spec,
|
|
85
|
+
{
|
|
86
|
+
"data": data,
|
|
87
|
+
"spec": base_asset_spec,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
nested_translation_fns = resolve_fields(
|
|
92
|
+
model=model,
|
|
93
|
+
resolved_cls=OmniTranslationArgs,
|
|
94
|
+
context=context.with_scope(
|
|
95
|
+
**{
|
|
96
|
+
"data": data,
|
|
97
|
+
"spec": processed_spec,
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
for_document = nested_translation_fns.get("for_document")
|
|
102
|
+
for_query = nested_translation_fns.get("for_query")
|
|
103
|
+
|
|
104
|
+
if isinstance(data.obj, OmniDocument) and for_document:
|
|
105
|
+
return for_document(processed_spec, data)
|
|
106
|
+
if isinstance(data.obj, OmniQuery) and for_query:
|
|
107
|
+
return for_query(processed_spec, data)
|
|
108
|
+
|
|
109
|
+
return processed_spec
|
|
110
|
+
|
|
111
|
+
return _translation_fn
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
OmniTranslationFn: TypeAlias = TranslationFn[OmniTranslatorData]
|
|
115
|
+
|
|
116
|
+
ResolvedTargetedOmniTranslationFn = Annotated[
|
|
117
|
+
OmniTranslationFn,
|
|
118
|
+
TranslationFnResolver[OmniTranslatorData](lambda data: {"data": data}),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
ResolvedTargetedKeyOnlyOmniTranslationFn = Annotated[
|
|
122
|
+
OmniTranslationFn,
|
|
123
|
+
TranslationFnResolver[OmniTranslatorData](
|
|
124
|
+
lambda data: {"data": data}, model_field_type=AssetSpecKeyUpdateKwargs.model()
|
|
125
|
+
),
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@record
|
|
130
|
+
class OmniTranslationArgs(AssetSpecUpdateKwargs, Resolvable):
|
|
131
|
+
"""Model used to allow per-object-type translation of an Omni object."""
|
|
132
|
+
|
|
133
|
+
for_document: Optional[ResolvedTargetedOmniTranslationFn] = None
|
|
134
|
+
for_query: Optional[ResolvedTargetedKeyOnlyOmniTranslationFn] = None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
ResolvedOmniTranslationFn = Annotated[
|
|
138
|
+
OmniTranslationFn,
|
|
139
|
+
Resolver(
|
|
140
|
+
_resolve_multilayer_translation,
|
|
141
|
+
model_field_type=Union[str, OmniTranslationArgs.model()],
|
|
142
|
+
),
|
|
143
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.27.11"
|
|
@@ -8,7 +8,7 @@ from aiohttp.client_exceptions import ClientResponseError
|
|
|
8
8
|
from dagster._utils.backoff import async_backoff, exponential_delay_generator
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
|
-
from dagster_omni.objects import OmniDocument, OmniQuery, OmniWorkspaceData
|
|
11
|
+
from dagster_omni.objects import OmniDocument, OmniQuery, OmniUser, OmniWorkspaceData
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
@@ -50,7 +50,7 @@ class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
|
50
50
|
return isinstance(exc, aiohttp.ClientError)
|
|
51
51
|
|
|
52
52
|
def _build_url(self, endpoint: str) -> str:
|
|
53
|
-
return f"{self.base_url.rstrip('/')}/
|
|
53
|
+
return f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
|
|
54
54
|
|
|
55
55
|
async def make_request(
|
|
56
56
|
self,
|
|
@@ -79,7 +79,7 @@ class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
|
79
79
|
|
|
80
80
|
async def _fetch_document_queries(self, document_identifier: str) -> list[OmniQuery]:
|
|
81
81
|
"""Fetch all queries for a specific document."""
|
|
82
|
-
endpoint = f"documents/{document_identifier}/queries"
|
|
82
|
+
endpoint = f"api/v1/documents/{document_identifier}/queries"
|
|
83
83
|
try:
|
|
84
84
|
response = await self.make_request(endpoint)
|
|
85
85
|
return [OmniQuery.from_json(query_data) for query_data in response.get("queries", [])]
|
|
@@ -96,7 +96,7 @@ class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
|
96
96
|
|
|
97
97
|
async def _fetch_documents(self) -> list[OmniDocument]:
|
|
98
98
|
"""Fetch all documents from the Omni API with their queries embedded."""
|
|
99
|
-
base_params = {"pageSize": "100"}
|
|
99
|
+
base_params = {"pageSize": "100", "include": "_count"}
|
|
100
100
|
documents = []
|
|
101
101
|
next_cursor = None
|
|
102
102
|
|
|
@@ -105,7 +105,7 @@ class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
|
105
105
|
if next_cursor:
|
|
106
106
|
params["cursor"] = next_cursor
|
|
107
107
|
|
|
108
|
-
response = await self.make_request("documents", params)
|
|
108
|
+
response = await self.make_request("api/v1/documents", params)
|
|
109
109
|
|
|
110
110
|
# Fan out the requests to fetch queries for each document in parallel
|
|
111
111
|
coroutines = [
|
|
@@ -120,10 +120,37 @@ class OmniWorkspace(dg.Resolvable, dg.Model):
|
|
|
120
120
|
|
|
121
121
|
return documents
|
|
122
122
|
|
|
123
|
+
async def _fetch_users(self) -> list[OmniUser]:
|
|
124
|
+
"""Fetch all users from the Omni SCIM API."""
|
|
125
|
+
base_params = {"count": "100"}
|
|
126
|
+
users = []
|
|
127
|
+
start_index = 1
|
|
128
|
+
|
|
129
|
+
while True:
|
|
130
|
+
params = base_params.copy()
|
|
131
|
+
params["startIndex"] = str(start_index)
|
|
132
|
+
|
|
133
|
+
response = await self.make_request("api/scim/v2/users", params)
|
|
134
|
+
|
|
135
|
+
user_resources = response.get("Resources", [])
|
|
136
|
+
if not user_resources:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
users.extend([OmniUser.from_json(user_data) for user_data in user_resources])
|
|
140
|
+
|
|
141
|
+
# Check if we've received fewer users than requested, indicating we're done
|
|
142
|
+
if len(user_resources) < int(base_params["count"]):
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
start_index += len(user_resources)
|
|
146
|
+
|
|
147
|
+
return users
|
|
148
|
+
|
|
123
149
|
async def fetch_omni_state(self) -> OmniWorkspaceData:
|
|
124
|
-
"""Fetch all documents from the Omni API
|
|
150
|
+
"""Fetch all documents and users from the Omni API.
|
|
125
151
|
|
|
126
152
|
This is the main public method for getting complete Omni state.
|
|
127
153
|
"""
|
|
128
|
-
documents
|
|
129
|
-
|
|
154
|
+
# Fetch documents and users concurrently
|
|
155
|
+
documents, users = await asyncio.gather(self._fetch_documents(), self._fetch_users())
|
|
156
|
+
return OmniWorkspaceData(documents=documents, users=users)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dagster_omni
|
|
3
|
-
Version: 0.27.
|
|
3
|
+
Version: 0.27.11
|
|
4
4
|
Summary: Package for integrating Omni with Dagster.
|
|
5
5
|
Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-omni
|
|
6
6
|
Author: Dagster Labs
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: dagster==1.11.
|
|
17
|
+
Requires-Dist: dagster==1.11.11
|
|
18
18
|
Requires-Dist: aiohttp
|
|
19
19
|
Dynamic: author
|
|
20
20
|
Dynamic: author-email
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.27.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|