nfl-bigquery 0.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.
- nfl_bigquery-0.1.0/.github/workflows/release.yml +38 -0
- nfl_bigquery-0.1.0/.github/workflows/test.yml +28 -0
- nfl_bigquery-0.1.0/.gitignore +12 -0
- nfl_bigquery-0.1.0/CHANGELOG.md +11 -0
- nfl_bigquery-0.1.0/LICENSE +21 -0
- nfl_bigquery-0.1.0/PKG-INFO +73 -0
- nfl_bigquery-0.1.0/README.md +41 -0
- nfl_bigquery-0.1.0/nfl_bigquery/__init__.py +3 -0
- nfl_bigquery-0.1.0/nfl_bigquery/_transform_util.py +19 -0
- nfl_bigquery-0.1.0/nfl_bigquery/_version.py +1 -0
- nfl_bigquery-0.1.0/nfl_bigquery/cli.py +145 -0
- nfl_bigquery-0.1.0/nfl_bigquery/client.py +48 -0
- nfl_bigquery-0.1.0/nfl_bigquery/dictionaries/pbp.csv +373 -0
- nfl_bigquery-0.1.0/nfl_bigquery/dictionaries/player_stats.csv +49 -0
- nfl_bigquery-0.1.0/nfl_bigquery/dictionaries/players.csv +40 -0
- nfl_bigquery-0.1.0/nfl_bigquery/dictionaries/schedules.csv +46 -0
- nfl_bigquery-0.1.0/nfl_bigquery/dictionary.py +72 -0
- nfl_bigquery-0.1.0/nfl_bigquery/docs/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/docs/renderers.py +76 -0
- nfl_bigquery-0.1.0/nfl_bigquery/docs/taxonomy.py +41 -0
- nfl_bigquery-0.1.0/nfl_bigquery/games/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/games/schema.py +43 -0
- nfl_bigquery-0.1.0/nfl_bigquery/games/transform.py +13 -0
- nfl_bigquery-0.1.0/nfl_bigquery/partition.py +27 -0
- nfl_bigquery-0.1.0/nfl_bigquery/players/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/players/schema.py +19 -0
- nfl_bigquery-0.1.0/nfl_bigquery/players/sync.py +27 -0
- nfl_bigquery-0.1.0/nfl_bigquery/players/transform.py +10 -0
- nfl_bigquery-0.1.0/nfl_bigquery/players/writer.py +61 -0
- nfl_bigquery-0.1.0/nfl_bigquery/plays/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/plays/enrichment.py +154 -0
- nfl_bigquery-0.1.0/nfl_bigquery/plays/schema.py +18 -0
- nfl_bigquery-0.1.0/nfl_bigquery/plays/transform.py +10 -0
- nfl_bigquery-0.1.0/nfl_bigquery/runs.py +82 -0
- nfl_bigquery-0.1.0/nfl_bigquery/schema.py +68 -0
- nfl_bigquery-0.1.0/nfl_bigquery/stats/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/stats/schema.py +25 -0
- nfl_bigquery-0.1.0/nfl_bigquery/stats/transform.py +10 -0
- nfl_bigquery-0.1.0/nfl_bigquery/sync.py +82 -0
- nfl_bigquery-0.1.0/nfl_bigquery/verify/__init__.py +0 -0
- nfl_bigquery-0.1.0/nfl_bigquery/verify/base.py +10 -0
- nfl_bigquery-0.1.0/nfl_bigquery/verify/internal.py +116 -0
- nfl_bigquery-0.1.0/nfl_bigquery/writer.py +120 -0
- nfl_bigquery-0.1.0/pyproject.toml +69 -0
- nfl_bigquery-0.1.0/scripts/_norton_ssl.py +106 -0
- nfl_bigquery-0.1.0/scripts/make_fixtures.py +37 -0
- nfl_bigquery-0.1.0/scripts/refresh_dictionaries.py +146 -0
- nfl_bigquery-0.1.0/tests/fixtures/mini_pbp.csv +8 -0
- nfl_bigquery-0.1.0/tests/fixtures/pbp_2023_sample.parquet +0 -0
- nfl_bigquery-0.1.0/tests/fixtures/player_stats_2023_sample.parquet +0 -0
- nfl_bigquery-0.1.0/tests/fixtures/players_2023_sample.parquet +0 -0
- nfl_bigquery-0.1.0/tests/fixtures/schedules_2023_sample.parquet +0 -0
- nfl_bigquery-0.1.0/tests/test_cli_players.py +26 -0
- nfl_bigquery-0.1.0/tests/test_cli_sync.py +94 -0
- nfl_bigquery-0.1.0/tests/test_client.py +53 -0
- nfl_bigquery-0.1.0/tests/test_dictionary.py +51 -0
- nfl_bigquery-0.1.0/tests/test_docs.py +48 -0
- nfl_bigquery-0.1.0/tests/test_enrichment_keys.py +45 -0
- nfl_bigquery-0.1.0/tests/test_fixtures_present.py +35 -0
- nfl_bigquery-0.1.0/tests/test_live_smoke.py +13 -0
- nfl_bigquery-0.1.0/tests/test_other_schemas.py +29 -0
- nfl_bigquery-0.1.0/tests/test_partition.py +16 -0
- nfl_bigquery-0.1.0/tests/test_players_writer.py +92 -0
- nfl_bigquery-0.1.0/tests/test_plays_schema.py +30 -0
- nfl_bigquery-0.1.0/tests/test_runs.py +45 -0
- nfl_bigquery-0.1.0/tests/test_schema.py +42 -0
- nfl_bigquery-0.1.0/tests/test_schema_drift.py +26 -0
- nfl_bigquery-0.1.0/tests/test_smoke.py +17 -0
- nfl_bigquery-0.1.0/tests/test_transforms.py +40 -0
- nfl_bigquery-0.1.0/tests/test_verify.py +87 -0
- nfl_bigquery-0.1.0/tests/test_writer.py +83 -0
- nfl_bigquery-0.1.0/uv.lock +1369 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: astral-sh/setup-uv@v3
|
|
14
|
+
- run: uv python install 3.13
|
|
15
|
+
- name: Create venv
|
|
16
|
+
run: uv venv
|
|
17
|
+
- name: Install build tools
|
|
18
|
+
run: uv pip install build
|
|
19
|
+
- run: uv run python -m build
|
|
20
|
+
- uses: actions/upload-artifact@v4
|
|
21
|
+
with:
|
|
22
|
+
name: dist
|
|
23
|
+
path: dist/
|
|
24
|
+
|
|
25
|
+
publish:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
needs: build
|
|
28
|
+
environment:
|
|
29
|
+
name: pypi
|
|
30
|
+
url: https://pypi.org/p/nfl-bigquery
|
|
31
|
+
permissions:
|
|
32
|
+
id-token: write
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/download-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: test
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v3
|
|
17
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
+
run: uv python install ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: uv sync --extra dev
|
|
21
|
+
- name: Test
|
|
22
|
+
run: uv run pytest -m "not network"
|
|
23
|
+
- name: Lint
|
|
24
|
+
run: uv run ruff check nfl_bigquery tests
|
|
25
|
+
- name: Type check
|
|
26
|
+
run: uv run pyright nfl_bigquery tests
|
|
27
|
+
- name: Build
|
|
28
|
+
run: uv run python -m build
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
- `sync`: season-chunked idempotent load of `nfl_plays` (372-col nflfastR pbp), `games`
|
|
5
|
+
(schedules), `weekly_player_stats`; `--resume` via `_nfl_ingest_runs`.
|
|
6
|
+
- `players`: `dim_players` MERGE upsert on `gsis_id`.
|
|
7
|
+
- `docs`: llm / bq-apply / markdown / dictionary / dbt renderers from dictionary-seeded
|
|
8
|
+
ColumnSpecs + enrichment overlay.
|
|
9
|
+
- `verify`: internal consistency + plays→weekly reconciliation for passing/rushing/receiving
|
|
10
|
+
yards and TDs (6 metrics, 8 checks total).
|
|
11
|
+
- Schema-drift guard test against nflverse fixtures.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Blahovec
|
|
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.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nfl-bigquery
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NFL play-by-play → BigQuery: idempotent nflverse ingestion + LLM-friendly docs + verification
|
|
5
|
+
Project-URL: Homepage, https://github.com/blahovec-labs/nfl-bigquery
|
|
6
|
+
Project-URL: Issues, https://github.com/blahovec-labs/nfl-bigquery/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/blahovec-labs/nfl-bigquery/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Jason Blahovec
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bigquery,data-engineering,nfl,nflfastr,nflverse,play-by-play
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: db-dtypes<2.0,>=1.0
|
|
21
|
+
Requires-Dist: google-cloud-bigquery<4.0,>=3.20
|
|
22
|
+
Requires-Dist: nflreadpy
|
|
23
|
+
Requires-Dist: pandas<3.0,>=2.0
|
|
24
|
+
Requires-Dist: pyarrow<19.0,>=15.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pyright>=1.1.380; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# nfl-bigquery
|
|
34
|
+
|
|
35
|
+
NFL play-by-play → BigQuery. Idempotent nflverse ingestion + LLM-friendly docs + verification.
|
|
36
|
+
Fourth in the `*-bigquery` family (`statcast-bigquery`, `nhl-bigquery`, `yfinance-bigquery`).
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
```bash
|
|
40
|
+
pip install nfl-bigquery
|
|
41
|
+
gcloud auth application-default login
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Sync
|
|
45
|
+
```bash
|
|
46
|
+
# Backfill 1999–present into a shared dataset (idempotent, resumable)
|
|
47
|
+
nfl-bigquery sync --seasons 1999-2025 --resume \
|
|
48
|
+
--pbp-table myproject.mydataset.nfl_plays
|
|
49
|
+
# Player dimension
|
|
50
|
+
nfl-bigquery players --players-table myproject.mydataset.dim_players
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Writes `nfl_plays`, `games`, `weekly_player_stats` (season-chunked DELETE→INSERT) and
|
|
54
|
+
`dim_players` (MERGE on `gsis_id`), plus a `_nfl_ingest_runs` log for `--resume`.
|
|
55
|
+
|
|
56
|
+
## Docs & verify
|
|
57
|
+
```bash
|
|
58
|
+
nfl-bigquery docs --format llm
|
|
59
|
+
nfl-bigquery docs --format bq-apply --table myproject.mydataset.nfl_plays
|
|
60
|
+
nfl-bigquery verify --source internal --season 2024 \
|
|
61
|
+
--pbp-table myproject.mydataset.nfl_plays
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `verify` command runs 8 checks: duplicate-play detection, orphan-play detection, and
|
|
65
|
+
plays→weekly reconciliation for **passing/rushing/receiving yards and TDs** (6 metrics).
|
|
66
|
+
The `--tolerance` flag (default 0.0) sets the maximum allowed absolute aggregate diff;
|
|
67
|
+
lateral plays cause small legitimate yard diffs, so consider `--tolerance 5` for yards.
|
|
68
|
+
|
|
69
|
+
## Data source
|
|
70
|
+
All data from [nflverse](https://nflreadpy.nflverse.com/) via `nflreadpy`. Play-by-play is
|
|
71
|
+
nflfastR's 372-column feed (EPA/WPA/air-yards/CPOE modeled), back to 1999.
|
|
72
|
+
|
|
73
|
+
MIT licensed.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# nfl-bigquery
|
|
2
|
+
|
|
3
|
+
NFL play-by-play → BigQuery. Idempotent nflverse ingestion + LLM-friendly docs + verification.
|
|
4
|
+
Fourth in the `*-bigquery` family (`statcast-bigquery`, `nhl-bigquery`, `yfinance-bigquery`).
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
```bash
|
|
8
|
+
pip install nfl-bigquery
|
|
9
|
+
gcloud auth application-default login
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Sync
|
|
13
|
+
```bash
|
|
14
|
+
# Backfill 1999–present into a shared dataset (idempotent, resumable)
|
|
15
|
+
nfl-bigquery sync --seasons 1999-2025 --resume \
|
|
16
|
+
--pbp-table myproject.mydataset.nfl_plays
|
|
17
|
+
# Player dimension
|
|
18
|
+
nfl-bigquery players --players-table myproject.mydataset.dim_players
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Writes `nfl_plays`, `games`, `weekly_player_stats` (season-chunked DELETE→INSERT) and
|
|
22
|
+
`dim_players` (MERGE on `gsis_id`), plus a `_nfl_ingest_runs` log for `--resume`.
|
|
23
|
+
|
|
24
|
+
## Docs & verify
|
|
25
|
+
```bash
|
|
26
|
+
nfl-bigquery docs --format llm
|
|
27
|
+
nfl-bigquery docs --format bq-apply --table myproject.mydataset.nfl_plays
|
|
28
|
+
nfl-bigquery verify --source internal --season 2024 \
|
|
29
|
+
--pbp-table myproject.mydataset.nfl_plays
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `verify` command runs 8 checks: duplicate-play detection, orphan-play detection, and
|
|
33
|
+
plays→weekly reconciliation for **passing/rushing/receiving yards and TDs** (6 metrics).
|
|
34
|
+
The `--tolerance` flag (default 0.0) sets the maximum allowed absolute aggregate diff;
|
|
35
|
+
lateral plays cause small legitimate yard diffs, so consider `--tolerance 5` for yards.
|
|
36
|
+
|
|
37
|
+
## Data source
|
|
38
|
+
All data from [nflverse](https://nflreadpy.nflverse.com/) via `nflreadpy`. Play-by-play is
|
|
39
|
+
nflfastR's 372-column feed (EPA/WPA/air-yards/CPOE modeled), back to 1999.
|
|
40
|
+
|
|
41
|
+
MIT licensed.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Shared transform helper: reindex to schema columns + stamp ingested_at."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import UTC, datetime
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from nfl_bigquery.schema import ColumnSpec, spec_names
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def align_to_schema(
|
|
12
|
+
df: pd.DataFrame, schema: list[ColumnSpec], *, stamp: bool = True
|
|
13
|
+
) -> pd.DataFrame:
|
|
14
|
+
df = df.copy()
|
|
15
|
+
if stamp:
|
|
16
|
+
df["ingested_at"] = datetime.now(UTC)
|
|
17
|
+
cols = spec_names(schema)
|
|
18
|
+
# Reindex adds missing NULLABLE columns as NA and drops columns not in schema.
|
|
19
|
+
return df.reindex(columns=cols)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""nfl-bigquery CLI entrypoint."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from nfl_bigquery._version import __version__
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
11
|
+
parser = argparse.ArgumentParser(prog="nfl-bigquery")
|
|
12
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
13
|
+
sub = parser.add_subparsers(dest="command")
|
|
14
|
+
|
|
15
|
+
sy = sub.add_parser("sync", help="Fetch nflverse data and write to BigQuery")
|
|
16
|
+
sy.add_argument("--seasons", required=True,
|
|
17
|
+
help="e.g. 1999-2025 | 2023,2025 | 2024 | latest")
|
|
18
|
+
sy.add_argument("--pbp-table", required=True, help="project.dataset.nfl_plays")
|
|
19
|
+
sy.add_argument("--games-table")
|
|
20
|
+
sy.add_argument("--weekly-stats-table")
|
|
21
|
+
sy.add_argument("--runs-table")
|
|
22
|
+
sy.add_argument("--skip-games", action="store_true")
|
|
23
|
+
sy.add_argument("--skip-weekly-stats", action="store_true")
|
|
24
|
+
sy.add_argument("--resume", action="store_true")
|
|
25
|
+
sy.add_argument("--dry-run", action="store_true")
|
|
26
|
+
|
|
27
|
+
pl = sub.add_parser("players", help="Upsert the dim_players dimension")
|
|
28
|
+
pl.add_argument("--players-table", required=True, help="project.dataset.dim_players")
|
|
29
|
+
pl.add_argument("--dry-run", action="store_true")
|
|
30
|
+
|
|
31
|
+
dc = sub.add_parser("docs", help="Render table documentation")
|
|
32
|
+
dc.add_argument("--format", required=True,
|
|
33
|
+
choices=["bq-apply", "llm", "dictionary", "markdown", "dbt"])
|
|
34
|
+
dc.add_argument("--table", help="project.dataset.table (required for bq-apply)")
|
|
35
|
+
dc.add_argument("--output", default="-", help="output path or - for stdout")
|
|
36
|
+
|
|
37
|
+
vf = sub.add_parser("verify", help="Run data-quality checks")
|
|
38
|
+
vf.add_argument("--source", default="internal", choices=["internal"])
|
|
39
|
+
vf.add_argument("--season", type=int, required=True)
|
|
40
|
+
vf.add_argument("--pbp-table", required=True)
|
|
41
|
+
vf.add_argument("--games-table")
|
|
42
|
+
vf.add_argument("--weekly-stats-table")
|
|
43
|
+
vf.add_argument("--tolerance", type=float, default=1.0)
|
|
44
|
+
|
|
45
|
+
return parser
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv: list[str] | None = None) -> int:
|
|
49
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
|
|
50
|
+
parser = build_parser()
|
|
51
|
+
ns = parser.parse_args(argv)
|
|
52
|
+
if not getattr(ns, "command", None):
|
|
53
|
+
parser.print_help()
|
|
54
|
+
return 0
|
|
55
|
+
if ns.command == "sync":
|
|
56
|
+
from google.cloud import bigquery
|
|
57
|
+
|
|
58
|
+
from nfl_bigquery.client import NflverseClient
|
|
59
|
+
from nfl_bigquery.runs import RunsTable
|
|
60
|
+
from nfl_bigquery.sync import run_sync
|
|
61
|
+
from nfl_bigquery.writer import BigQueryWriter
|
|
62
|
+
|
|
63
|
+
bq = bigquery.Client()
|
|
64
|
+
return run_sync(ns, client=NflverseClient(), writer=BigQueryWriter(client=bq),
|
|
65
|
+
runs=RunsTable(client=bq))
|
|
66
|
+
if ns.command == "players":
|
|
67
|
+
from google.cloud import bigquery
|
|
68
|
+
|
|
69
|
+
from nfl_bigquery.client import NflverseClient
|
|
70
|
+
from nfl_bigquery.players.sync import run_players
|
|
71
|
+
|
|
72
|
+
return run_players(ns, client=NflverseClient(), bq_client=bigquery.Client())
|
|
73
|
+
if ns.command == "docs":
|
|
74
|
+
from nfl_bigquery.docs.renderers import (
|
|
75
|
+
render_bq_apply,
|
|
76
|
+
render_dbt,
|
|
77
|
+
render_dictionary_rows,
|
|
78
|
+
render_llm,
|
|
79
|
+
render_markdown,
|
|
80
|
+
)
|
|
81
|
+
from nfl_bigquery.docs.taxonomy import TABLES
|
|
82
|
+
|
|
83
|
+
if ns.format == "llm":
|
|
84
|
+
text = render_llm(TABLES)
|
|
85
|
+
elif ns.format == "markdown":
|
|
86
|
+
text = render_markdown(TABLES)
|
|
87
|
+
elif ns.format == "dbt":
|
|
88
|
+
text = render_dbt(TABLES)
|
|
89
|
+
elif ns.format == "bq-apply":
|
|
90
|
+
if not ns.table:
|
|
91
|
+
parser.error("--table is required for --format bq-apply")
|
|
92
|
+
from google.cloud import bigquery
|
|
93
|
+
|
|
94
|
+
bq = bigquery.Client()
|
|
95
|
+
table_name = ns.table.split(".")[-1]
|
|
96
|
+
if table_name not in TABLES:
|
|
97
|
+
parser.error(f"unknown table '{table_name}'; choices: {sorted(TABLES)}")
|
|
98
|
+
desc = {n: d for n, d in render_bq_apply(table_name)}
|
|
99
|
+
fetched = bq.get_table(ns.table)
|
|
100
|
+
new_schema = [
|
|
101
|
+
bigquery.SchemaField(
|
|
102
|
+
f.name, f.field_type, mode=f.mode,
|
|
103
|
+
description=desc.get(f.name, f.description or ""),
|
|
104
|
+
)
|
|
105
|
+
for f in fetched.schema
|
|
106
|
+
]
|
|
107
|
+
fetched.schema = new_schema
|
|
108
|
+
bq.update_table(fetched, ["schema"])
|
|
109
|
+
print(f"applied descriptions to {ns.table}")
|
|
110
|
+
return 0
|
|
111
|
+
else: # dictionary
|
|
112
|
+
import json
|
|
113
|
+
|
|
114
|
+
text = json.dumps(render_dictionary_rows(TABLES), indent=2)
|
|
115
|
+
if ns.output == "-":
|
|
116
|
+
print(text)
|
|
117
|
+
else:
|
|
118
|
+
from pathlib import Path
|
|
119
|
+
|
|
120
|
+
Path(ns.output).write_text(text, encoding="utf-8")
|
|
121
|
+
return 0
|
|
122
|
+
if ns.command == "verify":
|
|
123
|
+
from google.cloud import bigquery
|
|
124
|
+
|
|
125
|
+
from nfl_bigquery.verify.internal import run_internal
|
|
126
|
+
from nfl_bigquery.writer import TableRef
|
|
127
|
+
|
|
128
|
+
pbp = TableRef.parse(ns.pbp_table)
|
|
129
|
+
games = ns.games_table or f"{pbp.project}.{pbp.dataset}.games"
|
|
130
|
+
stats = ns.weekly_stats_table or f"{pbp.project}.{pbp.dataset}.weekly_player_stats"
|
|
131
|
+
results = run_internal(
|
|
132
|
+
bigquery.Client(), plays_ref=str(pbp), games_ref=games, stats_ref=stats,
|
|
133
|
+
season=ns.season, tolerance=ns.tolerance)
|
|
134
|
+
failed = 0
|
|
135
|
+
for r in results:
|
|
136
|
+
mark = "PASS" if r.passed else "FAIL"
|
|
137
|
+
print(f"[{mark}] {r.name}: {r.detail}")
|
|
138
|
+
if not r.passed:
|
|
139
|
+
failed += 1
|
|
140
|
+
return 1 if failed else 0
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# nfl_bigquery/client.py
|
|
2
|
+
"""Thin nflreadpy wrapper: fetch a season's parquet, convert to pandas, retry."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import nflreadpy as nfl
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NflverseClient:
|
|
15
|
+
def __init__(self, max_retries: int = 5, backoff_seconds: float = 2.0) -> None:
|
|
16
|
+
self.max_retries = max_retries
|
|
17
|
+
self.backoff_seconds = backoff_seconds
|
|
18
|
+
|
|
19
|
+
def _retry(self, fn, *args) -> pd.DataFrame:
|
|
20
|
+
last: Exception | None = None
|
|
21
|
+
for attempt in range(1, self.max_retries + 1):
|
|
22
|
+
try:
|
|
23
|
+
return fn(*args).to_pandas()
|
|
24
|
+
except Exception as e: # noqa: BLE001 - retry any transient download error
|
|
25
|
+
last = e
|
|
26
|
+
wait = self.backoff_seconds * (2 ** (attempt - 1))
|
|
27
|
+
log.warning(
|
|
28
|
+
"nflverse load failed (attempt %d/%d): %s; retrying in %.1fs",
|
|
29
|
+
attempt,
|
|
30
|
+
self.max_retries,
|
|
31
|
+
e,
|
|
32
|
+
wait,
|
|
33
|
+
)
|
|
34
|
+
if attempt < self.max_retries:
|
|
35
|
+
time.sleep(wait)
|
|
36
|
+
raise RuntimeError(f"nflverse load failed after {self.max_retries} attempts") from last
|
|
37
|
+
|
|
38
|
+
def load_pbp(self, season: int) -> pd.DataFrame:
|
|
39
|
+
return self._retry(nfl.load_pbp, [season])
|
|
40
|
+
|
|
41
|
+
def load_schedules(self, season: int) -> pd.DataFrame:
|
|
42
|
+
return self._retry(nfl.load_schedules, [season])
|
|
43
|
+
|
|
44
|
+
def load_player_stats(self, season: int) -> pd.DataFrame:
|
|
45
|
+
return self._retry(nfl.load_player_stats, [season])
|
|
46
|
+
|
|
47
|
+
def load_players(self) -> pd.DataFrame:
|
|
48
|
+
return self._retry(nfl.load_players)
|