nominal 1.108.0__tar.gz → 1.110.0__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.
Files changed (118) hide show
  1. {nominal-1.108.0 → nominal-1.110.0}/CHANGELOG.md +20 -0
  2. {nominal-1.108.0 → nominal-1.110.0}/PKG-INFO +2 -2
  3. nominal-1.110.0/nominal/core/_checklist_types.py +48 -0
  4. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/api_tools.py +16 -2
  5. nominal-1.110.0/nominal/core/_video_types.py +16 -0
  6. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/asset.py +34 -18
  7. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/checklist.py +11 -25
  8. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/client.py +25 -29
  9. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/data_review.py +32 -11
  10. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/dataset_file.py +6 -0
  11. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/run.py +25 -11
  12. nominal-1.110.0/nominal/core/streaming_checklist.py +25 -0
  13. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/video.py +71 -13
  14. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/video_file.py +62 -2
  15. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/migration/migration_utils.py +213 -9
  16. {nominal-1.108.0 → nominal-1.110.0}/pyproject.toml +2 -2
  17. {nominal-1.108.0 → nominal-1.110.0}/.gitignore +0 -0
  18. {nominal-1.108.0 → nominal-1.110.0}/LICENSE +0 -0
  19. {nominal-1.108.0 → nominal-1.110.0}/README.md +0 -0
  20. {nominal-1.108.0 → nominal-1.110.0}/nominal/__init__.py +0 -0
  21. {nominal-1.108.0 → nominal-1.110.0}/nominal/__main__.py +0 -0
  22. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/README.md +0 -0
  23. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/__init__.py +0 -0
  24. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/dataclass_tools.py +0 -0
  25. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/deprecation_tools.py +0 -0
  26. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/iterator_tools.py +0 -0
  27. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/streaming_tools.py +0 -0
  28. {nominal-1.108.0 → nominal-1.110.0}/nominal/_utils/timing_tools.py +0 -0
  29. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/__init__.py +0 -0
  30. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/__main__.py +0 -0
  31. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/attachment.py +0 -0
  32. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/auth.py +0 -0
  33. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/config.py +0 -0
  34. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/dataset.py +0 -0
  35. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/download.py +0 -0
  36. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/mis.py +0 -0
  37. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/run.py +0 -0
  38. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/util/__init__.py +0 -0
  39. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/util/click_log_handler.py +0 -0
  40. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/util/global_decorators.py +0 -0
  41. {nominal-1.108.0 → nominal-1.110.0}/nominal/cli/util/verify_connection.py +0 -0
  42. {nominal-1.108.0 → nominal-1.110.0}/nominal/config/__init__.py +0 -0
  43. {nominal-1.108.0 → nominal-1.110.0}/nominal/config/_config.py +0 -0
  44. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/__init__.py +0 -0
  45. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_clientsbunch.py +0 -0
  46. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_constants.py +0 -0
  47. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_event_types.py +0 -0
  48. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_stream/__init__.py +0 -0
  49. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_stream/batch_processor.py +0 -0
  50. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_stream/batch_processor_proto.py +0 -0
  51. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_stream/write_stream.py +0 -0
  52. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_stream/write_stream_base.py +0 -0
  53. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_types.py +0 -0
  54. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/README.md +0 -0
  55. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/__init__.py +0 -0
  56. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/multipart.py +0 -0
  57. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/multipart_downloader.py +0 -0
  58. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/networking.py +0 -0
  59. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/pagination_tools.py +0 -0
  60. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/query_tools.py +0 -0
  61. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/_utils/queueing.py +0 -0
  62. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/attachment.py +0 -0
  63. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/bounds.py +0 -0
  64. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/channel.py +0 -0
  65. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/connection.py +0 -0
  66. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/containerized_extractors.py +0 -0
  67. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/dataset.py +0 -0
  68. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/datasource.py +0 -0
  69. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/event.py +0 -0
  70. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/exceptions.py +0 -0
  71. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/filetype.py +0 -0
  72. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/log.py +0 -0
  73. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/secret.py +0 -0
  74. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/unit.py +0 -0
  75. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/user.py +0 -0
  76. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/workbook.py +0 -0
  77. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/workbook_template.py +0 -0
  78. {nominal-1.108.0 → nominal-1.110.0}/nominal/core/workspace.py +0 -0
  79. {nominal-1.108.0 → nominal-1.110.0}/nominal/exceptions/__init__.py +0 -0
  80. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/__init__.py +0 -0
  81. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/README.md +0 -0
  82. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/__init__.py +0 -0
  83. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/_buckets.py +0 -0
  84. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/__init__.py +0 -0
  85. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/_enum_expr_impls.py +0 -0
  86. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/_numeric_expr_impls.py +0 -0
  87. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/_range_expr_impls.py +0 -0
  88. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/exprs.py +0 -0
  89. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/compute/dsl/params.py +0 -0
  90. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/dataset_utils/__init__.py +0 -0
  91. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/dataset_utils/_dataset_utils.py +0 -0
  92. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/logging/__init__.py +0 -0
  93. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/logging/click_log_handler.py +0 -0
  94. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/logging/nominal_log_handler.py +0 -0
  95. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/logging/rich_log_handler.py +0 -0
  96. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/migration/__init__.py +0 -0
  97. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/migration/migration_data_config.py +0 -0
  98. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/migration/migration_resources.py +0 -0
  99. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/rust_streaming/__init__.py +0 -0
  100. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/rust_streaming/rust_write_stream.py +0 -0
  101. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/stream_v2/__init__.py +0 -0
  102. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/stream_v2/_serializer.py +0 -0
  103. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/stream_v2/_write_stream.py +0 -0
  104. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/video_processing/__init__.py +0 -0
  105. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/video_processing/resolution.py +0 -0
  106. {nominal-1.108.0 → nominal-1.110.0}/nominal/experimental/video_processing/video_conversion.py +0 -0
  107. {nominal-1.108.0 → nominal-1.110.0}/nominal/nominal.py +0 -0
  108. {nominal-1.108.0 → nominal-1.110.0}/nominal/py.typed +0 -0
  109. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/__init__.py +0 -0
  110. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/matlab/__init__.py +0 -0
  111. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/matlab/_matlab.py +0 -0
  112. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/pandas/__init__.py +0 -0
  113. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/pandas/_pandas.py +0 -0
  114. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/polars/__init__.py +0 -0
  115. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/polars/polars_export_handler.py +0 -0
  116. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/tdms/__init__.py +0 -0
  117. {nominal-1.108.0 → nominal-1.110.0}/nominal/thirdparty/tdms/_tdms.py +0 -0
  118. {nominal-1.108.0 → nominal-1.110.0}/nominal/ts/__init__.py +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.110.0](https://github.com/nominal-io/nominal-client/compare/v1.109.0...v1.110.0) (2026-01-23)
4
+
5
+
6
+ ### Features
7
+
8
+ * add data review/checklist methods to assets and runs ([#567](https://github.com/nominal-io/nominal-client/issues/567)) ([4b080d9](https://github.com/nominal-io/nominal-client/commit/4b080d96700fc51555b69a093d592cb03b92e073))
9
+ * add video clone capability in experimental and refactor video methods ([#576](https://github.com/nominal-io/nominal-client/issues/576)) ([f58a89c](https://github.com/nominal-io/nominal-client/commit/f58a89c1fe2220677f439fdf306f0c743e441548))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * properly handle dataset files in the new deletion states ([#590](https://github.com/nominal-io/nominal-client/issues/590)) ([e71bcc9](https://github.com/nominal-io/nominal-client/commit/e71bcc9f8fd752257dc2b33789380f0ffb7c5410))
15
+
16
+ ## [1.109.0](https://github.com/nominal-io/nominal-client/compare/v1.108.0...v1.109.0) (2026-01-23)
17
+
18
+
19
+ ### Features
20
+
21
+ * clone channels when cloning datasets ([#587](https://github.com/nominal-io/nominal-client/issues/587)) ([ab6de09](https://github.com/nominal-io/nominal-client/commit/ab6de0962c55e2c0a6933d6b9f94fb36f5e91273))
22
+
3
23
  ## [1.108.0](https://github.com/nominal-io/nominal-client/compare/v1.107.0...v1.108.0) (2026-01-22)
4
24
 
5
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nominal
3
- Version: 1.108.0
3
+ Version: 1.110.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.1075.0
23
+ Requires-Dist: nominal-api==0.1079.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
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import IntEnum
4
+
5
+ from nominal_api import scout_api
6
+
7
+
8
+ class Priority(IntEnum):
9
+ P0 = 0
10
+ P1 = 1
11
+ P2 = 2
12
+ P3 = 3
13
+ P4 = 4
14
+
15
+ @classmethod
16
+ def _from_conjure(cls, priority: scout_api.Priority) -> Priority:
17
+ match priority.name:
18
+ case "P0":
19
+ return cls.P0
20
+ case "P1":
21
+ return cls.P1
22
+ case "P2":
23
+ return cls.P2
24
+ case "P3":
25
+ return cls.P3
26
+ case "P4":
27
+ return cls.P4
28
+ case _:
29
+ raise ValueError(f"unknown priority '{priority}', expected one of {list(cls)}")
30
+
31
+ def _to_conjure(self) -> scout_api.Priority:
32
+ match self:
33
+ case Priority.P0:
34
+ return scout_api.Priority.P0
35
+ case Priority.P1:
36
+ return scout_api.Priority.P1
37
+ case Priority.P2:
38
+ return scout_api.Priority.P2
39
+ case Priority.P3:
40
+ return scout_api.Priority.P3
41
+ case Priority.P4:
42
+ return scout_api.Priority.P4
43
+ case _:
44
+ raise ValueError(f"unknown priority '{self}', expected one of {list(Priority)}")
45
+
46
+
47
+ def _conjure_priority_to_priority(priority: scout_api.Priority) -> Priority:
48
+ return Priority._from_conjure(priority)
@@ -5,13 +5,15 @@ import importlib.metadata
5
5
  import logging
6
6
  import platform
7
7
  import sys
8
- from typing import Any, Generic, Mapping, Protocol, Sequence, TypeAlias, TypedDict, TypeVar, runtime_checkable
8
+ from typing import Any, Generic, Literal, Mapping, Protocol, Sequence, TypeAlias, TypedDict, TypeVar, runtime_checkable
9
9
 
10
- from nominal_api import scout_compute_api, scout_run_api
10
+ from nominal_api import scout_asset_api, scout_compute_api, scout_run_api
11
11
  from typing_extensions import NotRequired, Self
12
12
 
13
13
  from nominal._utils.dataclass_tools import update_dataclass
14
14
 
15
+ ScopeTypeSpecifier: TypeAlias = Literal["connection", "dataset", "video"]
16
+
15
17
  logger = logging.getLogger(__name__)
16
18
 
17
19
  T = TypeVar("T")
@@ -94,3 +96,15 @@ def create_api_tags(tags: Mapping[str, str] | None = None) -> dict[str, scout_co
94
96
  return {}
95
97
 
96
98
  return {key: scout_compute_api.StringConstant(literal=value) for key, value in tags.items()}
99
+
100
+
101
+ def filter_scopes(
102
+ scopes: Sequence[scout_asset_api.DataScope], scope_type: ScopeTypeSpecifier
103
+ ) -> Sequence[scout_asset_api.DataScope]:
104
+ return [scope for scope in scopes if scope.data_source.type.lower() == scope_type]
105
+
106
+
107
+ def filter_scope_rids(scopes: Sequence[scout_asset_api.DataScope], scope_type: ScopeTypeSpecifier) -> Mapping[str, str]:
108
+ return {
109
+ scope.data_scope_name: getattr(scope.data_source, scope_type) for scope in filter_scopes(scopes, scope_type)
110
+ }
@@ -0,0 +1,16 @@
1
+ from dataclasses import dataclass
2
+
3
+ from nominal.ts import IntegralNanosecondsUTC
4
+
5
+
6
+ @dataclass(init=True, repr=False, eq=False, order=False, unsafe_hash=False)
7
+ class McapVideoDetails:
8
+ mcap_channel_locator_topic: str
9
+
10
+
11
+ @dataclass(init=True, repr=False, eq=False, order=False, unsafe_hash=False)
12
+ class TimestampOptions:
13
+ starting_timestamp: IntegralNanosecondsUTC
14
+ ending_timestamp: IntegralNanosecondsUTC
15
+ scaling_factor: float
16
+ true_framerate: float
@@ -4,7 +4,7 @@ import datetime
4
4
  import logging
5
5
  from dataclasses import dataclass, field
6
6
  from types import MappingProxyType
7
- from typing import Iterable, Literal, Mapping, Protocol, Sequence, TypeAlias
7
+ from typing import Iterable, Mapping, Protocol, Sequence, TypeAlias
8
8
 
9
9
  from nominal_api import (
10
10
  event,
@@ -15,6 +15,7 @@ from nominal_api import (
15
15
  )
16
16
  from typing_extensions import Self
17
17
 
18
+ from nominal.core import data_review, streaming_checklist
18
19
  from nominal.core._clientsbunch import HasScoutParams
19
20
  from nominal.core._event_types import EventType, SearchEventOriginType
20
21
  from nominal.core._utils.api_tools import (
@@ -22,7 +23,10 @@ from nominal.core._utils.api_tools import (
22
23
  Link,
23
24
  LinkDict,
24
25
  RefreshableMixin,
26
+ ScopeTypeSpecifier,
25
27
  create_links,
28
+ filter_scope_rids,
29
+ filter_scopes,
26
30
  rid_from_instance_or_string,
27
31
  )
28
32
  from nominal.core._utils.pagination_tools import search_runs_by_asset_paginated
@@ -35,25 +39,10 @@ from nominal.core.video import Video, _create_video, _get_video
35
39
  from nominal.ts import IntegralNanosecondsDuration, IntegralNanosecondsUTC, _SecondsNanos
36
40
 
37
41
  ScopeType: TypeAlias = Connection | Dataset | Video
38
- ScopeTypeSpecifier: TypeAlias = Literal["connection", "dataset", "video"]
39
42
 
40
43
  logger = logging.getLogger(__name__)
41
44
 
42
45
 
43
- def _filter_scopes(
44
- scopes: Sequence[scout_asset_api.DataScope], scope_type: ScopeTypeSpecifier
45
- ) -> Sequence[scout_asset_api.DataScope]:
46
- return [scope for scope in scopes if scope.data_source.type.lower() == scope_type]
47
-
48
-
49
- def _filter_scope_rids(
50
- scopes: Sequence[scout_asset_api.DataScope], scope_type: ScopeTypeSpecifier
51
- ) -> Mapping[str, str]:
52
- return {
53
- scope.data_scope_name: getattr(scope.data_source, scope_type) for scope in _filter_scopes(scopes, scope_type)
54
- }
55
-
56
-
57
46
  @dataclass(frozen=True)
58
47
  class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
59
48
  rid: str
@@ -70,6 +59,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
70
59
  Video._Clients,
71
60
  Attachment._Clients,
72
61
  Event._Clients,
62
+ data_review.DataReview._Clients,
73
63
  HasScoutParams,
74
64
  Protocol,
75
65
  ):
@@ -94,11 +84,11 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
94
84
  return response[self.rid]
95
85
 
96
86
  def _list_dataset_scopes(self) -> Sequence[scout_asset_api.DataScope]:
97
- return _filter_scopes(self._get_latest_api().data_scopes, "dataset")
87
+ return filter_scopes(self._get_latest_api().data_scopes, "dataset")
98
88
 
99
89
  def _scope_rids(self, scope_type: ScopeTypeSpecifier) -> Mapping[str, str]:
100
90
  asset = self._get_latest_api()
101
- return _filter_scope_rids(asset.data_scopes, scope_type)
91
+ return filter_scope_rids(asset.data_scopes, scope_type)
102
92
 
103
93
  def update(
104
94
  self,
@@ -540,6 +530,32 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
540
530
  origin_types=origin_types,
541
531
  )
542
532
 
533
+ def search_data_reviews(
534
+ self,
535
+ runs: Sequence[Run | str] | None = None,
536
+ ) -> Sequence[data_review.DataReview]:
537
+ """Search for data reviews associated with this Asset. See nominal.core.client.search_data_reviews
538
+ for details.
539
+ """
540
+ return list(
541
+ data_review._iter_search_data_reviews(
542
+ self._clients,
543
+ assets=[self.rid],
544
+ runs=[rid_from_instance_or_string(run) for run in (runs or [])],
545
+ )
546
+ )
547
+
548
+ def list_streaming_checklists(self) -> Sequence[str]:
549
+ """List all Streaming Checklists associated with this Asset. See
550
+ nominal.core.client.list_streaming_checklists for details.
551
+ """
552
+ return list(
553
+ streaming_checklist._iter_list_streaming_checklists(
554
+ self._clients,
555
+ asset_rid=self.rid,
556
+ )
557
+ )
558
+
543
559
  def remove_attachments(self, attachments: Iterable[Attachment] | Iterable[str]) -> None:
544
560
  """Remove attachments from this asset.
545
561
  Does not remove the attachments from Nominal.
@@ -2,10 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import timedelta
5
- from typing import Literal, Mapping, Protocol, Sequence
5
+ from typing import Mapping, Protocol, Sequence
6
6
 
7
7
  from nominal_api import (
8
- scout_api,
8
+ event,
9
+ scout,
9
10
  scout_checklistexecution_api,
10
11
  scout_checks_api,
11
12
  scout_datareview_api,
@@ -13,11 +14,11 @@ from nominal_api import (
13
14
  )
14
15
  from typing_extensions import Self
15
16
 
17
+ from nominal.core import run as core_run
16
18
  from nominal.core._clientsbunch import HasScoutParams
17
19
  from nominal.core._utils.api_tools import HasRid, rid_from_instance_or_string
18
20
  from nominal.core.asset import Asset
19
21
  from nominal.core.data_review import DataReview
20
- from nominal.core.run import Run
21
22
  from nominal.ts import _to_api_duration
22
23
 
23
24
 
@@ -30,13 +31,17 @@ class Checklist(HasRid):
30
31
  labels: Sequence[str]
31
32
  _clients: _Clients = field(repr=False)
32
33
 
33
- class _Clients(DataReview._Clients, HasScoutParams, Protocol):
34
+ class _Clients(HasScoutParams, Protocol):
34
35
  @property
35
36
  def checklist(self) -> scout_checks_api.ChecklistService: ...
36
37
  @property
37
38
  def checklist_execution(self) -> scout_checklistexecution_api.ChecklistExecutionService: ...
38
39
  @property
39
40
  def datareview(self) -> scout_datareview_api.DataReviewService: ...
41
+ @property
42
+ def event(self) -> event.EventService: ...
43
+ @property
44
+ def run(self) -> scout.RunService: ...
40
45
 
41
46
  @classmethod
42
47
  def _from_conjure(cls, clients: _Clients, checklist: scout_checks_api.VersionedChecklist) -> Self:
@@ -53,7 +58,7 @@ class Checklist(HasRid):
53
58
  _clients=clients,
54
59
  )
55
60
 
56
- def execute(self, run: Run | str, commit: str | None = None) -> DataReview:
61
+ def execute(self, run: core_run.Run | str, commit: str | None = None) -> DataReview:
57
62
  """Execute a checklist against a run.
58
63
 
59
64
  Args:
@@ -152,26 +157,7 @@ class Checklist(HasRid):
152
157
  """Returns a link to the page for this checklist in the Nominal app"""
153
158
  return f"{self._clients.app_base_url}/checklists/{self.rid}"
154
159
 
155
- def preview_for_run_url(self, run: Run | str) -> str:
160
+ def preview_for_run_url(self, run: core_run.Run | str) -> str:
156
161
  """Returns a link to the page for previewing this checklist on a given run in the Nominal app"""
157
162
  run_rid = rid_from_instance_or_string(run)
158
163
  return f"{self.nominal_url}?previewRunRid={run_rid}"
159
-
160
-
161
- Priority = Literal[0, 1, 2, 3, 4]
162
-
163
-
164
- _priority_to_conjure_map: dict[Priority, scout_api.Priority] = {
165
- 0: scout_api.Priority.P0,
166
- 1: scout_api.Priority.P1,
167
- 2: scout_api.Priority.P2,
168
- 3: scout_api.Priority.P3,
169
- 4: scout_api.Priority.P4,
170
- }
171
-
172
-
173
- def _conjure_priority_to_priority(priority: scout_api.Priority) -> Priority:
174
- inverted_map = {v: k for k, v in _priority_to_conjure_map.items()}
175
- if priority in inverted_map:
176
- return inverted_map[priority]
177
- raise ValueError(f"unknown priority '{priority}', expected one of {_priority_to_conjure_map.values()}")
@@ -48,11 +48,8 @@ from nominal.core._utils.multipart import (
48
48
  upload_multipart_io,
49
49
  )
50
50
  from nominal.core._utils.pagination_tools import (
51
- list_streaming_checklists_for_asset_paginated,
52
- list_streaming_checklists_paginated,
53
51
  search_assets_paginated,
54
52
  search_checklists_paginated,
55
- search_data_reviews_paginated,
56
53
  search_datasets_paginated,
57
54
  search_runs_by_asset_paginated,
58
55
  search_runs_paginated,
@@ -84,7 +81,7 @@ from nominal.core.containerized_extractors import (
84
81
  FileExtractionInput,
85
82
  FileOutputFormat,
86
83
  )
87
- from nominal.core.data_review import DataReview, DataReviewBuilder
84
+ from nominal.core.data_review import DataReview, DataReviewBuilder, _iter_search_data_reviews
88
85
  from nominal.core.dataset import (
89
86
  Dataset,
90
87
  _create_dataset,
@@ -97,6 +94,7 @@ from nominal.core.exceptions import NominalConfigError, NominalError, NominalMet
97
94
  from nominal.core.filetype import FileType, FileTypes
98
95
  from nominal.core.run import Run, _create_run
99
96
  from nominal.core.secret import Secret
97
+ from nominal.core.streaming_checklist import _iter_list_streaming_checklists
100
98
  from nominal.core.unit import Unit, _available_units
101
99
  from nominal.core.user import User
102
100
  from nominal.core.video import Video, _create_video
@@ -1162,21 +1160,17 @@ class NominalClient:
1162
1160
  )
1163
1161
  return list(self._iter_search_assets(query))
1164
1162
 
1165
- def _iter_list_streaming_checklists(self, asset: str | None) -> Iterable[str]:
1166
- if asset is None:
1167
- return list_streaming_checklists_paginated(self._clients.checklist_execution, self._clients.auth_header)
1168
- return list_streaming_checklists_for_asset_paginated(
1169
- self._clients.checklist_execution, self._clients.auth_header, asset
1170
- )
1171
-
1172
- def list_streaming_checklists(self, asset: Asset | str | None = None) -> Iterable[str]:
1163
+ def list_streaming_checklists(self, asset: Asset | str | None = None) -> Sequence[str]:
1173
1164
  """List all Streaming Checklists.
1174
1165
 
1175
1166
  Args:
1176
1167
  asset: if provided, only return checklists associated with the given asset.
1168
+
1169
+ Returns:
1170
+ All streaming checklist RIDs that match the provided conditions
1177
1171
  """
1178
- asset = None if asset is None else rid_from_instance_or_string(asset)
1179
- return list(self._iter_list_streaming_checklists(asset))
1172
+ asset_rid = None if asset is None else rid_from_instance_or_string(asset)
1173
+ return list(_iter_list_streaming_checklists(self._clients, asset_rid))
1180
1174
 
1181
1175
  def data_review_builder(self) -> DataReviewBuilder:
1182
1176
  return DataReviewBuilder([], [], [], _clients=self._clients)
@@ -1220,27 +1214,29 @@ class NominalClient:
1220
1214
  responses = self._clients.event.batch_get_events(self._clients.auth_header, list(rids))
1221
1215
  return [Event._from_conjure(self._clients, response) for response in responses]
1222
1216
 
1223
- def _iter_search_data_reviews(
1224
- self,
1225
- assets: Sequence[Asset | str] | None = None,
1226
- runs: Sequence[Run | str] | None = None,
1227
- ) -> Iterable[DataReview]:
1228
- for review in search_data_reviews_paginated(
1229
- self._clients.datareview,
1230
- self._clients.auth_header,
1231
- assets=[rid_from_instance_or_string(asset) for asset in assets] if assets else None,
1232
- runs=[rid_from_instance_or_string(run) for run in runs] if runs else None,
1233
- ):
1234
- yield DataReview._from_conjure(self._clients, review)
1235
-
1236
1217
  def search_data_reviews(
1237
1218
  self,
1238
1219
  assets: Sequence[Asset | str] | None = None,
1239
1220
  runs: Sequence[Run | str] | None = None,
1240
1221
  ) -> Sequence[DataReview]:
1241
- """Search for any data reviews present within a collection of runs and assets."""
1222
+ """Search for data reviews meeting the specified filters.
1223
+ Filters are ANDed together, e.g. `(data_review.asset == asset) AND (data_review.run == run)`
1224
+
1225
+ Args:
1226
+ assets: List of assets that must be associated with a data review to be included.
1227
+ runs: List of runs that must be associated with a data review to be included.
1228
+
1229
+ Returns:
1230
+ All data reviews which match all of the provided conditions
1231
+ """
1242
1232
  # TODO (drake-nominal): Expose checklist_refs to users
1243
- return list(self._iter_search_data_reviews(assets, runs))
1233
+ return list(
1234
+ _iter_search_data_reviews(
1235
+ clients=self._clients,
1236
+ assets=[rid_from_instance_or_string(asset) for asset in assets] if assets else None,
1237
+ runs=[rid_from_instance_or_string(run) for run in runs] if runs else None,
1238
+ )
1239
+ )
1244
1240
 
1245
1241
  def search_events(
1246
1242
  self,
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import timedelta
5
5
  from time import sleep
6
- from typing import Protocol, Sequence
6
+ from typing import TYPE_CHECKING, Iterable, Protocol, Sequence
7
7
 
8
8
  from nominal_api import (
9
9
  event as event_api,
@@ -18,14 +18,19 @@ from nominal_api import (
18
18
  )
19
19
  from typing_extensions import Self, deprecated
20
20
 
21
- from nominal.core import checklist, event
21
+ from nominal.core._checklist_types import Priority, _conjure_priority_to_priority
22
22
  from nominal.core._clientsbunch import HasScoutParams
23
23
  from nominal.core._utils.api_tools import HasRid, rid_from_instance_or_string
24
- from nominal.core.asset import Asset
24
+ from nominal.core._utils.pagination_tools import search_data_reviews_paginated
25
+ from nominal.core.event import Event
25
26
  from nominal.core.exceptions import NominalMethodRemovedError
26
- from nominal.core.run import Run
27
27
  from nominal.ts import IntegralNanosecondsUTC, _SecondsNanos
28
28
 
29
+ if TYPE_CHECKING:
30
+ from nominal.core.asset import Asset
31
+ from nominal.core.checklist import Checklist
32
+ from nominal.core.run import Run
33
+
29
34
 
30
35
  @dataclass(frozen=True)
31
36
  class DataReview(HasRid):
@@ -66,8 +71,10 @@ class DataReview(HasRid):
66
71
  _clients=clients,
67
72
  )
68
73
 
69
- def get_checklist(self) -> checklist.Checklist:
70
- return checklist.Checklist._from_conjure(
74
+ def get_checklist(self) -> "Checklist":
75
+ from nominal.core.checklist import Checklist
76
+
77
+ return Checklist._from_conjure(
71
78
  self._clients,
72
79
  self._clients.checklist.get(self._clients.auth_header, self.checklist_rid, commit=self.checklist_commit),
73
80
  )
@@ -83,7 +90,7 @@ class DataReview(HasRid):
83
90
  "use 'nominal.core.DataReview.get_events()' instead",
84
91
  )
85
92
 
86
- def get_events(self) -> Sequence[event.Event]:
93
+ def get_events(self) -> Sequence[Event]:
87
94
  """Retrieves the list of events for the data review."""
88
95
  data_review_response = self._clients.datareview.get(self._clients.auth_header, self.rid).check_evaluations
89
96
  all_event_rids = [
@@ -93,7 +100,7 @@ class DataReview(HasRid):
93
100
  for event_rid in check.state._generated_alerts.event_rids
94
101
  ]
95
102
  event_response = self._clients.event.batch_get_events(self._clients.auth_header, all_event_rids)
96
- return [event.Event._from_conjure(self._clients, data_review_event) for data_review_event in event_response]
103
+ return [Event._from_conjure(self._clients, data_review_event) for data_review_event in event_response]
97
104
 
98
105
  def reload(self) -> DataReview:
99
106
  """Reloads the data review from the server."""
@@ -137,7 +144,7 @@ class CheckViolation:
137
144
  name: str
138
145
  start: IntegralNanosecondsUTC
139
146
  end: IntegralNanosecondsUTC | None
140
- priority: checklist.Priority | None
147
+ priority: Priority | None
141
148
 
142
149
  @classmethod
143
150
  def _from_conjure(cls, check_alert: scout_datareview_api.CheckAlert) -> CheckViolation:
@@ -147,7 +154,7 @@ class CheckViolation:
147
154
  name=check_alert.name,
148
155
  start=_SecondsNanos.from_api(check_alert.start).to_nanoseconds(),
149
156
  end=_SecondsNanos.from_api(check_alert.end).to_nanoseconds() if check_alert.end is not None else None,
150
- priority=checklist._conjure_priority_to_priority(check_alert.priority)
157
+ priority=_conjure_priority_to_priority(check_alert.priority)
151
158
  if check_alert.priority is not scout_api.Priority.UNKNOWN
152
159
  else None,
153
160
  )
@@ -192,7 +199,7 @@ class DataReviewBuilder:
192
199
  def execute_checklist(
193
200
  self,
194
201
  run: str | Run,
195
- checklist: str | checklist.Checklist,
202
+ checklist: str | Checklist,
196
203
  *,
197
204
  commit: str | None = None,
198
205
  asset: str | Asset | None = None,
@@ -273,3 +280,17 @@ def poll_until_completed(
273
280
  data_reviews: Sequence[DataReview], interval: timedelta = timedelta(seconds=2)
274
281
  ) -> Sequence[DataReview]:
275
282
  return [review.poll_for_completion(interval) for review in data_reviews]
283
+
284
+
285
+ def _iter_search_data_reviews(
286
+ clients: DataReview._Clients,
287
+ assets: Sequence[str] | None = None,
288
+ runs: Sequence[str] | None = None,
289
+ ) -> Iterable[DataReview]:
290
+ for review in search_data_reviews_paginated(
291
+ clients.datareview,
292
+ clients.auth_header,
293
+ assets=assets,
294
+ runs=runs,
295
+ ):
296
+ yield DataReview._from_conjure(clients, review)
@@ -280,6 +280,8 @@ class IngestStatus(Enum):
280
280
  SUCCESS = "SUCCESS"
281
281
  IN_PROGRESS = "IN_PROGRESS"
282
282
  FAILED = "FAILED"
283
+ DELETION_IN_PROGRESS = "DELETION_IN_PROGRESS"
284
+ DELETED = "DELETED"
283
285
 
284
286
  @classmethod
285
287
  def _from_conjure(cls, status: api.IngestStatusV2) -> IngestStatus:
@@ -289,6 +291,10 @@ class IngestStatus(Enum):
289
291
  return cls.IN_PROGRESS
290
292
  elif status.error is not None:
291
293
  return cls.FAILED
294
+ elif status.deletion_in_progress is not None:
295
+ return cls.DELETION_IN_PROGRESS
296
+ elif status.deleted is not None:
297
+ return cls.DELETED
292
298
  raise ValueError(f"Unknown ingest status: {status.type}")
293
299
 
294
300
 
@@ -3,16 +3,17 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime, timedelta
5
5
  from types import MappingProxyType
6
- from typing import Iterable, Mapping, Protocol, Sequence, cast
6
+ from typing import TYPE_CHECKING, Iterable, Mapping, Protocol, Sequence, cast
7
7
 
8
8
  from nominal_api import (
9
+ event,
10
+ scout,
9
11
  scout_asset_api,
12
+ scout_assets,
10
13
  scout_run_api,
11
14
  )
12
15
  from typing_extensions import Self
13
16
 
14
- from nominal.core import asset as core_asset
15
- from nominal.core._clientsbunch import HasScoutParams
16
17
  from nominal.core._event_types import EventType
17
18
  from nominal.core._utils.api_tools import (
18
19
  HasRid,
@@ -20,16 +21,20 @@ from nominal.core._utils.api_tools import (
20
21
  LinkDict,
21
22
  RefreshableMixin,
22
23
  create_links,
24
+ filter_scopes,
23
25
  rid_from_instance_or_string,
24
26
  )
25
- from nominal.core.asset import _filter_scopes
26
27
  from nominal.core.attachment import Attachment, _iter_get_attachments
27
28
  from nominal.core.connection import Connection, _get_connections
28
29
  from nominal.core.dataset import Dataset, _DatasetWrapper, _get_datasets
30
+ from nominal.core.datasource import DataSource
29
31
  from nominal.core.event import Event, _create_event
30
32
  from nominal.core.video import Video, _get_video
31
33
  from nominal.ts import IntegralNanosecondsDuration, IntegralNanosecondsUTC, _SecondsNanos, _to_api_duration
32
34
 
35
+ if TYPE_CHECKING:
36
+ from nominal.core.asset import Asset
37
+
33
38
 
34
39
  @dataclass(frozen=True)
35
40
  class Run(HasRid, RefreshableMixin[scout_run_api.Run], _DatasetWrapper):
@@ -48,11 +53,17 @@ class Run(HasRid, RefreshableMixin[scout_run_api.Run], _DatasetWrapper):
48
53
  _clients: _Clients = field(repr=False)
49
54
 
50
55
  class _Clients(
51
- core_asset.Asset._Clients,
52
- HasScoutParams,
56
+ Attachment._Clients,
57
+ DataSource._Clients,
58
+ Video._Clients,
53
59
  Protocol,
54
60
  ):
55
- pass
61
+ @property
62
+ def assets(self) -> scout_assets.AssetService: ...
63
+ @property
64
+ def event(self) -> event.EventService: ...
65
+ @property
66
+ def run(self) -> scout.RunService: ...
56
67
 
57
68
  @property
58
69
  def nominal_url(self) -> str:
@@ -105,7 +116,7 @@ class Run(HasRid, RefreshableMixin[scout_run_api.Run], _DatasetWrapper):
105
116
  if len(api_run.assets) > 1:
106
117
  raise RuntimeError("Can't retrieve dataset scopes on multi-asset runs")
107
118
 
108
- return _filter_scopes(api_run.asset_data_scopes, "dataset")
119
+ return filter_scopes(api_run.asset_data_scopes, "dataset")
109
120
 
110
121
  def _list_datasource_rids(
111
122
  self, datasource_type: str | None = None, property_name: str | None = None
@@ -350,13 +361,16 @@ class Run(HasRid, RefreshableMixin[scout_run_api.Run], _DatasetWrapper):
350
361
  """List a sequence of Attachments associated with this Run."""
351
362
  return list(self._iter_list_attachments())
352
363
 
353
- def _iter_list_assets(self) -> Iterable[core_asset.Asset]:
364
+ def _iter_list_assets(self) -> Iterable["Asset"]:
365
+ from nominal.core.asset import Asset
366
+
367
+ clients = cast(Asset._Clients, self._clients)
354
368
  run = self._get_latest_api()
355
369
  assets = self._clients.assets.get_assets(self._clients.auth_header, run.assets)
356
370
  for a in assets.values():
357
- yield core_asset.Asset._from_conjure(self._clients, a)
371
+ yield Asset._from_conjure(clients, a)
358
372
 
359
- def list_assets(self) -> Sequence[core_asset.Asset]:
373
+ def list_assets(self) -> Sequence["Asset"]:
360
374
  """List assets associated with this run."""
361
375
  return list(self._iter_list_assets())
362
376
 
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, Protocol
4
+
5
+ from nominal_api import scout_checklistexecution_api
6
+
7
+ from nominal.core._clientsbunch import HasScoutParams
8
+ from nominal.core._utils.pagination_tools import (
9
+ list_streaming_checklists_for_asset_paginated,
10
+ list_streaming_checklists_paginated,
11
+ )
12
+
13
+
14
+ class _Clients(HasScoutParams, Protocol):
15
+ @property
16
+ def checklist_execution(self) -> scout_checklistexecution_api.ChecklistExecutionService: ...
17
+
18
+
19
+ def _iter_list_streaming_checklists(
20
+ clients: _Clients,
21
+ asset_rid: str | None = None,
22
+ ) -> Iterable[str]:
23
+ if asset_rid is None:
24
+ return list_streaming_checklists_paginated(clients.checklist_execution, clients.auth_header)
25
+ return list_streaming_checklists_for_asset_paginated(clients.checklist_execution, clients.auth_header, asset_rid)