refactor: update terminal api
This commit is contained in:
parent
084f5082ad
commit
11bd4204aa
14 changed files with 1268 additions and 436 deletions
42
API.md
42
API.md
|
|
@ -98,6 +98,7 @@ This document describes the available endpoints and their usage. All endpoints r
|
|||
- [GET /api/system/configuration](#get-apisystemconfiguration)
|
||||
- [GET /api/system/limits](#get-apisystemlimits)
|
||||
- [POST /api/system/terminal](#post-apisystemterminal)
|
||||
- [GET /api/system/terminal](#get-apisystemterminal)
|
||||
- [GET /api/system/terminal/active](#get-apisystemterminalactive)
|
||||
- [GET /api/system/terminal/{session\_id}](#get-apisystemterminalsession_id)
|
||||
- [DELETE /api/system/terminal/{session\_id}](#delete-apisystemterminalsession_id)
|
||||
|
|
@ -2579,6 +2580,36 @@ or an error:
|
|||
|
||||
---
|
||||
|
||||
### GET /api/system/terminal
|
||||
**Purpose**: List recent terminal sessions.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"session_id": "3a8c5f7e2d3b4a8f9c0d1e2f3a4b5c6d",
|
||||
"command": "--help",
|
||||
"status": "completed",
|
||||
"created_at": 1713000000.0,
|
||||
"started_at": 1713000000.0,
|
||||
"finished_at": 1713000004.0,
|
||||
"expires_at": 1713086404.0,
|
||||
"available_until": 1713086404.0,
|
||||
"exit_code": 0,
|
||||
"last_sequence": 15
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Expired sessions are removed automatically later.
|
||||
|
||||
- `403 Forbidden` if console is disabled.
|
||||
|
||||
---
|
||||
|
||||
### GET /api/system/terminal/active
|
||||
**Purpose**: Return the currently active terminal session metadata, or `null` if no session is active.
|
||||
|
||||
|
|
@ -2606,7 +2637,7 @@ or
|
|||
---
|
||||
|
||||
### GET /api/system/terminal/{session_id}
|
||||
**Purpose**: Return metadata for a specific terminal session while it is active or still within the replay/drain window.
|
||||
**Purpose**: Return metadata for a specific terminal session.
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
|
|
@ -2617,7 +2648,7 @@ or
|
|||
"created_at": 1713000000.0,
|
||||
"started_at": 1713000000.0,
|
||||
"finished_at": 1713000004.0,
|
||||
"expires_at": 1713000034.0,
|
||||
"expires_at": 1713086404.0,
|
||||
"exit_code": 0,
|
||||
"last_sequence": 15
|
||||
}
|
||||
|
|
@ -2641,8 +2672,7 @@ or
|
|||
|
||||
**Notes**:
|
||||
- This only applies to the currently active session.
|
||||
- The client should stay attached to the stream to receive the final `close` event and refreshed terminal status.
|
||||
- Cancelled sessions finalize as `interrupted` and remain replayable until the drain window expires.
|
||||
- Cancelled sessions are marked as `interrupted`.
|
||||
|
||||
- `403 Forbidden` if console is disabled.
|
||||
- `404 Not Found` if the session does not exist or has already expired.
|
||||
|
|
@ -2674,10 +2704,6 @@ If both `since` and `Last-Event-ID` are present, the larger value is used.
|
|||
{ "exitcode": 0 }
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- Replay/restore works while the session is still running or until the finished session expires.
|
||||
- Finished sessions are removed lazily after the transcript drain window elapses.
|
||||
|
||||
- `403 Forbidden` if console is disabled.
|
||||
- `400 Bad Request` if the replay cursor is invalid.
|
||||
- `404 Not Found` if the session does not exist or has already expired.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Any
|
|||
from aiohttp import web
|
||||
|
||||
from app.library.config import Config
|
||||
from app.library.Scheduler import Scheduler
|
||||
from app.library.Services import Services
|
||||
from app.library.Singleton import Singleton
|
||||
|
||||
|
|
@ -31,8 +32,10 @@ ACTIVE_FILE_NAME = "active.json"
|
|||
METADATA_FILE_NAME = "metadata.json"
|
||||
TRANSCRIPT_FILE_NAME = "transcript.jsonl"
|
||||
DEFAULT_DRAIN_TTL = 30.0
|
||||
COMPLETED_SESSION_RETENTION = 86400.0
|
||||
DEFAULT_KEEPALIVE_INTERVAL = 15.0
|
||||
DEFAULT_SHUTDOWN_TIMEOUT = 5.0
|
||||
CLEANUP_SCHEDULE = "5 */1 * * *"
|
||||
|
||||
|
||||
class TerminalSessionConflictError(RuntimeError):
|
||||
|
|
@ -55,8 +58,10 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
self._lock = asyncio.Lock()
|
||||
self._active: ActiveTerminalSession | None = None
|
||||
self._drain_ttl: float = DEFAULT_DRAIN_TTL
|
||||
self._completed_retention: float = COMPLETED_SESSION_RETENTION
|
||||
self._keepalive_interval: float = DEFAULT_KEEPALIVE_INTERVAL
|
||||
self._shutdown_timeout: float = DEFAULT_SHUTDOWN_TIMEOUT
|
||||
self._cleanup_job_id: str | None = None
|
||||
|
||||
@staticmethod
|
||||
def get_instance() -> TerminalSessionManager:
|
||||
|
|
@ -67,11 +72,20 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
Services.get_instance().add("terminal_manager", self)
|
||||
app.on_startup.append(self.on_startup)
|
||||
app.on_shutdown.append(self.on_shutdown)
|
||||
self._cleanup_job_id = Scheduler.get_instance().add(
|
||||
timer=CLEANUP_SCHEDULE,
|
||||
func=self.cleanup,
|
||||
id=f"{__class__.__name__}.{__class__.cleanup.__name__}",
|
||||
)
|
||||
|
||||
async def on_startup(self, _: web.Application) -> None:
|
||||
await self.initialize()
|
||||
|
||||
async def on_shutdown(self, _: web.Application) -> None:
|
||||
if self._cleanup_job_id is not None:
|
||||
Scheduler.get_instance().remove(self._cleanup_job_id)
|
||||
self._cleanup_job_id = None
|
||||
|
||||
session_id: str | None = None
|
||||
task: asyncio.Task[None] | None = None
|
||||
|
||||
|
|
@ -121,12 +135,17 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
async def cleanup(self) -> None:
|
||||
async with self._lock:
|
||||
self._cleanup_expired_sessions(time.time())
|
||||
LOG.debug("Running scheduled terminal session cleanup.")
|
||||
removed = self._cleanup_expired_sessions(time.time())
|
||||
LOG.debug("Scheduled terminal session cleanup finished. Removed %d expired sessions.", removed)
|
||||
|
||||
async def list_sessions(self) -> list[dict[str, Any]]:
|
||||
async with self._lock:
|
||||
return self._list_sessions_locked()
|
||||
|
||||
async def create_session(self, command: str) -> dict[str, Any]:
|
||||
async with self._lock:
|
||||
now = time.time()
|
||||
self._cleanup_expired_sessions(now)
|
||||
|
||||
active_session = self._get_active_session_locked()
|
||||
if active_session is not None:
|
||||
|
|
@ -160,22 +179,20 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
async def get_active_session(self) -> dict[str, Any] | None:
|
||||
async with self._lock:
|
||||
self._cleanup_expired_sessions(time.time())
|
||||
metadata = self._get_active_session_locked()
|
||||
return None if metadata is None else dict(metadata)
|
||||
|
||||
async def get_session(self, session_id: str) -> dict[str, Any] | None:
|
||||
async with self._lock:
|
||||
self._cleanup_expired_sessions(time.time())
|
||||
metadata = self._load_metadata(session_id)
|
||||
return None if metadata is None else dict(metadata)
|
||||
if metadata is None or self._is_expired_metadata(metadata, time.time()):
|
||||
return None
|
||||
return dict(metadata)
|
||||
|
||||
async def cancel_session(self, session_id: str) -> dict[str, Any]:
|
||||
async with self._lock:
|
||||
self._cleanup_expired_sessions(time.time())
|
||||
|
||||
metadata = self._load_metadata(session_id)
|
||||
if metadata is None:
|
||||
if metadata is None or self._is_expired_metadata(metadata, time.time()):
|
||||
msg = f"Unknown terminal session '{session_id}'."
|
||||
raise FileNotFoundError(msg)
|
||||
|
||||
|
|
@ -218,6 +235,9 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
async with self._lock:
|
||||
metadata = self._load_metadata(session_id)
|
||||
if metadata is not None:
|
||||
if self._is_expired_metadata(metadata, time.time()):
|
||||
return response
|
||||
|
||||
replay_until = int(metadata.get("last_sequence", last_sent))
|
||||
|
||||
runtime = self._active
|
||||
|
|
@ -443,7 +463,7 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
metadata["status"] = status
|
||||
metadata["exit_code"] = exit_code
|
||||
metadata["finished_at"] = now
|
||||
metadata["expires_at"] = now + self._drain_ttl
|
||||
metadata["expires_at"] = now + self._completed_retention
|
||||
self._write_json(self._metadata_path(session_id), metadata)
|
||||
|
||||
if self._load_active_marker() == session_id:
|
||||
|
|
@ -468,6 +488,31 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
return metadata
|
||||
|
||||
def _list_sessions_locked(self) -> list[dict[str, Any]]:
|
||||
self._ensure_root()
|
||||
items: list[dict[str, Any]] = []
|
||||
|
||||
for path in self.root_path.iterdir():
|
||||
if path.name == ACTIVE_FILE_NAME or path.is_dir() is False:
|
||||
continue
|
||||
|
||||
metadata = self._load_metadata(path.name)
|
||||
if metadata is None:
|
||||
continue
|
||||
|
||||
if self._is_expired_metadata(metadata, time.time()):
|
||||
continue
|
||||
|
||||
item = dict(metadata)
|
||||
item["available_until"] = self._available_until(metadata)
|
||||
items.append(item)
|
||||
|
||||
items.sort(
|
||||
key=lambda item: float(item.get("finished_at") or item.get("started_at") or item.get("created_at") or 0),
|
||||
reverse=True,
|
||||
)
|
||||
return items
|
||||
|
||||
def _recover_orphaned_active_session(self, now: float) -> None:
|
||||
session_id = self._load_active_marker()
|
||||
if session_id is None:
|
||||
|
|
@ -480,14 +525,15 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
metadata["status"] = "interrupted"
|
||||
metadata["finished_at"] = now
|
||||
metadata["expires_at"] = now + self._drain_ttl
|
||||
metadata["expires_at"] = now + self._completed_retention
|
||||
metadata["exit_code"] = -1 if metadata.get("exit_code") is None else metadata["exit_code"]
|
||||
self._write_json(self._metadata_path(session_id), metadata)
|
||||
self._clear_active_marker()
|
||||
|
||||
def _cleanup_expired_sessions(self, now: float) -> None:
|
||||
def _cleanup_expired_sessions(self, now: float) -> int:
|
||||
self._ensure_root()
|
||||
active_session_id = self._load_active_marker()
|
||||
removed = 0
|
||||
|
||||
for path in self.root_path.iterdir():
|
||||
if path.name == ACTIVE_FILE_NAME or path.is_dir() is False:
|
||||
|
|
@ -496,10 +542,10 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
metadata = self._load_metadata(path.name)
|
||||
if metadata is None:
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
removed += 1
|
||||
continue
|
||||
|
||||
expires_at = metadata.get("expires_at")
|
||||
if expires_at is None or float(expires_at) > now:
|
||||
if not self._is_expired_metadata(metadata, now):
|
||||
continue
|
||||
|
||||
if active_session_id == path.name:
|
||||
|
|
@ -508,6 +554,10 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
|
||||
removed += 1
|
||||
|
||||
return removed
|
||||
|
||||
def _parse_since(self, request: Request) -> int:
|
||||
values: list[int] = []
|
||||
candidates = [request.query.get("since"), request.headers.get("Last-Event-ID")]
|
||||
|
|
@ -537,7 +587,7 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
now = time.time()
|
||||
metadata["status"] = "interrupted"
|
||||
metadata["finished_at"] = now
|
||||
metadata["expires_at"] = now + self._drain_ttl
|
||||
metadata["expires_at"] = now + self._completed_retention
|
||||
metadata["exit_code"] = -1 if metadata.get("exit_code") is None else metadata["exit_code"]
|
||||
self._write_json(self._metadata_path(session_id), metadata)
|
||||
|
||||
|
|
@ -618,6 +668,20 @@ class TerminalSessionManager(metaclass=Singleton):
|
|||
|
||||
return events
|
||||
|
||||
def _available_until(self, metadata: dict[str, Any]) -> float | None:
|
||||
expires_at = metadata.get("expires_at")
|
||||
if expires_at is None:
|
||||
return None
|
||||
|
||||
return float(expires_at)
|
||||
|
||||
def _is_expired_metadata(self, metadata: dict[str, Any], now: float) -> bool:
|
||||
expires_at = self._available_until(metadata)
|
||||
if expires_at is None:
|
||||
return False
|
||||
|
||||
return expires_at <= now
|
||||
|
||||
def _build_env(self) -> dict[str, str]:
|
||||
env_vars = os.environ.copy()
|
||||
env_vars.update(
|
||||
|
|
|
|||
|
|
@ -274,6 +274,20 @@ async def create_terminal_session(
|
|||
return web.json_response(data=metadata, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
||||
|
||||
|
||||
@route("GET", "api/system/terminal", "system.terminal.list")
|
||||
async def list_terminal_sessions(
|
||||
config: Config, encoder: Encoder, terminal_manager: TerminalSessionManager
|
||||
) -> Response:
|
||||
if not config.console_enabled:
|
||||
return web.json_response(
|
||||
{"error": "Console feature is disabled."},
|
||||
status=web.HTTPForbidden.status_code,
|
||||
)
|
||||
|
||||
items = await terminal_manager.list_sessions()
|
||||
return web.json_response(data={"items": items}, status=web.HTTPOk.status_code, dumps=encoder.encode)
|
||||
|
||||
|
||||
@route("GET", "api/system/terminal/active", "system.terminal.active")
|
||||
async def get_active_terminal_session(
|
||||
config: Config, encoder: Encoder, terminal_manager: TerminalSessionManager
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from uuid import uuid4
|
|||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
_TEST_RUN_ROOT = Path(gettempdir()) / "ytptube-tests" / uuid4().hex
|
||||
_TEST_RUN_ROOT = Path(gettempdir()) / "tests-ytptube" / uuid4().hex
|
||||
_TEST_SYSTEM_TEMP_ROOT = _TEST_RUN_ROOT / "tmp"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
import time
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
from aiohttp import web
|
||||
|
||||
from app.library.Scheduler import Scheduler
|
||||
from app.library.Services import Services
|
||||
from app.library.TerminalSessionManager import TerminalSessionManager
|
||||
from app.library.config import Config
|
||||
|
|
@ -13,6 +16,7 @@ from app.routes.api.system import (
|
|||
cancel_terminal_session,
|
||||
create_terminal_session,
|
||||
get_active_terminal_session,
|
||||
list_terminal_sessions,
|
||||
get_terminal_session,
|
||||
stream_terminal_session,
|
||||
)
|
||||
|
|
@ -133,6 +137,7 @@ class _TerminableProc:
|
|||
|
||||
@pytest.fixture
|
||||
def terminal_setup(tmp_path: Path) -> tuple[Config, TerminalSessionManager, Encoder]:
|
||||
Scheduler._reset_singleton()
|
||||
Services._reset_singleton()
|
||||
Config._reset_singleton()
|
||||
TerminalSessionManager._reset_singleton()
|
||||
|
|
@ -150,6 +155,16 @@ def terminal_setup(tmp_path: Path) -> tuple[Config, TerminalSessionManager, Enco
|
|||
|
||||
|
||||
class TestTerminalSessionRoutes:
|
||||
def test_attach_registers_cleanup_job(self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder]) -> None:
|
||||
_config, manager, _encoder = terminal_setup
|
||||
app = web.Application()
|
||||
|
||||
manager.attach(app)
|
||||
|
||||
scheduler = Scheduler.get_instance()
|
||||
assert scheduler.has(f"{TerminalSessionManager.__name__}.{TerminalSessionManager.cleanup.__name__}")
|
||||
assert manager._cleanup_job_id == f"{TerminalSessionManager.__name__}.{TerminalSessionManager.cleanup.__name__}"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_returns_session_metadata_and_active_conflict(
|
||||
self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder], monkeypatch: pytest.MonkeyPatch
|
||||
|
|
@ -245,7 +260,7 @@ class TestTerminalSessionRoutes:
|
|||
self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder], monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
config, manager, encoder = terminal_setup
|
||||
manager._drain_ttl = 0.05
|
||||
manager._completed_retention = 0.05
|
||||
await manager.initialize()
|
||||
|
||||
async def fake_create_subprocess_exec(*_args, **_kwargs):
|
||||
|
|
@ -271,8 +286,130 @@ class TestTerminalSessionRoutes:
|
|||
|
||||
expired = await manager.get_session(session_id)
|
||||
assert expired is None
|
||||
assert (manager.root_path / session_id).exists()
|
||||
|
||||
await manager.cleanup()
|
||||
|
||||
assert not (manager.root_path / session_id).exists()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_returns_recent_completed_session_until_retention_expires(
|
||||
self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder], monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
config, manager, encoder = terminal_setup
|
||||
manager._completed_retention = 0.2
|
||||
manager._drain_ttl = 0.01
|
||||
await manager.initialize()
|
||||
|
||||
async def fake_create_subprocess_exec(*_args, **_kwargs):
|
||||
return _CompletedProc([b"done\n"])
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.library.TerminalSessionManager.asyncio.create_subprocess_exec", fake_create_subprocess_exec
|
||||
)
|
||||
monkeypatch.setattr(manager, "_open_pty", lambda: None)
|
||||
|
||||
start_request = _FakeRequest(payload={"command": "--help"}, can_read_body=True)
|
||||
start_response = await create_terminal_session(start_request, config, encoder, manager)
|
||||
session_id = json.loads(start_response.body.decode("utf-8"))["session_id"]
|
||||
|
||||
assert manager._active is not None
|
||||
await manager._active.task
|
||||
|
||||
await asyncio.sleep(0.03)
|
||||
|
||||
listed_response = await list_terminal_sessions(config, encoder, manager)
|
||||
listed_payload = json.loads(listed_response.body.decode("utf-8"))
|
||||
|
||||
assert 200 == listed_response.status
|
||||
assert 1 == len(listed_payload["items"])
|
||||
assert session_id == listed_payload["items"][0]["session_id"]
|
||||
assert "completed" == listed_payload["items"][0]["status"]
|
||||
assert listed_payload["items"][0]["available_until"] is not None
|
||||
|
||||
persisted = await manager.get_session(session_id)
|
||||
assert persisted is not None
|
||||
|
||||
await asyncio.sleep(0.19)
|
||||
|
||||
expired = await manager.get_session(session_id)
|
||||
assert expired is None
|
||||
assert (manager.root_path / session_id).exists()
|
||||
|
||||
await manager.cleanup()
|
||||
|
||||
assert not (manager.root_path / session_id).exists()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_sessions_orders_newest_first_and_skips_expired_items(
|
||||
self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder]
|
||||
) -> None:
|
||||
config, manager, encoder = terminal_setup
|
||||
await manager.initialize()
|
||||
|
||||
first_id = "first"
|
||||
second_id = "second"
|
||||
expired_id = "expired"
|
||||
|
||||
for session_id in [first_id, second_id, expired_id]:
|
||||
session_dir = manager.root_path / session_id
|
||||
session_dir.mkdir(parents=True, exist_ok=True)
|
||||
manager._transcript_path(session_id).touch(exist_ok=True)
|
||||
|
||||
manager._write_json(
|
||||
manager._metadata_path(first_id),
|
||||
{
|
||||
"session_id": first_id,
|
||||
"command": "--first",
|
||||
"status": "completed",
|
||||
"created_at": 10.0,
|
||||
"started_at": 11.0,
|
||||
"finished_at": 20.0,
|
||||
"expires_at": time.time() + 60,
|
||||
"exit_code": 0,
|
||||
"last_sequence": 2,
|
||||
},
|
||||
)
|
||||
manager._write_json(
|
||||
manager._metadata_path(second_id),
|
||||
{
|
||||
"session_id": second_id,
|
||||
"command": "--second",
|
||||
"status": "running",
|
||||
"created_at": 30.0,
|
||||
"started_at": 31.0,
|
||||
"finished_at": None,
|
||||
"expires_at": None,
|
||||
"exit_code": None,
|
||||
"last_sequence": 4,
|
||||
},
|
||||
)
|
||||
manager._write_json(
|
||||
manager._metadata_path(expired_id),
|
||||
{
|
||||
"session_id": expired_id,
|
||||
"command": "--expired",
|
||||
"status": "completed",
|
||||
"created_at": 1.0,
|
||||
"started_at": 2.0,
|
||||
"finished_at": 3.0,
|
||||
"expires_at": time.time() - 1,
|
||||
"exit_code": 1,
|
||||
"last_sequence": 1,
|
||||
},
|
||||
)
|
||||
|
||||
listed_response = await list_terminal_sessions(config, encoder, manager)
|
||||
listed_payload = json.loads(listed_response.body.decode("utf-8"))
|
||||
|
||||
assert 200 == listed_response.status
|
||||
assert [second_id, first_id] == [item["session_id"] for item in listed_payload["items"]]
|
||||
assert (manager.root_path / expired_id).exists()
|
||||
|
||||
await manager.cleanup()
|
||||
|
||||
assert not (manager.root_path / expired_id).exists()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shutdown_interrupts_active_session_and_clears_active_marker(
|
||||
self, terminal_setup: tuple[Config, TerminalSessionManager, Encoder], monkeypatch: pytest.MonkeyPatch
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { EventSourceMessage } from '@microsoft/fetch-event-source';
|
|||
import { useStorage } from '@vueuse/core';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import type { TerminalSessionItem, TerminalSessionsResponse } from '~/types';
|
||||
import { parse_api_error, parse_api_response, request, uri } from '~/utils';
|
||||
|
||||
type ConsoleSessionStatus =
|
||||
|
|
@ -48,10 +49,25 @@ type CancelConsoleSessionResult = {
|
|||
message?: string;
|
||||
};
|
||||
|
||||
type RecentSessionItem = {
|
||||
sessionId: string;
|
||||
command: string;
|
||||
status: 'starting' | 'running' | 'completed';
|
||||
createdAt: number | null;
|
||||
startedAt: number | null;
|
||||
finishedAt: number | null;
|
||||
expiresAt: number | null;
|
||||
availableUntil: number | null;
|
||||
exitCode: number | null;
|
||||
lastSequence: number;
|
||||
};
|
||||
|
||||
const STORAGE_KEY = 'console_session_state';
|
||||
const HIDDEN_RECENT_SESSIONS_KEY = 'console_hidden_recent_sessions';
|
||||
const MAX_TRANSCRIPT_CHUNKS = 1500;
|
||||
const MAX_TRANSCRIPT_CHARS = 120000;
|
||||
const RECONNECT_DELAY_MS = 1500;
|
||||
const RECENT_SESSIONS_MAX = 30;
|
||||
|
||||
const DEFAULT_STATE: ConsoleSessionState = {
|
||||
sessionId: null,
|
||||
|
|
@ -160,6 +176,8 @@ const normalizePersistedState = (
|
|||
};
|
||||
|
||||
const sessionState = useStorage<ConsoleSessionState>(STORAGE_KEY, { ...DEFAULT_STATE });
|
||||
const hiddenRecentSessions = useStorage<Array<string>>(HIDDEN_RECENT_SESSIONS_KEY, []);
|
||||
const recentSessions = ref<Array<RecentSessionItem>>([]);
|
||||
const streamController = ref<AbortController | null>(null);
|
||||
const reconnectTimer = ref<ReturnType<typeof setTimeout> | null>(null);
|
||||
const connectionNonce = ref(0);
|
||||
|
|
@ -176,6 +194,68 @@ const updateState = (patch: Partial<ConsoleSessionState>): void => {
|
|||
);
|
||||
};
|
||||
|
||||
const normalizeHiddenRecentSessions = (): Array<string> => {
|
||||
return hiddenRecentSessions.value
|
||||
.filter((value): value is string => typeof value === 'string')
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => Boolean(value));
|
||||
};
|
||||
|
||||
const setHiddenRecentSessions = (items: Array<string>): void => {
|
||||
hiddenRecentSessions.value = Array.from(new Set(items));
|
||||
};
|
||||
|
||||
const isRecentSessionHidden = (sessionId: string): boolean => {
|
||||
return normalizeHiddenRecentSessions().includes(sessionId);
|
||||
};
|
||||
|
||||
const sortRecentSessions = (items: Array<RecentSessionItem>): Array<RecentSessionItem> => {
|
||||
return items.slice().sort((left, right) => {
|
||||
const leftTime = left.finishedAt ?? left.startedAt ?? left.createdAt ?? 0;
|
||||
const rightTime = right.finishedAt ?? right.startedAt ?? right.createdAt ?? 0;
|
||||
return rightTime - leftTime;
|
||||
});
|
||||
};
|
||||
|
||||
const trimRecentSessions = (items: Array<RecentSessionItem>): Array<RecentSessionItem> => {
|
||||
return items.slice(0, RECENT_SESSIONS_MAX);
|
||||
};
|
||||
|
||||
const setRecentSessions = (items: Array<RecentSessionItem>): void => {
|
||||
recentSessions.value = trimRecentSessions(
|
||||
sortRecentSessions(items.filter((item) => !isRecentSessionHidden(item.sessionId))),
|
||||
);
|
||||
};
|
||||
|
||||
const upsertRecentSession = (item: RecentSessionItem): void => {
|
||||
if (isRecentSessionHidden(item.sessionId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setRecentSessions([
|
||||
item,
|
||||
...recentSessions.value.filter((entry) => entry.sessionId !== item.sessionId),
|
||||
]);
|
||||
};
|
||||
|
||||
const hideRecentSession = (sessionId: string): void => {
|
||||
const normalized = sessionId.trim();
|
||||
if (!normalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
setHiddenRecentSessions([...normalizeHiddenRecentSessions(), normalized]);
|
||||
setRecentSessions(recentSessions.value.filter((item) => item.sessionId !== normalized));
|
||||
};
|
||||
|
||||
const clearRecentSessions = (): void => {
|
||||
setHiddenRecentSessions([
|
||||
...normalizeHiddenRecentSessions(),
|
||||
...recentSessions.value.map((item) => item.sessionId),
|
||||
]);
|
||||
recentSessions.value = [];
|
||||
};
|
||||
|
||||
const appendTranscript = (chunk: string): void => {
|
||||
if (!chunk) {
|
||||
return;
|
||||
|
|
@ -215,6 +295,7 @@ const clearTranscript = (): void => {
|
|||
const markExpired = (): void => {
|
||||
stopStream();
|
||||
updateState({ status: 'expired', error: '' });
|
||||
syncCurrentSessionToRecentSessions();
|
||||
};
|
||||
|
||||
const finishSession = (
|
||||
|
|
@ -227,6 +308,7 @@ const finishSession = (
|
|||
exitCode,
|
||||
error: '',
|
||||
});
|
||||
syncCurrentSessionToRecentSessions();
|
||||
};
|
||||
|
||||
const scheduleReconnect = (): void => {
|
||||
|
|
@ -289,6 +371,97 @@ const parseResponseError = async (response: Response): Promise<string> => {
|
|||
return response.statusText || 'Request failed.';
|
||||
};
|
||||
|
||||
const normalizeRecentSessionStatus = (
|
||||
status: string | null | undefined,
|
||||
): RecentSessionItem['status'] => {
|
||||
switch (normalizeStatus(status, 'finished')) {
|
||||
case 'starting':
|
||||
return 'starting';
|
||||
|
||||
case 'running':
|
||||
case 'reconnecting':
|
||||
return 'running';
|
||||
|
||||
default:
|
||||
return 'completed';
|
||||
}
|
||||
};
|
||||
|
||||
const recentSessionFromPayload = (payload: TerminalSessionItem): RecentSessionItem | null => {
|
||||
if (typeof payload.session_id !== 'string' || !payload.session_id.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof payload.command !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
sessionId: payload.session_id,
|
||||
command: payload.command,
|
||||
status: normalizeRecentSessionStatus(payload.status),
|
||||
createdAt: typeof payload.created_at === 'number' ? payload.created_at : null,
|
||||
startedAt: typeof payload.started_at === 'number' ? payload.started_at : null,
|
||||
finishedAt: typeof payload.finished_at === 'number' ? payload.finished_at : null,
|
||||
expiresAt: typeof payload.expires_at === 'number' ? payload.expires_at : null,
|
||||
availableUntil: typeof payload.available_until === 'number' ? payload.available_until : null,
|
||||
exitCode: typeof payload.exit_code === 'number' ? payload.exit_code : null,
|
||||
lastSequence: typeof payload.last_sequence === 'number' ? payload.last_sequence : 0,
|
||||
};
|
||||
};
|
||||
|
||||
const syncCurrentSessionToRecentSessions = (): void => {
|
||||
if (!sessionState.value.sessionId || !sessionState.value.command.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existing =
|
||||
recentSessions.value.find((item) => item.sessionId === sessionState.value.sessionId) ?? null;
|
||||
const isFinished = ['finished', 'interrupted', 'error', 'expired'].includes(
|
||||
sessionState.value.status,
|
||||
);
|
||||
const lastSequence =
|
||||
sessionState.value.lastEventId && /^\d+$/.test(sessionState.value.lastEventId)
|
||||
? Number(sessionState.value.lastEventId)
|
||||
: (existing?.lastSequence ?? 0);
|
||||
|
||||
upsertRecentSession({
|
||||
sessionId: sessionState.value.sessionId,
|
||||
command: sessionState.value.command,
|
||||
status: normalizeRecentSessionStatus(sessionState.value.status),
|
||||
createdAt: existing?.createdAt ?? null,
|
||||
startedAt: existing?.startedAt ?? null,
|
||||
finishedAt: isFinished
|
||||
? (existing?.finishedAt ?? Date.now() / 1000)
|
||||
: (existing?.finishedAt ?? null),
|
||||
expiresAt: existing?.expiresAt ?? null,
|
||||
availableUntil: existing?.availableUntil ?? null,
|
||||
exitCode: sessionState.value.exitCode ?? existing?.exitCode ?? null,
|
||||
lastSequence,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchRecentSessions = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await request('/api/system/terminal');
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = await parse_api_response<TerminalSessionsResponse>(response.json());
|
||||
const items = Array.isArray(payload?.items)
|
||||
? payload.items
|
||||
.map((item) => recentSessionFromPayload(item))
|
||||
.filter((item): item is RecentSessionItem => item !== null)
|
||||
: [];
|
||||
|
||||
setRecentSessions(items);
|
||||
syncCurrentSessionToRecentSessions();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshSessionMetadata = async (): Promise<void> => {
|
||||
if (!sessionState.value.sessionId) {
|
||||
return;
|
||||
|
|
@ -303,10 +476,14 @@ const refreshSessionMetadata = async (): Promise<void> => {
|
|||
}
|
||||
|
||||
updateState(normalizeResponse(payload));
|
||||
syncCurrentSessionToRecentSessions();
|
||||
} catch (error) {
|
||||
if (error instanceof ConsoleSessionExpiredError) {
|
||||
markExpired();
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -400,6 +577,7 @@ const connectStream = async (): Promise<void> => {
|
|||
onopen: async (response) => {
|
||||
if (response.ok) {
|
||||
updateState({ status: 'running', error: '' });
|
||||
syncCurrentSessionToRecentSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -434,6 +612,7 @@ const connectStream = async (): Promise<void> => {
|
|||
|
||||
if (event.event === 'status') {
|
||||
updateState(normalizeResponse(payload as ConsoleSessionResponse));
|
||||
syncCurrentSessionToRecentSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -501,6 +680,7 @@ const connectStream = async (): Promise<void> => {
|
|||
const message = error instanceof Error ? error.message : String(error);
|
||||
appendTranscript(`Error: ${message}\n`);
|
||||
updateState({ status: 'error', error: message });
|
||||
syncCurrentSessionToRecentSessions();
|
||||
} finally {
|
||||
if (streamController.value === controller) {
|
||||
streamController.value = null;
|
||||
|
|
@ -517,12 +697,15 @@ const restoreSession = async (): Promise<void> => {
|
|||
|
||||
if (payload) {
|
||||
updateState(normalizeResponse(payload));
|
||||
syncCurrentSessionToRecentSessions();
|
||||
}
|
||||
|
||||
if (sessionState.value.sessionId && isActiveSessionStatus(sessionState.value.status)) {
|
||||
await connectStream();
|
||||
}
|
||||
|
||||
await fetchRecentSessions();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -531,28 +714,36 @@ const restoreSession = async (): Promise<void> => {
|
|||
sessionState.value.command ||
|
||||
sessionState.value.transcript.length > 0
|
||||
) {
|
||||
await fetchRecentSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
const active = await readSessionResponse('/api/system/terminal/active', { allowMissing: true });
|
||||
if (!active) {
|
||||
await fetchRecentSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
updateState(normalizeResponse(active));
|
||||
syncCurrentSessionToRecentSessions();
|
||||
|
||||
if (sessionState.value.sessionId && isActiveSessionStatus(sessionState.value.status)) {
|
||||
await connectStream();
|
||||
}
|
||||
|
||||
await fetchRecentSessions();
|
||||
} catch (error) {
|
||||
if (error instanceof ConsoleSessionExpiredError) {
|
||||
markExpired();
|
||||
await fetchRecentSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionState.value.sessionId && isActiveSessionStatus(sessionState.value.status)) {
|
||||
await connectStream();
|
||||
}
|
||||
|
||||
await fetchRecentSessions();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -594,16 +785,20 @@ const startSession = async ({
|
|||
]),
|
||||
error: '',
|
||||
});
|
||||
syncCurrentSessionToRecentSessions();
|
||||
|
||||
if (sessionState.value.sessionId && isActiveSessionStatus(sessionState.value.status)) {
|
||||
await connectStream();
|
||||
}
|
||||
|
||||
await fetchRecentSessions();
|
||||
|
||||
return Boolean(sessionState.value.sessionId);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
appendTranscript(`Error: ${message}\n`);
|
||||
updateState({ status: 'error', error: message });
|
||||
syncCurrentSessionToRecentSessions();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -623,6 +818,7 @@ const cancelSession = async (): Promise<CancelConsoleSessionResult> => {
|
|||
|
||||
if (response.status === 404) {
|
||||
await refreshSessionMetadata();
|
||||
await fetchRecentSessions();
|
||||
return { status: 'missing', message: 'Terminal session not found.' };
|
||||
}
|
||||
|
||||
|
|
@ -631,6 +827,7 @@ const cancelSession = async (): Promise<CancelConsoleSessionResult> => {
|
|||
}
|
||||
|
||||
updateState({ error: '' });
|
||||
await fetchRecentSessions();
|
||||
return { status: 'cancelled' };
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
|
@ -642,6 +839,32 @@ const cancelSession = async (): Promise<CancelConsoleSessionResult> => {
|
|||
const resetState = (): void => {
|
||||
stopStream();
|
||||
sessionState.value = { ...DEFAULT_STATE };
|
||||
recentSessions.value = [];
|
||||
hiddenRecentSessions.value = [];
|
||||
};
|
||||
|
||||
const replaySession = async (item: RecentSessionItem): Promise<boolean> => {
|
||||
stopStream();
|
||||
updateState({
|
||||
sessionId: item.sessionId,
|
||||
command: item.command,
|
||||
status: item.status === 'running' ? 'reconnecting' : 'finished',
|
||||
lastEventId: null,
|
||||
exitCode: item.exitCode,
|
||||
transcript: [],
|
||||
error: '',
|
||||
});
|
||||
syncCurrentSessionToRecentSessions();
|
||||
|
||||
await connectStream();
|
||||
await fetchRecentSessions();
|
||||
|
||||
if (sessionState.value.status === 'expired') {
|
||||
hideRecentSession(item.sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return sessionState.value.status !== 'error';
|
||||
};
|
||||
|
||||
const useConsoleSession = () => {
|
||||
|
|
@ -649,7 +872,12 @@ const useConsoleSession = () => {
|
|||
state: sessionState,
|
||||
bufferedTranscript: computed(() => sessionState.value.transcript.slice()),
|
||||
isLoading: computed(() => isActiveSessionStatus(sessionState.value.status)),
|
||||
recentSessions: computed(() => recentSessions.value.slice()),
|
||||
clearTranscript,
|
||||
clearRecentSessions,
|
||||
fetchRecentSessions,
|
||||
hideRecentSession,
|
||||
replaySession,
|
||||
restoreSession,
|
||||
startSession,
|
||||
cancelSession,
|
||||
|
|
|
|||
|
|
@ -177,27 +177,27 @@
|
|||
class="flex items-center gap-2 text-sm font-semibold text-highlighted"
|
||||
>
|
||||
<UIcon name="i-lucide-history" class="size-4 text-toned" />
|
||||
<span>Command history</span>
|
||||
<span>Recent runs</span>
|
||||
</div>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
icon="i-lucide-trash"
|
||||
:disabled="historyEntries.length < 1"
|
||||
@click="() => void clearHistory()"
|
||||
icon="i-lucide-eye-off"
|
||||
:disabled="recentSessionEntries.length < 1"
|
||||
@click="() => void hideRecentSessions()"
|
||||
>
|
||||
Clear history
|
||||
Hide recent runs
|
||||
</UButton>
|
||||
</div>
|
||||
|
||||
<UAlert
|
||||
v-if="historyEntries.length < 1"
|
||||
v-if="recentSessionEntries.length < 1"
|
||||
color="info"
|
||||
variant="soft"
|
||||
icon="i-lucide-clock-3"
|
||||
title="Command history is empty"
|
||||
title="No recent console sessions"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
|
@ -205,34 +205,119 @@
|
|||
class="max-h-96 w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
|
||||
>
|
||||
<div class="w-full max-w-full overflow-auto overscroll-contain">
|
||||
<table class="min-w-155 w-full text-sm">
|
||||
<table class="w-full text-sm">
|
||||
<tbody class="divide-y divide-default">
|
||||
<tr
|
||||
v-for="(cmd, index) in historyEntries"
|
||||
:key="`${index}-${cmd}`"
|
||||
v-for="item in recentSessionEntries"
|
||||
:key="item.sessionId"
|
||||
class="transition-colors hover:bg-elevated/70 [&>td]:border-r [&>td]:border-default/60 [&>td:last-child]:border-r-0"
|
||||
>
|
||||
<td class="px-3 py-3 align-middle">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-left font-mono text-xs text-default hover:text-highlighted"
|
||||
@click="() => void loadCommand(cmd)"
|
||||
>
|
||||
{{ cmd.replace(/\n/g, ' ') }}
|
||||
</button>
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-left font-mono text-xs text-default hover:text-highlighted"
|
||||
@click="() => void replayRecentSession(item)"
|
||||
>
|
||||
{{ item.command }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-2 text-[11px] text-toned"
|
||||
>
|
||||
<UBadge
|
||||
:color="recentSessionStatusColor(item)"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
>
|
||||
<span
|
||||
v-if="recentSessionStatusSpinning(item)"
|
||||
class="inline-flex items-center gap-1.5"
|
||||
>
|
||||
<UIcon
|
||||
:name="recentSessionStatusIcon(item)"
|
||||
class="size-3.5 animate-spin"
|
||||
/>
|
||||
<span>{{ recentSessionStatusLabel(item) }}</span>
|
||||
</span>
|
||||
<span v-else class="inline-flex items-center gap-1.5">
|
||||
<UIcon
|
||||
:name="recentSessionStatusIcon(item)"
|
||||
class="size-3.5"
|
||||
/>
|
||||
<span>{{ recentSessionStatusLabel(item) }}</span>
|
||||
</span>
|
||||
</UBadge>
|
||||
|
||||
<UTooltip
|
||||
v-if="item.finishedAt || item.startedAt || item.createdAt"
|
||||
:text="
|
||||
moment
|
||||
.unix(
|
||||
item.finishedAt ||
|
||||
item.startedAt ||
|
||||
item.createdAt ||
|
||||
0,
|
||||
)
|
||||
.format('YYYY-M-DD H:mm Z')
|
||||
"
|
||||
>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 cursor-help"
|
||||
>
|
||||
<UIcon name="i-lucide-clock-3" class="size-3.5" />
|
||||
<time
|
||||
:datetime="
|
||||
moment
|
||||
.unix(
|
||||
item.finishedAt ||
|
||||
item.startedAt ||
|
||||
item.createdAt ||
|
||||
0,
|
||||
)
|
||||
.toISOString()
|
||||
"
|
||||
>
|
||||
{{
|
||||
moment
|
||||
.unix(
|
||||
item.finishedAt ||
|
||||
item.startedAt ||
|
||||
item.createdAt ||
|
||||
0,
|
||||
)
|
||||
.fromNow()
|
||||
}}
|
||||
</time>
|
||||
</span>
|
||||
</UTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td
|
||||
class="w-12 px-3 py-3 text-center align-middle whitespace-nowrap"
|
||||
class="w-28 px-3 py-3 text-center align-middle whitespace-nowrap"
|
||||
>
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
icon="i-lucide-x"
|
||||
square
|
||||
@click="removeFromHistory(index)"
|
||||
/>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
icon="i-lucide-terminal"
|
||||
@click="() => void loadCommand(item.command)"
|
||||
>
|
||||
Fill
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
color="neutral"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
icon="i-lucide-x"
|
||||
square
|
||||
@click="hideRecentSession(item.sessionId)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -315,6 +400,7 @@
|
|||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import moment from 'moment';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
|
|
@ -327,7 +413,6 @@ import type { AutoCompleteOptions } from '~/types/autocomplete';
|
|||
import { disableOpacity, enableOpacity } from '~/utils';
|
||||
import { requirePageShell } from '~/utils/topLevelNavigation';
|
||||
|
||||
const MAX_HISTORY_ITEMS = 50;
|
||||
const ACTIVE_SESSION_STATUSES = ['starting', 'running', 'reconnecting'];
|
||||
|
||||
let flushFrame: number | null = null;
|
||||
|
|
@ -351,7 +436,6 @@ const terminal_window = useTemplateRef<HTMLDivElement>('terminal_window');
|
|||
const commandInput = ref<InstanceType<typeof InputAutocomplete> | null>(null);
|
||||
const commandTextarea = ref<InstanceType<typeof TextareaAutocomplete> | null>(null);
|
||||
const storedCommand = useStorage<string>('console_command', '');
|
||||
const commandHistory = useStorage<string[]>('console_command_history', []);
|
||||
|
||||
const pageCardUi = {
|
||||
root: 'flex min-h-0 flex-1 w-full bg-transparent',
|
||||
|
|
@ -371,6 +455,7 @@ const ytDlpOptions = computed<AutoCompleteOptions>(() =>
|
|||
);
|
||||
|
||||
const bufferedTranscript = consoleSession.bufferedTranscript;
|
||||
const recentSessions = consoleSession.recentSessions;
|
||||
const sessionStatus = computed(() => consoleSession.state.value.status);
|
||||
const sessionError = computed(() => consoleSession.state.value.error);
|
||||
const sessionExitCode = computed(() => consoleSession.state.value.exitCode);
|
||||
|
|
@ -380,7 +465,7 @@ const runnableCommand = computed(() => displayCommand.value.replace(/\n/g, ' ').
|
|||
const hasValidCommand = computed(() => Boolean(runnableCommand.value));
|
||||
const isLoading = computed(() => consoleSession.isLoading.value);
|
||||
const isMultiLineInput = computed(() => Boolean(command.value && command.value.includes('\n')));
|
||||
const historyEntries = computed(() => commandHistory.value);
|
||||
const recentSessionEntries = computed(() => recentSessions.value);
|
||||
const canManualReconnect = computed(() => sessionStatus.value === 'reconnecting');
|
||||
const showCancelButton = computed(() =>
|
||||
['starting', 'running', 'reconnecting'].includes(sessionStatus.value),
|
||||
|
|
@ -481,6 +566,63 @@ const sessionStatusIcon = computed(() => {
|
|||
}
|
||||
});
|
||||
const sessionStatusSpinning = computed(() => ACTIVE_SESSION_STATUSES.includes(sessionStatus.value));
|
||||
|
||||
const isRecentSessionFailed = (item: (typeof recentSessions.value)[number]): boolean => {
|
||||
return item.status === 'completed' && item.exitCode !== null && item.exitCode !== 0;
|
||||
};
|
||||
|
||||
const recentSessionStatusLabel = (item: (typeof recentSessions.value)[number]): string => {
|
||||
if (isRecentSessionFailed(item)) {
|
||||
return 'Failed';
|
||||
}
|
||||
|
||||
switch (item.status) {
|
||||
case 'starting':
|
||||
return 'Starting';
|
||||
|
||||
case 'running':
|
||||
return 'Running';
|
||||
|
||||
default:
|
||||
return 'Completed';
|
||||
}
|
||||
};
|
||||
|
||||
const recentSessionStatusColor = (
|
||||
item: (typeof recentSessions.value)[number],
|
||||
): 'success' | 'error' | 'info' | 'neutral' => {
|
||||
if (isRecentSessionFailed(item)) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
switch (item.status) {
|
||||
case 'starting':
|
||||
case 'running':
|
||||
return 'info';
|
||||
|
||||
default:
|
||||
return item.exitCode === 0 ? 'success' : 'neutral';
|
||||
}
|
||||
};
|
||||
|
||||
const recentSessionStatusIcon = (item: (typeof recentSessions.value)[number]): string => {
|
||||
if (isRecentSessionFailed(item)) {
|
||||
return 'i-lucide-triangle-alert';
|
||||
}
|
||||
|
||||
switch (item.status) {
|
||||
case 'starting':
|
||||
case 'running':
|
||||
return 'i-lucide-loader-circle';
|
||||
|
||||
default:
|
||||
return item.exitCode === 0 ? 'i-lucide-circle-check' : 'i-lucide-circle-dot';
|
||||
}
|
||||
};
|
||||
|
||||
const recentSessionStatusSpinning = (item: (typeof recentSessions.value)[number]): boolean => {
|
||||
return ['starting', 'running'].includes(item.status);
|
||||
};
|
||||
watch(command, (value) => {
|
||||
storedCommand.value = value;
|
||||
});
|
||||
|
|
@ -765,13 +907,6 @@ const handlePaste = async (event: ClipboardEvent): Promise<void> => {
|
|||
}
|
||||
};
|
||||
|
||||
const addToHistory = (cmd: string): void => {
|
||||
commandHistory.value = [cmd, ...commandHistory.value.filter((item) => item !== cmd)].slice(
|
||||
0,
|
||||
MAX_HISTORY_ITEMS,
|
||||
);
|
||||
};
|
||||
|
||||
const runCommand = async (): Promise<void> => {
|
||||
if (!canStartCommand.value) {
|
||||
return;
|
||||
|
|
@ -795,8 +930,6 @@ const runCommand = async (): Promise<void> => {
|
|||
|
||||
await ensureTerminal();
|
||||
|
||||
addToHistory(displayCommand.value);
|
||||
|
||||
const started = await consoleSession.startSession({
|
||||
command: runnableCommand.value,
|
||||
displayCommand: displayCommand.value,
|
||||
|
|
@ -871,14 +1004,19 @@ const loadCommand = async (cmd: string): Promise<void> => {
|
|||
await focusInput();
|
||||
};
|
||||
|
||||
const clearHistory = async (): Promise<void> => {
|
||||
if (historyEntries.value.length < 1) {
|
||||
const hideRecentSession = (sessionId: string): void => {
|
||||
consoleSession.hideRecentSession(sessionId);
|
||||
};
|
||||
|
||||
const hideRecentSessions = async (): Promise<void> => {
|
||||
if (recentSessionEntries.value.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = await dialog.confirmDialog({
|
||||
title: 'Confirm Action',
|
||||
message: 'Clear commands history?',
|
||||
message: 'Hide the current recent runs from this browser?',
|
||||
confirmText: 'Hide runs',
|
||||
confirmColor: 'error',
|
||||
});
|
||||
|
||||
|
|
@ -886,11 +1024,20 @@ const clearHistory = async (): Promise<void> => {
|
|||
return;
|
||||
}
|
||||
|
||||
commandHistory.value = [];
|
||||
consoleSession.clearRecentSessions();
|
||||
await focusInput();
|
||||
};
|
||||
|
||||
const removeFromHistory = (index: number): void => {
|
||||
commandHistory.value = commandHistory.value.filter((_, i) => i !== index);
|
||||
const replayRecentSession = async (item: (typeof recentSessions.value)[number]): Promise<void> => {
|
||||
try {
|
||||
await consoleSession.replaySession(item);
|
||||
restoreBufferedTerminalOutput();
|
||||
} catch {
|
||||
consoleSession.hideRecentSession(item.sessionId);
|
||||
} finally {
|
||||
await nextTick();
|
||||
await focusInput();
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
|
|
@ -907,6 +1054,7 @@ onMounted(async () => {
|
|||
|
||||
await ensureTerminal();
|
||||
await consoleSession.restoreSession();
|
||||
await consoleSession.fetchRecentSessions();
|
||||
restoreBufferedTerminalOutput();
|
||||
|
||||
if (storedCommand.value.trim()) {
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<p class="description">Getting the interface ready. Please wait a moment...</p>
|
||||
<p class="description">Getting the interface ready.</p>
|
||||
|
||||
<div class="loader" aria-hidden="true"></div>
|
||||
<p class="hint">Please wait...</p>
|
||||
|
|
|
|||
27
ui/app/types/index.d.ts
vendored
27
ui/app/types/index.d.ts
vendored
|
|
@ -16,4 +16,29 @@ type version_check = {
|
|||
};
|
||||
};
|
||||
|
||||
export { ImportedItem, version_check };
|
||||
type TerminalSessionStatus = 'starting' | 'running' | 'completed' | 'interrupted' | 'failed';
|
||||
|
||||
type TerminalSessionItem = {
|
||||
session_id: string;
|
||||
command: string;
|
||||
status: TerminalSessionStatus;
|
||||
created_at: number | null;
|
||||
started_at: number | null;
|
||||
finished_at: number | null;
|
||||
expires_at: number | null;
|
||||
available_until: number | null;
|
||||
exit_code: number | null;
|
||||
last_sequence: number;
|
||||
};
|
||||
|
||||
type TerminalSessionsResponse = {
|
||||
items: Array<TerminalSessionItem>;
|
||||
};
|
||||
|
||||
export {
|
||||
ImportedItem,
|
||||
TerminalSessionItem,
|
||||
TerminalSessionStatus,
|
||||
TerminalSessionsResponse,
|
||||
version_check,
|
||||
};
|
||||
|
|
|
|||
472
ui/bun.lock
472
ui/bun.lock
File diff suppressed because it is too large
Load diff
|
|
@ -90,6 +90,9 @@ export default defineNuxtConfig({
|
|||
'marked-base-url',
|
||||
'marked-alert',
|
||||
'marked-gfm-heading-id',
|
||||
'hls.js',
|
||||
'@vue/devtools-core',
|
||||
'@vue/devtools-kit',
|
||||
],
|
||||
},
|
||||
server: {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@
|
|||
},
|
||||
"web-types": "./web-types.json",
|
||||
"dependencies": {
|
||||
"@iconify-json/lucide": "^1.2.102",
|
||||
"@iconify-json/lucide": "^1.2.103",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@nuxt/eslint": "^1.15.2",
|
||||
"@nuxt/eslint-config": "^1.15.2",
|
||||
"@nuxt/ui": "^4.6.1",
|
||||
"@nuxt/ui": "^4.7.0",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
"@vueuse/nuxt": "^14.2.1",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
|
|
@ -33,30 +33,30 @@
|
|||
"cron-parser": "^5.5.0",
|
||||
"cronstrue": "^3.14.0",
|
||||
"hls.js": "^1.6.16",
|
||||
"marked": "^18.0.0",
|
||||
"marked": "^18.0.2",
|
||||
"marked-alert": "^2.1.2",
|
||||
"marked-base-url": "^1.1.9",
|
||||
"marked-gfm-heading-id": "^4.1.4",
|
||||
"moment": "^2.30.1",
|
||||
"nuxt": "^4.4.2",
|
||||
"tailwindcss": "^4.2.2",
|
||||
"vue": "^3.5.32",
|
||||
"vue-router": "^5.0.4"
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vue": "^3.5.33",
|
||||
"vue-router": "^5.0.6"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"esbuild",
|
||||
"@parcel/watcher"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.12",
|
||||
"@types/bun": "^1.3.13",
|
||||
"@types/node": "25.6.0",
|
||||
"@types/jsdom": "^28.0.1",
|
||||
"@typescript-eslint/parser": "^8.58.2",
|
||||
"eslint": "^10.2.0",
|
||||
"@typescript-eslint/parser": "^8.59.0",
|
||||
"eslint": "^10.2.1",
|
||||
"jsdom": "^29.0.2",
|
||||
"oxfmt": "^0.45.0",
|
||||
"typescript": "^6.0.2",
|
||||
"oxfmt": "^0.46.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vue-eslint-parser": "^10.4.0",
|
||||
"vue-tsc": "^3.2.6"
|
||||
"vue-tsc": "^3.2.7"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -410,15 +410,10 @@ describe('useConsoleSession', () => {
|
|||
expect(session.state.value.status).toBe('running')
|
||||
expect(session.state.value.error).toBe('')
|
||||
|
||||
const requestCall = requestSpy.mock.calls.at(-1)
|
||||
expect(requestCall).toBeDefined()
|
||||
if (!requestCall) {
|
||||
throw new Error('Expected request to be called for session cancellation.')
|
||||
}
|
||||
|
||||
const [path, options] = requestCall as [string, { method: string }]
|
||||
expect(path).toBe('/api/system/terminal/sess-stop')
|
||||
expect(options.method).toBe('DELETE')
|
||||
const deleteCall = requestSpy.mock.calls.find(
|
||||
(call) => call[0] === '/api/system/terminal/sess-stop' && call[1]?.method === 'DELETE',
|
||||
)
|
||||
expect(deleteCall).toBeDefined()
|
||||
|
||||
requestSpy.mockRestore()
|
||||
})
|
||||
|
|
@ -502,4 +497,128 @@ describe('useConsoleSession', () => {
|
|||
expect(session.state.value.status).toBe('running')
|
||||
expect(session.state.value.lastEventId).toBe('15')
|
||||
})
|
||||
|
||||
it('fetches recent sessions and filters locally hidden entries', async () => {
|
||||
const requestSpy = spyOn(utils, 'request')
|
||||
requestSpy.mockResolvedValueOnce(
|
||||
createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
jsonData: {
|
||||
items: [
|
||||
{
|
||||
session_id: 'sess-newer',
|
||||
command: '--version',
|
||||
status: 'completed',
|
||||
created_at: 10,
|
||||
started_at: 11,
|
||||
finished_at: 20,
|
||||
expires_at: 100,
|
||||
available_until: 100,
|
||||
exit_code: 0,
|
||||
last_sequence: 2,
|
||||
},
|
||||
{
|
||||
session_id: 'sess-hidden',
|
||||
command: '--help',
|
||||
status: 'running',
|
||||
created_at: 1,
|
||||
started_at: 2,
|
||||
finished_at: null,
|
||||
expires_at: null,
|
||||
available_until: null,
|
||||
exit_code: null,
|
||||
last_sequence: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const session = useConsoleSession()
|
||||
session.hideRecentSession('sess-hidden')
|
||||
await session.fetchRecentSessions()
|
||||
|
||||
expect(session.recentSessions.value).toEqual([
|
||||
{
|
||||
sessionId: 'sess-newer',
|
||||
command: '--version',
|
||||
status: 'completed',
|
||||
createdAt: 10,
|
||||
startedAt: 11,
|
||||
finishedAt: 20,
|
||||
expiresAt: 100,
|
||||
availableUntil: 100,
|
||||
exitCode: 0,
|
||||
lastSequence: 2,
|
||||
},
|
||||
])
|
||||
|
||||
requestSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('replays a recent session by reconnecting to its stream', async () => {
|
||||
const requestSpy = spyOn(utils, 'request')
|
||||
requestSpy.mockResolvedValueOnce(
|
||||
createMockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
jsonData: {
|
||||
items: [
|
||||
{
|
||||
session_id: 'sess-replay',
|
||||
command: '--help',
|
||||
status: 'completed',
|
||||
created_at: 10,
|
||||
started_at: 11,
|
||||
finished_at: 12,
|
||||
expires_at: 100,
|
||||
available_until: 100,
|
||||
exit_code: 0,
|
||||
last_sequence: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
fetchEventSourceMock.mockImplementationOnce(async (_url, options) => {
|
||||
await options.onopen(new Response('', { status: 200 }))
|
||||
options.onmessage({
|
||||
event: 'output',
|
||||
id: '1',
|
||||
data: JSON.stringify({ line: 'restored output' }),
|
||||
})
|
||||
options.onmessage({
|
||||
event: 'close',
|
||||
id: '2',
|
||||
data: JSON.stringify({ exitcode: 0 }),
|
||||
})
|
||||
})
|
||||
|
||||
const session = useConsoleSession()
|
||||
const replayed = await session.replaySession({
|
||||
sessionId: 'sess-replay',
|
||||
command: '--help',
|
||||
status: 'completed',
|
||||
createdAt: 10,
|
||||
startedAt: 11,
|
||||
finishedAt: 12,
|
||||
expiresAt: 100,
|
||||
availableUntil: 100,
|
||||
exitCode: 0,
|
||||
lastSequence: 2,
|
||||
})
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
|
||||
expect(replayed).toBe(true)
|
||||
expect(session.state.value.sessionId).toBe('sess-replay')
|
||||
expect(session.state.value.transcript).toEqual(['restored output\n'])
|
||||
|
||||
const streamCall = fetchEventSourceMock.mock.calls.at(-1)
|
||||
expect(streamCall?.[0]).toContain('/base-path/api/system/terminal/sess-replay/stream')
|
||||
|
||||
requestSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
286
uv.lock
286
uv.lock
|
|
@ -146,7 +146,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "apprise"
|
||||
version = "1.9.9"
|
||||
version = "1.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
|
|
@ -157,9 +157,9 @@ dependencies = [
|
|||
{ name = "requests-oauthlib" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/f4/be5c7e39b83a2285ab62ae7c19bb10704836f59c0a5b4c471730f54c9f98/apprise-1.9.9.tar.gz", hash = "sha256:fd622c0df16bdc79ed385539735573488cafe2405d25747e87eebd6b09b26012", size = 2032822 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/74/9c16829d3e7e45ce7daf1b704687fa4fde7ea00d72eafe8de18c72bf5995/apprise-1.10.0.tar.gz", hash = "sha256:b768f32d99e45ed5f4c3eef1f67903e803c97f97ba61a531a5d0a45d40df90a8", size = 2188611 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/2f/54d068d7e011a8b4e0aae3e93b09a30b33bcf780829fe70c6e8876aeb0e0/apprise-1.9.9-py3-none-any.whl", hash = "sha256:55ceb8827a1c783d683881c9f77fa42eb43b3fc91b854419c452d557101c7068", size = 1519940 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/f9/177a73589d34e676d10bc4c6a8328710e28af5907234e9f25bb149a04eec/apprise-1.10.0-py3-none-any.whl", hash = "sha256:e685303d3568bb7a057d6ddeafd27ee12fff183ca36483ad4bacc0b9b4efa82c", size = 1632292 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -240,11 +240,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.2.25"
|
||||
version = "2026.4.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -351,14 +351,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.2"
|
||||
version = "8.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -538,39 +538,39 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/93/c8c508d68ba93232784bbc1b5474d92371f2897dfc6bc281b419f2e0d492/greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f", size = 628455 },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/46/cfaaa0ade435a60550fd83d07dfd5c41f873a01da17ede5c4cade0b9bab8/greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55", size = 426238 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291 },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/4a/74078d3936712cff6d3c91a930016f476ce4198d84e224fe6d81d3e02880/greenlet-3.4.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06c2d3b89e0c62ba50bd7adf491b14f39da9e7e701647cb7b9ff4c99bee04b19", size = 673405 },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621 },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/3e/df8a83ab894751bc31e1106fdfaa80ca9753222f106b04de93faaa55feb7/greenlet-3.4.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:070b8bac2ff3b4d9e0ff36a0d19e42103331d9737e8504747cd1e659f76297bd", size = 471670 },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/92/466b0d9afd44b8af623139a3599d651c7564fa4152f25f117e1ee5949ffb/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4cd56a9eb7a6444edbc19062f7b6fbc8f287c663b946e3171d899693b1c19fa", size = 665872 },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/14/3395a7ef3e260de0325152ddfe19dffb3e49fe10873b94654352b53ad48e/greenlet-3.4.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1f85f204c4d54134ae850d401fa435c89cd667d5ce9dc567571776b45941af72", size = 489237 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/15/a643b4ecd09969e30b8a150d5919960caae0abe4f5af75ab040b1ab85e78/greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d", size = 623234 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/18/3b13d5ef1275b0ffaf933b05efa21408ac4ca95823c7411d79682e4fdcff/greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae", size = 425243 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525 },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166 },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862 },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -684,11 +684,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
version = "3.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -892,11 +892,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "26.1"
|
||||
version = "26.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -926,11 +926,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pip"
|
||||
version = "26.0.1"
|
||||
version = "26.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/83/0d7d4e9efe3344b8e2fe25d93be44f64b65364d3c8d7bc6dc90198d5422e/pip-26.0.1.tar.gz", hash = "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8", size = 1812747 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/73/7e/d2b04004e1068ad4fdfa2f227b839b5d03e602e47cdbbf49de71137c9546/pip-26.1.tar.gz", hash = "sha256:81e13ebcca3ffa8cc85e4deff5c27e1ee26dea0aa7fc2f294a073ac208806ff3", size = 1840316 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723 },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/7a/be4bd8bcbb24ea475856dd68159d78b03b2bb53dae369f69c9606b8888f5/pip-26.1-py3-none-any.whl", hash = "sha256:4e8486d821d814b77319acb7b9e8bf5a4ee7590a643e7cb21029f209be8573c1", size = 1812804 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -944,21 +944,21 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "playwright"
|
||||
version = "1.58.0"
|
||||
version = "1.59.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "greenlet" },
|
||||
{ name = "pyee" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998 },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/48/abab23f40643b4de8f2665816f0a1bf0994eeecda39d6d62f0f292b2ad01/playwright-1.59.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:bfc6940100b57423175c819ce2422ec5880d55fa2769987f62ab7a1f5fe6783e", size = 43156922 },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/71/5e4d98b2ce3641b4343623c6450ff33b9de1c979d12a957505e392338b07/playwright-1.59.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:af068143a0c045ec11608b67d6c42e58db7e9cf65a742dd21fddedc1a9802c47", size = 41947177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/91/fd219aa78ca03d37e93aaedaed4e224131e3090a9264f9bb773c8271d67e/playwright-1.59.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:4a4a2d4842b0e4120de3fa48636e4b69085a05b81d8a35ad4353f530ade72ed6", size = 43156922 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/0c/1e513d37c5be07d12829ebce93dbfe7baee230084cb66966c423432799c4/playwright-1.59.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c5792aad9e22b91a09264b9edbc18553cf05ea5a39404d65dc19a012c6b2e51d", size = 47151793 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/2d/15f72288cb65d690134e18fefb9483cc4976f7579b580648c45e494481a7/playwright-1.59.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c881a19377d2b900af855fb525b5f22a27bf3cfbecba6d1edb36766d56cb100", size = 46877615 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/a1/717ac5bc99f387c0f60def91271ea4262125c0815d764a5d1776a272275c/playwright-1.59.0-py3-none-win32.whl", hash = "sha256:6989c476be2b9cd3e24a18cc9dcf202e266fb3d91e3e5395cd668c54ea54b119", size = 37713698 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/a5/4e630ee05d8b46b840f943268e86d6063703e8dadb2d3eb405c7b9b2e48c/playwright-1.59.0-py3-none-win_amd64.whl", hash = "sha256:d5a5cc064b82ca92996080025710844e417f44df8fda9001102c28f44174171c", size = 37713704 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/0c/3ece41761ba13c8321009aefcaec7a016eb42799c42eef5e03ace7f2de5b/playwright-1.59.0-py3-none-win_arm64.whl", hash = "sha256:93581ad515728cadc8af39b288a5633ba6d36e7d72048e79d890ce01ea2156f9", size = 33956745 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1110,7 +1110,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.2"
|
||||
version = "2.13.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
|
|
@ -1118,65 +1118,65 @@ dependencies = [
|
|||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/09/e5/06d23afac9973109d1e3c8ad38e1547a12e860610e327c05ee686827dc37/pydantic-2.13.2.tar.gz", hash = "sha256:b418196607e61081c3226dcd4f0672f2a194828abb9109e9cfb84026564df2d1", size = 843836 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/ca/b45c378e6e8d0b90577288b533e04e95b7afd61bb1d51b6c263176435489/pydantic-2.13.2-py3-none-any.whl", hash = "sha256:a525087f4c03d7e7456a3de89b64cd693d2229933bb1068b9af6befd5563694e", size = 471947 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.46.2"
|
||||
version = "2.46.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/bb/4742f05b739b2478459bb16fa8470549518c802e06ddcf3f106c5081315e/pydantic_core-2.46.2.tar.gz", hash = "sha256:37bb079f9ee3f1a519392b73fda2a96379b31f2013c6b467fe693e7f2987f596", size = 471269 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/07/2b/662e48254479a2d3450ba24b1e25061108b64339794232f503990c519144/pydantic_core-2.46.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d26e9eea3715008a09a74585fe9becd0c67fbb145dc4df9756d597d7230a652c", size = 2101762 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/ab/bafd7c7503757ccc8ec4d1911e106fe474c629443648c51a88f08b0fe91a/pydantic_core-2.46.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48b36e3235140510dc7861f0cd58b714b1cdd3d48f75e10ce52e69866b746f10", size = 1951814 },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/cc/7549c2d57ba2e9a42caa5861a2d398dbe31c02c6aca783253ace59ce84f8/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b1f99dc451f1a3981f236151465bcf995bbe712d0727c9f7b236fe228a8133", size = 1977329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/50/7ed4a8a0d478a4dca8f0134a5efa7193f03cc8520dd4c9509339fb2e5002/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8641c8d535c2d95b45c2e19b646ecd23ebba35d461e0ae48a3498277006250ab", size = 2051832 },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/16/bb35b193741c0298ddc5f5e4234269efdc0c65e2bcd198aa0de9b68845e4/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20fb194788a0a50993e87013e693494ba183a2af5b44e99cf060bbae10912b11", size = 2233127 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/a5/98f4b637149185addea19e1785ea20c373cca31b202f589111d8209d9873/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9262d11d0cd11ee3303a95156939402bed6cedfe5ed0e331b95a283a4da6eb8b", size = 2297418 },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/90/93a5d21990b152da7b7507b7fddb0b935f6a0984d57ac3ec45a6e17777a2/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac204542736aa295fa25f713b7fad6fc50b46ab7764d16087575c85f085174f3", size = 2093735 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/22/b8b1ffdddf08b4e84380bcb67f41dbbf4c171377c1d36fc6290794bb2094/pydantic_core-2.46.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9a7c43a0584742dface3ca0daf6f719d46c1ac2f87cf080050f9ae052c75e1b2", size = 2127570 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/26/e60d72b4e2d0ce1fa811044a974412ac1c567fe067d97b3e6b290530786e/pydantic_core-2.46.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd05e1edb6a90ad446fa268ab09e59202766b837597b714b2492db11ee87fab9", size = 2183524 },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/32/36bec7584a1eefb17dec4dfa1c946d3fe4440f466c5705b8adfda69c9a9f/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:91155b110788b5501abc7ea954f1d08606219e4e28e3c73a94124307c06efb80", size = 2185408 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/d6/1a5689d873620efd67d6b163db0c444c056adb0849b5bc33e2b9f09665a6/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e4e2c72a529fa03ff228be1d2b76944013f428220b764e03cc50ada67e17a42c", size = 2335171 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/8e/675104802abe8ef502b072050ee5f2e915251aa1a3af87e1015ce31ec42d/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:56291ec1a11c3499890c99a8fd9053b47e60fe837a77ec72c0671b1b8b3dce24", size = 2362743 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/bc/86c5dde4fa6e24467680eef5047da3c1a19be0a527d0d8e14aa76b39307c/pydantic_core-2.46.2-cp313-cp313-win32.whl", hash = "sha256:b50f9c5f826ddca1246f055148df939f5f3f2d0d96db73de28e2233f22210d4c", size = 1958074 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/97/2537e8c1282b2c4eb062580c0d7a4339e10b072b803d1ee0b7f1f0a5c22c/pydantic_core-2.46.2-cp313-cp313-win_amd64.whl", hash = "sha256:251a57788823230ca8cbc99e6245d1a2ed6e180ec4864f251c94182c580c7f2e", size = 2071741 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/aa/2ee75798706f9dbc4e76dbe59e41a396c5c311e3d6223b9cf6a5fa7780be/pydantic_core-2.46.2-cp313-cp313-win_arm64.whl", hash = "sha256:315d32d1a71494d6b4e1e14a9fa7a4329597b4c4340088ad7e1a9dafbeed92a9", size = 2025955 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/96/a50ccb6b539ae780f73cea74905468777680e30c6c3bdf714b9d4c116ea0/pydantic_core-2.46.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4f59b45f3ef8650c0c736a57f59031d47ed9df4c0a64e83796849d7d14863a2d", size = 2097111 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/5f/fdead7b3afa822ab6e5a18ee0ecffd54937de1877c01ed13a342e0fb3f07/pydantic_core-2.46.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a075a29ebef752784a91532a1a85be6b234ccffec0a9d7978a92696387c3da6", size = 1951904 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/e0/1c5d547e550cdab1bec737492aa08865337af6fe7fc9b96f7f45f17d9519/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d12d786e30c04a9d307c5d7080bf720d9bac7f1668191d8e37633a9562749e2", size = 1978667 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/cb/665ce629e218c8228302cb94beff4f6531082a2c87d3ecc3d5e63a26f392/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d5e6d6343b0b5dcacb3503b5de90022968da8ed0ab9ab39d3eda71c20cbf84e", size = 2046721 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/e9/6cb2cf60f54c1472bbdfce19d957553b43dbba79d1d7b2930a195c594785/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:233eebac0999b6b9ba76eb56f3ec8fce13164aa16b6d2225a36a79e0f95b5973", size = 2228483 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/2a/93e018dd5571f781ebaeda8c0cf65398489d5bee9b1f484df0b6149b43b9/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cc0eee720dd2f14f3b7c349469402b99ad81a174ab49d3533974529e9d93992", size = 2294663 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/4f/49e57ca55c770c93d9bb046666a54949b42e3c9099a0c5fe94557873fe30/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ee76bf2c9910513dbc19e7d82367131fa7508dedd6186a462393071cc11059", size = 2098742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/b0/6e46b5cd3332af665f794b8cdeea206618a8630bd9e7bcc36864518fce81/pydantic_core-2.46.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:d61db38eb4ee5192f0c261b7f2d38e420b554df8912245e3546aee5c45e2fd78", size = 2125922 },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/d1/40850c81585be443a2abfdf7f795f8fae831baf8e2f9b2133c8246ac671c/pydantic_core-2.46.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f09a713d17bcd55da8ab02ebd9110c5246a49c44182af213b5212800af8bc83", size = 2183000 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/af/8493d7dfa03ebb7866909e577c6aa65ea0de7377b86023cc51d0c8e11db3/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:30cacc5fb696e64b8ef6fd31d9549d394dd7d52760db072eecb98e37e3af1677", size = 2180335 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/5b/1f6a344c4ffdf284da41c6067b82d5ebcbd11ce1b515ae4b662d4adb6f61/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:7ccfb105fcfe91a22bbb5563ad3dc124bc1aa75bfd2e53a780ab05f78cdf6108", size = 2330002 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ff/9a694126c12d6d2f48a0cafa6f8eef88ef0d8825600e18d03ff2e896c3b2/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:13ffef637dc8370c249e5b26bd18e9a80a4fca3d809618c44e18ec834a7ca7a8", size = 2359920 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/c8/3a35c763d68a9cb2675eb10ef242cf66c5d4701b28ae12e688d67d2c180e/pydantic_core-2.46.2-cp314-cp314-win32.whl", hash = "sha256:1b0ab6d756ca2704a938e6c31b53f290c2f9c10d3914235410302a149de1a83e", size = 1953701 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/6a/f2726a780365f7dfd89d62036f984f7acb99978c60c5e1fa7c0cb898ed11/pydantic_core-2.46.2-cp314-cp314-win_amd64.whl", hash = "sha256:99ebade8c9ada4df975372d8dd25883daa0e379a05f1cd0c99aa0c04368d01a6", size = 2071867 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/79/76baacb9feba3d7c399b245ca1a29c74ea0db04ea693811374827eec2290/pydantic_core-2.46.2-cp314-cp314-win_arm64.whl", hash = "sha256:de87422197cf7f83db91d89c86a21660d749b3cd76cd8a45d115b8e675670f02", size = 2017252 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/3b/77c26938f817668d9ad9bab1a905cb23f11d9a3d4bf724d429b3e55a8eaf/pydantic_core-2.46.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:236f22b4a206b5b61db955396b7cf9e2e1ff77f372efe9570128ccfcd6a525eb", size = 2094545 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/de/42c13f590e3c260966aa49bcdb1674774f975467c49abd51191e502bea28/pydantic_core-2.46.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c2012f64d2cd7cca50f49f22445aa5a88691ac2b4498ee0a9a977f8ca4f7289f", size = 1933953 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/84/ebe3ebb3e2d8db656937cfa6f97f544cb7132f2307a4a7dfdcd0ea102a12/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07d6c63106d3a9c9a333e2636f9c82c703b1a9e3b079299e58747964e4fdb72", size = 1974435 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/15/0bf51ca6709477cd4ef86148b6d7844f3308f029eac361dd0383f1e17b1a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c326a2b4b85e959d9a1fc3a11f32f84611b6ec07c053e1828a860edf8d068208", size = 2031113 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/ae/b7b5af9b79db036d9e61a44c481c17a213dc8fc4b8b71fe6875a72fc778b/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac8a65e798f2462552c00d2e013d532c94d646729dda98458beaf51f9ec7b120", size = 2236325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ae/ecef7477b5a03d4a499708f7e75d2836452ebb70b776c2d64612b334f57a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a3c2bc1cc8164bedbc160b7bb1e8cc1e8b9c27f69ae4f9ae2b976cdae02b2dd", size = 2278135 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/e4/2f9d82faa47af6c39fc3f120145fd915971e1e0cb6b55b494fad9fdf8275/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69aa5e10b7e8b1bb4a6888650fd12fcbf11d396ca11d4a44de1450875702830", size = 2109071 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/9c/677cf10873fbd0b116575ab7b97c90482b21564f8a8040beb18edef7a577/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4e6df5c3301e65fb42bc5338bf9a1027a02b0a31dc7f54c33775229af474daf0", size = 2106028 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/53/6a06183544daba51c059123a2064a99039df25f115a06bdb26f2ea177038/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2f6e32548ac8d559b47944effcf8ae4d81c161f6b6c885edc53bc08b8f192d", size = 2164816 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/6f/10fcdd9e3eca66fc828eef0f6f5850f2dd3bca2c59e6e041fb8bc3da39be/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b089a81c58e6ea0485562bbbbbca4f65c0549521606d5ef27fba217aac9b665a", size = 2166130 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/83/92d3fd0e0156cad2e3cb5c26de73794af78ac9fa0c22ab666e566dd67061/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7f700a6d6f64112ae9193709b84303bbab84424ad4b47d0253301aabce9dfc70", size = 2316605 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/f1/facffdb970981068219582e499b8d0871ed163ffcc6b347de5c412669e4c/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:67db6814beaa5fefe91101ec7eb9efda613795767be96f7cf58b1ca8c9ca9972", size = 2358385 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/a1/b8160b2f22b2199467bc68581a4ed380643c16b348a27d6165c6c242d694/pydantic_core-2.46.2-cp314-cp314t-win32.whl", hash = "sha256:32fbc7447be8e3be99bf7869f7066308f16be55b61f9882c2cefc7931f5c7664", size = 1942373 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/90/db89acabe5b150e11d1b59fe3d947dda2ef6abbfef5c82f056ff63802f5d/pydantic_core-2.46.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b317a2b97019c0b95ce99f4f901ae383f40132da6706cdf1731066a73394c25c", size = 2052078 },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/32/e19b83ceb07a3f1bb21798407790bbc9a31740158fd132b94139cb84e16c/pydantic_core-2.46.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7dcb9d40930dfad7ab6b20bcc6ca9d2b030b0f347a0cd9909b54bd53ead521b1", size = 2016941 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908 },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968 },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342 },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1202,7 +1202,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyinstaller"
|
||||
version = "6.19.0"
|
||||
version = "6.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "altgraph" },
|
||||
|
|
@ -1213,19 +1213,19 @@ dependencies = [
|
|||
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c8/63/fd62472b6371d89dc138d40c36d87a50dc2de18a035803bbdc376b4ffac4/pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865", size = 4036072 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/eb/23374721fecfa72677e79800921cb6aceefa6ba48574dc404f3f6c6c3be7/pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134", size = 1040563 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/7e/dfd724b0b533f5aaec0ee5df406fe2319987ed6964480a706f85478b12ea/pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11", size = 735477 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/c9/ee3a4101c31f26344e66896c73c1fd6ed8282bf871473365b7f8674af406/pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf", size = 747143 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0a/fc77e9f861be8cf300ac37155f59cc92aff99b29f2ddd78546f563a5b5a6/pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122", size = 744849 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/e3/6872e020ee758afe0b821663858492c10745608b07150e5e2c824a5b3e1c/pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63", size = 741590 },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/60/b8db5f1a4b0fb228175f2ea0aa33f949adcc097fbe981cc524f9faf85777/pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe", size = 741448 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/4d/63b0600f2694e9141b83129fbc1c488ec84d5a0770b1448ec154dcd0fee9/pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83", size = 740613 },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d4/e812ad36178093a0e9fd4b8127577748dd85b0cb71de912229dca21fd741/pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6", size = 740350 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/03/b2c2ee41fb8e10fd2a45d21f5ec2ef25852cfb978dbf762972eed59e3d63/pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2", size = 1324317 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/d3/6d5e62b8270e2b53a6065e281b3a7785079b00e9019c8019952828dd1669/pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33", size = 1384894 },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/65/458cd523308a101a22fd2742893405030cc24994cc74b1b767cecf137160/pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea", size = 1325374 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1638,27 +1638,27 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.15.11"
|
||||
version = "0.15.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575 },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813 },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887 },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316 },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692 },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267 },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182 },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377 },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498 },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277 },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1816,11 +1816,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2026.1"
|
||||
version = "2026.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue