Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/scripts/update_python_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from typing import Any
from typing import Callable

# From https://github.com/python/devguide/blob/8996911bc6f85f589efc3dc0111ba19351271553/_tools/generate_release_cycle.py#L44
# From https://github.com/python/devguide/blob/8996911bc6f85f589efc3dc0111ba19351271553/_tools/generate_release_cycle.py#L44 # noqa: E501
RELEASE_CYCLE_URL = (
"https://peps.python.org/api/release-cycle.json"
)
Expand Down
67 changes: 67 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# CLAUDE.md

## Common Commands

```bash
# Setup
python3 -m venv .venv && . .venv/bin/activate
pip install -e '.[tests]'

# Run unit tests
pytest tests/unit

# Run a single test
pytest tests/unit/test_client.py::test_name -sv

# Run integration tests (starts a Trino Docker container automatically, or uses an existing one on localhost:8080)
pytest tests/integration

# Start a standalone Trino (+ LocalStack) dev server for manual testing, without pytest
python tests/development_server.py

# Lint and format checks
pre-commit run --all-files

# Type checking only
mypy trino/

# Run tests across all supported Python versions
tox
```

## Architecture

### Package layout (`trino/`)

- **`client.py`** - Core HTTP protocol implementation. `TrinoRequest` manages HTTP requests to the coordinator, `TrinoQuery` manages query lifecycle (submit -> poll -> fetch results), `ClientSession` holds connection state (headers, session properties, transaction ID). Implements the protocol described at https://github.com/trinodb/trino/wiki/HTTP-Protocol.
- **`dbapi.py`** - PEP 249 DBAPI 2.0 interface. `Connection` and `Cursor` classes that wrap `client.py`. Entry point is `trino.dbapi.connect()`.
- **`sqlalchemy/`** - SQLAlchemy dialect (`TrinoDialect`), compiler, and type mapping. Registered as `trino://` via `sqlalchemy.dialects` entry point. Compatible with SQLAlchemy 1.3.x, 1.4.x, and 2.0.x.
- **`auth.py`** - Authentication implementations (Basic, JWT, OAuth2, Kerberos/GSSAPI, Certificate).
- **`types.py`** - Python representations of Trino SQL types.
- **`mapper.py`** - Conversion between Trino wire types and Python objects.
- **`transaction.py`** - Transaction and isolation level management.
- **`exceptions.py`** - Exception hierarchy following DBAPI 2.0 spec.
- **`constants.py`** - Protocol header names and type metadata constants.
- **`logging.py`** - Logger factory used across the package.

### Tests (`tests/`)

- `tests/unit/` - No external dependencies needed. Uses `httpretty` for HTTP mocking.
- `tests/integration/` - Requires Docker. Tests automatically pull `trinodb/trino` image and start a container (or reuse one on port 8080).
- Env vars: `TRINO_VERSION` (image tag, default `latest`), `TRINO_RUNNING_HOST`/`TRINO_RUNNING_PORT` (use an existing server instead of starting one).
- When `TRINO_VERSION` is `latest` or >= 466, a LocalStack container with an S3 `spooling` bucket is also started to test the spooled client protocol; older versions use the `etc/*-pre-466*` configs.

### Key dependencies

- `requests` - HTTP transport
- `orjson` (CPython) / `json` (PyPy) - JSON parsing, selected at import time in `client.py`
- `lz4`, `zstandard` - Response decompression
- `python-dateutil`, `pytz`, `tzlocal` - Timezone/datetime handling

## Code Style

- **Max line length**: 120 characters (flake8)
- **Import ordering**: managed by `reorder-python-imports` (Python 3.9+ style)
- **Type checking**: mypy with strict settings, though `tests/*`, `trino/client`, `trino/dbapi`, and `trino/sqlalchemy.*` have `ignore_errors = true`
- **No mocking libraries**: write mocks by hand instead of using `unittest.mock` or similar. The project uses `httpretty` for HTTP-level stubbing only.
- **Pre-commit hooks**: flake8, mypy, reorder-python-imports, trailing whitespace, EOF newlines, YAML syntax, case-conflict checks
11 changes: 11 additions & 0 deletions tests/unit/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from httpretty import httprettified
from requests import Session

import trino.exceptions
from tests.unit.oauth_test_utils import _get_token_requests
from tests.unit.oauth_test_utils import _post_statement_requests
from tests.unit.oauth_test_utils import GetTokenCallback
Expand All @@ -27,6 +28,7 @@
from tests.unit.oauth_test_utils import SERVER_ADDRESS
from tests.unit.oauth_test_utils import TOKEN_RESOURCE
from trino import constants
from trino.auth import BasicAuthentication
from trino.auth import OAuth2Authentication
from trino.dbapi import connect
from trino.dbapi import Connection
Expand Down Expand Up @@ -362,3 +364,12 @@ def test_default_encoding_zstd():
def test_default_encoding_all():
connection = Connection("host", 8080, user="test")
assert connection._client_session.encoding == ["json+zstd", "json+lz4", "json"]


def test_error_when_auth_over_http():
with pytest.raises(trino.exceptions.TrinoAuthError, match="TLS/SSL is required for authentication"):
Connection("mytrinoserver.domain", http_scheme=constants.HTTP, auth=BasicAuthentication("u", "p"))


def test_no_error_when_auth_over_https():
Connection("mytrinoserver.domain", http_scheme=constants.HTTPS, auth=BasicAuthentication("u", "p"))
7 changes: 7 additions & 0 deletions trino/dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ def __init__(
else:
self.http_scheme = constants.HTTP

if auth is not None and self.http_scheme == constants.HTTP:
raise trino.exceptions.TrinoAuthError(
"TLS/SSL is required for authentication. "
"To use HTTPS, specify 'https://' in the host URL (which takes precedence "
"over http_scheme), or, if the host URL has no scheme, pass http_scheme='https'."
)
Comment thread
hashhar marked this conversation as resolved.

# Infer connection port: `hostname` takes precedence over explicit `port` argument
# If none is given, use default based on HTTP protocol
default_port = constants.DEFAULT_TLS_PORT if self.http_scheme == constants.HTTPS else constants.DEFAULT_PORT
Expand Down
Loading