From 167f527580c7f06336fbb613e2c6b690895472ef Mon Sep 17 00:00:00 2001
From: Felix Delattre <fd@gfz-potsdam.de>
Date: Thu, 22 Jul 2021 06:17:40 +0000
Subject: [PATCH] Added postgresql database

---
 .gitignore               |  2 ++
 .gitlab-ci.yml           | 10 ++++++++-
 obmdataapi/__init__.py   | 47 +++++++++++++++++++++++++++++++++++++++-
 setup.py                 |  3 ++-
 tests/conftest.py        | 16 ++++++++++++++
 tests/test_database.sql  |  3 +++
 tests/test_obmdataapi.py | 15 +++++++++++++
 7 files changed, 93 insertions(+), 3 deletions(-)
 create mode 100644 tests/test_database.sql

diff --git a/.gitignore b/.gitignore
index 7d3b5be..6408c72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,5 @@ __pycache__
 build
 dist
 venv
+
+config.py
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 574acac..e01c1e7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,16 @@
 image: python:3-buster
 
-# Make pip cache the installed dependencies
+services:
+  - postgres:13.3
+
 variables:
   PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
+  POSTGRES_DB: dataapi
+  POSTGRES_USER: example
+  POSTGRES_PASSWORD: ""
+  POSTGRES_HOST_AUTH_METHOD: trust
+  OBM_DATA_API_DATABASE_URL: "postgresql://example:@postgres:5432/dataapi"
+  OBM_DATA_API_TESTING: "True"
 cache:
   paths:
     - .cache/pip
diff --git a/obmdataapi/__init__.py b/obmdataapi/__init__.py
index 170f212..4a839c3 100644
--- a/obmdataapi/__init__.py
+++ b/obmdataapi/__init__.py
@@ -18,9 +18,13 @@
 
 import logging
 import sys
+import os
 
-from quart import Quart
 from ajsonrpc.backend.quart import JSONRPCQuart
+from databases import Database
+from dotenv import load_dotenv
+from pathlib import Path
+from quart import Quart
 
 logger = logging.getLogger()
 logger.setLevel(logging.DEBUG)
@@ -28,14 +32,55 @@ logger.addHandler(logging.StreamHandler(sys.stdout))
 
 logger.info("obmapidata has started")
 
+load_dotenv(Path(".env").resolve())
+
 app = Quart(__name__)
 app.api = JSONRPCQuart()
 
+
+# Load configuration from file
+config_file = Path("config.py")
+if config_file.is_file():
+    app.config.from_pyfile(config_file.resolve())
+
+# Allow some environment variables to overrite configuration
+if "OBM_DATA_API_DATABASE_URL" in os.environ:
+    app.config.update(DATABASE_URL=os.environ.get("OBM_DATA_API_DATABASE_URL"))
+if "OBM_DATA_API_TESTING" in os.environ:
+    app.config.update(TESTING=os.environ.get("OBM_DATA_API_TESTING"))
+
+
+# Define main entrypoint for JSON-RPC API
 app.post(
     "/v1",
 )(app.api.handler)
 
+# Add database connection
+@app.before_serving
+async def startup() -> None:
+    app.db = await _create_database_connection()
+
+
+async def _create_database_connection() -> Database:
+    db = Database(
+        app.config.get("DATABASE_URL", "postgresql://postgres:@localhost:5432/dataapi")
+    )
+    await db.connect()
+    return db
+
+
+# JSON-RPC API endpoints
+
 
 @app.api.add_function
 async def heartbeat() -> dict:
     return {"status": "healthy"}
+
+
+@app.api.add_function
+async def get_building(osm_id) -> dict:
+    building = await app.db.fetch_val(
+        "SELECT osm_id FROM osm_building_polygons WHERE osm_id = :osm_id",
+        values={"osm_id": int(osm_id)},
+    )
+    return {"building": str(building)}
diff --git a/setup.py b/setup.py
index 0a08415..2336a6b 100644
--- a/setup.py
+++ b/setup.py
@@ -22,6 +22,7 @@ from setuptools import setup, find_packages
 tests_require = [
     "jsonrpcclient[requests]",
     "pytest",
+    "pytest-asyncio",
 ]
 
 linters_require = ["pre-commit", "pylint"]
@@ -35,7 +36,7 @@ setup(
     license="AGPLv3+",
     keywords="API",
     author="Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ",
-    install_requires=["ajsonrpc>=1.2.0", "quart"],
+    install_requires=["ajsonrpc>=1.2.0", "python-dotenv", "databases[postgresql]", "quart"],
     tests_require=tests_require,
     extras_require={
         "tests": tests_require,
diff --git a/tests/conftest.py b/tests/conftest.py
index 5cc4e52..027f1fe 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -16,6 +16,7 @@
 # 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/.
 
+import asyncio
 import pytest
 import obmdataapi
 
@@ -28,4 +29,19 @@ def app():
 @pytest.fixture
 def client(app):
     """A test client for the app."""
+
+    init_db(app)
     return app.test_client()
+
+
+def init_db(app) -> None:
+    """Populates a test database with a basic schema and data."""
+
+    async def _inner() -> None:
+        if app.config.get("TESTING", False):
+            db = await obmdataapi._create_database_connection()
+            with app.open_resource("../tests/test_database.sql", "r") as file_:
+                for command in file_.read().split(";"):
+                    await db.execute(command)
+
+    asyncio.run(_inner())
diff --git a/tests/test_database.sql b/tests/test_database.sql
new file mode 100644
index 0000000..41b01af
--- /dev/null
+++ b/tests/test_database.sql
@@ -0,0 +1,3 @@
+DROP TABLE IF EXISTS osm_building_polygons;
+CREATE TABLE osm_building_polygons (osm_id integer NOT NULL PRIMARY KEY);
+INSERT INTO osm_building_polygons(osm_id) VALUES (-12412416);
diff --git a/tests/test_obmdataapi.py b/tests/test_obmdataapi.py
index 2a2f732..a4c7f76 100644
--- a/tests/test_obmdataapi.py
+++ b/tests/test_obmdataapi.py
@@ -17,12 +17,27 @@
 # along with this program. If not, see http://www.gnu.org/licenses/.
 
 import logging
+import pytest
 
+from databases import Database
 from jsonrpcclient import request
 
+from obmdataapi import _create_database_connection
+
 logger = logging.getLogger()
 
 
+@pytest.mark.asyncio
+async def test_create_database_connection(client) -> None:
+    db = await _create_database_connection()
+    assert type(db) is Database
+
+
 def test_heartbeat(client) -> None:
     response = request("http://127.0.0.1:5000/v1", "heartbeat").data.result
     assert "healthy" in response["status"]
+
+
+def test_get_building(client) -> None:
+    response = request("http://127.0.0.1:5000/v1", "get_building", "-12412416").data.result
+    assert "-12412416" in response["building"]
-- 
GitLab