googleapis / googleapis/google-cloud-python
feat(dbapi): wire timeout parameter through to execute_sql in cursor and connection
- 主要言語
- Python
- スター
- 5.4k
- フォーク
- 1.8k
- 平均マージ
- 3日 4時間
- マージ済み PR(30日)
- 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 はまだ評価されていません。