splime 0.2.5__tar.gz → 0.3.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 (106) hide show
  1. {splime-0.2.5/src/splime.egg-info → splime-0.3.0}/PKG-INFO +3 -1
  2. {splime-0.2.5 → splime-0.3.0}/pyproject.toml +8 -3
  3. {splime-0.2.5 → splime-0.3.0}/src/spl/_client.py +32 -119
  4. {splime-0.2.5 → splime-0.3.0}/src/spl/_http.py +9 -2
  5. {splime-0.2.5 → splime-0.3.0}/src/spl/_views.py +28 -61
  6. {splime-0.2.5 → splime-0.3.0}/src/spl/core/__init__.py +1 -6
  7. {splime-0.2.5 → splime-0.3.0}/src/spl/core/_common.py +76 -120
  8. splime-0.3.0/src/spl/core/entities/adapter.py +176 -0
  9. splime-0.3.0/src/spl/core/entities/artifact.py +112 -0
  10. splime-0.3.0/src/spl/core/entities/control.py +44 -0
  11. splime-0.3.0/src/spl/core/entities/distribution.py +56 -0
  12. splime-0.3.0/src/spl/core/entities/function.py +257 -0
  13. {splime-0.2.5 → splime-0.3.0}/src/spl/core/entities/local_function.py +27 -33
  14. {splime-0.2.5 → splime-0.3.0}/src/spl/core/entities/misc.py +3 -3
  15. splime-0.3.0/src/spl/core/entities/module.py +82 -0
  16. splime-0.3.0/src/spl/core/entities/node.py +289 -0
  17. splime-0.3.0/src/spl/core/entities/node_function.py +71 -0
  18. splime-0.3.0/src/spl/core/entities/node_remote.py +298 -0
  19. splime-0.3.0/src/spl/core/entities/pipeline.py +366 -0
  20. splime-0.3.0/src/spl/core/entities/scalar.py +48 -0
  21. splime-0.3.0/src/spl/core/ir/common.py +42 -0
  22. {splime-0.2.5 → splime-0.3.0}/src/spl/core/ir/parse.py +18 -18
  23. splime-0.3.0/src/spl/core/ir/unparse.py +38 -0
  24. splime-0.3.0/src/spl/core/ir/utils.py +157 -0
  25. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/canonical.py +6 -20
  26. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/cli.py +5 -15
  27. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/docker_environment.py +6 -21
  28. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/docker_pool.py +5 -20
  29. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/doctor.py +71 -12
  30. splime-0.3.0/src/spl/daemon/environment.py +429 -0
  31. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/environment_base.py +6 -19
  32. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/heartbeat_service.py +1 -5
  33. splime-0.3.0/src/spl/daemon/interpreter_visibility.py +69 -0
  34. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/metadata.py +12 -37
  35. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/remote_client.py +126 -117
  36. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/env.py +27 -20
  37. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/library.py +3 -12
  38. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/object.py +138 -176
  39. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/run.py +32 -8
  40. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/server_connection.py +11 -25
  41. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/_helpers.py +38 -20
  42. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/artifacts.py +5 -13
  43. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/diagnostics.py +57 -11
  44. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/envs.py +3 -5
  45. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/libraries.py +2 -2
  46. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/objects.py +12 -21
  47. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/remote.py +2 -2
  48. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/runs.py +3 -7
  49. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/server_connections.py +3 -6
  50. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/run_progress.py +5 -0
  51. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/runtime_backend.py +85 -12
  52. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/runtime_config.py +3 -12
  53. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/runtime_dependencies.py +13 -0
  54. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/secret_store.py +3 -6
  55. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/server.py +138 -148
  56. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/server_connection.py +2 -7
  57. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/services/sync.py +3 -14
  58. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/signature.py +15 -38
  59. splime-0.3.0/src/spl/daemon/spl_free_generator.py +241 -0
  60. splime-0.3.0/src/spl/daemon/spl_free_runner.py +216 -0
  61. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/storage_base.py +43 -93
  62. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/store.py +5 -3
  63. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/worker.py +49 -79
  64. splime-0.3.0/src/spl/daemon/worker_runtime_marker.py +3 -0
  65. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon_client.py +189 -121
  66. {splime-0.2.5 → splime-0.3.0}/src/spl/pipeline_widget.py +45 -38
  67. {splime-0.2.5 → splime-0.3.0}/src/spl/server_client.py +17 -33
  68. {splime-0.2.5 → splime-0.3.0/src/splime.egg-info}/PKG-INFO +3 -1
  69. {splime-0.2.5 → splime-0.3.0}/src/splime.egg-info/SOURCES.txt +4 -0
  70. {splime-0.2.5 → splime-0.3.0}/src/splime.egg-info/requires.txt +2 -0
  71. splime-0.2.5/src/spl/core/entities/adapter.py +0 -210
  72. splime-0.2.5/src/spl/core/entities/artifact.py +0 -140
  73. splime-0.2.5/src/spl/core/entities/control.py +0 -45
  74. splime-0.2.5/src/spl/core/entities/distribution.py +0 -65
  75. splime-0.2.5/src/spl/core/entities/function.py +0 -257
  76. splime-0.2.5/src/spl/core/entities/module.py +0 -88
  77. splime-0.2.5/src/spl/core/entities/node.py +0 -286
  78. splime-0.2.5/src/spl/core/entities/node_function.py +0 -79
  79. splime-0.2.5/src/spl/core/entities/node_remote.py +0 -349
  80. splime-0.2.5/src/spl/core/entities/pipeline.py +0 -436
  81. splime-0.2.5/src/spl/core/entities/scalar.py +0 -55
  82. splime-0.2.5/src/spl/core/ir/common.py +0 -34
  83. splime-0.2.5/src/spl/core/ir/unparse.py +0 -29
  84. splime-0.2.5/src/spl/core/ir/utils.py +0 -163
  85. splime-0.2.5/src/spl/daemon/environment.py +0 -269
  86. {splime-0.2.5 → splime-0.3.0}/LICENSE +0 -0
  87. {splime-0.2.5 → splime-0.3.0}/NOTICE +0 -0
  88. {splime-0.2.5 → splime-0.3.0}/README.md +0 -0
  89. {splime-0.2.5 → splime-0.3.0}/setup.cfg +0 -0
  90. {splime-0.2.5 → splime-0.3.0}/src/spl/__init__.py +0 -0
  91. {splime-0.2.5 → splime-0.3.0}/src/spl/_deprecate.py +0 -0
  92. {splime-0.2.5 → splime-0.3.0}/src/spl/client.py +0 -0
  93. {splime-0.2.5 → splime-0.3.0}/src/spl/core/common.py +0 -0
  94. {splime-0.2.5 → splime-0.3.0}/src/spl/core/entities/__init__.py +0 -0
  95. {splime-0.2.5 → splime-0.3.0}/src/spl/core/ir/__init__.py +0 -0
  96. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/__init__.py +0 -0
  97. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/__main__.py +0 -0
  98. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/client.py +0 -0
  99. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/__init__.py +0 -0
  100. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/repositories/sync_event.py +0 -0
  101. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/routes/__init__.py +0 -0
  102. {splime-0.2.5 → splime-0.3.0}/src/spl/daemon/services/__init__.py +0 -0
  103. {splime-0.2.5 → splime-0.3.0}/src/spl/py.typed +0 -0
  104. {splime-0.2.5 → splime-0.3.0}/src/splime.egg-info/dependency_links.txt +0 -0
  105. {splime-0.2.5 → splime-0.3.0}/src/splime.egg-info/entry_points.txt +0 -0
  106. {splime-0.2.5 → splime-0.3.0}/src/splime.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splime
3
- Version: 0.2.5
3
+ Version: 0.3.0
4
4
  Summary: Reuse Python functions across projects without rewriting or redeploying them.
5
5
  Author-email: Yastrebov Kirill <yastrebovks@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -42,6 +42,8 @@ Requires-Dist: pyexpect; extra == "test"
42
42
  Requires-Dist: mypy==2.1.0; extra == "test"
43
43
  Requires-Dist: pytest==9.1.1; extra == "test"
44
44
  Requires-Dist: pytest-asyncio; extra == "test"
45
+ Requires-Dist: ruff==0.15.20; extra == "test"
46
+ Requires-Dist: types-PyYAML==6.0.12.20260518; extra == "test"
45
47
  Provides-Extra: docs
46
48
  Requires-Dist: sphinx==9.1.0; extra == "docs"
47
49
  Requires-Dist: sphinx-rtd-theme==3.1.0; extra == "docs"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splime"
7
- version = "0.2.5"
7
+ version = "0.3.0"
8
8
 
9
9
  description = "Reuse Python functions across projects without rewriting or redeploying them."
10
10
  readme = "README.md"
@@ -55,7 +55,9 @@ test = [
55
55
  "pyexpect",
56
56
  "mypy==2.1.0",
57
57
  "pytest==9.1.1",
58
- "pytest-asyncio"]
58
+ "pytest-asyncio",
59
+ "ruff==0.15.20",
60
+ "types-PyYAML==6.0.12.20260518"]
59
61
 
60
62
  docs = [
61
63
  "sphinx==9.1.0",
@@ -105,7 +107,7 @@ strict = true
105
107
  line-length = 120
106
108
 
107
109
  [tool.ruff.lint]
108
- select = []
110
+ select = ["E4", "E7", "E9", "F"]
109
111
  ignore = []
110
112
 
111
113
  # Scoped, justified allowances. These silence rules only where the pattern is
@@ -125,6 +127,9 @@ ignore = []
125
127
  "S603", "S607", "S310", "S104", "S108", "S608",
126
128
  "S105", "B904", "S110", "S112", "N818",
127
129
  ]
130
+ # The legacy worker must repair sys.path before importing packages that can be
131
+ # shadowed by the daemon checkout injected through PYTHONPATH.
132
+ "src/spl/daemon/worker.py" = ["E402"]
128
133
  # The CLI and daemon bootstrap print to the console on purpose.
129
134
  "src/spl/daemon/cli.py" = ["T201"]
130
135
  "src/spl/daemon/server.py" = ["T201"]
@@ -18,6 +18,7 @@ dependencies imported yet.
18
18
 
19
19
  from __future__ import annotations
20
20
 
21
+ import builtins
21
22
  from dataclasses import dataclass, field, replace
22
23
  from html import escape
23
24
  from pathlib import Path
@@ -70,10 +71,7 @@ def _preview(value: Any, *, limit: int = 80) -> str:
70
71
 
71
72
 
72
73
  def _is_missing_server_connection(exc: Exception) -> bool:
73
- return (
74
- isinstance(exc, ClientError)
75
- and _NO_SERVER_CONNECTION_MESSAGE in str(exc)
76
- )
74
+ return isinstance(exc, ClientError) and _NO_SERVER_CONNECTION_MESSAGE in str(exc)
77
75
 
78
76
 
79
77
  def _progress_callback(progress: ProgressOption) -> RunStateCallback | None:
@@ -141,10 +139,7 @@ class PublishedObject:
141
139
  "entrypoint": self.entrypoint,
142
140
  }
143
141
  body = "".join(
144
- "<tr>"
145
- f"<th style='text-align:left'>{escape(key)}</th>"
146
- f"<td><code>{escape(value)}</code></td>"
147
- "</tr>"
142
+ f"<tr><th style='text-align:left'>{escape(key)}</th><td><code>{escape(value)}</code></td></tr>"
148
143
  for key, value in rows.items()
149
144
  )
150
145
  return f"<table><tbody>{body}</tbody></table>"
@@ -170,11 +165,7 @@ def _catalog_rows(
170
165
  rows: list[dict[str, str]] = []
171
166
  for record in records:
172
167
  library = record.get("library")
173
- library_name = (
174
- library.get("display_name") or library.get("slug")
175
- if isinstance(library, dict)
176
- else library
177
- )
168
+ library_name = library.get("display_name") or library.get("slug") if isinstance(library, dict) else library
178
169
  version_value = record.get("version")
179
170
  if version_value is None:
180
171
  current = record.get("current_version")
@@ -197,28 +188,16 @@ def _catalog_rows(
197
188
  def _rows_to_text(rows: list[dict[str, str]], title: str) -> str:
198
189
  if not rows:
199
190
  return f"{title}: (empty)"
200
- widths = {
201
- header: max(len(header), *(len(row[header]) for row in rows))
202
- for header in _CATALOG_HEADERS
203
- }
191
+ widths = {header: max(len(header), *(len(row[header]) for row in rows)) for header in _CATALOG_HEADERS}
204
192
  head = " ".join(header.ljust(widths[header]) for header in _CATALOG_HEADERS)
205
- body = "\n".join(
206
- " ".join(row[header].ljust(widths[header]) for header in _CATALOG_HEADERS)
207
- for row in rows
208
- )
193
+ body = "\n".join(" ".join(row[header].ljust(widths[header]) for header in _CATALOG_HEADERS) for row in rows)
209
194
  return f"{title} ({len(rows)}):\n{head}\n{body}"
210
195
 
211
196
 
212
197
  def _rows_to_html(rows: list[dict[str, str]], title: str) -> str:
213
- head = "".join(
214
- f"<th style='text-align:left'>{escape(header)}</th>"
215
- for header in _CATALOG_HEADERS
216
- )
198
+ head = "".join(f"<th style='text-align:left'>{escape(header)}</th>" for header in _CATALOG_HEADERS)
217
199
  body = "".join(
218
- "<tr>"
219
- + "".join(f"<td>{escape(row[header])}</td>" for header in _CATALOG_HEADERS)
220
- + "</tr>"
221
- for row in rows
200
+ "<tr>" + "".join(f"<td>{escape(row[header])}</td>" for header in _CATALOG_HEADERS) + "</tr>" for row in rows
222
201
  )
223
202
  return (
224
203
  f"<div><b>{escape(title)}</b> ({len(rows)})"
@@ -258,14 +237,10 @@ class ObjectCatalog(dict[str, Any]):
258
237
  """Local+server catalog that prints per-scope tables; ``.raw`` is a plain dict."""
259
238
 
260
239
  def __repr__(self) -> str:
261
- return "\n\n".join(
262
- _rows_to_text(_catalog_rows(value), str(key)) for key, value in self.items()
263
- )
240
+ return "\n\n".join(_rows_to_text(_catalog_rows(value), str(key)) for key, value in self.items())
264
241
 
265
242
  def _repr_html_(self) -> str:
266
- return "".join(
267
- _rows_to_html(_catalog_rows(value), str(key)) for key, value in self.items()
268
- )
243
+ return "".join(_rows_to_html(_catalog_rows(value), str(key)) for key, value in self.items())
269
244
 
270
245
  @property
271
246
  def raw(self) -> dict[str, Any]:
@@ -343,10 +318,7 @@ class RemoteResult:
343
318
  "artifacts": str(len(self.artifacts)),
344
319
  }
345
320
  body = "".join(
346
- "<tr>"
347
- f"<th style='text-align:left'>{escape(key)}</th>"
348
- f"<td><code>{escape(value)}</code></td>"
349
- "</tr>"
321
+ f"<tr><th style='text-align:left'>{escape(key)}</th><td><code>{escape(value)}</code></td></tr>"
350
322
  for key, value in rows.items()
351
323
  )
352
324
  return f"<table><tbody>{body}</tbody></table>"
@@ -375,13 +347,13 @@ class RemoteRun:
375
347
  def id(self) -> str:
376
348
  """Return the daemon run id."""
377
349
 
378
- return self.state["id"]
350
+ return cast(str, self.state["id"])
379
351
 
380
352
  @property
381
353
  def status(self) -> str:
382
354
  """Return the last known daemon status."""
383
355
 
384
- return self.state["status"]
356
+ return cast(str, self.state["status"])
385
357
 
386
358
  @property
387
359
  def mode(self) -> str:
@@ -490,17 +462,10 @@ class RemoteRun:
490
462
  )
491
463
  if final_state["status"] != "succeeded":
492
464
  error = final_state.get("error") or "run returned no error message"
493
- raise RuntimeError(
494
- f"{self.mode} run {self.id!r} ended as "
495
- f"{final_state.get('status')!r}: {error}"
496
- )
465
+ raise RuntimeError(f"{self.mode} run {self.id!r} ended as {final_state.get('status')!r}: {error}")
497
466
 
498
467
  payload = self.result()
499
- downloaded = (
500
- self.download_artifacts(artifacts_dir)
501
- if artifacts_dir is not None
502
- else {}
503
- )
468
+ downloaded = self.download_artifacts(artifacts_dir) if artifacts_dir is not None else {}
504
469
  return RemoteResult(
505
470
  run=final_state,
506
471
  payload=payload,
@@ -593,7 +558,7 @@ class _LibraryAdmin:
593
558
  grantee: str,
594
559
  *,
595
560
  grantee_type: str = "user",
596
- scopes: list[str] | None = None,
561
+ scopes: builtins.list[str] | None = None,
597
562
  ) -> dict[str, Any]:
598
563
  """Grant a user or team access to one central-server library."""
599
564
 
@@ -779,11 +744,7 @@ class SPLClient:
779
744
  )
780
745
  conn = self.current_server_connection()
781
746
  connection = conn.get("connection")
782
- nested_url = (
783
- connection.get("server_url")
784
- if isinstance(connection, dict)
785
- else None
786
- )
747
+ nested_url = connection.get("server_url") if isinstance(connection, dict) else None
787
748
  server_url = conn.get("server_url") or nested_url or DEFAULT_SERVER_URL
788
749
  return SPLServerClient(token=self._user_token, base_url=server_url)
789
750
 
@@ -800,9 +761,7 @@ class SPLClient:
800
761
  state = self._daemon.server_connection()
801
762
  except Exception as exc:
802
763
  if _is_missing_server_connection(exc):
803
- return ConnectionStatusView(
804
- {"connected": False, "offline": False, "connection": None}
805
- )
764
+ return ConnectionStatusView({"connected": False, "offline": False, "connection": None})
806
765
  raise
807
766
  state.setdefault("connected", bool(state.get("server_url")))
808
767
  return ConnectionStatusView(state)
@@ -819,9 +778,7 @@ class SPLClient:
819
778
 
820
779
  if not self._has_server_connection():
821
780
  return LibraryListView([])
822
- return LibraryListView(
823
- self._daemon.server_libraries(include_accessible=include_accessible)
824
- )
781
+ return LibraryListView(self._daemon.server_libraries(include_accessible=include_accessible))
825
782
 
826
783
  # The flat library aliases (create_library, get_library, update_library,
827
784
  # delete_library, grant_library, revoke_library_grant, add_reference,
@@ -1018,11 +975,7 @@ class SPLClient:
1018
975
  """
1019
976
 
1020
977
  if scope == "auto":
1021
- scope = (
1022
- "server"
1023
- if owner is not None or library is not None or self._has_server_connection()
1024
- else "local"
1025
- )
978
+ scope = "server" if owner is not None or library is not None or self._has_server_connection() else "local"
1026
979
  if scope == "local":
1027
980
  if owner is not None or library is not None:
1028
981
  raise ValueError("owner/library require scope='server', scope='all', or scope='auto'")
@@ -1220,9 +1173,7 @@ class SPLClient:
1220
1173
  """Return normalized function/node/link metadata for one object."""
1221
1174
 
1222
1175
  if self._is_node_remote(name):
1223
- return DecompositionView(
1224
- self._remote_decomposition_response(name, version=version)["decomposition"]
1225
- )
1176
+ return DecompositionView(self._remote_decomposition_response(name, version=version)["decomposition"])
1226
1177
  if owner is not None or library is not None:
1227
1178
  response = self._remote_decomposition_response(
1228
1179
  {
@@ -1269,11 +1220,7 @@ class SPLClient:
1269
1220
  record = response.get("object") or {}
1270
1221
  remote = response.get("remote") or {}
1271
1222
  object_name = (
1272
- title
1273
- or record.get("display_name")
1274
- or record.get("name")
1275
- or remote.get("name")
1276
- or pipeline.name
1223
+ title or record.get("display_name") or record.get("name") or remote.get("name") or pipeline.name
1277
1224
  )
1278
1225
  return PipelineGraphWidget(
1279
1226
  decomposition,
@@ -1328,10 +1275,7 @@ class SPLClient:
1328
1275
  theme=theme,
1329
1276
  )
1330
1277
 
1331
- raise TypeError(
1332
- "pipeline_widget expects an object name, PublishedObject, "
1333
- "spl.core Pipeline, or NodeRemote"
1334
- )
1278
+ raise TypeError("pipeline_widget expects an object name, PublishedObject, spl.core Pipeline, or NodeRemote")
1335
1279
 
1336
1280
  def draw_pipeline(
1337
1281
  self,
@@ -1371,20 +1315,11 @@ class SPLClient:
1371
1315
  function=function,
1372
1316
  )
1373
1317
  display_name = signature.get("display_name") or signature["name"]
1374
- lines = [
1375
- (
1376
- f"{display_name} "
1377
- f"v{signature['version']} ({signature['kind']})"
1378
- )
1379
- ]
1318
+ lines = [(f"{display_name} v{signature['version']} ({signature['kind']})")]
1380
1319
  if signature.get("description"):
1381
1320
  lines.append(signature["description"])
1382
1321
 
1383
- if (
1384
- function is None
1385
- and signature.get("kind") == "pipeline"
1386
- and signature.get("internal_functions")
1387
- ):
1322
+ if function is None and signature.get("kind") == "pipeline" and signature.get("internal_functions"):
1388
1323
  lines.append("Functions:")
1389
1324
  for item in signature["internal_functions"]:
1390
1325
  lines.append(f" - {item['name']}")
@@ -1393,29 +1328,16 @@ class SPLClient:
1393
1328
  if signature["inputs"]:
1394
1329
  for item in signature["inputs"]:
1395
1330
  required = "required" if item["required"] else "optional"
1396
- default = (
1397
- ""
1398
- if item["default"] is None
1399
- else f", default={item['default']}"
1400
- )
1401
- lines.append(
1402
- f" - {item['name']}: {item['type'] or 'Any'} "
1403
- f"({required}{default})"
1404
- )
1331
+ default = "" if item["default"] is None else f", default={item['default']}"
1332
+ lines.append(f" - {item['name']}: {item['type'] or 'Any'} ({required}{default})")
1405
1333
  else:
1406
1334
  lines.append(" - none")
1407
1335
 
1408
1336
  lines.append("Outputs:")
1409
1337
  if signature["outputs"]:
1410
1338
  for item in signature["outputs"]:
1411
- selector = (
1412
- f'output="{item["selector"]}"'
1413
- if item["selector"] is not None
1414
- else "no output selector"
1415
- )
1416
- lines.append(
1417
- f" - {item['name']}: {selector}; read {item['read']}"
1418
- )
1339
+ selector = f'output="{item["selector"]}"' if item["selector"] is not None else "no output selector"
1340
+ lines.append(f" - {item['name']}: {selector}; read {item['read']}")
1419
1341
  else:
1420
1342
  lines.append(" - none")
1421
1343
 
@@ -1628,11 +1550,7 @@ class SPLClient:
1628
1550
  *,
1629
1551
  version: int | None = None,
1630
1552
  ) -> dict[str, Any]:
1631
- ref = (
1632
- self._remote_node_payload(remote, version=version)
1633
- if self._is_node_remote(remote)
1634
- else dict(remote)
1635
- )
1553
+ ref = self._remote_node_payload(remote, version=version) if self._is_node_remote(remote) else dict(remote)
1636
1554
  if version is not None:
1637
1555
  ref["version"] = version
1638
1556
  return self._daemon.resolve_remote_decomposition(ref)
@@ -1735,11 +1653,7 @@ def export_objects_to_yaml(xs: list[Any], *, frame_offset: int = 2) -> str:
1735
1653
 
1736
1654
  top_level_deps = get_top_level_deps(frame_offset, xs)
1737
1655
 
1738
- mapping = {
1739
- root: DSPLSelfImport(name=cast(Any, root).name)
1740
- for (root, _) in top_level_deps
1741
- if hasattr(root, "name")
1742
- }
1656
+ mapping = {root: DSPLSelfImport(name=cast(Any, root).name) for (root, _) in top_level_deps if hasattr(root, "name")}
1743
1657
 
1744
1658
  normalized_deps = {
1745
1659
  root: [mapping.get(dependency, dependency) for dependency in dependencies]
@@ -1762,8 +1676,7 @@ def prepare_export_object(obj: Any, entrypoint: str | None) -> tuple[Any, str]:
1762
1676
  if entrypoint is None:
1763
1677
  if obj.name is None:
1764
1678
  raise ValueError(
1765
- "unnamed pipeline requires entrypoint; "
1766
- "use pipeline.render(name) or publish(..., entrypoint='name')"
1679
+ "unnamed pipeline requires entrypoint; use pipeline.render(name) or publish(..., entrypoint='name')"
1767
1680
  )
1768
1681
  return obj, obj.name
1769
1682
  return replace(obj, name=entrypoint), entrypoint
@@ -7,6 +7,9 @@ from functools import lru_cache
7
7
  from typing import Any
8
8
  from urllib.request import Request, urlopen
9
9
 
10
+ DEFAULT_HTTP_TIMEOUT_SECONDS = 60.0
11
+ DEFAULT_FILE_TRANSFER_TIMEOUT_SECONDS = 300.0
12
+
10
13
 
11
14
  @lru_cache(maxsize=1)
12
15
  def verified_https_context() -> ssl.SSLContext:
@@ -17,7 +20,11 @@ def verified_https_context() -> ssl.SSLContext:
17
20
  return ssl.create_default_context(cafile=certifi.where())
18
21
 
19
22
 
20
- def urlopen_verified(request: Request) -> Any:
23
+ def urlopen_verified(
24
+ request: Request,
25
+ *,
26
+ timeout: float | None = DEFAULT_HTTP_TIMEOUT_SECONDS,
27
+ ) -> Any:
21
28
  """Open a URL request with the bundled certifi CA bundle."""
22
29
 
23
- return urlopen(request, context=verified_https_context())
30
+ return urlopen(request, context=verified_https_context(), timeout=timeout)
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
  from collections.abc import Mapping
12
12
  from html import escape
13
13
  from pathlib import Path
14
- from typing import Any
14
+ from typing import Any, cast
15
15
 
16
16
  _EMPTY = "—"
17
17
  _DEFAULT_CELL_LIMIT = 72
@@ -40,10 +40,7 @@ def details_to_text(title: str, rows: list[tuple[str, Any]]) -> str:
40
40
  if not rows:
41
41
  return f"{title}: (empty)"
42
42
  width = max(len(label) for label, _ in rows)
43
- body = "\n".join(
44
- f"{label.ljust(width)}: {preview(value, limit=100)}"
45
- for label, value in rows
46
- )
43
+ body = "\n".join(f"{label.ljust(width)}: {preview(value, limit=100)}" for label, value in rows)
47
44
  return f"{title}:\n{body}"
48
45
 
49
46
 
@@ -57,10 +54,7 @@ def details_to_html(title: str, rows: list[tuple[str, Any]]) -> str:
57
54
  "</tr>"
58
55
  for label, value in rows
59
56
  )
60
- return (
61
- f"<div><b>{escape(title)}</b>"
62
- f"<table><tbody>{body}</tbody></table></div>"
63
- )
57
+ return f"<div><b>{escape(title)}</b><table><tbody>{body}</tbody></table></div>"
64
58
 
65
59
 
66
60
  def table_to_text(
@@ -72,19 +66,10 @@ def table_to_text(
72
66
 
73
67
  if not rows:
74
68
  return f"{title}: (empty)"
75
- normalized = [
76
- {header: preview(row.get(header), limit=_DEFAULT_CELL_LIMIT) for header in headers}
77
- for row in rows
78
- ]
79
- widths = {
80
- header: max(len(header), *(len(row[header]) for row in normalized))
81
- for header in headers
82
- }
69
+ normalized = [{header: preview(row.get(header), limit=_DEFAULT_CELL_LIMIT) for header in headers} for row in rows]
70
+ widths = {header: max(len(header), *(len(row[header]) for row in normalized)) for header in headers}
83
71
  head = " ".join(header.ljust(widths[header]) for header in headers)
84
- body = "\n".join(
85
- " ".join(row[header].ljust(widths[header]) for header in headers)
86
- for row in normalized
87
- )
72
+ body = "\n".join(" ".join(row[header].ljust(widths[header]) for header in headers) for row in normalized)
88
73
  return f"{title} ({len(rows)}):\n{head}\n{body}"
89
74
 
90
75
 
@@ -95,16 +80,9 @@ def table_to_html(
95
80
  ) -> str:
96
81
  """Render a compact HTML table for notebooks."""
97
82
 
98
- head = "".join(
99
- f"<th style='text-align:left'>{escape(header)}</th>" for header in headers
100
- )
83
+ head = "".join(f"<th style='text-align:left'>{escape(header)}</th>" for header in headers)
101
84
  body = "".join(
102
- "<tr>"
103
- + "".join(
104
- f"<td>{escape(preview(row.get(header), limit=160))}</td>"
105
- for header in headers
106
- )
107
- + "</tr>"
85
+ "<tr>" + "".join(f"<td>{escape(preview(row.get(header), limit=160))}</td>" for header in headers) + "</tr>"
108
86
  for row in rows
109
87
  )
110
88
  return (
@@ -169,7 +147,7 @@ class CompactDict(dict[str, Any]):
169
147
 
170
148
  @property
171
149
  def raw(self) -> dict[str, Any]:
172
- return plain(dict(self))
150
+ return cast(dict[str, Any], plain(dict(self)))
173
151
 
174
152
  def _summary_rows(self) -> list[tuple[str, Any]]:
175
153
  return [(str(key), value) for key, value in list(self.items())[:8]]
@@ -193,7 +171,7 @@ class CompactList(list[Any]):
193
171
 
194
172
  @property
195
173
  def raw(self) -> list[Any]:
196
- return plain(list(self))
174
+ return cast(list[Any], plain(list(self)))
197
175
 
198
176
  def _table_rows(self) -> list[dict[str, Any]]:
199
177
  return [{"item": item} for item in self]
@@ -209,32 +187,27 @@ class HealthView(CompactDict):
209
187
  title = "health"
210
188
 
211
189
  def _summary_rows(self) -> list[tuple[str, Any]]:
212
- counts = self.get("counts") if isinstance(self.get("counts"), Mapping) else {}
213
- db = self.get("db") if isinstance(self.get("db"), Mapping) else {}
214
- server = self.get("server") if isinstance(self.get("server"), Mapping) else {}
215
- builds = (
216
- self.get("environment_builds")
217
- if isinstance(self.get("environment_builds"), Mapping)
218
- else {}
219
- )
220
- by_status = builds.get("by_status") if isinstance(builds, Mapping) else {}
190
+ counts_value = self.get("counts")
191
+ db_value = self.get("db")
192
+ server_value = self.get("server")
193
+ builds_value = self.get("environment_builds")
194
+ counts = counts_value if isinstance(counts_value, Mapping) else {}
195
+ db = db_value if isinstance(db_value, Mapping) else {}
196
+ server = server_value if isinstance(server_value, Mapping) else {}
197
+ builds = builds_value if isinstance(builds_value, Mapping) else {}
198
+ by_status_value = builds.get("by_status")
199
+ by_status = by_status_value if isinstance(by_status_value, Mapping) else {}
221
200
  return [
222
201
  ("ok", self.get("ok")),
223
202
  ("db", f"{db.get('path', _EMPTY)} (exists={db.get('exists', _EMPTY)})"),
224
203
  ("server", "connected" if server.get("connected") else "offline"),
225
204
  (
226
205
  "counts",
227
- ", ".join(
228
- f"{key}={value}" for key, value in sorted(counts.items())
229
- )
230
- or _EMPTY,
206
+ ", ".join(f"{key}={value}" for key, value in sorted(counts.items())) or _EMPTY,
231
207
  ),
232
208
  (
233
209
  "env builds",
234
- ", ".join(
235
- f"{key}={value}" for key, value in sorted((by_status or {}).items())
236
- )
237
- or _EMPTY,
210
+ ", ".join(f"{key}={value}" for key, value in sorted((by_status or {}).items())) or _EMPTY,
238
211
  ),
239
212
  ]
240
213
 
@@ -424,7 +397,8 @@ class SignatureView(CompactDict):
424
397
  title = "signature"
425
398
 
426
399
  def _summary_rows(self) -> list[tuple[str, Any]]:
427
- call = self.get("call") if isinstance(self.get("call"), Mapping) else {}
400
+ call_value = self.get("call")
401
+ call = call_value if isinstance(call_value, Mapping) else {}
428
402
  return [
429
403
  ("name", _name(self)),
430
404
  ("kind", self.get("kind")),
@@ -489,11 +463,7 @@ class OutputListView(CompactList):
489
463
  ports = item.get("ports")
490
464
  output_type = None
491
465
  if isinstance(ports, list) and ports:
492
- output_type = ",".join(
493
- str(port.get("type") or "Any")
494
- for port in ports
495
- if isinstance(port, Mapping)
496
- )
466
+ output_type = ",".join(str(port.get("type") or "Any") for port in ports if isinstance(port, Mapping))
497
467
  rows.append(
498
468
  {
499
469
  "name": item.get("name"),
@@ -510,7 +480,8 @@ class DecompositionView(CompactDict):
510
480
  headers = ("node", "kind", "inputs", "outputs")
511
481
 
512
482
  def _node_rows(self) -> list[dict[str, Any]]:
513
- nodes = self.get("nodes") if isinstance(self.get("nodes"), list) else []
483
+ nodes_value = self.get("nodes")
484
+ nodes = nodes_value if isinstance(nodes_value, list) else []
514
485
  return [
515
486
  {
516
487
  "node": item.get("name") or item.get("function") or item.get("node_id"),
@@ -528,11 +499,7 @@ class DecompositionView(CompactDict):
528
499
  ("functions", len(self.get("functions") or [])),
529
500
  ("links", len(self.get("links") or [])),
530
501
  ]
531
- return (
532
- details_to_text(self.title, rows)
533
- + "\n\n"
534
- + table_to_text("nodes", self.headers, self._node_rows())
535
- )
502
+ return details_to_text(self.title, rows) + "\n\n" + table_to_text("nodes", self.headers, self._node_rows())
536
503
 
537
504
  def _repr_html_(self) -> str:
538
505
  rows = [
@@ -16,9 +16,4 @@ import spl.core.entities.scalar # noqa: F401
16
16
  from spl.core.entities.node_remote import NodeRemote
17
17
  from spl.core.ir.utils import spl_export_to_dir, spl_export_to_file, spl_import_from_file
18
18
 
19
- __all__ = [
20
- 'NodeRemote',
21
- 'spl_export_to_dir',
22
- 'spl_export_to_file',
23
- 'spl_import_from_file'
24
- ]
19
+ __all__ = ["NodeRemote", "spl_export_to_dir", "spl_export_to_file", "spl_import_from_file"]