generated from opensafely-core/repo-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This assumes the datastore will have been configured with the timescale extension already. Locally this is handled by the timescaledb container, and in production we have already configured it. The --database-url option for the CLI ensures we have that env var set. We could always pass it through (since it's in the context) but I wasn't sure if we actually needed it set via the CLI. The writer class means we can ensure the appropriate table exists and has had a hypertable created for it. All tables have a unique constraint with the common _must_be_different suffix so the INSERT's can act as an UPSERT (with ON CONFLICT) when it's just the value that needs updating.
- Loading branch information
Showing
14 changed files
with
130 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from .writer import TimescaleDBWriter | ||
|
||
|
||
__all__ = [ | ||
"TimescaleDBWriter", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
github_pull_requests = """ | ||
CREATE TABLE IF NOT EXISTS github_pull_requests ( | ||
time TIMESTAMP WITH TIME ZONE NOT NULL, | ||
name TEXT NOT NULL, | ||
value INTEGER NOT NULL, | ||
author TEXT NOT NULL, | ||
organisation TEXT NOT NULL, | ||
repo TEXT NOT NULL, | ||
CONSTRAINT github_pull_requests_must_be_different UNIQUE (time, name, author, repo) | ||
); | ||
""" | ||
slack_tech_support = """ | ||
CREATE TABLE IF NOT EXISTS slack_tech_support ( | ||
time TIMESTAMP WITH TIME ZONE NOT NULL, | ||
name TEXT NOT NULL, | ||
value INTEGER NOT NULL, | ||
CONSTRAINT slack_tech_support_must_be_different UNIQUE (time, name) | ||
); | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import os | ||
from datetime import datetime, time | ||
|
||
import psycopg | ||
import structlog | ||
|
||
from . import tables | ||
|
||
|
||
log = structlog.get_logger() | ||
|
||
DATABASE_URL = os.environ["DATABASE_URL"] | ||
|
||
|
||
def ensure_table(name): | ||
""" | ||
Ensure both the table and hypertable config exist in the database | ||
""" | ||
run(getattr(tables, name)) | ||
|
||
run( | ||
"SELECT create_hypertable(%s, 'time', if_not_exists => TRUE);", | ||
[name], | ||
) | ||
|
||
|
||
def run(sql, *args): | ||
with psycopg.connect(DATABASE_URL) as conn: | ||
cursor = conn.cursor() | ||
|
||
return cursor.execute(sql, *args) | ||
|
||
|
||
class TimescaleDBWriter: | ||
def __init__(self, table, key): | ||
self.key = key | ||
self.table = table | ||
|
||
def __enter__(self): | ||
ensure_table(self.table) | ||
|
||
return self | ||
|
||
def __exit__(self, *args): | ||
pass | ||
|
||
def write(self, date, value, **kwargs): | ||
# convert date to a timestamp | ||
# TODO: do we need to do any checking to make sure this is tz-aware and in | ||
# UTC? | ||
dt = datetime.combine(date, time()) | ||
|
||
# insert into the table set at instantiation | ||
# unique by the tables `{name}_must_be_different` and we always want to | ||
# bump the value if that triggers a conflict | ||
# the columns could differ per table… do we want an object to represent tables? | ||
if kwargs: | ||
extra_fields = ", " + ", ".join(kwargs.keys()) | ||
placeholders = ", " + ", ".join(["%s" for k in kwargs.keys()]) | ||
else: | ||
extra_fields = "" | ||
placeholders = "" | ||
sql = f""" | ||
INSERT INTO {self.table} (time, name, value {extra_fields}) | ||
VALUES (%s, %s, %s {placeholders}) | ||
ON CONFLICT ON CONSTRAINT {self.table}_must_be_different DO UPDATE SET value = EXCLUDED.value; | ||
""" | ||
|
||
run(sql, (dt, self.key, value, *kwargs.values())) | ||
|
||
log.debug( | ||
self.key, | ||
date=dt.isoformat(), | ||
value=value, | ||
**kwargs, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.