nds-mapviewer 2026.4.3.dev253__tar.gz → 2026.4.3.dev254__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.
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/CHANGELOG.md +0 -5
- {nds_mapviewer-2026.4.3.dev253/src/nds_mapviewer.egg-info → nds_mapviewer-2026.4.3.dev254}/PKG-INFO +4 -13
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/README.md +3 -12
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254/src/nds_mapviewer.egg-info}/PKG-INFO +4 -13
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/cli.py +6 -16
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/config.py +5 -70
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/tui.py +0 -3
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/conftest.py +0 -24
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_cli.py +0 -36
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_config.py +2 -87
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/.gitignore +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/pyproject.toml +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/setup.cfg +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/setup.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/SOURCES.txt +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/dependency_links.txt +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/entry_points.txt +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/requires.txt +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/scm_file_list.json +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/top_level.txt +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/__init__.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/browser.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/clipboard.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/container_runtime.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/docker_client.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/exceptions.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/ui.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/viewer.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_clipboard.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_docker_client.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_tui.py +0 -0
- {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_viewer.py +0 -0
|
@@ -29,11 +29,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
29
29
|
|
|
30
30
|
### Fixed
|
|
31
31
|
|
|
32
|
-
- A `.ndslive` filestore, SmartLayer folder or service URL holding object
|
|
33
|
-
data is now configured as `SmartLayerObjectService` instead of always
|
|
34
|
-
`SmartLayerTileService`, which the viewer rejected at startup; detection
|
|
35
|
-
reads the filestore tables, an `objects/` folder, or the service's OpenAPI
|
|
36
|
-
spec (falls back to tile when the spec cannot be fetched)
|
|
37
32
|
- Clipboard copy in the Docker screen now uses the native OS clipboard
|
|
38
33
|
(`clip.exe` / `pbcopy` / `wl-copy`/`xclip`/`xsel`) instead of relying on the
|
|
39
34
|
OSC 52 terminal escape, which is unreliable on Windows; warns when a copy
|
{nds_mapviewer-2026.4.3.dev253/src/nds_mapviewer.egg-info → nds_mapviewer-2026.4.3.dev254}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nds-mapviewer
|
|
3
|
-
Version: 2026.4.3.
|
|
3
|
+
Version: 2026.4.3.dev254
|
|
4
4
|
Summary: CLI to run the NDS MapViewer Docker container for visualizing map data (NDS.Live, GeoJSON, and more)
|
|
5
5
|
Author-email: NDS Association <support@nds-association.org>
|
|
6
6
|
License: Proprietary
|
|
@@ -49,7 +49,7 @@ Full documentation: **https://developer.nds.live/tools/mapviewer**
|
|
|
49
49
|
- **Multiple data formats** — NDS.Live, GeoJSON, NDS.Classic (member edition)
|
|
50
50
|
- **Live style editing** — modify map styles in real time with YAML-based rules
|
|
51
51
|
- **Feature inspection** — click any map feature to explore its attributes and relations
|
|
52
|
-
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
52
|
+
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
53
53
|
- **Guided setup** — interactive wizard for creating data source configs
|
|
54
54
|
- **Config management** — save, switch, and delete configs from the RunScreen (`c`)
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ Either Docker or [Podman](https://podman.io) works as the container runtime —
|
|
|
71
71
|
```bash
|
|
72
72
|
mapviewer # HomeScreen (or auto-loads ~/.nds/config.yaml)
|
|
73
73
|
|
|
74
|
-
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
74
|
+
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
75
75
|
mapviewer /path/to/folder # View a local SmartLayer folder
|
|
76
76
|
mapviewer /path/to/geojson/ # View a GeoJSON folder
|
|
77
77
|
mapviewer https://host/openapi.json # Connect to a remote service (unauthenticated)
|
|
@@ -87,19 +87,10 @@ mapviewer --demo --network host # Run the container on the host network
|
|
|
87
87
|
|
|
88
88
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
89
89
|
|
|
90
|
-
### Tile and object maps
|
|
91
|
-
|
|
92
|
-
Local sources are detected as tile or object maps: a `.ndslive` filestore with an
|
|
93
|
-
`objectTable`, or a SmartLayer folder with an `objects/` directory, becomes a
|
|
94
|
-
`SmartLayerObjectService`; everything else a `SmartLayerTileService`. Object maps
|
|
95
|
-
need MapViewer image 2026.4.2 or newer.
|
|
96
|
-
|
|
97
90
|
### Remote service URLs
|
|
98
91
|
|
|
99
92
|
A source can be an http(s) service URL (e.g. `mapviewer https://my-host/openapi.json`),
|
|
100
|
-
which is turned into a `SmartLayerTileService
|
|
101
|
-
OpenAPI spec offers `getObjectByReference`. If the spec cannot be fetched, the tile
|
|
102
|
-
service is assumed. The bare-URL form configures **no
|
|
93
|
+
which is turned into a `SmartLayerTileService`. The bare-URL form configures **no
|
|
103
94
|
authentication** — for protected services, proxy them through `ndslive-server` or
|
|
104
95
|
declare the service in a `config.yaml` with an `http-settings` block (api-key, basic,
|
|
105
96
|
bearer, OAuth2, proxy).
|
|
@@ -16,7 +16,7 @@ Full documentation: **https://developer.nds.live/tools/mapviewer**
|
|
|
16
16
|
- **Multiple data formats** — NDS.Live, GeoJSON, NDS.Classic (member edition)
|
|
17
17
|
- **Live style editing** — modify map styles in real time with YAML-based rules
|
|
18
18
|
- **Feature inspection** — click any map feature to explore its attributes and relations
|
|
19
|
-
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
19
|
+
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
20
20
|
- **Guided setup** — interactive wizard for creating data source configs
|
|
21
21
|
- **Config management** — save, switch, and delete configs from the RunScreen (`c`)
|
|
22
22
|
|
|
@@ -38,7 +38,7 @@ Either Docker or [Podman](https://podman.io) works as the container runtime —
|
|
|
38
38
|
```bash
|
|
39
39
|
mapviewer # HomeScreen (or auto-loads ~/.nds/config.yaml)
|
|
40
40
|
|
|
41
|
-
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
41
|
+
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
42
42
|
mapviewer /path/to/folder # View a local SmartLayer folder
|
|
43
43
|
mapviewer /path/to/geojson/ # View a GeoJSON folder
|
|
44
44
|
mapviewer https://host/openapi.json # Connect to a remote service (unauthenticated)
|
|
@@ -54,19 +54,10 @@ mapviewer --demo --network host # Run the container on the host network
|
|
|
54
54
|
|
|
55
55
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
56
56
|
|
|
57
|
-
### Tile and object maps
|
|
58
|
-
|
|
59
|
-
Local sources are detected as tile or object maps: a `.ndslive` filestore with an
|
|
60
|
-
`objectTable`, or a SmartLayer folder with an `objects/` directory, becomes a
|
|
61
|
-
`SmartLayerObjectService`; everything else a `SmartLayerTileService`. Object maps
|
|
62
|
-
need MapViewer image 2026.4.2 or newer.
|
|
63
|
-
|
|
64
57
|
### Remote service URLs
|
|
65
58
|
|
|
66
59
|
A source can be an http(s) service URL (e.g. `mapviewer https://my-host/openapi.json`),
|
|
67
|
-
which is turned into a `SmartLayerTileService
|
|
68
|
-
OpenAPI spec offers `getObjectByReference`. If the spec cannot be fetched, the tile
|
|
69
|
-
service is assumed. The bare-URL form configures **no
|
|
60
|
+
which is turned into a `SmartLayerTileService`. The bare-URL form configures **no
|
|
70
61
|
authentication** — for protected services, proxy them through `ndslive-server` or
|
|
71
62
|
declare the service in a `config.yaml` with an `http-settings` block (api-key, basic,
|
|
72
63
|
bearer, OAuth2, proxy).
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254/src/nds_mapviewer.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nds-mapviewer
|
|
3
|
-
Version: 2026.4.3.
|
|
3
|
+
Version: 2026.4.3.dev254
|
|
4
4
|
Summary: CLI to run the NDS MapViewer Docker container for visualizing map data (NDS.Live, GeoJSON, and more)
|
|
5
5
|
Author-email: NDS Association <support@nds-association.org>
|
|
6
6
|
License: Proprietary
|
|
@@ -49,7 +49,7 @@ Full documentation: **https://developer.nds.live/tools/mapviewer**
|
|
|
49
49
|
- **Multiple data formats** — NDS.Live, GeoJSON, NDS.Classic (member edition)
|
|
50
50
|
- **Live style editing** — modify map styles in real time with YAML-based rules
|
|
51
51
|
- **Feature inspection** — click any map feature to explore its attributes and relations
|
|
52
|
-
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
52
|
+
- **Local and remote data** — view SmartLayer folders, FileStore files, or remote services
|
|
53
53
|
- **Guided setup** — interactive wizard for creating data source configs
|
|
54
54
|
- **Config management** — save, switch, and delete configs from the RunScreen (`c`)
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ Either Docker or [Podman](https://podman.io) works as the container runtime —
|
|
|
71
71
|
```bash
|
|
72
72
|
mapviewer # HomeScreen (or auto-loads ~/.nds/config.yaml)
|
|
73
73
|
|
|
74
|
-
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
74
|
+
mapviewer /path/to/data.ndslive # View a local FileStore
|
|
75
75
|
mapviewer /path/to/folder # View a local SmartLayer folder
|
|
76
76
|
mapviewer /path/to/geojson/ # View a GeoJSON folder
|
|
77
77
|
mapviewer https://host/openapi.json # Connect to a remote service (unauthenticated)
|
|
@@ -87,19 +87,10 @@ mapviewer --demo --network host # Run the container on the host network
|
|
|
87
87
|
|
|
88
88
|
All commands accept `-e community|member` and `-i VERSION`.
|
|
89
89
|
|
|
90
|
-
### Tile and object maps
|
|
91
|
-
|
|
92
|
-
Local sources are detected as tile or object maps: a `.ndslive` filestore with an
|
|
93
|
-
`objectTable`, or a SmartLayer folder with an `objects/` directory, becomes a
|
|
94
|
-
`SmartLayerObjectService`; everything else a `SmartLayerTileService`. Object maps
|
|
95
|
-
need MapViewer image 2026.4.2 or newer.
|
|
96
|
-
|
|
97
90
|
### Remote service URLs
|
|
98
91
|
|
|
99
92
|
A source can be an http(s) service URL (e.g. `mapviewer https://my-host/openapi.json`),
|
|
100
|
-
which is turned into a `SmartLayerTileService
|
|
101
|
-
OpenAPI spec offers `getObjectByReference`. If the spec cannot be fetched, the tile
|
|
102
|
-
service is assumed. The bare-URL form configures **no
|
|
93
|
+
which is turned into a `SmartLayerTileService`. The bare-URL form configures **no
|
|
103
94
|
authentication** — for protected services, proxy them through `ndslive-server` or
|
|
104
95
|
declare the service in a `config.yaml` with an `http-settings` block (api-key, basic,
|
|
105
96
|
bearer, OAuth2, proxy).
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/cli.py
RENAMED
|
@@ -118,9 +118,7 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
118
118
|
create_geojson_config,
|
|
119
119
|
create_classic_config,
|
|
120
120
|
create_service_config,
|
|
121
|
-
detect_service_type,
|
|
122
121
|
get_demo_config,
|
|
123
|
-
SMART_TILE_SERVICE,
|
|
124
122
|
)
|
|
125
123
|
from .tui import MapViewerApp
|
|
126
124
|
|
|
@@ -222,21 +220,13 @@ def cmd_main(args: argparse.Namespace) -> int:
|
|
|
222
220
|
source_config_path: Path | None = None
|
|
223
221
|
|
|
224
222
|
for source_arg in source_args:
|
|
225
|
-
# Remote service: an http(s) URL is turned into a
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
# ndslive-server as a proxy or a config.yaml for authenticated
|
|
223
|
+
# Remote service: an http(s) URL is turned into a SmartLayerTileService
|
|
224
|
+
# source directly. Checked before the path:mapId split below because
|
|
225
|
+
# URLs contain colons. No auth is configured for the bare-URL form —
|
|
226
|
+
# use ndslive-server as a proxy or a config.yaml for authenticated
|
|
230
227
|
# services.
|
|
231
228
|
if source_arg.startswith(("http://", "https://")):
|
|
232
|
-
|
|
233
|
-
if service_type is None:
|
|
234
|
-
service_type = SMART_TILE_SERVICE
|
|
235
|
-
console.print(
|
|
236
|
-
f"[yellow]Note:[/yellow] could not read the service spec of "
|
|
237
|
-
f"{source_arg}, assuming {SMART_TILE_SERVICE}"
|
|
238
|
-
)
|
|
239
|
-
config = create_service_config(source_arg, service_type=service_type)
|
|
229
|
+
config = create_service_config(source_arg)
|
|
240
230
|
descriptions.append(f"service: {source_arg}")
|
|
241
231
|
all_sources.extend(config.get("sources", []))
|
|
242
232
|
all_http_settings.extend(config.get("http-settings", []))
|
|
@@ -330,7 +320,7 @@ Examples:
|
|
|
330
320
|
# Interactive HomeScreen
|
|
331
321
|
mapviewer
|
|
332
322
|
|
|
333
|
-
# Quick start with local data
|
|
323
|
+
# Quick start with local data
|
|
334
324
|
mapviewer /path/to/data.ndslive
|
|
335
325
|
mapviewer /path/to/smartlayer/folder
|
|
336
326
|
mapviewer /path/to/geojson/folder
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/config.py
RENAMED
|
@@ -4,9 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
-
import sqlite3
|
|
8
7
|
import tempfile
|
|
9
|
-
import urllib.request
|
|
10
8
|
from dataclasses import dataclass, field
|
|
11
9
|
from pathlib import Path, PurePath
|
|
12
10
|
from typing import Any
|
|
@@ -31,12 +29,6 @@ CUSTOM_STYLES_ROOT = "/custom-styles"
|
|
|
31
29
|
CUSTOM_BACKGROUNDS_ROOT = "/custom-backgrounds"
|
|
32
30
|
COORDINATE_LEGAL_TERMS_ROOT = "/coordinates-legal-terms"
|
|
33
31
|
|
|
34
|
-
# NDS.Live SmartLayer datasource types; the viewer rejects a source whose
|
|
35
|
-
# type does not match the data's addressing mode.
|
|
36
|
-
SMART_TILE_SERVICE = "SmartLayerTileService"
|
|
37
|
-
SMART_OBJECT_SERVICE = "SmartLayerObjectService"
|
|
38
|
-
SERVICE_SPEC_TIMEOUT_S = 5
|
|
39
|
-
|
|
40
32
|
# Default config for demo mode
|
|
41
33
|
DEFAULT_DEMO_CONFIG = {
|
|
42
34
|
"http-settings": [
|
|
@@ -696,61 +688,6 @@ def detect_directory_type(path: Path) -> str:
|
|
|
696
688
|
return "folder"
|
|
697
689
|
|
|
698
690
|
|
|
699
|
-
# --- SmartLayer kind detection ---
|
|
700
|
-
|
|
701
|
-
def detect_filestore_type(filestore_path: str | Path) -> str:
|
|
702
|
-
"""Return the SmartLayer datasource type for a ``.ndslive`` filestore.
|
|
703
|
-
|
|
704
|
-
Object filestores carry an ``objectTable``, tile filestores a
|
|
705
|
-
``tileTable``. Unreadable files fall back to the tile type and leave the
|
|
706
|
-
error reporting to the viewer.
|
|
707
|
-
"""
|
|
708
|
-
uri = f"{Path(filestore_path).resolve().as_uri()}?mode=ro"
|
|
709
|
-
try:
|
|
710
|
-
conn = sqlite3.connect(uri, uri=True)
|
|
711
|
-
try:
|
|
712
|
-
row = conn.execute(
|
|
713
|
-
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='objectTable'"
|
|
714
|
-
).fetchone()
|
|
715
|
-
finally:
|
|
716
|
-
conn.close()
|
|
717
|
-
except sqlite3.Error:
|
|
718
|
-
return SMART_TILE_SERVICE
|
|
719
|
-
return SMART_OBJECT_SERVICE if row else SMART_TILE_SERVICE
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
def detect_folder_type(folder_path: str | Path) -> str:
|
|
723
|
-
"""Return the SmartLayer datasource type for an unpacked SmartLayer folder.
|
|
724
|
-
|
|
725
|
-
Object folders keep their records in an ``objects/`` subdirectory.
|
|
726
|
-
"""
|
|
727
|
-
if (Path(folder_path) / "objects").is_dir():
|
|
728
|
-
return SMART_OBJECT_SERVICE
|
|
729
|
-
return SMART_TILE_SERVICE
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
def _fetch_service_spec(url: str) -> str | None:
|
|
733
|
-
"""Fetch a service's OpenAPI spec as text, or ``None`` if unreachable."""
|
|
734
|
-
try:
|
|
735
|
-
with urllib.request.urlopen(url, timeout=SERVICE_SPEC_TIMEOUT_S) as response:
|
|
736
|
-
return response.read().decode("utf-8", errors="replace")
|
|
737
|
-
except (OSError, ValueError):
|
|
738
|
-
return None
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
def detect_service_type(url: str) -> str | None:
|
|
742
|
-
"""Return the SmartLayer datasource type for a remote service URL.
|
|
743
|
-
|
|
744
|
-
Object services expose ``getObjectByReference`` in their OpenAPI spec.
|
|
745
|
-
Returns ``None`` when the spec cannot be fetched (e.g. it requires auth),
|
|
746
|
-
so the caller can choose a fallback.
|
|
747
|
-
"""
|
|
748
|
-
spec = _fetch_service_spec(url)
|
|
749
|
-
if spec is None:
|
|
750
|
-
return None
|
|
751
|
-
return SMART_OBJECT_SERVICE if "getObjectByReference" in spec else SMART_TILE_SERVICE
|
|
752
|
-
|
|
753
|
-
|
|
754
691
|
# --- Config generation functions ---
|
|
755
692
|
|
|
756
693
|
def create_geojson_config(folder_path: str | Path) -> dict[str, Any]:
|
|
@@ -781,12 +718,12 @@ def create_classic_config(
|
|
|
781
718
|
def create_folder_config(
|
|
782
719
|
folder_path: str | Path, map_id: str | None = None,
|
|
783
720
|
) -> dict[str, Any]:
|
|
784
|
-
"""Create a config dict for a SmartLayer folder
|
|
721
|
+
"""Create a config dict for a SmartLayer folder."""
|
|
785
722
|
folder_path = Path(folder_path)
|
|
786
723
|
return {
|
|
787
724
|
"sources": [
|
|
788
725
|
{
|
|
789
|
-
"type":
|
|
726
|
+
"type": "SmartLayerTileService",
|
|
790
727
|
"uri": f"folder:{folder_path}",
|
|
791
728
|
"mapId": map_id or folder_path.name,
|
|
792
729
|
}
|
|
@@ -797,12 +734,12 @@ def create_folder_config(
|
|
|
797
734
|
def create_filestore_config(
|
|
798
735
|
filestore_path: str | Path, map_id: str | None = None,
|
|
799
736
|
) -> dict[str, Any]:
|
|
800
|
-
"""Create a config dict for a FileStore
|
|
737
|
+
"""Create a config dict for a FileStore."""
|
|
801
738
|
filestore_path = Path(filestore_path)
|
|
802
739
|
return {
|
|
803
740
|
"sources": [
|
|
804
741
|
{
|
|
805
|
-
"type":
|
|
742
|
+
"type": "SmartLayerTileService",
|
|
806
743
|
"uri": f"filestore:{filestore_path}",
|
|
807
744
|
"mapId": map_id or filestore_path.stem,
|
|
808
745
|
}
|
|
@@ -827,13 +764,11 @@ def create_service_config(
|
|
|
827
764
|
proxy_port: int | None = None,
|
|
828
765
|
proxy_user: str | None = None,
|
|
829
766
|
proxy_password: str | None = None,
|
|
830
|
-
service_type: str = SMART_TILE_SERVICE,
|
|
831
767
|
) -> dict[str, Any]:
|
|
832
768
|
"""Create a config dict for connecting to a remote service.
|
|
833
769
|
|
|
834
770
|
Args:
|
|
835
771
|
url: Service URL (e.g., https://api.example.com/openapi.json)
|
|
836
|
-
service_type: SmartLayer datasource type, see ``detect_service_type``
|
|
837
772
|
auth_method: One of "none", "apiKey", "queryParam", "basic", "bearer", "oauth2"
|
|
838
773
|
api_key: API key value (for auth_method="apiKey")
|
|
839
774
|
query_params: List of {"key": k, "value": v} dicts (for auth_method="queryParam")
|
|
@@ -859,7 +794,7 @@ def create_service_config(
|
|
|
859
794
|
config: dict[str, Any] = {
|
|
860
795
|
"sources": [
|
|
861
796
|
{
|
|
862
|
-
"type":
|
|
797
|
+
"type": "SmartLayerTileService",
|
|
863
798
|
"uri": url,
|
|
864
799
|
"mapId": parsed.netloc.replace(".", "-"),
|
|
865
800
|
}
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/tui.py
RENAMED
|
@@ -33,7 +33,6 @@ from . import docker_client
|
|
|
33
33
|
from .config import (
|
|
34
34
|
CONFIGS_DIR,
|
|
35
35
|
DEFAULT_CONFIG,
|
|
36
|
-
SMART_TILE_SERVICE,
|
|
37
36
|
create_classic_config,
|
|
38
37
|
create_filestore_config,
|
|
39
38
|
create_folder_config,
|
|
@@ -41,7 +40,6 @@ from .config import (
|
|
|
41
40
|
create_service_config,
|
|
42
41
|
delete_config,
|
|
43
42
|
detect_directory_type,
|
|
44
|
-
detect_service_type,
|
|
45
43
|
get_demo_config,
|
|
46
44
|
list_saved_configs,
|
|
47
45
|
load_config,
|
|
@@ -714,7 +712,6 @@ class WizardScreen(Screen):
|
|
|
714
712
|
url=self._service_url,
|
|
715
713
|
**auth_kwargs,
|
|
716
714
|
**proxy_kwargs,
|
|
717
|
-
service_type=detect_service_type(self._service_url) or SMART_TILE_SERVICE,
|
|
718
715
|
)
|
|
719
716
|
elif self._source_type == "demo":
|
|
720
717
|
self._config = get_demo_config()
|
|
@@ -37,30 +37,6 @@ def edition():
|
|
|
37
37
|
return DEFAULT_EDITION
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
@pytest.fixture(autouse=True)
|
|
41
|
-
def offline_service_spec(monkeypatch):
|
|
42
|
-
"""Keep tests offline: service spec fetches fail unless a test stubs them."""
|
|
43
|
-
monkeypatch.setattr(
|
|
44
|
-
"ndslive.mapviewer.config._fetch_service_spec", lambda url: None,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@pytest.fixture
|
|
49
|
-
def make_filestore():
|
|
50
|
-
"""Factory for minimal SQLite ``.ndslive`` filestores with given tables."""
|
|
51
|
-
import sqlite3
|
|
52
|
-
|
|
53
|
-
def make(path, *tables):
|
|
54
|
-
conn = sqlite3.connect(path)
|
|
55
|
-
with conn:
|
|
56
|
-
for table in tables:
|
|
57
|
-
conn.execute(f"CREATE TABLE {table} (id INTEGER)")
|
|
58
|
-
conn.close()
|
|
59
|
-
return path
|
|
60
|
-
|
|
61
|
-
return make
|
|
62
|
-
|
|
63
|
-
|
|
64
40
|
@pytest.fixture
|
|
65
41
|
def temp_dir():
|
|
66
42
|
"""Create a temporary directory for test files."""
|
|
@@ -177,42 +177,6 @@ class TestCliUrlSource:
|
|
|
177
177
|
assert rc == 0
|
|
178
178
|
assert cap["config"]["sources"][0]["uri"] == url
|
|
179
179
|
|
|
180
|
-
def test_object_service_url(self, monkeypatch):
|
|
181
|
-
monkeypatch.setattr(
|
|
182
|
-
"ndslive.mapviewer.config._fetch_service_spec",
|
|
183
|
-
lambda url: '{"paths": {"/getObjectByReference": {}}}',
|
|
184
|
-
)
|
|
185
|
-
rc, cap = self._run_cmd_main(monkeypatch, ["https://my-host/openapi.json"])
|
|
186
|
-
assert rc == 0
|
|
187
|
-
assert cap["config"]["sources"][0]["type"] == "SmartLayerObjectService"
|
|
188
|
-
|
|
189
|
-
def test_unreadable_spec_falls_back_to_tile_with_note(self, monkeypatch, capsys):
|
|
190
|
-
rc, cap = self._run_cmd_main(monkeypatch, ["https://my-host/openapi.json"])
|
|
191
|
-
assert rc == 0
|
|
192
|
-
assert cap["config"]["sources"][0]["type"] == "SmartLayerTileService"
|
|
193
|
-
assert "could not read the service spec" in capsys.readouterr().out
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
class TestCliObjectSources:
|
|
197
|
-
"""Tests for detecting SmartLayer object sources from local paths."""
|
|
198
|
-
|
|
199
|
-
def test_mixed_tile_and_object_sources(self, monkeypatch, tmp_path, make_filestore):
|
|
200
|
-
tiles = make_filestore(tmp_path / "tiles.ndslive", "metadataTable", "tileTable")
|
|
201
|
-
objects = make_filestore(tmp_path / "objects.ndslive", "metadataTable", "objectTable")
|
|
202
|
-
folder = tmp_path / "objfolder"
|
|
203
|
-
(folder / "objects").mkdir(parents=True)
|
|
204
|
-
|
|
205
|
-
rc, cap = TestCliUrlSource()._run_cmd_main(
|
|
206
|
-
monkeypatch, [str(tiles), str(objects), str(folder)],
|
|
207
|
-
)
|
|
208
|
-
assert rc == 0
|
|
209
|
-
types = {s["mapId"]: s["type"] for s in cap["config"]["sources"]}
|
|
210
|
-
assert types == {
|
|
211
|
-
"tiles": "SmartLayerTileService",
|
|
212
|
-
"objects": "SmartLayerObjectService",
|
|
213
|
-
"objfolder": "SmartLayerObjectService",
|
|
214
|
-
}
|
|
215
|
-
|
|
216
180
|
|
|
217
181
|
class TestCliLegacyMigration:
|
|
218
182
|
"""Tests for legacy mapviewer.yaml migration hint."""
|
|
@@ -7,8 +7,6 @@ import yaml
|
|
|
7
7
|
|
|
8
8
|
from ndslive.mapviewer.config import (
|
|
9
9
|
CONFIGS_DIR,
|
|
10
|
-
SMART_OBJECT_SERVICE,
|
|
11
|
-
SMART_TILE_SERVICE,
|
|
12
10
|
_extract_local_path,
|
|
13
11
|
_is_localhost,
|
|
14
12
|
_join_container_path,
|
|
@@ -21,9 +19,6 @@ from ndslive.mapviewer.config import (
|
|
|
21
19
|
create_service_config,
|
|
22
20
|
delete_config,
|
|
23
21
|
detect_directory_type,
|
|
24
|
-
detect_filestore_type,
|
|
25
|
-
detect_folder_type,
|
|
26
|
-
detect_service_type,
|
|
27
22
|
list_saved_configs,
|
|
28
23
|
load_config,
|
|
29
24
|
resolve_config_source,
|
|
@@ -803,29 +798,16 @@ class TestCreateClassicConfig:
|
|
|
803
798
|
|
|
804
799
|
|
|
805
800
|
class TestCreateFolderConfig:
|
|
806
|
-
"""Tests for create_folder_config()."""
|
|
801
|
+
"""Tests for create_folder_config() custom mapId."""
|
|
807
802
|
|
|
808
803
|
def test_custom_map_id(self, temp_dir):
|
|
809
804
|
"""Custom mapId overrides the default."""
|
|
810
805
|
config = create_folder_config(temp_dir, map_id="CustomFolder")
|
|
811
806
|
assert config["sources"][0]["mapId"] == "CustomFolder"
|
|
812
807
|
|
|
813
|
-
def test_tile_folder(self, temp_dir):
|
|
814
|
-
"""A folder without objects/ is a tile service."""
|
|
815
|
-
(temp_dir / "tiles").mkdir()
|
|
816
|
-
config = create_folder_config(temp_dir)
|
|
817
|
-
assert config["sources"][0]["type"] == SMART_TILE_SERVICE
|
|
818
|
-
|
|
819
|
-
def test_object_folder(self, temp_dir):
|
|
820
|
-
"""A folder with objects/ is an object service."""
|
|
821
|
-
(temp_dir / "objects").mkdir()
|
|
822
|
-
config = create_folder_config(temp_dir)
|
|
823
|
-
assert config["sources"][0]["type"] == SMART_OBJECT_SERVICE
|
|
824
|
-
assert config["sources"][0]["uri"] == f"folder:{temp_dir}"
|
|
825
|
-
|
|
826
808
|
|
|
827
809
|
class TestCreateFilestoreConfig:
|
|
828
|
-
"""Tests for create_filestore_config()."""
|
|
810
|
+
"""Tests for create_filestore_config() custom mapId."""
|
|
829
811
|
|
|
830
812
|
def test_custom_map_id(self, temp_dir):
|
|
831
813
|
"""Custom mapId overrides the default."""
|
|
@@ -834,66 +816,6 @@ class TestCreateFilestoreConfig:
|
|
|
834
816
|
config = create_filestore_config(store, map_id="CustomStore")
|
|
835
817
|
assert config["sources"][0]["mapId"] == "CustomStore"
|
|
836
818
|
|
|
837
|
-
def test_tile_filestore(self, temp_dir, make_filestore):
|
|
838
|
-
"""A filestore with a tileTable is a tile service."""
|
|
839
|
-
store = make_filestore(temp_dir / "tiles.ndslive", "metadataTable", "tileTable")
|
|
840
|
-
config = create_filestore_config(store)
|
|
841
|
-
assert config["sources"][0]["type"] == SMART_TILE_SERVICE
|
|
842
|
-
|
|
843
|
-
def test_object_filestore(self, temp_dir, make_filestore):
|
|
844
|
-
"""A filestore with an objectTable is an object service."""
|
|
845
|
-
store = make_filestore(
|
|
846
|
-
temp_dir / "objects.ndslive",
|
|
847
|
-
"metadataTable", "objectTable", "tileObjectAssociationTable",
|
|
848
|
-
)
|
|
849
|
-
config = create_filestore_config(store)
|
|
850
|
-
source = config["sources"][0]
|
|
851
|
-
assert source["type"] == SMART_OBJECT_SERVICE
|
|
852
|
-
assert source["uri"] == f"filestore:{store}"
|
|
853
|
-
assert source["mapId"] == "objects"
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
class TestDetectSmartLayerType:
|
|
857
|
-
"""Tests for the SmartLayer tile/object detection helpers."""
|
|
858
|
-
|
|
859
|
-
def test_unreadable_filestore_falls_back_to_tile(self, temp_dir):
|
|
860
|
-
"""A file that is not SQLite is left to the viewer to reject."""
|
|
861
|
-
store = temp_dir / "broken.ndslive"
|
|
862
|
-
store.write_text("not a database")
|
|
863
|
-
assert detect_filestore_type(store) == SMART_TILE_SERVICE
|
|
864
|
-
|
|
865
|
-
def test_filestore_detection_leaves_file_unchanged(self, temp_dir, make_filestore):
|
|
866
|
-
"""Detection opens the filestore read-only."""
|
|
867
|
-
store = make_filestore(temp_dir / "objects.ndslive", "objectTable")
|
|
868
|
-
before = store.read_bytes()
|
|
869
|
-
assert detect_filestore_type(store) == SMART_OBJECT_SERVICE
|
|
870
|
-
assert store.read_bytes() == before
|
|
871
|
-
|
|
872
|
-
def test_folder_with_objects_file_is_tile(self, temp_dir):
|
|
873
|
-
"""Only an objects/ directory marks an object folder."""
|
|
874
|
-
(temp_dir / "objects").touch()
|
|
875
|
-
assert detect_folder_type(temp_dir) == SMART_TILE_SERVICE
|
|
876
|
-
|
|
877
|
-
def test_object_service_spec(self, monkeypatch):
|
|
878
|
-
"""A spec with getObjectByReference is an object service."""
|
|
879
|
-
monkeypatch.setattr(
|
|
880
|
-
"ndslive.mapviewer.config._fetch_service_spec",
|
|
881
|
-
lambda url: '{"paths": {"/getObjectByReference": {}}}',
|
|
882
|
-
)
|
|
883
|
-
assert detect_service_type("https://h/openapi.json") == SMART_OBJECT_SERVICE
|
|
884
|
-
|
|
885
|
-
def test_tile_service_spec(self, monkeypatch):
|
|
886
|
-
"""A spec without object methods is a tile service."""
|
|
887
|
-
monkeypatch.setattr(
|
|
888
|
-
"ndslive.mapviewer.config._fetch_service_spec",
|
|
889
|
-
lambda url: '{"paths": {"/getSmartLayerTile": {}}}',
|
|
890
|
-
)
|
|
891
|
-
assert detect_service_type("https://h/openapi.json") == SMART_TILE_SERVICE
|
|
892
|
-
|
|
893
|
-
def test_unreachable_spec_is_unknown(self):
|
|
894
|
-
"""An unreachable spec leaves the choice to the caller."""
|
|
895
|
-
assert detect_service_type("https://h/openapi.json") is None
|
|
896
|
-
|
|
897
819
|
|
|
898
820
|
class TestResolveConfigSourceDetection:
|
|
899
821
|
"""Tests for resolve_config_source() directory type detection."""
|
|
@@ -930,13 +852,6 @@ class TestCreateServiceConfig:
|
|
|
930
852
|
assert config["sources"][0]["uri"] == "https://api.example.com/openapi.json"
|
|
931
853
|
assert "http-settings" not in config
|
|
932
854
|
|
|
933
|
-
def test_object_service_type(self):
|
|
934
|
-
"""The given service type is written to the source."""
|
|
935
|
-
config = create_service_config(
|
|
936
|
-
"https://api.example.com/openapi.json", service_type=SMART_OBJECT_SERVICE,
|
|
937
|
-
)
|
|
938
|
-
assert config["sources"][0]["type"] == SMART_OBJECT_SERVICE
|
|
939
|
-
|
|
940
855
|
def test_api_key_auth(self):
|
|
941
856
|
"""API key auth populates http-settings with api-key."""
|
|
942
857
|
config = create_service_config(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/__init__.py
RENAMED
|
File without changes
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/browser.py
RENAMED
|
File without changes
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/clipboard.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/viewer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|