多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Data Formulator 的 BigQuery 数据加载器集成测试:基于 BigQuery Emulator 与 workspace/datalake 架构的完整实践指南

Data Formulator 的 BigQuery 数据加载器集成测试:基于 BigQuery Emulator 与 workspace/datalake 架构的完整实践指南 Data Formulator 的 BigQuery 数据加载器集成测试基于 BigQuery Emulator 与 workspace/datalake 架构的完整实践指南【免费下载链接】data-formulator Data Formulator is an interactive AI-powered data analysis system makes it easy to connect, explore and visualize data.项目地址: https://gitcode.com/GitHub_Trending/da/data-formulator本篇指南围绕仓库中 tests/database-dockers/bigquery/README.md 展开系统讲解 Data Formulator 如何借助 goccy/bigquery-emulator 在本地对BigQueryDataLoader进行端到端集成测试。读者将掌握BigQuery 连接器的核心参数与三种认证方式、list_tables()/fetch_data_as_arrow()/ingest_to_workspace()等关键 API 的调用链与源码实现以及从模拟器启动、数据初始化到测试运行与拆除的完整实战流程同时理解外部数据源 → Arrow → Parquet 工作区这一 workspace/datalake 摄取设计。一、测试对象与架构背景BigQueryDataLoader 与 workspace/datalake 设计BigQuery 集成测试的目标是验证 bigquery_data_loader.py 中的BigQueryDataLoader在真实交互层面的正确性——不是用 mock而是通过一个运行在本地的 BigQuery 模拟器Emulator模拟 Google Cloud BigQuery 的 REST/gRPC 接口从而覆盖连接、枚举、取数、摄取全链路。这一设计的关键约束写在该测试目录 README 的标题中workspace/datalake 设计。它意味着摄取路径统一走ingest_to_workspace()将数据以parquet格式写入一个临时 workspace不使用 DuckDB 作为存储。这一点在 external_data_loader.py 的基类注释中有明确对应Data loaders fetch data from external sources (databases, cloud storage, etc.) and store data as parquet files in the workspace. DuckDB is not used for storage; it is only the computation engine elsewhere in the application. Ingest flow: External Source → PyArrow Table → Parquet (workspace).因此整条摄取链路为外部数据源 → PyArrow Tablefetch_data_as_arrow()→ Parquetingest_to_workspace()写入 workspace全程避免 pandas 中间转换保证大数据量下的传输效率。二、测试环境总览2.1 依赖与前置条件运行该测试需要以下环境见 README 的 Prerequisites依赖用途Docker运行 BigQuery Emulator 容器Python 3.9运行 pytest 与加载器代码google-cloud-bigquery官方 BigQuery Python 客户端加载器与其交互2.2 测试覆盖范围README 明确列出的测试覆盖点如下它们是整个测试套件的验收清单list_tables()—— 全量枚举、按名称过滤、限定具体 datasetfetch_data_as_arrow()—— 从表取数、行数上限size limit、无效表报错ingest_to_workspace()—— 从表摄取入库表名净化sanitization、workspace 元数据、read_parquet/get_parquet_schema静态接口list_params()、auth_instructions()。这些覆盖点在 test_bigquery_loader.py 中一一对应实现详见第六节。三、快速开始启动模拟器 → 跑测试 → 拆除在仓库根目录执行以下三步对应 README 的 Quick start# 1. 启动模拟器 # Option A一键启动全部测试数据库 ./tests/database-dockers/run_test_dbs.sh start # Option B只启动 BigQuery ./tests/database-dockers/run_test_dbs.sh start bigquery # 或等价地cd tests/database-dockers/bigquery docker compose up -d # 2. 运行测试 pytest tests/backend/integration/test_bigquery/ -v # 3. 拆除 ./tests/database-dockers/run_test_dbs.sh stop需要说明两点实际的差异以仓库当前内容为准README 与测试文件 docstring 中给出的测试路径tests/backend/integration/test_bigquery/、tests/plugin/test_bigquery/在当前仓库中并不存在测试文件实际位于 tests/database-dockers/bigquery/test_bigquery_loader.py。因此实际可运行的方式是python -m pytest tests/database-dockers/bigquery/test_bigquery_loader.py -v # 或直接执行文件自带 __main__ 入口可独立运行 python tests/database-dockers/bigquery/test_bigquery_loader.pyrun_test_dbs.sh属于仓库团队的统一测试数据库管理脚本README 中约定的命令入口当前仓库内实际可见的等效替代是 tests/database-dockers/bigquery/start.sh 与各服务独立的 docker-compose.yml以及统一编排文件 docker-compose.test.yml。3.1 用 start.sh 一键起服务仓库自带的 start.sh 封装了启动模拟器 启动 Data Formulator 后端的完整流程# 启动 BigQuery 模拟器 DF 后端后端监听 5567 端口 ./tests/database-dockers/bigquery/start.sh # 仅拆除 BigQuery 容器 ./tests/database-dockers/bigquery/start.sh stop脚本行为要点检测容器df-test-bigquery是否已在运行未运行则执行docker compose -f $COMPOSE_FILE up -d --build --wait--wait会等待健康检查通过通过docker compose down拆除启动 DF 后端的命令是uv run data_formulator --port 5567 --dev前端需另行npx vite启动默认 5173 端口BigQuery: http://localhost:9050 (project: test-project) DF backend: http://localhost:5567 Run npx vite in another terminal for frontend on http://localhost:5173四、命令与统一编排run_test_dbs.shREADME 对run_test_dbs.sh定义了如下命令约定命令说明start bigquery构建并启动 BigQuery 模拟器容器stop bigquery停止容器test bigquery启动模拟器并运行 Python 测试reset bigquery停止、移除并用全新数据重启status显示所有容器状态若需同时启动 MySQL、PostgreSQL、MongoDB、CosmosDB、Superset 等全部测试数据源可以使用统一编排文件 docker-compose.test.yml。该文件采用 profile 机制BigQuery 服务的 profile 为[core, bigquery]端口默认映射9050HTTP与9060gRPC可通过BQ_PORT、BQ_GRPC_PORT环境变量覆盖例如# 只启动 core bigquery 服务 docker compose -f tests/database-dockers/docker-compose.test.yml --profile bigquery up -d --build五、环境变量BigQuery 测试通过环境变量控制目标端点对应 README 的 Environment variables 一节环境变量默认值说明BQ_PROJECT_IDtest-project模拟器中的项目 IDBQ_HTTP_ENDPOINThttp://localhost:9050模拟器 HTTP/REST 端点BQ_HTTP_PORT/BQ_GRPC_PORT9050/9060HTTP 与 gRPC 端口这些变量在 test_bigquery_loader.py 的get_test_config()中被读取def get_test_config() - Dict[str, Any]: return { project_id: os.getenv(BQ_PROJECT_ID, test-project), http_endpoint: os.getenv(BQ_HTTP_ENDPOINT, http://localhost:9050), dataset_id: , location: US, credentials_path: , }此外docker compose up -d也支持BQ_PORT/BQ_GRPC_PORT覆盖端口映射见 docker-compose.yml 中的${BQ_PORT:-9050}:9050写法。六、测试代码剖析从探测到断言6.1 模拟器可用性快速探测不阻塞测试收集pytest.ini 约定下测试收集阶段不会阻塞。测试类TestBigQueryDataLoader用unittest.skipUnless(bq_emulator_available(), ...)做条件跳过而bq_emulator_available()采用2 秒超时的 TCP socket 探测而非真实 API 调用——这是刻意的设计google-cloud-bigquery在端口未监听时会长时间阻塞放在 import/collect 阶段会拖慢整个 pytest 收集流程。测试类TestBigQueryEmulatorProbe专门回归验证了端口关闭时探测必须快速返回 false 5 秒这一行为。6.2 面向模拟器的客户端与加载器构造由于BigQueryDataLoader.__init__会创建指向真实 BigQuery 的客户端测试不能直接实例化。测试通过create_loader_for_emulator()用object.__new__绕过__init__再手工注入配置与匿名凭据 自定义 API 端点的模拟器客户端def create_emulator_client(config): return bigquery.Client( projectconfig[project_id], credentialsAnonymousCredentials(), client_options{api_endpoint: config[http_endpoint]}, )6.3 覆盖用例清单测试方法验证内容test_list_tables枚举到products/customers/orders/page_views等表且每条记录含name、metadata.columns、metadata.row_counttest_list_tables_with_filtertable_filterproduct时所有结果名都包含producttest_list_tables_specific_datasetdataset_ids[sample]时结果均限定在sample数据集test_fetch_data_as_arrow_from_table从test-project.sample.products取数含id/name/category/price列test_fetch_data_respects_sizesize5时行数 ≤ 5test_fetch_data_invalid_table_raises不存在的表抛异常test_ingest_table_to_workspace等摄取后 workspace 中存在 parquet 文件可read_data_as_df读回test_ingest_sanitizes_table_name带连字符的表名被净化小写 下划线test_get_table_info_from_datalakeworkspace 元数据、get_parquet_schema、列数一致性TestBigQueryDataLoaderStaticlist_params含 4 个参数且project_id必填auth_instructions长度 100 且包含认证说明七、源码级深入BigQueryDataLoader 的核心实现7.1 连接参数list_paramsbigquery_data_loader.py 声明了 4 个参数参数名类型必填层级tier说明project_idtext✅connectionGoogle Cloud 项目 IDdataset_idtext❌filter数据集 ID留空表示全部可用逗号分隔多个如billing,enterprise_collected,ga_apicredentials_pathtext❌auth服务账号 JSON 文件路径可选locationtext❌connectionadvancedBigQuery 位置默认US其中project_id是唯一必填项location标记为高级参数。连接后会在__init__中打印Successfully connected to BigQuery project: {project_id}日志。7.2 三种认证方式auth_instructionsauth_instructions()bigquery_data_loader.py为使用者提供了三种可选的认证途径Option 1 — Application Default Credentials推荐安装 Google Cloud SDK 后执行gcloud auth application-default logincredentials_path留空Option 2 — 服务账号密钥文件在 Google Cloud Console 创建服务账号并下载 JSON 密钥将完整路径填入credentials_path并为该账号授予BigQuery Data Viewer与BigQuery Job User角色Option 3 — 环境变量设置GOOGLE_APPLICATION_CREDENTIALS指向服务账号 JSON 路径credentials_path留空。对应地__init__中当credentials_path非空时用service_account.Credentials.from_service_account_file构造客户端否则走默认凭据ADC。7.3 表枚举与目录树浏览list_tables()bigquery_data_loader.py是**扁平/急切flat/eager**的枚举接口列出项目下数据集max_results50未指定dataset_id时仅遍历前 10 个数据集每个数据集内最多枚举 20 张表总结果上限 100 张达到即提前返回支持大小写不敏感的名称过滤table_filter为每张表附带row_count、columns含类型与可选的列描述等元数据schema 读取失败的表降级为{columns: []}不影响整体枚举。同时加载器实现了**惰性/分层lazy/hierarchical**的目录树浏览Catalog tree APIcatalog_hierarchy()bigquery_data_loader.py声明了三级结构[ {key: project_id, label: Project}, {key: dataset_id, label: Dataset}, {key: table, label: Table}, ]project_id因是必填参数而始终被钉住pinned即前端浏览时自动隐藏该层ls(path)按层级懒加载 dataset 列表≤200 个与表列表≤500 张并支持子串过滤get_metadata(path)返回单表的列、行数、表描述等详情。这与基类 external_data_loader.py 中描述的 list_tables扁平与 ls分层长期并存 的目录树模型完全一致。7.4 取数原生 Arrow 传输fetch_data_as_arrowfetch_data_as_arrow()bigquery_data_loader.py是性能关键路径代码注释明确说明其设计意图BigQuerys Python client provides.to_arrow()for efficient Arrow-native data transfer, avoiding pandas conversion overhead.实现要点import_options支持size行数上限被MAX_IMPORT_ROWS 2_000_000封顶常量定义于 external_data_loader.py、sort_columns、sort_orderasc/desc通过get_table读取 schema 后用_build_select_parts()处理嵌套 RECORD 字段递归展开非 REPEATED 的 RECORD将子字段拍平成带别名的列点号转为下划线、去非法字符、自动去重别名生成形如SELECT table.field AS alias ... FROM project.dataset.table ORDER BY ... LIMIT n的标准 SQL 交给client.query()执行随后直接query_job.to_arrow()返回 PyArrow 表全程无 pandas 转换。7.5 Agent 探测SPJQ 编译为 BigQuery SQLprobe()bigquery_data_loader.py用于让数据分析 Agent 在源端执行有界的单表 SPJQ 读取projection / filter / group-by / aggregate / order / limit将整段project.dataset.table作为一个整体标识符用反引号引用quote_ident(src, probe_utils.BIGQUERY)对应 BigQuery 的反引号语法通过probe_utils.probe_via_native_sql(query, relation..., dialectBIGQUERY, executelambda sql: self.client.query(sql).to_arrow())在服务端编译并执行见 probe_utils.py 中的BIGQUERY方言与probe_via_native_sql返回{rows, columns, row_count, exact, compiled_note}或{error}。7.6 摄取写入 workspace 的 parquetingest_to_workspace()是基类 external_data_loader.py 的通用实现对所有加载器统一生效核心流程为调用各加载器的fetch_data_as_arrow()取得 Arrow 表构造source_info加载器类型、脱敏后的连接参数、源表名、导入选项敏感参数如password、token通过get_safe_params()过滤由workspace.write_parquet_from_arrow()写入 parquet 并返回TableMetadata尽力而为的元数据富化优先使用调用方传入的source_metadata来自已同步的目录缓存否则回退到get_column_types()实时拉取把表/列描述合并进持久化元数据元数据失败绝不阻断导入。表名在写入前会经过净化sanitize测试test_ingest_sanitizes_table_name验证了test-table-with-dashes这类带连字符的名称会被处理为小写加下划线的合法表名调用链为sanitize_table_name→sanitize_external_loader_table_name见 external_data_loader.py。八、模拟器容器与初始化数据详解8.1 Dockerfile 与端口Dockerfile 基于ghcr.io/goccy/bigquery-emulator:latest构建将init_data.yaml拷入容器根目录暴露9050HTTP/REST与9060gRPC两个端口启动命令固定项目与初始化数据ENTRYPOINT [bigquery-emulator, --projecttest-project, --data-from-yaml/init_data.yaml]docker-compose.yml 将容器命名为df-test-bigquery端口可用BQ_PORT/BQ_GRPC_PORT覆盖并配置了健康检查wget -q --spider http://localhost:9050注意其|| exit 0使检查恒通过主要作用是配合--wait等待容器就绪。8.2 初始化数据集init_data.yamlinit_data.yaml 定义了模拟器预置的两个数据集sampleproducts10 条商品数据、customers5 条客户、orders5 条订单、nested_events含 RECORD 嵌套结构user_info.preferences.theme等专门用于验证嵌套字段展开逻辑analyticspage_views5 条页面访问记录。文件头部有一段极其重要的兼容性注释时间戳类字段必须用 STRINGISO8601而不是 TIMESTAMP 类型因为 goccy bigquery-emulator 会把 TIMESTAMP 序列化成浮点字符串如1704103200.0而 Google BigQuery Python 客户端会执行int(value)转换从而报错。这是模拟器与官方客户端之间一个真实存在的兼容性坑使用 goccy emulator 时务必遵循这一约定。九、常见问题与排错测试被跳过skippedTestBigQueryDataLoader依赖bq_emulator_available()的 TCP 探测模拟器未启动时会整体 skip。请先执行docker compose -f tests/database-dockers/bigquery/docker-compose.yml up -d或等价脚本再运行测试。TIMESTAMP 解析报错模拟器数据中时间字段类型须用 STRING参考 init_data.yaml 的注释说明。端口冲突9050/9060被占用时通过BQ_PORT、BQ_GRPC_PORT覆盖同时设置BQ_HTTP_ENDPOINT指向新端口。想重置为全新数据使用reset bigquery命令或docker compose down后重新up让模拟器重新加载init_data.yaml。十、小结该测试目录是理解 Data Formulator 数据连接器体系的一个缩影它以真实运行的模拟器为验证环境覆盖了连接器从参数声明 → 认证 → 枚举 → Arrow 取数 → Parquet 摄取 → 元数据管理的完整生命周期并严格对齐 workspace/datalake 架构External Source → Arrow → Parquet不落 DuckDB。对开发者而言既可以照此流程在本地快速验证 BigQuery 接入也可以将其作为模板为其他云数据源编写同等深度的集成测试。【免费下载链接】data-formulator Data Formulator is an interactive AI-powered data analysis system makes it easy to connect, explore and visualize data.项目地址: https://gitcode.com/GitHub_Trending/da/data-formulator创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表