nominal 1.101.0__tar.gz → 1.103.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.
- {nominal-1.101.0 → nominal-1.103.0}/.gitignore +7 -0
- {nominal-1.101.0 → nominal-1.103.0}/CHANGELOG.md +23 -0
- {nominal-1.101.0 → nominal-1.103.0}/PKG-INFO +1 -1
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/__init__.py +3 -1
- nominal-1.103.0/nominal/core/_event_types.py +100 -0
- nominal-1.103.0/nominal/core/_types.py +6 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/query_tools.py +52 -1
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/asset.py +87 -41
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/attachment.py +3 -1
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/client.py +24 -52
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/connection.py +3 -3
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/dataset.py +17 -16
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/dataset_file.py +5 -2
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/datasource.py +4 -4
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/event.py +9 -82
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/filetype.py +7 -5
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/run.py +6 -3
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/video.py +3 -2
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/migration/migration_utils.py +189 -10
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/rust_streaming/rust_write_stream.py +3 -2
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/video_processing/video_conversion.py +5 -2
- {nominal-1.101.0 → nominal-1.103.0}/nominal/nominal.py +9 -8
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/tdms/_tdms.py +4 -3
- {nominal-1.101.0 → nominal-1.103.0}/pyproject.toml +1 -1
- {nominal-1.101.0 → nominal-1.103.0}/LICENSE +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/README.md +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/__main__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/README.md +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/dataclass_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/deprecation_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/iterator_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/streaming_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/_utils/timing_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/__main__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/attachment.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/auth.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/config.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/dataset.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/download.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/mis.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/run.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/util/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/util/click_log_handler.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/util/global_decorators.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/cli/util/verify_connection.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/config/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/config/_config.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_clientsbunch.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_constants.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_stream/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_stream/batch_processor.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_stream/batch_processor_proto.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_stream/write_stream.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_stream/write_stream_base.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/README.md +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/api_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/multipart.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/multipart_downloader.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/networking.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/pagination_tools.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/_utils/queueing.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/bounds.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/channel.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/checklist.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/containerized_extractors.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/data_review.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/exceptions.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/log.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/secret.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/unit.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/user.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/video_file.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/workbook.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/workbook_template.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/core/workspace.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/exceptions/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/README.md +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/_buckets.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/_enum_expr_impls.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/_numeric_expr_impls.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/_range_expr_impls.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/exprs.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/compute/dsl/params.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/logging/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/logging/click_log_handler.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/logging/nominal_log_handler.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/logging/rich_log_handler.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/migration/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/rust_streaming/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/stream_v2/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/stream_v2/_serializer.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/stream_v2/_write_stream.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/video_processing/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/experimental/video_processing/resolution.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/py.typed +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/matlab/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/matlab/_matlab.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/pandas/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/pandas/_pandas.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/polars/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/polars/polars_export_handler.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/thirdparty/tdms/__init__.py +0 -0
- {nominal-1.101.0 → nominal-1.103.0}/nominal/ts/__init__.py +0 -0
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.103.0](https://github.com/nominal-io/nominal-client/compare/v1.102.0...v1.103.0) (2026-01-06)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add clone/copy run ([#563](https://github.com/nominal-io/nominal-client/issues/563)) ([276c511](https://github.com/nominal-io/nominal-client/commit/276c511d10c73b9f80ea2efed32d4047fe33795b))
|
|
9
|
+
* universally support paths and strings, and add PathLike alias ([#566](https://github.com/nominal-io/nominal-client/issues/566)) ([860dd3d](https://github.com/nominal-io/nominal-client/commit/860dd3d67456d269356b66b5434b904647e5a2ff))
|
|
10
|
+
|
|
11
|
+
## [1.102.0](https://github.com/nominal-io/nominal-client/compare/v1.101.0...v1.102.0) (2026-01-05)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* add run creation on an asset ([#558](https://github.com/nominal-io/nominal-client/issues/558)) ([9c0718a](https://github.com/nominal-io/nominal-client/commit/9c0718ae2706d4192087fb787e99f76d702a18c9))
|
|
17
|
+
* add util to search events by origin type and add clone/copy events ([#555](https://github.com/nominal-io/nominal-client/issues/555)) ([bc427d6](https://github.com/nominal-io/nominal-client/commit/bc427d6b97a777e6802dc8139c8d389582558a77))
|
|
18
|
+
* allow unarchiving a run ([#560](https://github.com/nominal-io/nominal-client/issues/560)) ([0221a56](https://github.com/nominal-io/nominal-client/commit/0221a56c73d133581567cc5183fda237607d79b5))
|
|
19
|
+
* clean up and deprecate old video creation methods in NominalClient ([#561](https://github.com/nominal-io/nominal-client/issues/561)) ([e61919c](https://github.com/nominal-io/nominal-client/commit/e61919ca338a5e0de818471431d750246d87977c))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* update how workspaces are selected in migration_utils ([#559](https://github.com/nominal-io/nominal-client/issues/559)) ([4379121](https://github.com/nominal-io/nominal-client/commit/4379121f44411eed19248ee16540672bbed743a0))
|
|
25
|
+
|
|
3
26
|
## [1.101.0](https://github.com/nominal-io/nominal-client/compare/v1.100.0...v1.101.0) (2025-12-23)
|
|
4
27
|
|
|
5
28
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from nominal.core._event_types import EventType, SearchEventOriginType
|
|
1
2
|
from nominal.core._stream.write_stream import WriteStream
|
|
2
3
|
from nominal.core._utils.api_tools import LinkDict
|
|
3
4
|
from nominal.core.asset import Asset
|
|
@@ -19,7 +20,7 @@ from nominal.core.data_review import CheckViolation, DataReview, DataReviewBuild
|
|
|
19
20
|
from nominal.core.dataset import Dataset, poll_until_ingestion_completed
|
|
20
21
|
from nominal.core.dataset_file import DatasetFile, IngestWaitType, as_files_ingested, wait_for_files_to_ingest
|
|
21
22
|
from nominal.core.datasource import DataSource
|
|
22
|
-
from nominal.core.event import Event
|
|
23
|
+
from nominal.core.event import Event
|
|
23
24
|
from nominal.core.filetype import FileType, FileTypes
|
|
24
25
|
from nominal.core.log import LogPoint
|
|
25
26
|
from nominal.core.run import Run
|
|
@@ -60,6 +61,7 @@ __all__ = [
|
|
|
60
61
|
"NominalClient",
|
|
61
62
|
"poll_until_ingestion_completed",
|
|
62
63
|
"Run",
|
|
64
|
+
"SearchEventOriginType",
|
|
63
65
|
"Secret",
|
|
64
66
|
"TagDetails",
|
|
65
67
|
"TimestampMetadata",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Iterable, NamedTuple
|
|
5
|
+
|
|
6
|
+
from nominal_api import event
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EventType(Enum):
|
|
10
|
+
INFO = "INFO"
|
|
11
|
+
FLAG = "FLAG"
|
|
12
|
+
ERROR = "ERROR"
|
|
13
|
+
SUCCESS = "SUCCESS"
|
|
14
|
+
UNKNOWN = "UNKNOWN"
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_api_event_type(cls, event: event.EventType) -> EventType:
|
|
18
|
+
if event.name == "INFO":
|
|
19
|
+
return cls.INFO
|
|
20
|
+
elif event.name == "FLAG":
|
|
21
|
+
return cls.FLAG
|
|
22
|
+
elif event.name == "ERROR":
|
|
23
|
+
return cls.ERROR
|
|
24
|
+
elif event.name == "SUCCESS":
|
|
25
|
+
return cls.SUCCESS
|
|
26
|
+
else:
|
|
27
|
+
return cls.UNKNOWN
|
|
28
|
+
|
|
29
|
+
def _to_api_event_type(self) -> event.EventType:
|
|
30
|
+
if self.name == "INFO":
|
|
31
|
+
return event.EventType.INFO
|
|
32
|
+
elif self.name == "FLAG":
|
|
33
|
+
return event.EventType.FLAG
|
|
34
|
+
elif self.name == "ERROR":
|
|
35
|
+
return event.EventType.ERROR
|
|
36
|
+
elif self.name == "SUCCESS":
|
|
37
|
+
return event.EventType.SUCCESS
|
|
38
|
+
else:
|
|
39
|
+
return event.EventType.UNKNOWN
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EventCreationType(Enum):
|
|
43
|
+
MANUAL = "MANUAL"
|
|
44
|
+
BY_EXTERNAL_RESOURCE = "BY_EXTERNAL_RESOURCE"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SearchEventOriginType(NamedTuple):
|
|
48
|
+
name: str
|
|
49
|
+
creation_type: EventCreationType
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_api_origin_type(cls, event: event.SearchEventOriginType) -> SearchEventOriginType:
|
|
53
|
+
if event.name == "WORKBOOK":
|
|
54
|
+
return SearchEventOriginTypes.WORKBOOK
|
|
55
|
+
elif event.name == "TEMPLATE":
|
|
56
|
+
return SearchEventOriginTypes.TEMPLATE
|
|
57
|
+
elif event.name == "API":
|
|
58
|
+
return SearchEventOriginTypes.API
|
|
59
|
+
elif event.name == "DATA_REVIEW":
|
|
60
|
+
return SearchEventOriginTypes.DATA_REVIEW
|
|
61
|
+
elif event.name == "PROCEDURE":
|
|
62
|
+
return SearchEventOriginTypes.PROCEDURE
|
|
63
|
+
elif event.name == "STREAMING_CHECKLIST":
|
|
64
|
+
return SearchEventOriginTypes.STREAMING_CHECKLIST
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError(f"Unexpected Event Origin {event.name}")
|
|
67
|
+
|
|
68
|
+
def _to_api_search_event_origin_type(self) -> event.SearchEventOriginType:
|
|
69
|
+
if self.name == "WORKBOOK":
|
|
70
|
+
return event.SearchEventOriginType.WORKBOOK
|
|
71
|
+
elif self.name == "TEMPLATE":
|
|
72
|
+
return event.SearchEventOriginType.TEMPLATE
|
|
73
|
+
elif self.name == "API":
|
|
74
|
+
return event.SearchEventOriginType.API
|
|
75
|
+
elif self.name == "DATA_REVIEW":
|
|
76
|
+
return event.SearchEventOriginType.DATA_REVIEW
|
|
77
|
+
elif self.name == "PROCEDURE":
|
|
78
|
+
return event.SearchEventOriginType.PROCEDURE
|
|
79
|
+
elif self.name == "STREAMING_CHECKLIST":
|
|
80
|
+
return event.SearchEventOriginType.STREAMING_CHECKLIST
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError(f"Unexpected Event Origin {self.name}")
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get_manual_origin_types(cls) -> Iterable[SearchEventOriginType]:
|
|
86
|
+
"""Return all origin types that are manually created."""
|
|
87
|
+
return [
|
|
88
|
+
origin_type
|
|
89
|
+
for origin_type in SearchEventOriginTypes.__dict__.values()
|
|
90
|
+
if isinstance(origin_type, SearchEventOriginType) and origin_type.creation_type == EventCreationType.MANUAL
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SearchEventOriginTypes:
|
|
95
|
+
WORKBOOK = SearchEventOriginType("WORKBOOK", EventCreationType.MANUAL)
|
|
96
|
+
TEMPLATE = SearchEventOriginType("TEMPLATE", EventCreationType.MANUAL)
|
|
97
|
+
API = SearchEventOriginType("API", EventCreationType.MANUAL)
|
|
98
|
+
DATA_REVIEW = SearchEventOriginType("DATA_REVIEW", EventCreationType.BY_EXTERNAL_RESOURCE)
|
|
99
|
+
PROCEDURE = SearchEventOriginType("PROCEDURE", EventCreationType.BY_EXTERNAL_RESOURCE)
|
|
100
|
+
STREAMING_CHECKLIST = SearchEventOriginType("STREAMING_CHECKLIST", EventCreationType.BY_EXTERNAL_RESOURCE)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from typing import Mapping, Sequence
|
|
4
|
+
from typing import Iterable, Mapping, Sequence
|
|
5
5
|
|
|
6
6
|
from nominal_api import (
|
|
7
7
|
api,
|
|
8
8
|
authentication_api,
|
|
9
|
+
event,
|
|
9
10
|
ingest_api,
|
|
10
11
|
scout_asset_api,
|
|
11
12
|
scout_catalog,
|
|
@@ -350,3 +351,53 @@ def create_search_workbook_templates_query(
|
|
|
350
351
|
queries.append(scout_template_api.SearchTemplatesQuery(is_published=published))
|
|
351
352
|
|
|
352
353
|
return scout_template_api.SearchTemplatesQuery(and_=queries)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _create_search_events_query( # noqa: PLR0912
|
|
357
|
+
search_text: str | None = None,
|
|
358
|
+
after: str | datetime | IntegralNanosecondsUTC | None = None,
|
|
359
|
+
before: str | datetime | IntegralNanosecondsUTC | None = None,
|
|
360
|
+
asset_rids: Iterable[str] | None = None,
|
|
361
|
+
labels: Iterable[str] | None = None,
|
|
362
|
+
properties: Mapping[str, str] | None = None,
|
|
363
|
+
created_by_rid: str | None = None,
|
|
364
|
+
workbook_rid: str | None = None,
|
|
365
|
+
data_review_rid: str | None = None,
|
|
366
|
+
assignee_rid: str | None = None,
|
|
367
|
+
event_type: event.EventType | None = None,
|
|
368
|
+
origin_types: Iterable[event.SearchEventOriginType] | None = None,
|
|
369
|
+
workspace_rid: str | None = None,
|
|
370
|
+
) -> event.SearchQuery:
|
|
371
|
+
queries = []
|
|
372
|
+
if search_text is not None:
|
|
373
|
+
queries.append(event.SearchQuery(search_text=search_text))
|
|
374
|
+
if after is not None:
|
|
375
|
+
queries.append(event.SearchQuery(after=_SecondsNanos.from_flexible(after).to_api()))
|
|
376
|
+
if before is not None:
|
|
377
|
+
queries.append(event.SearchQuery(before=_SecondsNanos.from_flexible(before).to_api()))
|
|
378
|
+
if asset_rids:
|
|
379
|
+
for asset in asset_rids:
|
|
380
|
+
queries.append(event.SearchQuery(asset=asset))
|
|
381
|
+
if labels:
|
|
382
|
+
for label in labels:
|
|
383
|
+
queries.append(event.SearchQuery(label=label))
|
|
384
|
+
if properties:
|
|
385
|
+
for name, value in properties.items():
|
|
386
|
+
queries.append(event.SearchQuery(property=api.Property(name=name, value=value)))
|
|
387
|
+
if created_by_rid:
|
|
388
|
+
queries.append(event.SearchQuery(created_by=created_by_rid))
|
|
389
|
+
if workbook_rid is not None:
|
|
390
|
+
queries.append(event.SearchQuery(workbook=workbook_rid))
|
|
391
|
+
if data_review_rid is not None:
|
|
392
|
+
queries.append(event.SearchQuery(data_review=data_review_rid))
|
|
393
|
+
if assignee_rid is not None:
|
|
394
|
+
queries.append(event.SearchQuery(assignee=assignee_rid))
|
|
395
|
+
if event_type is not None:
|
|
396
|
+
queries.append(event.SearchQuery(event_type=event_type))
|
|
397
|
+
if origin_types is not None:
|
|
398
|
+
origin_type_filter = event.OriginTypesFilter(api.SetOperator.OR, list(origin_types))
|
|
399
|
+
queries.append(event.SearchQuery(origin_types=origin_type_filter))
|
|
400
|
+
if workspace_rid is not None:
|
|
401
|
+
queries.append(event.SearchQuery(workspace=workspace_rid))
|
|
402
|
+
|
|
403
|
+
return event.SearchQuery(and_=queries)
|
|
@@ -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, Literal, Mapping, Protocol, Sequence, TypeAlias
|
|
8
8
|
|
|
9
9
|
from nominal_api import (
|
|
10
10
|
event,
|
|
@@ -16,13 +16,21 @@ from nominal_api import (
|
|
|
16
16
|
from typing_extensions import Self
|
|
17
17
|
|
|
18
18
|
from nominal.core._clientsbunch import HasScoutParams
|
|
19
|
-
from nominal.core.
|
|
19
|
+
from nominal.core._event_types import EventType, SearchEventOriginType
|
|
20
|
+
from nominal.core._utils.api_tools import (
|
|
21
|
+
HasRid,
|
|
22
|
+
Link,
|
|
23
|
+
LinkDict,
|
|
24
|
+
RefreshableMixin,
|
|
25
|
+
create_links,
|
|
26
|
+
rid_from_instance_or_string,
|
|
27
|
+
)
|
|
20
28
|
from nominal.core._utils.pagination_tools import search_runs_by_asset_paginated
|
|
21
29
|
from nominal.core.attachment import Attachment, _iter_get_attachments
|
|
22
30
|
from nominal.core.connection import Connection, _get_connections
|
|
23
31
|
from nominal.core.dataset import Dataset, _create_dataset, _DatasetWrapper, _get_datasets
|
|
24
32
|
from nominal.core.datasource import DataSource
|
|
25
|
-
from nominal.core.event import Event,
|
|
33
|
+
from nominal.core.event import Event, _create_event, _search_events
|
|
26
34
|
from nominal.core.video import Video, _create_video, _get_video
|
|
27
35
|
from nominal.ts import IntegralNanosecondsDuration, IntegralNanosecondsUTC, _SecondsNanos
|
|
28
36
|
|
|
@@ -38,6 +46,14 @@ def _filter_scopes(
|
|
|
38
46
|
return [scope for scope in scopes if scope.data_source.type.lower() == scope_type]
|
|
39
47
|
|
|
40
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
|
+
|
|
41
57
|
@dataclass(frozen=True)
|
|
42
58
|
class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
43
59
|
rid: str
|
|
@@ -80,13 +96,9 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
80
96
|
def _list_dataset_scopes(self) -> Sequence[scout_asset_api.DataScope]:
|
|
81
97
|
return _filter_scopes(self._get_latest_api().data_scopes, "dataset")
|
|
82
98
|
|
|
83
|
-
def
|
|
99
|
+
def _scope_rids(self, scope_type: ScopeTypeSpecifier) -> Mapping[str, str]:
|
|
84
100
|
asset = self._get_latest_api()
|
|
85
|
-
return
|
|
86
|
-
scope.data_scope_name: cast(str, getattr(scope.data_source, stype))
|
|
87
|
-
for scope in asset.data_scopes
|
|
88
|
-
if scope.data_source.type.lower() == stype
|
|
89
|
-
}
|
|
101
|
+
return _filter_scope_rids(asset.data_scopes, scope_type)
|
|
90
102
|
|
|
91
103
|
def update(
|
|
92
104
|
self,
|
|
@@ -153,23 +165,25 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
153
165
|
"""
|
|
154
166
|
return (*self.list_datasets(), *self.list_connections(), *self.list_videos())
|
|
155
167
|
|
|
156
|
-
def
|
|
168
|
+
def remove_data_scopes(
|
|
157
169
|
self,
|
|
158
170
|
*,
|
|
159
|
-
|
|
160
|
-
|
|
171
|
+
names: Sequence[str] | None = None,
|
|
172
|
+
scopes: Sequence[ScopeType | str] | None = None,
|
|
161
173
|
) -> None:
|
|
162
|
-
|
|
163
|
-
data_sources = data_sources or []
|
|
164
|
-
|
|
165
|
-
if isinstance(data_sources, str):
|
|
166
|
-
raise RuntimeError("Expect `data_sources` to be a sequence, not a string")
|
|
174
|
+
"""Remove data scopes from this asset.
|
|
167
175
|
|
|
168
|
-
|
|
176
|
+
Args:
|
|
177
|
+
names: Names of datascopes to remove
|
|
178
|
+
scopes: Rids or instances of scope types (dataset, video, connection) to remove.
|
|
179
|
+
"""
|
|
180
|
+
scope_names_to_remove = names or []
|
|
181
|
+
data_scopes_to_remove = scopes or []
|
|
169
182
|
|
|
183
|
+
scope_rids_to_remove = {rid_from_instance_or_string(ds) for ds in data_scopes_to_remove}
|
|
170
184
|
conjure_asset = self._get_latest_api()
|
|
171
185
|
|
|
172
|
-
|
|
186
|
+
data_scopes_to_keep = [
|
|
173
187
|
scout_asset_api.CreateAssetDataScope(
|
|
174
188
|
data_scope_name=ds.data_scope_name,
|
|
175
189
|
data_source=ds.data_source,
|
|
@@ -177,31 +191,21 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
177
191
|
offset=ds.offset,
|
|
178
192
|
)
|
|
179
193
|
for ds in conjure_asset.data_scopes
|
|
180
|
-
if ds.data_scope_name not in
|
|
181
|
-
and (
|
|
194
|
+
if ds.data_scope_name not in scope_names_to_remove
|
|
195
|
+
and all(
|
|
196
|
+
rid not in scope_rids_to_remove
|
|
197
|
+
for rid in (ds.data_source.dataset, ds.data_source.connection, ds.data_source.video)
|
|
198
|
+
)
|
|
182
199
|
]
|
|
183
200
|
|
|
184
|
-
|
|
201
|
+
updated_asset = self._clients.assets.update_asset(
|
|
185
202
|
self._clients.auth_header,
|
|
186
203
|
scout_asset_api.UpdateAssetRequest(
|
|
187
|
-
data_scopes=
|
|
204
|
+
data_scopes=data_scopes_to_keep,
|
|
188
205
|
),
|
|
189
206
|
self.rid,
|
|
190
207
|
)
|
|
191
|
-
self._refresh_from_api(
|
|
192
|
-
|
|
193
|
-
def remove_data_scopes(
|
|
194
|
-
self,
|
|
195
|
-
*,
|
|
196
|
-
names: Sequence[str] | None = None,
|
|
197
|
-
scopes: Sequence[ScopeType | str] | None = None,
|
|
198
|
-
) -> None:
|
|
199
|
-
"""Remove data scopes from this asset.
|
|
200
|
-
|
|
201
|
-
`names` are scope names.
|
|
202
|
-
`scopes` are rids or scope objects.
|
|
203
|
-
"""
|
|
204
|
-
self._remove_data_sources(data_scope_names=names, data_sources=scopes)
|
|
208
|
+
self._refresh_from_api(updated_asset)
|
|
205
209
|
|
|
206
210
|
def add_dataset(
|
|
207
211
|
self,
|
|
@@ -382,6 +386,46 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
382
386
|
labels=labels,
|
|
383
387
|
)
|
|
384
388
|
|
|
389
|
+
def create_run(
|
|
390
|
+
self,
|
|
391
|
+
name: str,
|
|
392
|
+
start: datetime.datetime | IntegralNanosecondsUTC,
|
|
393
|
+
end: datetime.datetime | IntegralNanosecondsUTC | None,
|
|
394
|
+
*,
|
|
395
|
+
description: str | None = None,
|
|
396
|
+
properties: Mapping[str, str] | None = None,
|
|
397
|
+
labels: Sequence[str] = (),
|
|
398
|
+
links: Sequence[str | Link | LinkDict] = (),
|
|
399
|
+
attachments: Iterable[Attachment] | Iterable[str] = (),
|
|
400
|
+
) -> Run:
|
|
401
|
+
"""Create a run associated with this Asset for a given span of time.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
name: Name of the run.
|
|
405
|
+
start: Starting timestamp of the run.
|
|
406
|
+
end: Ending timestamp of the run, or None for an unbounded run.
|
|
407
|
+
description: Optionally, a human readable description of the run to create.
|
|
408
|
+
properties: Key-value pairs to use as properties on the created run.
|
|
409
|
+
labels: Sequence of labels to use on the created run.
|
|
410
|
+
links: Link metadata to add to the created run.
|
|
411
|
+
attachments: Attachments to associate with the created run.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Returns the created run
|
|
415
|
+
"""
|
|
416
|
+
return _create_run(
|
|
417
|
+
self._clients,
|
|
418
|
+
name=name,
|
|
419
|
+
start=start,
|
|
420
|
+
end=end,
|
|
421
|
+
description=description,
|
|
422
|
+
properties=properties,
|
|
423
|
+
labels=labels,
|
|
424
|
+
links=links,
|
|
425
|
+
attachments=attachments,
|
|
426
|
+
asset_rids=[self.rid],
|
|
427
|
+
)
|
|
428
|
+
|
|
385
429
|
def get_dataset(self, data_scope_name: str) -> Dataset:
|
|
386
430
|
"""Retrieve a dataset by data scope name, or raise ValueError if one is not found."""
|
|
387
431
|
dataset = self.get_data_scope(data_scope_name)
|
|
@@ -410,7 +454,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
410
454
|
"""List the datasets associated with this asset.
|
|
411
455
|
Returns (data_scope_name, dataset) pairs for each dataset.
|
|
412
456
|
"""
|
|
413
|
-
scope_rid = self.
|
|
457
|
+
scope_rid = self._scope_rids(scope_type="dataset")
|
|
414
458
|
if not scope_rid:
|
|
415
459
|
return []
|
|
416
460
|
|
|
@@ -428,7 +472,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
428
472
|
"""List the connections associated with this asset.
|
|
429
473
|
Returns (data_scope_name, connection) pairs for each connection.
|
|
430
474
|
"""
|
|
431
|
-
scope_rid = self.
|
|
475
|
+
scope_rid = self._scope_rids(scope_type="connection")
|
|
432
476
|
connections_meta = _get_connections(self._clients, list(scope_rid.values()))
|
|
433
477
|
return [
|
|
434
478
|
(scope, Connection._from_conjure(self._clients, connection))
|
|
@@ -439,7 +483,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
439
483
|
"""List the videos associated with this asset.
|
|
440
484
|
Returns (data_scope_name, dataset) pairs for each video.
|
|
441
485
|
"""
|
|
442
|
-
scope_rid = self.
|
|
486
|
+
scope_rid = self._scope_rids(scope_type="video")
|
|
443
487
|
return [
|
|
444
488
|
(scope, Video._from_conjure(self._clients, _get_video(self._clients, rid)))
|
|
445
489
|
for (scope, rid) in scope_rid.items()
|
|
@@ -477,6 +521,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
477
521
|
data_review_rid: str | None = None,
|
|
478
522
|
assignee_rid: str | None = None,
|
|
479
523
|
event_type: EventType | None = None,
|
|
524
|
+
origin_types: Iterable[SearchEventOriginType] | None = None,
|
|
480
525
|
) -> Sequence[Event]:
|
|
481
526
|
"""Search for events associated with this Asset. See nominal.core.event._search_events for details."""
|
|
482
527
|
return _search_events(
|
|
@@ -492,6 +537,7 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
492
537
|
data_review_rid=data_review_rid,
|
|
493
538
|
assignee_rid=assignee_rid,
|
|
494
539
|
event_type=event_type,
|
|
540
|
+
origin_types=origin_types,
|
|
495
541
|
)
|
|
496
542
|
|
|
497
543
|
def remove_attachments(self, attachments: Iterable[Attachment] | Iterable[str]) -> None:
|
|
@@ -528,4 +574,4 @@ class Asset(_DatasetWrapper, HasRid, RefreshableMixin[scout_asset_api.Asset]):
|
|
|
528
574
|
|
|
529
575
|
|
|
530
576
|
# Moving to bottom to deal with circular dependencies
|
|
531
|
-
from nominal.core.run import Run # noqa: E402
|
|
577
|
+
from nominal.core.run import Run, _create_run # noqa: E402
|
|
@@ -10,6 +10,7 @@ from nominal_api import attachments_api
|
|
|
10
10
|
from typing_extensions import Self
|
|
11
11
|
|
|
12
12
|
from nominal.core._clientsbunch import HasScoutParams
|
|
13
|
+
from nominal.core._types import PathLike
|
|
13
14
|
from nominal.core._utils.api_tools import HasRid, RefreshableMixin
|
|
14
15
|
from nominal.ts import IntegralNanosecondsUTC, _SecondsNanos
|
|
15
16
|
|
|
@@ -69,11 +70,12 @@ class Attachment(HasRid, RefreshableMixin[attachments_api.Attachment]):
|
|
|
69
70
|
# this acts like a file-like object in binary-mode.
|
|
70
71
|
return cast(BinaryIO, response)
|
|
71
72
|
|
|
72
|
-
def write(self, path:
|
|
73
|
+
def write(self, path: PathLike, mkdir: bool = True) -> None:
|
|
73
74
|
"""Write an attachment to the filesystem.
|
|
74
75
|
|
|
75
76
|
`path` should be the path you want to save to, i.e. a file, not a directory.
|
|
76
77
|
"""
|
|
78
|
+
path = Path(path)
|
|
77
79
|
if mkdir:
|
|
78
80
|
path.parent.mkdir(exist_ok=True, parents=True)
|
|
79
81
|
with open(path, "wb") as wf:
|
|
@@ -36,6 +36,8 @@ from nominal._utils.deprecation_tools import warn_on_deprecated_argument
|
|
|
36
36
|
from nominal.config import NominalConfig, _config
|
|
37
37
|
from nominal.core._clientsbunch import ClientsBunch
|
|
38
38
|
from nominal.core._constants import DEFAULT_API_BASE_URL
|
|
39
|
+
from nominal.core._event_types import EventType
|
|
40
|
+
from nominal.core._types import PathLike
|
|
39
41
|
from nominal.core._utils.api_tools import (
|
|
40
42
|
Link,
|
|
41
43
|
LinkDict,
|
|
@@ -43,7 +45,6 @@ from nominal.core._utils.api_tools import (
|
|
|
43
45
|
rid_from_instance_or_string,
|
|
44
46
|
)
|
|
45
47
|
from nominal.core._utils.multipart import (
|
|
46
|
-
path_upload_name,
|
|
47
48
|
upload_multipart_io,
|
|
48
49
|
)
|
|
49
50
|
from nominal.core._utils.pagination_tools import (
|
|
@@ -91,8 +92,8 @@ from nominal.core.dataset import (
|
|
|
91
92
|
_get_datasets,
|
|
92
93
|
)
|
|
93
94
|
from nominal.core.datasource import DataSource
|
|
94
|
-
from nominal.core.event import Event,
|
|
95
|
-
from nominal.core.exceptions import NominalConfigError, NominalError,
|
|
95
|
+
from nominal.core.event import Event, _create_event, _search_events
|
|
96
|
+
from nominal.core.exceptions import NominalConfigError, NominalError, NominalMethodRemovedError
|
|
96
97
|
from nominal.core.filetype import FileType, FileTypes
|
|
97
98
|
from nominal.core.run import Run, _create_run
|
|
98
99
|
from nominal.core.secret import Secret
|
|
@@ -732,7 +733,7 @@ class NominalClient:
|
|
|
732
733
|
|
|
733
734
|
return dataset
|
|
734
735
|
|
|
735
|
-
def
|
|
736
|
+
def create_video(
|
|
736
737
|
self,
|
|
737
738
|
name: str,
|
|
738
739
|
*,
|
|
@@ -762,6 +763,8 @@ class NominalClient:
|
|
|
762
763
|
)
|
|
763
764
|
return Video._from_conjure(self._clients, response)
|
|
764
765
|
|
|
766
|
+
create_empty_video = create_video
|
|
767
|
+
|
|
765
768
|
def get_video(self, rid: str) -> Video:
|
|
766
769
|
"""Retrieve a video by its RID."""
|
|
767
770
|
response = self._clients.video.get(self._clients.auth_header, rid)
|
|
@@ -851,7 +854,7 @@ class NominalClient:
|
|
|
851
854
|
|
|
852
855
|
def create_attachment(
|
|
853
856
|
self,
|
|
854
|
-
attachment_file:
|
|
857
|
+
attachment_file: PathLike,
|
|
855
858
|
*,
|
|
856
859
|
description: str | None = None,
|
|
857
860
|
properties: Mapping[str, str] | None = None,
|
|
@@ -957,9 +960,13 @@ class NominalClient:
|
|
|
957
960
|
response = self._clients.connection.get_connection(self._clients.auth_header, rid)
|
|
958
961
|
return Connection._from_conjure(self._clients, response)
|
|
959
962
|
|
|
963
|
+
@deprecated(
|
|
964
|
+
"`create_video_from_mcap` is deprecated and will be removed in a future version. "
|
|
965
|
+
"Create a new video with `create_video` and then `add_mcap` to upload a file to the video."
|
|
966
|
+
)
|
|
960
967
|
def create_video_from_mcap(
|
|
961
968
|
self,
|
|
962
|
-
path:
|
|
969
|
+
path: PathLike,
|
|
963
970
|
topic: str,
|
|
964
971
|
name: str | None = None,
|
|
965
972
|
description: str | None = None,
|
|
@@ -977,18 +984,14 @@ class NominalClient:
|
|
|
977
984
|
if name is None:
|
|
978
985
|
name = path.name
|
|
979
986
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
name=name,
|
|
984
|
-
topic=topic,
|
|
985
|
-
file_type=FileTypes.MCAP,
|
|
986
|
-
description=description,
|
|
987
|
-
labels=labels,
|
|
988
|
-
properties=properties,
|
|
989
|
-
file_name=path_upload_name(path, FileTypes.MCAP),
|
|
990
|
-
)
|
|
987
|
+
video = self.create_video(name, description=description, labels=labels, properties=properties)
|
|
988
|
+
video.add_mcap(path, topic, description)
|
|
989
|
+
return video
|
|
991
990
|
|
|
991
|
+
@deprecated(
|
|
992
|
+
"`create_video_from_mcap_io` is deprecated and will be removed in a future version. "
|
|
993
|
+
"Create a new video with `create_video` and then `add_mcap_from_io` to upload a file to the video."
|
|
994
|
+
)
|
|
992
995
|
def create_video_from_mcap_io(
|
|
993
996
|
self,
|
|
994
997
|
mcap: BinaryIO,
|
|
@@ -1007,40 +1010,9 @@ class NominalClient:
|
|
|
1007
1010
|
|
|
1008
1011
|
If name is None, the name of the file will be used.
|
|
1009
1012
|
"""
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if file_name is None:
|
|
1014
|
-
file_name = name
|
|
1015
|
-
|
|
1016
|
-
file_type = FileType(*file_type)
|
|
1017
|
-
s3_path = upload_multipart_io(
|
|
1018
|
-
self._clients.auth_header, self._clients.workspace_rid, mcap, file_name, file_type, self._clients.upload
|
|
1019
|
-
)
|
|
1020
|
-
request = ingest_api.IngestRequest(
|
|
1021
|
-
options=ingest_api.IngestOptions(
|
|
1022
|
-
video=ingest_api.VideoOpts(
|
|
1023
|
-
source=ingest_api.IngestSource(s3=ingest_api.S3IngestSource(s3_path)),
|
|
1024
|
-
target=ingest_api.VideoIngestTarget(
|
|
1025
|
-
new=ingest_api.NewVideoIngestDestination(
|
|
1026
|
-
title=name,
|
|
1027
|
-
description=description,
|
|
1028
|
-
properties={} if properties is None else dict(properties),
|
|
1029
|
-
labels=list(labels),
|
|
1030
|
-
workspace=self._clients.workspace_rid,
|
|
1031
|
-
marking_rids=[],
|
|
1032
|
-
)
|
|
1033
|
-
),
|
|
1034
|
-
timestamp_manifest=scout_video_api.VideoFileTimestampManifest(
|
|
1035
|
-
mcap=scout_video_api.McapTimestampManifest(api.McapChannelLocator(topic=topic))
|
|
1036
|
-
),
|
|
1037
|
-
)
|
|
1038
|
-
)
|
|
1039
|
-
)
|
|
1040
|
-
response = self._clients.ingest.ingest(self._clients.auth_header, request)
|
|
1041
|
-
if response.details.video is None:
|
|
1042
|
-
raise NominalIngestError("error ingesting mcap video: no video created")
|
|
1043
|
-
return self.get_video(response.details.video.video_rid)
|
|
1013
|
+
video = self.create_video(name, description=description, labels=labels, properties=properties)
|
|
1014
|
+
video.add_mcap_from_io(mcap, file_name or name, topic, description, file_type)
|
|
1015
|
+
return video
|
|
1044
1016
|
|
|
1045
1017
|
def create_streaming_connection(
|
|
1046
1018
|
self,
|
|
@@ -1514,7 +1486,7 @@ class NominalClient:
|
|
|
1514
1486
|
properties: A mapping of key-value pairs that must ALL be present on an workbook to be included.
|
|
1515
1487
|
created_by: Searches for workbook templates with the given creator's rid
|
|
1516
1488
|
archived: Searches for workbook templates that are archived if true
|
|
1517
|
-
published: Searches
|
|
1489
|
+
published: Searches for workbook templates that have been published if true
|
|
1518
1490
|
|
|
1519
1491
|
Returns:
|
|
1520
1492
|
All workbook templates which match all of the provided conditions
|