Skip to content

Commit

Permalink
Merge branch 'master' into TP2000-1471--task-workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
paulpepper-trade authored Sep 20, 2024
2 parents ee4871d + 830cbdb commit 9faac8b
Show file tree
Hide file tree
Showing 44 changed files with 2,253 additions and 184 deletions.
2 changes: 1 addition & 1 deletion common/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ def __init__(self, *args, **kwargs):

self.fields["end_date"].help_text = (
f"Leave empty if {get_model_indefinite_article(self.instance)} "
f"{self.instance._meta.verbose_name} is needed for an unlimited time."
f"{self.instance._meta.verbose_name} is needed for an unlimited time"
)

if self.instance.valid_between:
Expand Down
6 changes: 3 additions & 3 deletions common/jinja2/common/app_info.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@
{"text": "Environment variable"},
],
[
{"text": "APP_UPDATED_TIME"},
{"text": APP_UPDATED_TIME},
{"text": "Estimated application deploy time"},
{"text": "UPTIME"},
{"text": UPTIME},
{"text": "Time this instance has been in service"},
],
[
{"text": "LAST_TRANSACTION_TIME"},
Expand Down
59 changes: 59 additions & 0 deletions common/tests/test_util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import json
import os
from unittest import mock

Expand All @@ -14,6 +15,64 @@
pytestmark = pytest.mark.django_db


@pytest.mark.parametrize(
"environment_key, expected_result",
(
(
{
"engine": "engine",
"username": "username",
"password": "password",
"host": "host",
"port": 1234,
"dbname": "dbname",
},
"engine://username:password@host:1234/dbname",
),
(
{
"engine": "engine",
"username": "username",
"host": "host",
"dbname": "dbname",
},
"engine://username@host/dbname",
),
(
{
"engine": "engine",
"host": "host",
"dbname": "dbname",
},
"engine://host/dbname",
),
(
{
"engine": "engine",
"password": "password",
"port": 1234,
"dbname": "dbname",
},
"engine:///dbname",
),
(
{
"engine": "engine",
"dbname": "dbname",
},
"engine:///dbname",
),
),
)
def test_database_url_from_env(environment_key, expected_result):
with mock.patch.dict(
os.environ,
{"DATABASE_CREDENTIALS": json.dumps(environment_key)},
clear=True,
):
assert util.database_url_from_env("DATABASE_CREDENTIALS") == expected_result


@pytest.mark.parametrize(
"value, expected",
[
Expand Down
44 changes: 44 additions & 0 deletions common/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import json
import os
import re
import typing
Expand Down Expand Up @@ -54,10 +55,53 @@
major, minor, patch = python_version_tuple()


def is_cloud_foundry():
"""Return True if the deployment environment contains a `VCAP_SERVICES` env
var, indicating a CloudFoundry environment, False otherwise."""
return "VCAP_SERVICES" in os.environ


def classproperty(fn):
return classmethod(property(fn))


def database_url_from_env(environment_key: str) -> str:
"""
Return a database URL string from the environment variable identified by
`environment_key`. The environment variable should be parsable as a
JSON-like string and may contain the keys:
"engine" (Required) - database engine id. For instance "postgres" or "sqlite".
"username" (Optional if "password" is not present) - database user name.
"password" (Optional) - database user's password.
"host" (Optional if "port" is not present) - database hostname.
"port" (Optional) - database host port.
"dbname" (Required) - database name.
If all keys are present, then the returned result would be a string of the
form:
<engine>://<username>:<password>@<host>:<port>/<dbname>
This is a plug-in, less naive version of
`dbt_copilot_python.database.database_url_from_env()` making `username`,
`password`, `host` and `port` an optional as described above.
"""
config = json.loads(os.environ[environment_key])

username = config.get("username", "")
password = config.get("password")
host = config.get("host", "")
port = config.get("port")

config["username"] = username
config["password"] = f":{password}" if username and password else ""
config["host"] = f"@{host}" if (username or password) and host else host
config["port"] = f":{port}" if host and port else ""

return "{engine}://{username}{password}{host}{port}/{dbname}".format(**config)


def is_truthy(value: Union[str, bool]) -> bool:
"""
Check whether a string represents a True boolean value.
Expand Down
36 changes: 33 additions & 3 deletions common/views/pages.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Common views."""

import logging
import os
import time
from datetime import datetime
from datetime import timedelta
from typing import Dict
from typing import List
from typing import Optional
Expand Down Expand Up @@ -36,6 +38,7 @@
from common.celery import app as celery_app
from common.forms import HomeSearchForm
from common.models import Transaction
from common.util import is_cloud_foundry
from exporter.sqlite.util import sqlite_dumps
from footnotes.models import Footnote
from geo_areas.models import GeographicalArea
Expand All @@ -47,6 +50,8 @@
from workbaskets.models import WorkBasket
from workbaskets.models import WorkflowStatus

logger = logging.getLogger(__name__)


class HomeView(LoginRequiredMixin, FormView):
template_name = "common/homepage.jinja"
Expand Down Expand Up @@ -322,6 +327,33 @@ def get(self, request, *args, **kwargs) -> HttpResponse:
)


def get_uptime() -> str:
"""
Return approximate system uptime in a platform-independent way as a string
in the following format:
"<days> days, <hours> hours, <mins> minutes"
"""
try:
if is_cloud_foundry():
# CF recycles Garden containers so time.monotonic() returns a
# misleading value. However, file modified time is set on deployment.
uptime = timedelta(seconds=(time.time() - os.path.getmtime(__file__)))
else:
# time.monotonic() doesn't count time spent in hibernation, so may
# be inaccurate on systems that hibernate.
uptime = timedelta(seconds=time.monotonic())

formatted_uptime = (
f"{uptime.days} days, {uptime.seconds // 3600} hours, "
f"{uptime.seconds // 60 % 60} minutes"
)
except Exception as e:
logger.error(e)
formatted_uptime = "Error getting uptime"

return formatted_uptime


class AppInfoView(
LoginRequiredMixin,
TemplateView,
Expand Down Expand Up @@ -416,9 +448,7 @@ def get_context_data(self, **kwargs):
if self.request.user.is_superuser:
data["GIT_BRANCH"] = os.getenv("GIT_BRANCH", "Unavailable")
data["GIT_COMMIT"] = os.getenv("GIT_COMMIT", "Unavailable")
data["APP_UPDATED_TIME"] = AppInfoView.timestamp_to_datetime_string(
os.path.getmtime(__file__),
)
data["UPTIME"] = get_uptime()
last_transaction = Transaction.objects.order_by("updated_at").last()
data["LAST_TRANSACTION_TIME"] = (
format(
Expand Down
4 changes: 2 additions & 2 deletions exporter/sqlite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ def make_export(connection: apsw.Connection):
Path(temp_sqlite_db.name),
)
plan = make_export_plan(plan_runner)
# make_tamato_database() creates a Connection instance that needs
# closing once an in-memory plan has been created from it.
# Runner.make_tamato_database() (above) creates a Connection instance
# that needs closing once an in-memory plan has been created from it.
plan_runner.database.close()

export_runner = runner.Runner(connection)
Expand Down
Loading

0 comments on commit 9faac8b

Please sign in to comment.