lodapi 0.0.1__tar.gz → 0.0.2__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.
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## 0.0.2 — 2026-06-14
4
+
5
+ ### Fixed
6
+ - **`buildings()` crashte bei jedem Call** durch Schema-Drift: das Modell
7
+ `BuildingsBboxAttribution` verlangte ein Pflichtfeld `bundesland`, die Live-API
8
+ liefert aber `bundesland_code` (+ `license_id`, `attribution_url`, `license_url`,
9
+ `requires_attribution`). Modell an die **echte** Live-Form angepasst
10
+ (verifiziert gegen api.lodapi.de). Ursache: der committete OpenAPI-Snapshot,
11
+ aus dem die v0-Modelle abgeleitet wurden, war gegenüber dem Prod-Deploy stale.
12
+
13
+ ### Changed
14
+ - Response-Modelle der buildings-Endpoints (`BuildingsBboxAttribution`,
15
+ `BuildingDetail`) auf **durchgängig Optional** umgestellt. Ein Client-SDK gegen
16
+ eine junge API darf nicht an einem umbenannten/fehlenden Feld crashen
17
+ (`extra="allow"` fängt *neue* Felder, Optional fängt *entfernte/umbenannte*).
18
+ - Neuer opt-in **Live-Smoke-Test** (`pytest -m live`) gegen api.lodapi.de, der
19
+ genau diese Art Drift künftig fängt — die gemockten Unit-Tests prüften das
20
+ Fixture, nicht die reale API.
21
+
22
+ ## 0.0.1 — 2026-06-14
23
+
24
+ - Erstes Release: ergonomische Facade über der Lodapi-REST-API
25
+ (buildings/terrain/tilesets/datasets), `X-API-Key`-Auth, Free-Tier,
26
+ optionales `[geo]`-Extra, RFC-7807-Fehler, Soft-Quota-Header.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lodapi
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: Ergonomic Python SDK for the Lodapi REST API (LoD2 buildings, terrain, 3D tiles).
5
5
  Project-URL: Homepage, https://lodapi.de
6
6
  Project-URL: Documentation, https://lodapi.de/docs
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lodapi"
7
- version = "0.0.1"
7
+ version = "0.0.2"
8
8
  description = "Ergonomic Python SDK for the Lodapi REST API (LoD2 buildings, terrain, 3D tiles)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -46,3 +46,9 @@ packages = ["src/lodapi"]
46
46
 
47
47
  [tool.pytest.ini_options]
48
48
  testpaths = ["tests"]
49
+ # Live-Tests (echte api.lodapi.de) standardmäßig ausgeschlossen — CI/offline
50
+ # läuft rein gegen respx-Mocks. Explizit anfordern via: pytest -m live
51
+ addopts = "-m 'not live'"
52
+ markers = [
53
+ "live: hits the real api.lodapi.de (network); run with `pytest -m live`",
54
+ ]
@@ -33,7 +33,7 @@ from .resources.tilesets import TilesetsResource
33
33
  try:
34
34
  __version__ = version("lodapi")
35
35
  except PackageNotFoundError: # pragma: no cover - editable/uninstalled
36
- __version__ = "0.0.1"
36
+ __version__ = "0.0.2"
37
37
 
38
38
 
39
39
  class Client:
@@ -47,9 +47,22 @@ class _Model(BaseModel):
47
47
  # --------------------------------------------------------------------------
48
48
 
49
49
  class BuildingsBboxAttribution(_Model):
50
- bundesland: str
51
- license: Optional[str] = None
50
+ """Per-Bundesland attribution/licence block in ``lodapi.attribution[]``.
51
+
52
+ Shape verified against the live API (api.lodapi.de, 2026-06-14). All fields
53
+ are Optional on purpose: this is a young, evolving API and a client SDK must
54
+ never crash on a renamed/missing attribution field (``extra="allow"`` covers
55
+ *added* fields; Optional covers *removed/renamed* ones). The committed
56
+ OpenAPI snapshot lagged here (had ``bundesland``/``license``) — see SDK
57
+ CHANGELOG 0.0.2.
58
+ """
59
+
60
+ bundesland_code: Optional[str] = None
61
+ license_id: Optional[str] = None
52
62
  attribution_text: Optional[str] = None
63
+ attribution_url: Optional[str] = None
64
+ license_url: Optional[str] = None
65
+ requires_attribution: Optional[bool] = None
53
66
 
54
67
 
55
68
  class BuildingsBboxLodapiMeta(_Model):
@@ -90,9 +103,11 @@ class BuildingsBboxResponse(_Model):
90
103
  # --------------------------------------------------------------------------
91
104
 
92
105
  class BuildingDetail(_Model):
93
- gmlid: str
94
- bundesland_code: str
95
- building_id: int
106
+ # All Optional by design — a client SDK must not crash if the API renames
107
+ # or drops a field (extra="allow" covers added fields). See 0.0.2 fix.
108
+ gmlid: Optional[str] = None
109
+ bundesland_code: Optional[str] = None
110
+ building_id: Optional[int] = None
96
111
  geometry: Optional[dict[str, Any]] = None
97
112
  lod: str = "LoD2"
98
113
 
@@ -44,8 +44,18 @@ def buildings_page(next_cursor=None, ids=("A", "B")):
44
44
  ],
45
45
  "lodapi": {
46
46
  "next": next_cursor,
47
+ # Real live-API shape (verified api.lodapi.de 2026-06-14): the
48
+ # attribution block uses bundesland_code/license_id + url/flag fields,
49
+ # NOT the stale bundesland/license the snapshot once had.
47
50
  "attribution": [
48
- {"bundesland": "bw", "license": "dl-de-zero-2.0", "attribution_text": "© LGL-BW"}
51
+ {
52
+ "bundesland_code": "bw",
53
+ "license_id": "dl-de-zero-2.0",
54
+ "attribution_text": "© LGL-BW",
55
+ "attribution_url": "https://www.lgl-bw.de/",
56
+ "license_url": "https://www.govdata.de/dl-de/zero-2-0",
57
+ "requires_attribution": False,
58
+ }
49
59
  ],
50
60
  },
51
61
  }
@@ -37,7 +37,10 @@ def test_buildings_single_page(client):
37
37
 
38
38
  assert page.count == 2
39
39
  assert len(page.features) == 2
40
- assert page.lodapi.attribution[0].bundesland == "bw"
40
+ att = page.lodapi.attribution[0]
41
+ assert att.bundesland_code == "bw"
42
+ assert att.license_id == "dl-de-zero-2.0"
43
+ assert att.requires_attribution is False
41
44
  # bbox tuple serialized into the query, limit forwarded, no `bl` param
42
45
  req = route.calls.last.request
43
46
  assert req.url.params["bbox"] == "7.0,50.9,7.1,51.0"
@@ -0,0 +1,47 @@
1
+ """Opt-in live smoke tests against the real api.lodapi.de.
2
+
3
+ These are EXCLUDED from the default run (`addopts = -m 'not live'` in
4
+ pyproject) because CI runs offline against respx mocks. Run explicitly with::
5
+
6
+ uv run --extra dev pytest -m live
7
+
8
+ Why these exist: the 0.0.2 buildings() crash slipped through because the
9
+ mocked unit tests validated the *fixture*, not the live API. A real call
10
+ parsing through the Pydantic models is the only thing that catches schema
11
+ drift between the SDK and the deployed API.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import pytest
17
+
18
+ from lodapi import Client
19
+ from lodapi.models import BuildingsBboxResponse
20
+
21
+ # Small bbox over Cologne (NRW) — stable, non-empty, free-tier (no key needed).
22
+ _BBOX = (7.00, 50.94, 7.02, 50.95)
23
+
24
+
25
+ @pytest.mark.live
26
+ def test_buildings_bbox_parses_against_live_api() -> None:
27
+ """buildings() must parse a real federated response without raising."""
28
+ with Client() as c:
29
+ page = c.buildings(bbox=_BBOX, limit=1)
30
+
31
+ assert isinstance(page, BuildingsBboxResponse)
32
+ assert page.type == "FeatureCollection"
33
+ assert isinstance(page.count, int)
34
+ # The attribution block is the field that drifted in 0.0.2 — assert it
35
+ # parses and carries the real live shape.
36
+ if page.lodapi.attribution:
37
+ att = page.lodapi.attribution[0]
38
+ assert att.bundesland_code is not None
39
+ # license/url/flag are tolerated as Optional, just must not raise.
40
+
41
+
42
+ @pytest.mark.live
43
+ def test_datasets_parses_against_live_api() -> None:
44
+ with Client() as c:
45
+ ds = c.datasets()
46
+ assert ds.datasets, "expected at least one live dataset"
47
+ assert ds.datasets[0].bundesland_code
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