Skip to content

Commit 07007f9

Browse files
committed
refactored to apply inheritance on time and config classes
1 parent d3377d1 commit 07007f9

2 files changed

Lines changed: 43 additions & 64 deletions

File tree

python/lib/sift_client/_tests/resources/test_data_imports.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ def test_to_proto_defaults(self):
271271
assert proto.run_name == ""
272272
assert proto.scale_values is False
273273

274-
def test_no_run_id_field(self):
274+
def test_run_id_inherited_but_unused(self):
275275
config = Ch10ImportConfig(asset_name="my_asset")
276-
assert not hasattr(config, "run_id")
276+
assert config.run_id is None
277277

278278

279279
class TestTdmsConfig:

python/lib/sift_client/sift_types/data_import.py

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from abc import ABC
34
from datetime import datetime # noqa: TC003
45
from enum import Enum
56
from typing import Union
@@ -82,7 +83,27 @@ class DataTypeKey(Enum):
8283
}
8384

8485

85-
class CsvTimeColumn(BaseModel):
86+
class TimeColumnBase(BaseModel, ABC):
87+
"""Base class for time column configurations.
88+
89+
Attributes:
90+
format: The time format used in this column.
91+
relative_start_time: Required when using a relative time format.
92+
"""
93+
94+
format: TimeFormat
95+
relative_start_time: datetime | None = None
96+
97+
@model_validator(mode="after")
98+
def _check_relative_start_time(self) -> TimeColumnBase:
99+
if self.format.name.startswith("RELATIVE_") and self.relative_start_time is None:
100+
raise ValueError(
101+
f"'relative_start_time' is required when using a relative time format ({self.format.name})."
102+
)
103+
return self
104+
105+
106+
class CsvTimeColumn(TimeColumnBase):
86107
"""Time column configuration for CSV imports.
87108
88109
Attributes:
@@ -92,8 +113,6 @@ class CsvTimeColumn(BaseModel):
92113
"""
93114

94115
column: int
95-
format: TimeFormat
96-
relative_start_time: datetime | None = None
97116

98117
def _to_proto(self) -> CsvTimeColumnProto:
99118
proto = CsvTimeColumnProto(
@@ -104,14 +123,6 @@ def _to_proto(self) -> CsvTimeColumnProto:
104123
proto.relative_start_time.CopyFrom(to_pb_timestamp(self.relative_start_time))
105124
return proto
106125

107-
@model_validator(mode="after")
108-
def _check_relative_start_time(self) -> CsvTimeColumn:
109-
if self.format.name.startswith("RELATIVE_") and self.relative_start_time is None:
110-
raise ValueError(
111-
f"'relative_start_time' is required when using a relative time format ({self.format.name})."
112-
)
113-
return self
114-
115126

116127
class CsvDataColumn(BaseModel):
117128
"""A data column definition for CSV imports.
@@ -131,21 +142,29 @@ class CsvDataColumn(BaseModel):
131142
description: str = ""
132143

133144

134-
class CsvImportConfig(BaseModel):
135-
"""Configuration for importing a CSV file.
145+
class ImportConfigBase(BaseModel, ABC):
146+
"""Base class for all import configurations.
136147
137148
Attributes:
138149
asset_name: Name of the asset to import data into.
139150
run_name: Name for the run. Ignored if ``run_id`` is set.
140151
run_id: ID of an existing run to append data to.
141-
first_data_row: The first row containing data (1-indexed). Defaults to 2 to skip a header row.
142-
time_column: Time column configuration.
143-
data_columns: List of data column definitions.
144152
"""
145153

146154
asset_name: str
147155
run_name: str | None = None
148156
run_id: str | None = None
157+
158+
159+
class CsvImportConfig(ImportConfigBase):
160+
"""Configuration for importing a CSV file.
161+
162+
Attributes:
163+
first_data_row: The first row containing data (1-indexed). Defaults to 2 to skip a header row.
164+
time_column: Time column configuration.
165+
data_columns: List of data column definitions.
166+
"""
167+
149168
first_data_row: int = 2
150169
time_column: CsvTimeColumn
151170
data_columns: list[CsvDataColumn]
@@ -229,7 +248,7 @@ class ParquetComplexTypesImportMode(Enum):
229248
BYTES = PARQUET_COMPLEX_TYPES_IMPORT_MODE_BYTES
230249

231250

232-
class ParquetTimeColumn(BaseModel):
251+
class ParquetTimeColumn(TimeColumnBase):
233252
"""Time column configuration for Parquet imports.
234253
235254
Attributes:
@@ -240,7 +259,6 @@ class ParquetTimeColumn(BaseModel):
240259

241260
path: str
242261
format: TimeFormat = TimeFormat.ABSOLUTE_UNIX_NANOSECONDS
243-
relative_start_time: datetime | None = None
244262

245263
def _to_proto(self) -> ParquetTimeColumnProto:
246264
if not self.path:
@@ -268,14 +286,6 @@ def _from_proto(cls, proto: ParquetTimeColumnProto) -> ParquetTimeColumn:
268286
relative_start_time=relative_start_time,
269287
)
270288

271-
@model_validator(mode="after")
272-
def _check_relative_start_time(self) -> ParquetTimeColumn:
273-
if self.format.name.startswith("RELATIVE_") and self.relative_start_time is None:
274-
raise ValueError(
275-
f"'relative_start_time' is required when using a relative time format ({self.format.name})."
276-
)
277-
return self
278-
279289

280290
class ParquetDataColumn(BaseModel):
281291
"""A data column definition for Parquet flat dataset imports.
@@ -295,15 +305,12 @@ class ParquetDataColumn(BaseModel):
295305
description: str = ""
296306

297307

298-
class ParquetFlatDatasetImportConfig(BaseModel):
308+
class ParquetFlatDatasetImportConfig(ImportConfigBase):
299309
"""Configuration for importing a Parquet file with a flat dataset layout.
300310
301311
Each column in the file maps to a separate channel.
302312
303313
Attributes:
304-
asset_name: Name of the asset to import data into.
305-
run_name: Name for the run. Ignored if ``run_id`` is set.
306-
run_id: ID of an existing run to append data to.
307314
time_column: Time column configuration.
308315
data_columns: List of data column definitions.
309316
footer_offset: Byte offset where the Parquet footer begins. Populated
@@ -313,9 +320,6 @@ class ParquetFlatDatasetImportConfig(BaseModel):
313320
complex_types_import_mode: How to handle complex Parquet types.
314321
"""
315322

316-
asset_name: str
317-
run_name: str | None = None
318-
run_id: str | None = None
319323
time_column: ParquetTimeColumn
320324
data_columns: list[ParquetDataColumn]
321325
footer_offset: int = 0
@@ -430,16 +434,13 @@ class ParquetMultiChannelConfig(BaseModel):
430434
data_path: str
431435

432436

433-
class ParquetSingleChannelPerRowImportConfig(BaseModel):
437+
class ParquetSingleChannelPerRowImportConfig(ImportConfigBase):
434438
"""Configuration for importing a Parquet file where each row represents
435439
a single channel's data point.
436440
437441
Exactly one of ``single_channel`` or ``multi_channel`` must be set.
438442
439443
Attributes:
440-
asset_name: Name of the asset to import data into.
441-
run_name: Name for the run. Ignored if ``run_id`` is set.
442-
run_id: ID of an existing run to append data to.
443444
time_column: Time column configuration.
444445
single_channel: Set when the entire file contains data for one channel.
445446
multi_channel: Set when each row identifies its channel via a name column.
@@ -450,9 +451,6 @@ class ParquetSingleChannelPerRowImportConfig(BaseModel):
450451
complex_types_import_mode: How to handle complex Parquet types.
451452
"""
452453

453-
asset_name: str
454-
run_name: str | None = None
455-
run_id: str | None = None
456454
time_column: ParquetTimeColumn
457455
single_channel: ParquetSingleChannelConfig | None = None
458456
multi_channel: ParquetMultiChannelConfig | None = None
@@ -540,17 +538,13 @@ def _from_proto(
540538
)
541539

542540

543-
class Ch10ImportConfig(BaseModel):
541+
class Ch10ImportConfig(ImportConfigBase):
544542
"""Configuration for importing a CH10 file.
545543
546544
Attributes:
547-
asset_name: Name of the asset to import data into.
548-
run_name: Name for the run.
549545
scale_values: Whether to apply EU (engineering unit) scaling to channel values.
550546
"""
551547

552-
asset_name: str
553-
run_name: str | None = None
554548
scale_values: bool = False
555549

556550
def _to_proto(self) -> Ch10ConfigProto:
@@ -561,21 +555,15 @@ def _to_proto(self) -> Ch10ConfigProto:
561555
)
562556

563557

564-
class TdmsImportConfig(BaseModel):
558+
class TdmsImportConfig(ImportConfigBase):
565559
"""Configuration for importing a TDMS file.
566560
567561
Attributes:
568-
asset_name: Name of the asset to import data into.
569-
run_name: Name for the run. Ignored if ``run_id`` is set.
570-
run_id: ID of an existing run to append data to.
571562
start_time_override: Override the ``wf_start_time`` metadata field for all channels.
572563
Useful when waveform channels have ``wf_increment`` but no ``wf_start_time``.
573564
file_size: The file size in bytes. Required if the file has truncated chunks.
574565
"""
575566

576-
asset_name: str
577-
run_name: str | None = None
578-
run_id: str | None = None
579567
start_time_override: datetime | None = None
580568
file_size: int | None = None
581569

@@ -622,21 +610,15 @@ class Hdf5DataColumn(BaseModel):
622610
value_field: str | None = None
623611

624612

625-
class Hdf5ImportConfig(BaseModel):
613+
class Hdf5ImportConfig(ImportConfigBase):
626614
"""Configuration for importing an HDF5 file.
627615
628616
Attributes:
629-
asset_name: Name of the asset to import data into.
630-
run_name: Name for the run. Ignored if ``run_id`` is set.
631-
run_id: ID of an existing run to append data to.
632617
data: List of dataset mappings, each pairing a time and value dataset to a channel.
633618
time_format: The time format used across all time datasets.
634619
relative_start_time: Required when using a relative time format.
635620
"""
636621

637-
asset_name: str
638-
run_name: str | None = None
639-
run_id: str | None = None
640622
data: list[Hdf5DataColumn]
641623
time_format: TimeFormat
642624
relative_start_time: datetime | None = None
@@ -678,9 +660,6 @@ def _to_proto(self) -> Hdf5ConfigProto:
678660
return proto
679661

680662

681-
# Note: Using Union instead of | syntax for Python 3.9 compatibility at module level.
682-
# While `from __future__ import annotations` allows | in type hints (they're strings),
683-
# module-level type aliases are evaluated at runtime and require Union in Python <3.10.
684663
ImportConfig = Union[
685664
CsvImportConfig,
686665
ParquetFlatDatasetImportConfig,

0 commit comments

Comments
 (0)