Spaces:
Running
Running
yangzhitao
commited on
Commit
·
b4929ca
1
Parent(s):
12947f9
feat: integrate FastAPI backend and health check functionality
Browse files- Added FastAPI as a backend server to handle API requests.
- Implemented health check endpoint to monitor backend status.
- Introduced new configuration management using Pydantic.
- Updated Gradio interface to display backend health status dynamically.
- Added necessary dependencies for FastAPI and Uvicorn in project configuration.
- app.py +27 -4
- pyproject.toml +3 -0
- src/backend/app.py +30 -0
- src/backend/config.py +38 -0
- src/backend/routes/health.py +17 -0
- src/backend/routes/hf.py +47 -0
- src/backend/routes/main.py +8 -0
- src/backend/schemas.py +44 -0
- uv.lock +28 -0
app.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import gradio.components as grc
|
| 3 |
import pandas as pd
|
|
|
|
| 4 |
from apscheduler.schedulers.background import BackgroundScheduler
|
| 5 |
from huggingface_hub import snapshot_download
|
| 6 |
from rich import print
|
|
@@ -14,6 +17,7 @@ from src.about import (
|
|
| 14 |
LLM_BENCHMARKS_TEXT,
|
| 15 |
TITLE,
|
| 16 |
)
|
|
|
|
| 17 |
from src.display.css_html_js import custom_css
|
| 18 |
from src.display.utils import (
|
| 19 |
BASE_COLS,
|
|
@@ -404,7 +408,26 @@ with demo:
|
|
| 404 |
show_copy_button=True,
|
| 405 |
)
|
| 406 |
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import threading
|
| 2 |
+
|
| 3 |
import gradio as gr
|
| 4 |
import gradio.components as grc
|
| 5 |
import pandas as pd
|
| 6 |
+
import uvicorn
|
| 7 |
from apscheduler.schedulers.background import BackgroundScheduler
|
| 8 |
from huggingface_hub import snapshot_download
|
| 9 |
from rich import print
|
|
|
|
| 17 |
LLM_BENCHMARKS_TEXT,
|
| 18 |
TITLE,
|
| 19 |
)
|
| 20 |
+
from src.backend.app import create_app
|
| 21 |
from src.display.css_html_js import custom_css
|
| 22 |
from src.display.utils import (
|
| 23 |
BASE_COLS,
|
|
|
|
| 408 |
show_copy_button=True,
|
| 409 |
)
|
| 410 |
|
| 411 |
+
|
| 412 |
+
if __name__ == "__main__":
|
| 413 |
+
# Backend server - 在单独的线程中运行
|
| 414 |
+
app = create_app()
|
| 415 |
+
|
| 416 |
+
def run_fastapi():
|
| 417 |
+
print("Starting FastAPI server on http://0.0.0.0:8000")
|
| 418 |
+
uvicorn.run(
|
| 419 |
+
app,
|
| 420 |
+
host="0.0.0.0",
|
| 421 |
+
port=8000,
|
| 422 |
+
log_level="info",
|
| 423 |
+
access_log=True,
|
| 424 |
+
)
|
| 425 |
+
|
| 426 |
+
fastapi_thread = threading.Thread(target=run_fastapi, daemon=True)
|
| 427 |
+
fastapi_thread.start()
|
| 428 |
+
|
| 429 |
+
# Gradio server - 在主线程中运行(阻塞)
|
| 430 |
+
scheduler = BackgroundScheduler()
|
| 431 |
+
scheduler.add_job(restart_space, "interval", seconds=1800)
|
| 432 |
+
scheduler.start()
|
| 433 |
+
demo.queue(default_concurrency_limit=40).launch()
|
pyproject.toml
CHANGED
|
@@ -25,6 +25,9 @@ dependencies = [
|
|
| 25 |
"pydantic>=2.11.10",
|
| 26 |
"pydantic-settings>=2.11.0",
|
| 27 |
"rich>=14.2.0",
|
|
|
|
|
|
|
|
|
|
| 28 |
]
|
| 29 |
|
| 30 |
[dependency-groups]
|
|
|
|
| 25 |
"pydantic>=2.11.10",
|
| 26 |
"pydantic-settings>=2.11.0",
|
| 27 |
"rich>=14.2.0",
|
| 28 |
+
"fastapi>=0.120.0",
|
| 29 |
+
"loguru>=0.7.3",
|
| 30 |
+
"uvicorn>=0.38.0",
|
| 31 |
]
|
| 32 |
|
| 33 |
[dependency-groups]
|
src/backend/app.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request
|
| 2 |
+
from fastapi.responses import RedirectResponse
|
| 3 |
+
from fastapi.routing import APIRoute
|
| 4 |
+
|
| 5 |
+
from .config import settings
|
| 6 |
+
from .routes.main import api_router
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def create_app() -> FastAPI:
|
| 10 |
+
app = FastAPI(
|
| 11 |
+
title="Leaderboard API",
|
| 12 |
+
description="API for the Leaderboard",
|
| 13 |
+
version="0.1.0",
|
| 14 |
+
docs_url=f"{settings.API_V1_STR}/docs",
|
| 15 |
+
openapi_url=f"{settings.API_V1_STR}/openapi.json",
|
| 16 |
+
generate_unique_id_function=custom_generate_unique_id,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
app.include_router(api_router, prefix=settings.API_V1_STR)
|
| 20 |
+
app.add_route("/", _redirect_to_docs, methods=["GET"])
|
| 21 |
+
|
| 22 |
+
return app
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def custom_generate_unique_id(route: APIRoute) -> str:
|
| 26 |
+
return f"{route.tags[0]}-{route.name}"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
async def _redirect_to_docs(_: Request):
|
| 30 |
+
return RedirectResponse(url=f"{settings.API_V1_STR}/docs")
|
src/backend/config.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import cached_property
|
| 2 |
+
from typing import Annotated
|
| 3 |
+
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
from pydantic import Field, SecretStr
|
| 6 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class Settings(BaseSettings):
|
| 12 |
+
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
| 13 |
+
|
| 14 |
+
API_V1_STR: str = "/api/v1"
|
| 15 |
+
|
| 16 |
+
# ------------------------------------------------------------
|
| 17 |
+
# Hugging Face settings
|
| 18 |
+
# ------------------------------------------------------------
|
| 19 |
+
HF_TOKEN: Annotated[SecretStr, Field(description="Hugging Face Access Token")] = SecretStr("changethis")
|
| 20 |
+
|
| 21 |
+
@cached_property
|
| 22 |
+
def hf_api(self):
|
| 23 |
+
from huggingface_hub import HfApi
|
| 24 |
+
|
| 25 |
+
token = self.HF_TOKEN.get_secret_value()
|
| 26 |
+
if not token or token == "changethis":
|
| 27 |
+
raise RuntimeError("Hugging Face Access Token is not set.")
|
| 28 |
+
|
| 29 |
+
return HfApi(
|
| 30 |
+
endpoint="https://huggingface.co",
|
| 31 |
+
token=token,
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
REQUESTS_REPO_ID: str = "y-playground/requests"
|
| 35 |
+
RESULTS_REPO_ID: str = "y-playground/results"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
settings = Settings()
|
src/backend/routes/health.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime, timezone
|
| 2 |
+
|
| 3 |
+
from fastapi import APIRouter
|
| 4 |
+
|
| 5 |
+
router = APIRouter(tags=["health"])
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
@router.get("/")
|
| 9 |
+
async def health():
|
| 10 |
+
utc_now = datetime.now(timezone.utc).isoformat()
|
| 11 |
+
return {
|
| 12 |
+
"code": 0,
|
| 13 |
+
"msg": "ok",
|
| 14 |
+
"data": {
|
| 15 |
+
"utc_now": utc_now,
|
| 16 |
+
},
|
| 17 |
+
}
|
src/backend/routes/hf.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
from dataclasses import asdict
|
| 3 |
+
|
| 4 |
+
from fastapi import APIRouter
|
| 5 |
+
from loguru import logger
|
| 6 |
+
|
| 7 |
+
from src.backend.config import settings
|
| 8 |
+
from src.backend.schemas import HfRepoUrl, ResponseData, Submit_Params, Submit_RespData
|
| 9 |
+
|
| 10 |
+
router = APIRouter(tags=["huggingface"])
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@router.post("/community/submit/")
|
| 14 |
+
async def submit(params: Submit_Params) -> ResponseData[Submit_RespData]:
|
| 15 |
+
"""Submit a new evaluation request to the Hugging Face repository."""
|
| 16 |
+
file_obj = io.BytesIO(params.content.encode("utf-8"))
|
| 17 |
+
commit_info = settings.hf_api.upload_file(
|
| 18 |
+
path_or_fileobj=file_obj,
|
| 19 |
+
path_in_repo=params.path_in_repo,
|
| 20 |
+
repo_id=settings.REQUESTS_REPO_ID,
|
| 21 |
+
repo_type="dataset",
|
| 22 |
+
commit_message=params.commit_message,
|
| 23 |
+
)
|
| 24 |
+
data_dict = asdict(commit_info)
|
| 25 |
+
try:
|
| 26 |
+
repo_url = HfRepoUrl.model_validate({
|
| 27 |
+
"endpoint": commit_info.repo_url.endpoint,
|
| 28 |
+
"namespace": commit_info.repo_url.namespace,
|
| 29 |
+
"repo_name": commit_info.repo_url.repo_name,
|
| 30 |
+
"repo_id": commit_info.repo_url.repo_id,
|
| 31 |
+
"repo_type": commit_info.repo_url.repo_type,
|
| 32 |
+
"url": commit_info.repo_url.url,
|
| 33 |
+
})
|
| 34 |
+
return ResponseData(
|
| 35 |
+
data=Submit_RespData.model_validate({
|
| 36 |
+
**data_dict,
|
| 37 |
+
"repo_url": repo_url,
|
| 38 |
+
})
|
| 39 |
+
)
|
| 40 |
+
except Exception as e:
|
| 41 |
+
msg = f"Failed to validate repo url: {e}"
|
| 42 |
+
logger.warning(msg)
|
| 43 |
+
return ResponseData(
|
| 44 |
+
code=500,
|
| 45 |
+
msg=msg,
|
| 46 |
+
data=Submit_RespData.model_validate(data_dict),
|
| 47 |
+
)
|
src/backend/routes/main.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
|
| 3 |
+
from .health import router as health_router
|
| 4 |
+
from .hf import router as hf_router
|
| 5 |
+
|
| 6 |
+
api_router = APIRouter()
|
| 7 |
+
api_router.include_router(health_router, prefix="/health")
|
| 8 |
+
api_router.include_router(hf_router, prefix="/hf")
|
src/backend/schemas.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Annotated, Generic, Literal, TypeVar
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, ConfigDict, Field
|
| 4 |
+
|
| 5 |
+
T = TypeVar("T", bound=BaseModel)
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class ResponseData(BaseModel, Generic[T]):
|
| 9 |
+
code: int = 0
|
| 10 |
+
msg: str = "ok"
|
| 11 |
+
data: T | None = None
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# --- Submit
|
| 15 |
+
class Submit_Params(BaseModel):
|
| 16 |
+
path_in_repo: str
|
| 17 |
+
commit_message: str | None = None
|
| 18 |
+
|
| 19 |
+
model_id: str
|
| 20 |
+
model_sha: str = "main"
|
| 21 |
+
model_dtype: str
|
| 22 |
+
|
| 23 |
+
content: str
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Submit_RespData(BaseModel):
|
| 27 |
+
model_config = ConfigDict(extra="allow")
|
| 28 |
+
|
| 29 |
+
commit_url: str
|
| 30 |
+
commit_message: str
|
| 31 |
+
commit_description: str
|
| 32 |
+
oid: Annotated[str, Field(description="Commit hash id.")]
|
| 33 |
+
repo_url: "HfRepoUrl | None" = None
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class HfRepoUrl(BaseModel):
|
| 37 |
+
model_config = ConfigDict(extra="allow")
|
| 38 |
+
|
| 39 |
+
endpoint: str
|
| 40 |
+
namespace: str
|
| 41 |
+
repo_name: str
|
| 42 |
+
repo_id: str
|
| 43 |
+
repo_type: Literal["model", "dataset", "space"]
|
| 44 |
+
url: str
|
uv.lock
CHANGED
|
@@ -674,10 +674,12 @@ source = { virtual = "." }
|
|
| 674 |
dependencies = [
|
| 675 |
{ name = "apscheduler" },
|
| 676 |
{ name = "datasets" },
|
|
|
|
| 677 |
{ name = "gradio", extra = ["oauth"] },
|
| 678 |
{ name = "gradio-client" },
|
| 679 |
{ name = "gradio-leaderboard" },
|
| 680 |
{ name = "huggingface-hub" },
|
|
|
|
| 681 |
{ name = "matplotlib" },
|
| 682 |
{ name = "numpy" },
|
| 683 |
{ name = "pandas" },
|
|
@@ -690,6 +692,7 @@ dependencies = [
|
|
| 690 |
{ name = "tokenizers" },
|
| 691 |
{ name = "tqdm" },
|
| 692 |
{ name = "transformers" },
|
|
|
|
| 693 |
]
|
| 694 |
|
| 695 |
[package.dev-dependencies]
|
|
@@ -701,11 +704,13 @@ dev = [
|
|
| 701 |
requires-dist = [
|
| 702 |
{ name = "apscheduler" },
|
| 703 |
{ name = "datasets" },
|
|
|
|
| 704 |
{ name = "gradio" },
|
| 705 |
{ name = "gradio", extras = ["oauth"] },
|
| 706 |
{ name = "gradio-client" },
|
| 707 |
{ name = "gradio-leaderboard", specifier = "==0.0.13" },
|
| 708 |
{ name = "huggingface-hub", specifier = ">=0.18.0" },
|
|
|
|
| 709 |
{ name = "matplotlib" },
|
| 710 |
{ name = "numpy" },
|
| 711 |
{ name = "pandas" },
|
|
@@ -718,11 +723,25 @@ requires-dist = [
|
|
| 718 |
{ name = "tokenizers", specifier = ">=0.15.0" },
|
| 719 |
{ name = "tqdm" },
|
| 720 |
{ name = "transformers" },
|
|
|
|
| 721 |
]
|
| 722 |
|
| 723 |
[package.metadata.requires-dev]
|
| 724 |
dev = [{ name = "ruff", specifier = ">=0.14.0,<0.15.0" }]
|
| 725 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
[[package]]
|
| 727 |
name = "markdown-it-py"
|
| 728 |
version = "4.0.0"
|
|
@@ -1478,6 +1497,15 @@ wheels = [
|
|
| 1478 |
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
| 1479 |
]
|
| 1480 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1481 |
[[package]]
|
| 1482 |
name = "xxhash"
|
| 1483 |
version = "3.6.0"
|
|
|
|
| 674 |
dependencies = [
|
| 675 |
{ name = "apscheduler" },
|
| 676 |
{ name = "datasets" },
|
| 677 |
+
{ name = "fastapi" },
|
| 678 |
{ name = "gradio", extra = ["oauth"] },
|
| 679 |
{ name = "gradio-client" },
|
| 680 |
{ name = "gradio-leaderboard" },
|
| 681 |
{ name = "huggingface-hub" },
|
| 682 |
+
{ name = "loguru" },
|
| 683 |
{ name = "matplotlib" },
|
| 684 |
{ name = "numpy" },
|
| 685 |
{ name = "pandas" },
|
|
|
|
| 692 |
{ name = "tokenizers" },
|
| 693 |
{ name = "tqdm" },
|
| 694 |
{ name = "transformers" },
|
| 695 |
+
{ name = "uvicorn" },
|
| 696 |
]
|
| 697 |
|
| 698 |
[package.dev-dependencies]
|
|
|
|
| 704 |
requires-dist = [
|
| 705 |
{ name = "apscheduler" },
|
| 706 |
{ name = "datasets" },
|
| 707 |
+
{ name = "fastapi", specifier = ">=0.120.0" },
|
| 708 |
{ name = "gradio" },
|
| 709 |
{ name = "gradio", extras = ["oauth"] },
|
| 710 |
{ name = "gradio-client" },
|
| 711 |
{ name = "gradio-leaderboard", specifier = "==0.0.13" },
|
| 712 |
{ name = "huggingface-hub", specifier = ">=0.18.0" },
|
| 713 |
+
{ name = "loguru", specifier = ">=0.7.3" },
|
| 714 |
{ name = "matplotlib" },
|
| 715 |
{ name = "numpy" },
|
| 716 |
{ name = "pandas" },
|
|
|
|
| 723 |
{ name = "tokenizers", specifier = ">=0.15.0" },
|
| 724 |
{ name = "tqdm" },
|
| 725 |
{ name = "transformers" },
|
| 726 |
+
{ name = "uvicorn", specifier = ">=0.38.0" },
|
| 727 |
]
|
| 728 |
|
| 729 |
[package.metadata.requires-dev]
|
| 730 |
dev = [{ name = "ruff", specifier = ">=0.14.0,<0.15.0" }]
|
| 731 |
|
| 732 |
+
[[package]]
|
| 733 |
+
name = "loguru"
|
| 734 |
+
version = "0.7.3"
|
| 735 |
+
source = { registry = "https://pypi.org/simple" }
|
| 736 |
+
dependencies = [
|
| 737 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 738 |
+
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
| 739 |
+
]
|
| 740 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
| 741 |
+
wheels = [
|
| 742 |
+
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
| 743 |
+
]
|
| 744 |
+
|
| 745 |
[[package]]
|
| 746 |
name = "markdown-it-py"
|
| 747 |
version = "4.0.0"
|
|
|
|
| 1497 |
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
| 1498 |
]
|
| 1499 |
|
| 1500 |
+
[[package]]
|
| 1501 |
+
name = "win32-setctime"
|
| 1502 |
+
version = "1.2.0"
|
| 1503 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1504 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
| 1505 |
+
wheels = [
|
| 1506 |
+
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
| 1507 |
+
]
|
| 1508 |
+
|
| 1509 |
[[package]]
|
| 1510 |
name = "xxhash"
|
| 1511 |
version = "3.6.0"
|