From 98b1b15e0fbff1cbf53496c2e28227a1511289c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Han?= Date: Thu, 13 Mar 2025 23:34:53 +0100 Subject: [PATCH] refactor: move all datetime.now() calls to UTC (#1589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What does this PR do? Updated all instances of datetime.now() to use timezone.utc for consistency in handling time across different systems. This ensures that timestamps are always in Coordinated Universal Time (UTC), avoiding issues with time zone discrepancies and promoting uniformity in time-related data. Signed-off-by: Sébastien Han --- llama_stack/cli/download.py | 4 ++-- .../llama3/prompt_templates/system_prompts.py | 4 +++- .../agents/meta_reference/agent_instance.py | 24 +++++++++---------- .../agents/meta_reference/persistence.py | 4 ++-- .../post_training/torchtune/post_training.py | 8 +++---- .../recipes/lora_finetuning_single_device.py | 4 ++-- .../meta_reference/console_span_processor.py | 8 +++---- .../meta_reference/sqlite_span_processor.py | 12 +++++----- .../code_interpreter/code_execution.py | 2 +- .../providers/utils/telemetry/tracing.py | 6 ++--- pyproject.toml | 21 +++++++++------- 11 files changed, 52 insertions(+), 45 deletions(-) diff --git a/llama_stack/cli/download.py b/llama_stack/cli/download.py index b43d50217..f1b722183 100644 --- a/llama_stack/cli/download.py +++ b/llama_stack/cli/download.py @@ -10,7 +10,7 @@ import json import os import shutil from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timezone from functools import partial from pathlib import Path from typing import Dict, List, Optional @@ -404,7 +404,7 @@ def _download_from_manifest(manifest_file: str, max_concurrent_downloads: int): d = json.load(f) manifest = Manifest(**d) - if datetime.now() > manifest.expires_on: + if datetime.now(timezone.utc) > manifest.expires_on: raise ValueError(f"Manifest URLs have expired on {manifest.expires_on}") console = Console() diff --git a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py index 02e4814bc..b835d0ec0 100644 --- a/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py +++ b/llama_stack/models/llama/llama3/prompt_templates/system_prompts.py @@ -34,7 +34,9 @@ class SystemDefaultGenerator(PromptTemplateGeneratorBase): ) return PromptTemplate( template_str.lstrip("\n"), - {"today": datetime.now().strftime("%d %B %Y")}, + { + "today": datetime.now().strftime("%d %B %Y") # noqa: DTZ005 - we don't care about timezones here since we are displaying the date + }, ) def data_examples(self) -> List[Any]: diff --git a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py index 0ae1996cc..03692bcc7 100644 --- a/llama_stack/providers/inline/agents/meta_reference/agent_instance.py +++ b/llama_stack/providers/inline/agents/meta_reference/agent_instance.py @@ -11,7 +11,7 @@ import re import secrets import string import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import AsyncGenerator, List, Optional, Union from urllib.parse import urlparse @@ -239,7 +239,7 @@ class ChatAgent(ShieldRunnerMixin): in_progress_tool_call_step = await self.storage.get_in_progress_tool_call_step( request.session_id, request.turn_id ) - now = datetime.now().astimezone().isoformat() + now = datetime.now(timezone.utc).isoformat() tool_execution_step = ToolExecutionStep( step_id=(in_progress_tool_call_step.step_id if in_progress_tool_call_step else str(uuid.uuid4())), turn_id=request.turn_id, @@ -264,7 +264,7 @@ class ChatAgent(ShieldRunnerMixin): start_time = last_turn.started_at else: messages.extend(request.messages) - start_time = datetime.now().astimezone().isoformat() + start_time = datetime.now(timezone.utc).isoformat() input_messages = request.messages output_message = None @@ -295,7 +295,7 @@ class ChatAgent(ShieldRunnerMixin): input_messages=input_messages, output_message=output_message, started_at=start_time, - completed_at=datetime.now().astimezone().isoformat(), + completed_at=datetime.now(timezone.utc).isoformat(), steps=steps, ) await self.storage.add_turn_to_session(request.session_id, turn) @@ -386,7 +386,7 @@ class ChatAgent(ShieldRunnerMixin): return step_id = str(uuid.uuid4()) - shield_call_start_time = datetime.now().astimezone().isoformat() + shield_call_start_time = datetime.now(timezone.utc).isoformat() try: yield AgentTurnResponseStreamChunk( event=AgentTurnResponseEvent( @@ -410,7 +410,7 @@ class ChatAgent(ShieldRunnerMixin): turn_id=turn_id, violation=e.violation, started_at=shield_call_start_time, - completed_at=datetime.now().astimezone().isoformat(), + completed_at=datetime.now(timezone.utc).isoformat(), ), ) ) @@ -433,7 +433,7 @@ class ChatAgent(ShieldRunnerMixin): turn_id=turn_id, violation=None, started_at=shield_call_start_time, - completed_at=datetime.now().astimezone().isoformat(), + completed_at=datetime.now(timezone.utc).isoformat(), ), ) ) @@ -472,7 +472,7 @@ class ChatAgent(ShieldRunnerMixin): client_tools[tool.name] = tool while True: step_id = str(uuid.uuid4()) - inference_start_time = datetime.now().astimezone().isoformat() + inference_start_time = datetime.now(timezone.utc).isoformat() yield AgentTurnResponseStreamChunk( event=AgentTurnResponseEvent( payload=AgentTurnResponseStepStartPayload( @@ -582,7 +582,7 @@ class ChatAgent(ShieldRunnerMixin): turn_id=turn_id, model_response=copy.deepcopy(message), started_at=inference_start_time, - completed_at=datetime.now().astimezone().isoformat(), + completed_at=datetime.now(timezone.utc).isoformat(), ), ) ) @@ -653,7 +653,7 @@ class ChatAgent(ShieldRunnerMixin): turn_id=turn_id, tool_calls=[tool_call], tool_responses=[], - started_at=datetime.now().astimezone().isoformat(), + started_at=datetime.now(timezone.utc).isoformat(), ), ) yield message @@ -670,7 +670,7 @@ class ChatAgent(ShieldRunnerMixin): "input": message.model_dump_json(), }, ) as span: - tool_execution_start_time = datetime.now().astimezone().isoformat() + tool_execution_start_time = datetime.now(timezone.utc).isoformat() tool_call = message.tool_calls[0] tool_result = await self.execute_tool_call_maybe( session_id, @@ -708,7 +708,7 @@ class ChatAgent(ShieldRunnerMixin): ) ], started_at=tool_execution_start_time, - completed_at=datetime.now().astimezone().isoformat(), + completed_at=datetime.now(timezone.utc).isoformat(), ), ) ) diff --git a/llama_stack/providers/inline/agents/meta_reference/persistence.py b/llama_stack/providers/inline/agents/meta_reference/persistence.py index 2c04305fd..e7d7d1828 100644 --- a/llama_stack/providers/inline/agents/meta_reference/persistence.py +++ b/llama_stack/providers/inline/agents/meta_reference/persistence.py @@ -7,7 +7,7 @@ import json import logging import uuid -from datetime import datetime +from datetime import datetime, timezone from typing import List, Optional from pydantic import BaseModel @@ -36,7 +36,7 @@ class AgentPersistence: session_info = AgentSessionInfo( session_id=session_id, session_name=name, - started_at=datetime.now(), + started_at=datetime.now(timezone.utc), ) await self.kvstore.set( key=f"session:{self.agent_id}:{session_id}", diff --git a/llama_stack/providers/inline/post_training/torchtune/post_training.py b/llama_stack/providers/inline/post_training/torchtune/post_training.py index 3a1affc91..2c129ef41 100644 --- a/llama_stack/providers/inline/post_training/torchtune/post_training.py +++ b/llama_stack/providers/inline/post_training/torchtune/post_training.py @@ -3,7 +3,7 @@ # # This source code is licensed under the terms described in the LICENSE file in # the root directory of this source tree. -from datetime import datetime +from datetime import datetime, timezone from typing import Any, Dict, Optional from llama_stack.apis.datasetio import DatasetIO @@ -64,7 +64,7 @@ class TorchtunePostTrainingImpl: job_status_response = PostTrainingJobStatusResponse( job_uuid=job_uuid, status=JobStatus.scheduled, - scheduled_at=datetime.now(), + scheduled_at=datetime.now(timezone.utc), ) self.jobs[job_uuid] = job_status_response @@ -84,7 +84,7 @@ class TorchtunePostTrainingImpl: ) job_status_response.status = JobStatus.in_progress - job_status_response.started_at = datetime.now() + job_status_response.started_at = datetime.now(timezone.utc) await recipe.setup() resources_allocated, checkpoints = await recipe.train() @@ -93,7 +93,7 @@ class TorchtunePostTrainingImpl: job_status_response.resources_allocated = resources_allocated job_status_response.checkpoints = checkpoints job_status_response.status = JobStatus.completed - job_status_response.completed_at = datetime.now() + job_status_response.completed_at = datetime.now(timezone.utc) except Exception: job_status_response.status = JobStatus.failed diff --git a/llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py b/llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py index c88787f18..941c629e3 100644 --- a/llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py +++ b/llama_stack/providers/inline/post_training/torchtune/recipes/lora_finetuning_single_device.py @@ -8,7 +8,7 @@ import gc import logging import os import time -from datetime import datetime +from datetime import datetime, timezone from functools import partial from pathlib import Path from typing import Any, Dict, List, Optional, Tuple @@ -532,7 +532,7 @@ class LoraFinetuningSingleDevice: checkpoint_path = await self.save_checkpoint(epoch=curr_epoch) checkpoint = Checkpoint( identifier=f"{self.model_id}-sft-{curr_epoch}", - created_at=datetime.now(), + created_at=datetime.now(timezone.utc), epoch=curr_epoch, post_training_job_id=self.job_uuid, path=checkpoint_path, diff --git a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py index 2e3bd4d3a..42b538876 100644 --- a/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py +++ b/llama_stack/providers/inline/telemetry/meta_reference/console_span_processor.py @@ -5,7 +5,7 @@ # the root directory of this source tree. import json -from datetime import datetime +from datetime import datetime, timezone from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanProcessor @@ -34,7 +34,7 @@ class ConsoleSpanProcessor(SpanProcessor): if span.attributes and span.attributes.get("__autotraced__"): return - timestamp = datetime.utcfromtimestamp(span.start_time / 1e9).strftime("%H:%M:%S.%f")[:-3] + timestamp = datetime.fromtimestamp(span.start_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] print( f"{COLORS['dim']}{timestamp}{COLORS['reset']} " @@ -46,7 +46,7 @@ class ConsoleSpanProcessor(SpanProcessor): if span.attributes and span.attributes.get("__autotraced__"): return - timestamp = datetime.utcfromtimestamp(span.end_time / 1e9).strftime("%H:%M:%S.%f")[:-3] + timestamp = datetime.fromtimestamp(span.end_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] span_context = ( f"{COLORS['dim']}{timestamp}{COLORS['reset']} " @@ -74,7 +74,7 @@ class ConsoleSpanProcessor(SpanProcessor): print(f" {COLORS['dim']}{key}: {str_value}{COLORS['reset']}") for event in span.events: - event_time = datetime.utcfromtimestamp(event.timestamp / 1e9).strftime("%H:%M:%S.%f")[:-3] + event_time = datetime.fromtimestamp(event.timestamp / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] severity = event.attributes.get("severity", "info") message = event.attributes.get("message", event.name) diff --git a/llama_stack/providers/inline/telemetry/meta_reference/sqlite_span_processor.py b/llama_stack/providers/inline/telemetry/meta_reference/sqlite_span_processor.py index b39ee7716..5ed586fce 100644 --- a/llama_stack/providers/inline/telemetry/meta_reference/sqlite_span_processor.py +++ b/llama_stack/providers/inline/telemetry/meta_reference/sqlite_span_processor.py @@ -8,7 +8,7 @@ import json import os import sqlite3 import threading -from datetime import datetime +from datetime import datetime, timezone from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.trace import Span @@ -124,8 +124,8 @@ class SQLiteSpanProcessor(SpanProcessor): trace_id, service_name, (span_id if not parent_span_id else None), - datetime.fromtimestamp(span.start_time / 1e9).isoformat(), - datetime.fromtimestamp(span.end_time / 1e9).isoformat(), + datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(), + datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(), ), ) @@ -143,8 +143,8 @@ class SQLiteSpanProcessor(SpanProcessor): trace_id, parent_span_id, span.name, - datetime.fromtimestamp(span.start_time / 1e9).isoformat(), - datetime.fromtimestamp(span.end_time / 1e9).isoformat(), + datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(), + datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(), json.dumps(dict(span.attributes)), span.status.status_code.name, span.kind.name, @@ -161,7 +161,7 @@ class SQLiteSpanProcessor(SpanProcessor): ( span_id, event.name, - datetime.fromtimestamp(event.timestamp / 1e9).isoformat(), + datetime.fromtimestamp(event.timestamp / 1e9, timezone.utc).isoformat(), json.dumps(dict(event.attributes)), ), ) diff --git a/llama_stack/providers/inline/tool_runtime/code_interpreter/code_execution.py b/llama_stack/providers/inline/tool_runtime/code_interpreter/code_execution.py index d7b2dbdef..810591c1c 100644 --- a/llama_stack/providers/inline/tool_runtime/code_interpreter/code_execution.py +++ b/llama_stack/providers/inline/tool_runtime/code_interpreter/code_execution.py @@ -168,7 +168,7 @@ def process_matplotlib_response(response, matplotlib_dump_dir: str): image_paths = [] for i, img in enumerate(images): # create new directory for each day to better organize data: - dump_dname = datetime.today().strftime("%Y-%m-%d") + dump_dname = datetime.today().strftime("%Y-%m-%d") # noqa: DTZ002 - we don't care about timezones here since we are displaying the date dump_dpath = Path(matplotlib_dump_dir, dump_dname) dump_dpath.mkdir(parents=True, exist_ok=True) # save image into a file diff --git a/llama_stack/providers/utils/telemetry/tracing.py b/llama_stack/providers/utils/telemetry/tracing.py index bef229080..607d1a918 100644 --- a/llama_stack/providers/utils/telemetry/tracing.py +++ b/llama_stack/providers/utils/telemetry/tracing.py @@ -11,7 +11,7 @@ import logging import queue import threading import uuid -from datetime import datetime +from datetime import datetime, timezone from functools import wraps from typing import Any, Callable, Dict, List, Optional @@ -86,7 +86,7 @@ class TraceContext: span_id=generate_short_uuid(), trace_id=self.trace_id, name=name, - start_time=datetime.now(), + start_time=datetime.now(timezone.utc), parent_span_id=current_span.span_id if current_span else None, attributes=attributes, ) @@ -203,7 +203,7 @@ class TelemetryHandler(logging.Handler): UnstructuredLogEvent( trace_id=span.trace_id, span_id=span.span_id, - timestamp=datetime.now(), + timestamp=datetime.now(timezone.utc), message=self.format(record), severity=severity(record.levelname), ) diff --git a/pyproject.toml b/pyproject.toml index aaea4f7c4..ff7f46f77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,14 +124,15 @@ exclude = [ [tool.ruff.lint] select = [ - "B", # flake8-bugbear - "B9", # flake8-bugbear subset - "C", # comprehensions - "E", # pycodestyle - "F", # Pyflakes - "N", # Naming - "W", # Warnings - "I", # isort + "B", # flake8-bugbear + "B9", # flake8-bugbear subset + "C", # comprehensions + "E", # pycodestyle + "F", # Pyflakes + "N", # Naming + "W", # Warnings + "I", # isort + "DTZ", # datetime rules ] ignore = [ # The following ignores are desired by the project maintainers. @@ -145,6 +146,10 @@ ignore = [ "C901", # Complexity of the function is too high ] +# Ignore the following errors for the following files +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = ["DTZ"] # Ignore datetime rules for tests + [tool.mypy] mypy_path = ["llama_stack"] packages = ["llama_stack"]