From b4db721d0ff80250699c0db24177b9c069f9bb28 Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Tue, 2 Dec 2025 12:46:26 +0000 Subject: [PATCH 1/6] Use raw strings for regexps --- application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application.py b/application.py index 611d0c6..2585c89 100644 --- a/application.py +++ b/application.py @@ -188,7 +188,7 @@ def build_graph(name, results, topics): color = '#105060EE' shape = 'ellipse' - label = re.sub('\(.*\)', '', interest) + label = re.sub(r'\(.*\)', '', interest) graph.add_node(interest, label = label, style = 'filled', fontname = 'Helvetica', shape = shape, color = color, fontcolor = 'white', URL = url_for('show_topic', name = interest)) graph.add_edge(person, interest, color = '#00000050') @@ -202,7 +202,7 @@ def build_graph(name, results, topics): color = '#701050EE' shape = 'ellipse' - label = re.sub('\(.*\)', '', technology) + label = re.sub(r'\(.*\)', '', technology) graph.add_node(technology, label = label, style = 'filled', fontname = 'Helvetica', shape = shape, color = color, fontcolor = 'white') graph.add_edge(person, technology, color = '#00000050') From 413d95717d27ad680004ca20432ace2f51bb87fd Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Tue, 2 Dec 2025 12:46:33 +0000 Subject: [PATCH 2/6] Dockerfile: Switch to python:3.14-alpine base image This should be more minimal than the ubuntu image, and is more up-to-date. Ubuntu 20.04 is out of support. --- Dockerfile | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 27f89e5..5b417c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,13 @@ -FROM ubuntu:20.04 +FROM python:3.14-alpine # Maintainer of the docker image, not the code! LABEL maintainer="Ian Hinder " ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update -y && \ - apt-get install -y \ - python3 \ - python3-pip \ - python3-requests \ - pkg-config \ - graphviz \ - libgraphviz-dev \ - libxslt1-dev \ - curl && \ - rm -rf /var/lib/apt/lists/* -# Note: the ordering of these packages matters as libgraphviz-dev requires python3-pip +RUN apk add build-base + +RUN apk add graphviz graphviz-dev COPY ./requirements.txt /app/requirements.txt From 5db8d733205017b5a2ac3b30e8a3c6668f3c3fd2 Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Tue, 2 Dec 2025 12:55:55 +0000 Subject: [PATCH 3/6] Strip down requirements.in to essentials and upgrade all packages --- requirements.in | 6 ----- requirements.txt | 60 +++++++++++++++--------------------------------- 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/requirements.in b/requirements.in index 55fd06b..0843eb5 100644 --- a/requirements.in +++ b/requirements.in @@ -1,13 +1,7 @@ -Click Flask -itsdangerous -Jinja2 lxml -MarkupSafe natsort pygraphviz -pyparsing pytest urllib3 -Werkzeug pip-tools diff --git a/requirements.txt b/requirements.txt index eea93e4..373d648 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,41 +1,30 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.14 # by the following command: # # pip-compile # -blinker==1.8.2 +blinker==1.9.0 # via flask -build==1.2.2.post1 +build==1.3.0 # via pip-tools -click==8.1.8 +click==8.3.1 # via - # -r requirements.in # flask # pip-tools -exceptiongroup==1.3.0 - # via pytest -flask==3.0.3 +flask==3.1.2 # via -r requirements.in -importlib-metadata==8.5.0 - # via - # build - # flask -iniconfig==2.1.0 +iniconfig==2.3.0 # via pytest itsdangerous==2.2.0 - # via - # -r requirements.in - # flask + # via flask jinja2==3.1.6 - # via - # -r requirements.in - # flask + # via flask lxml==6.0.2 # via -r requirements.in -markupsafe==2.1.5 +markupsafe==3.0.3 # via - # -r requirements.in + # flask # jinja2 # werkzeug natsort==8.4.0 @@ -44,37 +33,26 @@ packaging==25.0 # via # build # pytest -pip-tools==7.5.0 +pip-tools==7.5.2 # via -r requirements.in -pluggy==1.5.0 +pluggy==1.6.0 # via pytest -pygraphviz==1.11 - # via -r requirements.in -pyparsing==3.1.4 +pygments==2.19.2 + # via pytest +pygraphviz==1.14 # via -r requirements.in pyproject-hooks==1.2.0 # via # build # pip-tools -pytest==8.3.5 +pytest==9.0.1 # via -r requirements.in -tomli==2.2.1 - # via - # build - # pip-tools - # pytest -typing-extensions==4.13.2 - # via exceptiongroup -urllib3==2.2.3 +urllib3==2.5.0 # via -r requirements.in -werkzeug==3.0.6 - # via - # -r requirements.in - # flask +werkzeug==3.1.4 + # via flask wheel==0.45.1 # via pip-tools -zipp==3.20.2 - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: # pip From 4f3f0447fa63adda205c44030c6d0364cc58dfef Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Fri, 12 Dec 2025 10:10:40 +0000 Subject: [PATCH 4/6] test.yml: Add logs if container fails to become healthy --- .github/workflows/test.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5bc7847..db26866 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,15 @@ jobs: run: | # Launch the project from docker-compose.yml and wait up to # 30s for its built-in health check to return "healthy" - docker compose -f compose.yml -f compose.test.yml up --wait --wait-timeout 30 - docker compose -f compose.yml run --entrypoint "" web pytest + set -eu + + if ! docker compose -f compose.yml -f compose.test.yml up --wait --wait-timeout 30; then + echo "Project failed to become healthy" >&2 + echo "Logs:" + docker compose -f compose.yml -f compose.test.yml logs + exit 1 + fi + docker compose -f compose.yml down + + docker compose -f compose.yml run --entrypoint "" web pytest From 99750bf30f03890cdf43eee740c919a974616b98 Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Wed, 18 Mar 2026 15:46:20 +0000 Subject: [PATCH 5/6] Fix SVG image paths for newer version of PyGraphViz Newer versions of PyGraphViz don't support GV_FILE_PATH for security reasons. Instead, absolute paths need to be included in the graph. These seem to need to exist at the time the node is added. The path prefixes then need to be removed at the time the path is converted into a URL. --- application.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/application.py b/application.py index 2585c89..310e85a 100644 --- a/application.py +++ b/application.py @@ -47,10 +47,9 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1) os.environ['PATH'] = os.environ['PATH'] + ':/usr/local/bin' -os.environ['GV_FILE_PATH'] = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/images/')) + '/' +IMAGE_FILE_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static/images')) print('PATH: ' + os.environ['PATH'], file=sys.stderr) -print('GV_FILE_PATH: ' + os.environ['GV_FILE_PATH'], file=sys.stderr) class TitleNotFoundException(Exception): pass @@ -141,14 +140,14 @@ def get_graph_string(graph): # URL to an image, only the file path. for image in images: image_filename = image.attrib['{http://www.w3.org/1999/xlink}href'] - image_url = url_for('static', filename = 'images/' + image_filename) + image_url = url_for('static', filename = 'images/' + image_filename.replace(f"{IMAGE_FILE_PATH}/","")) image.attrib['{http://www.w3.org/1999/xlink}href'] = image_url return etree.tostring(svg_obj, pretty_print = True).decode('utf-8') def get_image_files(): image_files = [] - image_dir = os.environ['GV_FILE_PATH'] + image_dir = IMAGE_FILE_PATH for root, sub_folders, files in os.walk(image_dir): for filename in files: @@ -177,7 +176,7 @@ def build_graph(name, results, topics): # check added for _ in name e.g. Anja Le_Blanc; that is: convert _ to space myperson= person.replace(' ', '\n') - graph.add_node(person, label = myperson.replace('_' , ' '), fontname = 'Helvetica', fixedsize = True, imagescale = True, width = '1.5', height = '1.5', fontcolor = 'white', shape = 'circle', style = 'filled', color = '#303030', URL = url_for('show_person', name = person), image = image_file) + graph.add_node(person, label = myperson.replace('_' , ' '), fontname = 'Helvetica', fixedsize = True, imagescale = True, width = '1.5', height = '1.5', fontcolor = 'white', shape = 'circle', style = 'filled', color = '#303030', URL = url_for('show_person', name = person), image = f"{IMAGE_FILE_PATH}/{image_file}") interests = people[person]['interests'] for interest in interests: From 7e91e462c55b0eb8eff3064fe26fdd53dac0e999 Mon Sep 17 00:00:00 2001 From: Ian Hinder Date: Wed, 18 Mar 2026 16:59:19 +0000 Subject: [PATCH 6/6] Dockerfile: Add curl for healthcheck --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5b417c1..b60bc18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,8 @@ RUN apk add build-base RUN apk add graphviz graphviz-dev +RUN apk add curl + COPY ./requirements.txt /app/requirements.txt WORKDIR /app