Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API server #201

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def warninger(message, category, filename, lineno, line=None):
with engine.begin() as conn:
conn.execute(text(f"CREATE SCHEMA IF NOT EXISTS {table.schema}"))
elif engine.name == "sqlite":
table.schema = ""
table.name = f"{args.schema_prefix}{module_name}_{table.name}"

if args.drop_first:
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ xlrd
cssselect
sqlalchemy
pyproj
shapely
shapely
flask
3 changes: 3 additions & 0 deletions server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from server.__main__ import API

__all__ = ["API"]
99 changes: 99 additions & 0 deletions server/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# TODO: docs, requirements
# TODO: expose bool arg
import argparse
import logging

from flask import Flask, abort, jsonify, render_template, request
from sqlalchemy import create_engine, text, func, column, collate
from sqlalchemy.orm import sessionmaker


class API(Flask):
def __init__(self, import_name, connstring: str):
super(API, self).__init__(import_name)
self.engine = create_engine(connstring)
self.sessionmaker = sessionmaker(bind=self.engine)

self.table_schemas = {}
from data.justice.schema import meta

for table_name, table in meta.tables.items():
table.name = "justice_" + table.name # TODO: jen pro sqlite
self.table_schemas[("justice", table_name)] = table

self.route("/", methods=["GET"])(self.index)
self.route("/status", methods=["GET"])(self.status)
self.route("/api/search", methods=["GET"])(self.search)
# self.route("/api/datasets", methods=["GET"])(self.datasets)

def status(self):
return jsonify({"status": "ok"})

def index(self):
return render_template("index.j2")

def search(self):
q = request.args.get("q")
if not q:
abort(400, "missing query parameter 'q'")

results = []
session = self.sessionmaker()

# TODO: neni tam zadne razeni
# TODO: neni to kolace nad latin1_general_ci_ai (nejak mi nefungovala)
# TODO: neni to indexovane (na sqlite mi to nechce chytit index na computed sloupec)
tbl = self.table_schemas[("justice", "angazovane_osoby")]
cols = {k: v for k, v in tbl.columns.items()} # TODO: yikes
query = (
session.query(cols["jmeno"], cols["prijmeni"], cols["datum_narozeni"])
.filter(
func.lower((column("jmeno") + " " + column("prijmeni"))).contains(q)
)
.group_by(column("jmeno"), column("prijmeni"), column("datum_narozeni"))
.limit(100)
)
print(query)
results = [
{
"jmeno": j.jmeno,
"prijmeni": j.prijmeni,
"datum_narozeni": j.datum_narozeni.isoformat()
if j.datum_narozeni
else None,
}
for j in query.all()
]

return jsonify(result={"status": "ok", "results": results})


if __name__ == "__main__":
logging.getLogger().setLevel(logging.INFO)
parser = argparse.ArgumentParser()
parser.add_argument(
"--debug",
action="store_true",
help="enable debug mode in Flask (autoreload, exception handling)",
)
parser.add_argument(
"--port", type=int, default=3000, help="HTTP port to host this on"
)
parser.add_argument(
"--connstring",
required=True,
type=str,
help="connection string pro databazi tve volby",
)
parser.add_argument(
"--schema_prefix",
type=str,
default="",
help="prefix pro nazvy schemat (postgres) ci tabulek (sqlite)",
)
args = parser.parse_args()

app = API(__name__, connstring=args.connstring)
app.json.ensure_ascii = False

app.run(port=args.port, debug=args.debug)
32 changes: 32 additions & 0 deletions server/templates/index.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<title>ODAN</title>

<form action="/" method="get">
<input name="q" id="q" />
<input type="submit" id="search" value="Hledat" />
</form>

<div id="results"></div>

<script type="text/javascript">
document.getElementById("search").addEventListener("click", async function(e) {
e.preventDefault();
const q = document.getElementById("q").value;
if (q.length < 3) {
alert("Zadejte alespoň 3 znaky");
return false;
}

const results = document.getElementById("results");
console.log("Hledam " + q);
const data = await fetch("/api/search?q=" + q); // TODO: cistsi parametrizace?
const json = await data.json();
results.innerHTML = `Nalezeno ${json.result.results.length} výsledků`;

json.result.results.forEach(function(item) {
const div = document.createElement("div");
div.innerHTML = `<h3>${item.jmeno} ${item.prijmeni}</h3><p>${item.datum_narozeni}</p>`;
results.appendChild(div);
});
});

</script>
Empty file added server/tests/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions server/tests/test_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest

from server import API


@pytest.fixture
def client(tmp_path):
db_path = tmp_path / "test.db"
yield API("test", f"sqlite:///{db_path}").test_client()


def test_status(client):
response = client.get("/status")
assert response.status_code == 200
assert response.data == b'{"status":"ok"}\n'
Loading