feat: add migration for theme column in presentations and enhance database migration handling
- Introduced a new Alembic migration to add a 'theme' column to the 'presentations' table. - Updated migration scripts to handle unversioned databases more gracefully. - Added a script for programmatically deleting local SQLite database files and running migrations. - Enhanced database initialization logic to conditionally run migrations based on environment settings.
This commit is contained in:
parent
9349b2fd3d
commit
024f174867
8 changed files with 265 additions and 84 deletions
|
|
@ -2,7 +2,7 @@
|
|||
# migrations.py sets these programmatically when running at app startup.
|
||||
|
||||
[alembic]
|
||||
script_location = alembic
|
||||
script_location = %(here)s/alembic
|
||||
# sqlalchemy.url is overridden by env.py (uses DATABASE_URL or APP_DATA_DIRECTORY)
|
||||
sqlalchemy.url = sqlite:///placeholder
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ if alembic_config.config_file_name is not None:
|
|||
|
||||
target_metadata = SQLModel.metadata
|
||||
|
||||
# alembic.ini sets this so Config validates; treat it as "unset" for URL resolution.
|
||||
_CLI_PLACEHOLDER_DB_URL = "sqlite:///placeholder"
|
||||
|
||||
|
||||
def _to_sync_database_url(database_url: str) -> str:
|
||||
# Preserve slash counts for sqlite URLs so Windows paths stay valid.
|
||||
|
|
@ -51,7 +54,7 @@ def _get_url() -> str:
|
|||
falling back to the DATABASE_URL environment variable or a local SQLite DB.
|
||||
"""
|
||||
configured = alembic_config.get_main_option("sqlalchemy.url")
|
||||
if configured:
|
||||
if configured and configured != _CLI_PLACEHOLDER_DB_URL:
|
||||
return configured
|
||||
|
||||
from utils.db_utils import get_database_url_and_connect_args
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
"""add_theme_to_presentations
|
||||
|
||||
Revision ID: 7bdd5fd9eabf
|
||||
Revises: 00b3c27a13bc
|
||||
Create Date: 2026-03-25 18:52:10.116838
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op # type: ignore[import-not-found]
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '7bdd5fd9eabf'
|
||||
down_revision: Union[str, None] = '00b3c27a13bc'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('presentations', sa.Column('theme', sa.JSON(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('presentations', 'theme')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -3,10 +3,14 @@ from pathlib import Path
|
|||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
from alembic.script import ScriptDirectory
|
||||
from sqlalchemy import create_engine, inspect, text
|
||||
|
||||
from utils.db_utils import get_database_url_and_connect_args
|
||||
from utils.get_env import get_migrate_database_on_startup_env
|
||||
|
||||
LEGACY_BASELINE_REVISION = "00b3c27a13bc"
|
||||
|
||||
|
||||
def _to_sync_database_url(database_url: str) -> str:
|
||||
# Preserve slash counts for sqlite URLs so Windows paths stay valid.
|
||||
|
|
@ -28,6 +32,12 @@ async def migrate_database_on_startup() -> None:
|
|||
print("Migrations run successfully", flush=True)
|
||||
except Exception as exc:
|
||||
print(f"Error running migrations: {exc}", flush=True)
|
||||
raise
|
||||
|
||||
|
||||
def run_migrations_sync() -> None:
|
||||
"""Apply Alembic migrations to head (for CLI/scripts; no env gate)."""
|
||||
_run_migrations()
|
||||
|
||||
|
||||
def _run_migrations() -> None:
|
||||
|
|
@ -43,4 +53,55 @@ def _run_migrations() -> None:
|
|||
database_url = _to_sync_database_url(database_url)
|
||||
|
||||
config.set_main_option("sqlalchemy.url", database_url)
|
||||
command.upgrade(config, "head")
|
||||
|
||||
try:
|
||||
command.upgrade(config, "head")
|
||||
except Exception as exc:
|
||||
# Recovery path for historical DBs that were created via create_all()
|
||||
# without an alembic_version table.
|
||||
if _is_unversioned_populated_database(database_url):
|
||||
script = ScriptDirectory.from_config(config)
|
||||
known_revisions = {rev.revision for rev in script.walk_revisions()}
|
||||
baseline_revision = (
|
||||
LEGACY_BASELINE_REVISION
|
||||
if LEGACY_BASELINE_REVISION in known_revisions
|
||||
else script.get_base()
|
||||
)
|
||||
print(
|
||||
"Detected existing unversioned database schema. "
|
||||
f"Stamping revision to {baseline_revision} before upgrading.",
|
||||
flush=True,
|
||||
)
|
||||
command.stamp(config, baseline_revision)
|
||||
command.upgrade(config, "head")
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
def _is_unversioned_populated_database(database_url: str) -> bool:
|
||||
known_app_tables = {
|
||||
"presentations",
|
||||
"slides",
|
||||
"templates",
|
||||
"keyvaluesqlmodel",
|
||||
"imageasset",
|
||||
"presentation_layout_codes",
|
||||
"async_presentation_generation_tasks",
|
||||
"webhook_subscriptions",
|
||||
}
|
||||
engine = create_engine(database_url)
|
||||
try:
|
||||
with engine.connect() as connection:
|
||||
inspector = inspect(connection)
|
||||
table_names = set(inspector.get_table_names())
|
||||
has_alembic_version_table = "alembic_version" in table_names
|
||||
has_applied_revision = False
|
||||
if has_alembic_version_table:
|
||||
revision_count = connection.execute(
|
||||
text("SELECT COUNT(*) FROM alembic_version")
|
||||
).scalar_one()
|
||||
has_applied_revision = revision_count > 0
|
||||
has_known_app_tables = len(table_names.intersection(known_app_tables)) > 0
|
||||
return has_known_app_tables and not has_applied_revision
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
|
|
|||
74
electron/servers/fastapi/scripts/fresh_sqlite_migrate.py
Normal file
74
electron/servers/fastapi/scripts/fresh_sqlite_migrate.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Delete local SQLite database files and run migrations programmatically (same as app startup).
|
||||
|
||||
Use this when alembic_version points at a removed revision or you want a clean DB.
|
||||
|
||||
From electron/servers/fastapi:
|
||||
|
||||
uv run python scripts/fresh_sqlite_migrate.py
|
||||
|
||||
Optional — match Electron dev layout (default if unset):
|
||||
|
||||
APP_DATA_DIRECTORY=/abs/path/to/electron/app_data uv run python scripts/fresh_sqlite_migrate.py
|
||||
|
||||
Then create migrations from models (DB is at init only):
|
||||
|
||||
uv run alembic revision --autogenerate -m "add_theme_to_presentations"
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# servers/fastapi as import root (same as runtime)
|
||||
_FASTAPI_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(_FASTAPI_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(_FASTAPI_ROOT))
|
||||
|
||||
os.environ.setdefault(
|
||||
"APP_DATA_DIRECTORY",
|
||||
str(_FASTAPI_ROOT.parent.parent / "app_data"),
|
||||
)
|
||||
os.environ.setdefault("MIGRATE_DATABASE_ON_STARTUP", "True")
|
||||
|
||||
from migrations import _to_sync_database_url, run_migrations_sync # noqa: E402
|
||||
from utils.db_utils import get_database_url_and_connect_args # noqa: E402
|
||||
|
||||
|
||||
def _sqlite_file_path(sync_url: str) -> Path | None:
|
||||
if not sync_url.startswith("sqlite:///"):
|
||||
return None
|
||||
raw = sync_url[len("sqlite:///") :]
|
||||
if os.name == "nt" and len(raw) >= 3 and raw[0] == "/" and raw[2] == ":":
|
||||
raw = raw[1:]
|
||||
return Path(raw)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
database_url, _ = get_database_url_and_connect_args()
|
||||
sync_url = _to_sync_database_url(database_url)
|
||||
paths: list[Path] = []
|
||||
|
||||
p = _sqlite_file_path(sync_url)
|
||||
if p is not None:
|
||||
paths.append(p)
|
||||
paths.append(p.parent / "container.db")
|
||||
|
||||
seen: set[Path] = set()
|
||||
for path in paths:
|
||||
if path in seen:
|
||||
continue
|
||||
seen.add(path)
|
||||
if path.is_file():
|
||||
print(f"Removing {path}", flush=True)
|
||||
path.unlink()
|
||||
|
||||
print("Running Alembic upgrade (programmatic)...", flush=True)
|
||||
run_migrations_sync()
|
||||
print("Done.", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -21,6 +21,7 @@ from models.sql.template import TemplateModel
|
|||
from models.sql.webhook_subscription import WebhookSubscription
|
||||
from utils.db_utils import get_database_url_and_connect_args
|
||||
from utils.get_env import get_app_data_directory_env
|
||||
from utils.get_env import get_migrate_database_on_startup_env
|
||||
|
||||
|
||||
database_url, connect_args = get_database_url_and_connect_args()
|
||||
|
|
@ -52,22 +53,24 @@ async def get_container_db_async_session() -> AsyncGenerator[AsyncSession, None]
|
|||
|
||||
# Create Database and Tables
|
||||
async def create_db_and_tables():
|
||||
async with sql_engine.begin() as conn:
|
||||
await conn.run_sync(
|
||||
lambda sync_conn: SQLModel.metadata.create_all(
|
||||
sync_conn,
|
||||
tables=[
|
||||
PresentationModel.__table__,
|
||||
SlideModel.__table__,
|
||||
KeyValueSqlModel.__table__,
|
||||
ImageAsset.__table__,
|
||||
PresentationLayoutCodeModel.__table__,
|
||||
TemplateModel.__table__,
|
||||
WebhookSubscription.__table__,
|
||||
AsyncPresentationGenerationTaskModel.__table__,
|
||||
],
|
||||
should_run_alembic = get_migrate_database_on_startup_env() in ["true", "True"]
|
||||
if not should_run_alembic:
|
||||
async with sql_engine.begin() as conn:
|
||||
await conn.run_sync(
|
||||
lambda sync_conn: SQLModel.metadata.create_all(
|
||||
sync_conn,
|
||||
tables=[
|
||||
PresentationModel.__table__,
|
||||
SlideModel.__table__,
|
||||
KeyValueSqlModel.__table__,
|
||||
ImageAsset.__table__,
|
||||
PresentationLayoutCodeModel.__table__,
|
||||
TemplateModel.__table__,
|
||||
WebhookSubscription.__table__,
|
||||
AsyncPresentationGenerationTaskModel.__table__,
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
async with container_db_engine.begin() as conn:
|
||||
await conn.run_sync(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import os
|
|||
import aiohttp
|
||||
from fastapi import HTTPException
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
from openai import NOT_GIVEN, AsyncOpenAI
|
||||
from models.image_prompt import ImagePrompt
|
||||
from models.sql.image_asset import ImageAsset
|
||||
|
|
@ -149,15 +150,39 @@ class ImageGenerationService:
|
|||
response = await asyncio.to_thread(
|
||||
client.models.generate_content,
|
||||
model=model,
|
||||
contents=[prompt],
|
||||
contents=prompt,
|
||||
config=types.GenerateContentConfig(
|
||||
response_modalities=["IMAGE"],
|
||||
),
|
||||
)
|
||||
|
||||
# Latest SDK docs expose images in response.parts.
|
||||
response_parts = getattr(response, "parts", None)
|
||||
if not response_parts and getattr(response, "candidates", None):
|
||||
first_candidate = response.candidates[0] if response.candidates else None
|
||||
content = getattr(first_candidate, "content", None) if first_candidate else None
|
||||
response_parts = getattr(content, "parts", None) if content else None
|
||||
|
||||
image_path = None
|
||||
for part in response.candidates[0].content.parts:
|
||||
for part in response_parts or []:
|
||||
if part.inline_data is not None:
|
||||
image = part.as_image()
|
||||
image_path = os.path.join(output_directory, f"{uuid.uuid4()}.jpg")
|
||||
image.save(image_path)
|
||||
mime_type = getattr(part.inline_data, "mime_type", "") or ""
|
||||
ext = mime_type.split("/")[-1] if mime_type.startswith("image/") else "png"
|
||||
image_path = os.path.join(output_directory, f"{uuid.uuid4()}.{ext}")
|
||||
if hasattr(part, "as_image"):
|
||||
part.as_image().save(image_path)
|
||||
else:
|
||||
# Backward-compatible fallback if helper method is unavailable.
|
||||
image_data = getattr(part.inline_data, "data", None)
|
||||
if image_data is None:
|
||||
continue
|
||||
image_bytes = (
|
||||
base64.b64decode(image_data)
|
||||
if isinstance(image_data, str)
|
||||
else image_data
|
||||
)
|
||||
with open(image_path, "wb") as image_file:
|
||||
image_file.write(image_bytes)
|
||||
|
||||
if not image_path:
|
||||
raise HTTPException(
|
||||
|
|
@ -169,9 +194,9 @@ class ImageGenerationService:
|
|||
async def generate_image_gemini_flash(
|
||||
self, prompt: str, output_directory: str
|
||||
) -> str:
|
||||
"""Generate image using Gemini Flash (gemini-2.5-flash-image-preview)."""
|
||||
"""Generate image using Gemini Flash (gemini-2.5-flash-image)."""
|
||||
return await self._generate_image_google(
|
||||
prompt, output_directory, "gemini-2.5-flash-image-preview"
|
||||
prompt, output_directory, "gemini-2.5-flash-image"
|
||||
)
|
||||
|
||||
async def generate_image_nanobanana_pro(
|
||||
|
|
|
|||
103
electron/servers/fastapi/uv.lock
generated
103
electron/servers/fastapi/uv.lock
generated
|
|
@ -235,15 +235,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.5.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.8.3"
|
||||
|
|
@ -741,10 +732,10 @@ wheels = [
|
|||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.24.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b", size = 37601, upload-time = "2026-02-16T02:50:45.614Z" }
|
||||
source = { registry = "https://download.pytorch.org/whl/cpu" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556", size = 24152, upload-time = "2026-02-16T02:50:44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -793,43 +784,49 @@ wheels = [
|
|||
[[package]]
|
||||
name = "fsspec"
|
||||
version = "2026.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" }
|
||||
source = { registry = "https://download.pytorch.org/whl/cpu" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-auth"
|
||||
version = "2.40.3"
|
||||
version = "2.49.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cachetools" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "pyasn1-modules" },
|
||||
{ name = "rsa" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825, upload-time = "2026-03-12T19:30:58.135Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737, upload-time = "2026-03-12T19:30:53.159Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
requests = [
|
||||
{ name = "requests" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "google-genai"
|
||||
version = "1.28.0"
|
||||
version = "1.68.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "google-auth" },
|
||||
{ name = "distro" },
|
||||
{ name = "google-auth", extra = ["requests"] },
|
||||
{ name = "httpx" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "requests" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/23/f1/039bb08df4670e204c55b5da0b2fa5228dff3346bda01389a86b300f6f58/google_genai-1.28.0.tar.gz", hash = "sha256:e93053c02e616842679ba5ecce5b99db8c0ca6310623c55ff6245b5b1d293138", size = 221029, upload-time = "2025-07-30T21:39:57.002Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/2c/f059982dbcb658cc535c81bbcbe7e2c040d675f4b563b03cdb01018a4bc3/google_genai-1.68.0.tar.gz", hash = "sha256:ac30c0b8bc630f9372993a97e4a11dae0e36f2e10d7c55eacdca95a9fa14ca96", size = 511285, upload-time = "2026-03-18T01:03:18.243Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/ea/b704df3b348d3ae3572b0db5b52438fa426900b0830cff664107abfdba69/google_genai-1.28.0-py3-none-any.whl", hash = "sha256:7fd506799005cc87d3c5704a2eb5a2cb020d45b4d216a802e606700308f7f2f3", size = 219384, upload-time = "2025-07-30T21:39:55.652Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/de/7d3ee9c94b74c3578ea4f88d45e8de9405902f857932334d81e89bce3dfa/google_genai-1.68.0-py3-none-any.whl", hash = "sha256:a1bc9919c0e2ea2907d1e319b65471d3d6d58c54822039a249fe1323e4178d15", size = 750912, upload-time = "2026-03-18T01:03:15.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1357,27 +1354,27 @@ wheels = [
|
|||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" }
|
||||
source = { registry = "https://download.pytorch.org/whl/cpu" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821" },
|
||||
{ url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2389,18 +2386,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "4.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyasn1" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtree"
|
||||
version = "1.4.1"
|
||||
|
|
@ -2722,7 +2707,7 @@ dependencies = [
|
|||
{ name = "torch", version = "2.10.0", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a76ce7b8d4fce291a25721ee2f921c783acc6dbd4fc32dc741ed2a1d5a8dde2f" },
|
||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a76ce7b8d4fce291a25721ee2f921c783acc6dbd4fc32dc741ed2a1d5a8dde2f" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2739,8 +2724,8 @@ dependencies = [
|
|||
{ name = "torch", version = "2.10.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torchvision-0.25.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:59be99d1c470ef470b134468aa6afa6f968081a503acb4ee883d70332f822e35" },
|
||||
{ url = "https://download.pytorch.org/whl/cpu/torchvision-0.25.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:aa016ab73e06a886f72edc8929ed2ed4c85aaaa6e10500ecdef921b03129b19e" },
|
||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.25.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:59be99d1c470ef470b134468aa6afa6f968081a503acb4ee883d70332f822e35" },
|
||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.25.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:aa016ab73e06a886f72edc8929ed2ed4c85aaaa6e10500ecdef921b03129b19e" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue