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 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, 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)
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.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")
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
- 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