feat: drop python 3.10 support (#2469)

# What does this PR do?

dropped python3.10, updated pyproject and dependencies, and also removed
some blocks of code with special handling for enum.StrEnum

Closes #2458

Signed-off-by: Charlie Doern <cdoern@redhat.com>
This commit is contained in:
Charlie Doern 2025-06-19 02:37:14 -04:00 committed by GitHub
parent db2cd9e8f3
commit d12f195f56
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 63 additions and 104 deletions

View file

@ -4,7 +4,7 @@ inputs:
python-version: python-version:
description: The Python version to use description: The Python version to use
required: false required: false
default: "3.10" default: "3.11"
runs: runs:
using: "composite" using: "composite"
steps: steps:

View file

@ -26,7 +26,7 @@ jobs:
# TODO: generate matrix list from tests/integration when fixed # TODO: generate matrix list from tests/integration when fixed
test-type: [agents, inference, datasets, inspect, scoring, post_training, providers, tool_runtime, vector_io] test-type: [agents, inference, datasets, inspect, scoring, post_training, providers, tool_runtime, vector_io]
client-type: [library, http] client-type: [library, http]
python-version: ["3.10", "3.11", "3.12"] python-version: ["3.11", "3.12"]
fail-fast: false # we want to run all tests regardless of failure fail-fast: false # we want to run all tests regardless of failure
steps: steps:

View file

@ -25,7 +25,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python: python:
- "3.10"
- "3.11" - "3.11"
- "3.12" - "3.12"
- "3.13" - "3.13"

View file

@ -4,10 +4,9 @@
# This source code is licensed under the terms described in the LICENSE file in # This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree. # the root directory of this source tree.
import sys
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from datetime import datetime from datetime import datetime
from enum import Enum from enum import StrEnum
from typing import Annotated, Any, Literal, Protocol, runtime_checkable from typing import Annotated, Any, Literal, Protocol, runtime_checkable
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
@ -40,14 +39,6 @@ from .openai_responses import (
OpenAIResponseText, OpenAIResponseText,
) )
# TODO: use enum.StrEnum when we drop support for python 3.10
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
class StrEnum(str, Enum):
"""Backport of StrEnum for Python 3.10 and below."""
class Attachment(BaseModel): class Attachment(BaseModel):
"""An attachment to an agent turn. """An attachment to an agent turn.

View file

@ -4,7 +4,6 @@
# This source code is licensed under the terms described in the LICENSE file in # This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree. # the root directory of this source tree.
import sys
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from enum import Enum from enum import Enum
from typing import ( from typing import (
@ -37,15 +36,7 @@ register_schema(ToolCall)
register_schema(ToolParamDefinition) register_schema(ToolParamDefinition)
register_schema(ToolDefinition) register_schema(ToolDefinition)
# TODO: use enum.StrEnum when we drop support for python 3.10
if sys.version_info >= (3, 11):
from enum import StrEnum from enum import StrEnum
else:
class StrEnum(str, Enum):
"""Backport of StrEnum for Python 3.10 and below."""
pass
@json_schema_type @json_schema_type

View file

@ -4,21 +4,11 @@
# This source code is licensed under the terms described in the LICENSE file in # This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree. # the root directory of this source tree.
import sys
from enum import Enum from enum import StrEnum
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
# TODO: use enum.StrEnum when we drop support for python 3.10
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
class StrEnum(str, Enum):
"""Backport of StrEnum for Python 3.10 and below."""
pass
class ResourceType(StrEnum): class ResourceType(StrEnum):
model = "model" model = "model"

View file

@ -5,8 +5,7 @@
# the root directory of this source tree. # the root directory of this source tree.
# TODO: use enum.StrEnum when we drop support for python 3.10 # TODO: use enum.StrEnum when we drop support for python 3.10
import sys from enum import StrEnum
from enum import Enum
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
@ -21,15 +20,6 @@ from llama_stack.apis.common.type_system import ParamType
from llama_stack.apis.resource import Resource, ResourceType from llama_stack.apis.resource import Resource, ResourceType
from llama_stack.schema_utils import json_schema_type, register_schema, webmethod from llama_stack.schema_utils import json_schema_type, register_schema, webmethod
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
class StrEnum(str, Enum):
"""Backport of StrEnum for Python 3.10 and below."""
pass
# Perhaps more structure can be imposed on these functions. Maybe they could be associated # Perhaps more structure can be imposed on these functions. Maybe they could be associated
# with standard metrics so they can be rolled up? # with standard metrics so they can be rolled up?

View file

@ -11,7 +11,7 @@ import os
import shutil import shutil
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime, timezone from datetime import UTC, datetime
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
@ -409,7 +409,7 @@ def _download_from_manifest(manifest_file: str, max_concurrent_downloads: int):
d = json.load(f) d = json.load(f)
manifest = Manifest(**d) manifest = Manifest(**d)
if datetime.now(timezone.utc) > manifest.expires_on.astimezone(timezone.utc): if datetime.now(UTC) > manifest.expires_on.astimezone(UTC):
raise ValueError(f"Manifest URLs have expired on {manifest.expires_on}") raise ValueError(f"Manifest URLs have expired on {manifest.expires_on}")
console = Console() console = Console()

View file

@ -5,9 +5,9 @@
# the root directory of this source tree. # the root directory of this source tree.
from enum import Enum from enum import Enum
from typing import Self
from pydantic import BaseModel, model_validator from pydantic import BaseModel, model_validator
from typing_extensions import Self
from .conditions import parse_conditions from .conditions import parse_conditions

View file

@ -101,7 +101,7 @@ def build_image(
template_or_config: str, template_or_config: str,
run_config: str | None = None, run_config: str | None = None,
): ):
container_base = build_config.distribution_spec.container_image or "python:3.10-slim" container_base = build_config.distribution_spec.container_image or "python:3.11-slim"
normal_deps, special_deps = get_provider_dependencies(build_config) normal_deps, special_deps = get_provider_dependencies(build_config)
normal_deps += SERVER_DEPENDENCIES normal_deps += SERVER_DEPENDENCIES

View file

@ -49,7 +49,7 @@ ensure_conda_env_python310() {
local env_name="$1" local env_name="$1"
local pip_dependencies="$2" local pip_dependencies="$2"
local special_pip_deps="$3" local special_pip_deps="$3"
local python_version="3.10" local python_version="3.11"
# Check if conda command is available # Check if conda command is available
if ! is_command_available conda; then if ! is_command_available conda; then

View file

@ -99,7 +99,7 @@ class ProviderImpl(Providers):
try: try:
health = await asyncio.wait_for(impl.health(), timeout=timeout) health = await asyncio.wait_for(impl.health(), timeout=timeout)
return api_name, health return api_name, health
except (asyncio.TimeoutError, TimeoutError): except TimeoutError:
return ( return (
api_name, api_name,
HealthResponse( HealthResponse(

View file

@ -615,7 +615,7 @@ class InferenceRouter(Inference):
continue continue
health = await asyncio.wait_for(impl.health(), timeout=timeout) health = await asyncio.wait_for(impl.health(), timeout=timeout)
health_statuses[provider_id] = health health_statuses[provider_id] = health
except (asyncio.TimeoutError, TimeoutError): except TimeoutError:
health_statuses[provider_id] = HealthResponse( health_statuses[provider_id] = HealthResponse(
status=HealthStatus.ERROR, status=HealthStatus.ERROR,
message=f"Health check timed out after {timeout} seconds", message=f"Health check timed out after {timeout} seconds",

View file

@ -9,12 +9,12 @@ import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from asyncio import Lock from asyncio import Lock
from pathlib import Path from pathlib import Path
from typing import Self
from urllib.parse import parse_qs from urllib.parse import parse_qs
import httpx import httpx
from jose import jwt from jose import jwt
from pydantic import BaseModel, Field, field_validator, model_validator from pydantic import BaseModel, Field, field_validator, model_validator
from typing_extensions import Self
from llama_stack.distribution.datatypes import AuthenticationConfig, AuthProviderType, User from llama_stack.distribution.datatypes import AuthenticationConfig, AuthProviderType, User
from llama_stack.log import get_logger from llama_stack.log import get_logger

View file

@ -6,7 +6,7 @@
import json import json
import time import time
from datetime import datetime, timedelta, timezone from datetime import UTC, datetime, timedelta
from starlette.types import ASGIApp, Receive, Scope, Send from starlette.types import ASGIApp, Receive, Scope, Send
@ -79,7 +79,7 @@ class QuotaMiddleware:
if int(prev) == 0: if int(prev) == 0:
# Set with expiration datetime when it is the first request in the window. # Set with expiration datetime when it is the first request in the window.
expiration = datetime.now(timezone.utc) + timedelta(seconds=self.window_seconds) expiration = datetime.now(UTC) + timedelta(seconds=self.window_seconds)
await kv.set(key, str(count), expiration=expiration) await kv.set(key, str(count), expiration=expiration)
else: else:
await kv.set(key, str(count)) await kv.set(key, str(count))

View file

@ -145,7 +145,7 @@ async def shutdown(app):
await asyncio.wait_for(impl.shutdown(), timeout=5) await asyncio.wait_for(impl.shutdown(), timeout=5)
else: else:
logger.warning("No shutdown method for %s", impl_name) logger.warning("No shutdown method for %s", impl_name)
except (asyncio.TimeoutError, TimeoutError): except TimeoutError:
logger.exception("Shutdown timeout for %s ", impl_name, exc_info=True) logger.exception("Shutdown timeout for %s ", impl_name, exc_info=True)
except (Exception, asyncio.CancelledError) as e: except (Exception, asyncio.CancelledError) as e:
logger.exception("Failed to shutdown %s: %s", impl_name, {e}) logger.exception("Failed to shutdown %s: %s", impl_name, {e})

View file

@ -11,7 +11,7 @@ import secrets
import string import string
import uuid import uuid
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from datetime import datetime, timezone from datetime import UTC, datetime
import httpx import httpx
@ -242,7 +242,7 @@ class ChatAgent(ShieldRunnerMixin):
in_progress_tool_call_step = await self.storage.get_in_progress_tool_call_step( in_progress_tool_call_step = await self.storage.get_in_progress_tool_call_step(
request.session_id, request.turn_id request.session_id, request.turn_id
) )
now = datetime.now(timezone.utc).isoformat() now = datetime.now(UTC).isoformat()
tool_execution_step = ToolExecutionStep( tool_execution_step = ToolExecutionStep(
step_id=(in_progress_tool_call_step.step_id if in_progress_tool_call_step else str(uuid.uuid4())), step_id=(in_progress_tool_call_step.step_id if in_progress_tool_call_step else str(uuid.uuid4())),
turn_id=request.turn_id, turn_id=request.turn_id,
@ -267,7 +267,7 @@ class ChatAgent(ShieldRunnerMixin):
start_time = last_turn.started_at start_time = last_turn.started_at
else: else:
messages.extend(request.messages) messages.extend(request.messages)
start_time = datetime.now(timezone.utc).isoformat() start_time = datetime.now(UTC).isoformat()
input_messages = request.messages input_messages = request.messages
output_message = None output_message = None
@ -298,7 +298,7 @@ class ChatAgent(ShieldRunnerMixin):
input_messages=input_messages, input_messages=input_messages,
output_message=output_message, output_message=output_message,
started_at=start_time, started_at=start_time,
completed_at=datetime.now(timezone.utc).isoformat(), completed_at=datetime.now(UTC).isoformat(),
steps=steps, steps=steps,
) )
await self.storage.add_turn_to_session(request.session_id, turn) await self.storage.add_turn_to_session(request.session_id, turn)
@ -389,7 +389,7 @@ class ChatAgent(ShieldRunnerMixin):
return return
step_id = str(uuid.uuid4()) step_id = str(uuid.uuid4())
shield_call_start_time = datetime.now(timezone.utc).isoformat() shield_call_start_time = datetime.now(UTC).isoformat()
try: try:
yield AgentTurnResponseStreamChunk( yield AgentTurnResponseStreamChunk(
event=AgentTurnResponseEvent( event=AgentTurnResponseEvent(
@ -413,7 +413,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
violation=e.violation, violation=e.violation,
started_at=shield_call_start_time, started_at=shield_call_start_time,
completed_at=datetime.now(timezone.utc).isoformat(), completed_at=datetime.now(UTC).isoformat(),
), ),
) )
) )
@ -436,7 +436,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
violation=None, violation=None,
started_at=shield_call_start_time, started_at=shield_call_start_time,
completed_at=datetime.now(timezone.utc).isoformat(), completed_at=datetime.now(UTC).isoformat(),
), ),
) )
) )
@ -491,7 +491,7 @@ class ChatAgent(ShieldRunnerMixin):
client_tools[tool.name] = tool client_tools[tool.name] = tool
while True: while True:
step_id = str(uuid.uuid4()) step_id = str(uuid.uuid4())
inference_start_time = datetime.now(timezone.utc).isoformat() inference_start_time = datetime.now(UTC).isoformat()
yield AgentTurnResponseStreamChunk( yield AgentTurnResponseStreamChunk(
event=AgentTurnResponseEvent( event=AgentTurnResponseEvent(
payload=AgentTurnResponseStepStartPayload( payload=AgentTurnResponseStepStartPayload(
@ -603,7 +603,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
model_response=copy.deepcopy(message), model_response=copy.deepcopy(message),
started_at=inference_start_time, started_at=inference_start_time,
completed_at=datetime.now(timezone.utc).isoformat(), completed_at=datetime.now(UTC).isoformat(),
), ),
) )
) )
@ -681,7 +681,7 @@ class ChatAgent(ShieldRunnerMixin):
"input": message.model_dump_json(), "input": message.model_dump_json(),
}, },
) as span: ) as span:
tool_execution_start_time = datetime.now(timezone.utc).isoformat() tool_execution_start_time = datetime.now(UTC).isoformat()
tool_result = await self.execute_tool_call_maybe( tool_result = await self.execute_tool_call_maybe(
session_id, session_id,
tool_call, tool_call,
@ -710,7 +710,7 @@ class ChatAgent(ShieldRunnerMixin):
) )
], ],
started_at=tool_execution_start_time, started_at=tool_execution_start_time,
completed_at=datetime.now(timezone.utc).isoformat(), completed_at=datetime.now(UTC).isoformat(),
) )
# Yield the step completion event # Yield the step completion event
@ -747,7 +747,7 @@ class ChatAgent(ShieldRunnerMixin):
turn_id=turn_id, turn_id=turn_id,
tool_calls=client_tool_calls, tool_calls=client_tool_calls,
tool_responses=[], tool_responses=[],
started_at=datetime.now(timezone.utc).isoformat(), started_at=datetime.now(UTC).isoformat(),
), ),
) )

View file

@ -7,7 +7,7 @@
import logging import logging
import uuid import uuid
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from datetime import datetime, timezone from datetime import UTC, datetime
from llama_stack.apis.agents import ( from llama_stack.apis.agents import (
Agent, Agent,
@ -93,7 +93,7 @@ class MetaReferenceAgentsImpl(Agents):
agent_config: AgentConfig, agent_config: AgentConfig,
) -> AgentCreateResponse: ) -> AgentCreateResponse:
agent_id = str(uuid.uuid4()) agent_id = str(uuid.uuid4())
created_at = datetime.now(timezone.utc) created_at = datetime.now(UTC)
agent_info = AgentInfo( agent_info = AgentInfo(
**agent_config.model_dump(), **agent_config.model_dump(),

View file

@ -7,7 +7,7 @@
import json import json
import logging import logging
import uuid import uuid
from datetime import datetime, timezone from datetime import UTC, datetime
from llama_stack.apis.agents import AgentConfig, Session, ToolExecutionStep, Turn from llama_stack.apis.agents import AgentConfig, Session, ToolExecutionStep, Turn
from llama_stack.distribution.access_control.access_control import AccessDeniedError, is_action_allowed from llama_stack.distribution.access_control.access_control import AccessDeniedError, is_action_allowed
@ -47,7 +47,7 @@ class AgentPersistence:
session_info = AgentSessionInfo( session_info = AgentSessionInfo(
session_id=session_id, session_id=session_id,
session_name=name, session_name=name,
started_at=datetime.now(timezone.utc), started_at=datetime.now(UTC),
owner=user, owner=user,
turns=[], turns=[],
identifier=name, # should this be qualified in any way? identifier=name, # should this be qualified in any way?

View file

@ -11,7 +11,7 @@ import multiprocessing
import os import os
import signal import signal
import sys import sys
from datetime import datetime, timezone from datetime import UTC, datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -670,7 +670,7 @@ class HFFinetuningSingleDevice:
# Create checkpoint # Create checkpoint
checkpoint = Checkpoint( checkpoint = Checkpoint(
identifier=f"{model}-sft-{config.n_epochs}", identifier=f"{model}-sft-{config.n_epochs}",
created_at=datetime.now(timezone.utc), created_at=datetime.now(UTC),
epoch=config.n_epochs, epoch=config.n_epochs,
post_training_job_id=job_uuid, post_training_job_id=job_uuid,
path=str(output_dir_path / "merged_model"), path=str(output_dir_path / "merged_model"),

View file

@ -7,7 +7,7 @@
import logging import logging
import os import os
import time import time
from datetime import datetime, timezone from datetime import UTC, datetime
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@ -537,7 +537,7 @@ class LoraFinetuningSingleDevice:
checkpoint_path = await self.save_checkpoint(epoch=curr_epoch) checkpoint_path = await self.save_checkpoint(epoch=curr_epoch)
checkpoint = Checkpoint( checkpoint = Checkpoint(
identifier=f"{self.model_id}-sft-{curr_epoch}", identifier=f"{self.model_id}-sft-{curr_epoch}",
created_at=datetime.now(timezone.utc), created_at=datetime.now(UTC),
epoch=curr_epoch, epoch=curr_epoch,
post_training_job_id=self.job_uuid, post_training_job_id=self.job_uuid,
path=checkpoint_path, path=checkpoint_path,

View file

@ -5,7 +5,7 @@
# the root directory of this source tree. # the root directory of this source tree.
import json import json
from datetime import datetime, timezone from datetime import UTC, datetime
from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace.export import SpanProcessor from opentelemetry.sdk.trace.export import SpanProcessor
@ -34,7 +34,7 @@ class ConsoleSpanProcessor(SpanProcessor):
if span.attributes and span.attributes.get("__autotraced__"): if span.attributes and span.attributes.get("__autotraced__"):
return return
timestamp = datetime.fromtimestamp(span.start_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] timestamp = datetime.fromtimestamp(span.start_time / 1e9, tz=UTC).strftime("%H:%M:%S.%f")[:-3]
print( print(
f"{COLORS['dim']}{timestamp}{COLORS['reset']} " f"{COLORS['dim']}{timestamp}{COLORS['reset']} "
@ -46,7 +46,7 @@ class ConsoleSpanProcessor(SpanProcessor):
if span.attributes and span.attributes.get("__autotraced__"): if span.attributes and span.attributes.get("__autotraced__"):
return return
timestamp = datetime.fromtimestamp(span.end_time / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] timestamp = datetime.fromtimestamp(span.end_time / 1e9, tz=UTC).strftime("%H:%M:%S.%f")[:-3]
span_context = ( span_context = (
f"{COLORS['dim']}{timestamp}{COLORS['reset']} " f"{COLORS['dim']}{timestamp}{COLORS['reset']} "
@ -74,7 +74,7 @@ class ConsoleSpanProcessor(SpanProcessor):
print(f" {COLORS['dim']}{key}: {str_value}{COLORS['reset']}") print(f" {COLORS['dim']}{key}: {str_value}{COLORS['reset']}")
for event in span.events: for event in span.events:
event_time = datetime.fromtimestamp(event.timestamp / 1e9, tz=timezone.utc).strftime("%H:%M:%S.%f")[:-3] event_time = datetime.fromtimestamp(event.timestamp / 1e9, tz=UTC).strftime("%H:%M:%S.%f")[:-3]
severity = event.attributes.get("severity", "info") severity = event.attributes.get("severity", "info")
message = event.attributes.get("message", event.name) message = event.attributes.get("message", event.name)

View file

@ -8,7 +8,7 @@ import json
import os import os
import sqlite3 import sqlite3
import threading import threading
from datetime import datetime, timezone from datetime import UTC, datetime
from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.sdk.trace import SpanProcessor
from opentelemetry.trace import Span from opentelemetry.trace import Span
@ -125,8 +125,8 @@ class SQLiteSpanProcessor(SpanProcessor):
trace_id, trace_id,
service_name, service_name,
(span_id if span.attributes.get("__root_span__") == "true" else None), (span_id if span.attributes.get("__root_span__") == "true" else None),
datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(), datetime.fromtimestamp(span.start_time / 1e9, UTC).isoformat(),
datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(), datetime.fromtimestamp(span.end_time / 1e9, UTC).isoformat(),
), ),
) )
@ -144,8 +144,8 @@ class SQLiteSpanProcessor(SpanProcessor):
trace_id, trace_id,
parent_span_id, parent_span_id,
span.name, span.name,
datetime.fromtimestamp(span.start_time / 1e9, timezone.utc).isoformat(), datetime.fromtimestamp(span.start_time / 1e9, UTC).isoformat(),
datetime.fromtimestamp(span.end_time / 1e9, timezone.utc).isoformat(), datetime.fromtimestamp(span.end_time / 1e9, UTC).isoformat(),
json.dumps(dict(span.attributes)), json.dumps(dict(span.attributes)),
span.status.status_code.name, span.status.status_code.name,
span.kind.name, span.kind.name,
@ -162,7 +162,7 @@ class SQLiteSpanProcessor(SpanProcessor):
( (
span_id, span_id,
event.name, event.name,
datetime.fromtimestamp(event.timestamp / 1e9, timezone.utc).isoformat(), datetime.fromtimestamp(event.timestamp / 1e9, UTC).isoformat(),
json.dumps(dict(event.attributes)), json.dumps(dict(event.attributes)),
), ),
) )

View file

@ -87,9 +87,7 @@ class RefreshableBotoSession:
"access_key": session_credentials.access_key, "access_key": session_credentials.access_key,
"secret_key": session_credentials.secret_key, "secret_key": session_credentials.secret_key,
"token": session_credentials.token, "token": session_credentials.token,
"expiry_time": datetime.datetime.fromtimestamp( "expiry_time": datetime.datetime.fromtimestamp(time() + self.session_ttl, datetime.UTC).isoformat(),
time() + self.session_ttl, datetime.timezone.utc
).isoformat(),
} }
return credentials return credentials

View file

@ -5,7 +5,7 @@
# the root directory of this source tree. # the root directory of this source tree.
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from datetime import datetime, timezone from datetime import UTC, datetime
from typing import Any from typing import Any
from llama_stack.apis.inference import ( from llama_stack.apis.inference import (
@ -122,7 +122,7 @@ async def stream_and_store_openai_completion(
final_response = OpenAIChatCompletion( final_response = OpenAIChatCompletion(
id=id, id=id,
choices=assembled_choices, choices=assembled_choices,
created=created or int(datetime.now(timezone.utc).timestamp()), created=created or int(datetime.now(UTC).timestamp()),
model=model, model=model,
object="chat.completion", object="chat.completion",
) )

View file

@ -9,7 +9,7 @@ import asyncio
import functools import functools
import threading import threading
from collections.abc import Callable, Coroutine, Iterable from collections.abc import Callable, Coroutine, Iterable
from datetime import datetime, timezone from datetime import UTC, datetime
from enum import Enum from enum import Enum
from typing import Any, TypeAlias from typing import Any, TypeAlias
@ -61,7 +61,7 @@ class Job:
self._handler = handler self._handler = handler
self._artifacts: list[JobArtifact] = [] self._artifacts: list[JobArtifact] = []
self._logs: list[LogMessage] = [] self._logs: list[LogMessage] = []
self._state_transitions: list[tuple[datetime, JobStatus]] = [(datetime.now(timezone.utc), JobStatus.new)] self._state_transitions: list[tuple[datetime, JobStatus]] = [(datetime.now(UTC), JobStatus.new)]
@property @property
def handler(self) -> JobHandler: def handler(self) -> JobHandler:
@ -77,7 +77,7 @@ class Job:
raise ValueError(f"Job is already in a completed state ({self.status})") raise ValueError(f"Job is already in a completed state ({self.status})")
if self.status == status: if self.status == status:
return return
self._state_transitions.append((datetime.now(timezone.utc), status)) self._state_transitions.append((datetime.now(UTC), status))
@property @property
def artifacts(self) -> list[JobArtifact]: def artifacts(self) -> list[JobArtifact]:
@ -215,7 +215,7 @@ class Scheduler:
self._backend = _get_backend_impl(backend) self._backend = _get_backend_impl(backend)
def _on_log_message_cb(self, job: Job, message: str) -> None: def _on_log_message_cb(self, job: Job, message: str) -> None:
msg = (datetime.now(timezone.utc), message) msg = (datetime.now(UTC), message)
# At least for the time being, until there's a better way to expose # At least for the time being, until there's a better way to expose
# logs to users, log messages on console # logs to users, log messages on console
logger.info(f"Job {job.id}: {message}") logger.info(f"Job {job.id}: {message}")

View file

@ -11,7 +11,7 @@ import queue
import random import random
import threading import threading
from collections.abc import Callable from collections.abc import Callable
from datetime import datetime, timezone from datetime import UTC, datetime
from functools import wraps from functools import wraps
from typing import Any from typing import Any
@ -121,7 +121,7 @@ class TraceContext:
span_id=generate_span_id(), span_id=generate_span_id(),
trace_id=self.trace_id, trace_id=self.trace_id,
name=name, name=name,
start_time=datetime.now(timezone.utc), start_time=datetime.now(UTC),
parent_span_id=current_span.span_id if current_span else None, parent_span_id=current_span.span_id if current_span else None,
attributes=attributes, attributes=attributes,
) )
@ -239,7 +239,7 @@ class TelemetryHandler(logging.Handler):
UnstructuredLogEvent( UnstructuredLogEvent(
trace_id=span.trace_id, trace_id=span.trace_id,
span_id=span.span_id, span_id=span.span_id,
timestamp=datetime.now(timezone.utc), timestamp=datetime.now(UTC),
message=self.format(record), message=self.format(record),
severity=severity(record.levelname), severity=severity(record.levelname),
) )

View file

@ -8,7 +8,7 @@ version = "0.2.11"
authors = [{ name = "Meta Llama", email = "llama-oss@meta.com" }] authors = [{ name = "Meta Llama", email = "llama-oss@meta.com" }]
description = "Llama Stack" description = "Llama Stack"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.11"
license = { "text" = "MIT" } license = { "text" = "MIT" }
classifiers = [ classifiers = [
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",

View file

@ -6,7 +6,7 @@
# This source code is licensed under the terms described in the LICENSE file in # This source code is licensed under the terms described in the LICENSE file in
# the root directory of this source tree. # the root directory of this source tree.
PYTHON_VERSION=${PYTHON_VERSION:-3.10} PYTHON_VERSION=${PYTHON_VERSION:-3.11}
command -v uv >/dev/null 2>&1 || { echo >&2 "uv is required but it's not installed. Exiting."; exit 1; } command -v uv >/dev/null 2>&1 || { echo >&2 "uv is required but it's not installed. Exiting."; exit 1; }

View file

@ -40,4 +40,4 @@ name = "llama-stack-provider-ollama"
version = "0.1.0" version = "0.1.0"
description = "External provider for Ollama using the Llama Stack API" description = "External provider for Ollama using the Llama Stack API"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.11"

View file

@ -13,7 +13,7 @@ Any additional arguments are passed to pytest. For example, you can specify a te
./scripts/unit-tests.sh tests/unit/registry/test_registry.py -vvv ./scripts/unit-tests.sh tests/unit/registry/test_registry.py -vvv
``` ```
If you'd like to run for a non-default version of Python (currently 3.10), pass `PYTHON_VERSION` variable as follows: If you'd like to run for a non-default version of Python (currently 3.11), pass `PYTHON_VERSION` variable as follows:
``` ```
source .venv/bin/activate source .venv/bin/activate

2
uv.lock generated
View file

@ -1,6 +1,6 @@
version = 1 version = 1
revision = 2 revision = 2
requires-python = ">=3.10" requires-python = ">=3.11"
resolution-markers = [ resolution-markers = [
"(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",