googleapis / googleapis/google-cloud-python

feat(dbapi): wire timeout parameter through to execute_sql in cursor and connection

未关闭
#16,492 2 条评论 0 个 reaction 已指派 1 人 已指派给 @sinhasubham 在 GitHub 查看
api: spanner
主要语言
Python
星标
5.4k
派生
1.8k
平均合并
3 天 4 小时
30 天内合并 PR
122

描述

## Summary

The Spanner DBAPI layer (`spanner_dbapi`) does not pass the `timeout` parameter when calling `_SnapshotBase.execute_sql()`, causing all queries to use the gRPC default timeout of 3600 seconds. This prevents DBAPI consumers (SQLAlchemy, Django, raw DBAPI) from controlling per-statement gRPC deadlines.

## Background

`_SnapshotBase.execute_sql()` accepts a `timeout` parameter that controls the gRPC deadline for the `ExecuteStreamingSql` RPC. When not provided, it defaults to `gapic_v1.method.DEFAULT`, which resolves to `default_timeout=3600.0` in the gRPC transport layer.

The DBAPI calls `execute_sql()` in three code paths, none of which pass `timeout=`:

1. **Snapshot reads** — [`cursor._handle_DQL_with_snapshot()`](https://github.com/googleapis/python-spanner/blob/e5638ab932d0b4281f02ffb72533250433c43fef/google/cloud/spanner_dbapi/cursor.py#L543-L549) calls `snapshot.execute_sql(sql, params, param_types, request_options=...)` without `timeout=`
2. **Transaction reads/writes** — [`connection.run_statement()`](https://github.com/googleapis/python-spanner/blob/e5638ab932d0b4281f02ffb72533250433c43fef/google/cloud/spanner_dbapi/connection.py#L561-L567) calls `transaction.execute_sql(sql, params, param_types=..., request_options=...)` without `timeout=`
3. **Autocommit DML** — [`cursor._do_execute_update_in_autocommit()`](https://github.com/googleapis/python-spanner/blob/e5638ab932d0b4281f02ffb72533250433c43fef/google/cloud/spanner_dbapi/cursor.py#L226-L237) calls `transaction.execute_sql(sql, params=..., param_types=..., last_statement=True)` without `timeout=`

Since `timeout` defaults to `gapic_v1.method.DEFAULT`, which resolves to `default_timeout=3600.0` in the gRPC transport layer (`services/spanner/transports/base.py`), all DBAPI queries have a 3600-second gRPC deadline regardless of the caller's intent.

## Timeline

| Date | Commit | Event |
|---|---|---|
| Nov 2018 | `9b7fcd6` ([PR #6536](https://github.com/googleapis/google-cloud-python/pull/6536)) | `timeout=` added to `_SnapshotBase.execute_sql()` |
| Nov 2020 | `2493fa1` (PR googleapis/python-spanner#160) | DBAPI created — calls `execute_sql()` without `timeout=` |
| Nov 2020 | `d59d502` (PR googleapis/python-spanner#168) | `run_statement()` added — calls `execute_sql()` without `timeout=` |
| Mar 2021 | `1a7c9d2` (PR googleapis/python-spanner#278) | `timeout=` expanded to `read()`, `partition_read()`, `partition_query()` |
| Oct 2021 | `cd3b950` (PR googleapis/python-spanner#475) | `_handle_DQL_with_snapshot()` extracted — still no `timeout=` |
| Oct 2022 | `ab768e4` (PR googleapis/python-spanner#838) | `request_options` added to `_handle_DQL_with_snapshot()` — `timeout=` not added |
| Jan 2025 | `ee9662f` (PR googleapis/python-spanner#1262) | `request_tag`/`transaction_tag` added — `timeout=` not added |

Other execution parameters (`request_options`, `request_priority`, `transaction_tag`, `request_tag`) were each wired through incrementally via the same pattern: a `Connection` property plus pass-through in cursor methods. The `timeout` parameter was not included in any of these additions.

## Proposed Change

Add a `timeout` property to `Connection`, following the same pattern used for `staleness`, `read_only`, and `request_priority`. Pass `timeout=` in the three cursor/connection methods that call `execute_sql()`.

### Files changed

1. **`connection.py`** — Add `self._timeout = None` to `__init__`, add `timeout` property/setter, pass `timeout=` in `run_statement()`
2. **`cursor.py`** — Pass `timeout=` in `_handle_DQL_with_snapshot()` and `_do_execute_update_in_autocommit()`

### Usage

```python
from google.cloud.spanner_dbapi import connect

conn = connect(instance_id, database_id, project=project)
conn.timeout = 60 # 60-second gRPC deadline for subsequent statements

cursor = conn.cursor()
cursor.execute("SELECT * FROM my_table")
```

This also enables framework integration. The companion change in `python-spanner-sqlalchemy` can wire this through `execution_options`:

```python
engine.execution_options(timeout=60)
```

## Related

- Google's own samples demonstrate `timeout=` on `execute_sql()`: PR googleapis/python-spanner#1380 (`spanner_set_statement_timeout` sample), PR googleapis/python-spanner#1107 (`spanner_set_custom_timeout_and_retry` sample)
- Companion SQLAlchemy dialect change: googleapis/google-cloud-python#16467

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。