Skip to content

Commit d27b070

Browse files
committed
added relative time validation, refactored the import process
1 parent 7224b79 commit d27b070

3 files changed

Lines changed: 34 additions & 15 deletions

File tree

python/lib/sift_client/resources/data_imports.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import TYPE_CHECKING
88

99
from sift_client._internal.low_level_wrappers.data_imports import DataImportsLowLevelClient
10+
from sift_client._internal.util.executor import run_sync_function
1011
from sift_client.resources._base import ResourceBase
1112
from sift_client.sift_types.data_import import (
1213
EXTENSION_TO_DATA_TYPE_KEY,
@@ -25,6 +26,16 @@
2526
_DETECT_CONFIG_SAMPLE_SIZE = 65_536 # 64 KiB
2627

2728

29+
def _validate_config(config: ImportConfig) -> None:
30+
"""Validate an import config before sending it to the server."""
31+
if isinstance(config, CsvImportConfig):
32+
tc = config.time_column
33+
if tc.format.name.startswith("RELATIVE_") and tc.relative_start_time is None:
34+
raise ValueError(
35+
f"'relative_start_time' is required when using a relative time format ({tc.format.name})."
36+
)
37+
38+
2839
class DataImportAPIAsync(ResourceBase):
2940
"""High-level API for importing data into Sift.
3041
@@ -55,9 +66,8 @@ async def import_from_path(
5566
) -> DataImport:
5667
"""Import data from a local file.
5768
58-
Creates a data import on the server and uploads the file to the
59-
returned presigned URL. Returns a :class:`DataImport` that can be
60-
polled for status via ``data_import.refresh()``.
69+
Creates a data import on the server, uploads the file, and waits
70+
for the import to complete. Returns the completed :class:`DataImport`.
6171
6272
When ``config`` is omitted the file format is auto-detected via
6373
:meth:`detect_config` and a :class:`CsvImportConfig` is built using
@@ -88,9 +98,7 @@ async def import_from_path(
8898

8999
if config is None:
90100
if asset_name is None:
91-
raise ValueError(
92-
"Either 'config' or 'asset_name' must be provided."
93-
)
101+
raise ValueError("Either 'config' or 'asset_name' must be provided.")
94102
detected = await self.detect_config(file_path)
95103
config = detected.model_copy(
96104
update={
@@ -100,14 +108,14 @@ async def import_from_path(
100108
}
101109
)
102110

111+
_validate_config(config)
103112
data_import_id, upload_url = await self._low_level_client.create_from_upload(config)
104113
logger.info("Created data import %s", data_import_id)
105114

106115
await self._low_level_client.upload_file(upload_url, path)
107116
logger.info("Uploaded file to presigned URL for import %s", data_import_id)
108117

109-
data_import = await self._low_level_client.get(data_import_id)
110-
return self._apply_client_to_instance(data_import)
118+
return await self.wait_until_complete(data_import_id)
111119

112120
async def import_from_url(
113121
self,
@@ -128,11 +136,11 @@ async def import_from_url(
128136
Returns:
129137
A :class:`DataImport` representing the import operation.
130138
"""
139+
_validate_config(config)
131140
data_import_id = await self._low_level_client.create_from_url(url, config)
132141
logger.info("Created URL-based data import %s", data_import_id)
133142

134-
data_import = await self._low_level_client.get(data_import_id)
135-
return self._apply_client_to_instance(data_import)
143+
return await self.wait_until_complete(data_import_id)
136144

137145
async def get(self, data_import_id: str) -> DataImport:
138146
"""Get a data import by ID.
@@ -229,8 +237,11 @@ async def detect_config(self, file_path: str | Path) -> ImportConfig:
229237
f"Supported: {', '.join(sorted(EXTENSION_TO_DATA_TYPE_KEY))}"
230238
)
231239

232-
with open(path, "rb") as f:
233-
sample = f.read(_DETECT_CONFIG_SAMPLE_SIZE)
240+
def _read_sample() -> bytes:
241+
with open(path, "rb") as f:
242+
return f.read(_DETECT_CONFIG_SAMPLE_SIZE)
243+
244+
sample = await run_sync_function(_read_sample)
234245

235246
response = await self._low_level_client.detect_config(sample, data_type_key.value)
236247

python/lib/sift_client/resources/sync_stubs/__init__.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
2424
CalculatedChannelUpdate,
2525
)
2626
from sift_client.sift_types.channel import Channel
27-
from sift_client.sift_types.data_import import CsvImportConfig, DataImport, DataImportStatus
27+
from sift_client.sift_types.data_import import DataImport, DataImportStatus
2828
from sift_client.sift_types.export import ExportOutputFormat
2929
from sift_client.sift_types.file_attachment import (
3030
FileAttachment,

python/lib/sift_client/sift_types/data_import.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,17 @@ def _to_proto(self) -> CsvConfigProto:
171171
@classmethod
172172
def _from_proto(cls, proto: CsvConfigProto) -> CsvImportConfig:
173173
"""Create from a proto CsvConfig (e.g. from DetectConfig response)."""
174+
relative_start_time = None
175+
if proto.time_column.HasField("relative_start_time"):
176+
from datetime import timezone
177+
178+
relative_start_time = proto.time_column.relative_start_time.ToDatetime(
179+
tzinfo=timezone.utc
180+
)
174181
time_column = CsvTimeColumn(
175182
column=proto.time_column.column_number,
176183
format=TimeFormat(proto.time_column.format),
184+
relative_start_time=relative_start_time,
177185
)
178186
data_columns = [
179187
CsvDataColumn(
@@ -204,8 +212,8 @@ class DataImport(BaseType[DataImportProto, "DataImport"]):
204212
"""A data import in the Sift system.
205213
206214
Represents the status and metadata of an import operation. Use
207-
``client.data_import.upload()`` to create one, or ``client.data_import.get()``
208-
to retrieve an existing import by ID.
215+
``client.data_import.import_from_path()`` to create one, or
216+
``client.data_import.get()`` to retrieve an existing import by ID.
209217
"""
210218

211219
# Required fields

0 commit comments

Comments
 (0)