nominal 1.95.0__tar.gz → 1.97.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 (108) hide show
  1. {nominal-1.95.0 → nominal-1.97.0}/CHANGELOG.md +21 -0
  2. {nominal-1.95.0 → nominal-1.97.0}/PKG-INFO +2 -2
  3. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/__init__.py +1 -2
  4. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/auth.py +2 -13
  5. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/dataset.py +2 -2
  6. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/util/click_log_handler.py +6 -3
  7. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/util/global_decorators.py +6 -48
  8. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/multipart.py +43 -19
  9. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/client.py +54 -21
  10. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/data_review.py +5 -2
  11. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/dataset.py +27 -38
  12. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/event.py +1 -6
  13. nominal-1.97.0/nominal/core/exceptions.py +78 -0
  14. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/run.py +148 -148
  15. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/workbook.py +5 -8
  16. {nominal-1.95.0 → nominal-1.97.0}/nominal/nominal.py +4 -3
  17. {nominal-1.95.0 → nominal-1.97.0}/pyproject.toml +2 -2
  18. nominal-1.95.0/nominal/core/exceptions.py +0 -39
  19. {nominal-1.95.0 → nominal-1.97.0}/.gitignore +0 -0
  20. {nominal-1.95.0 → nominal-1.97.0}/LICENSE +0 -0
  21. {nominal-1.95.0 → nominal-1.97.0}/README.md +0 -0
  22. {nominal-1.95.0 → nominal-1.97.0}/nominal/__init__.py +0 -0
  23. {nominal-1.95.0 → nominal-1.97.0}/nominal/__main__.py +0 -0
  24. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/README.md +0 -0
  25. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/__init__.py +0 -0
  26. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/dataclass_tools.py +0 -0
  27. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/deprecation_tools.py +0 -0
  28. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/iterator_tools.py +0 -0
  29. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/streaming_tools.py +0 -0
  30. {nominal-1.95.0 → nominal-1.97.0}/nominal/_utils/timing_tools.py +0 -0
  31. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/__main__.py +0 -0
  32. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/attachment.py +0 -0
  33. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/config.py +0 -0
  34. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/download.py +0 -0
  35. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/mis.py +0 -0
  36. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/run.py +0 -0
  37. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/util/__init__.py +0 -0
  38. {nominal-1.95.0 → nominal-1.97.0}/nominal/cli/util/verify_connection.py +0 -0
  39. {nominal-1.95.0 → nominal-1.97.0}/nominal/config/__init__.py +0 -0
  40. {nominal-1.95.0 → nominal-1.97.0}/nominal/config/_config.py +0 -0
  41. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/__init__.py +0 -0
  42. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_clientsbunch.py +0 -0
  43. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_constants.py +0 -0
  44. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_stream/__init__.py +0 -0
  45. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_stream/batch_processor.py +0 -0
  46. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_stream/batch_processor_proto.py +0 -0
  47. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_stream/write_stream.py +0 -0
  48. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_stream/write_stream_base.py +0 -0
  49. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/README.md +0 -0
  50. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/__init__.py +0 -0
  51. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/api_tools.py +0 -0
  52. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/multipart_downloader.py +0 -0
  53. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/networking.py +0 -0
  54. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/pagination_tools.py +0 -0
  55. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/query_tools.py +0 -0
  56. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/_utils/queueing.py +0 -0
  57. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/asset.py +176 -176
  58. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/attachment.py +0 -0
  59. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/bounds.py +0 -0
  60. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/channel.py +0 -0
  61. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/checklist.py +0 -0
  62. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/connection.py +0 -0
  63. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/containerized_extractors.py +0 -0
  64. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/dataset_file.py +0 -0
  65. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/datasource.py +0 -0
  66. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/filetype.py +0 -0
  67. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/log.py +0 -0
  68. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/secret.py +0 -0
  69. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/unit.py +0 -0
  70. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/user.py +0 -0
  71. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/video.py +0 -0
  72. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/video_file.py +0 -0
  73. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/workbook_template.py +0 -0
  74. {nominal-1.95.0 → nominal-1.97.0}/nominal/core/workspace.py +0 -0
  75. {nominal-1.95.0 → nominal-1.97.0}/nominal/exceptions/__init__.py +0 -0
  76. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/__init__.py +0 -0
  77. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/README.md +0 -0
  78. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/__init__.py +0 -0
  79. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/_buckets.py +0 -0
  80. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/__init__.py +0 -0
  81. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/_enum_expr_impls.py +0 -0
  82. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/_numeric_expr_impls.py +0 -0
  83. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/_range_expr_impls.py +0 -0
  84. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/exprs.py +0 -0
  85. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/compute/dsl/params.py +0 -0
  86. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/logging/__init__.py +0 -0
  87. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/logging/click_log_handler.py +0 -0
  88. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/logging/nominal_log_handler.py +0 -0
  89. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/logging/rich_log_handler.py +0 -0
  90. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/rust_streaming/__init__.py +0 -0
  91. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/rust_streaming/rust_write_stream.py +0 -0
  92. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/stream_v2/__init__.py +0 -0
  93. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/stream_v2/_serializer.py +0 -0
  94. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/stream_v2/_write_stream.py +0 -0
  95. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/video_processing/__init__.py +0 -0
  96. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/video_processing/resolution.py +0 -0
  97. {nominal-1.95.0 → nominal-1.97.0}/nominal/experimental/video_processing/video_conversion.py +0 -0
  98. {nominal-1.95.0 → nominal-1.97.0}/nominal/py.typed +0 -0
  99. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/__init__.py +0 -0
  100. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/matlab/__init__.py +0 -0
  101. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/matlab/_matlab.py +0 -0
  102. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/pandas/__init__.py +0 -0
  103. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/pandas/_pandas.py +0 -0
  104. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/polars/__init__.py +0 -0
  105. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/polars/polars_export_handler.py +0 -0
  106. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/tdms/__init__.py +0 -0
  107. {nominal-1.95.0 → nominal-1.97.0}/nominal/thirdparty/tdms/_tdms.py +0 -0
  108. {nominal-1.95.0 → nominal-1.97.0}/nominal/ts/__init__.py +0 -0
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.97.0](https://github.com/nominal-io/nominal-client/compare/v1.96.0...v1.97.0) (2025-12-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * add create_workbook_template ([#535](https://github.com/nominal-io/nominal-client/issues/535)) ([9c98975](https://github.com/nominal-io/nominal-client/commit/9c989753828c39e417bafed3db2981444132aeac))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add per-part retries during multipart uploads ([#537](https://github.com/nominal-io/nominal-client/issues/537)) ([ffdbc4a](https://github.com/nominal-io/nominal-client/commit/ffdbc4ac6f82562fb9eff3fed4b254b784aa89bc))
14
+
15
+ ## [1.96.0](https://github.com/nominal-io/nominal-client/compare/v1.95.0...v1.96.0) (2025-12-03)
16
+
17
+
18
+ ### Features
19
+
20
+ * add tags to dataflash apis ([#534](https://github.com/nominal-io/nominal-client/issues/534)) ([00cd79f](https://github.com/nominal-io/nominal-client/commit/00cd79fdb84e299a4013b69ce9639cfc75644b32))
21
+ * remove deprecated fields, methods, files ([#525](https://github.com/nominal-io/nominal-client/issues/525)) ([e11175d](https://github.com/nominal-io/nominal-client/commit/e11175de0e303d97d91efa7eaf6ee6010d6b08ef))
22
+ * reorder methods in run and asset class ([#530](https://github.com/nominal-io/nominal-client/issues/530)) ([5813157](https://github.com/nominal-io/nominal-client/commit/58131574bd85a82d36f7e76f18279c8c7cfaf1a8))
23
+
3
24
  ## [1.95.0](https://github.com/nominal-io/nominal-client/compare/v1.94.0...v1.95.0) (2025-11-19)
4
25
 
5
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nominal
3
- Version: 1.95.0
3
+ Version: 1.97.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.982.0
23
+ Requires-Dist: nominal-api==0.1019.0
24
24
  Requires-Dist: nominal-streaming==0.5.8
25
25
  Requires-Dist: openpyxl>=0.0.0
26
26
  Requires-Dist: pandas>=0.0.0
@@ -2,7 +2,7 @@ import importlib.metadata
2
2
 
3
3
  import click
4
4
 
5
- from nominal.cli import attachment, auth, config, dataset, download, mis, run
5
+ from nominal.cli import attachment, config, dataset, download, mis, run
6
6
 
7
7
 
8
8
  @click.group(context_settings={"show_default": True, "help_option_names": ("-h", "--help")})
@@ -12,7 +12,6 @@ def nom() -> None:
12
12
 
13
13
 
14
14
  nom.add_command(attachment.attachment_cmd)
15
- nom.add_command(auth.auth_cmd)
16
15
  nom.add_command(config.config_cmd)
17
16
  nom.add_command(dataset.dataset_cmd)
18
17
  nom.add_command(download.download_cmd)
@@ -1,12 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- import warnings
4
-
5
3
  import click
6
4
 
7
5
  from nominal.cli.util.global_decorators import global_options
8
- from nominal.cli.util.verify_connection import validate_token_url
9
- from nominal.config import _config as _deprecated_config
6
+ from nominal.core.exceptions import NominalMethodRemovedError
10
7
 
11
8
 
12
9
  @click.group(name="auth")
@@ -25,12 +22,4 @@ def set_token(token: str, base_url: str) -> None:
25
22
 
26
23
  Update the token for a given URL in the (deprecated) Nominal config file
27
24
  """
28
- warnings.warn(
29
- "`nom auth set-token` is deprecated, use `nom config profile add` instead",
30
- UserWarning,
31
- stacklevel=2,
32
- )
33
- path = _deprecated_config._DEFAULT_NOMINAL_CONFIG_PATH
34
- validate_token_url(token, base_url, None)
35
- _deprecated_config.set_token(base_url, token)
36
- click.secho(f"Successfully set token for '{base_url}' in {path}", fg="green")
25
+ raise NominalMethodRemovedError("nominal auth set-token", "use 'nominal config profile add' instead")
@@ -70,7 +70,7 @@ def upload_csv(
70
70
  description=description,
71
71
  prefix_tree_delimiter=channel_name_delimiter,
72
72
  )
73
- dataset.add_tabular_data(
73
+ dataset_file = dataset.add_tabular_data(
74
74
  file,
75
75
  timestamp_column=timestamp_column,
76
76
  timestamp_type=timestamp_type,
@@ -78,7 +78,7 @@ def upload_csv(
78
78
 
79
79
  # block until ingestion completed, if requested
80
80
  if wait:
81
- dataset.poll_until_ingestion_completed()
81
+ dataset_file.poll_until_ingestion_completed()
82
82
 
83
83
  click.echo(dataset)
84
84
 
@@ -4,7 +4,7 @@ import logging
4
4
 
5
5
  from typing_extensions import deprecated
6
6
 
7
- from nominal.experimental.logging import ClickLogHandler, install_click_log_handler
7
+ from nominal.core.exceptions import NominalMethodRemovedError
8
8
 
9
9
 
10
10
  @deprecated(
@@ -20,7 +20,10 @@ def install_log_handler(level: int = logging.WARNING, no_color: bool = False) ->
20
20
  no_color: If true, prevents log messages from being stylized by severity level
21
21
 
22
22
  """
23
- install_click_log_handler(level=level, no_color=no_color)
23
+ raise NominalMethodRemovedError(
24
+ "nominal.cli.util.install_log_handler",
25
+ "use nominal.experimental.logging.install_click_log_handler instead",
26
+ )
24
27
 
25
28
 
26
- __all__ = ["ClickLogHandler", "install_log_handler"]
29
+ __all__ = ["install_log_handler"]
@@ -8,7 +8,6 @@ import typing
8
8
 
9
9
  import click
10
10
 
11
- from nominal.config._config import _DEFAULT_NOMINAL_CONFIG_PATH, get_token
12
11
  from nominal.core.client import NominalClient
13
12
  from nominal.experimental.logging import install_click_log_handler
14
13
 
@@ -105,14 +104,14 @@ def client_options(func: typing.Callable[Param, T]) -> typing.Callable[..., T]:
105
104
  """Decorator to add click options to a click command for dynamically creating and injecting an instance of the
106
105
  NominalClient into commands based on user-provided flags to configure its creation.
107
106
 
108
- This will add three options, --profile, --base-url, and --token, which perform the three aforementioned
109
- configurations before spawning a NominalClient.
107
+ This will add an option --profile which perform the aforementioned configurations before spawning a NominalClient.
110
108
 
111
109
  NOTE: any click command utilizing this decorator MUST accept a key-value argument pair named client of type
112
110
  NominalClient.
113
111
  """
114
112
  profile_option = click.option(
115
113
  "--profile",
114
+ required=True,
116
115
  help=(
117
116
  "If provided, use the given named config profile for instantiating a Nominal Client. "
118
117
  "This is the preferred mechanism for instantiating a client today-- see `nom config profile add` "
@@ -120,26 +119,6 @@ def client_options(func: typing.Callable[Param, T]) -> typing.Callable[..., T]:
120
119
  "--base-url."
121
120
  ),
122
121
  )
123
- url_option = click.option(
124
- "--base-url",
125
- default="https://api.gov.nominal.io/api",
126
- show_default=True,
127
- help="Base URL of the Nominal API to hit. Useful for hitting other clusters, e.g., staging for internal users.",
128
- )
129
- token_path_option = click.option(
130
- "--token-path",
131
- default=_DEFAULT_NOMINAL_CONFIG_PATH,
132
- type=click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path),
133
- show_default=True,
134
- help="Path to the yaml file containing the Nominal access token for authenticating with the API",
135
- )
136
- token_option = click.option(
137
- "--token",
138
- help=(
139
- "API Access token to use when creating the nominal client. "
140
- "If provided, takes precedence over --token-path and --base-url"
141
- ),
142
- )
143
122
  trust_store_option = click.option(
144
123
  "--trust-store-path",
145
124
  type=click.Path(dir_okay=False, exists=True, resolve_path=True, path_type=pathlib.Path),
@@ -154,35 +133,14 @@ def client_options(func: typing.Callable[Param, T]) -> typing.Callable[..., T]:
154
133
  *args: Param.args,
155
134
  **kwargs: Param.kwargs,
156
135
  ) -> T:
157
- profile: str | None = kwargs.pop("profile") # type: ignore[assignment]
158
- base_url: str = kwargs.pop("base_url", "") # type: ignore[assignment]
159
- token: str | None = kwargs.pop("token") # type: ignore[assignment]
160
- token_path: pathlib.Path = kwargs.pop("token_path") # type: ignore[assignment]
136
+ profile: str = kwargs.pop("profile") # type: ignore[assignment]
161
137
  trust_store_path: pathlib.Path | None = kwargs.pop("trust_store_path") # type: ignore[assignment]
162
138
 
163
139
  trust_store_str = str(trust_store_path) if trust_store_path else None
164
140
 
165
- if profile is not None:
166
- logger.info("Instantiating client from profile '%s'", profile)
167
- client = NominalClient.from_profile(profile, trust_store_path=trust_store_str)
168
- kwargs["client"] = client
169
- return func(*args, **kwargs)
170
-
171
- logger.warning(
172
- "Creating a Nominal Client without using '--profile' is deprecated! "
173
- "See 'https://docs.nominal.io/core/sdk/python-client/authentication' for details..."
174
- )
175
-
176
- if token is None:
177
- if token_path.exists():
178
- token = get_token(base_url, token_path)
179
- else:
180
- raise ValueError(
181
- f"Cannot instantiate client: no token provided and token path {token_path} does not exist."
182
- )
183
-
184
- client = NominalClient.create(base_url, token=token, trust_store_path=trust_store_str)
141
+ logger.info("Instantiating client from profile '%s'", profile)
142
+ client = NominalClient.from_profile(profile, trust_store_path=trust_store_str)
185
143
  kwargs["client"] = client
186
144
  return func(*args, **kwargs)
187
145
 
188
- return profile_option(token_option(token_path_option(url_option(trust_store_option(wrapped_function)))))
146
+ return profile_option(trust_store_option(wrapped_function))
@@ -27,29 +27,53 @@ def _sign_and_upload_part_job(
27
27
  upload_id: str,
28
28
  q: Queue[bytes],
29
29
  part: int,
30
+ num_retries: int = 3,
30
31
  ) -> requests.Response:
31
32
  data = q.get()
33
+
32
34
  try:
33
- response = upload_client.sign_part(auth_header, key, part, upload_id)
34
- logger.debug(
35
- "successfully signed multipart upload part",
36
- extra={"key": key, "part": part, "upload_id": upload_id, "response.url": response.url},
37
- )
38
- put_response = requests.put(
39
- response.url,
40
- data=data,
41
- headers=response.headers,
42
- verify=upload_client._verify,
43
- )
44
- logger.debug(
45
- "put multipart upload part",
46
- extra={"url": response.url, "size": len(data), "status_code": put_response.status_code},
35
+ last_ex: Exception | None = None
36
+ for attempt in range(num_retries):
37
+ try:
38
+ log_extras = {"key": key, "part": part, "upload_id": upload_id, "attempt": attempt + 1}
39
+
40
+ logger.debug("Signing part %d for upload", part, extra=log_extras)
41
+ sign_response = upload_client.sign_part(auth_header, key, part, upload_id)
42
+ logger.debug(
43
+ "Successfully signed part %d for upload",
44
+ part,
45
+ extra={"response.url": sign_response.url, **log_extras},
46
+ )
47
+
48
+ logger.debug("Pushing part %d for multipart upload", extra=log_extras)
49
+ put_response = requests.put(
50
+ sign_response.url,
51
+ data=data,
52
+ headers=sign_response.headers,
53
+ verify=upload_client._verify,
54
+ )
55
+ logger.debug(
56
+ "Finished pushing part %d for multipart upload with status %d",
57
+ part,
58
+ put_response.status_code,
59
+ extra={"response.url": put_response.url, **log_extras},
60
+ )
61
+ put_response.raise_for_status()
62
+ return put_response
63
+ except Exception as ex:
64
+ logger.warning(
65
+ "Failed to upload part %d: %s",
66
+ part,
67
+ ex,
68
+ extra=log_extras,
69
+ )
70
+ last_ex = ex
71
+
72
+ raise (
73
+ last_ex
74
+ if last_ex
75
+ else RuntimeError(f"Unknown error uploading part {part} for upload_id={upload_id} and key={key}")
47
76
  )
48
- put_response.raise_for_status()
49
- return put_response
50
- except Exception as e:
51
- logger.exception("error uploading part", exc_info=e, extra={"key": key, "upload_id": upload_id, "part": part})
52
- raise e
53
77
  finally:
54
78
  q.task_done()
55
79
 
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  import logging
5
+ import uuid
5
6
  from dataclasses import dataclass, field
6
7
  from datetime import datetime, timedelta
7
8
  from io import TextIOBase
@@ -21,17 +22,18 @@ from nominal_api import (
21
22
  scout_catalog,
22
23
  scout_checks_api,
23
24
  scout_datasource_connection_api,
25
+ scout_layout_api,
24
26
  scout_notebook_api,
25
27
  scout_run_api,
26
28
  scout_template_api,
27
29
  scout_video_api,
30
+ scout_workbookcommon_api,
28
31
  secrets_api,
29
32
  storage_datasource_api,
30
33
  )
31
34
  from typing_extensions import Self, deprecated
32
35
 
33
36
  from nominal import ts
34
- from nominal._utils.deprecation_tools import warn_on_deprecated_argument
35
37
  from nominal.config import NominalConfig, _config
36
38
  from nominal.core._clientsbunch import ClientsBunch
37
39
  from nominal.core._constants import DEFAULT_API_BASE_URL
@@ -94,7 +96,7 @@ from nominal.core.dataset import (
94
96
  )
95
97
  from nominal.core.datasource import DataSource
96
98
  from nominal.core.event import Event, EventType
97
- from nominal.core.exceptions import NominalConfigError, NominalError, NominalIngestError
99
+ from nominal.core.exceptions import NominalConfigError, NominalError, NominalIngestError, NominalMethodRemovedError
98
100
  from nominal.core.filetype import FileType, FileTypes
99
101
  from nominal.core.run import Run
100
102
  from nominal.core.secret import Secret
@@ -338,10 +340,6 @@ class NominalClient:
338
340
  for raw_dataset in search_datasets_paginated(self._clients.catalog, self._clients.auth_header, query):
339
341
  yield Dataset._from_conjure(self._clients, raw_dataset)
340
342
 
341
- @warn_on_deprecated_argument(
342
- "workspace_rid",
343
- "`workspace_rid` has been deprecated and will be removed in a future version. Use `workspace` instead.",
344
- )
345
343
  def search_datasets(
346
344
  self,
347
345
  *,
@@ -351,7 +349,6 @@ class NominalClient:
351
349
  properties: Mapping[str, str] | None = None,
352
350
  before: str | datetime | IntegralNanosecondsUTC | None = None,
353
351
  after: str | datetime | IntegralNanosecondsUTC | None = None,
354
- workspace_rid: Workspace | str | None = None,
355
352
  workspace: WorkspaceSearchT = WorkspaceSearchType.ALL,
356
353
  archived: bool | None = None,
357
354
  ) -> Sequence[Dataset]:
@@ -365,7 +362,6 @@ class NominalClient:
365
362
  properties: A mapping of key-value pairs that must ALL be present on a secret to be included.
366
363
  before: Searches for datasets created before some time (inclusive).
367
364
  after: Searches for datasets created before after time (inclusive).
368
- workspace_rid: deprecated. use `workspace` instead.
369
365
  workspace: Filters search to given workspace.
370
366
  archived: Filters results to either archived or unarchived datasets.
371
367
 
@@ -377,12 +373,6 @@ class NominalClient:
377
373
  Returns:
378
374
  All datasets which match all of the provided conditions
379
375
  """
380
- if workspace is not None and workspace_rid is not None:
381
- raise ValueError("Both `workspace` and `workspace_rid` provided-- must use one or the other.")
382
-
383
- if workspace_rid is not None:
384
- workspace = workspace_rid
385
-
386
376
  query = create_search_datasets_query(
387
377
  exact_match=exact_match,
388
378
  search_text=search_text,
@@ -1477,7 +1467,7 @@ class NominalClient:
1477
1467
 
1478
1468
  @deprecated(
1479
1469
  "Calling `NominalClient.create_workbook_from_template` is deprecated and will be removed "
1480
- "in a future release. Use `Template.create_workbook` instead"
1470
+ "in a future release. Use `WorkbookTemplate.create_workbook` instead"
1481
1471
  )
1482
1472
  def create_workbook_from_template(
1483
1473
  self,
@@ -1487,13 +1477,56 @@ class NominalClient:
1487
1477
  description: str | None = None,
1488
1478
  is_draft: bool = False,
1489
1479
  ) -> Workbook:
1490
- """Creates a workbook from a workbook template.
1480
+ raise NominalMethodRemovedError(
1481
+ "nominal.core.NominalClient.create_workbook_from_template",
1482
+ "use 'nominal.core.WorkbookTemplate.create_workbook' instead",
1483
+ )
1491
1484
 
1492
- NOTE: is_draft is intentionally unused and will be removed in a future release.
1485
+ def create_workbook_template(
1486
+ self,
1487
+ title: str,
1488
+ *,
1489
+ description: str | None = None,
1490
+ labels: list[str] | None = None,
1491
+ properties: dict[str, str] | None = None,
1492
+ commit_message: str | None = None,
1493
+ workspace: Workspace | str | None = None,
1494
+ ) -> WorkbookTemplate:
1495
+ """Create an empty workbook template.
1496
+
1497
+ Args:
1498
+ title: Title of the workbook template
1499
+ description: Description of the workbook template
1500
+ labels: Labels to attach to the workbook template
1501
+ properties: Properties to attach to the workbook template
1502
+ commit_message: An optional message to include with the creation of the template
1503
+ workspace: Workspace to create the workbook template in.
1504
+
1505
+ Returns:
1506
+ The created WorkbookTemplate
1493
1507
  """
1494
- template = self.get_workbook_template(template_rid)
1495
- return template.create_workbook(
1508
+ request = scout_template_api.CreateTemplateRequest(
1496
1509
  title=title,
1497
- description=description,
1498
- run=run_rid,
1510
+ description=description if description is not None else "",
1511
+ labels=labels if labels is not None else [],
1512
+ properties=properties if properties is not None else {},
1513
+ is_published=False,
1514
+ layout=scout_layout_api.WorkbookLayout(
1515
+ v1=scout_layout_api.WorkbookLayoutV1(
1516
+ root_panel=scout_layout_api.Panel(
1517
+ tabbed=scout_layout_api.TabbedPanel(
1518
+ v1=scout_layout_api.TabbedPanelV1(
1519
+ id=str(uuid.uuid4()),
1520
+ tabs=[],
1521
+ )
1522
+ )
1523
+ )
1524
+ )
1525
+ ),
1526
+ content=scout_workbookcommon_api.WorkbookContent(channel_variables={}, charts={}),
1527
+ message=commit_message if commit_message is not None else "Initial blank workbook template",
1528
+ workspace=self._workspace_rid_for_search(workspace or WorkspaceSearchType.ALL),
1499
1529
  )
1530
+
1531
+ template = self._clients.template.create(self._clients.auth_header, request)
1532
+ return WorkbookTemplate._from_conjure(self._clients, template)
@@ -21,6 +21,7 @@ from typing_extensions import Self, deprecated
21
21
  from nominal.core import checklist, event
22
22
  from nominal.core._clientsbunch import HasScoutParams
23
23
  from nominal.core._utils.api_tools import HasRid
24
+ from nominal.core.exceptions import NominalMethodRemovedError
24
25
  from nominal.ts import IntegralNanosecondsUTC, _SecondsNanos
25
26
 
26
27
 
@@ -75,8 +76,10 @@ class DataReview(HasRid):
75
76
  )
76
77
  def get_violations(self) -> Sequence[CheckViolation]:
77
78
  """Retrieves the list of check violations for the data review."""
78
- response = self._clients.datareview.get_check_alerts_for_data_review(self._clients.auth_header, self.rid)
79
- return [CheckViolation._from_conjure(alert) for alert in response]
79
+ raise NominalMethodRemovedError(
80
+ "nominal.core.DataReview.get_violations",
81
+ "use 'nominal.core.DataReview.get_events()' instead",
82
+ )
80
83
 
81
84
  def get_events(self) -> Sequence[event.Event]:
82
85
  """Retrieves the list of events for the data review."""
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import time
5
4
  from dataclasses import dataclass
6
5
  from datetime import timedelta
7
6
  from io import TextIOBase
@@ -20,7 +19,7 @@ from nominal.core.bounds import Bounds
20
19
  from nominal.core.containerized_extractors import ContainerizedExtractor
21
20
  from nominal.core.dataset_file import DatasetFile
22
21
  from nominal.core.datasource import DataSource
23
- from nominal.core.exceptions import NominalIngestError, NominalIngestFailed, NominalIngestMultiError
22
+ from nominal.core.exceptions import NominalIngestError, NominalIngestMultiError, NominalMethodRemovedError
24
23
  from nominal.core.filetype import FileType, FileTypes
25
24
  from nominal.core.log import LogPoint, _write_logs
26
25
  from nominal.ts import (
@@ -55,38 +54,12 @@ class Dataset(DataSource, RefreshableMixin[scout_catalog.EnrichedDataset]):
55
54
  "obtained when ingesting files or by calling `dataset.list_files()`."
56
55
  )
57
56
  def poll_until_ingestion_completed(self, interval: timedelta = timedelta(seconds=1)) -> Self:
58
- """Block until dataset file ingestion has completed.
59
- This method polls Nominal for ingest status after uploading a file to a dataset on an interval.
60
-
61
- Raises:
62
- ------
63
- NominalIngestFailed: if the ingest failed
64
- NominalIngestError: if the ingest status is not known
65
-
66
- """
67
- while True:
68
- progress = self._clients.catalog.get_ingest_progress_v2(self._clients.auth_header, self.rid)
69
- if progress.ingest_status.type == "success":
70
- break
71
- elif progress.ingest_status.type == "inProgress": # "type" strings are camelCase
72
- pass
73
- elif progress.ingest_status.type == "error":
74
- error = progress.ingest_status.error
75
- if error is not None:
76
- raise NominalIngestFailed(
77
- f"ingest failed for dataset {self.rid!r}: {error.message} ({error.error_type})"
78
- )
79
- raise NominalIngestError(
80
- f"ingest status type marked as 'error' but with no instance for dataset {self.rid!r}"
81
- )
82
- else:
83
- raise NominalIngestError(
84
- f"unhandled ingest status {progress.ingest_status.type!r} for dataset {self.rid!r}"
85
- )
86
- time.sleep(interval.total_seconds())
87
-
88
- # Update metadata now that data has successfully ingested
89
- return self.refresh()
57
+ raise NominalMethodRemovedError(
58
+ "nominal.core.Dataset.poll_until_ingestion_completed",
59
+ "poll for ingestion completion on individual 'nominal.core.DatasetFile's, "
60
+ "which are obtained when ingesting files or by calling "
61
+ "'nominal.core.Dataset.list_files()' etc.",
62
+ )
90
63
 
91
64
  def update(
92
65
  self,
@@ -339,8 +312,14 @@ class Dataset(DataSource, RefreshableMixin[scout_catalog.EnrichedDataset]):
339
312
  def add_ardupilot_dataflash(
340
313
  self,
341
314
  path: Path | str,
315
+ tags: Mapping[str, str] | None = None,
342
316
  ) -> DatasetFile:
343
- """Add a Dataflash file to an existing dataset."""
317
+ """Add a Dataflash file to an existing dataset.
318
+
319
+ Args:
320
+ path: Path to the Dataflash file to add to this dataset.
321
+ tags: key-value pairs to apply as tags to all data uniformly in the file.
322
+ """
344
323
  dataflash_path = Path(path)
345
324
  s3_path = upload_multipart_file(
346
325
  self._clients.auth_header,
@@ -352,7 +331,7 @@ class Dataset(DataSource, RefreshableMixin[scout_catalog.EnrichedDataset]):
352
331
  target = ingest_api.DatasetIngestTarget(
353
332
  existing=ingest_api.ExistingDatasetIngestDestination(dataset_rid=self.rid)
354
333
  )
355
- request = _create_dataflash_ingest_request(s3_path, target)
334
+ request = _create_dataflash_ingest_request(s3_path, target, tags)
356
335
  resp = self._clients.ingest.ingest(self._clients.auth_header, request)
357
336
  return self._handle_ingest_response(resp)
358
337
 
@@ -548,6 +527,10 @@ class Dataset(DataSource, RefreshableMixin[scout_catalog.EnrichedDataset]):
548
527
  )
549
528
 
550
529
 
530
+ @deprecated(
531
+ "poll_until_ingestion_completed() is deprecated and will be removed in a future release. "
532
+ "Instead, call poll_until_ingestion_completed() on individual DatasetFiles."
533
+ )
551
534
  def poll_until_ingestion_completed(datasets: Iterable[Dataset], interval: timedelta = timedelta(seconds=1)) -> None:
552
535
  """Block until all dataset ingestions have completed (succeeded or failed).
553
536
 
@@ -562,7 +545,8 @@ def poll_until_ingestion_completed(datasets: Iterable[Dataset], interval: timede
562
545
  errors = {}
563
546
  for dataset in datasets:
564
547
  try:
565
- dataset.poll_until_ingestion_completed(interval=interval)
548
+ for dataset_file in dataset.list_files():
549
+ dataset_file.poll_until_ingestion_completed(interval=interval)
566
550
  except NominalIngestError as e:
567
551
  errors[dataset.rid] = e
568
552
  if errors:
@@ -611,12 +595,17 @@ def _create_dataset(
611
595
  return client.create_dataset(auth_header, request)
612
596
 
613
597
 
614
- def _create_dataflash_ingest_request(s3_path: str, target: ingest_api.DatasetIngestTarget) -> ingest_api.IngestRequest:
598
+ def _create_dataflash_ingest_request(
599
+ s3_path: str,
600
+ target: ingest_api.DatasetIngestTarget,
601
+ tags: Mapping[str, str] | None = None,
602
+ ) -> ingest_api.IngestRequest:
615
603
  return ingest_api.IngestRequest(
616
604
  ingest_api.IngestOptions(
617
605
  dataflash=ingest_api.DataflashOpts(
618
606
  source=ingest_api.IngestSource(s3=ingest_api.S3IngestSource(path=s3_path)),
619
607
  target=target,
608
+ additional_file_tags={**tags} if tags else None,
620
609
  )
621
610
  ),
622
611
  )
@@ -7,7 +7,7 @@ from enum import Enum
7
7
  from typing import Iterable, Mapping, Protocol, Sequence
8
8
 
9
9
  from nominal_api import event
10
- from typing_extensions import Self, deprecated
10
+ from typing_extensions import Self
11
11
 
12
12
  from nominal.core._clientsbunch import HasScoutParams
13
13
  from nominal.core._utils.api_tools import HasRid, RefreshableMixin, rid_from_instance_or_string
@@ -45,11 +45,6 @@ class Event(HasRid, RefreshableMixin[event.Event]):
45
45
 
46
46
  return resp[0]
47
47
 
48
- @property
49
- @deprecated("The uuid field of an event is deprecated and will be removed in a future release")
50
- def uuid(self) -> str:
51
- return self._uuid
52
-
53
48
  def update(
54
49
  self,
55
50
  *,