nominal 1.105.0__py3-none-any.whl → 1.106.0__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.
CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.106.0](https://github.com/nominal-io/nominal-client/compare/v1.105.0...v1.106.0) (2026-01-21)
4
+
5
+
6
+ ### Features
7
+
8
+ * create dataset with uuid endpoint for mgiration ([#580](https://github.com/nominal-io/nominal-client/issues/580)) ([f3a3b38](https://github.com/nominal-io/nominal-client/commit/f3a3b383d62e8466af617179479918ef0d1ee534))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * handle case where compute returns a non-bucketed response for a bucketed query ([#579](https://github.com/nominal-io/nominal-client/issues/579)) ([58b84f5](https://github.com/nominal-io/nominal-client/commit/58b84f55d282a84c8eb6703816b16920e3b8ecf3))
14
+
3
15
  ## [1.105.0](https://github.com/nominal-io/nominal-client/compare/v1.104.3...v1.105.0) (2026-01-21)
4
16
 
5
17
 
nominal/core/dataset.py CHANGED
@@ -1093,6 +1093,7 @@ def _construct_new_ingest_options(
1093
1093
  tag_columns=tag_columns,
1094
1094
  is_archive=file_type.is_parquet_archive(),
1095
1095
  additional_file_tags={**tags} if tags else None,
1096
+ exclude_columns=[],
1096
1097
  )
1097
1098
  )
1098
1099
  else:
@@ -1107,6 +1108,7 @@ def _construct_new_ingest_options(
1107
1108
  channel_prefix=channel_prefix,
1108
1109
  tag_columns=tag_columns,
1109
1110
  additional_file_tags={**tags} if tags else None,
1111
+ exclude_columns=[],
1110
1112
  )
1111
1113
  )
1112
1114
 
@@ -1139,6 +1141,7 @@ def _construct_existing_ingest_options(
1139
1141
  tag_columns=tag_columns,
1140
1142
  is_archive=file_type.is_parquet_archive(),
1141
1143
  additional_file_tags={**tags} if tags else None,
1144
+ exclude_columns=[],
1142
1145
  )
1143
1146
  )
1144
1147
  else:
@@ -1152,5 +1155,6 @@ def _construct_existing_ingest_options(
1152
1155
  timestamp_metadata=timestamp_metadata,
1153
1156
  tag_columns=tag_columns,
1154
1157
  additional_file_tags={**tags} if tags else None,
1158
+ exclude_columns=[],
1155
1159
  )
1156
1160
  )
@@ -317,10 +317,41 @@ def _compute_buckets(
317
317
  def _numeric_buckets_from_compute_response(
318
318
  response: scout_compute_api.ComputeNodeResponse,
319
319
  ) -> Iterable[tuple[api.Timestamp, scout_compute_api.NumericBucket]]:
320
- if response.type != "bucketedNumeric" or response.bucketed_numeric is None:
321
- return
322
-
323
- yield from zip(response.bucketed_numeric.timestamps, response.bucketed_numeric.buckets)
320
+ if response.numeric_point is not None:
321
+ # single point would be returned-- create a synthetic bucket
322
+ val = response.numeric_point.value
323
+ yield (
324
+ response.numeric_point.timestamp,
325
+ scout_compute_api.NumericBucket(
326
+ count=1,
327
+ first_point=response.numeric_point,
328
+ max=val,
329
+ mean=val,
330
+ min=val,
331
+ variance=0,
332
+ last_point=response.numeric_point,
333
+ ),
334
+ )
335
+ elif response.numeric is not None:
336
+ # Not enough points to reach the number of requested bucket count, so
337
+ # gets returned as all of the raw data.
338
+ for timestamp, value in zip(response.numeric.timestamps, response.numeric.values):
339
+ point = scout_compute_api.NumericPoint(timestamp, value)
340
+ yield (
341
+ timestamp,
342
+ scout_compute_api.NumericBucket(
343
+ count=1,
344
+ first_point=point,
345
+ max=value,
346
+ min=value,
347
+ mean=value,
348
+ variance=0,
349
+ last_point=point,
350
+ ),
351
+ )
352
+ elif response.bucketed_numeric is not None:
353
+ # Actually bucketed data
354
+ yield from zip(response.bucketed_numeric.timestamps, response.bucketed_numeric.buckets)
324
355
 
325
356
 
326
357
  def _timestamp_from_conjure(timestamp: api.Timestamp) -> params.NanosecondsUTC:
@@ -0,0 +1,5 @@
1
+ from nominal.experimental.dataset_utils._dataset_utils import create_dataset_with_uuid
2
+
3
+ __all__ = [
4
+ "create_dataset_with_uuid",
5
+ ]
@@ -0,0 +1,52 @@
1
+ from collections.abc import Mapping, Sequence
2
+
3
+ from nominal_api import scout_catalog
4
+
5
+ from nominal.core import Dataset, NominalClient
6
+
7
+
8
+ def create_dataset_with_uuid(
9
+ client: NominalClient,
10
+ dataset_uuid: str,
11
+ name: str,
12
+ *,
13
+ description: str | None = None,
14
+ labels: Sequence[str] = (),
15
+ properties: Mapping[str, str] | None = None,
16
+ ) -> Dataset:
17
+ """Create a dataset with a specific UUID.
18
+
19
+ This is useful for migrations where the dataset UUID must be controlled by the caller.
20
+ Throws a conflict error if a dataset with the specified UUID already exists.
21
+
22
+ This endpoint is not intended for general use. Use `NominalClient.create_dataset` instead
23
+ to create a new dataset with an auto-generated UUID.
24
+
25
+ Args:
26
+ client: The NominalClient to use for creating the dataset.
27
+ dataset_uuid: The UUID to assign to the new dataset.
28
+ name: Name of the dataset to create.
29
+ description: Human readable description of the dataset.
30
+ labels: Text labels to apply to the created dataset.
31
+ properties: Key-value properties to apply to the created dataset.
32
+
33
+ Returns:
34
+ Reference to the created dataset in Nominal.
35
+ """
36
+ create_dataset_request = scout_catalog.CreateDataset(
37
+ name=name,
38
+ description=description,
39
+ labels=list(labels),
40
+ properties={} if properties is None else dict(properties),
41
+ is_v2_dataset=True,
42
+ metadata={},
43
+ origin_metadata=scout_catalog.DatasetOriginMetadata(),
44
+ workspace=client._clients.workspace_rid,
45
+ marking_rids=[],
46
+ )
47
+ request = scout_catalog.CreateDatasetWithUuidRequest(
48
+ create_dataset=create_dataset_request,
49
+ uuid=dataset_uuid,
50
+ )
51
+ response = client._clients.catalog.create_dataset_with_uuid(client._clients.auth_header, request)
52
+ return Dataset._from_conjure(client._clients, response)
@@ -26,6 +26,7 @@ from nominal.core._event_types import EventType, SearchEventOriginType
26
26
  from nominal.core._utils.api_tools import Link, LinkDict
27
27
  from nominal.core.attachment import Attachment
28
28
  from nominal.core.run import Run
29
+ from nominal.experimental.dataset_utils import create_dataset_with_uuid
29
30
  from nominal.experimental.migration.migration_resources import MigrationResources
30
31
  from nominal.ts import (
31
32
  IntegralNanosecondsDuration,
@@ -422,6 +423,7 @@ def copy_dataset_from(
422
423
  new_dataset_properties: dict[str, Any] | None = None,
423
424
  new_dataset_labels: Sequence[str] | None = None,
424
425
  include_files: bool = False,
426
+ preserve_uuid: bool = False,
425
427
  ) -> Dataset:
426
428
  """Copy a dataset from the source to the destination client.
427
429
 
@@ -435,6 +437,9 @@ def copy_dataset_from(
435
437
  properties are used.
436
438
  new_dataset_labels: Optional new labels for the copied dataset. If not provided, the original labels are used.
437
439
  include_files: Whether to include files in the copied dataset.
440
+ preserve_uuid: If True, create the dataset with the same UUID as the source dataset.
441
+ This is useful for migrations where references to datasets must be preserved.
442
+ Throws a conflict error if a dataset with the UUID already exists.
438
443
 
439
444
  Returns:
440
445
  The newly created Dataset in the destination client.
@@ -448,12 +453,34 @@ def copy_dataset_from(
448
453
  source_dataset.rid,
449
454
  extra=log_extras,
450
455
  )
451
- new_dataset = destination_client.create_dataset(
452
- name=new_dataset_name if new_dataset_name is not None else source_dataset.name,
453
- description=new_dataset_description if new_dataset_description is not None else source_dataset.description,
454
- properties=new_dataset_properties if new_dataset_properties is not None else source_dataset.properties,
455
- labels=new_dataset_labels if new_dataset_labels is not None else source_dataset.labels,
456
- )
456
+
457
+ dataset_name = new_dataset_name if new_dataset_name is not None else source_dataset.name
458
+ dataset_description = new_dataset_description if new_dataset_description is not None else source_dataset.description
459
+ dataset_properties = new_dataset_properties if new_dataset_properties is not None else source_dataset.properties
460
+ dataset_labels = new_dataset_labels if new_dataset_labels is not None else source_dataset.labels
461
+
462
+ if preserve_uuid:
463
+ # Extract the UUID from the source dataset's rid
464
+ match = UUID_PATTERN.search(source_dataset.rid)
465
+ if not match:
466
+ raise ValueError(f"Could not extract UUID from dataset rid: {source_dataset.rid}")
467
+ source_uuid = match.group(2)
468
+ new_dataset = create_dataset_with_uuid(
469
+ client=destination_client,
470
+ dataset_uuid=source_uuid,
471
+ name=dataset_name,
472
+ description=dataset_description,
473
+ labels=dataset_labels,
474
+ properties=dataset_properties,
475
+ )
476
+ else:
477
+ new_dataset = destination_client.create_dataset(
478
+ name=dataset_name,
479
+ description=dataset_description,
480
+ properties=dataset_properties,
481
+ labels=dataset_labels,
482
+ )
483
+
457
484
  if include_files:
458
485
  for source_file in source_dataset.list_files():
459
486
  copy_file_to_dataset(source_file, new_dataset)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nominal
3
- Version: 1.105.0
3
+ Version: 1.106.0
4
4
  Summary: Automate Nominal workflows in Python
5
5
  Project-URL: Homepage, https://nominal.io
6
6
  Project-URL: Documentation, https://docs.nominal.io
@@ -20,7 +20,7 @@ Requires-Dist: cachetools>=6.1.0
20
20
  Requires-Dist: click<9,>=8
21
21
  Requires-Dist: conjure-python-client<4,>=3.1.0
22
22
  Requires-Dist: ffmpeg-python>=0.2.0
23
- Requires-Dist: nominal-api==0.1032.0
23
+ Requires-Dist: nominal-api==0.1075.0
24
24
  Requires-Dist: nominal-streaming==0.5.8; platform_python_implementation == 'CPython' and python_version >= '3.10' and ((sys_platform == 'win32' and platform_machine == 'AMD64') or (sys_platform == 'darwin' and platform_machine == 'arm64') or (sys_platform == 'linux' and (platform_machine == 'x86_64' or platform_machine == 'armv7l')))
25
25
  Requires-Dist: openpyxl>=0.0.0
26
26
  Requires-Dist: pandas>=0.0.0
@@ -1,4 +1,4 @@
1
- CHANGELOG.md,sha256=RvN85qhQ4iyE4X8kwMTM03KSIBZeXEGRxxUOCEx_TmQ,88256
1
+ CHANGELOG.md,sha256=roEVqpYae9DRMQRYCiJQm7ei603DhmGY7_Il7PmaoDg,88865
2
2
  LICENSE,sha256=zEGHG9mjDjaIS3I79O8mweQo-yiTbqx8jJvUPppVAwk,1067
3
3
  README.md,sha256=KKe0dxh_pHXCtB7I9G4qWGQYvot_BZU8yW6MJyuyUHM,311
4
4
  nominal/__init__.py,sha256=rbraORnXUrNn1hywLXM0XwSQCd9UmQt20PDYlsBalfE,2167
@@ -41,7 +41,7 @@ nominal/core/client.py,sha256=Awt9WPkE-YXBfOwJMTL7Su8AZFJY3UMH7IKp5hI26YQ,68328
41
41
  nominal/core/connection.py,sha256=LYllr3a1H2xp8-i4MaX1M7yK8X-HnwuIkciyK9XgLtQ,5175
42
42
  nominal/core/containerized_extractors.py,sha256=fUz3-NHoNWYKqOCD15gLwGXDKVfdsW-x_kpXnkOI3BE,10224
43
43
  nominal/core/data_review.py,sha256=Z_W1Okp_FSQDiVCk6aKb9gV0EXbE2jtiQaPqc6TaL0g,11038
44
- nominal/core/dataset.py,sha256=iOR-jiTrnUxTnsF_eteQCHQmbe8IZovdSzhAgq40bB8,46648
44
+ nominal/core/dataset.py,sha256=LqofzNAlOd3S_3Aaw6b7DoY50rj6GyMHbUClIA2TmpY,46792
45
45
  nominal/core/dataset_file.py,sha256=8rCW6MO89MFbQ2NH0WtFWmJfRWeTxhmyuoGojuQQ4Qg,16545
46
46
  nominal/core/datasource.py,sha256=V5UahbqsCNIdml978kOHiY6boIxKxbp76KscNBpN5xc,16934
47
47
  nominal/core/event.py,sha256=8trZXyuAqRlKedgcqSgDIimXAAJBmEfDLyHkOOBwUC0,7762
@@ -75,20 +75,22 @@ nominal/exceptions/__init__.py,sha256=W2r_GWJkZQQ6t3HooFjGRdhIgJq3fBvRV7Yn6gseoO
75
75
  nominal/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  nominal/experimental/compute/README.md,sha256=jdgqPSIVhMSTnY3qebtDdyd8SCi4o107EtKqui2L76A,4865
77
77
  nominal/experimental/compute/__init__.py,sha256=NmjX_ZuCy5L0FYodpuFXpPLUyf1R8V-OitlEKLQvrAs,178
78
- nominal/experimental/compute/_buckets.py,sha256=W6njKDm1Tkr1yRITmgtRKLBDfoUYE0cJ65Fr2jS3MT8,14329
78
+ nominal/experimental/compute/_buckets.py,sha256=W2RckTYEZSxT3fjCnjS1iUYSqRV8EzQEix5T1B0qr30,15523
79
79
  nominal/experimental/compute/dsl/__init__.py,sha256=IDHrxnYLOy87zihq_HM1STtXPlxobzddvWbMOAQQh6A,502
80
80
  nominal/experimental/compute/dsl/_enum_expr_impls.py,sha256=RtKenY8ClmSYZEmsBh2ttRyVv3OSVMyANvFgbUylX1I,6899
81
81
  nominal/experimental/compute/dsl/_numeric_expr_impls.py,sha256=3EIMiysAUdwwrezr9aZ2vB4Z8xt-hHxeZ66h3lVLSV4,17083
82
82
  nominal/experimental/compute/dsl/_range_expr_impls.py,sha256=VCcU8L8gfSLSBphi_B8JBcN6zDVskvjmgsa8waz1QEU,1741
83
83
  nominal/experimental/compute/dsl/exprs.py,sha256=ppDbdblujuwN3vNkIqGwlTvbUmCnnjoOTCiHukpFGX8,10248
84
84
  nominal/experimental/compute/dsl/params.py,sha256=xwFwDMGV1Ifi4pjQGLb0v0YU8jJ_XKatN1nIGU1sm0I,3295
85
+ nominal/experimental/dataset_utils/__init__.py,sha256=OpJWUldJ7p7sgFCE_jfosP9J6giUoktFBlVoUS9QwWE,134
86
+ nominal/experimental/dataset_utils/_dataset_utils.py,sha256=vk3xdfBxkYY0KFhx4THX8B2w-pATMQFnOQIZAZqD9Wg,1896
85
87
  nominal/experimental/logging/__init__.py,sha256=9HrTkk_eyHv_w7EwqwOE1lf1Sa1Cm0cAf66beOsUfU0,322
86
88
  nominal/experimental/logging/click_log_handler.py,sha256=ANLf4IGgmh95V0kJlr756wQrjmQKp6DEpc9Cj7J5qHM,2331
87
89
  nominal/experimental/logging/nominal_log_handler.py,sha256=hyTxyjsvFnE7vtyrDJpunAqADHmXekNWALwxXPIJGCk,5120
88
90
  nominal/experimental/logging/rich_log_handler.py,sha256=8yz_VtxNgJg2oiesnXz2iXoBvQrUP5pAsYkxknOXgXA,1231
89
91
  nominal/experimental/migration/__init__.py,sha256=E2IgWJLwJ5bN6jbl8k5nHECKFx5aT11jKAzVYcyXn3o,460
90
92
  nominal/experimental/migration/migration_resources.py,sha256=Tf_7kNBeSaY8z2fTF7DAxk-9q3a7F8xXFVvxI8tTc9c,415
91
- nominal/experimental/migration/migration_utils.py,sha256=ZHH_-pMFHHA-eIcCeoINaxueNrLe7fapVgdcVc4bQCI,30074
93
+ nominal/experimental/migration/migration_utils.py,sha256=E3K2_kMsWIG9p2oIwxM64aXJLRemhsCbqfJ4Jv4SJ4M,31214
92
94
  nominal/experimental/rust_streaming/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
95
  nominal/experimental/rust_streaming/rust_write_stream.py,sha256=oQ6ixwm8ct8ZDc_qNB7AucDt8o5-_aBVlW2fFCQ_nmA,1541
94
96
  nominal/experimental/stream_v2/__init__.py,sha256=W39vK46pssx5sXvmsImMuJiEPs7iGtwrbYBI0bWnXCY,2313
@@ -107,8 +109,8 @@ nominal/thirdparty/polars/polars_export_handler.py,sha256=hGCSwXX9dC4MG01CmmjlTb
107
109
  nominal/thirdparty/tdms/__init__.py,sha256=6n2ImFr2Wiil6JM1P5Q7Mpr0VzLcnDkmup_ftNpPq-s,142
108
110
  nominal/thirdparty/tdms/_tdms.py,sha256=m4gxbpxB9MTLi2FuYvGlbUGSyDAZKFxbM3ia2x1wIz0,8746
109
111
  nominal/ts/__init__.py,sha256=hmd0ENvDhxRnzDKGLxIub6QG8LpcxCgcyAct029CaEs,21442
110
- nominal-1.105.0.dist-info/METADATA,sha256=sr6qiNjoqRlvLRiWc-2KeA6a-B8qx2jlR4rV-rvQ03E,2307
111
- nominal-1.105.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
112
- nominal-1.105.0.dist-info/entry_points.txt,sha256=-mCLhxgg9R_lm5efT7vW9wuBH12izvY322R0a3TYxbE,66
113
- nominal-1.105.0.dist-info/licenses/LICENSE,sha256=zEGHG9mjDjaIS3I79O8mweQo-yiTbqx8jJvUPppVAwk,1067
114
- nominal-1.105.0.dist-info/RECORD,,
112
+ nominal-1.106.0.dist-info/METADATA,sha256=cdf0KpzkFmrFb5XsWVEa6F4UNUr1WMp79uekN4hIkq8,2307
113
+ nominal-1.106.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
114
+ nominal-1.106.0.dist-info/entry_points.txt,sha256=-mCLhxgg9R_lm5efT7vW9wuBH12izvY322R0a3TYxbE,66
115
+ nominal-1.106.0.dist-info/licenses/LICENSE,sha256=zEGHG9mjDjaIS3I79O8mweQo-yiTbqx8jJvUPppVAwk,1067
116
+ nominal-1.106.0.dist-info/RECORD,,