Skip to content

Commit 8a248bd

Browse files
feat: add Python 3.10 support
- Lower requires-python from >=3.11 to >=3.10 (aligns with pydantic-ai) - Add 3.10 to CI test matrix (3.10, 3.11, 3.12, 3.13) - Replace datetime.UTC (3.11+) with datetime.timezone.utc (all versions) across 28 files in src/ and tests/ - Use asyncio.TimeoutError with noqa for ruff UP041 compat - Update ruff target-version to py310 - Update README, classifiers, and docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b441d3e commit 8a248bd

28 files changed

Lines changed: 142 additions & 122 deletions

src/chat_sdk/adapters/discord/adapter.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import os
1414
import re
1515
from contextvars import ContextVar
16-
from datetime import UTC, datetime
16+
from datetime import datetime, timezone
1717
from typing import Any
1818
from urllib.parse import quote
1919

@@ -590,7 +590,7 @@ async def _handle_forwarded_message(
590590
metadata=MessageMetadata(
591591
date_sent=datetime.fromisoformat(data.get("timestamp", ""))
592592
if data.get("timestamp")
593-
else datetime.now(UTC),
593+
else datetime.now(timezone.utc),
594594
edited=False,
595595
),
596596
attachments=[
@@ -1239,7 +1239,9 @@ def _parse_discord_message(self, raw: dict[str, Any], thread_id: str) -> Message
12391239
is_me=is_me,
12401240
),
12411241
metadata=MessageMetadata(
1242-
date_sent=datetime.fromisoformat(msg["timestamp"]) if msg.get("timestamp") else datetime.now(UTC),
1242+
date_sent=datetime.fromisoformat(msg["timestamp"])
1243+
if msg.get("timestamp")
1244+
else datetime.now(timezone.utc),
12431245
edited=msg.get("edited_timestamp") is not None,
12441246
edited_at=datetime.fromisoformat(msg["edited_timestamp"]) if msg.get("edited_timestamp") else None,
12451247
),
@@ -1284,7 +1286,7 @@ async def _create_discord_thread(
12841286
message_id: str,
12851287
) -> dict[str, str]:
12861288
"""Create a Discord thread from a message."""
1287-
thread_name = f"Thread {datetime.now(UTC).isoformat()}"
1289+
thread_name = f"Thread {datetime.now(timezone.utc).isoformat()}"
12881290

12891291
self._logger.debug(
12901292
"Discord API: POST thread",

src/chat_sdk/adapters/github/adapter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import re
1717
import time
1818
from collections.abc import AsyncIterable
19-
from datetime import UTC, datetime
19+
from datetime import datetime, timezone
2020
from typing import Any
2121

2222
from chat_sdk.adapters.github.cards import card_to_github_markdown
@@ -386,7 +386,7 @@ def _parse_issue_comment(
386386
metadata=MessageMetadata(
387387
date_sent=datetime.fromisoformat(created_at.replace("Z", "+00:00"))
388388
if created_at
389-
else datetime.now(tz=UTC),
389+
else datetime.now(tz=timezone.utc),
390390
edited=edited,
391391
edited_at=datetime.fromisoformat(updated_at.replace("Z", "+00:00")) if edited and updated_at else None,
392392
),
@@ -428,7 +428,7 @@ def _parse_review_comment(
428428
metadata=MessageMetadata(
429429
date_sent=datetime.fromisoformat(created_at.replace("Z", "+00:00"))
430430
if created_at
431-
else datetime.now(tz=UTC),
431+
else datetime.now(tz=timezone.utc),
432432
edited=edited,
433433
edited_at=datetime.fromisoformat(updated_at.replace("Z", "+00:00")) if edited and updated_at else None,
434434
),
@@ -787,7 +787,7 @@ async def list_threads(
787787
metadata=MessageMetadata(
788788
date_sent=datetime.fromisoformat(pr.get("created_at", "").replace("Z", "+00:00"))
789789
if pr.get("created_at")
790-
else datetime.now(tz=UTC),
790+
else datetime.now(tz=timezone.utc),
791791
edited=pr.get("created_at") != pr.get("updated_at"),
792792
),
793793
)

src/chat_sdk/adapters/google_chat/adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import re
1717
import time
1818
from collections.abc import AsyncIterable, Awaitable, Callable
19-
from datetime import UTC, datetime
19+
from datetime import datetime, timezone
2020
from typing import Any
2121

2222
from chat_sdk.adapters.google_chat.cards import card_to_google_card
@@ -2727,9 +2727,9 @@ def _parse_message_metadata(message: dict[str, Any]) -> Any:
27272727
try:
27282728
date_sent = datetime.fromisoformat(create_time.replace("Z", "+00:00"))
27292729
except (ValueError, AttributeError):
2730-
date_sent = datetime.now(tz=UTC)
2730+
date_sent = datetime.now(tz=timezone.utc)
27312731
else:
2732-
date_sent = datetime.now(tz=UTC)
2732+
date_sent = datetime.now(tz=timezone.utc)
27332733

27342734
return MessageMetadata(
27352735
date_sent=date_sent,

src/chat_sdk/adapters/linear/adapter.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import os
1616
import re
1717
import time
18-
from datetime import UTC, datetime
18+
from datetime import datetime, timezone
1919
from typing import Any
2020

2121
from chat_sdk.adapters.linear.cards import card_to_linear_markdown
@@ -415,7 +415,7 @@ def _build_message(
415415
raw=LinearRawMessage(comment=comment),
416416
author=author,
417417
metadata=MessageMetadata(
418-
date_sent=datetime.fromisoformat(created_at) if created_at else datetime.now(UTC),
418+
date_sent=datetime.fromisoformat(created_at) if created_at else datetime.now(timezone.utc),
419419
edited=created_at != updated_at,
420420
edited_at=datetime.fromisoformat(updated_at) if (created_at != updated_at and updated_at) else None,
421421
),
@@ -749,7 +749,9 @@ def _comment_node_to_message(
749749
is_me=user_id == self._bot_user_id,
750750
),
751751
metadata=MessageMetadata(
752-
date_sent=datetime.fromisoformat(node["createdAt"]) if node.get("createdAt") else datetime.now(UTC),
752+
date_sent=datetime.fromisoformat(node["createdAt"])
753+
if node.get("createdAt")
754+
else datetime.now(timezone.utc),
753755
edited=node.get("createdAt") != node.get("updatedAt"),
754756
edited_at=(
755757
datetime.fromisoformat(node["updatedAt"])
@@ -852,7 +854,7 @@ def parse_message(self, raw: LinearRawMessage) -> Message:
852854
is_me=user_id == self._bot_user_id,
853855
),
854856
metadata=MessageMetadata(
855-
date_sent=(datetime.fromisoformat(created_at) if created_at else datetime.now(UTC)),
857+
date_sent=(datetime.fromisoformat(created_at) if created_at else datetime.now(timezone.utc)),
856858
edited=created_at != updated_at,
857859
edited_at=(datetime.fromisoformat(updated_at) if created_at != updated_at and updated_at else None),
858860
),

src/chat_sdk/adapters/slack/adapter.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from collections import OrderedDict
2121
from collections.abc import AsyncIterable, Awaitable, Callable
2222
from contextvars import ContextVar
23-
from datetime import UTC, datetime
23+
from datetime import datetime, timezone
2424
from typing import Any
2525
from urllib.parse import parse_qs
2626

@@ -1574,14 +1574,14 @@ async def _parse_slack_message(
15741574

15751575
ts_str = event.get("ts", "0")
15761576
try:
1577-
date_sent = datetime.fromtimestamp(float(ts_str), tz=UTC)
1577+
date_sent = datetime.fromtimestamp(float(ts_str), tz=timezone.utc)
15781578
except (ValueError, TypeError, OSError):
1579-
date_sent = datetime.now(tz=UTC)
1579+
date_sent = datetime.now(tz=timezone.utc)
15801580

15811581
edited_at: datetime | None = None
15821582
if event.get("edited"):
15831583
try:
1584-
edited_at = datetime.fromtimestamp(float(event["edited"].get("ts", "0")), tz=UTC)
1584+
edited_at = datetime.fromtimestamp(float(event["edited"].get("ts", "0")), tz=timezone.utc)
15851585
except (ValueError, TypeError, OSError):
15861586
edited_at = None
15871587

@@ -1616,14 +1616,14 @@ def _parse_slack_message_sync(self, event: dict[str, Any], thread_id: str) -> Me
16161616

16171617
ts_str = event.get("ts", "0")
16181618
try:
1619-
date_sent = datetime.fromtimestamp(float(ts_str), tz=UTC)
1619+
date_sent = datetime.fromtimestamp(float(ts_str), tz=timezone.utc)
16201620
except (ValueError, TypeError, OSError):
1621-
date_sent = datetime.now(tz=UTC)
1621+
date_sent = datetime.now(tz=timezone.utc)
16221622

16231623
edited_at: datetime | None = None
16241624
if event.get("edited"):
16251625
try:
1626-
edited_at = datetime.fromtimestamp(float(event["edited"].get("ts", "0")), tz=UTC)
1626+
edited_at = datetime.fromtimestamp(float(event["edited"].get("ts", "0")), tz=timezone.utc)
16271627
except (ValueError, TypeError, OSError):
16281628
edited_at = None
16291629

@@ -2585,7 +2585,7 @@ async def list_threads(self, channel_id: str, options: ListThreadsOptions | None
25852585
last_reply_at: datetime | None = None
25862586
if msg.get("latest_reply"):
25872587
try:
2588-
last_reply_at = datetime.fromtimestamp(float(msg["latest_reply"]), tz=UTC)
2588+
last_reply_at = datetime.fromtimestamp(float(msg["latest_reply"]), tz=timezone.utc)
25892589
except (ValueError, TypeError, OSError):
25902590
last_reply_at = None
25912591

src/chat_sdk/adapters/teams/adapter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import json
1414
import os
1515
import re
16-
from datetime import UTC, datetime
16+
from datetime import datetime, timezone
1717
from typing import Any, NoReturn
1818

1919
from chat_sdk.adapters.teams.cards import card_to_adaptive_card
@@ -529,7 +529,7 @@ def _parse_teams_message(
529529
metadata=MessageMetadata(
530530
date_sent=datetime.fromisoformat(activity["timestamp"])
531531
if activity.get("timestamp")
532-
else datetime.now(UTC),
532+
else datetime.now(timezone.utc),
533533
edited=False,
534534
),
535535
attachments=attachments,
@@ -1430,7 +1430,9 @@ def _map_graph_message(self, msg: dict[str, Any], thread_id: str) -> Message:
14301430
),
14311431
metadata=MessageMetadata(
14321432
date_sent=(
1433-
datetime.fromisoformat(msg["createdDateTime"]) if msg.get("createdDateTime") else datetime.now(UTC)
1433+
datetime.fromisoformat(msg["createdDateTime"])
1434+
if msg.get("createdDateTime")
1435+
else datetime.now(timezone.utc)
14341436
),
14351437
edited=bool(msg.get("lastModifiedDateTime")),
14361438
),

src/chat_sdk/adapters/telegram/adapter.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import os
1818
import re
1919
from dataclasses import dataclass
20-
from datetime import UTC, datetime
20+
from datetime import datetime, timezone
2121
from typing import Any
2222

2323
from chat_sdk.adapters.telegram.cards import (
@@ -944,7 +944,7 @@ async def edit_message(
944944
metadata=MessageMetadata(
945945
date_sent=existing.metadata.date_sent,
946946
edited=True,
947-
edited_at=datetime.now(UTC),
947+
edited_at=datetime.now(timezone.utc),
948948
),
949949
attachments=existing.attachments,
950950
is_mention=existing.is_mention,
@@ -1259,9 +1259,9 @@ def parse_telegram_message(
12591259
raw=raw,
12601260
author=author,
12611261
metadata=MessageMetadata(
1262-
date_sent=datetime.fromtimestamp(raw["date"], tz=UTC),
1262+
date_sent=datetime.fromtimestamp(raw["date"], tz=timezone.utc),
12631263
edited=edit_date is not None,
1264-
edited_at=(datetime.fromtimestamp(edit_date, tz=UTC) if edit_date is not None else None),
1264+
edited_at=(datetime.fromtimestamp(edit_date, tz=timezone.utc) if edit_date is not None else None),
12651265
),
12661266
attachments=self.extract_attachments(raw),
12671267
is_mention=self.is_bot_mentioned(raw, plain_text),

src/chat_sdk/adapters/whatsapp/adapter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import os
1616
import time
1717
from collections.abc import AsyncIterable
18-
from datetime import UTC, datetime
18+
from datetime import datetime, timezone
1919
from typing import Any
2020
from urllib.parse import parse_qs, urlparse
2121

@@ -525,7 +525,7 @@ def _build_message(
525525
metadata=MessageMetadata(
526526
date_sent=datetime.fromtimestamp(
527527
int(inbound.get("timestamp", "0")),
528-
tz=UTC,
528+
tz=timezone.utc,
529529
),
530530
edited=False,
531531
),
@@ -981,7 +981,7 @@ def parse_message(self, raw: WhatsAppRawMessage) -> Message:
981981
metadata=MessageMetadata(
982982
date_sent=datetime.fromtimestamp(
983983
int(raw["message"].get("timestamp", "0")),
984-
tz=UTC,
984+
tz=timezone.utc,
985985
),
986986
edited=False,
987987
),

src/chat_sdk/channel.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from collections.abc import AsyncIterator
1111
from dataclasses import dataclass
12-
from datetime import UTC, datetime
12+
from datetime import datetime, timezone
1313
from typing import Any
1414

1515
from chat_sdk.errors import ChatNotImplementedError
@@ -444,7 +444,7 @@ async def _remove_reaction(emoji: EmojiValue | str) -> None:
444444
is_me=True,
445445
),
446446
metadata=MessageMetadata(
447-
date_sent=datetime.now(tz=UTC),
447+
date_sent=datetime.now(tz=timezone.utc),
448448
edited=False,
449449
),
450450
attachments=attachments,

src/chat_sdk/chat.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import uuid
1515
from collections.abc import Awaitable, Callable
1616
from dataclasses import dataclass
17-
from datetime import UTC, datetime
17+
from datetime import datetime, timezone
1818
from typing import Any
1919

2020
from chat_sdk.channel import ChannelImpl
@@ -1143,7 +1143,7 @@ async def _handle_action_event(self, event: ActionEvent) -> None:
11431143
formatted={"type": "root", "children": []},
11441144
raw=event.raw,
11451145
author=event.user,
1146-
metadata=MessageMetadata(date_sent=datetime.now(tz=UTC), edited=False),
1146+
metadata=MessageMetadata(date_sent=datetime.now(tz=timezone.utc), edited=False),
11471147
attachments=[],
11481148
)
11491149
thread = self._create_thread(event.adapter, event.thread_id, dummy_message, is_subscribed)
@@ -1175,7 +1175,7 @@ async def _open_modal(modal: Any) -> dict[str, str] | None:
11751175
metadata=getattr(
11761176
raw_fetched,
11771177
"metadata",
1178-
MessageMetadata(date_sent=datetime.now(tz=UTC), edited=False),
1178+
MessageMetadata(date_sent=datetime.now(tz=timezone.utc), edited=False),
11791179
),
11801180
)
11811181
except Exception:
@@ -1253,7 +1253,7 @@ async def _handle_reaction_event(self, event: ReactionEvent) -> None:
12531253
formatted={"type": "root", "children": []},
12541254
raw=None,
12551255
author=event.user,
1256-
metadata=MessageMetadata(date_sent=datetime.now(tz=UTC), edited=False),
1256+
metadata=MessageMetadata(date_sent=datetime.now(tz=timezone.utc), edited=False),
12571257
),
12581258
is_subscribed,
12591259
)
@@ -1314,7 +1314,7 @@ async def open_dm(self, user: str | Author) -> ThreadImpl:
13141314
formatted={"type": "root", "children": []},
13151315
raw=None,
13161316
author=Author(user_id="", user_name="", full_name="", is_bot=False, is_me=False),
1317-
metadata=MessageMetadata(date_sent=datetime.now(tz=UTC), edited=False),
1317+
metadata=MessageMetadata(date_sent=datetime.now(tz=timezone.utc), edited=False),
13181318
),
13191319
False,
13201320
)
@@ -1559,7 +1559,7 @@ async def _handle_queue_or_debounce(
15591559
)
15601560
return
15611561

1562-
now = int(datetime.now(tz=UTC).timestamp() * 1000)
1562+
now = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
15631563
entry = QueueEntry(
15641564
message=message,
15651565
enqueued_at=now,
@@ -1577,7 +1577,7 @@ async def _handle_queue_or_debounce(
15771577

15781578
try:
15791579
if strategy == "debounce":
1580-
now = int(datetime.now(tz=UTC).timestamp() * 1000)
1580+
now = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
15811581
await self._state_adapter.enqueue(
15821582
lock_key,
15831583
QueueEntry(message=message, enqueued_at=now, expires_at=now + queue_entry_ttl_ms),
@@ -1634,7 +1634,7 @@ async def _debounce_loop(
16341634
break
16351635

16361636
msg = self._rehydrate_message(entry.message)
1637-
now = int(datetime.now(tz=UTC).timestamp() * 1000)
1637+
now = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
16381638
if now > entry.expires_at:
16391639
self._logger.info("message-expired", {"thread_id": thread_id, "message_id": msg.id})
16401640
continue
@@ -1664,7 +1664,7 @@ async def _drain_queue(
16641664
if entry is None:
16651665
break
16661666
msg = self._rehydrate_message(entry.message)
1667-
now = int(datetime.now(tz=UTC).timestamp() * 1000)
1667+
now = int(datetime.now(tz=timezone.utc).timestamp() * 1000)
16681668
if now <= entry.expires_at:
16691669
pending.append((msg, entry.expires_at))
16701670
else:
@@ -1869,7 +1869,7 @@ def _rehydrate_message(self, raw: Any) -> Message:
18691869
if isinstance(date_sent, str):
18701870
date_sent = datetime.fromisoformat(date_sent)
18711871
elif not isinstance(date_sent, datetime):
1872-
date_sent = datetime.now(tz=UTC)
1872+
date_sent = datetime.now(tz=timezone.utc)
18731873

18741874
edited_at = metadata_raw.get("edited_at")
18751875
if isinstance(edited_at, str):
@@ -1930,7 +1930,7 @@ def _message_from_json(data: dict[str, Any]) -> Message:
19301930
if isinstance(date_sent, str):
19311931
date_sent = datetime.fromisoformat(date_sent)
19321932
elif not isinstance(date_sent, datetime):
1933-
date_sent = datetime.now(tz=UTC)
1933+
date_sent = datetime.now(tz=timezone.utc)
19341934

19351935
edited_at = metadata_raw.get("editedAt") or metadata_raw.get("edited_at")
19361936
if isinstance(edited_at, str):

0 commit comments

Comments
 (0)