arrayview 0.47.0__tar.gz → 0.47.3__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.
- {arrayview-0.47.0 → arrayview-0.47.3}/PKG-INFO +1 -1
- {arrayview-0.47.0 → arrayview-0.47.3}/pyproject.toml +1 -1
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_io.py +41 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_launcher.py +6 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_loading.py +13 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_preferences.py +1 -1
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_session.py +6 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_viewer.html +6 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_vscode_extension.py +1 -1
- arrayview-0.47.3/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/conftest.py +15 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_api.py +626 -4092
- arrayview-0.47.3/tests/test_browser.py +1010 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_launch_blackbox.py +69 -283
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_nifti_meta.py +23 -0
- arrayview-0.47.3/tests/test_vsix_bundle.py +19 -0
- arrayview-0.47.0/src/arrayview/arrayview-opener.vsix +0 -0
- arrayview-0.47.0/tests/lifecycle_matrix.py +0 -416
- arrayview-0.47.0/tests/test_bootstrap_app.py +0 -114
- arrayview-0.47.0/tests/test_browser.py +0 -7533
- arrayview-0.47.0/tests/test_colorbar_hover_highlight.py +0 -156
- arrayview-0.47.0/tests/test_display_opener_results.py +0 -536
- arrayview-0.47.0/tests/test_doctor_commands.py +0 -104
- arrayview-0.47.0/tests/test_invocation_contract_matrix.py +0 -232
- arrayview-0.47.0/tests/test_launch_diagnostics.py +0 -137
- arrayview-0.47.0/tests/test_launch_execution_ownership.py +0 -283
- arrayview-0.47.0/tests/test_launch_plan.py +0 -816
- arrayview-0.47.0/tests/test_launch_trace.py +0 -231
- arrayview-0.47.0/tests/test_lifecycle_contract.py +0 -2511
- arrayview-0.47.0/tests/test_loading_server.py +0 -107
- arrayview-0.47.0/tests/test_mode_consistency.py +0 -1248
- arrayview-0.47.0/tests/test_mode_matrix.py +0 -658
- arrayview-0.47.0/tests/test_preferences_ui.py +0 -185
- arrayview-0.47.0/tests/test_public_invocation_regressions.py +0 -350
- arrayview-0.47.0/tests/test_render_failure_reporting.py +0 -74
- arrayview-0.47.0/tests/test_render_progress.py +0 -275
- arrayview-0.47.0/tests/test_roi_hud.py +0 -200
- arrayview-0.47.0/tests/test_server_identity_status.py +0 -82
- arrayview-0.47.0/tests/test_session_spec.py +0 -93
- arrayview-0.47.0/tests/test_spawn_ownership.py +0 -175
- arrayview-0.47.0/tests/test_tunnel_warm_path.py +0 -160
- arrayview-0.47.0/tests/test_tutorial_browser.py +0 -481
- arrayview-0.47.0/tests/test_value_formatting.py +0 -126
- arrayview-0.47.0/tests/test_view_handle_lifecycle.py +0 -110
- arrayview-0.47.0/tests/test_view_launch_execution.py +0 -311
- arrayview-0.47.0/tests/test_vscode_ack_protocol.py +0 -485
- arrayview-0.47.0/tests/test_window_level_preview.py +0 -101
- {arrayview-0.47.0 → arrayview-0.47.3}/.gitignore +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/LICENSE +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/README.md +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/ARCHITECTURE.md +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/__init__.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/__main__.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_analysis.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_app.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_bootstrap_app.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_config.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_diagnostics.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_dicom.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_diff.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_extra_ports.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_icon.png +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_imaging.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_instance_registry.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_launch_plan.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_launch_trace.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_lifecycle.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_overlays.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_platform.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_render.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_analysis.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_drop.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_export.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_persistence.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_preload.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_query.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_rendering.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_segmentation.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_state.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_vectorfield.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_routes_websocket.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_segmentation.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_server.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_session_spec.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_shell.html +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_source_safety.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_synthetic_mri.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_torch.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_tutorial.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_vectorfield.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_vscode.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_vscode_browser.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_vscode_signal.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/_watch.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/gsap.min.js +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/src/arrayview/todo.md +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/capture_patient_loading.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/capture_v_animation.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_1d_arrays.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_backend_shared.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_cli.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_config.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_dicom.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_drop_import.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_instance_registry.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_io.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_large_arrays.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_mode_roundtrip.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_nifti_series.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_safe_server_stop.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_source_safety.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_torch.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/test_tutorial.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/ui_audit.py +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/v_anim_frames/.gitkeep +0 -0
- {arrayview-0.47.0 → arrayview-0.47.3}/tests/visual_smoke.py +0 -0
|
@@ -1863,3 +1863,44 @@ def _peek_file_shape(fpath: str, ext: str):
|
|
|
1863
1863
|
except Exception:
|
|
1864
1864
|
pass
|
|
1865
1865
|
return None
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
def nifti_grid_meta(filepath):
|
|
1869
|
+
"""Return a NIfTI file's native-order spatial metadata (header only), or None."""
|
|
1870
|
+
if not (filepath.endswith(".nii") or filepath.endswith(".nii.gz")):
|
|
1871
|
+
return None
|
|
1872
|
+
return _nifti_header_with_meta(filepath, native=True)[1]
|
|
1873
|
+
|
|
1874
|
+
|
|
1875
|
+
def align_overlay_to_base(ov_path, ov_data, base_meta):
|
|
1876
|
+
"""Resample a NIfTI overlay onto the base volume's voxel grid.
|
|
1877
|
+
|
|
1878
|
+
Only used when the overlay's spatial shape differs from the base, which
|
|
1879
|
+
otherwise leaves the overlay undisplayable. Both files' affines place the
|
|
1880
|
+
voxels in world space; label data uses nearest-neighbour so mask values
|
|
1881
|
+
stay exact. Returns ``ov_data`` unchanged when alignment does not apply.
|
|
1882
|
+
"""
|
|
1883
|
+
if not base_meta or not (ov_path.endswith(".nii") or ov_path.endswith(".nii.gz")):
|
|
1884
|
+
return ov_data
|
|
1885
|
+
base_shape = base_meta.get("spatial_shape")
|
|
1886
|
+
base_affine = base_meta.get("data_affine")
|
|
1887
|
+
if base_shape is None or base_affine is None:
|
|
1888
|
+
return ov_data
|
|
1889
|
+
ov_shape = tuple(int(v) for v in ov_data.shape)
|
|
1890
|
+
if len(ov_shape) != 3 or ov_shape == tuple(base_shape):
|
|
1891
|
+
return ov_data
|
|
1892
|
+
from scipy.ndimage import affine_transform
|
|
1893
|
+
|
|
1894
|
+
ov_affine = np.asarray(_nib().load(ov_path).affine, dtype=np.float64)
|
|
1895
|
+
base_to_ov = np.linalg.inv(ov_affine) @ np.asarray(base_affine, dtype=np.float64)
|
|
1896
|
+
src = np.asarray(ov_data)
|
|
1897
|
+
is_label = np.issubdtype(src.dtype, np.integer) or src.dtype == bool
|
|
1898
|
+
return affine_transform(
|
|
1899
|
+
src.astype(np.float32) if not is_label else src,
|
|
1900
|
+
base_to_ov[:3, :3],
|
|
1901
|
+
offset=base_to_ov[:3, 3],
|
|
1902
|
+
output_shape=tuple(base_shape),
|
|
1903
|
+
order=0 if is_label else 1,
|
|
1904
|
+
mode="constant",
|
|
1905
|
+
cval=0,
|
|
1906
|
+
)
|
|
@@ -1314,6 +1314,7 @@ def _load_session_from_filepath(
|
|
|
1314
1314
|
watch: bool = False,
|
|
1315
1315
|
initial_view: dict | None = None,
|
|
1316
1316
|
code_source: dict | None = None,
|
|
1317
|
+
align_to: str | None = None,
|
|
1317
1318
|
) -> dict:
|
|
1318
1319
|
requested_sid = uuid.uuid4().hex
|
|
1319
1320
|
payload = {
|
|
@@ -1329,6 +1330,8 @@ def _load_session_from_filepath(
|
|
|
1329
1330
|
}
|
|
1330
1331
|
if source_staging_dir:
|
|
1331
1332
|
payload["source_staging_dir"] = source_staging_dir
|
|
1333
|
+
if align_to:
|
|
1334
|
+
payload["align_to"] = align_to
|
|
1332
1335
|
if watch:
|
|
1333
1336
|
payload["watch"] = True
|
|
1334
1337
|
if initial_view:
|
|
@@ -1686,6 +1689,7 @@ def _register_cli_session_with_existing_server_impl(
|
|
|
1686
1689
|
ov_name,
|
|
1687
1690
|
expected_server_id=expected_server_id,
|
|
1688
1691
|
source_staging_dir=(source_staging_dirs or {}).get(ov_path),
|
|
1692
|
+
align_to=os.path.abspath(base_file),
|
|
1689
1693
|
)
|
|
1690
1694
|
if "error" in ov_result:
|
|
1691
1695
|
raise RuntimeError(
|
|
@@ -4803,6 +4807,7 @@ def _serve_daemon(
|
|
|
4803
4807
|
array_choice_placeholder,
|
|
4804
4808
|
load_data,
|
|
4805
4809
|
load_data_with_meta,
|
|
4810
|
+
align_overlay_to_base,
|
|
4806
4811
|
load_dir_collection,
|
|
4807
4812
|
list_array_keys,
|
|
4808
4813
|
)
|
|
@@ -4928,6 +4933,7 @@ def _serve_daemon(
|
|
|
4928
4933
|
):
|
|
4929
4934
|
try:
|
|
4930
4935
|
ov_data = load_data(ov_path)
|
|
4936
|
+
ov_data = align_overlay_to_base(ov_path, ov_data, spatial_meta)
|
|
4931
4937
|
ov_session = _session_mod.Session(
|
|
4932
4938
|
ov_data, filepath=ov_path, name=ov_name
|
|
4933
4939
|
)
|
|
@@ -277,6 +277,7 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
277
277
|
and os.path.abspath(existing.filepath) == abs_path
|
|
278
278
|
and current_signature is not None
|
|
279
279
|
and getattr(existing, "file_signature", None) == current_signature
|
|
280
|
+
and getattr(existing, "aligned_to", None) == body.get("align_to")
|
|
280
281
|
):
|
|
281
282
|
if acquire_session_leases([existing.sid]):
|
|
282
283
|
return {"sid": existing.sid, "name": existing.name, "notified": False}
|
|
@@ -458,6 +459,17 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
458
459
|
key=_key,
|
|
459
460
|
select=body.get("select"),
|
|
460
461
|
)
|
|
462
|
+
if body.get("align_to"):
|
|
463
|
+
from ._io import align_overlay_to_base, nifti_grid_meta
|
|
464
|
+
|
|
465
|
+
base_meta = await asyncio.to_thread(
|
|
466
|
+
nifti_grid_meta, str(body["align_to"])
|
|
467
|
+
)
|
|
468
|
+
aligned = await asyncio.to_thread(
|
|
469
|
+
align_overlay_to_base, filepath, data, base_meta
|
|
470
|
+
)
|
|
471
|
+
if aligned is not data:
|
|
472
|
+
data, spatial_meta = aligned, base_meta
|
|
461
473
|
except Exception as e:
|
|
462
474
|
if staging_dir:
|
|
463
475
|
cleanup_staging_directory(staging_dir)
|
|
@@ -479,6 +491,7 @@ def register_loading_routes(app, *, notify_shells, setup_rgb) -> None:
|
|
|
479
491
|
str(value) for value in body.get("related_sids", [])
|
|
480
492
|
]
|
|
481
493
|
apply_launch_view(session, body)
|
|
494
|
+
session.aligned_to = body.get("align_to")
|
|
482
495
|
if staging_dir:
|
|
483
496
|
session._source_staging_dirs = [str(staging_dir)]
|
|
484
497
|
if not dir_patterns:
|
|
@@ -37,7 +37,7 @@ def _response_payload() -> dict:
|
|
|
37
37
|
if not native_available:
|
|
38
38
|
defaults["window"]["default"] = "browser"
|
|
39
39
|
defaults["window"]["terminal"] = "browser"
|
|
40
|
-
for key in ("
|
|
40
|
+
for key in ("terminal", "vscode", "jupyter", "julia"):
|
|
41
41
|
schema["window"][key] = [
|
|
42
42
|
value for value in schema["window"][key] if value != "native"
|
|
43
43
|
]
|
|
@@ -765,6 +765,12 @@ def upgrade_memmap_in_background(session) -> bool:
|
|
|
765
765
|
from ._io import _nifti_display_array
|
|
766
766
|
|
|
767
767
|
loaded = _nifti_display_array(data)
|
|
768
|
+
# An uncompressed proxy whose dtype needs no conversion comes
|
|
769
|
+
# back as a view of the file map, so nothing was read yet and
|
|
770
|
+
# every new orientation would still fault from disk. Copy in
|
|
771
|
+
# file order to make it one sequential read.
|
|
772
|
+
if not loaded.flags.owndata:
|
|
773
|
+
loaded = np.array(loaded, copy=True, order="K")
|
|
768
774
|
except Exception:
|
|
769
775
|
return # keep serving from the lazy source
|
|
770
776
|
# A /reload or a released session must not be clobbered by a read that
|
|
@@ -7086,6 +7086,7 @@
|
|
|
7086
7086
|
let _mvPrevZoom = 1.0;
|
|
7087
7087
|
let _cmvPrevSquareStretch = false;
|
|
7088
7088
|
let _qmriPrevZoom = 1.0; // saved on enterQmri, restored on exitQmri (mirrors _mvPrevZoom)
|
|
7089
|
+
let _qmriPrevActiveDim = null; // scroll dim before qMRI moved it off the map dim; null while rebuilding keeps the original
|
|
7089
7090
|
let _comparePrevZoom = 1.0; // saved on enterCompareMode, restored on exitCompareMode
|
|
7090
7091
|
let _comparePrevNormalView = null; // Phase 4: saved primary View, restored on exitCompareMode
|
|
7091
7092
|
let _eggsVisible = true;
|
|
@@ -35472,6 +35473,7 @@
|
|
|
35472
35473
|
if (multiViewActive) exitMultiView();
|
|
35473
35474
|
if (stackMosaicActive) exitStackMosaic();
|
|
35474
35475
|
_qmriPrevZoom = userZoom; // save prior zoom so exitQmri can restore (mirrors enterMultiView)
|
|
35476
|
+
if (_qmriPrevActiveDim === null) _qmriPrevActiveDim = activeDim;
|
|
35475
35477
|
userZoom = 1.0;
|
|
35476
35478
|
qvPan.reset();
|
|
35477
35479
|
// Opening straight into qMRI never draws the main canvas, so its
|
|
@@ -35994,6 +35996,10 @@
|
|
|
35994
35996
|
wrap.innerHTML = '';
|
|
35995
35997
|
wrap.className = '';
|
|
35996
35998
|
userZoom = _qmriPrevZoom; // restore prior zoom (mirrors exitMultiView line 10521)
|
|
35999
|
+
if (!_qmriSyntheticRebuilding && _qmriPrevActiveDim !== null) {
|
|
36000
|
+
if (_qmriPrevActiveDim < shape.length) activeDim = _qmriPrevActiveDim;
|
|
36001
|
+
_qmriPrevActiveDim = null;
|
|
36002
|
+
}
|
|
35997
36003
|
if (!_qmriSyntheticRebuilding) updateView();
|
|
35998
36004
|
_reconcileUI();
|
|
35999
36005
|
renderInfo();
|
|
@@ -58,7 +58,7 @@ _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
|
|
|
58
58
|
_VSCODE_EXT_RELOAD_REQUIRED = False # installed files are newer than the live host
|
|
59
59
|
_VSCODE_EXT_INSTALL_FAILED = False # automatic install could not complete safely
|
|
60
60
|
_VSCODE_EXT_NO_LIVE_WINDOW = False # no live host claims this terminal's window id
|
|
61
|
-
_VSCODE_EXT_VERSION = "0.15.
|
|
61
|
+
_VSCODE_EXT_VERSION = "0.15.63" # current bundled extension version
|
|
62
62
|
_VSCODE_CONFIGURED_PORTS: set[int] = set()
|
|
63
63
|
# Version-skew notices already printed, so a long-lived process (Jupyter, a
|
|
64
64
|
# script opening several arrays) states the mismatch once instead of per call.
|
|
Binary file
|
|
@@ -34,6 +34,21 @@ def isolated_runtime_directory(tmp_path_factory, monkeypatch):
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
@pytest.fixture(autouse=True)
|
|
38
|
+
def no_leaked_fake_sessions():
|
|
39
|
+
"""Drop anything a test put in the shared session table that isn't a session.
|
|
40
|
+
|
|
41
|
+
The test server is shared by the whole run, so one stray placeholder makes
|
|
42
|
+
every later load in every later file fail.
|
|
43
|
+
"""
|
|
44
|
+
from arrayview._session import SESSIONS, Session
|
|
45
|
+
|
|
46
|
+
yield
|
|
47
|
+
for sid, value in list(SESSIONS.items()):
|
|
48
|
+
if not isinstance(value, Session):
|
|
49
|
+
SESSIONS.pop(sid, None)
|
|
50
|
+
|
|
51
|
+
|
|
37
52
|
# ---------------------------------------------------------------------------
|
|
38
53
|
# Server
|
|
39
54
|
# ---------------------------------------------------------------------------
|