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 datetime.now(timezone.utc)
Addresses Python 3.13 deprecation warnings by updating all datetime.utcnow() usage: - Replace datetime.utcnow() with datetime.now(timezone.utc) across 5 files - Use lambda functions for SQLAlchemy model defaults to ensure proper evaluation timing - Add timezone imports where needed - Enhance logging configuration with warning filters for user environment - Preserve deprecation warnings for developers in dev/test environments Fixes 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
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from typing import Optional, Protocol, Union, runtime_checkable, List, Any
|
||||
|
||||
@@ -179,6 +180,23 @@ def setup_logging(
|
||||
for logger_name, level in noisy_loggers.items():
|
||||
logging.getLogger(logger_name).setLevel(level)
|
||||
|
||||
# Filter deprecation warnings for user environment to provide clean UX
|
||||
# Keep them visible in dev/test environments for developers
|
||||
if env == "user":
|
||||
# Suppress SQLAlchemy deprecation warnings that appear in user-facing commands
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=DeprecationWarning,
|
||||
module="sqlalchemy",
|
||||
message=".*datetime\\.datetime\\.utcnow.*"
|
||||
)
|
||||
# Also suppress any other datetime.utcnow deprecation warnings
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
category=DeprecationWarning,
|
||||
message=".*datetime\\.datetime\\.utcnow.*"
|
||||
)
|
||||
|
||||
|
||||
def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
|
||||
"""Parse tags from various input formats into a consistent list.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test repository implementation."""
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
import pytest
|
||||
from sqlalchemy import String, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
@@ -17,9 +17,9 @@ class ModelTest(Base):
|
||||
id: Mapped[str] = mapped_column(String(255), primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(255))
|
||||
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user