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.
Files changed (32) hide show
  1. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/CHANGELOG.md +0 -5
  2. {nds_mapviewer-2026.4.3.dev253/src/nds_mapviewer.egg-info → nds_mapviewer-2026.4.3.dev254}/PKG-INFO +4 -13
  3. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/README.md +3 -12
  4. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254/src/nds_mapviewer.egg-info}/PKG-INFO +4 -13
  5. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/cli.py +6 -16
  6. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/config.py +5 -70
  7. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/tui.py +0 -3
  8. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/conftest.py +0 -24
  9. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_cli.py +0 -36
  10. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_config.py +2 -87
  11. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/.gitignore +0 -0
  12. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/pyproject.toml +0 -0
  13. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/setup.cfg +0 -0
  14. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/setup.py +0 -0
  15. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/SOURCES.txt +0 -0
  16. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/dependency_links.txt +0 -0
  17. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/entry_points.txt +0 -0
  18. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/requires.txt +0 -0
  19. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/scm_file_list.json +0 -0
  20. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/nds_mapviewer.egg-info/top_level.txt +0 -0
  21. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/__init__.py +0 -0
  22. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/browser.py +0 -0
  23. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/clipboard.py +0 -0
  24. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/container_runtime.py +0 -0
  25. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/docker_client.py +0 -0
  26. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/exceptions.py +0 -0
  27. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/ui.py +0 -0
  28. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/src/ndslive/mapviewer/viewer.py +0 -0
  29. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_clipboard.py +0 -0
  30. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_docker_client.py +0 -0
  31. {nds_mapviewer-2026.4.3.dev253 → nds_mapviewer-2026.4.3.dev254}/tests/test_tui.py +0 -0
  32. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nds-mapviewer
3
- Version: 2026.4.3.dev253
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, as tile or object maps
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 (tile or object map)
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`, or a `SmartLayerObjectService` when its
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, as tile or object maps
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 (tile or object map)
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`, or a `SmartLayerObjectService` when its
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).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nds-mapviewer
3
- Version: 2026.4.3.dev253
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, as tile or object maps
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 (tile or object map)
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`, or a `SmartLayerObjectService` when its
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).
@@ -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 SmartLayer tile or
226
- # object service source directly, depending on its OpenAPI spec.
227
- # Checked before the path:mapId split below because URLs contain
228
- # colons. No auth is configured for the bare-URL form — use
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
- service_type = detect_service_type(source_arg)
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 (tile or object maps are detected)
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
@@ -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 (tile or object)."""
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": detect_folder_type(folder_path),
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 (tile or object)."""
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": detect_filestore_type(filestore_path),
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": service_type,
797
+ "type": "SmartLayerTileService",
863
798
  "uri": url,
864
799
  "mapId": parsed.netloc.replace(".", "-"),
865
800
  }
@@ -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(