dasl-client 1.0.23__py3-none-any.whl → 1.0.24__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.
Potentially problematic release.
This version of dasl-client might be problematic. Click here for more details.
- dasl_client/__init__.py +1 -0
- dasl_client/client.py +240 -23
- dasl_client/exec_rule.py +92 -0
- dasl_client/helpers.py +1 -1
- dasl_client/preset_development/errors.py +42 -0
- dasl_client/preset_development/preview_engine.py +106 -25
- dasl_client/preset_development/preview_parameters.py +206 -94
- dasl_client/preset_development/stage.py +87 -24
- dasl_client/regions.json +3 -0
- dasl_client/regions.py +18 -0
- dasl_client/types/datasource.py +51 -0
- dasl_client/types/rule.py +33 -0
- dasl_client/types/workspace_config.py +13 -1
- dasl_client-1.0.24.dist-info/METADATA +18 -0
- dasl_client-1.0.24.dist-info/RECORD +32 -0
- {dasl_client-1.0.23.dist-info → dasl_client-1.0.24.dist-info}/WHEEL +1 -1
- {dasl_client-1.0.23.dist-info → dasl_client-1.0.24.dist-info}/top_level.txt +0 -1
- dasl_client-1.0.23.dist-info/METADATA +0 -34
- dasl_client-1.0.23.dist-info/RECORD +0 -36
- test/__init__.py +0 -0
- test/conftest.py +0 -18
- test/constants.py +0 -10
- test/test_api_changes.py +0 -137
- test/test_api_surface.py +0 -306
- test/test_databricks_secret_auth.py +0 -119
- test/test_marshaling.py +0 -921
- {dasl_client-1.0.23.dist-info → dasl_client-1.0.24.dist-info/licenses}/LICENSE +0 -0
|
@@ -4,6 +4,8 @@ from pyspark.sql.dataframe import DataFrame
|
|
|
4
4
|
from pyspark.sql.functions import col, lit
|
|
5
5
|
from dasl_client.preset_development.errors import *
|
|
6
6
|
|
|
7
|
+
import re
|
|
8
|
+
|
|
7
9
|
FieldSpec = Dict[str, Any]
|
|
8
10
|
|
|
9
11
|
|
|
@@ -45,6 +47,57 @@ class Stage:
|
|
|
45
47
|
|
|
46
48
|
__op_list = ["assert", "literal", "from", "alias", "expr", "join"]
|
|
47
49
|
|
|
50
|
+
# From fieldSpec valid characters.
|
|
51
|
+
__invalid_char_pattern = re.compile(r"[\s,;{}\(\)\n\t=]")
|
|
52
|
+
|
|
53
|
+
def __validate_field_spec(self, fields: List[Dict[str, str]], stage: str):
|
|
54
|
+
names = []
|
|
55
|
+
for field in self._fields:
|
|
56
|
+
# Check for name. If no name check for assert.
|
|
57
|
+
if not (name := field.get("name", None)):
|
|
58
|
+
if not field.get("assert", None): # Can't walrus em all :/
|
|
59
|
+
raise MissingFieldNameError(self._stage, self._name)
|
|
60
|
+
|
|
61
|
+
# Check this new name does not duplicate an existing.
|
|
62
|
+
if name in names:
|
|
63
|
+
raise DuplicateFieldNameError(self._stage, self._name, name)
|
|
64
|
+
names += [name] if name != None else []
|
|
65
|
+
|
|
66
|
+
# Check for only 1 defined operation.
|
|
67
|
+
missing_op_count = [
|
|
68
|
+
spec for spec in [field.get(op, None) for op in self.__op_list]
|
|
69
|
+
].count(None)
|
|
70
|
+
if (missing_op_count == len(self.__op_list)) or (
|
|
71
|
+
len(self.__op_list) - missing_op_count > 1
|
|
72
|
+
):
|
|
73
|
+
raise MalformedFieldError(
|
|
74
|
+
self._stage, self._name, field.get("name", None)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Literal must be a string.
|
|
78
|
+
if lit := field.get("literal", None):
|
|
79
|
+
if type(lit) != str:
|
|
80
|
+
raise InvalidLiteralError(
|
|
81
|
+
self._stage, self._name, field.get("name", None)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Validate from (makes sure its not an expression, etc.). This mirrors Scala code's validation.
|
|
85
|
+
if frm := field.get("from", None):
|
|
86
|
+
if len(frm) >= 256:
|
|
87
|
+
raise InvalidFromError(
|
|
88
|
+
self._stage,
|
|
89
|
+
self._name,
|
|
90
|
+
field.get("name", None),
|
|
91
|
+
"Column name too long",
|
|
92
|
+
)
|
|
93
|
+
if frm.strip() == "" or self.__invalid_char_pattern.search(frm):
|
|
94
|
+
raise InvalidFromError(
|
|
95
|
+
self._stage,
|
|
96
|
+
self._name,
|
|
97
|
+
field.get("name", None),
|
|
98
|
+
"Malformed column name referenced",
|
|
99
|
+
)
|
|
100
|
+
|
|
48
101
|
def __init__(self, spark: SparkSession, stage: str, table: Dict[str, any]):
|
|
49
102
|
"""
|
|
50
103
|
Initializes a Stage object that encapsulates all operations required for a single
|
|
@@ -68,9 +121,10 @@ class Stage:
|
|
|
68
121
|
self._utils = table.get("utils", {})
|
|
69
122
|
self._input = table.get("input", None)
|
|
70
123
|
|
|
124
|
+
# The dasl_id does not exist before bronze or when dealing with temp fields.
|
|
71
125
|
fields = (
|
|
72
126
|
[{"name": "dasl_id", "from": "dasl_id"}] + table.get("fields", [])
|
|
73
|
-
if self._stage
|
|
127
|
+
if self._stage not in ["temp_fields", "bronze pretransform"]
|
|
74
128
|
else table.get("fields", [])
|
|
75
129
|
)
|
|
76
130
|
self._fields = [
|
|
@@ -78,24 +132,7 @@ class Stage:
|
|
|
78
132
|
]
|
|
79
133
|
self._assertions = [f for f in fields if f.get("assert", None)]
|
|
80
134
|
|
|
81
|
-
|
|
82
|
-
for field in self._fields:
|
|
83
|
-
if not (name := field.get("name", None)):
|
|
84
|
-
if not field.get("assert", None): # Can't walrus em all :/
|
|
85
|
-
raise MissingFieldNameError(self._stage, self._name)
|
|
86
|
-
if name in names:
|
|
87
|
-
raise DuplicateFieldNameError(self._stage, self._name, name)
|
|
88
|
-
names += [name]
|
|
89
|
-
|
|
90
|
-
missing_op_count = [
|
|
91
|
-
spec for spec in [field.get(op, None) for op in self.__op_list]
|
|
92
|
-
].count(None)
|
|
93
|
-
if (missing_op_count == len(self.__op_list)) or (
|
|
94
|
-
len(self.__op_list) - missing_op_count > 1
|
|
95
|
-
):
|
|
96
|
-
raise MalformedFieldError(
|
|
97
|
-
self._stage, self._name, field.get("name", None)
|
|
98
|
-
)
|
|
135
|
+
self.__validate_field_spec(self._fields, self._stage)
|
|
99
136
|
|
|
100
137
|
def _referenced_columns(self) -> List[str]:
|
|
101
138
|
"""
|
|
@@ -311,9 +348,15 @@ class Stage:
|
|
|
311
348
|
# check that the from column exists in the df?
|
|
312
349
|
return f"{self.auto_backtick(field['from'])} AS {self.auto_backtick(name)}"
|
|
313
350
|
elif field.get("literal", None):
|
|
314
|
-
return f"
|
|
351
|
+
return f"{repr(field['literal'])} AS {self.auto_backtick(name)}"
|
|
315
352
|
elif field.get("expr", None) is not None:
|
|
316
|
-
|
|
353
|
+
expr = field["expr"].replace("\\", "\\\\")
|
|
354
|
+
# If we are in a bronze pretransform, we do not want the fieldSpec.name.
|
|
355
|
+
return (
|
|
356
|
+
f"{expr} AS {self.auto_backtick(name)}"
|
|
357
|
+
if self._stage != "bronze pretransform"
|
|
358
|
+
else expr
|
|
359
|
+
)
|
|
317
360
|
else:
|
|
318
361
|
return ""
|
|
319
362
|
|
|
@@ -426,6 +469,8 @@ class Stage:
|
|
|
426
469
|
select_fields = self.render_fields(self._fields)
|
|
427
470
|
|
|
428
471
|
if preserve := self._utils.get("unreferencedColumns", None):
|
|
472
|
+
if self._stage == "gold": # No utils run in gold.
|
|
473
|
+
raise DisallowedUtilityConfigurationError("unreferencedColumns", "gold")
|
|
429
474
|
should_preserve = preserve.get("preserve", None)
|
|
430
475
|
if type(should_preserve) != bool:
|
|
431
476
|
raise MissingUtilityConfigurationFieldError(
|
|
@@ -438,9 +483,23 @@ class Stage:
|
|
|
438
483
|
# applying backticks to all of them is OK here
|
|
439
484
|
# since they will never use "obj.key" to reference nested fields of structs
|
|
440
485
|
# so we just go ahead and apply backticks to all across the board
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
486
|
+
colType = preserve.get("embedColumnType", "struct")
|
|
487
|
+
if colType == "struct":
|
|
488
|
+
select_fields += [
|
|
489
|
+
f"struct({', '.join(list(map(lambda x: self.force_apply_backticks(x), preserved_columns)))}) AS {self.auto_backtick(embed_col)}"
|
|
490
|
+
]
|
|
491
|
+
elif colType == "json":
|
|
492
|
+
select_fields += [
|
|
493
|
+
f"to_json(struct({', '.join(list(map(lambda x: self.force_apply_backticks(x), preserved_columns)))})) AS {self.auto_backtick(embed_col)}"
|
|
494
|
+
]
|
|
495
|
+
elif colType == "variant":
|
|
496
|
+
select_fields += [
|
|
497
|
+
f"parse_json(to_json(struct({', '.join(list(map(lambda x: self.force_apply_backticks(x), preserved_columns)))}))) AS {self.auto_backtick(embed_col)}"
|
|
498
|
+
]
|
|
499
|
+
else:
|
|
500
|
+
raise UnknownUtilityConfigurationFieldError(
|
|
501
|
+
"embedColumnType", "unreferencedColumns"
|
|
502
|
+
)
|
|
444
503
|
else:
|
|
445
504
|
(
|
|
446
505
|
preserved_columns,
|
|
@@ -476,6 +535,8 @@ class Stage:
|
|
|
476
535
|
A DataFrame with the resultant operation's records.
|
|
477
536
|
"""
|
|
478
537
|
if json_extracts := self._utils.get("jsonExtract", None):
|
|
538
|
+
if self._stage == "gold": # No utils run in gold.
|
|
539
|
+
raise DisallowedUtilityConfigurationError("jsonExtract", "gold")
|
|
479
540
|
for json_extract in json_extracts:
|
|
480
541
|
source = json_extract.get("source")
|
|
481
542
|
if not source:
|
|
@@ -610,6 +671,8 @@ class Stage:
|
|
|
610
671
|
A DataFrame with the resultant operation's records.
|
|
611
672
|
"""
|
|
612
673
|
if temp_fields := self._utils.get("temporaryFields", None):
|
|
674
|
+
if self._stage == "gold": # No utils run in gold.
|
|
675
|
+
raise DisallowedUtilityConfigurationError("temporaryFields", "gold")
|
|
613
676
|
df = Stage(self._spark, "temp_fields", {"fields": temp_fields}).run(df)
|
|
614
677
|
return df
|
|
615
678
|
|
dasl_client/regions.json
ADDED
dasl_client/regions.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from importlib import resources
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
_data = json.loads(resources.files(__package__).joinpath("regions.json").read_text())
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Regions:
|
|
9
|
+
@staticmethod
|
|
10
|
+
def lookup(name: str) -> str:
|
|
11
|
+
try:
|
|
12
|
+
return _data(name)
|
|
13
|
+
except KeyError as e:
|
|
14
|
+
raise ValueError(f"unknown region {name}") from e
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def list() -> List[str]:
|
|
18
|
+
return list(_data.keys())
|
dasl_client/types/datasource.py
CHANGED
|
@@ -32,6 +32,7 @@ from dasl_api import (
|
|
|
32
32
|
CoreV1DataSourceFieldUtils,
|
|
33
33
|
CoreV1DataSourceFieldUtilsUnreferencedColumns,
|
|
34
34
|
CoreV1DataSourceFieldUtilsJsonExtractInner,
|
|
35
|
+
CoreV1DataSourcePrimaryKeySpec,
|
|
35
36
|
)
|
|
36
37
|
|
|
37
38
|
from .helpers import Helpers
|
|
@@ -354,6 +355,11 @@ class BronzeSpec(BaseModel):
|
|
|
354
355
|
The name of the bronze table to create and hold the imported data.
|
|
355
356
|
skip_bronze_loading (Optional[bool]):
|
|
356
357
|
Indicates whether to skip the bronze loading step.
|
|
358
|
+
load_as_single_variant (Optional[bool]):
|
|
359
|
+
Indicates whether to ingest data into a single VARIANT-typed column called `data`
|
|
360
|
+
pre_transform (Optional[List[List[str]]]):
|
|
361
|
+
A list of pre-transform steps to execute.
|
|
362
|
+
The outer list form stages and the inner list contains SQL select expressions to be executed within each stage
|
|
357
363
|
"""
|
|
358
364
|
|
|
359
365
|
class Clustering(BaseModel):
|
|
@@ -391,6 +397,8 @@ class BronzeSpec(BaseModel):
|
|
|
391
397
|
clustering: Optional["BronzeSpec.Clustering"] = None
|
|
392
398
|
bronze_table: Optional[str] = None
|
|
393
399
|
skip_bronze_loading: Optional[bool] = None
|
|
400
|
+
load_as_single_variant: Optional[bool] = None
|
|
401
|
+
pre_transform: Optional[List[List[str]]] = None
|
|
394
402
|
|
|
395
403
|
@staticmethod
|
|
396
404
|
def from_api_obj(obj: Optional[CoreV1DataSourceSpecBronze]) -> "BronzeSpec":
|
|
@@ -400,6 +408,8 @@ class BronzeSpec(BaseModel):
|
|
|
400
408
|
clustering=BronzeSpec.Clustering.from_api_obj(obj.clustering),
|
|
401
409
|
bronze_table=obj.bronze_table,
|
|
402
410
|
skip_bronze_loading=obj.skip_bronze_loading,
|
|
411
|
+
load_as_single_variant=obj.load_as_single_variant,
|
|
412
|
+
pre_transform=obj.pre_transform,
|
|
403
413
|
)
|
|
404
414
|
|
|
405
415
|
def to_api_obj(self) -> CoreV1DataSourceSpecBronze:
|
|
@@ -407,6 +417,8 @@ class BronzeSpec(BaseModel):
|
|
|
407
417
|
clustering=Helpers.maybe(lambda o: o.to_api_obj(), self.clustering),
|
|
408
418
|
bronze_table=self.bronze_table,
|
|
409
419
|
skip_bronze_loading=self.skip_bronze_loading,
|
|
420
|
+
load_as_single_variant=self.load_as_single_variant,
|
|
421
|
+
pre_transform=self.pre_transform,
|
|
410
422
|
)
|
|
411
423
|
|
|
412
424
|
|
|
@@ -1193,12 +1205,16 @@ class DataSource(BaseModel):
|
|
|
1193
1205
|
The schedule for data ingestion.
|
|
1194
1206
|
custom (Optional[DataSource.CustomNotebook]):
|
|
1195
1207
|
A custom notebook for the datasource.
|
|
1208
|
+
primary_key (Optional[PrimaryKey]):
|
|
1209
|
+
Primary key configuration of the datasource.
|
|
1196
1210
|
use_preset (Optional[str]):
|
|
1197
1211
|
The name of the preset to use for this data source.
|
|
1198
1212
|
autoloader (Optional[DataSource.Autoloader]):
|
|
1199
1213
|
Autoloader configuration.
|
|
1200
1214
|
bronze (Optional[BronzeSpec]):
|
|
1201
1215
|
Bronze table configuration.
|
|
1216
|
+
compute_mode (Optional[str]):
|
|
1217
|
+
The compute mode to use for this datasource's job.
|
|
1202
1218
|
silver (Optional[SilverSpec]):
|
|
1203
1219
|
Silver transformation configuration.
|
|
1204
1220
|
gold (Optional[GoldSpec]):
|
|
@@ -1233,6 +1249,35 @@ class DataSource(BaseModel):
|
|
|
1233
1249
|
notebook=self.notebook,
|
|
1234
1250
|
)
|
|
1235
1251
|
|
|
1252
|
+
class PrimaryKey(BaseModel):
|
|
1253
|
+
"""
|
|
1254
|
+
PrimaryKey configuration for DataSource
|
|
1255
|
+
|
|
1256
|
+
Attributes:
|
|
1257
|
+
time_column (str): column name used as timestamp portion of the sortable synthetic key
|
|
1258
|
+
additionalColumns (List[str]): list of columns to compute hashkey over
|
|
1259
|
+
"""
|
|
1260
|
+
|
|
1261
|
+
time_column: str
|
|
1262
|
+
additional_columns: List[str]
|
|
1263
|
+
|
|
1264
|
+
@staticmethod
|
|
1265
|
+
def from_api_obj(
|
|
1266
|
+
obj: Optional[CoreV1DataSourcePrimaryKeySpec],
|
|
1267
|
+
) -> "DataSource.PrimaryKey":
|
|
1268
|
+
if obj is None:
|
|
1269
|
+
return None
|
|
1270
|
+
return DataSource.PrimaryKey(
|
|
1271
|
+
time_column=obj.time_column,
|
|
1272
|
+
additional_columns=obj.additional_columns,
|
|
1273
|
+
)
|
|
1274
|
+
|
|
1275
|
+
def to_api_obj(self) -> CoreV1DataSourcePrimaryKeySpec:
|
|
1276
|
+
return CoreV1DataSourcePrimaryKeySpec(
|
|
1277
|
+
timeColumn=self.time_column,
|
|
1278
|
+
additionalColumns=self.additional_columns,
|
|
1279
|
+
)
|
|
1280
|
+
|
|
1236
1281
|
class Autoloader(BaseModel):
|
|
1237
1282
|
"""
|
|
1238
1283
|
Autoloader configuration for the DataSource.
|
|
@@ -1310,9 +1355,11 @@ class DataSource(BaseModel):
|
|
|
1310
1355
|
source_type: Optional[str] = None
|
|
1311
1356
|
schedule: Optional[Schedule] = None
|
|
1312
1357
|
custom: Optional["DataSource.CustomNotebook"] = None
|
|
1358
|
+
primary_key: Optional["DataSource.PrimaryKey"] = None
|
|
1313
1359
|
use_preset: Optional[str] = None
|
|
1314
1360
|
use_preset_version: Optional[int] = None
|
|
1315
1361
|
autoloader: Optional["DataSource.Autoloader"] = None
|
|
1362
|
+
compute_mode: Optional[str] = None
|
|
1316
1363
|
bronze: Optional[BronzeSpec] = None
|
|
1317
1364
|
silver: Optional[SilverSpec] = None
|
|
1318
1365
|
gold: Optional[GoldSpec] = None
|
|
@@ -1326,9 +1373,11 @@ class DataSource(BaseModel):
|
|
|
1326
1373
|
source_type=obj.spec.source_type,
|
|
1327
1374
|
schedule=Schedule.from_api_obj(obj.spec.schedule),
|
|
1328
1375
|
custom=DataSource.CustomNotebook.from_api_obj(obj.spec.custom),
|
|
1376
|
+
primary_key=DataSource.PrimaryKey.from_api_obj(obj.spec.primary_key),
|
|
1329
1377
|
use_preset=obj.spec.use_preset,
|
|
1330
1378
|
use_preset_version=obj.spec.use_preset_version,
|
|
1331
1379
|
autoloader=DataSource.Autoloader.from_api_obj(obj.spec.autoloader),
|
|
1380
|
+
compute_mode=obj.spec.compute_mode,
|
|
1332
1381
|
bronze=BronzeSpec.from_api_obj(obj.spec.bronze),
|
|
1333
1382
|
silver=SilverSpec.from_api_obj(obj.spec.silver),
|
|
1334
1383
|
gold=GoldSpec.from_api_obj(obj.spec.gold),
|
|
@@ -1346,9 +1395,11 @@ class DataSource(BaseModel):
|
|
|
1346
1395
|
source_type=self.source_type,
|
|
1347
1396
|
schedule=Helpers.maybe(to_api_obj, self.schedule),
|
|
1348
1397
|
custom=Helpers.maybe(to_api_obj, self.custom),
|
|
1398
|
+
primary_key=Helpers.maybe(to_api_obj, self.primary_key),
|
|
1349
1399
|
use_preset=self.use_preset,
|
|
1350
1400
|
use_preset_version=self.use_preset_version,
|
|
1351
1401
|
autoloader=Helpers.maybe(to_api_obj, self.autoloader),
|
|
1402
|
+
compute_mode=self.compute_mode,
|
|
1352
1403
|
bronze=Helpers.maybe(to_api_obj, self.bronze),
|
|
1353
1404
|
silver=Helpers.maybe(to_api_obj, self.silver),
|
|
1354
1405
|
gold=Helpers.maybe(to_api_obj, self.gold),
|
dasl_client/types/rule.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pydantic import BaseModel
|
|
2
2
|
from typing import Dict, List, Optional, Union
|
|
3
|
+
from datetime import datetime, timezone
|
|
3
4
|
|
|
4
5
|
from dasl_api import (
|
|
5
6
|
CoreV1Rule,
|
|
@@ -319,9 +320,25 @@ class Rule(BaseModel):
|
|
|
319
320
|
|
|
320
321
|
Attributes:
|
|
321
322
|
tables (Optional[List[Rule.Input.Stream.Table]]):
|
|
323
|
+
List of input tables and join rules.
|
|
322
324
|
filter (Optional[str]):
|
|
325
|
+
A filter expression to be applied to the input stream.
|
|
326
|
+
Note that this cannot be used in conjunction with a
|
|
327
|
+
custom SQL expression (i.e. the sql field).
|
|
323
328
|
sql (Optional[str]):
|
|
329
|
+
A custom SQL expression to apply to the input stream
|
|
330
|
+
before matching. Note that this cannot be used in
|
|
331
|
+
conjunction with a filter expression (i.e. the
|
|
332
|
+
filter member).
|
|
324
333
|
custom (Optional[Rule.Input.CustomStream]):
|
|
334
|
+
starting_timestamp (Optional[datetime]):
|
|
335
|
+
Starting timestamp for streaming input data. If this
|
|
336
|
+
value is not specified, then the timestamp when this rule
|
|
337
|
+
was created will be used. This setting is used to determine
|
|
338
|
+
the starting point for streaming historical data, and only
|
|
339
|
+
applies on the first run of the rule. Once some data has
|
|
340
|
+
been streamed and a checkpoint has been created, this
|
|
341
|
+
setting no longer has any impact.
|
|
325
342
|
"""
|
|
326
343
|
|
|
327
344
|
class Table(BaseModel):
|
|
@@ -427,6 +444,7 @@ class Rule(BaseModel):
|
|
|
427
444
|
filter: Optional[str] = None
|
|
428
445
|
sql: Optional[str] = None
|
|
429
446
|
custom: Optional["Rule.Input.CustomStream"] = None
|
|
447
|
+
starting_timestamp: Optional[datetime] = None
|
|
430
448
|
|
|
431
449
|
@staticmethod
|
|
432
450
|
def from_api_obj(
|
|
@@ -440,22 +458,37 @@ class Rule(BaseModel):
|
|
|
440
458
|
Rule.Input.Stream.Table.from_api_obj(item)
|
|
441
459
|
for item in obj.tables
|
|
442
460
|
]
|
|
461
|
+
|
|
462
|
+
starting_timestamp = obj.starting_timestamp
|
|
463
|
+
if starting_timestamp is not None and starting_timestamp.tzinfo is None:
|
|
464
|
+
starting_timestamp = starting_timestamp.replace(tzinfo=timezone.utc)
|
|
465
|
+
|
|
443
466
|
return Rule.Input.Stream(
|
|
444
467
|
tables=tables,
|
|
445
468
|
filter=obj.filter,
|
|
446
469
|
sql=obj.sql,
|
|
447
470
|
custom=Rule.Input.CustomStream.from_api_obj(obj.custom),
|
|
471
|
+
starting_timestamp=starting_timestamp,
|
|
448
472
|
)
|
|
449
473
|
|
|
450
474
|
def to_api_obj(self) -> CoreV1RuleSpecInputStream:
|
|
451
475
|
tables = None
|
|
452
476
|
if self.tables is not None:
|
|
453
477
|
tables = [item.to_api_obj() for item in self.tables]
|
|
478
|
+
|
|
479
|
+
# tzinfo must be attached to the starting timestamp or else
|
|
480
|
+
# the serialization (without trailing time zone) will be
|
|
481
|
+
# rejected by the server.
|
|
482
|
+
starting_timestamp = self.starting_timestamp
|
|
483
|
+
if starting_timestamp is not None and starting_timestamp.tzinfo is None:
|
|
484
|
+
starting_timestamp = starting_timestamp.replace(tzinfo=timezone.utc)
|
|
485
|
+
|
|
454
486
|
return CoreV1RuleSpecInputStream(
|
|
455
487
|
tables=tables,
|
|
456
488
|
filter=self.filter,
|
|
457
489
|
sql=self.sql,
|
|
458
490
|
custom=Helpers.maybe(lambda o: o.to_api_obj(), self.custom),
|
|
491
|
+
starting_timestamp=starting_timestamp,
|
|
459
492
|
)
|
|
460
493
|
|
|
461
494
|
class Batch(BaseModel):
|
|
@@ -22,7 +22,7 @@ from dasl_api import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
from .helpers import Helpers
|
|
25
|
-
from .types import Metadata, ResourceStatus
|
|
25
|
+
from .types import Metadata, ResourceStatus, Schedule
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class ExportConfig(BaseModel):
|
|
@@ -255,6 +255,8 @@ class DatasourcesConfig(BaseModel):
|
|
|
255
255
|
The catalog name to use as the resource's default.
|
|
256
256
|
checkpoint_location (Optional[str]):
|
|
257
257
|
The base checkpoint location to use in Rule notebooks.
|
|
258
|
+
default_compute_mode (Optional[str]):
|
|
259
|
+
The default compute mode to use for datasource jobs.
|
|
258
260
|
"""
|
|
259
261
|
|
|
260
262
|
catalog_name: Optional[str] = None
|
|
@@ -262,6 +264,7 @@ class DatasourcesConfig(BaseModel):
|
|
|
262
264
|
silver_schema: Optional[str] = None
|
|
263
265
|
gold_schema: Optional[str] = None
|
|
264
266
|
checkpoint_location: Optional[str] = None
|
|
267
|
+
default_compute_mode: Optional[str] = None
|
|
265
268
|
|
|
266
269
|
@staticmethod
|
|
267
270
|
def from_api_obj(
|
|
@@ -276,6 +279,7 @@ class DatasourcesConfig(BaseModel):
|
|
|
276
279
|
silver_schema=obj.silver_schema,
|
|
277
280
|
gold_schema=obj.gold_schema,
|
|
278
281
|
checkpoint_location=obj.checkpoint_location,
|
|
282
|
+
default_compute_mode=obj.default_compute_mode,
|
|
279
283
|
)
|
|
280
284
|
|
|
281
285
|
def to_api_obj(self) -> WorkspaceV1WorkspaceConfigSpecDatasources:
|
|
@@ -285,6 +289,7 @@ class DatasourcesConfig(BaseModel):
|
|
|
285
289
|
silver_schema=self.silver_schema,
|
|
286
290
|
gold_schema=self.gold_schema,
|
|
287
291
|
checkpoint_location=self.checkpoint_location,
|
|
292
|
+
default_compute_mode=self.default_compute_mode,
|
|
288
293
|
)
|
|
289
294
|
|
|
290
295
|
|
|
@@ -662,6 +667,10 @@ class WorkspaceConfig(BaseModel):
|
|
|
662
667
|
state.
|
|
663
668
|
dasl_custom_presets_path (Optional[str]):
|
|
664
669
|
An optional path to a directory containing user defined presets.
|
|
670
|
+
default_rule_schedule (Optional[str]):
|
|
671
|
+
A default schedule for detections. If a detection is created without a schedule,
|
|
672
|
+
it will inherit the schedule provided here. Note that, should this schedule be updated,
|
|
673
|
+
it will affect all detections inheriting it.
|
|
665
674
|
default_config (Optional[DefaultConfig]):
|
|
666
675
|
(DEPRECATED) Configuration settings regarding storage of bronze,
|
|
667
676
|
silver, and gold tables and related assets for each resource type.
|
|
@@ -688,6 +697,7 @@ class WorkspaceConfig(BaseModel):
|
|
|
688
697
|
observables: Optional[WorkspaceConfigObservables] = None
|
|
689
698
|
dasl_storage_path: Optional[str] = None
|
|
690
699
|
dasl_custom_presets_path: Optional[str] = None
|
|
700
|
+
default_rule_schedule: Optional[Schedule] = None
|
|
691
701
|
default_config: Optional[DefaultConfig] = None
|
|
692
702
|
default_custom_notebook_location: Optional[str] = None
|
|
693
703
|
datasources: Optional[DatasourcesConfig] = None
|
|
@@ -721,6 +731,7 @@ class WorkspaceConfig(BaseModel):
|
|
|
721
731
|
observables=WorkspaceConfigObservables.from_api_obj(spec.observables),
|
|
722
732
|
dasl_storage_path=spec.dasl_storage_path,
|
|
723
733
|
dasl_custom_presets_path=spec.dasl_custom_presets_path,
|
|
734
|
+
default_rule_schedule=spec.default_rule_schedule,
|
|
724
735
|
default_config=DefaultConfig.from_api_obj(spec.default_config),
|
|
725
736
|
default_custom_notebook_location=spec.default_custom_notebook_location,
|
|
726
737
|
datasources=DatasourcesConfig.from_api_obj(spec.datasources),
|
|
@@ -754,6 +765,7 @@ class WorkspaceConfig(BaseModel):
|
|
|
754
765
|
observables=Helpers.maybe(to_api_obj, self.observables),
|
|
755
766
|
dasl_storage_path=self.dasl_storage_path,
|
|
756
767
|
dasl_custom_presets_path=self.dasl_custom_presets_path,
|
|
768
|
+
default_rule_schedule=self.default_rule_schedule,
|
|
757
769
|
default_config=Helpers.maybe(to_api_obj, self.default_config),
|
|
758
770
|
default_custom_notebook_location=self.default_custom_notebook_location,
|
|
759
771
|
datasources=Helpers.maybe(to_api_obj, self.datasources),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dasl_client
|
|
3
|
+
Version: 1.0.24
|
|
4
|
+
Summary: The DASL client library used for interacting with the DASL workspace
|
|
5
|
+
Home-page: https://github.com/antimatter/asl
|
|
6
|
+
Author: Antimatter Team
|
|
7
|
+
Author-email: Antimatter Team <support@antimatter.io>
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: dasl_api==0.1.24
|
|
12
|
+
Requires-Dist: databricks-sdk>=0.41.0
|
|
13
|
+
Requires-Dist: pydantic>=2
|
|
14
|
+
Requires-Dist: typing_extensions>=4.10.0
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
Dynamic: requires-python
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
dasl_client/__init__.py,sha256=MuaH74tnEHwfymHtlK6GqeMHRRWaSDfS6hSzbMrd7iQ,150
|
|
2
|
+
dasl_client/client.py,sha256=08QU-XfEUmpH-LmkI4CdZkZi8JIhRvReCwRAAiAfm8E,36541
|
|
3
|
+
dasl_client/exec_rule.py,sha256=kn-Yo-9L0fjxbulyAghiIKO1SYcqv2XHZn45F8FvUzE,3599
|
|
4
|
+
dasl_client/helpers.py,sha256=aXqI3uM4k9T3qTXSXVHZKtwxSlzTZ-vtvUfWnqizmiE,1091
|
|
5
|
+
dasl_client/regions.json,sha256=PyMfnvPlpTSAP-a97FFP-yc-BrJdQWg_wVd8Sre12Z4,54
|
|
6
|
+
dasl_client/regions.py,sha256=dHYW_IuxFQXkwdbXCeQWMRaYYxyF_j6eEglmfIXUIZo,442
|
|
7
|
+
dasl_client/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dasl_client/auth/auth.py,sha256=yTeijYYpfJVJ_wYyq0U6kAntg4xz5MzIR37_CpVR57k,7277
|
|
9
|
+
dasl_client/conn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
dasl_client/conn/client_identifier.py,sha256=kPrX0wPa6y7ifhKSb6dQriDSlIgPOUhBq7OoC73E7NU,657
|
|
11
|
+
dasl_client/conn/conn.py,sha256=7o-2qeoVhzULQGW5l6OLYE5dZ60_8OU080ebV_-AW9Q,1433
|
|
12
|
+
dasl_client/errors/__init__.py,sha256=lpH2HGF5kCRTk6MxpPEyY9ulTvsLBFKb4NnLuFFLZZA,40
|
|
13
|
+
dasl_client/errors/errors.py,sha256=u-B8dR8zlxdNVeEdHi6UozX178jwJJ5ZJOGl9YjONRc,4008
|
|
14
|
+
dasl_client/preset_development/__init__.py,sha256=9yC4gmQfombvYLThzo0pSfT5JMolfNVWFVQIuIg_XUA,131
|
|
15
|
+
dasl_client/preset_development/errors.py,sha256=9tLYugMWH2oGxpibKPEVOnRWUuvo-ZS_wVEQuSY6YX4,7013
|
|
16
|
+
dasl_client/preset_development/preview_engine.py,sha256=Y3NHYWpK0e8AWdwgTeDA2S5Su5CTPZSwFyNnkXXjAes,18031
|
|
17
|
+
dasl_client/preset_development/preview_parameters.py,sha256=aZrpCkB2JrqZCp7Lqb2Tz5DMJOFc0qDMdmDxAI5tv98,20183
|
|
18
|
+
dasl_client/preset_development/stage.py,sha256=z2DJmvdnxa-i-TS1yIDdS0rB-QrxQaxspTBm8LfmlZE,26577
|
|
19
|
+
dasl_client/types/__init__.py,sha256=GsXC3eWuv21VTLPLPH9pzM95JByaKnKrPjJkh2rlZfQ,170
|
|
20
|
+
dasl_client/types/admin_config.py,sha256=Kmx3Kuai9_LWMeO2NpWasRUgLihYSEXGtuYVfG0FkjU,2200
|
|
21
|
+
dasl_client/types/content.py,sha256=HegSq2i7w78CH4kq4XwDyeaB8LuZWWwFZkg0tYTY8d0,7349
|
|
22
|
+
dasl_client/types/datasource.py,sha256=GiTxkwsofPBZIVtiInmtVnnPnmxstlUSQtXP61FcgpE,55094
|
|
23
|
+
dasl_client/types/dbui.py,sha256=k2WXNjfrEjXa-5iBlZ17pvFAs_jgbd-ir5NJl5sXvpA,16097
|
|
24
|
+
dasl_client/types/helpers.py,sha256=gLGTvrssAKrdkQT9h80twEosld2egwhvj-zAudxWFPs,109
|
|
25
|
+
dasl_client/types/rule.py,sha256=L19QLg07UWRayw9HYuVsSVCUw_LaGnty_9WqPZ6nHdE,27755
|
|
26
|
+
dasl_client/types/types.py,sha256=DeUOfdYGOhUGEy7yKOfo0OYTXYRrs57yYgNLUbu7Tlc,8806
|
|
27
|
+
dasl_client/types/workspace_config.py,sha256=w0f0paZ42yURiIFREJ6fG7zKaoIT7BU-Zw3nZMuFNMw,29426
|
|
28
|
+
dasl_client-1.0.24.dist-info/licenses/LICENSE,sha256=M35UepUPyKmFkvENlkweeaMElheQqNoM5Emh8ADO-rs,4
|
|
29
|
+
dasl_client-1.0.24.dist-info/METADATA,sha256=J2cWOcCXDSjKOo35RUKkLM2qfuwiuKb3_oPO8hj5mY0,562
|
|
30
|
+
dasl_client-1.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
dasl_client-1.0.24.dist-info/top_level.txt,sha256=kIv8ox_2oJPjGB8_yuey5vvuPCyfY8kywG138f9oSOY,12
|
|
32
|
+
dasl_client-1.0.24.dist-info/RECORD,,
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: dasl_client
|
|
3
|
-
Version: 1.0.23
|
|
4
|
-
Summary: The DASL client library used for interacting with the DASL workspace
|
|
5
|
-
Home-page: https://github.com/antimatter/asl
|
|
6
|
-
Author: Antimatter Team
|
|
7
|
-
Author-email: Antimatter Team <support@antimatter.io>
|
|
8
|
-
Requires-Python: >=3.8
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
License-File: LICENSE
|
|
11
|
-
Requires-Dist: dasl-api ==0.1.20
|
|
12
|
-
Requires-Dist: databricks-sdk >=0.41.0
|
|
13
|
-
Requires-Dist: pydantic >=2
|
|
14
|
-
Requires-Dist: typing-extensions >=4.10.0
|
|
15
|
-
|
|
16
|
-
# DASL Client Library
|
|
17
|
-
|
|
18
|
-
This client library is used for interacting with the DASL services in python.
|
|
19
|
-
|
|
20
|
-
## Requirements
|
|
21
|
-
Python:
|
|
22
|
-
- wheel
|
|
23
|
-
- setuptools
|
|
24
|
-
- asl_api
|
|
25
|
-
|
|
26
|
-
Other:
|
|
27
|
-
- Earthly
|
|
28
|
-
|
|
29
|
-
## Build
|
|
30
|
-
|
|
31
|
-
To build manually here:
|
|
32
|
-
```bash
|
|
33
|
-
python setup.py sdist bdist_wheel
|
|
34
|
-
```
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
dasl_client/__init__.py,sha256=E6gOgO8qg96Y38JKA-4LyNBvc2ytQPEfhdniYsCWBxA,127
|
|
2
|
-
dasl_client/client.py,sha256=yNXomAWzvMWdbKuomVsTh_VSx9WwZiXVJ3Mq8bbpqew,27821
|
|
3
|
-
dasl_client/helpers.py,sha256=L7ycxrqyG28glRRGZgsrVBdCJzXYCW7DB0hAvupGMuA,1118
|
|
4
|
-
dasl_client/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
dasl_client/auth/auth.py,sha256=yTeijYYpfJVJ_wYyq0U6kAntg4xz5MzIR37_CpVR57k,7277
|
|
6
|
-
dasl_client/conn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
dasl_client/conn/client_identifier.py,sha256=kPrX0wPa6y7ifhKSb6dQriDSlIgPOUhBq7OoC73E7NU,657
|
|
8
|
-
dasl_client/conn/conn.py,sha256=7o-2qeoVhzULQGW5l6OLYE5dZ60_8OU080ebV_-AW9Q,1433
|
|
9
|
-
dasl_client/errors/__init__.py,sha256=lpH2HGF5kCRTk6MxpPEyY9ulTvsLBFKb4NnLuFFLZZA,40
|
|
10
|
-
dasl_client/errors/errors.py,sha256=u-B8dR8zlxdNVeEdHi6UozX178jwJJ5ZJOGl9YjONRc,4008
|
|
11
|
-
dasl_client/preset_development/__init__.py,sha256=9yC4gmQfombvYLThzo0pSfT5JMolfNVWFVQIuIg_XUA,131
|
|
12
|
-
dasl_client/preset_development/errors.py,sha256=jsqBFMZtl7uHi6O9bBHnOt0UQ4WM9KN9x0uYtf5c268,5482
|
|
13
|
-
dasl_client/preset_development/preview_engine.py,sha256=mNDLuuTVXKenRa-jhr5-xQtonsLGIC6ZyD3asSFFf8A,14745
|
|
14
|
-
dasl_client/preset_development/preview_parameters.py,sha256=h0DwYFKdA3qAvBJD5Kww21uOPpsfYS9DVF1ssJ1m6Gs,15743
|
|
15
|
-
dasl_client/preset_development/stage.py,sha256=2FPOZvb_bCVpjrY5TsYB05BD4KYbrhgfAe9uZCQFkOk,23397
|
|
16
|
-
dasl_client/types/__init__.py,sha256=GsXC3eWuv21VTLPLPH9pzM95JByaKnKrPjJkh2rlZfQ,170
|
|
17
|
-
dasl_client/types/admin_config.py,sha256=Kmx3Kuai9_LWMeO2NpWasRUgLihYSEXGtuYVfG0FkjU,2200
|
|
18
|
-
dasl_client/types/content.py,sha256=HegSq2i7w78CH4kq4XwDyeaB8LuZWWwFZkg0tYTY8d0,7349
|
|
19
|
-
dasl_client/types/datasource.py,sha256=1r55rYHeN8bmTU190dWcpEWPJ64pJoAwOJy_pVcMP8o,52848
|
|
20
|
-
dasl_client/types/dbui.py,sha256=k2WXNjfrEjXa-5iBlZ17pvFAs_jgbd-ir5NJl5sXvpA,16097
|
|
21
|
-
dasl_client/types/helpers.py,sha256=gLGTvrssAKrdkQT9h80twEosld2egwhvj-zAudxWFPs,109
|
|
22
|
-
dasl_client/types/rule.py,sha256=BqhWhT8Eh95UXNytd0PxVcjqYuWQcdN1tfKjUB4Tk74,25781
|
|
23
|
-
dasl_client/types/types.py,sha256=DeUOfdYGOhUGEy7yKOfo0OYTXYRrs57yYgNLUbu7Tlc,8806
|
|
24
|
-
dasl_client/types/workspace_config.py,sha256=sknQcLjZ7efwn2iBOVwxBj4oqO6xVwaBZVEbmU-UJbc,28661
|
|
25
|
-
test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
test/conftest.py,sha256=ZfNULJxVQ609GHxw9UsreTcbQMl3gbcTP_DKT1oySwQ,440
|
|
27
|
-
test/constants.py,sha256=ed3xiemWDJVBlHDwn-iQToCbcaXD3AN-5r8HkURCqBs,438
|
|
28
|
-
test/test_api_changes.py,sha256=RzLauhCkwLmf_gK5yZZ7R7TI9803XCGr-YCyv_jSc94,3827
|
|
29
|
-
test/test_api_surface.py,sha256=SnJQtaWIfeuTcySFeGfj6cVDksmLyIa8BmblB0t-ZJg,11014
|
|
30
|
-
test/test_databricks_secret_auth.py,sha256=w0ZX23j9gDbJtZuNdZus3joUmdD5U2rX8Qrs0vbqMd4,3736
|
|
31
|
-
test/test_marshaling.py,sha256=ltMuJCqBMpqMpchQ1ZjdO3vrGk_ef1NR3yd07SHV7gU,38212
|
|
32
|
-
dasl_client-1.0.23.dist-info/LICENSE,sha256=M35UepUPyKmFkvENlkweeaMElheQqNoM5Emh8ADO-rs,4
|
|
33
|
-
dasl_client-1.0.23.dist-info/METADATA,sha256=LMDZqT-XHfQgsccfU8YwbAeatbsPl9TFekjj0Nyk7us,741
|
|
34
|
-
dasl_client-1.0.23.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
35
|
-
dasl_client-1.0.23.dist-info/top_level.txt,sha256=943P5S_qILHKZYxAvxPUeqOzM2yV18d5SBVKxzPw2OE,17
|
|
36
|
-
dasl_client-1.0.23.dist-info/RECORD,,
|
test/__init__.py
DELETED
|
File without changes
|
test/conftest.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from dasl_client import Client
|
|
4
|
-
|
|
5
|
-
from .constants import *
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@pytest.fixture(scope="session")
|
|
9
|
-
def api_client():
|
|
10
|
-
client = Client.new_workspace(
|
|
11
|
-
admin_email="test@antimatter.io",
|
|
12
|
-
app_client_id=app_client_id,
|
|
13
|
-
service_principal_id=databricks_client_id,
|
|
14
|
-
service_principal_secret=databricks_client_secret,
|
|
15
|
-
workspace_url=databricks_host,
|
|
16
|
-
dasl_host=dasl_host,
|
|
17
|
-
)
|
|
18
|
-
yield client
|
test/constants.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from urllib.parse import urlparse
|
|
3
|
-
|
|
4
|
-
dasl_host = os.environ["DASL_API_URL"]
|
|
5
|
-
databricks_host = os.environ["DASL_DATABRICKS_HOST"]
|
|
6
|
-
databricks_client_id = os.environ["DASL_DATABRICKS_CLIENT_ID"]
|
|
7
|
-
databricks_client_secret = os.environ["DASL_DATABRICKS_CLIENT_SECRET"]
|
|
8
|
-
workspace = urlparse(databricks_host).hostname
|
|
9
|
-
app_client_id = "22853b93-68ba-4ae2-8e41-976417f501dd"
|
|
10
|
-
alternate_app_client_id = "335ac0d3-e0ea-4732-ba93-0277423b5029"
|