Parquet export
Browse files- backend/Makefile +5 -2
- backend/pyproject.toml +4 -0
- backend/src/app_factory.py +57 -30
- backend/src/parquet.py +99 -0
- backend/src/test_parquet.py +254 -0
- backend/uv.lock +240 -0
backend/Makefile
CHANGED
@@ -3,6 +3,9 @@
|
|
3 |
dev:
|
4 |
HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1 uv run uvicorn src.app:app --reload
|
5 |
|
|
|
|
|
|
|
6 |
quality:
|
7 |
ruff check src
|
8 |
ruff format --check src
|
@@ -12,5 +15,5 @@ style:
|
|
12 |
ruff format src
|
13 |
ruff check --fix src
|
14 |
|
15 |
-
|
16 |
-
|
|
|
3 |
dev:
|
4 |
HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1 uv run uvicorn src.app:app --reload
|
5 |
|
6 |
+
preview:
|
7 |
+
HF_HUB_DISABLE_EXPERIMENTAL_WARNING=1 FRONTEND_PATH=../frontend/dist/ uv run uvicorn src.app:app
|
8 |
+
|
9 |
quality:
|
10 |
ruff check src
|
11 |
ruff format --check src
|
|
|
15 |
ruff format src
|
16 |
ruff check --fix src
|
17 |
|
18 |
+
test:
|
19 |
+
uv run pytest src/test_parquet.py
|
backend/pyproject.toml
CHANGED
@@ -12,10 +12,14 @@ dependencies = [
|
|
12 |
"uvicorn",
|
13 |
"sqlmodel",
|
14 |
"huggingface-hub[oauth]>=0.34.4",
|
|
|
|
|
|
|
15 |
]
|
16 |
|
17 |
[dependency-groups]
|
18 |
dev = [
|
|
|
19 |
"ruff",
|
20 |
"ty",
|
21 |
]
|
|
|
12 |
"uvicorn",
|
13 |
"sqlmodel",
|
14 |
"huggingface-hub[oauth]>=0.34.4",
|
15 |
+
"pandas>=2.3.1",
|
16 |
+
"pyarrow>=21.0.0",
|
17 |
+
"apscheduler>=3.11.0",
|
18 |
]
|
19 |
|
20 |
[dependency-groups]
|
21 |
dev = [
|
22 |
+
"pytest",
|
23 |
"ruff",
|
24 |
"ty",
|
25 |
]
|
backend/src/app_factory.py
CHANGED
@@ -1,16 +1,25 @@
|
|
1 |
-
import
|
2 |
from contextlib import asynccontextmanager, contextmanager
|
3 |
from typing import Annotated, Generator
|
4 |
|
|
|
5 |
from fastapi import Depends, FastAPI, HTTPException, Request
|
6 |
from fastapi.middleware.cors import CORSMiddleware
|
7 |
from fastapi.responses import FileResponse, RedirectResponse
|
8 |
from fastapi.staticfiles import StaticFiles
|
9 |
-
from huggingface_hub import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
from sqlalchemy.engine import Engine
|
11 |
from sqlmodel import Session, SQLModel, create_engine
|
12 |
|
13 |
from . import constants
|
|
|
14 |
|
15 |
_ENGINE_SINGLETON: Engine | None = None
|
16 |
|
@@ -34,8 +43,7 @@ OptionalOAuth = Annotated[OAuthInfo | None, Depends(_oauth_info_optional)]
|
|
34 |
RequiredOAuth = Annotated[OAuthInfo, Depends(_oauth_info_required)]
|
35 |
|
36 |
|
37 |
-
|
38 |
-
def _get_engine() -> Engine:
|
39 |
"""Get the engine."""
|
40 |
global _ENGINE_SINGLETON
|
41 |
if _ENGINE_SINGLETON is None:
|
@@ -46,7 +54,7 @@ def _get_engine() -> Engine:
|
|
46 |
@contextmanager
|
47 |
def get_session() -> Generator[Session, None, None]:
|
48 |
"""Get a session from the engine."""
|
49 |
-
engine =
|
50 |
with Session(engine) as session:
|
51 |
yield session
|
52 |
|
@@ -63,45 +71,48 @@ async def _database_lifespan(app: FastAPI):
|
|
63 |
4. Yield control to FastAPI app.
|
64 |
5. Close database + force push backup to remote dataset.
|
65 |
"""
|
66 |
-
|
67 |
-
|
|
|
68 |
|
|
|
69 |
print("Back-up database is enabled")
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
# Try to load backup from remote dataset
|
72 |
print("Trying to load backup from remote dataset...")
|
73 |
try:
|
74 |
-
|
75 |
-
repo_id=
|
76 |
repo_type="dataset",
|
77 |
-
filename="database.db",
|
78 |
token=constants.HF_TOKEN,
|
79 |
-
|
80 |
-
force_download=True,
|
81 |
)
|
82 |
except Exception:
|
83 |
# If backup is enabled but no backup is found, delete local database to prevent confusion.
|
84 |
print("Couldn't find backup in remote dataset.")
|
85 |
print("Deleting local database for a fresh start.")
|
86 |
-
try:
|
87 |
-
os.remove(constants.DATABASE_FILE)
|
88 |
-
except OSError:
|
89 |
-
pass
|
90 |
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
repo_id=constants.BACKUP_DATASET_ID, # type: ignore[arg-type]
|
95 |
-
folder_path=constants.DATABASE_PATH,
|
96 |
-
allow_patterns="database.db",
|
97 |
-
token=constants.HF_TOKEN,
|
98 |
-
private=True,
|
99 |
-
repo_type="dataset",
|
100 |
-
every=5,
|
101 |
-
)
|
102 |
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
105 |
|
106 |
yield
|
107 |
|
@@ -113,7 +124,23 @@ async def _database_lifespan(app: FastAPI):
|
|
113 |
|
114 |
if constants.BACKUP_DB:
|
115 |
print("Pushing backup to remote dataset...")
|
116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
|
119 |
def create_app() -> FastAPI:
|
|
|
1 |
+
import tempfile
|
2 |
from contextlib import asynccontextmanager, contextmanager
|
3 |
from typing import Annotated, Generator
|
4 |
|
5 |
+
from apscheduler.schedulers.background import BackgroundScheduler
|
6 |
from fastapi import Depends, FastAPI, HTTPException, Request
|
7 |
from fastapi.middleware.cors import CORSMiddleware
|
8 |
from fastapi.responses import FileResponse, RedirectResponse
|
9 |
from fastapi.staticfiles import StaticFiles
|
10 |
+
from huggingface_hub import (
|
11 |
+
OAuthInfo,
|
12 |
+
attach_huggingface_oauth,
|
13 |
+
create_repo,
|
14 |
+
parse_huggingface_oauth,
|
15 |
+
snapshot_download,
|
16 |
+
upload_folder,
|
17 |
+
)
|
18 |
from sqlalchemy.engine import Engine
|
19 |
from sqlmodel import Session, SQLModel, create_engine
|
20 |
|
21 |
from . import constants
|
22 |
+
from .parquet import export_to_parquet, import_from_parquet
|
23 |
|
24 |
_ENGINE_SINGLETON: Engine | None = None
|
25 |
|
|
|
43 |
RequiredOAuth = Annotated[OAuthInfo, Depends(_oauth_info_required)]
|
44 |
|
45 |
|
46 |
+
def get_engine() -> Engine:
|
|
|
47 |
"""Get the engine."""
|
48 |
global _ENGINE_SINGLETON
|
49 |
if _ENGINE_SINGLETON is None:
|
|
|
54 |
@contextmanager
|
55 |
def get_session() -> Generator[Session, None, None]:
|
56 |
"""Get a session from the engine."""
|
57 |
+
engine = get_engine()
|
58 |
with Session(engine) as session:
|
59 |
yield session
|
60 |
|
|
|
71 |
4. Yield control to FastAPI app.
|
72 |
5. Close database + force push backup to remote dataset.
|
73 |
"""
|
74 |
+
scheduler = BackgroundScheduler()
|
75 |
+
engine = get_engine()
|
76 |
+
SQLModel.metadata.create_all(engine)
|
77 |
|
78 |
+
if constants.BACKUP_DB:
|
79 |
print("Back-up database is enabled")
|
80 |
|
81 |
+
# Create remote dataset if it doesn't exist
|
82 |
+
repo_url = create_repo(
|
83 |
+
repo_id=constants.BACKUP_DATASET_ID, # type: ignore[arg-type]
|
84 |
+
repo_type="dataset",
|
85 |
+
token=constants.HF_TOKEN,
|
86 |
+
private=True,
|
87 |
+
exist_ok=True,
|
88 |
+
)
|
89 |
+
print(f"Backup dataset: {repo_url}")
|
90 |
+
repo_id = repo_url.repo_id
|
91 |
+
|
92 |
# Try to load backup from remote dataset
|
93 |
print("Trying to load backup from remote dataset...")
|
94 |
try:
|
95 |
+
backup_dir = snapshot_download(
|
96 |
+
repo_id=repo_id,
|
97 |
repo_type="dataset",
|
|
|
98 |
token=constants.HF_TOKEN,
|
99 |
+
allow_patterns="*.parquet",
|
|
|
100 |
)
|
101 |
except Exception:
|
102 |
# If backup is enabled but no backup is found, delete local database to prevent confusion.
|
103 |
print("Couldn't find backup in remote dataset.")
|
104 |
print("Deleting local database for a fresh start.")
|
|
|
|
|
|
|
|
|
105 |
|
106 |
+
engine = get_engine()
|
107 |
+
SQLModel.metadata.drop_all(engine)
|
108 |
+
SQLModel.metadata.create_all(engine)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
|
110 |
+
# Import parquet files to database
|
111 |
+
import_from_parquet(get_engine(), backup_dir)
|
112 |
+
|
113 |
+
# Start back-up scheduler
|
114 |
+
scheduler.add_job(_backup_to_hub, args=[repo_id], trigger="interval", minutes=5)
|
115 |
+
scheduler.start()
|
116 |
|
117 |
yield
|
118 |
|
|
|
124 |
|
125 |
if constants.BACKUP_DB:
|
126 |
print("Pushing backup to remote dataset...")
|
127 |
+
_backup_to_hub(repo_id)
|
128 |
+
|
129 |
+
|
130 |
+
def _backup_to_hub(repo_id: str) -> None:
|
131 |
+
"""Export backup to remote dataset as parquet files."""
|
132 |
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
133 |
+
export_to_parquet(get_engine(), tmp_dir)
|
134 |
+
|
135 |
+
upload_folder(
|
136 |
+
repo_id=repo_id,
|
137 |
+
folder_path=tmp_dir,
|
138 |
+
token=constants.HF_TOKEN,
|
139 |
+
repo_type="dataset",
|
140 |
+
allow_patterns="*.parquet",
|
141 |
+
commit_message="Backup database as parquet",
|
142 |
+
delete_patterns=["*.parquet"],
|
143 |
+
)
|
144 |
|
145 |
|
146 |
def create_app() -> FastAPI:
|
backend/src/parquet.py
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Parquet module.
|
3 |
+
|
4 |
+
TODO: handle migrations
|
5 |
+
TODO: make it work with chunked exports.
|
6 |
+
TODO: make it work with chunked imports.
|
7 |
+
|
8 |
+
Mostly auto-generated by Cursor + GPT-5.
|
9 |
+
"""
|
10 |
+
|
11 |
+
import os
|
12 |
+
|
13 |
+
import pandas as pd
|
14 |
+
from sqlalchemy import inspect, text
|
15 |
+
from sqlalchemy.engine import Engine
|
16 |
+
from sqlmodel import Session
|
17 |
+
|
18 |
+
|
19 |
+
def export_to_parquet(engine: Engine, backup_dir: str) -> None:
|
20 |
+
"""
|
21 |
+
Export each table in the database to a separate Parquet file.
|
22 |
+
Loads entire tables into memory and sorts deterministically.
|
23 |
+
|
24 |
+
TODO: make it work with chunked exports.
|
25 |
+
TODO: handle migrations
|
26 |
+
"""
|
27 |
+
os.makedirs(backup_dir, exist_ok=True)
|
28 |
+
inspector = inspect(engine)
|
29 |
+
table_names = inspector.get_table_names()
|
30 |
+
|
31 |
+
for table_name in table_names:
|
32 |
+
file_path = os.path.join(backup_dir, f"{table_name}.parquet")
|
33 |
+
|
34 |
+
# Load entire table into memory
|
35 |
+
query = text(f"SELECT * FROM {table_name}")
|
36 |
+
with engine.connect() as conn:
|
37 |
+
df = pd.read_sql_query(query, conn)
|
38 |
+
|
39 |
+
# Sort deterministically by all columns
|
40 |
+
sort_cols = list(df.columns)
|
41 |
+
df_sorted = df.sort_values(by=sort_cols).reset_index(drop=True)
|
42 |
+
|
43 |
+
# Write to Parquet
|
44 |
+
df_sorted.to_parquet(file_path, index=False)
|
45 |
+
print(f"Exported {table_name} to {file_path}")
|
46 |
+
|
47 |
+
|
48 |
+
def import_from_parquet(engine: Engine, backup_dir: str) -> None:
|
49 |
+
"""
|
50 |
+
Import each Parquet file into the database.
|
51 |
+
Checks schema strictly (column names + types).
|
52 |
+
Loads entire files into memory.
|
53 |
+
|
54 |
+
TODO: make it work with chunked imports.
|
55 |
+
TODO: handle migrations
|
56 |
+
"""
|
57 |
+
inspector = inspect(engine)
|
58 |
+
table_names = inspector.get_table_names()
|
59 |
+
|
60 |
+
for table_name in table_names:
|
61 |
+
file_path = os.path.join(backup_dir, f"{table_name}.parquet")
|
62 |
+
if not os.path.exists(file_path):
|
63 |
+
print(f"No backup found for table {table_name}, skipping.")
|
64 |
+
continue
|
65 |
+
|
66 |
+
# Clear table before import
|
67 |
+
with Session(engine) as session:
|
68 |
+
session.exec(text(f"DELETE FROM {table_name}"))
|
69 |
+
|
70 |
+
# Load entire file and insert at once
|
71 |
+
df = pd.read_parquet(file_path)
|
72 |
+
with engine.begin() as conn:
|
73 |
+
conn.execute(text(f"DELETE FROM {table_name}"))
|
74 |
+
if not df.empty:
|
75 |
+
columns = df.columns.tolist()
|
76 |
+
total_rows = len(df)
|
77 |
+
chunk_size = 10000
|
78 |
+
for start in range(0, total_rows, chunk_size):
|
79 |
+
end = min(start + chunk_size, total_rows)
|
80 |
+
chunk = df.iloc[start:end]
|
81 |
+
values = chunk.to_dict(orient="records")
|
82 |
+
insert_stmt = text(
|
83 |
+
f"INSERT INTO {table_name} ({', '.join(columns)}) VALUES "
|
84 |
+
+ ", ".join(
|
85 |
+
[
|
86 |
+
"("
|
87 |
+
+ ", ".join([f":{col}_{i}" for col in columns])
|
88 |
+
+ ")"
|
89 |
+
for i in range(len(values))
|
90 |
+
]
|
91 |
+
)
|
92 |
+
)
|
93 |
+
params = {}
|
94 |
+
for i, row in enumerate(values):
|
95 |
+
for col in columns:
|
96 |
+
params[f"{col}_{i}"] = row[col]
|
97 |
+
conn.execute(insert_stmt, params)
|
98 |
+
|
99 |
+
print(f"Imported {table_name} from {file_path}")
|
backend/src/test_parquet.py
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Test the parquet module.
|
3 |
+
|
4 |
+
Mostly auto-generated by Cursor + GPT-5.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os
|
8 |
+
import tempfile
|
9 |
+
from typing import Any
|
10 |
+
|
11 |
+
import pandas as pd
|
12 |
+
import pytest
|
13 |
+
from sqlalchemy import create_engine, text
|
14 |
+
from sqlalchemy.engine import Engine
|
15 |
+
from sqlmodel import Field, Session, SQLModel
|
16 |
+
|
17 |
+
from parquet import export_to_parquet, import_from_parquet
|
18 |
+
|
19 |
+
|
20 |
+
# Test model for creating temporary tables
|
21 |
+
class DummyUser(SQLModel, table=True):
|
22 |
+
id: int = Field(primary_key=True)
|
23 |
+
name: str = Field(max_length=100)
|
24 |
+
email: str = Field(max_length=255)
|
25 |
+
age: int = Field()
|
26 |
+
|
27 |
+
|
28 |
+
class DummyProduct(SQLModel, table=True):
|
29 |
+
id: int = Field(primary_key=True)
|
30 |
+
name: str = Field(max_length=200)
|
31 |
+
price: float = Field()
|
32 |
+
category: str = Field(max_length=100)
|
33 |
+
|
34 |
+
|
35 |
+
@pytest.fixture
|
36 |
+
def temp_db_engine():
|
37 |
+
"""Create a temporary SQLite database engine for testing."""
|
38 |
+
# Create temporary database file
|
39 |
+
temp_db = tempfile.NamedTemporaryFile(delete=False, suffix=".db")
|
40 |
+
temp_db.close()
|
41 |
+
|
42 |
+
# Create engine
|
43 |
+
engine = create_engine(f"sqlite:///{temp_db.name}")
|
44 |
+
|
45 |
+
# Create tables
|
46 |
+
SQLModel.metadata.create_all(engine)
|
47 |
+
|
48 |
+
yield engine
|
49 |
+
|
50 |
+
# Cleanup
|
51 |
+
engine.dispose()
|
52 |
+
os.unlink(temp_db.name)
|
53 |
+
|
54 |
+
|
55 |
+
@pytest.fixture
|
56 |
+
def sample_data():
|
57 |
+
"""Sample data for testing."""
|
58 |
+
users_data = [
|
59 |
+
{"id": 1, "name": "Alice", "email": "[email protected]", "age": 30},
|
60 |
+
{"id": 2, "name": "Bob", "email": "[email protected]", "age": 25},
|
61 |
+
{"id": 3, "name": "Charlie", "email": "[email protected]", "age": 35},
|
62 |
+
]
|
63 |
+
|
64 |
+
products_data = [
|
65 |
+
{"id": 1, "name": "Laptop", "price": 999.99, "category": "Electronics"},
|
66 |
+
{"id": 2, "name": "Book", "price": 19.99, "category": "Education"},
|
67 |
+
{"id": 3, "name": "Coffee", "price": 4.99, "category": "Food"},
|
68 |
+
]
|
69 |
+
|
70 |
+
return {"users": users_data, "products": products_data}
|
71 |
+
|
72 |
+
|
73 |
+
@pytest.fixture
|
74 |
+
def populated_db(temp_db_engine: Engine, sample_data: dict[str, list[dict[str, Any]]]):
|
75 |
+
"""Populate the temporary database with sample data."""
|
76 |
+
with Session(temp_db_engine) as session:
|
77 |
+
# Insert users
|
78 |
+
for user_data in sample_data["users"]:
|
79 |
+
user = DummyUser(**user_data)
|
80 |
+
session.add(user)
|
81 |
+
|
82 |
+
# Insert products
|
83 |
+
for product_data in sample_data["products"]:
|
84 |
+
product = DummyProduct(**product_data)
|
85 |
+
session.add(product)
|
86 |
+
|
87 |
+
session.commit()
|
88 |
+
|
89 |
+
return temp_db_engine
|
90 |
+
|
91 |
+
|
92 |
+
def test_export_to_parquet_success(
|
93 |
+
populated_db: Engine, sample_data: dict[str, list[dict[str, Any]]]
|
94 |
+
):
|
95 |
+
"""Test successful export of tables to parquet files."""
|
96 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
97 |
+
export_to_parquet(populated_db, temp_dir)
|
98 |
+
|
99 |
+
# Check that files were created
|
100 |
+
assert os.path.exists(os.path.join(temp_dir, "dummyuser.parquet"))
|
101 |
+
assert os.path.exists(os.path.join(temp_dir, "dummyproduct.parquet"))
|
102 |
+
|
103 |
+
# Verify data integrity
|
104 |
+
users_df = pd.read_parquet(os.path.join(temp_dir, "dummyuser.parquet"))
|
105 |
+
products_df = pd.read_parquet(os.path.join(temp_dir, "dummyproduct.parquet"))
|
106 |
+
|
107 |
+
assert len(users_df) == len(sample_data["users"])
|
108 |
+
assert len(products_df) == len(sample_data["products"])
|
109 |
+
|
110 |
+
# Check that data is sorted
|
111 |
+
assert users_df.equals(
|
112 |
+
users_df.sort_values(by=list(users_df.columns)).reset_index(drop=True)
|
113 |
+
)
|
114 |
+
assert products_df.equals(
|
115 |
+
products_df.sort_values(by=list(products_df.columns)).reset_index(drop=True)
|
116 |
+
)
|
117 |
+
|
118 |
+
|
119 |
+
def test_export_to_parquet_empty_table(temp_db_engine: Engine):
|
120 |
+
"""Test export with empty table."""
|
121 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
122 |
+
export_to_parquet(temp_db_engine, temp_dir)
|
123 |
+
|
124 |
+
# Should create file but skip empty table
|
125 |
+
assert os.path.exists(os.path.join(temp_dir, "dummyuser.parquet"))
|
126 |
+
assert os.path.exists(os.path.join(temp_dir, "dummyproduct.parquet"))
|
127 |
+
|
128 |
+
|
129 |
+
def test_export_to_parquet_creates_directory(populated_db):
|
130 |
+
"""Test that export creates the backup directory if it doesn't exist."""
|
131 |
+
temp_dir = os.path.join(tempfile.gettempdir(), "test_backup_dir")
|
132 |
+
|
133 |
+
try:
|
134 |
+
export_to_parquet(populated_db, temp_dir)
|
135 |
+
assert os.path.exists(temp_dir)
|
136 |
+
assert os.path.isdir(temp_dir)
|
137 |
+
finally:
|
138 |
+
if os.path.exists(temp_dir):
|
139 |
+
import shutil
|
140 |
+
|
141 |
+
shutil.rmtree(temp_dir)
|
142 |
+
|
143 |
+
|
144 |
+
def test_import_from_parquet_success(
|
145 |
+
populated_db: Engine, sample_data: dict[str, list[dict[str, Any]]]
|
146 |
+
):
|
147 |
+
"""Test successful import from parquet files."""
|
148 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
149 |
+
# First export
|
150 |
+
export_to_parquet(populated_db, temp_dir)
|
151 |
+
|
152 |
+
# Clear the database
|
153 |
+
with Session(populated_db) as session:
|
154 |
+
session.exec(text("DELETE FROM dummyuser"))
|
155 |
+
session.exec(text("DELETE FROM dummyproduct"))
|
156 |
+
session.commit()
|
157 |
+
|
158 |
+
# Verify tables are empty
|
159 |
+
with Session(populated_db) as session:
|
160 |
+
users = session.exec(text("SELECT COUNT(*) FROM dummyuser")).first()
|
161 |
+
products = session.exec(text("SELECT COUNT(*) FROM dummyproduct")).first()
|
162 |
+
assert users[0] == 0
|
163 |
+
assert products[0] == 0
|
164 |
+
|
165 |
+
# Import from parquet
|
166 |
+
import_from_parquet(populated_db, temp_dir)
|
167 |
+
|
168 |
+
# Verify data was imported
|
169 |
+
with Session(populated_db) as session:
|
170 |
+
users = session.exec(text("SELECT COUNT(*) FROM dummyuser")).first()
|
171 |
+
products = session.exec(text("SELECT COUNT(*) FROM dummyproduct")).first()
|
172 |
+
assert users[0] == len(sample_data["users"])
|
173 |
+
assert products[0] == len(sample_data["products"])
|
174 |
+
|
175 |
+
|
176 |
+
def test_import_from_parquet_missing_file(populated_db: Engine):
|
177 |
+
"""Test import handles missing parquet files gracefully."""
|
178 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
179 |
+
# Don't create any parquet files
|
180 |
+
import_from_parquet(populated_db, temp_dir)
|
181 |
+
# Should not raise an error, just skip missing files
|
182 |
+
|
183 |
+
|
184 |
+
def test_import_from_parquet_clears_existing_data(populated_db: Engine):
|
185 |
+
"""Test that import clears existing data before inserting new data."""
|
186 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
187 |
+
# First export
|
188 |
+
export_to_parquet(populated_db, temp_dir)
|
189 |
+
|
190 |
+
# Modify data in database
|
191 |
+
with Session(populated_db) as session:
|
192 |
+
session.exec(text("UPDATE dummyuser SET name = 'Modified' WHERE id = 1"))
|
193 |
+
session.commit()
|
194 |
+
|
195 |
+
# Verify modification
|
196 |
+
with Session(populated_db) as session:
|
197 |
+
result = session.exec(
|
198 |
+
text("SELECT name FROM dummyuser WHERE id = 1")
|
199 |
+
).first()
|
200 |
+
assert result[0] == "Modified"
|
201 |
+
|
202 |
+
# Import should clear and restore original data
|
203 |
+
import_from_parquet(populated_db, temp_dir)
|
204 |
+
|
205 |
+
# Original name restored
|
206 |
+
with Session(populated_db) as session:
|
207 |
+
result = session.exec(
|
208 |
+
text("SELECT name FROM dummyuser WHERE id = 1")
|
209 |
+
).first()
|
210 |
+
assert result[0] == "Alice"
|
211 |
+
|
212 |
+
|
213 |
+
def test_export_import_cycle(
|
214 |
+
populated_db: Engine, sample_data: dict[str, list[dict[str, Any]]]
|
215 |
+
):
|
216 |
+
"""Test complete export and import cycle maintains data integrity."""
|
217 |
+
with tempfile.TemporaryDirectory() as temp_dir:
|
218 |
+
# Export
|
219 |
+
export_to_parquet(populated_db, temp_dir)
|
220 |
+
|
221 |
+
# Clear database
|
222 |
+
with Session(populated_db) as session:
|
223 |
+
session.exec(text("DELETE FROM dummyuser"))
|
224 |
+
session.exec(text("DELETE FROM dummyproduct"))
|
225 |
+
session.commit()
|
226 |
+
|
227 |
+
# Import
|
228 |
+
import_from_parquet(populated_db, temp_dir)
|
229 |
+
|
230 |
+
# Verify data integrity
|
231 |
+
with Session(populated_db) as session:
|
232 |
+
# Check users
|
233 |
+
users_result = session.exec(
|
234 |
+
text("SELECT * FROM dummyuser ORDER BY id")
|
235 |
+
).fetchall()
|
236 |
+
assert len(users_result) == len(sample_data["users"])
|
237 |
+
|
238 |
+
for i, user in enumerate(users_result):
|
239 |
+
assert user[0] == sample_data["users"][i]["id"]
|
240 |
+
assert user[1] == sample_data["users"][i]["name"]
|
241 |
+
assert user[2] == sample_data["users"][i]["email"]
|
242 |
+
assert user[3] == sample_data["users"][i]["age"]
|
243 |
+
|
244 |
+
# Check products
|
245 |
+
products_result = session.exec(
|
246 |
+
text("SELECT * FROM dummyproduct ORDER BY id")
|
247 |
+
).fetchall()
|
248 |
+
assert len(products_result) == len(sample_data["products"])
|
249 |
+
|
250 |
+
for i, product in enumerate(products_result):
|
251 |
+
assert product[0] == sample_data["products"][i]["id"]
|
252 |
+
assert product[1] == sample_data["products"][i]["name"]
|
253 |
+
assert product[2] == sample_data["products"][i]["price"]
|
254 |
+
assert product[3] == sample_data["products"][i]["category"]
|
backend/uv.lock
CHANGED
@@ -25,6 +25,18 @@ wheels = [
|
|
25 |
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
|
26 |
]
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
[[package]]
|
29 |
name = "authlib"
|
30 |
version = "1.6.1"
|
@@ -182,28 +194,36 @@ name = "docker-space-fastapi-react"
|
|
182 |
version = "0.1.0"
|
183 |
source = { virtual = "." }
|
184 |
dependencies = [
|
|
|
185 |
{ name = "fastapi" },
|
186 |
{ name = "huggingface-hub", extra = ["oauth"] },
|
|
|
|
|
187 |
{ name = "sqlmodel" },
|
188 |
{ name = "uvicorn" },
|
189 |
]
|
190 |
|
191 |
[package.dev-dependencies]
|
192 |
dev = [
|
|
|
193 |
{ name = "ruff" },
|
194 |
{ name = "ty" },
|
195 |
]
|
196 |
|
197 |
[package.metadata]
|
198 |
requires-dist = [
|
|
|
199 |
{ name = "fastapi" },
|
200 |
{ name = "huggingface-hub", extras = ["oauth"], specifier = ">=0.34.4" },
|
|
|
|
|
201 |
{ name = "sqlmodel" },
|
202 |
{ name = "uvicorn" },
|
203 |
]
|
204 |
|
205 |
[package.metadata.requires-dev]
|
206 |
dev = [
|
|
|
207 |
{ name = "ruff" },
|
208 |
{ name = "ty" },
|
209 |
]
|
@@ -361,6 +381,15 @@ wheels = [
|
|
361 |
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
362 |
]
|
363 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
364 |
[[package]]
|
365 |
name = "itsdangerous"
|
366 |
version = "2.2.0"
|
@@ -370,6 +399,69 @@ wheels = [
|
|
370 |
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
|
371 |
]
|
372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
373 |
[[package]]
|
374 |
name = "packaging"
|
375 |
version = "25.0"
|
@@ -379,6 +471,78 @@ wheels = [
|
|
379 |
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
380 |
]
|
381 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
[[package]]
|
383 |
name = "pycparser"
|
384 |
version = "2.22"
|
@@ -445,6 +609,52 @@ wheels = [
|
|
445 |
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
446 |
]
|
447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
448 |
[[package]]
|
449 |
name = "pyyaml"
|
450 |
version = "6.0.2"
|
@@ -511,6 +721,15 @@ wheels = [
|
|
511 |
{ url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" },
|
512 |
]
|
513 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
514 |
[[package]]
|
515 |
name = "sniffio"
|
516 |
version = "1.3.1"
|
@@ -633,6 +852,27 @@ wheels = [
|
|
633 |
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
634 |
]
|
635 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
636 |
[[package]]
|
637 |
name = "urllib3"
|
638 |
version = "2.5.0"
|
|
|
25 |
{ url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" },
|
26 |
]
|
27 |
|
28 |
+
[[package]]
|
29 |
+
name = "apscheduler"
|
30 |
+
version = "3.11.0"
|
31 |
+
source = { registry = "https://pypi.org/simple" }
|
32 |
+
dependencies = [
|
33 |
+
{ name = "tzlocal" },
|
34 |
+
]
|
35 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347, upload-time = "2024-11-24T19:39:26.463Z" }
|
36 |
+
wheels = [
|
37 |
+
{ url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004, upload-time = "2024-11-24T19:39:24.442Z" },
|
38 |
+
]
|
39 |
+
|
40 |
[[package]]
|
41 |
name = "authlib"
|
42 |
version = "1.6.1"
|
|
|
194 |
version = "0.1.0"
|
195 |
source = { virtual = "." }
|
196 |
dependencies = [
|
197 |
+
{ name = "apscheduler" },
|
198 |
{ name = "fastapi" },
|
199 |
{ name = "huggingface-hub", extra = ["oauth"] },
|
200 |
+
{ name = "pandas" },
|
201 |
+
{ name = "pyarrow" },
|
202 |
{ name = "sqlmodel" },
|
203 |
{ name = "uvicorn" },
|
204 |
]
|
205 |
|
206 |
[package.dev-dependencies]
|
207 |
dev = [
|
208 |
+
{ name = "pytest" },
|
209 |
{ name = "ruff" },
|
210 |
{ name = "ty" },
|
211 |
]
|
212 |
|
213 |
[package.metadata]
|
214 |
requires-dist = [
|
215 |
+
{ name = "apscheduler", specifier = ">=3.11.0" },
|
216 |
{ name = "fastapi" },
|
217 |
{ name = "huggingface-hub", extras = ["oauth"], specifier = ">=0.34.4" },
|
218 |
+
{ name = "pandas", specifier = ">=2.3.1" },
|
219 |
+
{ name = "pyarrow", specifier = ">=21.0.0" },
|
220 |
{ name = "sqlmodel" },
|
221 |
{ name = "uvicorn" },
|
222 |
]
|
223 |
|
224 |
[package.metadata.requires-dev]
|
225 |
dev = [
|
226 |
+
{ name = "pytest" },
|
227 |
{ name = "ruff" },
|
228 |
{ name = "ty" },
|
229 |
]
|
|
|
381 |
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
382 |
]
|
383 |
|
384 |
+
[[package]]
|
385 |
+
name = "iniconfig"
|
386 |
+
version = "2.1.0"
|
387 |
+
source = { registry = "https://pypi.org/simple" }
|
388 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
389 |
+
wheels = [
|
390 |
+
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
391 |
+
]
|
392 |
+
|
393 |
[[package]]
|
394 |
name = "itsdangerous"
|
395 |
version = "2.2.0"
|
|
|
399 |
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
|
400 |
]
|
401 |
|
402 |
+
[[package]]
|
403 |
+
name = "numpy"
|
404 |
+
version = "2.3.2"
|
405 |
+
source = { registry = "https://pypi.org/simple" }
|
406 |
+
sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" }
|
407 |
+
wheels = [
|
408 |
+
{ url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" },
|
409 |
+
{ url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" },
|
410 |
+
{ url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" },
|
411 |
+
{ url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" },
|
412 |
+
{ url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" },
|
413 |
+
{ url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" },
|
414 |
+
{ url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" },
|
415 |
+
{ url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" },
|
416 |
+
{ url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" },
|
417 |
+
{ url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" },
|
418 |
+
{ url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" },
|
419 |
+
{ url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" },
|
420 |
+
{ url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" },
|
421 |
+
{ url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" },
|
422 |
+
{ url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" },
|
423 |
+
{ url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" },
|
424 |
+
{ url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" },
|
425 |
+
{ url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" },
|
426 |
+
{ url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" },
|
427 |
+
{ url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" },
|
428 |
+
{ url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" },
|
429 |
+
{ url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" },
|
430 |
+
{ url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" },
|
431 |
+
{ url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" },
|
432 |
+
{ url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" },
|
433 |
+
{ url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" },
|
434 |
+
{ url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" },
|
435 |
+
{ url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" },
|
436 |
+
{ url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" },
|
437 |
+
{ url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" },
|
438 |
+
{ url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" },
|
439 |
+
{ url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" },
|
440 |
+
{ url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" },
|
441 |
+
{ url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" },
|
442 |
+
{ url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" },
|
443 |
+
{ url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" },
|
444 |
+
{ url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" },
|
445 |
+
{ url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" },
|
446 |
+
{ url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" },
|
447 |
+
{ url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" },
|
448 |
+
{ url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" },
|
449 |
+
{ url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" },
|
450 |
+
{ url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" },
|
451 |
+
{ url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" },
|
452 |
+
{ url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" },
|
453 |
+
{ url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" },
|
454 |
+
{ url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" },
|
455 |
+
{ url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" },
|
456 |
+
{ url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" },
|
457 |
+
{ url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" },
|
458 |
+
{ url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" },
|
459 |
+
{ url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" },
|
460 |
+
{ url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" },
|
461 |
+
{ url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" },
|
462 |
+
{ url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" },
|
463 |
+
]
|
464 |
+
|
465 |
[[package]]
|
466 |
name = "packaging"
|
467 |
version = "25.0"
|
|
|
471 |
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
472 |
]
|
473 |
|
474 |
+
[[package]]
|
475 |
+
name = "pandas"
|
476 |
+
version = "2.3.1"
|
477 |
+
source = { registry = "https://pypi.org/simple" }
|
478 |
+
dependencies = [
|
479 |
+
{ name = "numpy" },
|
480 |
+
{ name = "python-dateutil" },
|
481 |
+
{ name = "pytz" },
|
482 |
+
{ name = "tzdata" },
|
483 |
+
]
|
484 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" }
|
485 |
+
wheels = [
|
486 |
+
{ url = "https://files.pythonhosted.org/packages/46/de/b8445e0f5d217a99fe0eeb2f4988070908979bec3587c0633e5428ab596c/pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3", size = 11588172, upload-time = "2025-07-07T19:18:52.054Z" },
|
487 |
+
{ url = "https://files.pythonhosted.org/packages/1e/e0/801cdb3564e65a5ac041ab99ea6f1d802a6c325bb6e58c79c06a3f1cd010/pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232", size = 10717365, upload-time = "2025-07-07T19:18:54.785Z" },
|
488 |
+
{ url = "https://files.pythonhosted.org/packages/51/a5/c76a8311833c24ae61a376dbf360eb1b1c9247a5d9c1e8b356563b31b80c/pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e", size = 11280411, upload-time = "2025-07-07T19:18:57.045Z" },
|
489 |
+
{ url = "https://files.pythonhosted.org/packages/da/01/e383018feba0a1ead6cf5fe8728e5d767fee02f06a3d800e82c489e5daaf/pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4", size = 11988013, upload-time = "2025-07-07T19:18:59.771Z" },
|
490 |
+
{ url = "https://files.pythonhosted.org/packages/5b/14/cec7760d7c9507f11c97d64f29022e12a6cc4fc03ac694535e89f88ad2ec/pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8", size = 12767210, upload-time = "2025-07-07T19:19:02.944Z" },
|
491 |
+
{ url = "https://files.pythonhosted.org/packages/50/b9/6e2d2c6728ed29fb3d4d4d302504fb66f1a543e37eb2e43f352a86365cdf/pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679", size = 13440571, upload-time = "2025-07-07T19:19:06.82Z" },
|
492 |
+
{ url = "https://files.pythonhosted.org/packages/80/a5/3a92893e7399a691bad7664d977cb5e7c81cf666c81f89ea76ba2bff483d/pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8", size = 10987601, upload-time = "2025-07-07T19:19:09.589Z" },
|
493 |
+
{ url = "https://files.pythonhosted.org/packages/32/ed/ff0a67a2c5505e1854e6715586ac6693dd860fbf52ef9f81edee200266e7/pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22", size = 11531393, upload-time = "2025-07-07T19:19:12.245Z" },
|
494 |
+
{ url = "https://files.pythonhosted.org/packages/c7/db/d8f24a7cc9fb0972adab0cc80b6817e8bef888cfd0024eeb5a21c0bb5c4a/pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a", size = 10668750, upload-time = "2025-07-07T19:19:14.612Z" },
|
495 |
+
{ url = "https://files.pythonhosted.org/packages/0f/b0/80f6ec783313f1e2356b28b4fd8d2148c378370045da918c73145e6aab50/pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928", size = 11342004, upload-time = "2025-07-07T19:19:16.857Z" },
|
496 |
+
{ url = "https://files.pythonhosted.org/packages/e9/e2/20a317688435470872885e7fc8f95109ae9683dec7c50be29b56911515a5/pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9", size = 12050869, upload-time = "2025-07-07T19:19:19.265Z" },
|
497 |
+
{ url = "https://files.pythonhosted.org/packages/55/79/20d746b0a96c67203a5bee5fb4e00ac49c3e8009a39e1f78de264ecc5729/pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12", size = 12750218, upload-time = "2025-07-07T19:19:21.547Z" },
|
498 |
+
{ url = "https://files.pythonhosted.org/packages/7c/0f/145c8b41e48dbf03dd18fdd7f24f8ba95b8254a97a3379048378f33e7838/pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb", size = 13416763, upload-time = "2025-07-07T19:19:23.939Z" },
|
499 |
+
{ url = "https://files.pythonhosted.org/packages/b2/c0/54415af59db5cdd86a3d3bf79863e8cc3fa9ed265f0745254061ac09d5f2/pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956", size = 10987482, upload-time = "2025-07-07T19:19:42.699Z" },
|
500 |
+
{ url = "https://files.pythonhosted.org/packages/48/64/2fd2e400073a1230e13b8cd604c9bc95d9e3b962e5d44088ead2e8f0cfec/pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a", size = 12029159, upload-time = "2025-07-07T19:19:26.362Z" },
|
501 |
+
{ url = "https://files.pythonhosted.org/packages/d8/0a/d84fd79b0293b7ef88c760d7dca69828d867c89b6d9bc52d6a27e4d87316/pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9", size = 11393287, upload-time = "2025-07-07T19:19:29.157Z" },
|
502 |
+
{ url = "https://files.pythonhosted.org/packages/50/ae/ff885d2b6e88f3c7520bb74ba319268b42f05d7e583b5dded9837da2723f/pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275", size = 11309381, upload-time = "2025-07-07T19:19:31.436Z" },
|
503 |
+
{ url = "https://files.pythonhosted.org/packages/85/86/1fa345fc17caf5d7780d2699985c03dbe186c68fee00b526813939062bb0/pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab", size = 11883998, upload-time = "2025-07-07T19:19:34.267Z" },
|
504 |
+
{ url = "https://files.pythonhosted.org/packages/81/aa/e58541a49b5e6310d89474333e994ee57fea97c8aaa8fc7f00b873059bbf/pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96", size = 12704705, upload-time = "2025-07-07T19:19:36.856Z" },
|
505 |
+
{ url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" },
|
506 |
+
]
|
507 |
+
|
508 |
+
[[package]]
|
509 |
+
name = "pluggy"
|
510 |
+
version = "1.6.0"
|
511 |
+
source = { registry = "https://pypi.org/simple" }
|
512 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
513 |
+
wheels = [
|
514 |
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
515 |
+
]
|
516 |
+
|
517 |
+
[[package]]
|
518 |
+
name = "pyarrow"
|
519 |
+
version = "21.0.0"
|
520 |
+
source = { registry = "https://pypi.org/simple" }
|
521 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" }
|
522 |
+
wheels = [
|
523 |
+
{ url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" },
|
524 |
+
{ url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" },
|
525 |
+
{ url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" },
|
526 |
+
{ url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" },
|
527 |
+
{ url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" },
|
528 |
+
{ url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" },
|
529 |
+
{ url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" },
|
530 |
+
{ url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" },
|
531 |
+
{ url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" },
|
532 |
+
{ url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" },
|
533 |
+
{ url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" },
|
534 |
+
{ url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" },
|
535 |
+
{ url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" },
|
536 |
+
{ url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" },
|
537 |
+
{ url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" },
|
538 |
+
{ url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" },
|
539 |
+
{ url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" },
|
540 |
+
{ url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" },
|
541 |
+
{ url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" },
|
542 |
+
{ url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" },
|
543 |
+
{ url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" },
|
544 |
+
]
|
545 |
+
|
546 |
[[package]]
|
547 |
name = "pycparser"
|
548 |
version = "2.22"
|
|
|
609 |
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
|
610 |
]
|
611 |
|
612 |
+
[[package]]
|
613 |
+
name = "pygments"
|
614 |
+
version = "2.19.2"
|
615 |
+
source = { registry = "https://pypi.org/simple" }
|
616 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
617 |
+
wheels = [
|
618 |
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
619 |
+
]
|
620 |
+
|
621 |
+
[[package]]
|
622 |
+
name = "pytest"
|
623 |
+
version = "8.4.1"
|
624 |
+
source = { registry = "https://pypi.org/simple" }
|
625 |
+
dependencies = [
|
626 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
627 |
+
{ name = "iniconfig" },
|
628 |
+
{ name = "packaging" },
|
629 |
+
{ name = "pluggy" },
|
630 |
+
{ name = "pygments" },
|
631 |
+
]
|
632 |
+
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
633 |
+
wheels = [
|
634 |
+
{ url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
|
635 |
+
]
|
636 |
+
|
637 |
+
[[package]]
|
638 |
+
name = "python-dateutil"
|
639 |
+
version = "2.9.0.post0"
|
640 |
+
source = { registry = "https://pypi.org/simple" }
|
641 |
+
dependencies = [
|
642 |
+
{ name = "six" },
|
643 |
+
]
|
644 |
+
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
|
645 |
+
wheels = [
|
646 |
+
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
647 |
+
]
|
648 |
+
|
649 |
+
[[package]]
|
650 |
+
name = "pytz"
|
651 |
+
version = "2025.2"
|
652 |
+
source = { registry = "https://pypi.org/simple" }
|
653 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
|
654 |
+
wheels = [
|
655 |
+
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
656 |
+
]
|
657 |
+
|
658 |
[[package]]
|
659 |
name = "pyyaml"
|
660 |
version = "6.0.2"
|
|
|
721 |
{ url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" },
|
722 |
]
|
723 |
|
724 |
+
[[package]]
|
725 |
+
name = "six"
|
726 |
+
version = "1.17.0"
|
727 |
+
source = { registry = "https://pypi.org/simple" }
|
728 |
+
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
|
729 |
+
wheels = [
|
730 |
+
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
731 |
+
]
|
732 |
+
|
733 |
[[package]]
|
734 |
name = "sniffio"
|
735 |
version = "1.3.1"
|
|
|
852 |
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
|
853 |
]
|
854 |
|
855 |
+
[[package]]
|
856 |
+
name = "tzdata"
|
857 |
+
version = "2025.2"
|
858 |
+
source = { registry = "https://pypi.org/simple" }
|
859 |
+
sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
|
860 |
+
wheels = [
|
861 |
+
{ url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
|
862 |
+
]
|
863 |
+
|
864 |
+
[[package]]
|
865 |
+
name = "tzlocal"
|
866 |
+
version = "5.3.1"
|
867 |
+
source = { registry = "https://pypi.org/simple" }
|
868 |
+
dependencies = [
|
869 |
+
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
870 |
+
]
|
871 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
|
872 |
+
wheels = [
|
873 |
+
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
874 |
+
]
|
875 |
+
|
876 |
[[package]]
|
877 |
name = "urllib3"
|
878 |
version = "2.5.0"
|