Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .flake8

This file was deleted.

1 change: 1 addition & 0 deletions .gcloudignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ CHANGELOG.md
dist/
Dockerfile.*
docs/
gpxsamples/
junit/
LICENSE
README.rst
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.2.0
- uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
python-version: "3.12"
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.2.0
- uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
python-version: "3.12"
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Expand All @@ -39,7 +39,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.2.0
with:
# the test suite reads its GPX inputs from the gpxsamples submodule
submodules: true
# gpxsamples is a separate private repo, and the default GITHUB_TOKEN is
# scoped to this repo alone -- it cannot clone it (GitHub reports that as
# "Repository not found"). GPXSAMPLES_TOKEN is a PAT with read access to
# both repos; checkout applies it to submodule fetches as well.
# Falls back to the default token so forks still check out the main repo.
token: ${{ secrets.GPXSAMPLES_TOKEN || github.token }}
- uses: astral-sh/setup-uv@v9.0.0
with:
enable-cache: true
python-version: "3.12"
Expand Down
19 changes: 15 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ GPXtable converts GPX files (routes and tracks with waypoints) into Markdown or
# Install for development (all extras: gunicorn, tests, lint/mypy)
pip install -e ".[dev]"

# Check out the GPX test inputs (gpxsamples submodule) — required by the tests
git submodule update --init

# Run all tests
TZ=America/Los_Angeles pytest

Expand All @@ -24,19 +27,19 @@ pytest tests/test_gpxtable.py::test_print_header
# Regenerate expected CLI output files in samples/
python tests/test_cli.py --generate

# Lint (ruff only — replaces flake8)
# Lint (ruff only — replaces flake8; rule set is pinned in [tool.ruff.lint])
ruff check .

# Type check
mypy src/

# Run the CLI
gpxtable samples/basecamp-route.gpx
gpxtable --departure "07/30/2023 09:15:00" samples/basecamp-tracks.gpx
gpxtable gpxsamples/basecamp-route.gpx
gpxtable --departure "07/30/2023 09:15:00" gpxsamples/basecamp-tracks.gpx

# Dump/customize waypoint classifier config
gpxtable --dump-config > myconfig.json
gpxtable --config myconfig.json samples/basecamp-route.gpx
gpxtable --config myconfig.json gpxsamples/basecamp-route.gpx

# Run the Flask dev server
flask --app gpxtable.wsgi:create_app run
Expand Down Expand Up @@ -89,6 +92,8 @@ The package has four modules under `src/gpxtable/`:

### Testing approach

Sample data is split in two: the large `.gpx` **inputs** live in the `gpxsamples` git submodule (checked out at `gpxsamples/`), while the small expected-output fixtures (`*.txt`, `default-config.json`) stay versioned in `samples/`. `tests/conftest.py` owns both paths — use its `gpx_sample()` / `expected_output()` helpers rather than hardcoding directories, and it fails collection with an actionable message if the submodule was never initialized.

CLI tests in `test_cli.py` use golden-file comparison: they run the CLI subprocess and diff against `samples/*.txt` files. When changing output format, regenerate these with `python tests/test_cli.py --generate`. The test environment sets `TZ=America/Los_Angeles` for reproducibility.

Unit tests construct `GPX` objects directly (no file I/O) and assert on `StringIO` output or return values.
Expand All @@ -103,6 +108,12 @@ lint + test → deploy-to-gae → release-please

release-please only runs after a successful GAE deployment.

### Required Actions secrets

**`GPXSAMPLES_TOKEN`** — a PAT with read access to *both* `pleasantone/gpxtable` and the private `pleasantone/gpxsamples` submodule repo. The `test` job passes it to `actions/checkout`, which applies it to submodule fetches too. Without it the default `GITHUB_TOKEN` is scoped to this repo alone and the submodule clone fails with the misleading `Repository not found`. The workflow falls back to the default token when the secret is absent, so forks still check out the main repo (their `test` job will fail on the submodule).

**`MY_RELEASE_PLEASE_TOKEN`** — used by the release-please job.

**`requirements.txt`** lists the production runtime deps (flask, gunicorn, gpxpy, etc.) and is required for GAE — the platform reads this file to install dependencies, it does not read `pyproject.toml`. Keep it in sync with the `web`/`gunicorn` extras in `pyproject.toml`.

**`app.yaml`** entrypoint uses `--pythonpath src "gpxtable.wsgi:create_app()"` so the module address matches the installed package name.
Expand Down
33 changes: 20 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install install-dev test test-unit test-cli lint typecheck \
.PHONY: help install install-dev submodules test test-unit test-cli lint typecheck \
generate-samples run-web run-web-gunicorn \
demo-route demo-tracks demo-basecamp demo-config \
build clean
Expand All @@ -10,6 +10,7 @@ help:
@echo "Setup"
@echo " install Install package (core deps only)"
@echo " install-dev Install package with all extras (dev = gunicorn + tests + lint)"
@echo " submodules Check out the gpxsamples submodule (GPX test inputs)"
@echo ""
@echo "Testing"
@echo " test Run all tests"
Expand Down Expand Up @@ -44,18 +45,24 @@ install:
install-dev:
pip install -e ".[dev]"

# GPX inputs used by the tests live in the gpxsamples submodule.
submodules:
git submodule update --init --recursive

# ── Testing ──────────────────────────────────────────────────────────────────

test:
test: submodules
TZ=America/Los_Angeles pytest

test-unit:
# test-unit needs no GPX inputs itself, but conftest's submodule check is
# deliberately global (see tests/conftest.py), so every pytest run requires it.
test-unit: submodules
TZ=America/Los_Angeles pytest tests/test_gpxtable.py

test-cli:
test-cli: submodules
TZ=America/Los_Angeles pytest tests/test_cli.py

generate-samples:
generate-samples: submodules
TZ=America/Los_Angeles python tests/test_cli.py --generate

# ── Linting ──────────────────────────────────────────────────────────────────
Expand All @@ -68,20 +75,20 @@ typecheck:

# ── CLI demos ────────────────────────────────────────────────────────────────

demo-route:
gpxtable samples/basecamp-route.gpx
demo-route: submodules
gpxtable gpxsamples/basecamp-route.gpx

demo-tracks:
gpxtable --departure "07/30/2023 09:15:00" samples/basecamp-tracks.gpx
demo-tracks: submodules
gpxtable --departure "07/30/2023 09:15:00" gpxsamples/basecamp-tracks.gpx

demo-basecamp:
gpxtable samples/basecamp.gpx
demo-basecamp: submodules
gpxtable gpxsamples/basecamp.gpx

demo-config:
gpxtable --dump-config

demo-custom: /tmp/myconfig.json
gpxtable --config /tmp/myconfig.json samples/basecamp-route.gpx
demo-custom: submodules /tmp/myconfig.json
gpxtable --config /tmp/myconfig.json gpxsamples/basecamp-route.gpx
Comment thread
sourcery-ai[bot] marked this conversation as resolved.

/tmp/myconfig.json:
gpxtable --dump-config > /tmp/myconfig.json
Expand Down
4 changes: 2 additions & 2 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ route configuration itself in Basecamp. These may be overridden with the flag
based upon the `--departure` and `--speed` options.

.. literalinclude:: ../samples/basecamp-route.txt
:caption: gpxtable samples/basecamp-route.gpx
:caption: gpxtable gpxsamples/basecamp-route.gpx
:language: text

Building a table from a track and waypoints
Expand All @@ -96,7 +96,7 @@ symbol type or by including keywords in the waypoint name.
Scenic Area: 5 minutes

.. literalinclude:: ../samples/basecamp-route.txt
:caption: gpxtable --departure "07/30/2022 09:15:00" samples/basecamp-route.gpx
:caption: gpxtable --departure "07/30/2022 09:15:00" gpxsamples/basecamp-route.gpx
:language: text

Limitations:
Expand Down
39 changes: 32 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ gunicorn = [
"gunicorn>=26.0.0",
]
tests = [
"pytest",
"pytest-flask",
"responses",
"pytest>=9.1.1",
"pytest-flask>=1.3.0",
"responses>=0.26.2",
]
lint = [
"mypy",
"ruff",
"types-python-dateutil",
"types-requests",
"mypy>=2.3.0",
"ruff>=0.16.0",
"types-python-dateutil>=2.9.0.20260716",
"types-requests>=2.33.0.20260712",
]
dev = [
"gpxtable[gunicorn,tests,lint]",
Expand All @@ -66,6 +66,31 @@ version = {attr = "gpxtable.__version__"}
[tool.setuptools.package-data]
gpxtable = ["static/*", "templates/*"]

[tool.ruff]
target-version = "py310"

[tool.ruff.lint]
# Pin the rule set explicitly. Ruff's implicit default set grows with each
# release, so relying on it turns a routine ruff upgrade into a broken build.
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"SIM", # flake8-simplify
"RUF", # ruff-specific rules
]
ignore = [
"E501", # line length is handled by the formatter
]

[tool.ruff.lint.isort]
# "conftest" is the test suite's own shared-helper module, not a third-party package.
known-first-party = ["gpxtable", "conftest"]

[tool.mypy]
python_version = "3.10"
warn_return_any = true
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ python-dateutil>=2.9
flask>=3.1.3
requests>=2.34.2
gunicorn>=26.0.0
click>=8.4.1
click>=8.4.2
15 changes: 15 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# samples

Expected output fixtures, versioned in this repository:

- `*.txt` — golden CLI output used by the `tests/test_cli.py` comparison tests.
Regenerate with `make generate-samples` after an intentional output change.
- `default-config.json` — the default waypoint classifier, included in the docs.

The GPX **inputs** that produce these outputs are not here. They live in the
[`gpxsamples`](https://github.com/pleasantone/gpxsamples) submodule, checked out
at `gpxsamples/` in the repository root:

```bash
git submodule update --init # or: make submodules
```
17 changes: 17 additions & 0 deletions samples/basecamp-route.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
* Garmin Desktop App
* Default speed: 30.00 mph

## Route: Fort Ross Run

| Name | Dist. | GL | ETA | Notes
| :----------------------------- | ------: | -- | ----: | :----
| Peet's Coffee Northgate Mall | 0 | | 09:15 | Restaurant
| Nicasio Square | 12 | | 09:39 | Restroom (+0:15)
| Pat's International | 65 | L | 11:41 | Restaurant (+1:00)
| 76 Gureneville | 65/65 | G | 12:41 | Gas Station (+0:15)
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
| Willy's America | 79 | | 13:23 | Scenic Area (+0:05)
| 76 Bodega Bay | 67/132 | G | 15:14 | Gas Station (+0:15)
| Point Reyes Station | 165 | | 16:36 | Restroom (+0:05)
| Starbucks Strawberry Village | 63/195 | | 17:41 | Restaurant

* 07/30/23: Sunrise: 06:11, Starts: 09:15, Ends: 17:41, Sunset: 20:20
19 changes: 19 additions & 0 deletions samples/basecamp-tracks.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
* Garmin Desktop App
* Departure at Sat Jul 30 09:15:00 2022 PDT
* Total distance: 196 mi
* Default speed: 30.00 mph

## Track: Fort Ross Run tk

| Name | Dist. | GL | ETA | Notes
| :----------------------------- | ------: | -- | ----: | :----
| Peet's Coffee Northgate Mall | 0 | | 09:15 | Restaurant
| Nicasio Square | 12 | | 09:39 | Restroom (+0:15)
| Pat's International | 65 | L | 11:40 | Restaurant (+1:00)
| 76 Guerneville | 65/65 | G | 12:40 | Gas Station (+0:15)
| Willy's America | 79 | | 13:22 | Scenic Area (+0:05)
| 76 Bodega Bay | 67/132 | G | 15:14 | Gas Station (+0:15)
| Point Reyes Station | 165 | | 16:35 | Restroom (+0:15)
| Starbucks Strawberry Village | 63/196 | | 17:51 | Restaurant

* 07/30/22: Sunrise: 06:11, Starts: 09:15, Ends: 17:51, Sunset: 20:20
34 changes: 34 additions & 0 deletions samples/basecamp.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
* Garmin Desktop App
* Departure at Sun Jul 30 09:15:00 2023 PDT
* Total distance: 196 mi
* Default speed: 30.00 mph

## Track: Fort Ross Run tk

| Name | Dist. | GL | ETA | Notes
| :----------------------------- | ------: | -- | ----: | :----
| Peet's Coffee Northgate Mall | 0 | | 09:15 | Restaurant
| Nicasio Square | 12 | | 09:39 | Restroom (+0:15)
| Pat's International | 65 | L | 11:40 | Restaurant (+1:00)
| 76 Guerneville | 65/65 | G | 12:40 | Gas Station (+0:15)
| Willy's America | 79 | | 13:22 | Scenic Area (+0:05)
| 76 Bodega Bay | 67/132 | G | 15:14 | Gas Station (+0:15)
| Point Reyes Station | 165 | | 16:35 | Restroom (+0:15)
| Starbucks Strawberry Village | 63/196 | | 17:51 | Restaurant

* 07/30/23: Sunrise: 06:11, Starts: 09:15, Ends: 17:51, Sunset: 20:20

## Route: Fort Ross Run

| Name | Dist. | GL | ETA | Notes
| :----------------------------- | ------: | -- | ----: | :----
| Peet's Coffee Northgate Mall | 0 | | 09:15 | Restaurant
| Nicasio Square | 12 | | 09:39 | Restroom (+0:15)
| Pat's International | 65 | L | 11:40 | Restaurant (+1:00)
| 76 Guerneville | 65/65 | G | 12:40 | Gas Station (+0:15)
| Willy's America | 79 | | 13:22 | Scenic Area (+0:05)
| 76 Bodega Bay | 67/132 | G | 15:14 | Gas Station (+0:15)
| Point Reyes Station | 165 | | 16:35 | Restroom (+0:05)
| Starbucks Strawberry Village | 63/195 | | 17:40 | Restaurant

* 07/30/23: Sunrise: 06:11, Starts: 09:15, Ends: 17:40, Sunset: 20:20
36 changes: 36 additions & 0 deletions samples/default-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[
{
"delay": 75,
"fuel_reset": true,
"marker": "GL",
"search": "(?=.*\\b(Gas|Fuel)\\b)(?=.*\\b(Lunch|Meal)\\b)",
"symbol": "Gas/Restaurant"
},
{
"delay": 15,
"fuel_reset": true,
"marker": "G",
"search": "\\bGas\\b|\\bFuel\\b|\\b\\(G\\)\\b",
"symbol": "Gas Station"
},
{
"delay": 60,
"marker": "L",
"search": "\\bRestaurant\\b|\\bLunch\\b|\\bBreakfast\\b|\\bDinner\\b|\\b\\(L\\)\\b",
"symbol": "Restaurant"
},
{
"delay": 15,
"search": "\\bRestroom\\b|\\bBreak\\b|\\b\\(R\\)\\b",
"symbol": "Restroom"
},
{
"delay": 5,
"symbol": "Scenic Area"
},
{
"delay": 5,
"search": "\\bPhotos?\\b|\\b\\(P\\)\\b",
"symbol": "Photo"
}
]
Loading