Skip to content

Commit e738037

Browse files
claude[bot]groksrc
andauthored
fix: replace deprecated datetime.utcnow() with timezone-aware alternatives and suppress SQLAlchemy warnings
Replace all datetime.utcnow() calls with datetime.now(timezone.utc) to fix Python 3.13 deprecation warnings: - Update JWT token generation in auth providers (11 instances across 2 files) - Fix test fixtures with timezone-aware datetime (3 instances) - Add timezone imports where needed - Add SQLAlchemy to noisy_loggers with WARNING level to suppress deprecation warnings Fixes Python 3.13 deprecation warnings that appeared when running commands like 'bm project add'. Code is now ready for Python 3.15 when datetime.utcnow() will be removed. Fixes #210 Co-authored-by: Drew Cain <groksrc@users.noreply.github.com>
1 parent 23ddf19 commit e738037

4 files changed

Lines changed: 19 additions & 17 deletions

File tree

src/basic_memory/mcp/auth_provider.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""OAuth authentication provider for Basic Memory MCP server."""
22

33
import secrets
4-
from datetime import datetime, timedelta
4+
from datetime import datetime, timedelta, timezone
55
from typing import Dict, Optional
66

77
import jwt
@@ -92,7 +92,7 @@ async def authorize(
9292
self.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
9393
code=auth_code,
9494
scopes=params.scopes or [],
95-
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
95+
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
9696
client_id=client.client_id,
9797
code_challenge=params.code_challenge,
9898
redirect_uri=params.redirect_uri,
@@ -119,7 +119,7 @@ async def load_authorization_code(
119119

120120
if code and code.client_id == client.client_id:
121121
# Check if expired
122-
if datetime.utcnow().timestamp() > code.expires_at:
122+
if datetime.now(timezone.utc).timestamp() > code.expires_at:
123123
del self.authorization_codes[authorization_code]
124124
return None
125125
return code
@@ -135,7 +135,7 @@ async def exchange_authorization_code(
135135
refresh_token = secrets.token_urlsafe(32)
136136

137137
# Store tokens
138-
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
138+
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
139139

140140
self.access_tokens[access_token] = BasicMemoryAccessToken(
141141
token=access_token,
@@ -187,7 +187,7 @@ async def exchange_refresh_token(
187187
new_refresh_token = secrets.token_urlsafe(32)
188188

189189
# Store new tokens
190-
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
190+
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
191191

192192
self.access_tokens[new_access_token] = BasicMemoryAccessToken(
193193
token=new_access_token,
@@ -220,7 +220,7 @@ async def load_access_token(self, token: str) -> Optional[BasicMemoryAccessToken
220220

221221
if access_token:
222222
# Check if expired
223-
if access_token.expires_at and datetime.utcnow().timestamp() > access_token.expires_at:
223+
if access_token.expires_at and datetime.now(timezone.utc).timestamp() > access_token.expires_at:
224224
logger.debug("Token found in memory but expired, removing")
225225
del self.access_tokens[token]
226226
return None
@@ -262,8 +262,8 @@ def _generate_access_token(self, client_id: str, scopes: list[str]) -> str:
262262
"iss": self.issuer_url,
263263
"sub": client_id,
264264
"aud": "basic-memory",
265-
"exp": datetime.utcnow() + timedelta(hours=1),
266-
"iat": datetime.utcnow(),
265+
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
266+
"iat": datetime.now(timezone.utc),
267267
"scopes": scopes,
268268
}
269269

src/basic_memory/mcp/supabase_auth_provider.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import os
44
import secrets
55
from dataclasses import dataclass
6-
from datetime import datetime, timedelta
6+
from datetime import datetime, timedelta, timezone
77
from typing import Optional, Dict, Any
88

99
import httpx
@@ -123,7 +123,7 @@ async def authorize(
123123
self.pending_auth_codes[state] = SupabaseAuthorizationCode(
124124
code=state,
125125
scopes=params.scopes or [],
126-
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
126+
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
127127
client_id=client.client_id,
128128
code_challenge=params.code_challenge,
129129
redirect_uri=params.redirect_uri,
@@ -218,7 +218,7 @@ async def load_authorization_code(
218218

219219
if code and code.client_id == client.client_id:
220220
# Check expiration
221-
if datetime.utcnow().timestamp() > code.expires_at:
221+
if datetime.now(timezone.utc).timestamp() > code.expires_at:
222222
del self.pending_auth_codes[authorization_code]
223223
return None
224224
return code
@@ -453,8 +453,8 @@ def _generate_mcp_token(
453453
"email": email,
454454
"scopes": scopes,
455455
"supabase_token": supabase_access_token[:10] + "...", # Reference only
456-
"exp": datetime.utcnow() + timedelta(hours=1),
457-
"iat": datetime.utcnow(),
456+
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
457+
"iat": datetime.now(timezone.utc),
458458
}
459459

460460
# Use Supabase JWT secret if available

src/basic_memory/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ def setup_logging(
173173
"httpx": logging.WARNING,
174174
# File watching logs
175175
"watchfiles.main": logging.WARNING,
176+
# SQLAlchemy deprecation warnings
177+
"sqlalchemy": logging.WARNING,
176178
}
177179

178180
# Set log levels for noisy loggers

tests/mcp/test_auth_provider.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tests for OAuth authentication provider."""
22

33
import pytest
4-
from datetime import datetime, timedelta
4+
from datetime import datetime, timedelta, timezone
55
from mcp.server.auth.provider import AuthorizationParams
66
from mcp.shared.auth import OAuthClientInformationFull
77
from pydantic import AnyHttpUrl
@@ -185,7 +185,7 @@ async def test_token_revocation(self, provider, client):
185185
token=token_str,
186186
client_id=client.client_id,
187187
scopes=["read", "write"],
188-
expires_at=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
188+
expires_at=int((datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()),
189189
)
190190
provider.access_tokens[token_str] = access_token
191191

@@ -226,7 +226,7 @@ async def test_expired_authorization_code(self, provider, client):
226226
provider.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
227227
code=auth_code,
228228
scopes=["read"],
229-
expires_at=(datetime.utcnow() - timedelta(minutes=1)).timestamp(),
229+
expires_at=(datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp(),
230230
client_id=client.client_id,
231231
code_challenge="challenge",
232232
redirect_uri=AnyHttpUrl("http://localhost:3000/callback"),
@@ -288,7 +288,7 @@ async def test_expired_access_token_in_memory(self, provider):
288288
token=expired_token_str,
289289
client_id="test-client",
290290
scopes=["read"],
291-
expires_at=int((datetime.utcnow() - timedelta(minutes=1)).timestamp()), # Expired
291+
expires_at=int((datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp()), # Expired
292292
)
293293
provider.access_tokens[expired_token_str] = expired_access_token
294294

0 commit comments

Comments
 (0)