Skip to content

BqUtils: support a BigQuery quota project override + consolidate client construction#706

Draft
kmontemayor2-sc wants to merge 11 commits into
mainfrom
kmonte/bq-quota-project
Draft

BqUtils: support a BigQuery quota project override + consolidate client construction#706
kmontemayor2-sc wants to merge 11 commits into
mainfrom
kmonte/bq-quota-project

Conversation

@kmontemayor2-sc

Copy link
Copy Markdown
Collaborator

What

  • BqUtils.init now accepts an optional quota_project_id and resolves the BigQuery client's quota/billing project as explicit arg → GIGL_BIGQUERY_QUOTA_PROJECT env var → none (unset ⇒ unchanged default behavior). When set, it's applied via ClientOptions(quota_project_id=...).
  • Adds BqUtils.load_files_to_bq(source_uris, bq_path, job_config, should_run_async=False) — loads files (e.g. Avro) from GCS into a BigQuery table (fulfilling the long-standing TODO to centralize this in BqUtils).
  • Routes the two remaining direct bigquery.Client(...) sites through BqUtils: the Avro export in gigl/common/data/export.py (load_embeddings_to_bigquery / load_predictions_to_bigquery) and the shard-key check in gigl/common/beam/sharded_read.py. After this change, the only bigquery.Client(...) construction in gigl/ lives inside BqUtils.

Why

The BigQuery client's quota project (used for API-enablement and per-API quota checks) defaults to the project associated with the active credentials. When a job runs under credentials whose default project differs from the project that owns the BigQuery resources — e.g. a workload that creates load jobs in a data project while its credentials belong to a different infrastructure project — the enablement/quota check runs against that default project, which may not have the BigQuery API enabled.

A per-client quota_project_id lets callers direct BigQuery's quota/billing to the appropriate project. It is intentionally BigQuery-scoped (set only on the BqUtils client), so it does not affect other Google Cloud clients (e.g. GCS) — unlike the process-global GOOGLE_CLOUD_QUOTA_PROJECT. Consolidating client construction in BqUtils means this (and future client config) applies uniformly to all callers without per-call-site plumbing.

Setting the quota project requires the caller to have serviceusage.services.use on that project (the roles/serviceusage.serviceUsageConsumer role).

Configuration

  • GIGL_BIGQUERY_QUOTA_PROJECT (new, optional) — default quota project for all BqUtils BigQuery clients. Unset ⇒ no override (current behavior).
  • Per-call override: pass quota_project_id=... (takes precedence over the env var). export.py's public functions forward an optional quota_project_id for this.

kmonte and others added 10 commits July 15, 2026 17:55
Thread an optional `quota_project_id` through `_load_records_to_bigquery`
and its public callers (`load_embeddings_to_bigquery`,
`load_predictions_to_bigquery`). When provided, the BigQuery client is
built with `ClientOptions(quota_project_id=...)` so only the BigQuery
client's quota / billing project is scoped, leaving other Google clients
(e.g. GCS) on their default quota project.

The parameter defaults to None, so existing callers are unaffected: no
quota project is forced and google-auth's default resolution applies.

Extend the export unit tests to cover both the scoped and default cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add quota-project resolution to `BqUtils.__init__` (new optional
`quota_project_id` param): resolved as explicit param > the
`GIGL_BIGQUERY_QUOTA_PROJECT` env var > none, where an empty value means
"unset". When resolved, the underlying `bigquery.Client` is built with
`ClientOptions(quota_project_id=...)` so the override scopes only this
BigQuery client, leaving other Google clients unaffected. With neither
the param nor the env var set, `client_options=None` preserves the exact
prior behavior for all existing call sites.

Also add `BqUtils.load_files_to_bq(...)`, a generic wildcard GCS-to-BQ
load helper: the caller supplies the `LoadJobConfig`; sync waits for the
job and logs the loaded row count, async returns the created job
immediately.

Add `GIGL_BIGQUERY_QUOTA_PROJECT_ENV_KEY` to `gigl/env/constants.py` and
env-hermetic unit tests for both the quota resolution and the loader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Migrate `_load_records_to_bigquery` off its own `bigquery.Client` onto
`BqUtils` + `BqUtils.load_files_to_bq`, resolving the standing TODO. The
public `load_embeddings_to_bigquery` / `load_predictions_to_bigquery`
signatures and `LoadJob` return are unchanged; the `quota_project_id`
param is now forwarded to `BqUtils` (which also honors the
`GIGL_BIGQUERY_QUOTA_PROJECT` env var when the param is omitted).

Behavior parity is preserved: Avro source format, WRITE_APPEND, schema,
the `*.avro` glob, sync-waits / async-returns, and the returned job. The
destination is built as a plain `project.dataset.table` string so
domain-scoped project IDs are not rejected.

Rewrite the export BQ tests to verify delegation to BqUtils (construction
args + load_files_to_bq args) plus an end-to-end env-var test, and fix a
`should_run_asnyc` docstring typo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the standalone `bigquery.Client()` in
`_assert_shard_key_in_table` with `BqUtils.fetch_bq_table_schema`, so all
GiGL BigQuery-client construction goes through `BqUtils` (and picks up the
`GIGL_BIGQUERY_QUOTA_PROJECT` override). Behavior is unchanged: the schema
lookup accepts the same table-path formats, and a missing table still
propagates `NotFound`.

Add the first unit tests for this module covering the pass, missing-key,
and missing-table cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the quota_project_id parameter from _load_records_to_bigquery,
load_embeddings_to_bigquery, and load_predictions_to_bigquery. The
export layer now gets the quota / billing project solely from the
GIGL_BIGQUERY_QUOTA_PROJECT environment variable, resolved inside
BqUtils. BqUtils itself keeps its explicit quota_project_id parameter
for callers that need a per-client override.

Trim the export unit tests accordingly: quota / billing resolution is
BqUtils's responsibility and is covered in bq_test.py, so the export
tests no longer assert on it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cover load_predictions_to_bigquery end-to-end against real GCS and
BigQuery, mirroring the existing embedding export integration test.
Real load behavior for the export helpers now lives in integration
tests rather than mocked unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the mocked sharded_read unit tests with an integration test
that constructs ShardedExportRead against a real table, covering the
valid shard-key path, the missing-column ValueError, and the missing
table NotFound through the public constructor instead of the private
helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exercise ShardedExportRead against the real BigQuery table by running the
read in a DirectRunner pipeline and asserting all rows are recovered across
the shards, rather than only validating construction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove test_load_embedding_to_bigquery and test_load_prediction_to_bigquery,
which only verified mocks; the real load behavior is covered by the export
integration tests. Prune imports left unused by the deletion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the load-files test class (covered by the export integration tests)
and trim a redundant assertion from the client-construction precedence tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kmontemayor2-sc

Copy link
Copy Markdown
Collaborator Author

/all_test

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:28UTC : 🔄 Lint Test started.

@ 20:40:24UTC : ✅ Workflow completed successfully.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:29UTC : 🔄 Scala Unit Test started.

@ 20:43:13UTC : ✅ Workflow completed successfully.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:30UTC : 🔄 C++ Unit Test started.

@ 20:34:21UTC : ✅ Workflow completed successfully.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:31UTC : 🔄 Python Unit Test started.

@ 21:39:34UTC : ✅ Workflow completed successfully.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:33UTC : 🔄 E2E Test started.

@ 22:45:01UTC : ✅ Workflow completed successfully.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

GiGL Automation

@ 20:32:33UTC : 🔄 Integration Test started.

@ 22:06:12UTC : ✅ Workflow completed successfully.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants