mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
fix: replace deprecated datetime.utcnow() with timezone-aware alternatives
Replace all datetime.utcnow() calls with datetime.now(timezone.utc) to fix Python 3.13 deprecation warnings: - Update SQLAlchemy model defaults in project.py with lambda functions - Fix JWT token generation in auth providers - Update test fixtures with timezone-aware datetime - Add timezone imports where needed 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>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""OAuth authentication provider for Basic Memory MCP server."""
|
||||
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, Optional
|
||||
|
||||
import jwt
|
||||
@@ -92,7 +92,7 @@ class BasicMemoryOAuthProvider(
|
||||
self.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
|
||||
code=auth_code,
|
||||
scopes=params.scopes or [],
|
||||
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
|
||||
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
|
||||
client_id=client.client_id,
|
||||
code_challenge=params.code_challenge,
|
||||
redirect_uri=params.redirect_uri,
|
||||
@@ -119,7 +119,7 @@ class BasicMemoryOAuthProvider(
|
||||
|
||||
if code and code.client_id == client.client_id:
|
||||
# Check if expired
|
||||
if datetime.utcnow().timestamp() > code.expires_at:
|
||||
if datetime.now(timezone.utc).timestamp() > code.expires_at:
|
||||
del self.authorization_codes[authorization_code]
|
||||
return None
|
||||
return code
|
||||
@@ -135,7 +135,7 @@ class BasicMemoryOAuthProvider(
|
||||
refresh_token = secrets.token_urlsafe(32)
|
||||
|
||||
# Store tokens
|
||||
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
|
||||
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
|
||||
|
||||
self.access_tokens[access_token] = BasicMemoryAccessToken(
|
||||
token=access_token,
|
||||
@@ -187,7 +187,7 @@ class BasicMemoryOAuthProvider(
|
||||
new_refresh_token = secrets.token_urlsafe(32)
|
||||
|
||||
# Store new tokens
|
||||
expires_at = (datetime.utcnow() + timedelta(hours=1)).timestamp()
|
||||
expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()
|
||||
|
||||
self.access_tokens[new_access_token] = BasicMemoryAccessToken(
|
||||
token=new_access_token,
|
||||
@@ -220,7 +220,7 @@ class BasicMemoryOAuthProvider(
|
||||
|
||||
if access_token:
|
||||
# Check if expired
|
||||
if access_token.expires_at and datetime.utcnow().timestamp() > access_token.expires_at:
|
||||
if access_token.expires_at and datetime.now(timezone.utc).timestamp() > access_token.expires_at:
|
||||
logger.debug("Token found in memory but expired, removing")
|
||||
del self.access_tokens[token]
|
||||
return None
|
||||
@@ -262,8 +262,8 @@ class BasicMemoryOAuthProvider(
|
||||
"iss": self.issuer_url,
|
||||
"sub": client_id,
|
||||
"aud": "basic-memory",
|
||||
"exp": datetime.utcnow() + timedelta(hours=1),
|
||||
"iat": datetime.utcnow(),
|
||||
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"scopes": scopes,
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import os
|
||||
import secrets
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
import httpx
|
||||
@@ -123,7 +123,7 @@ class SupabaseOAuthProvider(
|
||||
self.pending_auth_codes[state] = SupabaseAuthorizationCode(
|
||||
code=state,
|
||||
scopes=params.scopes or [],
|
||||
expires_at=(datetime.utcnow() + timedelta(minutes=10)).timestamp(),
|
||||
expires_at=(datetime.now(timezone.utc) + timedelta(minutes=10)).timestamp(),
|
||||
client_id=client.client_id,
|
||||
code_challenge=params.code_challenge,
|
||||
redirect_uri=params.redirect_uri,
|
||||
@@ -218,7 +218,7 @@ class SupabaseOAuthProvider(
|
||||
|
||||
if code and code.client_id == client.client_id:
|
||||
# Check expiration
|
||||
if datetime.utcnow().timestamp() > code.expires_at:
|
||||
if datetime.now(timezone.utc).timestamp() > code.expires_at:
|
||||
del self.pending_auth_codes[authorization_code]
|
||||
return None
|
||||
return code
|
||||
@@ -453,8 +453,8 @@ class SupabaseOAuthProvider(
|
||||
"email": email,
|
||||
"scopes": scopes,
|
||||
"supabase_token": supabase_access_token[:10] + "...", # Reference only
|
||||
"exp": datetime.utcnow() + timedelta(hours=1),
|
||||
"iat": datetime.utcnow(),
|
||||
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
|
||||
"iat": datetime.now(timezone.utc),
|
||||
}
|
||||
|
||||
# Use Supabase JWT secret if available
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Project model for Basic Memory."""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -52,9 +52,9 @@ class Project(Base):
|
||||
is_default: Mapped[Optional[bool]] = mapped_column(Boolean, default=None, nullable=True)
|
||||
|
||||
# Timestamps
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
|
||||
DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
|
||||
# Define relationships to entities, observations, and relations
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for OAuth authentication provider."""
|
||||
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from mcp.server.auth.provider import AuthorizationParams
|
||||
from mcp.shared.auth import OAuthClientInformationFull
|
||||
from pydantic import AnyHttpUrl
|
||||
@@ -185,7 +185,7 @@ class TestBasicMemoryOAuthProvider:
|
||||
token=token_str,
|
||||
client_id=client.client_id,
|
||||
scopes=["read", "write"],
|
||||
expires_at=int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
|
||||
expires_at=int((datetime.now(timezone.utc) + timedelta(hours=1)).timestamp()),
|
||||
)
|
||||
provider.access_tokens[token_str] = access_token
|
||||
|
||||
@@ -226,7 +226,7 @@ class TestBasicMemoryOAuthProvider:
|
||||
provider.authorization_codes[auth_code] = BasicMemoryAuthorizationCode(
|
||||
code=auth_code,
|
||||
scopes=["read"],
|
||||
expires_at=(datetime.utcnow() - timedelta(minutes=1)).timestamp(),
|
||||
expires_at=(datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp(),
|
||||
client_id=client.client_id,
|
||||
code_challenge="challenge",
|
||||
redirect_uri=AnyHttpUrl("http://localhost:3000/callback"),
|
||||
@@ -288,7 +288,7 @@ class TestBasicMemoryOAuthProvider:
|
||||
token=expired_token_str,
|
||||
client_id="test-client",
|
||||
scopes=["read"],
|
||||
expires_at=int((datetime.utcnow() - timedelta(minutes=1)).timestamp()), # Expired
|
||||
expires_at=int((datetime.now(timezone.utc) - timedelta(minutes=1)).timestamp()), # Expired
|
||||
)
|
||||
provider.access_tokens[expired_token_str] = expired_access_token
|
||||
|
||||
|
||||
Reference in New Issue
Block a user