Skip to content

Commit f650124

Browse files
authored
Merge branch 'main' into fix/csv_export_revert_visualization
2 parents c7627aa + 2d818ca commit f650124

20 files changed

Lines changed: 1896 additions & 491 deletions

File tree

.github/workflows/build-test.yml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ on:
1818
env:
1919
python_version: '3.11'
2020
java_version: '11' # needed by setup-openapi-generator.sh
21+
liquibase_version: '4.33.0'
2122

2223
jobs:
2324
build-test:
@@ -66,17 +67,21 @@ jobs:
6667
scripts/lint-tests.sh
6768
6869
- name: Install Liquibase
69-
run: |
70-
wget -O- https://repo.liquibase.com/liquibase.asc | gpg --dearmor > liquibase-keyring.gpg && \
71-
cat liquibase-keyring.gpg | sudo tee /usr/share/keyrings/liquibase-keyring.gpg > /dev/null && \
72-
echo 'deb [trusted=yes arch=amd64 signed-by=/usr/share/keyrings/liquibase-keyring.gpg] https://repo.liquibase.com stable main' | sudo tee /etc/apt/sources.list.d/liquibase.list
73-
74-
sudo apt-get update
75-
sudo apt-get install liquibase=4.25.1
76-
70+
env:
71+
LIQUIBASE_VERSION: ${{ env.liquibase_version }}
72+
run: |
73+
curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz
74+
rm -rf liquibase-dist
75+
mkdir liquibase-dist
76+
tar -xzf liquibase.tar.gz -C liquibase-dist
77+
sudo rm -rf /usr/local/liquibase
78+
sudo mv liquibase-dist /usr/local/liquibase
79+
sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase
80+
liquibase --version
81+
7782
- name: Run Liquibase on Python functions test DB
83+
working-directory: ${{ github.workspace }}/liquibase
7884
run: |
79-
export LIQUIBASE_CLASSPATH="liquibase"
8085
export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml"
8186
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:54320/MobilityDatabaseTest
8287
export LIQUIBASE_COMMAND_USERNAME=postgres

.github/workflows/datasets-batch-deployer.yml

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ on:
4141

4242
env:
4343
python_version: '3.11'
44+
liquibase_version: '4.33.0'
4445

4546
jobs:
4647
terraform:
@@ -88,23 +89,21 @@ jobs:
8889
python-version: ${{ env.python_version }}
8990

9091
- name: Install Liquibase
91-
run: |
92-
wget -O- https://repo.liquibase.com/liquibase.asc | gpg --dearmor > liquibase-keyring.gpg && \
93-
cat liquibase-keyring.gpg | sudo tee /usr/share/keyrings/liquibase-keyring.gpg > /dev/null && \
94-
echo 'deb [trusted=yes arch=amd64 signed-by=/usr/share/keyrings/liquibase-keyring.gpg] https://repo.liquibase.com stable main' | sudo tee /etc/apt/sources.list.d/liquibase.list
92+
env:
93+
LIQUIBASE_VERSION: ${{ env.liquibase_version }}
94+
run: |
95+
curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz
96+
rm -rf liquibase-dist
97+
mkdir liquibase-dist
98+
tar -xzf liquibase.tar.gz -C liquibase-dist
99+
sudo rm -rf /usr/local/liquibase
100+
sudo mv liquibase-dist /usr/local/liquibase
101+
sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase
102+
liquibase --version
95103
96-
sudo apt-get update
97-
sudo apt-get install liquibase=4.25.1
98-
99-
# Uncomment the following block to test the local databases connections
100-
# - name: Test Database Connection
101-
# run: |
102-
# sudo apt-get update && sudo apt-get install -y postgresql-client
103-
# PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d MobilityDatabase -c "SELECT version();"
104-
105104
- name: Run Liquibase on Python functions DB
105+
working-directory: ${{ github.workspace }}/liquibase
106106
run: |
107-
export LIQUIBASE_CLASSPATH="liquibase"
108107
export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml"
109108
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:5432/MobilityDatabase
110109
export LIQUIBASE_COMMAND_USERNAME=postgres

.github/workflows/db-update.yml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ on:
7070

7171
env:
7272
python_version: '3.11'
73+
liquibase_version: '4.33.0'
7374

7475
jobs:
7576
db-schema-update:
@@ -111,22 +112,27 @@ jobs:
111112
sudo apt-get update && sudo apt-get install -y postgresql-client
112113
PGPASSWORD=${{ secrets.DB_USER_PASSWORD }} psql -h localhost -p 5432 -U ${{ secrets.DB_USER_NAME }} -d ${{ inputs.DB_NAME }} -c "SELECT version();"
113114
115+
- name: Install Liquibase
116+
env:
117+
LIQUIBASE_VERSION: ${{ env.liquibase_version }}
118+
run: |
119+
curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz
120+
rm -rf liquibase-dist
121+
mkdir liquibase-dist
122+
tar -xzf liquibase.tar.gz -C liquibase-dist
123+
sudo rm -rf /usr/local/liquibase
124+
sudo mv liquibase-dist /usr/local/liquibase
125+
sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase
126+
liquibase --version
127+
114128
- name: Run Liquibase
129+
working-directory: ${{ github.workspace }}/liquibase
115130
run: |
116-
wget -O- https://repo.liquibase.com/liquibase.asc | gpg --dearmor > liquibase-keyring.gpg && \
117-
cat liquibase-keyring.gpg | sudo tee /usr/share/keyrings/liquibase-keyring.gpg > /dev/null && \
118-
echo 'deb [trusted=yes arch=amd64 signed-by=/usr/share/keyrings/liquibase-keyring.gpg] https://repo.liquibase.com stable main' | sudo tee /etc/apt/sources.list.d/liquibase.list
119-
120-
sudo apt-get update
121-
sudo apt-get install liquibase=4.25.1
122-
123-
export LIQUIBASE_CLASSPATH="liquibase"
124131
export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml"
125132
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:5432/${{ inputs.DB_NAME }}
126133
export LIQUIBASE_COMMAND_USERNAME=${{ secrets.DB_USER_NAME }}
127134
export LIQUIBASE_COMMAND_PASSWORD=${{ secrets.DB_USER_PASSWORD }}
128135
export LIQUIBASE_LOG_LEVEL=FINE
129-
130136
liquibase update
131137
132138
db-content-update:

.github/workflows/integration-tests-pr.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ env:
1919
python_version: '3.11'
2020
java_version: '11' # needed by setup-openapi-generator.sh
2121
API_URL: 'http://localhost:8080'
22+
liquibase_version: '4.33.0'
2223

2324
jobs:
2425
integration-tests-pr:
@@ -61,17 +62,21 @@ jobs:
6162
working-directory: ${{ github.workspace }}
6263

6364
- name: Install Liquibase
65+
env:
66+
LIQUIBASE_VERSION: ${{ env.liquibase_version }}
6467
run: |
65-
wget -O- https://repo.liquibase.com/liquibase.asc | gpg --dearmor > liquibase-keyring.gpg && \
66-
cat liquibase-keyring.gpg | sudo tee /usr/share/keyrings/liquibase-keyring.gpg > /dev/null && \
67-
echo 'deb [trusted=yes arch=amd64 signed-by=/usr/share/keyrings/liquibase-keyring.gpg] https://repo.liquibase.com stable main' | sudo tee /etc/apt/sources.list.d/liquibase.list
68-
69-
sudo apt-get update
70-
sudo apt-get install liquibase=4.25.1
68+
curl -sSL https://github.com/liquibase/liquibase/releases/download/v${LIQUIBASE_VERSION}/liquibase-${LIQUIBASE_VERSION}.tar.gz -o liquibase.tar.gz
69+
rm -rf liquibase-dist
70+
mkdir liquibase-dist
71+
tar -xzf liquibase.tar.gz -C liquibase-dist
72+
sudo rm -rf /usr/local/liquibase
73+
sudo mv liquibase-dist /usr/local/liquibase
74+
sudo ln -sf /usr/local/liquibase/liquibase /usr/local/bin/liquibase
75+
liquibase --version
7176
7277
- name: Run Liquibase on API local DB
78+
working-directory: ${{ github.workspace }}/liquibase
7379
run: |
74-
export LIQUIBASE_CLASSPATH="liquibase"
7580
export LIQUIBASE_COMMAND_CHANGELOG_FILE="changelog.xml"
7681
export LIQUIBASE_COMMAND_URL=jdbc:postgresql://localhost:5432/MobilityDatabase
7782
export LIQUIBASE_COMMAND_USERNAME=postgres

functions-python/pmtiles_builder/src/csv_cache.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import csv
1717
import logging
1818
import os
19+
import subprocess
20+
from pathlib import Path
1921
from typing import TypedDict, List, Dict
2022

2123
from gtfs import stop_txt_is_lat_log_required
@@ -39,6 +41,40 @@ class ShapeTrips(TypedDict):
3941
trip_ids: List[str]
4042

4143

44+
def get_volume_size(mountpoint: str):
45+
"""
46+
Returns the total size of the specified filesystem mount point in a human-readable format.
47+
48+
This function uses the `df` command-line utility to determine the size of the filesystem
49+
mounted at the path specified by `mountpoint`. If the mount point does not exist, the function
50+
prints an error message to the standard error and returns "N/A".
51+
52+
Parameters:
53+
mountpoint: str
54+
The filesystem mount point path to check.
55+
56+
Returns:
57+
str
58+
The total size of the specified filesystem mount point in human-readable format. If the
59+
mount point is not found, returns "N/A".
60+
"""
61+
mp = Path(mountpoint)
62+
if not mp.exists():
63+
logging.warning("Mountpoint not found: %s", mountpoint)
64+
return "N/A"
65+
cmd = [
66+
"bash",
67+
"-c",
68+
"df -h \"$1\" | awk 'NR==2 {print $2}'",
69+
"_", # $0 placeholder (ignored)
70+
str(mp), # $1
71+
]
72+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
73+
size = result.stdout.strip()
74+
75+
return size
76+
77+
4278
class CsvCache:
4379
"""
4480
CsvCache provides cached access to GTFS CSV files in a specified working directory.
@@ -68,6 +104,7 @@ def __init__(
68104
self.trips_no_shapes_per_route: Dict[str, List[str]] = {}
69105

70106
self.logger.info("Using work directory: %s", self.workdir)
107+
self.logger.info("Size of workdir: %s", get_volume_size(self.workdir))
71108

72109
def debug_log_size(self, label: str, obj: object) -> None:
73110
"""Log the deep size of an object in bytes when DEBUG is enabled."""

functions-python/pmtiles_builder/src/main.py

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import json
2121
import logging
2222
import os
23+
import shutil
2324
import subprocess
2425
import sys
25-
import tempfile
2626
from enum import Enum
2727
from typing import TypedDict, Tuple, List, Dict
2828

@@ -53,6 +53,108 @@
5353
init_logger()
5454

5555

56+
class EphemeralOrDebugWorkdir:
57+
"""Context manager similar to tempfile.TemporaryDirectory with debug + auto-clean.
58+
59+
Behavior:
60+
- If DEBUG_WORKDIR env var is set (non-empty): that directory is created (if needed),
61+
returned, and never deleted nor cleaned.
62+
- Else: creates a temporary directory under the provided dir (or WORKDIR_ROOT env var),
63+
removes sibling directories older than a TTL (default 3600s / override via WORKDIR_MAX_AGE_SECONDS),
64+
and deletes the created directory on exit.
65+
66+
Only directories whose names start with the fixed CLEANUP_PREFIX are considered for cleanup
67+
to avoid deleting unrelated folders that might exist under the same root.
68+
69+
The final on-disk directory name always starts with the hardcoded prefix 'pmtiles_'.
70+
The caller-supplied prefix (if any) is appended verbatim after that.
71+
"""
72+
73+
CLEANUP_PREFIX = "pmtiles_"
74+
75+
def __init__(
76+
self,
77+
dir: str | None = None,
78+
prefix: str | None = None,
79+
logger: logging.Logger | None = None,
80+
):
81+
import tempfile
82+
83+
self._debug_dir = os.getenv("DEBUG_WORKDIR") or None
84+
self._root = dir or os.getenv("WORKDIR_ROOT", "/tmp/in-memory")
85+
self._logger = logger or get_logger("Workdir")
86+
self._temp: tempfile.TemporaryDirectory[str] | None = None
87+
self.name: str
88+
89+
os.makedirs(self._root, exist_ok=True)
90+
91+
self._ttl_seconds = int(os.getenv("WORKDIR_MAX_AGE_SECONDS", "3600"))
92+
93+
if self._debug_dir:
94+
os.makedirs(self._debug_dir, exist_ok=True)
95+
self.name = self._debug_dir
96+
return
97+
98+
self._cleanup_old()
99+
100+
# Simple prefix: fixed manager prefix + raw user prefix (if any)
101+
combined_prefix = self.CLEANUP_PREFIX + (prefix or "")
102+
103+
self._temp = tempfile.TemporaryDirectory(dir=self._root, prefix=combined_prefix)
104+
self.name = self._temp.name
105+
106+
def _cleanup_old(self):
107+
"""
108+
Delete stale work directories created by this manager (names starting with CLEANUP_PREFIX)
109+
whose modification time is older than the configured TTL.
110+
"""
111+
import time
112+
113+
# If in debug mode, dont cleanup anything
114+
if self._debug_dir:
115+
return
116+
117+
now = time.time()
118+
deleted_count = 0
119+
try:
120+
entries = list(os.scandir(self._root))
121+
except OSError as e:
122+
self._logger.warning("Could not scan workdir root %s: %s", self._root, e)
123+
return
124+
125+
for entry in entries:
126+
try:
127+
if not entry.is_dir(follow_symlinks=False):
128+
continue
129+
if not entry.name.startswith(self.CLEANUP_PREFIX):
130+
continue
131+
try:
132+
age = now - entry.stat(follow_symlinks=False).st_mtime
133+
except OSError:
134+
continue
135+
if age > self._ttl_seconds:
136+
shutil.rmtree(entry.path, ignore_errors=True)
137+
deleted_count += 1
138+
self._logger.debug(
139+
"Removed expired workdir: %s age=%.0fs", entry.path, age
140+
)
141+
except OSError as e:
142+
self._logger.warning("Failed to remove %s: %s", entry.path, e)
143+
144+
if deleted_count:
145+
self._logger.info(
146+
"Cleanup removed %d expired workdirs from %s", deleted_count, self._root
147+
)
148+
149+
def __enter__(self) -> str: # Return path like TemporaryDirectory
150+
return self.name
151+
152+
def __exit__(self, exc_type, exc, tb):
153+
if self._temp:
154+
self._temp.cleanup()
155+
return False # do not suppress exceptions
156+
157+
56158
class RouteCoordinates(TypedDict):
57159
shape_id: str
58160
trip_ids: List[str]
@@ -93,18 +195,11 @@ def build_pmtiles_handler(request: flask.Request) -> dict:
93195
}
94196

95197
try:
96-
# Create a temporary folder to work in. It will be deleted when exiting the block.
97-
with tempfile.TemporaryDirectory(prefix="build_pmtiles_") as temp_dir:
98-
# If DEBUG_WORKDIR is set, use it as the work directory so it survives at the end and can be examined.
99-
# In that case temp_dir will not be used but still deleted at the end of the block.
100-
101-
debug_workdir = os.getenv("DEBUG_WORKDIR")
102-
if debug_workdir:
103-
os.makedirs(debug_workdir, exist_ok=True)
104-
workdir = debug_workdir
105-
else:
106-
workdir = temp_dir
107-
198+
workdir_root = os.getenv("WORKDIR_ROOT", "/tmp/in-memory")
199+
# Use combined context manager that also cleans old directories
200+
with EphemeralOrDebugWorkdir(
201+
dir=workdir_root, prefix=f"{dataset_stable_id}_"
202+
) as workdir:
108203
result = {
109204
"params": {
110205
"feed_stable_id": feed_stable_id,

0 commit comments

Comments
 (0)