Add types and docstrings
This commit is contained in:
parent
848737742c
commit
2302809364
82
web_view.py
82
web_view.py
|
@ -4,15 +4,17 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import typing
|
||||||
from time import sleep, time
|
from time import sleep, time
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import flask_login
|
import flask_login # type: ignore
|
||||||
import GeoIP
|
import GeoIP # type: ignore
|
||||||
import lxml
|
import lxml
|
||||||
import maxminddb
|
import maxminddb
|
||||||
import requests
|
import requests
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
import werkzeug
|
||||||
from requests_oauthlib import OAuth1Session
|
from requests_oauthlib import OAuth1Session
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
from sqlalchemy.sql.expression import update
|
from sqlalchemy.sql.expression import update
|
||||||
|
@ -34,6 +36,8 @@ from matcher.data import property_map
|
||||||
|
|
||||||
# from werkzeug.debug.tbtools import get_current_traceback
|
# from werkzeug.debug.tbtools import get_current_traceback
|
||||||
|
|
||||||
|
StrDict = dict[str, typing.Any]
|
||||||
|
|
||||||
srid = 4326
|
srid = 4326
|
||||||
re_point = re.compile(r"^POINT\((.+) (.+)\)$")
|
re_point = re.compile(r"^POINT\((.+) (.+)\)$")
|
||||||
|
|
||||||
|
@ -56,7 +60,8 @@ re_qid = re.compile(r"^Q\d+$")
|
||||||
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def shutdown_session(exception=None):
|
def shutdown_session(exception=None) -> None:
|
||||||
|
"""Shutdown session."""
|
||||||
database.session.remove()
|
database.session.remove()
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,14 +75,15 @@ def dict_repr_values(d):
|
||||||
return {key: repr(value) for key, value in d.items()}
|
return {key: repr(value) for key, value in d.items()}
|
||||||
|
|
||||||
|
|
||||||
def cors_jsonify(*args, **kwargs):
|
def cors_jsonify(*args, **kwargs) -> flask.Response:
|
||||||
"""Add CORS header to JSON."""
|
"""Add CORS header to JSON."""
|
||||||
response = flask.jsonify(*args, **kwargs)
|
response = flask.jsonify(*args, **kwargs)
|
||||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def check_for_tagged_qids(qids):
|
def check_for_tagged_qids(qids: list[str]) -> set[str]:
|
||||||
|
"""Check OSM for existing wikidata tags for given QIDs."""
|
||||||
tagged = set()
|
tagged = set()
|
||||||
for qid in qids:
|
for qid in qids:
|
||||||
for cls in model.Point, model.Polygon, model.Line:
|
for cls in model.Point, model.Polygon, model.Line:
|
||||||
|
@ -103,23 +109,27 @@ def check_for_tagged_qid(qid):
|
||||||
def geoip_user_record():
|
def geoip_user_record():
|
||||||
gi = GeoIP.open(app.config["GEOIP_DATA"], GeoIP.GEOIP_STANDARD)
|
gi = GeoIP.open(app.config["GEOIP_DATA"], GeoIP.GEOIP_STANDARD)
|
||||||
|
|
||||||
remote_ip = flask.request.get("ip", flask.request.remote_addr)
|
remote_ip = flask.request.args.get("ip", flask.request.remote_addr)
|
||||||
return gi.record_by_addr(remote_ip)
|
return gi.record_by_addr(remote_ip)
|
||||||
|
|
||||||
|
|
||||||
def get_user_location():
|
def get_user_location() -> StrDict | None:
|
||||||
|
"""Get user location."""
|
||||||
remote_ip = flask.request.args.get("ip", flask.request.remote_addr)
|
remote_ip = flask.request.args.get("ip", flask.request.remote_addr)
|
||||||
|
assert remote_ip
|
||||||
maxmind = maxminddb_reader.get(remote_ip)
|
maxmind = maxminddb_reader.get(remote_ip)
|
||||||
return maxmind.get("location") if maxmind else None
|
return typing.cast(StrDict, maxmind.get("location")) if maxmind else None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def redirect_from_root():
|
def redirect_from_root() -> werkzeug.wrappers.Response:
|
||||||
|
"""Redirect from root to map start page."""
|
||||||
return flask.redirect(flask.url_for("map_start_page"))
|
return flask.redirect(flask.url_for("map_start_page"))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/index")
|
@app.route("/index")
|
||||||
def index_page():
|
def index_page() -> str:
|
||||||
|
"""Index page."""
|
||||||
return flask.render_template("index.html")
|
return flask.render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,7 +140,8 @@ def get_username() -> str | None:
|
||||||
|
|
||||||
|
|
||||||
@app.route("/isa/Q<int:item_id>", methods=["GET", "POST"])
|
@app.route("/isa/Q<int:item_id>", methods=["GET", "POST"])
|
||||||
def isa_page(item_id):
|
def isa_page(item_id: int) -> werkzeug.wrappers.Response | str:
|
||||||
|
"""Return IsA page."""
|
||||||
item = api.get_item(item_id)
|
item = api.get_item(item_id)
|
||||||
|
|
||||||
if flask.request.method == "POST":
|
if flask.request.method == "POST":
|
||||||
|
@ -140,19 +151,26 @@ def isa_page(item_id):
|
||||||
database.session.commit()
|
database.session.commit()
|
||||||
flask.flash("extra OSM tag/key added")
|
flask.flash("extra OSM tag/key added")
|
||||||
|
|
||||||
return flask.redirect(flask.url_for(flask.request.endpoint, item_id=item_id))
|
endpoint = flask.request.endpoint
|
||||||
|
assert endpoint
|
||||||
|
return flask.redirect(flask.url_for(endpoint, item_id=item_id))
|
||||||
|
|
||||||
q = model.ItemExtraKeys.query.filter_by(item=item)
|
q = model.ItemExtraKeys.query.filter_by(item=item)
|
||||||
extra = [e.tag_or_key for e in q]
|
extra = [e.tag_or_key for e in q]
|
||||||
subclass_property = "P279"
|
subclass_property = "P279"
|
||||||
|
|
||||||
subclass_list = []
|
subclass_list = []
|
||||||
|
assert item
|
||||||
for s in item.get_claim(subclass_property):
|
for s in item.get_claim(subclass_property):
|
||||||
subclass = api.get_item(s["numeric-id"])
|
assert isinstance(s, dict)
|
||||||
|
subclass_item_id = s["numeric-id"]
|
||||||
|
assert subclass_item_id and isinstance(subclass_item_id, int)
|
||||||
|
subclass = api.get_item(subclass_item_id)
|
||||||
|
assert subclass
|
||||||
subclass_list.append(
|
subclass_list.append(
|
||||||
{
|
{
|
||||||
"qid": s["id"],
|
"qid": s["id"],
|
||||||
"item_id": s["numeric-id"],
|
"item_id": subclass_item_id,
|
||||||
"label": subclass.label(),
|
"label": subclass.label(),
|
||||||
"description": subclass.description(),
|
"description": subclass.description(),
|
||||||
"isa_page_url": flask.url_for("isa_page", item_id=s["numeric-id"]),
|
"isa_page_url": flask.url_for("isa_page", item_id=s["numeric-id"]),
|
||||||
|
@ -241,7 +259,8 @@ def identifier_page(pid):
|
||||||
|
|
||||||
|
|
||||||
@app.route("/map")
|
@app.route("/map")
|
||||||
def map_start_page():
|
def map_start_page() -> werkzeug.wrappers.Response:
|
||||||
|
"""Map start page."""
|
||||||
loc = get_user_location()
|
loc = get_user_location()
|
||||||
|
|
||||||
if loc:
|
if loc:
|
||||||
|
@ -278,6 +297,7 @@ def documentation_page() -> str:
|
||||||
def search_page() -> str:
|
def search_page() -> str:
|
||||||
"""Search."""
|
"""Search."""
|
||||||
loc = get_user_location()
|
loc = get_user_location()
|
||||||
|
assert loc
|
||||||
q = flask.request.args.get("q")
|
q = flask.request.args.get("q")
|
||||||
|
|
||||||
user = flask_login.current_user
|
user = flask_login.current_user
|
||||||
|
@ -298,15 +318,16 @@ def search_page() -> str:
|
||||||
|
|
||||||
@app.route("/map/<int:zoom>/<float(signed=True):lat>/<float(signed=True):lon>")
|
@app.route("/map/<int:zoom>/<float(signed=True):lat>/<float(signed=True):lon>")
|
||||||
def map_location(zoom: int, lat: float, lon: float) -> str:
|
def map_location(zoom: int, lat: float, lon: float) -> str:
|
||||||
|
"""Map location."""
|
||||||
qid = flask.request.args.get("item")
|
qid = flask.request.args.get("item")
|
||||||
isa_param = flask.request.args.get("isa")
|
isa_param = flask.request.args.get("isa")
|
||||||
if qid:
|
if qid:
|
||||||
api.get_item(qid[1:])
|
api.get_item(int(qid[1:]))
|
||||||
|
|
||||||
isa_list = []
|
isa_list = []
|
||||||
if isa_param:
|
if isa_param:
|
||||||
for isa_qid in isa_param.split(";"):
|
for isa_qid in isa_param.split(";"):
|
||||||
isa = api.get_item(isa_qid[1:])
|
isa = api.get_item(int(isa_qid[1:]))
|
||||||
if not isa:
|
if not isa:
|
||||||
continue
|
continue
|
||||||
cur = {
|
cur = {
|
||||||
|
@ -330,7 +351,8 @@ def map_location(zoom: int, lat: float, lon: float) -> str:
|
||||||
|
|
||||||
|
|
||||||
@app.route("/item/Q<int:item_id>")
|
@app.route("/item/Q<int:item_id>")
|
||||||
def lookup_item(item_id):
|
def lookup_item(item_id: int):
|
||||||
|
"""Lookup item."""
|
||||||
item = api.get_item(item_id)
|
item = api.get_item(item_id)
|
||||||
if not item:
|
if not item:
|
||||||
# TODO: show nicer page for Wikidata item not found
|
# TODO: show nicer page for Wikidata item not found
|
||||||
|
@ -360,7 +382,8 @@ def lookup_item(item_id):
|
||||||
|
|
||||||
|
|
||||||
@app.route("/search/map")
|
@app.route("/search/map")
|
||||||
def search_map_page():
|
def search_map_page() -> str:
|
||||||
|
"""Search map page."""
|
||||||
user_lat, user_lon = get_user_location() or (None, None)
|
user_lat, user_lon = get_user_location() or (None, None)
|
||||||
|
|
||||||
q = flask.request.args.get("q")
|
q = flask.request.args.get("q")
|
||||||
|
@ -394,12 +417,13 @@ def read_isa_filter_param():
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/1/location")
|
@app.route("/api/1/location")
|
||||||
def show_user_location():
|
def show_user_location() -> werkzeug.wrappers.Response:
|
||||||
|
"""User location."""
|
||||||
return cors_jsonify(get_user_location())
|
return cors_jsonify(get_user_location())
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/1/count")
|
@app.route("/api/1/count")
|
||||||
def api_wikidata_items_count():
|
def api_wikidata_items_count() -> werkzeug.wrappers.Response:
|
||||||
t0 = time()
|
t0 = time()
|
||||||
isa_filter = read_isa_filter_param()
|
isa_filter = read_isa_filter_param()
|
||||||
count = api.wikidata_items_count(read_bounds_param(), isa_filter=isa_filter)
|
count = api.wikidata_items_count(read_bounds_param(), isa_filter=isa_filter)
|
||||||
|
@ -610,7 +634,7 @@ def api_polygon(osm_type, osm_id):
|
||||||
@app.route("/refresh/Q<int:item_id>")
|
@app.route("/refresh/Q<int:item_id>")
|
||||||
def refresh_item(item_id: int) -> str:
|
def refresh_item(item_id: int) -> str:
|
||||||
"""Refresh the local mirror of a Wikidata item."""
|
"""Refresh the local mirror of a Wikidata item."""
|
||||||
assert not model.Item.query.get(item_id)
|
existing = model.Item.query.get(item_id)
|
||||||
|
|
||||||
qid = f"Q{item_id}"
|
qid = f"Q{item_id}"
|
||||||
entity = wikidata_api.get_entity(qid)
|
entity = wikidata_api.get_entity(qid)
|
||||||
|
@ -618,13 +642,17 @@ def refresh_item(item_id: int) -> str:
|
||||||
assert qid == entity_qid
|
assert qid == entity_qid
|
||||||
|
|
||||||
coords = wikidata.get_entity_coords(entity["claims"])
|
coords = wikidata.get_entity_coords(entity["claims"])
|
||||||
assert coords
|
|
||||||
|
|
||||||
obj = {k: v for k, v in entity.items() if k in entity_keys}
|
obj = {k: v for k, v in entity.items() if k in entity_keys}
|
||||||
item = model.Item(item_id=item_id, **obj)
|
if existing:
|
||||||
print(item)
|
for k, v in obj.items():
|
||||||
item.locations = model.location_objects(coords)
|
setattr(model, k, v)
|
||||||
database.session.add(item)
|
else:
|
||||||
|
item = model.Item(item_id=item_id, **obj)
|
||||||
|
database.session.add(item)
|
||||||
|
|
||||||
|
if coords:
|
||||||
|
item.locations = model.location_objects(coords)
|
||||||
database.session.commit()
|
database.session.commit()
|
||||||
|
|
||||||
return "done"
|
return "done"
|
||||||
|
|
Loading…
Reference in a new issue