Compare commits
No commits in common. "b430411b7edc4d56bc07fcf1b6eeec9ee21282f3" and "76437d524048507f86857a3dacb59565e4dfcd1f" have entirely different histories.
b430411b7e
...
76437d5240
44 changed files with 0 additions and 118081 deletions
|
|
@ -151,13 +151,6 @@ def lat_lon_to_wikidata(
|
||||||
if not nearby_result.get("missing"):
|
if not nearby_result.get("missing"):
|
||||||
return {"elements": elements, "result": nearby_result}
|
return {"elements": elements, "result": nearby_result}
|
||||||
|
|
||||||
# For points in the sea near any coast (e.g. England), try nearest polygon
|
|
||||||
nearest = model.Polygon.nearest(lat, lon)
|
|
||||||
if nearest:
|
|
||||||
nearby_result = do_lookup([nearest], lat, lon)
|
|
||||||
if not nearby_result.get("missing"):
|
|
||||||
return {"elements": elements, "result": nearby_result}
|
|
||||||
|
|
||||||
if not needs_commons:
|
if not needs_commons:
|
||||||
# Direct lookup: find Wikidata items whose P402 (OSM relation ID) matches
|
# Direct lookup: find Wikidata items whose P402 (OSM relation ID) matches
|
||||||
# one of the OSM polygons that contain this point.
|
# one of the OSM polygons that contain this point.
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Download and save fixture data for the sample locations.
|
|
||||||
|
|
||||||
Run from the project root directory:
|
|
||||||
|
|
||||||
python tests/capture_fixtures.py
|
|
||||||
|
|
||||||
This makes real HTTP requests to WDQS and the Wikidata API and uses the
|
|
||||||
local PostGIS database. The output is saved to tests/fixtures/ and is
|
|
||||||
used by test_examples.py so those tests run without any network access.
|
|
||||||
|
|
||||||
Re-run this script whenever the expected results change (e.g. after an OSM
|
|
||||||
or Wikidata edit that affects a sample location).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
||||||
|
|
||||||
import geocode.wikidata as wikidata_module
|
|
||||||
from geocode import samples
|
|
||||||
from lookup import app, lat_lon_to_wikidata
|
|
||||||
|
|
||||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
|
||||||
|
|
||||||
|
|
||||||
def capture_sample(i: int, lat: float, lon: float, name: str) -> None:
|
|
||||||
"""Capture and save fixture data for one sample location."""
|
|
||||||
wdqs_calls: dict[str, list] = {}
|
|
||||||
api_calls: dict[str, dict] = {}
|
|
||||||
|
|
||||||
# Hold references to the real functions before patching.
|
|
||||||
real_wdqs = wikidata_module.wdqs
|
|
||||||
real_api_call = wikidata_module.api_call
|
|
||||||
|
|
||||||
def recording_wdqs(query: str) -> list:
|
|
||||||
result = real_wdqs(query)
|
|
||||||
wdqs_calls[query] = result
|
|
||||||
return result
|
|
||||||
|
|
||||||
def recording_api_call(params: dict) -> dict:
|
|
||||||
key = json.dumps(params, sort_keys=True)
|
|
||||||
result = real_api_call(params)
|
|
||||||
api_calls[key] = result
|
|
||||||
return result
|
|
||||||
|
|
||||||
with patch("geocode.wikidata.wdqs", side_effect=recording_wdqs), patch(
|
|
||||||
"geocode.wikidata.api_call", side_effect=recording_api_call
|
|
||||||
):
|
|
||||||
reply = lat_lon_to_wikidata(lat, lon)
|
|
||||||
|
|
||||||
result = reply["result"]
|
|
||||||
commons_cat = result.get("commons_cat")
|
|
||||||
|
|
||||||
fixture: dict = {
|
|
||||||
"lat": lat,
|
|
||||||
"lon": lon,
|
|
||||||
"name": name,
|
|
||||||
"wdqs": wdqs_calls,
|
|
||||||
"api": api_calls,
|
|
||||||
"expected_wikidata": result.get("wikidata"),
|
|
||||||
"expected_commons_cat": commons_cat["title"]
|
|
||||||
if isinstance(commons_cat, dict)
|
|
||||||
else None,
|
|
||||||
}
|
|
||||||
|
|
||||||
fixture_path = FIXTURES_DIR / f"sample_{i:02d}.json"
|
|
||||||
fixture_path.write_text(json.dumps(fixture, indent=2, ensure_ascii=False))
|
|
||||||
|
|
||||||
qid = fixture["expected_wikidata"] or "none"
|
|
||||||
cat = fixture["expected_commons_cat"] or "no commons cat"
|
|
||||||
print(f" [{i:02d}] {name}: {qid} / {cat}")
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""Capture fixtures for all samples."""
|
|
||||||
FIXTURES_DIR.mkdir(exist_ok=True)
|
|
||||||
|
|
||||||
indices = None
|
|
||||||
if len(sys.argv) > 1:
|
|
||||||
# Allow passing specific indices on the command line, e.g.:
|
|
||||||
# python tests/capture_fixtures.py 0 5 12
|
|
||||||
indices = {int(a) for a in sys.argv[1:]}
|
|
||||||
|
|
||||||
print(f"Capturing fixtures for {len(samples)} samples...")
|
|
||||||
with app.app_context():
|
|
||||||
for i, (lat, lon, name) in enumerate(samples):
|
|
||||||
if indices and i not in indices:
|
|
||||||
continue
|
|
||||||
print(f"[{i:02d}/{len(samples) - 1}] {name} ({lat}, {lon})")
|
|
||||||
capture_sample(i, lat, lon, name)
|
|
||||||
|
|
||||||
print(f"\nDone. Fixtures saved to {FIXTURES_DIR}/")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
"""Shared pytest fixtures."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def flask_app():
|
|
||||||
"""Return the Flask application."""
|
|
||||||
from lookup import app
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
|
||||||
def app_ctx(flask_app):
|
|
||||||
"""Push a Flask application context for the whole test session."""
|
|
||||||
with flask_app.app_context():
|
|
||||||
yield
|
|
||||||
1703
tests/fixtures/sample_00.json
vendored
1703
tests/fixtures/sample_00.json
vendored
File diff suppressed because it is too large
Load diff
2919
tests/fixtures/sample_01.json
vendored
2919
tests/fixtures/sample_01.json
vendored
File diff suppressed because it is too large
Load diff
1706
tests/fixtures/sample_02.json
vendored
1706
tests/fixtures/sample_02.json
vendored
File diff suppressed because it is too large
Load diff
2649
tests/fixtures/sample_03.json
vendored
2649
tests/fixtures/sample_03.json
vendored
File diff suppressed because it is too large
Load diff
7088
tests/fixtures/sample_04.json
vendored
7088
tests/fixtures/sample_04.json
vendored
File diff suppressed because it is too large
Load diff
1914
tests/fixtures/sample_05.json
vendored
1914
tests/fixtures/sample_05.json
vendored
File diff suppressed because it is too large
Load diff
1891
tests/fixtures/sample_06.json
vendored
1891
tests/fixtures/sample_06.json
vendored
File diff suppressed because it is too large
Load diff
1522
tests/fixtures/sample_07.json
vendored
1522
tests/fixtures/sample_07.json
vendored
File diff suppressed because it is too large
Load diff
27
tests/fixtures/sample_08.json
vendored
27
tests/fixtures/sample_08.json
vendored
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"lat": 53.351,
|
|
||||||
"lon": -2.701,
|
|
||||||
"name": "Halton",
|
|
||||||
"wdqs": {
|
|
||||||
"SELECT DISTINCT ?item ?itemLabel ?commonsSiteLink ?commonsCat WHERE {\n { ?item rdfs:label 'Runcorn'@en } UNION { ?item skos:altLabel 'Runcorn'@en }\n FILTER NOT EXISTS { ?item wdt:P31 wd:Q17362920 } .# ignore Wikimedia duplicated page\n OPTIONAL { ?commonsSiteLink schema:about ?item ;\n schema:isPartOf <https://commons.wikimedia.org/> }\n OPTIONAL { ?item wdt:P373 ?commonsCat }\n ?item wdt:P625 ?coords .\n\n FILTER(geof:distance(?coords, \"Point(-2.701 53.351)\"^^geo:wktLiteral) < 10)\n FILTER(?commonsCat || ?commonsSiteLink)\n\n SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\". }\n}\n": [
|
|
||||||
{
|
|
||||||
"item": {
|
|
||||||
"type": "uri",
|
|
||||||
"value": "http://www.wikidata.org/entity/Q1009316"
|
|
||||||
},
|
|
||||||
"commonsCat": {
|
|
||||||
"type": "literal",
|
|
||||||
"value": "Runcorn"
|
|
||||||
},
|
|
||||||
"itemLabel": {
|
|
||||||
"xml:lang": "en",
|
|
||||||
"type": "literal",
|
|
||||||
"value": "Runcorn"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"api": {},
|
|
||||||
"expected_wikidata": "Q1009316",
|
|
||||||
"expected_commons_cat": "Runcorn"
|
|
||||||
}
|
|
||||||
1910
tests/fixtures/sample_09.json
vendored
1910
tests/fixtures/sample_09.json
vendored
File diff suppressed because it is too large
Load diff
2625
tests/fixtures/sample_10.json
vendored
2625
tests/fixtures/sample_10.json
vendored
File diff suppressed because it is too large
Load diff
1863
tests/fixtures/sample_11.json
vendored
1863
tests/fixtures/sample_11.json
vendored
File diff suppressed because it is too large
Load diff
6794
tests/fixtures/sample_12.json
vendored
6794
tests/fixtures/sample_12.json
vendored
File diff suppressed because it is too large
Load diff
1408
tests/fixtures/sample_13.json
vendored
1408
tests/fixtures/sample_13.json
vendored
File diff suppressed because it is too large
Load diff
1655
tests/fixtures/sample_14.json
vendored
1655
tests/fixtures/sample_14.json
vendored
File diff suppressed because it is too large
Load diff
1992
tests/fixtures/sample_15.json
vendored
1992
tests/fixtures/sample_15.json
vendored
File diff suppressed because it is too large
Load diff
2281
tests/fixtures/sample_16.json
vendored
2281
tests/fixtures/sample_16.json
vendored
File diff suppressed because it is too large
Load diff
1838
tests/fixtures/sample_17.json
vendored
1838
tests/fixtures/sample_17.json
vendored
File diff suppressed because it is too large
Load diff
2160
tests/fixtures/sample_18.json
vendored
2160
tests/fixtures/sample_18.json
vendored
File diff suppressed because it is too large
Load diff
1264
tests/fixtures/sample_19.json
vendored
1264
tests/fixtures/sample_19.json
vendored
File diff suppressed because it is too large
Load diff
1798
tests/fixtures/sample_20.json
vendored
1798
tests/fixtures/sample_20.json
vendored
File diff suppressed because it is too large
Load diff
1956
tests/fixtures/sample_21.json
vendored
1956
tests/fixtures/sample_21.json
vendored
File diff suppressed because it is too large
Load diff
5167
tests/fixtures/sample_22.json
vendored
5167
tests/fixtures/sample_22.json
vendored
File diff suppressed because it is too large
Load diff
1886
tests/fixtures/sample_23.json
vendored
1886
tests/fixtures/sample_23.json
vendored
File diff suppressed because it is too large
Load diff
682
tests/fixtures/sample_24.json
vendored
682
tests/fixtures/sample_24.json
vendored
|
|
@ -1,682 +0,0 @@
|
||||||
{
|
|
||||||
"lat": 53.392,
|
|
||||||
"lon": -0.022,
|
|
||||||
"name": "Brackenborough with Little Grimsby",
|
|
||||||
"wdqs": {},
|
|
||||||
"api": {
|
|
||||||
"{\"action\": \"wbgetentities\", \"ids\": \"Q4953658\"}": {
|
|
||||||
"entities": {
|
|
||||||
"Q4953658": {
|
|
||||||
"pageid": 4734351,
|
|
||||||
"ns": 0,
|
|
||||||
"title": "Q4953658",
|
|
||||||
"lastrevid": 2445683664,
|
|
||||||
"modified": "2025-12-22T21:20:25Z",
|
|
||||||
"type": "item",
|
|
||||||
"id": "Q4953658",
|
|
||||||
"labels": {
|
|
||||||
"en": {
|
|
||||||
"language": "en",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
},
|
|
||||||
"sv": {
|
|
||||||
"language": "sv",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
},
|
|
||||||
"ceb": {
|
|
||||||
"language": "ceb",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
},
|
|
||||||
"tr": {
|
|
||||||
"language": "tr",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
},
|
|
||||||
"ga": {
|
|
||||||
"language": "ga",
|
|
||||||
"value": "Brackenborough le Little Grimsby"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"language": "fr",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
},
|
|
||||||
"mul": {
|
|
||||||
"language": "mul",
|
|
||||||
"value": "Brackenborough with Little Grimsby"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"descriptions": {
|
|
||||||
"de": {
|
|
||||||
"language": "de",
|
|
||||||
"value": "Gemeinde in der englischen Grafschaft Lincolnshire"
|
|
||||||
},
|
|
||||||
"fr": {
|
|
||||||
"language": "fr",
|
|
||||||
"value": "localité britannique du comté anglais de Lincolnshire"
|
|
||||||
},
|
|
||||||
"en": {
|
|
||||||
"language": "en",
|
|
||||||
"value": "civil parish in Lincolnshire, UK"
|
|
||||||
},
|
|
||||||
"ar": {
|
|
||||||
"language": "ar",
|
|
||||||
"value": "أبرشية مدنية في المملكة المتحدة"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"en": [
|
|
||||||
{
|
|
||||||
"language": "en",
|
|
||||||
"value": "Brackenborough with Little Grimsby (civil parish), Lincolnshire"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"language": "en",
|
|
||||||
"value": "Brackenborough with Little Grimsby, Lincolnshire"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"claims": {
|
|
||||||
"P625": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P625",
|
|
||||||
"hash": "d56de4b68b43b43fe97aee6a61dccc5000554e9e",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"latitude": 53.4,
|
|
||||||
"longitude": 0,
|
|
||||||
"altitude": null,
|
|
||||||
"precision": 0.1,
|
|
||||||
"globe": "http://www.wikidata.org/entity/Q2"
|
|
||||||
},
|
|
||||||
"type": "globecoordinate"
|
|
||||||
},
|
|
||||||
"datatype": "globe-coordinate"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$9718EB71-20AB-4466-9282-340CC6984F56",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "fa278ebfc458360e5aed63d5058cca83c46134f1",
|
|
||||||
"snaks": {
|
|
||||||
"P143": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P143",
|
|
||||||
"hash": "e4f6d9441d0600513c4533c672b5ab472dc73694",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 328,
|
|
||||||
"id": "Q328"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P143"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P17": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P17",
|
|
||||||
"hash": "a44880420787fb348836dc51d3babc5227c4ef7e",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 145,
|
|
||||||
"id": "Q145"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$A761C40C-581F-4A8C-BB10-7B167EC4C5EC",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "fa278ebfc458360e5aed63d5058cca83c46134f1",
|
|
||||||
"snaks": {
|
|
||||||
"P143": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P143",
|
|
||||||
"hash": "e4f6d9441d0600513c4533c672b5ab472dc73694",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 328,
|
|
||||||
"id": "Q328"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P143"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P31": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P31",
|
|
||||||
"hash": "b55cf11b6c802451b5ce3c81756cd063d3c7b7fb",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 1115575,
|
|
||||||
"id": "Q1115575"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$4FB2A362-5DD1-4C3B-B11B-2FFBBAB2C784",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1566": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1566",
|
|
||||||
"hash": "46d8fff6acdd96c5878adadb277c78cdf5a2b26d",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "7301566",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "external-id"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$8CA40F0B-2DFB-4425-9476-AC43081E77AA",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "88694a0f4d1486770c269f7db16a1982f74da69d",
|
|
||||||
"snaks": {
|
|
||||||
"P248": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P248",
|
|
||||||
"hash": "1b3ef912a2bd61e18dd43abd184337eb010b2e96",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 830106,
|
|
||||||
"id": "Q830106"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P248"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P373": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P373",
|
|
||||||
"hash": "c7bffdf55152b4948146dd62dce67865247d4ce9",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "Brackenborough with Little Grimsby",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "string"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$112752D4-ECDA-4269-A9B7-4B601A7E574B",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P836": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P836",
|
|
||||||
"hash": "4766718e26a4faf7dcde33195620c24100a0140f",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "E04005605",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "external-id"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$896CDCC6-E620-41D9-8B04-173F1B50D707",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "4881a6edaba7b0f179d612969aefc94ed4768626",
|
|
||||||
"snaks": {
|
|
||||||
"P813": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P813",
|
|
||||||
"hash": "310c4d7f7d53b6a5c57ec198e61d481cbfccb045",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"time": "+2017-03-14T00:00:00Z",
|
|
||||||
"timezone": 0,
|
|
||||||
"before": 0,
|
|
||||||
"after": 0,
|
|
||||||
"precision": 11,
|
|
||||||
"calendarmodel": "http://www.wikidata.org/entity/Q1985727"
|
|
||||||
},
|
|
||||||
"type": "time"
|
|
||||||
},
|
|
||||||
"datatype": "time"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P813"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P131": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P131",
|
|
||||||
"hash": "a45427cde8301216e2ab3586ad98b4df63c800f2",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 82181,
|
|
||||||
"id": "Q82181"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$D9512857-C801-44BE-AEA6-8787D89F0277",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "a0dde1d73f9a85c67c4a2cbeddbcecf7198f07f9",
|
|
||||||
"snaks": {
|
|
||||||
"P143": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P143",
|
|
||||||
"hash": "141583182d86fb45cf7098a86a993bc44e404092",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 2242955,
|
|
||||||
"id": "Q2242955"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P143"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P3120": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P3120",
|
|
||||||
"hash": "3ef13dfc0e9c62c2218248c5945be0496061bf08",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "7000000000005371",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "external-id"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"qualifiers": {
|
|
||||||
"P2868": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P2868",
|
|
||||||
"hash": "a92e88154c4cc68adfb7a6c25dcb0dcbe2904dea",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 1115575,
|
|
||||||
"id": "Q1115575"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"qualifiers-order": [
|
|
||||||
"P2868"
|
|
||||||
],
|
|
||||||
"id": "Q4953658$CE73E131-69FB-4EDA-A5B2-DDECDD7ABBEE",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P402": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P402",
|
|
||||||
"hash": "8be7d707321f55f9b15e9817305bd7b2c244c996",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "1318169",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "external-id"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$8c291528-a8b5-4ea2-a530-ab5eed62a2f5",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P646": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P646",
|
|
||||||
"hash": "e5c4757c4fc5cc3cd91be680cac5bf11b6c5a9c9",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "/m/095lbx",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "external-id"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$CAE845CD-2739-4F5B-A1C8-07BF19CA476E",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1082": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1082",
|
|
||||||
"hash": "82b127e7d94aea3e97df48c239ba95a8636a7f15",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"amount": "+83",
|
|
||||||
"unit": "1"
|
|
||||||
},
|
|
||||||
"type": "quantity"
|
|
||||||
},
|
|
||||||
"datatype": "quantity"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"qualifiers": {
|
|
||||||
"P1539": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1539",
|
|
||||||
"hash": "d7a33aa3284a7d6d72a972b23ee46451239a975e",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"amount": "+38",
|
|
||||||
"unit": "1"
|
|
||||||
},
|
|
||||||
"type": "quantity"
|
|
||||||
},
|
|
||||||
"datatype": "quantity"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1540": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1540",
|
|
||||||
"hash": "bca52a91b62b1e768f81afdba8b515b8125aad85",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"amount": "+45",
|
|
||||||
"unit": "1"
|
|
||||||
},
|
|
||||||
"type": "quantity"
|
|
||||||
},
|
|
||||||
"datatype": "quantity"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1538": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1538",
|
|
||||||
"hash": "9210a01fc4dfd89134e386c82fddf587299ad72d",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"amount": "+35",
|
|
||||||
"unit": "1"
|
|
||||||
},
|
|
||||||
"type": "quantity"
|
|
||||||
},
|
|
||||||
"datatype": "quantity"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P459": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P459",
|
|
||||||
"hash": "f34609d440cf44ebaa8a3e704c9369413240618c",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 39825,
|
|
||||||
"id": "Q39825"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P585": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P585",
|
|
||||||
"hash": "4a6fe7f861358928efef462e361b21704446d129",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"time": "+2021-00-00T00:00:00Z",
|
|
||||||
"timezone": 0,
|
|
||||||
"before": 0,
|
|
||||||
"after": 0,
|
|
||||||
"precision": 9,
|
|
||||||
"calendarmodel": "http://www.wikidata.org/entity/Q1985727"
|
|
||||||
},
|
|
||||||
"type": "time"
|
|
||||||
},
|
|
||||||
"datatype": "time"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"qualifiers-order": [
|
|
||||||
"P1539",
|
|
||||||
"P1540",
|
|
||||||
"P1538",
|
|
||||||
"P459",
|
|
||||||
"P585"
|
|
||||||
],
|
|
||||||
"id": "Q4953658$DBF8ABC1-17EA-40A2-B292-A47CCA907133",
|
|
||||||
"rank": "normal",
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"hash": "1777794f031f3bb865bc1ba87bbd53bc9876131c",
|
|
||||||
"snaks": {
|
|
||||||
"P854": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P854",
|
|
||||||
"hash": "4c66907bd33eaecbde827c8cd51e360e09c96a39",
|
|
||||||
"datavalue": {
|
|
||||||
"value": "https://www.nomisweb.co.uk/sources/census_2021_pp",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"datatype": "url"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1476": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1476",
|
|
||||||
"hash": "aa1dc2edff48c56602cb3ea15bb643db64bca4ca",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"text": "Parish Profiles",
|
|
||||||
"language": "en-gb"
|
|
||||||
},
|
|
||||||
"type": "monolingualtext"
|
|
||||||
},
|
|
||||||
"datatype": "monolingualtext"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P1433": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P1433",
|
|
||||||
"hash": "cd20b2aec2e4baf705095049375a6af4c4bd8fb6",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 16970190,
|
|
||||||
"id": "Q16970190"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P123": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P123",
|
|
||||||
"hash": "dd874c88654df8083801b38f5c0607e1755b0234",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 1334971,
|
|
||||||
"id": "Q1334971"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P813": [
|
|
||||||
{
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P813",
|
|
||||||
"hash": "997eb33b0d995db84c7ff54ea10dea029e3b9829",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"time": "+2024-08-05T00:00:00Z",
|
|
||||||
"timezone": 0,
|
|
||||||
"before": 0,
|
|
||||||
"after": 0,
|
|
||||||
"precision": 11,
|
|
||||||
"calendarmodel": "http://www.wikidata.org/entity/Q1985727"
|
|
||||||
},
|
|
||||||
"type": "time"
|
|
||||||
},
|
|
||||||
"datatype": "time"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"snaks-order": [
|
|
||||||
"P854",
|
|
||||||
"P1476",
|
|
||||||
"P1433",
|
|
||||||
"P123",
|
|
||||||
"P813"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"P7959": [
|
|
||||||
{
|
|
||||||
"mainsnak": {
|
|
||||||
"snaktype": "value",
|
|
||||||
"property": "P7959",
|
|
||||||
"hash": "3b8c6dafefe7046353b50e3fbf6371f4cda7c5e7",
|
|
||||||
"datavalue": {
|
|
||||||
"value": {
|
|
||||||
"entity-type": "item",
|
|
||||||
"numeric-id": 67533486,
|
|
||||||
"id": "Q67533486"
|
|
||||||
},
|
|
||||||
"type": "wikibase-entityid"
|
|
||||||
},
|
|
||||||
"datatype": "wikibase-item"
|
|
||||||
},
|
|
||||||
"type": "statement",
|
|
||||||
"id": "Q4953658$388032D0-22B6-4585-87FA-8DB11ABEF47E",
|
|
||||||
"rank": "normal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"sitelinks": {
|
|
||||||
"cebwiki": {
|
|
||||||
"site": "cebwiki",
|
|
||||||
"title": "Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
},
|
|
||||||
"commonswiki": {
|
|
||||||
"site": "commonswiki",
|
|
||||||
"title": "Category:Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
},
|
|
||||||
"enwiki": {
|
|
||||||
"site": "enwiki",
|
|
||||||
"title": "Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
},
|
|
||||||
"frwiki": {
|
|
||||||
"site": "frwiki",
|
|
||||||
"title": "Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
},
|
|
||||||
"svwiki": {
|
|
||||||
"site": "svwiki",
|
|
||||||
"title": "Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
},
|
|
||||||
"trwiki": {
|
|
||||||
"site": "trwiki",
|
|
||||||
"title": "Brackenborough with Little Grimsby",
|
|
||||||
"badges": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"success": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"expected_wikidata": "Q4953658",
|
|
||||||
"expected_commons_cat": "Brackenborough with Little Grimsby"
|
|
||||||
}
|
|
||||||
1870
tests/fixtures/sample_25.json
vendored
1870
tests/fixtures/sample_25.json
vendored
File diff suppressed because it is too large
Load diff
1871
tests/fixtures/sample_26.json
vendored
1871
tests/fixtures/sample_26.json
vendored
File diff suppressed because it is too large
Load diff
1139
tests/fixtures/sample_27.json
vendored
1139
tests/fixtures/sample_27.json
vendored
File diff suppressed because it is too large
Load diff
1494
tests/fixtures/sample_28.json
vendored
1494
tests/fixtures/sample_28.json
vendored
File diff suppressed because it is too large
Load diff
3657
tests/fixtures/sample_29.json
vendored
3657
tests/fixtures/sample_29.json
vendored
File diff suppressed because it is too large
Load diff
1882
tests/fixtures/sample_30.json
vendored
1882
tests/fixtures/sample_30.json
vendored
File diff suppressed because it is too large
Load diff
1855
tests/fixtures/sample_31.json
vendored
1855
tests/fixtures/sample_31.json
vendored
File diff suppressed because it is too large
Load diff
31
tests/fixtures/sample_32.json
vendored
31
tests/fixtures/sample_32.json
vendored
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"lat": 55.7644,
|
|
||||||
"lon": -4.177,
|
|
||||||
"name": "East Kilbride",
|
|
||||||
"wdqs": {
|
|
||||||
"SELECT ?item ?itemLabel ?commonsSiteLink ?commonsCat WHERE {\n ?item wdt:P528 \"277\" .\n ?item wdt:P31 wd:Q5124673 .\n OPTIONAL { ?commonsSiteLink schema:about ?item ;\n schema:isPartOf <https://commons.wikimedia.org/> }\n OPTIONAL { ?item wdt:P373 ?commonsCat }\n SERVICE wikibase:label { bd:serviceParam wikibase:language \"[AUTO_LANGUAGE],en\". }\n}": [
|
|
||||||
{
|
|
||||||
"item": {
|
|
||||||
"type": "uri",
|
|
||||||
"value": "http://www.wikidata.org/entity/Q68826060"
|
|
||||||
},
|
|
||||||
"commonsSiteLink": {
|
|
||||||
"type": "uri",
|
|
||||||
"value": "https://commons.wikimedia.org/wiki/Category:East_Kilbride_(civil_parish)"
|
|
||||||
},
|
|
||||||
"commonsCat": {
|
|
||||||
"type": "literal",
|
|
||||||
"value": "East Kilbride (civil parish)"
|
|
||||||
},
|
|
||||||
"itemLabel": {
|
|
||||||
"xml:lang": "en",
|
|
||||||
"type": "literal",
|
|
||||||
"value": "East Kilbride"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"api": {},
|
|
||||||
"expected_wikidata": "Q68826060",
|
|
||||||
"expected_commons_cat": "East Kilbride (civil parish)"
|
|
||||||
}
|
|
||||||
6248
tests/fixtures/sample_33.json
vendored
6248
tests/fixtures/sample_33.json
vendored
File diff suppressed because it is too large
Load diff
1087
tests/fixtures/sample_34.json
vendored
1087
tests/fixtures/sample_34.json
vendored
File diff suppressed because it is too large
Load diff
20092
tests/fixtures/sample_35.json
vendored
20092
tests/fixtures/sample_35.json
vendored
File diff suppressed because it is too large
Load diff
6470
tests/fixtures/sample_36.json
vendored
6470
tests/fixtures/sample_36.json
vendored
File diff suppressed because it is too large
Load diff
5767
tests/fixtures/sample_37.json
vendored
5767
tests/fixtures/sample_37.json
vendored
File diff suppressed because it is too large
Load diff
3857
tests/fixtures/sample_38.json
vendored
3857
tests/fixtures/sample_38.json
vendored
File diff suppressed because it is too large
Load diff
1842
tests/fixtures/sample_39.json
vendored
1842
tests/fixtures/sample_39.json
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -1,96 +0,0 @@
|
||||||
"""
|
|
||||||
Tests for the sample locations listed in geocode.samples.
|
|
||||||
|
|
||||||
Fixtures are pre-recorded snapshots of WDQS and Wikidata API responses.
|
|
||||||
Generate or refresh them (requires network + DB) with:
|
|
||||||
|
|
||||||
python tests/capture_fixtures.py
|
|
||||||
|
|
||||||
The tests themselves only need the local PostGIS database.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from geocode import samples
|
|
||||||
from lookup import lat_lon_to_wikidata
|
|
||||||
|
|
||||||
FIXTURES_DIR = Path(__file__).parent / "fixtures"
|
|
||||||
|
|
||||||
|
|
||||||
def _load_fixture(i: int) -> dict:
|
|
||||||
path = FIXTURES_DIR / f"sample_{i:02d}.json"
|
|
||||||
if not path.exists():
|
|
||||||
pytest.skip(f"No fixture file — run: python tests/capture_fixtures.py {i}")
|
|
||||||
return json.loads(path.read_text())
|
|
||||||
|
|
||||||
|
|
||||||
def _make_mock_wdqs(fixture: dict, name: str):
|
|
||||||
"""Return a mock for geocode.wikidata.wdqs that replays saved responses."""
|
|
||||||
saved = fixture["wdqs"]
|
|
||||||
|
|
||||||
def mock_wdqs(query: str) -> list:
|
|
||||||
if query not in saved:
|
|
||||||
short = query[:120].replace("\n", " ")
|
|
||||||
raise AssertionError(
|
|
||||||
f"[{name}] Unexpected WDQS query (not in fixture):\n {short}…"
|
|
||||||
)
|
|
||||||
return saved[query]
|
|
||||||
|
|
||||||
return mock_wdqs
|
|
||||||
|
|
||||||
|
|
||||||
def _make_mock_api_call(fixture: dict, name: str):
|
|
||||||
"""Return a mock for geocode.wikidata.api_call that replays saved responses."""
|
|
||||||
saved = fixture["api"]
|
|
||||||
|
|
||||||
def mock_api_call(params: dict) -> dict:
|
|
||||||
key = json.dumps(params, sort_keys=True)
|
|
||||||
if key not in saved:
|
|
||||||
raise AssertionError(
|
|
||||||
f"[{name}] Unexpected api_call (not in fixture): {key}"
|
|
||||||
)
|
|
||||||
return saved[key]
|
|
||||||
|
|
||||||
return mock_api_call
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"i,lat,lon,name",
|
|
||||||
[(i, lat, lon, name) for i, (lat, lon, name) in enumerate(samples)],
|
|
||||||
ids=[name for _, _, name in samples],
|
|
||||||
)
|
|
||||||
def test_example(app_ctx, mocker, i: int, lat: float, lon: float, name: str) -> None:
|
|
||||||
"""Each sample location resolves to the expected Wikidata item and Commons category."""
|
|
||||||
fixture = _load_fixture(i)
|
|
||||||
|
|
||||||
mocker.patch("geocode.wikidata.wdqs", side_effect=_make_mock_wdqs(fixture, name))
|
|
||||||
mocker.patch(
|
|
||||||
"geocode.wikidata.api_call", side_effect=_make_mock_api_call(fixture, name)
|
|
||||||
)
|
|
||||||
|
|
||||||
reply = lat_lon_to_wikidata(lat, lon)
|
|
||||||
result = reply["result"]
|
|
||||||
|
|
||||||
expected_qid = fixture["expected_wikidata"]
|
|
||||||
assert result.get("wikidata") == expected_qid, (
|
|
||||||
f"{name}: wikidata mismatch — expected {expected_qid!r}, "
|
|
||||||
f"got {result.get('wikidata')!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_cat = fixture["expected_commons_cat"]
|
|
||||||
if expected_cat:
|
|
||||||
commons = result.get("commons_cat")
|
|
||||||
assert isinstance(commons, dict), (
|
|
||||||
f"{name}: expected commons_cat={expected_cat!r} but result has none"
|
|
||||||
)
|
|
||||||
assert commons["title"] == expected_cat, (
|
|
||||||
f"{name}: commons_cat mismatch — expected {expected_cat!r}, "
|
|
||||||
f"got {commons['title']!r}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
assert not result.get("commons_cat"), (
|
|
||||||
f"{name}: expected no commons_cat but got {result.get('commons_cat')!r}"
|
|
||||||
)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue