159 lines
4.6 KiB
Python
159 lines
4.6 KiB
Python
# This file is part of the Glowtables software
|
|
# Copyright (C) 2023 Valentin Lorentz
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it under the
|
|
# terms of the GNU Affero General Public License version 3, as published by the
|
|
# Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
# PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along with
|
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""Minimal webapp to display Glowtables"""
|
|
|
|
import functools
|
|
import importlib.metadata
|
|
import importlib.resources
|
|
import logging
|
|
import operator
|
|
import xml.etree.ElementTree as ET
|
|
from typing import Callable, List, TypeVar
|
|
|
|
import flask
|
|
|
|
from .cache import Cache
|
|
from .shortxml import Namespace
|
|
from .sparql import RemoteSparqlBackend
|
|
from .table import Language, Table
|
|
|
|
HTML = Namespace("http://www.w3.org/1999/xhtml")
|
|
LANG = Language("en") # TODO: configurable
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = flask.Flask(__name__)
|
|
|
|
TView = TypeVar("TView", bound=Callable)
|
|
|
|
|
|
def _sparql_backend() -> RemoteSparqlBackend:
|
|
return RemoteSparqlBackend(
|
|
"https://query.wikidata.org/sparql",
|
|
agent="Unconfigured Glowtable instance",
|
|
cache=Cache("file:sparql_cache.sqlite3"),
|
|
)
|
|
|
|
|
|
def xhtml_view(f: TView) -> TView:
|
|
"""Decorator for Flask views which may return XHTML as :mod:`xml.etree.ElementTree`
|
|
objects."""
|
|
|
|
@functools.wraps(f)
|
|
def newf(*args, **kwargs):
|
|
res = f(*args, **kwargs)
|
|
if isinstance(res, (ET.Element, ET.ElementTree)):
|
|
xml = ET.tostring(res, default_namespace=HTML.uri)
|
|
return flask.Response(xml, mimetype="application/xhtml+xml")
|
|
else:
|
|
return res
|
|
|
|
return newf # type: ignore[return-value]
|
|
|
|
|
|
def list_tables() -> List[Table]:
|
|
"""Returns all :class:`Table` instances registered as ``glowtables.tables``
|
|
entrypoints."""
|
|
table_entrypoints: List[
|
|
importlib.metadata.EntryPoint
|
|
] = importlib.metadata.entry_points( # type: ignore[call-arg,assignment]
|
|
group="glowtables.tables"
|
|
)
|
|
tables = []
|
|
for table_entrypoint in sorted(table_entrypoints, key=operator.attrgetter("name")):
|
|
table = table_entrypoint.load()
|
|
if not isinstance(table, Table):
|
|
logger.error(
|
|
"%s is %r, which is not an instance of glowtables.table.Table",
|
|
table_entrypoint.name,
|
|
table,
|
|
)
|
|
continue
|
|
tables.append(table)
|
|
|
|
return tables
|
|
|
|
|
|
@app.route("/")
|
|
@xhtml_view
|
|
def index() -> ET.Element:
|
|
"""Displays the list of tables."""
|
|
tables = list_tables()
|
|
|
|
return HTML.html(
|
|
HTML.head(
|
|
HTML.title("Glowtables"),
|
|
HTML.link(rel="stylesheet", type="text/css", href="/style.css"),
|
|
),
|
|
HTML.body(
|
|
HTML.h1("Glowtables"),
|
|
HTML.ul(
|
|
[
|
|
HTML.li(
|
|
HTML.a(table.display_names[LANG], href=f"/tables/{table.id}/")
|
|
)
|
|
for table in tables
|
|
]
|
|
)
|
|
if tables
|
|
else HTML.p(
|
|
"""
|
|
There are no tables defined, check the Glowtables documentation
|
|
to find how to configure them.
|
|
"""
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
@app.route("/style.css")
|
|
def style() -> flask.Response:
|
|
"""Serves the CSS."""
|
|
css = importlib.resources.files(__package__).joinpath("style.css").read_bytes()
|
|
return flask.Response(css, mimetype="text/css")
|
|
|
|
|
|
@app.route("/tables/<table_id>/")
|
|
@xhtml_view
|
|
def table_(table_id: str) -> ET.Element:
|
|
"""Displays a table."""
|
|
tables = list_tables()
|
|
for table in tables:
|
|
if table.id == table_id:
|
|
break
|
|
else:
|
|
flask.abort(404)
|
|
|
|
return HTML.html(
|
|
HTML.head(
|
|
HTML.title("Glowtables"),
|
|
HTML.link(rel="stylesheet", type="text/css", href="/style.css"),
|
|
),
|
|
HTML.body(
|
|
HTML.h1(table.display_names[LANG]),
|
|
HTML.table(
|
|
HTML.thead(
|
|
HTML.tr(
|
|
HTML.th(field.display_names[LANG]) for field in table.fields
|
|
)
|
|
),
|
|
HTML.tbody(
|
|
HTML.tr(HTML.td(cell) for cell in row)
|
|
for row in table.query(_sparql_backend())
|
|
),
|
|
),
|
|
),
|
|
)
|