arrayview 0.33.0__tar.gz → 0.33.1__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 (104) hide show
  1. {arrayview-0.33.0 → arrayview-0.33.1}/PKG-INFO +1 -1
  2. {arrayview-0.33.0 → arrayview-0.33.1}/pyproject.toml +1 -1
  3. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_instance_registry.py +3 -5
  4. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_launch_plan.py +8 -0
  5. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_launcher.py +6 -1
  6. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_server.py +1 -0
  7. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_vscode_extension.py +1 -1
  8. arrayview-0.33.1/src/arrayview/arrayview-opener.vsix +0 -0
  9. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_api.py +1 -0
  10. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_cli.py +1 -0
  11. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_instance_registry.py +4 -2
  12. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_launch_execution_ownership.py +3 -0
  13. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_launch_plan.py +38 -0
  14. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_server_identity_status.py +1 -0
  15. arrayview-0.33.0/src/arrayview/arrayview-opener.vsix +0 -0
  16. {arrayview-0.33.0 → arrayview-0.33.1}/.gitignore +0 -0
  17. {arrayview-0.33.0 → arrayview-0.33.1}/LICENSE +0 -0
  18. {arrayview-0.33.0 → arrayview-0.33.1}/README.md +0 -0
  19. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/ARCHITECTURE.md +0 -0
  20. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/__init__.py +0 -0
  21. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/__main__.py +0 -0
  22. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_analysis.py +0 -0
  23. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_app.py +0 -0
  24. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_codex_open.py +0 -0
  25. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_config.py +0 -0
  26. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_dicom.py +0 -0
  27. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_diff.py +0 -0
  28. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_icon.png +0 -0
  29. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_imaging.py +0 -0
  30. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_io.py +0 -0
  31. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_launch_trace.py +0 -0
  32. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_lifecycle.py +0 -0
  33. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_overlays.py +0 -0
  34. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_platform.py +0 -0
  35. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_render.py +0 -0
  36. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_analysis.py +0 -0
  37. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_drop.py +0 -0
  38. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_export.py +0 -0
  39. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_loading.py +0 -0
  40. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_persistence.py +0 -0
  41. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_preload.py +0 -0
  42. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_query.py +0 -0
  43. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_rendering.py +0 -0
  44. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_segmentation.py +0 -0
  45. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_state.py +0 -0
  46. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_vectorfield.py +0 -0
  47. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_routes_websocket.py +0 -0
  48. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_segmentation.py +0 -0
  49. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_session.py +0 -0
  50. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_session_spec.py +0 -0
  51. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_shell.html +0 -0
  52. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_synthetic_mri.py +0 -0
  53. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_torch.py +0 -0
  54. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_vectorfield.py +0 -0
  55. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_viewer.html +0 -0
  56. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_vscode.py +0 -0
  57. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_vscode_browser.py +0 -0
  58. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/_vscode_signal.py +0 -0
  59. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/gsap.min.js +0 -0
  60. {arrayview-0.33.0 → arrayview-0.33.1}/src/arrayview/todo.md +0 -0
  61. {arrayview-0.33.0 → arrayview-0.33.1}/tests/capture_patient_loading.py +0 -0
  62. {arrayview-0.33.0 → arrayview-0.33.1}/tests/capture_v_animation.py +0 -0
  63. {arrayview-0.33.0 → arrayview-0.33.1}/tests/conftest.py +0 -0
  64. {arrayview-0.33.0 → arrayview-0.33.1}/tests/lifecycle_matrix.py +0 -0
  65. {arrayview-0.33.0 → arrayview-0.33.1}/tests/make_vectorfield_test_arrays.py +0 -0
  66. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_backend_shared.py +0 -0
  67. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_browser.py +0 -0
  68. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_codex_open.py +0 -0
  69. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_colorbar_hover_highlight.py +0 -0
  70. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_command_reachability.py +0 -0
  71. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_config.py +0 -0
  72. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_cross_mode_parametrized.py +0 -0
  73. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_dicom.py +0 -0
  74. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_display_opener_results.py +0 -0
  75. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_doctor_commands.py +0 -0
  76. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_drop_import.py +0 -0
  77. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_interactions.py +0 -0
  78. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_invocation_contract_matrix.py +0 -0
  79. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_io.py +0 -0
  80. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_large_arrays.py +0 -0
  81. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_launch_blackbox.py +0 -0
  82. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_launch_diagnostics.py +0 -0
  83. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_launch_trace.py +0 -0
  84. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_lifecycle_contract.py +0 -0
  85. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_loading_server.py +0 -0
  86. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_mode_consistency.py +0 -0
  87. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_mode_matrix.py +0 -0
  88. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_mode_roundtrip.py +0 -0
  89. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_nifti_meta.py +0 -0
  90. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_nifti_series.py +0 -0
  91. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_public_invocation_regressions.py +0 -0
  92. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_rgb_pixel_art.py +0 -0
  93. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_safe_server_stop.py +0 -0
  94. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_session_spec.py +0 -0
  95. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_spawn_ownership.py +0 -0
  96. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_torch.py +0 -0
  97. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_view_component_integration.py +0 -0
  98. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_view_component_unit.py +0 -0
  99. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_view_handle_lifecycle.py +0 -0
  100. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_view_launch_execution.py +0 -0
  101. {arrayview-0.33.0 → arrayview-0.33.1}/tests/test_vscode_ack_protocol.py +0 -0
  102. {arrayview-0.33.0 → arrayview-0.33.1}/tests/ui_audit.py +0 -0
  103. {arrayview-0.33.0 → arrayview-0.33.1}/tests/v_anim_frames/.gitkeep +0 -0
  104. {arrayview-0.33.0 → arrayview-0.33.1}/tests/visual_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.33.0
3
+ Version: 0.33.1
4
4
  Summary: Fast multi-dimensional array viewer
5
5
  Project-URL: Home, https://github.com/oscarvanderheide/arrayview
6
6
  Project-URL: Source, https://github.com/oscarvanderheide/arrayview
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.33.0"
7
+ version = "0.33.1"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -9,8 +9,6 @@ from __future__ import annotations
9
9
  from contextlib import contextmanager
10
10
  from dataclasses import asdict, dataclass
11
11
  import ctypes
12
- import getpass
13
- import hashlib
14
12
  import json
15
13
  import os
16
14
  from pathlib import Path
@@ -70,9 +68,9 @@ def runtime_directory() -> Path:
70
68
  base = os.environ.get("XDG_RUNTIME_DIR")
71
69
  if base:
72
70
  return Path(base) / "arrayview"
73
- user = getpass.getuser().encode("utf-8", "replace")
74
- suffix = hashlib.sha256(user).hexdigest()[:12]
75
- return Path(tempfile.gettempdir()) / f"arrayview-{suffix}"
71
+ if hasattr(os, "getuid"):
72
+ return Path(tempfile.gettempdir()) / f"arrayview-{os.getuid()}"
73
+ return Path(tempfile.gettempdir()) / f"arrayview-{os.getpid()}"
76
74
 
77
75
 
78
76
  def process_start_identity(pid: int) -> str | None:
@@ -132,6 +132,7 @@ class ServerSnapshot:
132
132
  server_process_start: str | None = None
133
133
  server_capabilities: tuple[str, ...] = ()
134
134
  server_protocol_version: str | None = None
135
+ server_uid: int | None = None
135
136
 
136
137
 
137
138
  @dataclass(frozen=True)
@@ -635,6 +636,7 @@ def _server_snapshot(port: int) -> ServerSnapshot:
635
636
  port_busy=_port_busy(port),
636
637
  arrayview_server_alive=payload is not None,
637
638
  server_pid=_int_or_none(payload.get("pid")) if payload else None,
639
+ server_uid=_int_or_none(payload.get("uid")) if payload else None,
638
640
  server_hostname=_str_or_none(payload.get("hostname")) if payload else None,
639
641
  server_instance_id=(
640
642
  _str_or_none(payload.get("instance_id")) if payload else None
@@ -658,8 +660,14 @@ def _server_snapshot(port: int) -> ServerSnapshot:
658
660
 
659
661
 
660
662
  def _server_reusable(server: ServerSnapshot) -> bool:
663
+ same_user = (
664
+ server.server_uid == os.geteuid()
665
+ if hasattr(os, "geteuid")
666
+ else True
667
+ )
661
668
  return (
662
669
  server.arrayview_server_alive
670
+ and same_user
663
671
  and server.server_instance_id is not None
664
672
  and server.server_protocol_version == "1"
665
673
  and "identity-fenced-load" in server.server_capabilities
@@ -111,8 +111,13 @@ def _register_server_runtime(port: int, owner_mode: str):
111
111
  from arrayview._instance_registry import InstanceRecord, InstanceRegistry
112
112
 
113
113
  registry = InstanceRegistry()
114
+ registry._prepare()
114
115
  log_directory = registry.directory / "logs"
115
- log_directory.mkdir(parents=True, exist_ok=True)
116
+ log_directory.mkdir(parents=True, exist_ok=True, mode=0o700)
117
+ try:
118
+ os.chmod(log_directory, 0o700)
119
+ except OSError:
120
+ pass
116
121
  record = InstanceRecord.create(
117
122
  port=port,
118
123
  protocol_version=_server_mod().SERVER_PROTOCOL_VERSION,
@@ -228,6 +228,7 @@ def ping():
228
228
  "ok": True,
229
229
  "service": "arrayview",
230
230
  "pid": os.getpid(),
231
+ "uid": os.geteuid() if hasattr(os, "geteuid") else None,
231
232
  "hostname": socket.gethostname(),
232
233
  "protocol_version": runtime.protocol_version,
233
234
  "package_version": _av_version,
@@ -54,7 +54,7 @@ _VSCODE_EXT_INSTALLED = False # cached so we only check once per process
54
54
  _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
55
55
  _VSCODE_EXT_RELOAD_REQUIRED = False # installed files are newer than the live host
56
56
  _VSCODE_EXT_INSTALL_FAILED = False # automatic install could not complete safely
57
- _VSCODE_EXT_VERSION = "0.14.70" # current bundled extension version
57
+ _VSCODE_EXT_VERSION = "0.14.71" # current bundled extension version
58
58
  _VSCODE_CONFIGURED_PORTS: set[int] = set()
59
59
 
60
60
  def _bundled_vscode_vsix_version(vsix_path: str) -> str | None:
@@ -6580,6 +6580,7 @@ def _isolate_view_planner(
6580
6580
  if port in alive_ports
6581
6581
  else (),
6582
6582
  "1" if port in alive_ports else None,
6583
+ server_uid=os.geteuid() if port in alive_ports else None,
6583
6584
  ),
6584
6585
  )
6585
6586
  if alive_ports:
@@ -60,6 +60,7 @@ def _mock_launch_server_snapshot(monkeypatch, *, alive: bool, busy: bool | None
60
60
  None,
61
61
  ("identity-fenced-load", "identity-fenced-mutations") if alive else (),
62
62
  "1" if alive else None,
63
+ server_uid=os.geteuid() if alive else None,
63
64
  ),
64
65
  )
65
66
  if alive:
@@ -164,15 +164,17 @@ def test_runtime_directory_override(monkeypatch, tmp_path):
164
164
  assert runtime_directory() == tmp_path / "custom"
165
165
 
166
166
 
167
- def test_runtime_directory_is_per_user_without_override(monkeypatch, tmp_path):
167
+ def test_runtime_directory_is_per_uid_without_override(monkeypatch, tmp_path):
168
168
  monkeypatch.delenv("ARRAYVIEW_RUNTIME_DIR", raising=False)
169
169
  monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False)
170
170
  monkeypatch.setattr("arrayview._instance_registry.tempfile.gettempdir", lambda: str(tmp_path))
171
+ current_uid = os.getuid()
171
172
  first = runtime_directory()
172
- monkeypatch.setattr("arrayview._instance_registry.getpass.getuser", lambda: "different-user")
173
+ monkeypatch.setattr("arrayview._instance_registry.os.getuid", lambda: current_uid + 1)
173
174
  second = runtime_directory()
174
175
  assert first.parent == second.parent == tmp_path
175
176
  assert first != second
177
+ assert first.name == f"arrayview-{current_uid}"
176
178
 
177
179
 
178
180
  def test_import_does_not_load_heavy_dependencies():
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import os
4
5
  from threading import Thread as RealThread
5
6
 
6
7
  import numpy as np
@@ -167,6 +168,7 @@ def test_http_load_plan_fails_closed_when_selected_server_changes(
167
168
  "planned-process-start",
168
169
  ("identity-fenced-load", "identity-fenced-mutations"),
169
170
  "1",
171
+ server_uid=os.geteuid(),
170
172
  ),
171
173
  )
172
174
  monkeypatch.setattr(
@@ -222,6 +224,7 @@ def test_display_error_after_http_registration_is_not_relabelled(monkeypatch):
222
224
  "planned-process-start",
223
225
  ("identity-fenced-load", "identity-fenced-mutations"),
224
226
  "1",
227
+ server_uid=os.geteuid(),
225
228
  ),
226
229
  )
227
230
  monkeypatch.setattr(
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  import socket
3
4
  import sys
4
5
  from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -42,6 +43,7 @@ class _PingHandler(BaseHTTPRequestHandler):
42
43
  "ok": True,
43
44
  "service": "arrayview",
44
45
  "pid": 4321,
46
+ "uid": os.geteuid() if hasattr(os, "geteuid") else None,
45
47
  "hostname": "test-host",
46
48
  "instance_id": "test-instance",
47
49
  "process_start": "test-process-start",
@@ -223,6 +225,9 @@ def test_snapshot_reports_arrayview_server_ping(monkeypatch, clear_launch_env, p
223
225
  assert snapshot.server.port_busy is True
224
226
  assert snapshot.server.arrayview_server_alive is True
225
227
  assert snapshot.server.server_pid == 4321
228
+ assert snapshot.server.server_uid == (
229
+ os.geteuid() if hasattr(os, "geteuid") else None
230
+ )
226
231
  assert snapshot.server.server_hostname == "test-host"
227
232
  assert snapshot.server.server_instance_id == "test-instance"
228
233
  assert snapshot.server.server_process_start == "test-process-start"
@@ -581,6 +586,7 @@ def test_remote_python_reuses_healthy_cli_default_server():
581
586
  "remote-process-start",
582
587
  ("identity-fenced-load", "identity-fenced-mutations"),
583
588
  "1",
589
+ server_uid=os.geteuid(),
584
590
  ),
585
591
  )
586
592
  plan = plan_launch(LaunchIntent(Invocation.PYTHON, 8123), facts)
@@ -656,6 +662,7 @@ def test_existing_server_is_reused_through_http():
656
662
  "process-start",
657
663
  ("identity-fenced-load", "identity-fenced-mutations"),
658
664
  "1",
665
+ server_uid=os.geteuid(),
659
666
  )
660
667
  )
661
668
  plan = plan_launch(LaunchIntent(Invocation.CLI, 8123), facts)
@@ -664,6 +671,37 @@ def test_existing_server_is_reused_through_http():
664
671
  assert plan.registration.value == "http_load"
665
672
 
666
673
 
674
+ @pytest.mark.parametrize(
675
+ "server_uid",
676
+ [None, os.geteuid() + 1],
677
+ ids=["missing-uid", "different-uid"],
678
+ )
679
+ def test_unowned_existing_server_uses_new_port(server_uid):
680
+ from arrayview._launch_plan import Invocation, LaunchIntent, ServerSnapshot, plan_launch
681
+
682
+ facts = _facts(
683
+ server=ServerSnapshot(
684
+ 8123,
685
+ True,
686
+ True,
687
+ 99,
688
+ "host",
689
+ "instance",
690
+ "process-start",
691
+ ("identity-fenced-load", "identity-fenced-mutations"),
692
+ "1",
693
+ server_uid=server_uid,
694
+ )
695
+ )
696
+
697
+ plan = plan_launch(LaunchIntent(Invocation.PYTHON, 8123), facts)
698
+
699
+ assert plan.effective_port == 8124
700
+ assert plan.server_owner.value == "in_process"
701
+ assert plan.registration.value == "in_process_session"
702
+ assert "incompatible_server_new_port" in plan.reasons
703
+
704
+
667
705
  def test_julia_always_spawns_daemon():
668
706
  from arrayview._launch_plan import Environment, Invocation, LaunchIntent, plan_launch
669
707
 
@@ -29,6 +29,7 @@ def test_ping_preserves_compatibility_and_reports_stable_runtime(monkeypatch):
29
29
  assert first["ok"] is True
30
30
  assert first["service"] == "arrayview"
31
31
  assert first["pid"] == os.getpid()
32
+ assert first["uid"] == (os.geteuid() if hasattr(os, "geteuid") else None)
32
33
  assert first["viewer_sockets"] == 2
33
34
  assert first["shell_sockets"] == 1
34
35
  assert first["instance_id"] == "test-instance"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes