diff --git a/electron/servers/fastapi/alembic.ini b/electron/servers/fastapi/alembic.ini index 816710ec..f11ee6ae 100644 --- a/electron/servers/fastapi/alembic.ini +++ b/electron/servers/fastapi/alembic.ini @@ -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 diff --git a/electron/servers/fastapi/alembic/env.py b/electron/servers/fastapi/alembic/env.py index ff79a803..72da76b3 100644 --- a/electron/servers/fastapi/alembic/env.py +++ b/electron/servers/fastapi/alembic/env.py @@ -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 diff --git a/electron/servers/fastapi/alembic/versions/7bdd5fd9eabf_add_theme_to_presentations.py b/electron/servers/fastapi/alembic/versions/7bdd5fd9eabf_add_theme_to_presentations.py new file mode 100644 index 00000000..7508d67e --- /dev/null +++ b/electron/servers/fastapi/alembic/versions/7bdd5fd9eabf_add_theme_to_presentations.py @@ -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 ### diff --git a/electron/servers/fastapi/migrations.py b/electron/servers/fastapi/migrations.py index 51857107..d6356f2c 100644 --- a/electron/servers/fastapi/migrations.py +++ b/electron/servers/fastapi/migrations.py @@ -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() diff --git a/electron/servers/fastapi/scripts/fresh_sqlite_migrate.py b/electron/servers/fastapi/scripts/fresh_sqlite_migrate.py new file mode 100644 index 00000000..82923672 --- /dev/null +++ b/electron/servers/fastapi/scripts/fresh_sqlite_migrate.py @@ -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() diff --git a/electron/servers/fastapi/services/database.py b/electron/servers/fastapi/services/database.py index 6149447a..7188cd92 100644 --- a/electron/servers/fastapi/services/database.py +++ b/electron/servers/fastapi/services/database.py @@ -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( diff --git a/electron/servers/fastapi/services/image_generation_service.py b/electron/servers/fastapi/services/image_generation_service.py index f9ec1202..adbf881a 100644 --- a/electron/servers/fastapi/services/image_generation_service.py +++ b/electron/servers/fastapi/services/image_generation_service.py @@ -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( diff --git a/electron/servers/fastapi/uv.lock b/electron/servers/fastapi/uv.lock index e31671a0..74a53a69 100644 --- a/electron/servers/fastapi/uv.lock +++ b/electron/servers/fastapi/uv.lock @@ -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]]