queenscoach 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. queenscoach-1.1.0/.dockerignore +15 -0
  2. queenscoach-1.1.0/.env.example +23 -0
  3. queenscoach-1.1.0/.github/workflows/ci.yml +104 -0
  4. queenscoach-1.1.0/.github/workflows/release.yml +119 -0
  5. queenscoach-1.1.0/.gitignore +14 -0
  6. queenscoach-1.1.0/Dockerfile +27 -0
  7. queenscoach-1.1.0/LICENSE +21 -0
  8. queenscoach-1.1.0/PKG-INFO +416 -0
  9. queenscoach-1.1.0/README.md +392 -0
  10. queenscoach-1.1.0/deploy/GCP.md +317 -0
  11. queenscoach-1.1.0/deploy/README.md +233 -0
  12. queenscoach-1.1.0/deploy/gcp-spend-cap/main.py +106 -0
  13. queenscoach-1.1.0/deploy/gcp-spend-cap/requirements.txt +3 -0
  14. queenscoach-1.1.0/deploy/queenscoach.env.example +22 -0
  15. queenscoach-1.1.0/deploy/queenscoach.service +45 -0
  16. queenscoach-1.1.0/pyproject.toml +66 -0
  17. queenscoach-1.1.0/scripts/deploy-gcp.sh +819 -0
  18. queenscoach-1.1.0/scripts/install.sh +685 -0
  19. queenscoach-1.1.0/server.json +29 -0
  20. queenscoach-1.1.0/src/queenscoach/__init__.py +6 -0
  21. queenscoach-1.1.0/src/queenscoach/__main__.py +6 -0
  22. queenscoach-1.1.0/src/queenscoach/cache.py +84 -0
  23. queenscoach-1.1.0/src/queenscoach/config.py +444 -0
  24. queenscoach-1.1.0/src/queenscoach/feed_http.py +66 -0
  25. queenscoach-1.1.0/src/queenscoach/gtfs_csv.py +41 -0
  26. queenscoach-1.1.0/src/queenscoach/http.py +193 -0
  27. queenscoach-1.1.0/src/queenscoach/main.py +145 -0
  28. queenscoach-1.1.0/src/queenscoach/oauth.py +410 -0
  29. queenscoach-1.1.0/src/queenscoach/realtime.py +298 -0
  30. queenscoach-1.1.0/src/queenscoach/server.py +178 -0
  31. queenscoach-1.1.0/src/queenscoach/static_gtfs.py +171 -0
  32. queenscoach-1.1.0/src/queenscoach/token_store.py +224 -0
  33. queenscoach-1.1.0/src/queenscoach/token_store_firestore.py +310 -0
  34. queenscoach-1.1.0/src/queenscoach/tools.py +363 -0
  35. queenscoach-1.1.0/src/queenscoach/transit.py +354 -0
  36. queenscoach-1.1.0/tests/__init__.py +1 -0
  37. queenscoach-1.1.0/tests/conftest.py +92 -0
  38. queenscoach-1.1.0/tests/fixtures/Alerts.pb +0 -0
  39. queenscoach-1.1.0/tests/fixtures/TripUpdates.pb +0 -0
  40. queenscoach-1.1.0/tests/fixtures/VehiclePositions.pb +0 -0
  41. queenscoach-1.1.0/tests/fixtures/gtfs-static.zip +0 -0
  42. queenscoach-1.1.0/tests/test_cache.py +110 -0
  43. queenscoach-1.1.0/tests/test_config.py +256 -0
  44. queenscoach-1.1.0/tests/test_feed_http.py +125 -0
  45. queenscoach-1.1.0/tests/test_gtfs_csv.py +36 -0
  46. queenscoach-1.1.0/tests/test_http.py +323 -0
  47. queenscoach-1.1.0/tests/test_main.py +57 -0
  48. queenscoach-1.1.0/tests/test_oauth.py +349 -0
  49. queenscoach-1.1.0/tests/test_realtime.py +69 -0
  50. queenscoach-1.1.0/tests/test_server.py +89 -0
  51. queenscoach-1.1.0/tests/test_static_gtfs.py +64 -0
  52. queenscoach-1.1.0/tests/test_stdio.py +71 -0
  53. queenscoach-1.1.0/tests/test_token_store.py +272 -0
  54. queenscoach-1.1.0/tests/test_tools.py +194 -0
  55. queenscoach-1.1.0/tests/test_transit.py +96 -0
@@ -0,0 +1,15 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ .env
5
+ **/__pycache__
6
+ **/*.py[cod]
7
+ .pytest_cache
8
+ .mypy_cache
9
+ .ruff_cache
10
+ build
11
+ dist
12
+ *.egg-info
13
+ deploy
14
+ scripts
15
+ tests
@@ -0,0 +1,23 @@
1
+ # HTTP transport with Google OAuth. Copy to .env and fill in; .env is gitignored.
2
+ # Not needed for the stdio transport.
3
+
4
+ QUEENSCOACH_TRANSPORT=http
5
+
6
+ # From an OAuth 2.0 "Web application" client at
7
+ # https://console.cloud.google.com/apis/credentials
8
+ # Its authorized redirect URI must be exactly <QUEENSCOACH_PUBLIC_URL>/auth/google/callback
9
+ QUEENSCOACH_GOOGLE_CLIENT_ID=
10
+ QUEENSCOACH_GOOGLE_CLIENT_SECRET=
11
+
12
+ # Who may use the server. At least one of these three is required; without one
13
+ # the server refuses to start rather than admitting every Google account.
14
+ QUEENSCOACH_ALLOWED_EMAILS=you@example.com
15
+ # QUEENSCOACH_ALLOWED_DOMAINS=example.com
16
+ # QUEENSCOACH_ALLOW_ANY_GOOGLE_ACCOUNT=false
17
+
18
+ # The externally reachable origin: what clients dial, and this server's OAuth
19
+ # issuer. Must be https unless it is loopback.
20
+ QUEENSCOACH_PUBLIC_URL=https://queenscoach.example.com
21
+
22
+ # QUEENSCOACH_HTTP_HOST=127.0.0.1
23
+ # QUEENSCOACH_HTTP_PORT=8000
@@ -0,0 +1,104 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ # So the release workflow can gate a publish on this same suite.
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ # A newer push to the same branch makes an in-flight run redundant.
15
+ concurrency:
16
+ group: ci-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
26
+ steps:
27
+ - uses: actions/checkout@v5
28
+ - uses: actions/setup-python@v6
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ cache: pip
32
+ cache-dependency-path: pyproject.toml
33
+ - run: pip install -e ".[dev]"
34
+ - run: pytest
35
+
36
+ lint:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v5
40
+ - uses: actions/setup-python@v6
41
+ with:
42
+ # The version pyproject targets for ruff and mypy.
43
+ python-version: "3.11"
44
+ cache: pip
45
+ cache-dependency-path: pyproject.toml
46
+ - run: pip install -e ".[dev]"
47
+ - run: ruff check
48
+ - run: ruff format --check
49
+ - run: mypy
50
+
51
+ # stdio exactly as a PyPI or MCP-registry user gets it: no optional extras.
52
+ stdio-plain-install:
53
+ runs-on: ubuntu-latest
54
+ steps:
55
+ - uses: actions/checkout@v5
56
+ - uses: actions/setup-python@v6
57
+ with:
58
+ python-version: "3.11"
59
+ - run: pip install . pytest pytest-asyncio
60
+ - name: The gcp extra must not arrive with a plain install
61
+ run: |
62
+ python - <<'PY'
63
+ import importlib.util, sys
64
+
65
+ try:
66
+ found = importlib.util.find_spec("google.cloud.firestore") is not None
67
+ except ModuleNotFoundError:
68
+ found = False
69
+ sys.exit("google-cloud-firestore is installed without the gcp extra" if found else 0)
70
+ PY
71
+ - run: queenscoach --version
72
+ - run: pytest tests/test_stdio.py
73
+
74
+ # The Firestore token store against the emulator; the main suite skips it.
75
+ firestore:
76
+ runs-on: ubuntu-latest
77
+ env:
78
+ FIRESTORE_EMULATOR_HOST: 127.0.0.1:8085
79
+ steps:
80
+ - uses: actions/checkout@v5
81
+ - uses: actions/setup-python@v6
82
+ with:
83
+ python-version: "3.11"
84
+ cache: pip
85
+ cache-dependency-path: pyproject.toml
86
+ # The emulator is a Java program shipped as a gcloud component.
87
+ - uses: actions/setup-java@v4
88
+ with:
89
+ distribution: temurin
90
+ java-version: "21"
91
+ - uses: google-github-actions/setup-gcloud@v2
92
+ with:
93
+ install_components: beta,cloud-firestore-emulator
94
+ - run: pip install -e ".[dev]"
95
+ - name: Start the Firestore emulator
96
+ run: |
97
+ gcloud emulators firestore start --host-port="$FIRESTORE_EMULATOR_HOST" > emulator.log 2>&1 &
98
+ for _ in $(seq 1 60); do
99
+ curl -sf "http://$FIRESTORE_EMULATOR_HOST" > /dev/null && exit 0
100
+ sleep 1
101
+ done
102
+ cat emulator.log
103
+ exit 1
104
+ - run: pytest tests/test_token_store.py
@@ -0,0 +1,119 @@
1
+ name: Release
2
+
3
+ # Cutting a version means pushing a tag: `git tag v1.0.1 && git push origin v1.0.1`.
4
+ on:
5
+ push:
6
+ tags: ["v*"]
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ # PyPI refuses a re-upload, so a version mismatch is unrecoverable under that
13
+ # tag. Catch it before anything is built.
14
+ check-version:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v5
18
+ - uses: actions/setup-python@v6
19
+ with:
20
+ # tomllib needs 3.11+; don't depend on the runner's default.
21
+ python-version: "3.11"
22
+ - name: Tag must match pyproject.toml, server.json, and SERVER_VERSION
23
+ run: |
24
+ python - <<'PY'
25
+ import json, os, re, sys, tomllib
26
+
27
+ tag = os.environ["GITHUB_REF_NAME"].removeprefix("v")
28
+ server = json.load(open("server.json"))
29
+ # What the server reports to clients in its MCP initialize response.
30
+ init = open("src/queenscoach/__init__.py").read()
31
+ reported = re.search(r'^SERVER_VERSION = "([^"]+)"', init, re.MULTILINE)
32
+ found = {
33
+ "pyproject.toml": tomllib.load(open("pyproject.toml", "rb"))["project"]["version"],
34
+ "server.json (version)": server["version"],
35
+ "server.json (packages[0].version)": server["packages"][0]["version"],
36
+ "src/queenscoach/__init__.py (SERVER_VERSION)": reported.group(1) if reported else None,
37
+ }
38
+ bad = {where: got for where, got in found.items() if got != tag}
39
+ for where, got in bad.items():
40
+ print(f"::error::tag is {tag} but {where} is {got}")
41
+ sys.exit(1 if bad else 0)
42
+ PY
43
+
44
+ test:
45
+ needs: check-version
46
+ uses: ./.github/workflows/ci.yml
47
+
48
+ publish:
49
+ needs: test
50
+ runs-on: ubuntu-latest
51
+ # Must match the environment named in the PyPI trusted publisher.
52
+ environment: pypi
53
+ permissions:
54
+ # Mints the short-lived OIDC token PyPI trusts. No API token to store.
55
+ id-token: write
56
+ steps:
57
+ - uses: actions/checkout@v5
58
+ - uses: actions/setup-python@v6
59
+ with:
60
+ python-version: "3.11"
61
+ - run: pip install build
62
+ - run: python -m build
63
+ - name: Check the built distributions
64
+ run: pipx run twine check --strict dist/*
65
+ - uses: pypa/gh-action-pypi-publish@release/v1
66
+ - uses: actions/upload-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+
71
+ publish-registry:
72
+ needs: publish
73
+ runs-on: ubuntu-latest
74
+ permissions:
75
+ # mcp-publisher trades this for a registry token; no secret to store.
76
+ id-token: write
77
+ contents: read
78
+ steps:
79
+ - uses: actions/checkout@v5
80
+ - name: Wait for the release to be visible on PyPI
81
+ # The registry verifies the mcp-name marker by reading the package
82
+ # description off PyPI, so it has to be able to see this version.
83
+ run: |
84
+ version="${GITHUB_REF_NAME#v}"
85
+ for attempt in $(seq 1 20); do
86
+ if curl -sf "https://pypi.org/pypi/queenscoach/$version/json" > /dev/null; then
87
+ echo "queenscoach $version is on PyPI"
88
+ exit 0
89
+ fi
90
+ echo "attempt $attempt: not visible yet, retrying in 15s"
91
+ sleep 15
92
+ done
93
+ echo "::error::queenscoach $version never appeared on PyPI"
94
+ exit 1
95
+ - name: Install mcp-publisher
96
+ run: |
97
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
98
+ - name: Authenticate to the MCP Registry
99
+ run: ./mcp-publisher login github-oidc
100
+ - name: Publish to the MCP Registry
101
+ run: ./mcp-publisher publish
102
+
103
+ github-release:
104
+ needs: [publish, publish-registry]
105
+ runs-on: ubuntu-latest
106
+ permissions:
107
+ # Creating the release writes to the repo.
108
+ contents: write
109
+ steps:
110
+ # Only so gh infers the repository; the notes come from the API.
111
+ - uses: actions/checkout@v5
112
+ - uses: actions/download-artifact@v4
113
+ with:
114
+ name: dist
115
+ path: dist/
116
+ - name: Create the GitHub release
117
+ env:
118
+ GH_TOKEN: ${{ github.token }}
119
+ run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ *.log
11
+ .env
12
+
13
+ # Local working notes and prompts, never committed.
14
+ *scratchpad*.md
@@ -0,0 +1,27 @@
1
+ # The HTTP transport for a hosted deployment, as scripts/deploy-gcp.sh builds it
2
+ # on Cloud Build. The stdio server does not need this: `pip install queenscoach`.
3
+ FROM python:3.12-slim
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1 \
7
+ PIP_NO_CACHE_DIR=1 \
8
+ PIP_DISABLE_PIP_VERSION_CHECK=1
9
+
10
+ WORKDIR /app
11
+ COPY pyproject.toml README.md LICENSE ./
12
+ COPY src ./src
13
+ # The gcp extra adds the Firestore token store (QUEENSCOACH_TOKEN_STORE=firestore).
14
+ RUN pip install '.[gcp]' \
15
+ && useradd --system --no-create-home --uid 10001 queenscoach
16
+
17
+ USER queenscoach
18
+
19
+ # Cloud Run delivers traffic to port 8080 by default. The server binds every
20
+ # interface because the platform's front end is the only way in; it still
21
+ # requires QUEENSCOACH_PUBLIC_URL, the Google client, and an allow list at runtime.
22
+ ENV QUEENSCOACH_TRANSPORT=http \
23
+ QUEENSCOACH_HTTP_HOST=0.0.0.0 \
24
+ QUEENSCOACH_HTTP_PORT=8080
25
+
26
+ EXPOSE 8080
27
+ CMD ["queenscoach"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Wanninger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.