Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
13 changes: 8 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
version: "latest"

- name: Setup Python
run: uv python install 3.12
run: uv python install 3.9

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -44,7 +44,7 @@ jobs:
version: "latest"

- name: Setup Python
run: uv python install 3.12
run: uv python install 3.9

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -53,8 +53,11 @@ jobs:
run: uv run ty check drift/ tests/

test:
name: Unit Tests
name: Unit Tests (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.14"]
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -65,7 +68,7 @@ jobs:
version: "latest"

- name: Setup Python
run: uv python install 3.12
run: uv python install ${{ matrix.python-version }}

- name: Install dependencies
run: uv sync --all-extras
Expand All @@ -86,7 +89,7 @@ jobs:
version: "latest"

- name: Setup Python
run: uv python install 3.12
run: uv python install 3.9

- name: Install dependencies
run: uv sync --all-extras
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
version: "latest"

- name: Setup Python
run: uv python install 3.12
run: uv python install 3.9

- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ For comprehensive guides and API reference, visit our [full documentation](https

## Requirements

- Python 3.12+
- Python 3.9+

Tusk Drift currently supports the following packages and versions:

Expand Down
5 changes: 4 additions & 1 deletion drift/core/resilience.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from enum import Enum
from typing import TypeVar

T = TypeVar("T")

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -51,7 +54,7 @@ def calculate_backoff_delay(
return delay


async def retry_async[T](
async def retry_async(
operation: Callable[[], Awaitable[T]],
config: RetryConfig | None = None,
retryable_exceptions: tuple[type[Exception], ...] = (Exception,),
Expand Down
4 changes: 2 additions & 2 deletions drift/core/span_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta, timezone
from typing import Any

from betterproto.lib.google.protobuf import Struct as ProtoStruct
Expand Down Expand Up @@ -114,7 +114,7 @@ def clean_span_to_proto(span: CleanSpanData) -> ProtoSpan:
is_root_span=span.is_root_span,
timestamp=datetime.fromtimestamp(
span.timestamp.seconds + span.timestamp.nanos / 1_000_000_000,
tz=UTC,
tz=timezone.utc,
),
duration=timedelta(
seconds=span.duration.seconds,
Expand Down
8 changes: 5 additions & 3 deletions drift/core/tracing/adapters/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
import gzip
import logging
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, Any, override
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Any

from typing_extensions import override

from ...resilience import (
CircuitBreaker,
Expand Down Expand Up @@ -270,7 +272,7 @@ def _transform_span_to_protobuf(self, clean_span: CleanSpanData) -> Any:

timestamp = datetime.fromtimestamp(
clean_span.timestamp.seconds + clean_span.timestamp.nanos / 1_000_000_000,
tz=UTC,
tz=timezone.utc,
)

duration = timedelta(
Expand Down
8 changes: 5 additions & 3 deletions drift/core/tracing/adapters/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import logging
from collections import OrderedDict
from dataclasses import asdict
from datetime import UTC, datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING, Any, override
from typing import TYPE_CHECKING, Any

from typing_extensions import override

from .base import ExportResult, SpanExportAdapter

Expand Down Expand Up @@ -63,7 +65,7 @@ def _get_or_create_file_path(self, trace_id: str) -> Path:
return self._trace_file_map[trace_id]

# Create new file with timestamp prefix
iso_timestamp = datetime.now(UTC).isoformat().replace(":", "-").replace(".", "-")
iso_timestamp = datetime.now(timezone.utc).isoformat().replace(":", "-").replace(".", "-")
file_path = self._base_directory / f"{iso_timestamp}_trace_{trace_id}.jsonl"
self._trace_file_map[trace_id] = file_path

Expand Down
4 changes: 3 additions & 1 deletion drift/core/tracing/adapters/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from __future__ import annotations

from typing import TYPE_CHECKING, override
from typing import TYPE_CHECKING

from typing_extensions import override

from .base import ExportResult, SpanExportAdapter

Expand Down
2 changes: 2 additions & 0 deletions drift/instrumentation/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from types import ModuleType

Expand Down
4 changes: 2 additions & 2 deletions drift/instrumentation/datetime/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from __future__ import annotations

import logging
from datetime import UTC, datetime
from datetime import datetime, timezone

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -45,7 +45,7 @@ def start_time_travel(timestamp: datetime | str | int | float, trace_id: str | N

# Parse timestamp to datetime if needed
if isinstance(timestamp, (int, float)):
dt = datetime.fromtimestamp(timestamp, tz=UTC)
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
elif isinstance(timestamp, str):
ts = timestamp.replace("Z", "+00:00")
dt = datetime.fromisoformat(ts)
Expand Down
2 changes: 1 addition & 1 deletion drift/instrumentation/django/e2e-tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-e /sdk # Mount point for drift SDK
Django>=5.0
Django>=4.2
requests>=2.32.5
gunicorn>=22.0.0

4 changes: 3 additions & 1 deletion drift/instrumentation/django/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import logging
from types import ModuleType
from typing import TYPE_CHECKING, Any, override
from typing import TYPE_CHECKING, Any

from typing_extensions import override

logger = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions drift/instrumentation/e2e_common/Dockerfile.base
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Python E2E Test Base Image
#
# This base image contains:
# - Python 3.12
# - Python 3.9 (minimum supported version)
# - Tusk CLI (for running replay tests)
# - System utilities (curl, postgresql-client)
#
# Build this image before running e2e tests:
# docker build -t python-e2e-base:latest -f drift/instrumentation/e2e-common/Dockerfile.base .

FROM python:3.12-slim
FROM python:3.9-slim

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
Expand Down
2 changes: 2 additions & 0 deletions drift/instrumentation/e2e_common/base_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
customize only the setup phase.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
"""

from __future__ import annotations

import json
import os
import signal
Expand Down
4 changes: 3 additions & 1 deletion drift/instrumentation/fastapi/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from collections.abc import Callable
from functools import wraps
from types import ModuleType
from typing import TYPE_CHECKING, Any, override
from typing import TYPE_CHECKING, Any

from typing_extensions import override

logger = logging.getLogger(__name__)

Expand Down
4 changes: 3 additions & 1 deletion drift/instrumentation/flask/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import logging
from collections.abc import Iterable
from types import ModuleType
from typing import TYPE_CHECKING, Any, override
from typing import TYPE_CHECKING, Any

from typing_extensions import override

logger = logging.getLogger(__name__)

Expand Down
8 changes: 4 additions & 4 deletions drift/instrumentation/psycopg/e2e-tests/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def db_query():
cur.execute("SELECT * FROM users ORDER BY id LIMIT 10")
Comment thread
jy-tan marked this conversation as resolved.
rows = cur.fetchall()
columns = [desc[0] for desc in cur.description]
results = [dict(zip(columns, row, strict=False)) for row in rows]
results = [dict(zip(columns, row)) for row in rows]

return jsonify({"count": len(results), "data": results})
except Exception as e:
Expand All @@ -63,7 +63,7 @@ def db_insert():
)
row = cur.fetchone()
columns = [desc[0] for desc in cur.description]
user = dict(zip(columns, row, strict=False))
user = dict(zip(columns, row))
conn.commit()

return jsonify(user), 201
Expand All @@ -83,7 +83,7 @@ def db_update(user_id):
row = cur.fetchone()
if row:
columns = [desc[0] for desc in cur.description]
user = dict(zip(columns, row, strict=False))
user = dict(zip(columns, row))
conn.commit()
return jsonify(user)
else:
Expand Down Expand Up @@ -177,7 +177,7 @@ def test_server_cursor():
cur.execute("SELECT id, name, email FROM users ORDER BY id LIMIT 5")
rows = cur.fetchall()
columns = [desc[0] for desc in cur.description] if cur.description else ["id", "name", "email"]
results = [dict(zip(columns, row, strict=False)) for row in rows]
results = [dict(zip(columns, row)) for row in rows]
return jsonify({"count": len(results), "data": results})
except Exception as e:
return jsonify({"error": str(e)}), 500
Expand Down
4 changes: 2 additions & 2 deletions drift/instrumentation/psycopg/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1110,7 +1110,7 @@ def transform_row(row):
return row[0] if isinstance(row, list) and len(row) > 0 else row
values = tuple(row) if isinstance(row, list) else row
if row_factory_type == "dict" and column_names:
return dict(zip(column_names, values, strict=False))
return dict(zip(column_names, values))
elif row_factory_type in ("namedtuple", "class") and RowClass is not None:
return RowClass(*values)
return values
Expand Down Expand Up @@ -1396,7 +1396,7 @@ def transform_row(row, col_names, RowClass):
return row
values = tuple(row) if isinstance(row, list) else row
if row_factory_type == "dict" and col_names:
return dict(zip(col_names, values, strict=False))
return dict(zip(col_names, values))
elif row_factory_type == "namedtuple" and RowClass is not None:
return RowClass(*values)
return values
Expand Down
13 changes: 10 additions & 3 deletions drift/instrumentation/psycopg2/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import json
import logging
from types import ModuleType
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, Union

if TYPE_CHECKING:
from psycopg2.extensions import cursor as BaseCursorType
from psycopg2.sql import Composable

QueryType = str | bytes | Composable
QueryType = Union[str, bytes, Composable]

from opentelemetry import trace
from opentelemetry.trace import SpanKind as OTelSpanKind
Expand Down Expand Up @@ -752,7 +752,14 @@ def _mock_execute_with_data(self, cursor: Any, mock_data: dict[str, Any]) -> Non
# If it's a dict cursor and we have description, convert rows to dicts
if is_dict_cursor and description_data:
column_names = [col["name"] for col in description_data]
mock_rows = [dict(zip(column_names, row, strict=True)) for row in mock_rows]
converted_rows = []
for row in mock_rows:
if len(column_names) != len(row):
raise ValueError(
f"Column count mismatch: {len(column_names)} columns but row has {len(row)} values"
)
converted_rows.append(dict(zip(column_names, row)))
mock_rows = converted_rows

cursor._mock_rows = mock_rows # pyright: ignore[reportAttributeAccessIssue]
cursor._mock_index = 0 # pyright: ignore[reportAttributeAccessIssue]
Expand Down
5 changes: 4 additions & 1 deletion drift/instrumentation/registry.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import importlib.abc
import importlib.machinery
import sys
from collections.abc import Callable, Sequence
from types import ModuleType
from typing import override

from typing_extensions import override

PatchFn = Callable[[ModuleType], None]

Expand Down
4 changes: 3 additions & 1 deletion drift/instrumentation/wsgi/instrumentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import logging
from types import ModuleType
from typing import TYPE_CHECKING, Any, override
from typing import TYPE_CHECKING, Any

from typing_extensions import override

if TYPE_CHECKING:
from _typeshed.wsgi import WSGIApplication
Expand Down
Loading
Loading