Skip to content

Commit 61854d9

Browse files
committed
perf(dump): add --exclude so deploy-pages skips the game dump
`python -m app.dump` regenerates every collection. With ~962k games the Pages build spent hours writing per-record files that the assemble step then deleted before upload (the last deploy ran 5h26m). Add a repeatable `--exclude COLLECTION` flag: `generate()` already accepted a `collections` list, so this just threads it through `run()` and the CLI via a new `resolve_collections()` helper. Unknown names raise instead of being ignored, so a typo in a workflow fails loudly. deploy-pages now runs `--exclude games`, which drops the wasted generation work and makes the post-hoc `rm -rf _site/v1/games` + manifest edit unnecessary (the manifest is written without games to begin with).
1 parent 885fd66 commit 61854d9

3 files changed

Lines changed: 60 additions & 19 deletions

File tree

.github/workflows/deploy-pages.yml

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ jobs:
4444
- name: Generate static JSON dump + openapi.json
4545
env:
4646
TECHAPI_DATA_DIR: ${{ github.workspace }}/TechAPI/data
47-
run: python -m app.dump --output dump
47+
# Games are not published here: ~962k per-record files exceed the fixed
48+
# GitHub Pages deployment window (the deploy step times out at
49+
# "syncing_files"). Skipping them at generation time also keeps this
50+
# step from writing files that would only be deleted before upload.
51+
# They remain available as versioned data in the TechAPI repo.
52+
run: python -m app.dump --output dump --exclude games
4853

4954
- uses: actions/setup-node@v4
5055
with:
@@ -63,19 +68,6 @@ jobs:
6368
mkdir -p _site
6469
cp -r site/dist/. _site/
6570
cp -r dump/. _site/
66-
# Games remain available as versioned data in the TechAPI repo, but
67-
# publishing ~1M per-record files exceeds the fixed GitHub Pages
68-
# deployment window (the deploy step times out at "syncing_files").
69-
# Exclude them from the published artifact to keep deploys reliable,
70-
# mirroring the TechAPI homepage workflow.
71-
rm -rf _site/v1/games
72-
node <<'NODE'
73-
const fs = require("fs");
74-
const path = "_site/v1/index.json";
75-
const manifest = JSON.parse(fs.readFileSync(path, "utf8"));
76-
delete manifest.collections?.games;
77-
fs.writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n");
78-
NODE
7971
touch _site/.nojekyll
8072
8173
- uses: actions/upload-pages-artifact@v3

app/dump.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@
3939
PAGE_LIMIT = 100 # API max page size (§7.3)
4040

4141

42+
def resolve_collections(exclude: list[str] | None = None) -> list[str]:
43+
"""Return the collections to dump, minus ``exclude``.
44+
45+
Unknown names raise instead of being ignored, so a typo in a workflow fails
46+
loudly rather than silently dumping everything.
47+
"""
48+
if not exclude:
49+
return list(COLLECTIONS)
50+
unknown = sorted(set(exclude) - set(COLLECTIONS))
51+
if unknown:
52+
raise ValueError(
53+
f"unknown collection(s) {unknown}; valid names: {', '.join(COLLECTIONS)}"
54+
)
55+
return [resource for resource in COLLECTIONS if resource not in set(exclude)]
56+
57+
4258
def _write_json(path: Path, data: object) -> None:
4359
path.parent.mkdir(parents=True, exist_ok=True)
4460
path.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
@@ -98,23 +114,39 @@ def generate(
98114
return counts
99115

100116

101-
def run(output_dir: Path = OUTPUT_DIR) -> None:
117+
def run(output_dir: Path = OUTPUT_DIR, exclude: list[str] | None = None) -> None:
102118
from sqlmodel import Session
103119

104120
from app.database import create_db_and_tables, engine
105121
from app.main import app
106122
from app.seed import seed
107123

124+
collections = resolve_collections(exclude)
125+
108126
create_db_and_tables()
109127
with Session(engine) as session:
110128
seed(session)
111129
with TestClient(app) as client:
112-
counts = generate(client, output_dir)
130+
counts = generate(client, output_dir, collections)
113131
total = sum(counts.values())
114-
print(f"Dumped {total} records to {output_dir}: {counts}")
132+
skipped = sorted(set(COLLECTIONS) - set(collections))
133+
suffix = f" (skipped: {', '.join(skipped)})" if skipped else ""
134+
print(f"Dumped {total} records to {output_dir}: {counts}{suffix}")
115135

116136

117137
if __name__ == "__main__":
118138
parser = argparse.ArgumentParser(description="Generate the TechAPI static JSON dump (§4.2)")
119139
parser.add_argument("--output", type=Path, default=OUTPUT_DIR, help="output directory")
120-
run(parser.parse_args().output)
140+
parser.add_argument(
141+
"--exclude",
142+
action="append",
143+
default=[],
144+
metavar="COLLECTION",
145+
help=(
146+
"collection to skip, repeatable (e.g. --exclude games). Useful when a "
147+
"consumer does not publish a large collection: skipping it avoids "
148+
"writing hundreds of thousands of files that are discarded anyway."
149+
),
150+
)
151+
args = parser.parse_args()
152+
run(args.output, args.exclude)

tests/integration/test_dump.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import json
66
from pathlib import Path
77

8+
import pytest
89
from fastapi.testclient import TestClient
910

10-
from app.dump import generate
11+
from app.dump import COLLECTIONS, generate, resolve_collections
1112
from tests.integration.mobile_device_fixtures import ensure_mobile_device_fixtures
1213

1314

@@ -47,3 +48,19 @@ def test_dump_writes_scores_and_scored_count(client: TestClient, tmp_path: Path)
4748
cpus = manifest["collections"]["cpus"]
4849
assert isinstance(cpus["scored"], int)
4950
assert 0 <= cpus["scored"] <= cpus["count"]
51+
52+
53+
def test_resolve_collections_defaults_to_everything() -> None:
54+
assert resolve_collections() == COLLECTIONS
55+
assert resolve_collections([]) == COLLECTIONS
56+
57+
58+
def test_resolve_collections_drops_excluded_and_keeps_order() -> None:
59+
resolved = resolve_collections(["games"])
60+
assert "games" not in resolved
61+
assert resolved == [c for c in COLLECTIONS if c != "games"]
62+
63+
64+
def test_resolve_collections_rejects_unknown_names() -> None:
65+
with pytest.raises(ValueError, match="unknown collection"):
66+
resolve_collections(["gmaes"])

0 commit comments

Comments
 (0)