"""Cliente base para Google BigQuery.""" from dataclasses import dataclass, field import google.auth from google.cloud import bigquery from google.oauth2 import service_account @dataclass class BQClient: """Cliente para Google BigQuery. Attributes: project_id: ID del proyecto GCP. _client: Cliente oficial de BigQuery (interno). """ project_id: str _client: bigquery.Client = field(repr=False) def close(self) -> None: """Cierra el cliente BigQuery.""" self._client.close() def __enter__(self): return self def __exit__(self, *args): self.close() def bq_auth( project_id: str = "", credentials_path: str = "", drop_quota_project: bool = False, ) -> BQClient: """Autentica contra Google BigQuery. Tres modos de autenticacion: 1. ADC (Application Default Credentials): sin argumentos, usa gcloud auth 2. Service account JSON: con credentials_path 3. Proyecto explicito: con project_id (usa ADC para credenciales) Con drop_quota_project=True (y sin credentials_path) resuelve las credenciales ADC via google.auth.default y elimina el quota project fijado en el ADC del usuario (creds.with_quota_project(None)). Esto evita el error 403 USER_PROJECT_DENIED cuando el ADC lleva un quota_project_id ajeno al proyecto contra el que se consulta. Args: project_id: ID del proyecto GCP. Vacio = detectar de credenciales. credentials_path: Ruta a archivo JSON de service account. Vacio = ADC. drop_quota_project: Si True y sin credentials_path, resuelve ADC con google.auth.default y descarta el quota project del ADC (with_quota_project(None)). Default False = comportamiento original. Returns: BQClient autenticado listo para usar. Raises: google.auth.exceptions.DefaultCredentialsError: Si no hay credenciales configuradas. FileNotFoundError: Si credentials_path no existe. Example: >>> client = bq_auth() # ADC >>> client = bq_auth("my-project") # ADC con proyecto explicito >>> client = bq_auth(credentials_path="/path/to/sa.json") # Service account >>> client = bq_auth("autingo-159109", drop_quota_project=True) # sin quota project """ if credentials_path: creds = service_account.Credentials.from_service_account_file(credentials_path) proj = project_id or creds.project_id client = bigquery.Client(credentials=creds, project=proj) elif drop_quota_project: creds, adc_project = google.auth.default( scopes=["https://www.googleapis.com/auth/bigquery"] ) if hasattr(creds, "with_quota_project"): creds = creds.with_quota_project(None) client = bigquery.Client(project=project_id or adc_project, credentials=creds) elif project_id: client = bigquery.Client(project=project_id) else: client = bigquery.Client() return BQClient(project_id=client.project, _client=client)