nominal 1.95.0__py3-none-any.whl → 1.97.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- CHANGELOG.md +21 -0
- nominal/cli/__init__.py +1 -2
- nominal/cli/auth.py +2 -13
- nominal/cli/dataset.py +2 -2
- nominal/cli/util/click_log_handler.py +6 -3
- nominal/cli/util/global_decorators.py +6 -48
- nominal/core/_utils/multipart.py +43 -19
- nominal/core/asset.py +176 -176
- nominal/core/client.py +54 -21
- nominal/core/data_review.py +5 -2
- nominal/core/dataset.py +27 -38
- nominal/core/event.py +1 -6
- nominal/core/exceptions.py +39 -0
- nominal/core/run.py +148 -148
- nominal/core/workbook.py +5 -8
- nominal/nominal.py +4 -3
- {nominal-1.95.0.dist-info → nominal-1.97.0.dist-info}/METADATA +2 -2
- {nominal-1.95.0.dist-info → nominal-1.97.0.dist-info}/RECORD +21 -21
- {nominal-1.95.0.dist-info → nominal-1.97.0.dist-info}/WHEEL +1 -1
- {nominal-1.95.0.dist-info → nominal-1.97.0.dist-info}/entry_points.txt +0 -0
- {nominal-1.95.0.dist-info → nominal-1.97.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
|
@@ -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
|
|
nominal/cli/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@ import importlib.metadata
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
-
from nominal.cli import attachment,
|
|
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)
|
nominal/cli/auth.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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")
|
nominal/cli/dataset.py
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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__ = ["
|
|
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
|
|
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
|
|
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
|
-
|
|
166
|
-
|
|
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(
|
|
146
|
+
return profile_option(trust_store_option(wrapped_function))
|
nominal/core/_utils/multipart.py
CHANGED
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|