googleapis / googleapis/google-cloud-python

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

Đang mở
#16,492 2 bình luận 0 reaction 1 người được giao Được giao cho @sinhasubham Xem trên GitHub
api: spanner
Ngôn ngữ chính
Python
Star
5.4k
Fork
1.8k
Merge trung bình
3 ngày 4 giờ
Pull request đã merge (30 ngày)
122

Mô tả

## 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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.