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