1+ import json
2+ from typing import List
3+ from sqlalchemy import create_engine , inspect
4+ from sqlalchemy .engine import Engine
5+ from pontoon .base import Namespace
6+ from pontoon .source .sql_source import SQLSource
7+
8+
9+ class BigQuerySource (SQLSource ):
10+ """BigQuery-specific implementation of SQLSource"""
11+
12+ def _create_engine (self , connect_config : dict ) -> Engine :
13+ """Create BigQuery-specific SQLAlchemy engine with service account authentication"""
14+ project_id = connect_config .get ('project_id' )
15+ service_account = connect_config .get ('service_account' )
16+
17+ if not project_id :
18+ raise ValueError ("BigQuery connection config must include 'project_id' field" )
19+
20+ if not service_account :
21+ raise ValueError ("BigQuery connection config must include 'service_account' field" )
22+
23+ # Parse service account JSON
24+ try :
25+ credentials_info = json .loads (service_account )
26+ except json .JSONDecodeError as e :
27+ raise ValueError (f"Invalid service account JSON: { e } " )
28+
29+ # Build BigQuery connection string and create engine
30+ connection_string = f"bigquery://{ project_id } "
31+
32+ # Get chunk size from connect config, defaulting to 1024 if not specified
33+ chunk_size = connect_config .get ('chunk_size' , 1024 )
34+
35+ return create_engine (
36+ connection_string ,
37+ credentials_info = credentials_info ,
38+ arraysize = chunk_size
39+ )
40+
41+ def _validate_auth_type (self , auth_type : str ) -> None :
42+ """Validate authentication type for BigQuery - only 'service_account' is supported"""
43+ if auth_type != 'service_account' :
44+ raise ValueError (f"BigQuery source only supports 'service_account' authentication, got '{ auth_type } '" )
45+
46+ def _get_namespace (self , connect_config : dict ) -> Namespace :
47+ """Extract namespace from BigQuery connection config using project_id"""
48+ project_id = connect_config .get ('project_id' )
49+ if not project_id :
50+ raise ValueError ("BigQuery connection config must include 'project_id' field" )
51+
52+ return Namespace (project_id )
53+
54+ def _inspect_streams_impl (self ) -> List [dict ]:
55+ """BigQuery-specific stream inspection logic"""
56+ streams = []
57+
58+ with self ._connect () as conn :
59+ # Use the inspector to get schema and table information
60+ inspector = inspect (conn )
61+
62+ # Get all available schemas (datasets in BigQuery terminology)
63+ schemas = inspector .get_schema_names ()
64+
65+ for schema in schemas :
66+ # For each table in the schema
67+ for table in inspector .get_table_names (schema = schema ):
68+ # BigQuery table names come in format "project.dataset.table"
69+ # We need to extract just the table name
70+ if '.' in table :
71+ _ , table_name = table .split ('.' , 1 )
72+ if '.' in table_name :
73+ # Handle case where table is "dataset.table"
74+ _ , table_name = table_name .split ('.' , 1 )
75+ else :
76+ table_name = table
77+
78+ # Get column information using the full table reference
79+ project_id = self ._config ['connect' ]['project_id' ]
80+ full_table_name = f"{ project_id } .{ schema } .{ table_name } "
81+
82+ try :
83+ columns = inspector .get_columns (full_table_name )
84+ streams .append ({
85+ 'schema_name' : schema ,
86+ 'stream_name' : table_name ,
87+ 'fields' : [{'name' : col ['name' ], 'type' : str (col ['type' ])} for col in columns ]
88+ })
89+ except Exception :
90+ # Skip tables that can't be inspected (e.g., views, external tables)
91+ continue
92+
93+ return streams
0 commit comments