diff --git a/src/storage/Makefile b/src/storage/Makefile index 8e1ccd08..93318a88 100644 --- a/src/storage/Makefile +++ b/src/storage/Makefile @@ -1,24 +1,47 @@ +help:: + @echo "Available commands" + @echo " help -- (default) print this message" + start-infra: supabase --workdir infra start -x studio,gotrue,postgrest,mailpit,realtime,edge-runtime,logflare,vector,supavisor +help:: + @echo " start-infra -- start containers for tests" stop-infra: supabase --workdir infra stop +help:: + @echo " stop-infra -- stop containers for tests" + +tests: mypy pytest +help:: + @echo " tests -- run all tests for storage3" -tests: pytest +mypy: + uv run --package storage3 mypy src/storage3 tests +help:: + @echo " mypy -- run mypy on storage3" pytest: start-infra uv run --package storage3 pytest --cov=./ --cov-report=xml --cov-report=html -vv +help:: + @echo " pytest -- run pytest on storage3" build-sync: uv run --package storage3 run-unasync.py sed -i '0,/SyncMock, /{s/SyncMock, //}' tests/_sync/test_bucket.py tests/_sync/test_client.py sed -i 's/SyncMock/Mock/g' tests/_sync/test_bucket.py tests/_sync/test_client.py - sed -i 's/SyncClient/Client/g' storage3/_sync/client.py storage3/_sync/bucket.py storage3/_sync/file_api.py tests/_sync/test_bucket.py tests/_sync/test_client.py - sed -i 's/self\.session\.aclose/self\.session\.close/g' storage3/_sync/client.py + sed -i 's/SyncClient/Client/g' src/storage3/_sync/client.py src/storage3/_sync/bucket.py src/storage3/_sync/file_api.py tests/_sync/test_bucket.py tests/_sync/test_client.py + sed -i 's/self\.session\.aclose/self\.session\.close/g' src/storage3/_sync/client.py +help:: + @echo " build-sync -- generate _sync from _async implementation" clean: rm -rf htmlcov .pytest_cache .mypy_cache .ruff_cache rm -f .coverage coverage.xml +help:: + @echo " clean -- clean intermediary files" build: uv build --package storage3 +help:: + @echo " build -- invoke uv build on storage3 package" diff --git a/src/storage/pyproject.toml b/src/storage/pyproject.toml index b4429800..5365a8c2 100644 --- a/src/storage/pyproject.toml +++ b/src/storage/pyproject.toml @@ -36,7 +36,10 @@ repository = "https://github.com/supabase/supabase-py" lints = [ "pre-commit >=4.2.0", "ruff >=0.12.1", - "unasync >= 0.6.0" + "unasync >= 0.6.0", + "python-lsp-server (>=1.12.2,<2.0.0)", + "pylsp-mypy (>=0.7.0,<0.8.0)", + "python-lsp-ruff (>=2.2.2,<3.0.0)", ] docs = [ "Sphinx >=7.1.2", @@ -52,7 +55,6 @@ tests = [ dev = [ { include-group = "lints" }, { include-group = "tests" }, - { include-group = "lints" }, { include-group = "docs" }, ] @@ -85,6 +87,10 @@ filterwarnings = [ "ignore::DeprecationWarning", # ignore deprecation warnings globally ] +[tool.mypy] +follow_untyped_imports = true # for deprecation module that does not have stubs +allow_redefinition = true + [tool.uv] default-groups = [ "dev" ] diff --git a/src/storage/run-unasync.py b/src/storage/run-unasync.py index e4a94232..936a2628 100644 --- a/src/storage/run-unasync.py +++ b/src/storage/run-unasync.py @@ -2,7 +2,7 @@ import unasync -paths = Path("src/functions").glob("**/*.py") +paths = Path("src/storage3").glob("**/*.py") tests = Path("tests").glob("**/*.py") rules = (unasync._DEFAULT_RULE,) diff --git a/src/storage/src/storage3/_async/file_api.py b/src/storage/src/storage3/_async/file_api.py index 80952466..0b3cf681 100644 --- a/src/storage/src/storage3/_async/file_api.py +++ b/src/storage/src/storage3/_async/file_api.py @@ -22,6 +22,7 @@ RequestMethod, SignedUploadURL, SignedUrlResponse, + TransformOptions, UploadData, UploadResponse, URLOptions, @@ -165,7 +166,7 @@ async def create_signed_url( options options to be passed for downloading or transforming the file. """ - json = {"expiresIn": str(expires_in)} + json: dict[str, str | bool | TransformOptions] = {"expiresIn": str(expires_in)} download_query = "" if options.get("download"): json.update({"download": options["download"]}) @@ -209,7 +210,10 @@ async def create_signed_urls( options options to be passed for downloading the file. """ - json = {"paths": paths, "expiresIn": str(expires_in)} + json: dict[str, str | bool | None | list[str]] = { + "paths": paths, + "expiresIn": str(expires_in), + } download_query = "" if options.get("download"): json.update({"download": options.get("download")}) @@ -265,9 +269,7 @@ async def get_public_url(self, path: str, options: URLOptions = {}) -> str: render_path = "render/image" if options.get("transform") else "object" transformation_query = ( - urllib.parse.urlencode(options.get("transform")) - if options.get("transform") - else None + urllib.parse.urlencode(t) if (t := options.get("transform")) else None ) if transformation_query: @@ -322,7 +324,7 @@ async def copy(self, from_path: str, to_path: str) -> dict[str, str]: ) return res.json() - async def remove(self, paths: list) -> list[dict[str, Any]]: + async def remove(self, paths: list[str]) -> list[dict[str, Any]]: """ Deletes files within the same bucket @@ -341,7 +343,7 @@ async def remove(self, paths: list) -> list[dict[str, Any]]: async def info( self, path: str, - ) -> list[dict[str, str]]: + ) -> dict[str, Any]: """ Lists info for a particular file. @@ -381,7 +383,7 @@ async def list( self, path: Optional[str] = None, options: Optional[ListBucketFilesOptions] = None, - ) -> list[dict[str, str]]: + ) -> list[dict[str, Any]]: """ Lists all the files within a bucket. diff --git a/src/storage/src/storage3/_sync/file_api.py b/src/storage/src/storage3/_sync/file_api.py index a10a180f..128e063c 100644 --- a/src/storage/src/storage3/_sync/file_api.py +++ b/src/storage/src/storage3/_sync/file_api.py @@ -22,6 +22,7 @@ RequestMethod, SignedUploadURL, SignedUrlResponse, + TransformOptions, UploadData, UploadResponse, URLOptions, @@ -165,7 +166,7 @@ def create_signed_url( options options to be passed for downloading or transforming the file. """ - json = {"expiresIn": str(expires_in)} + json: dict[str, str | bool | TransformOptions] = {"expiresIn": str(expires_in)} download_query = "" if options.get("download"): json.update({"download": options["download"]}) @@ -209,7 +210,10 @@ def create_signed_urls( options options to be passed for downloading the file. """ - json = {"paths": paths, "expiresIn": str(expires_in)} + json: dict[str, str | bool | None | list[str]] = { + "paths": paths, + "expiresIn": str(expires_in), + } download_query = "" if options.get("download"): json.update({"download": options.get("download")}) @@ -265,9 +269,7 @@ def get_public_url(self, path: str, options: URLOptions = {}) -> str: render_path = "render/image" if options.get("transform") else "object" transformation_query = ( - urllib.parse.urlencode(options.get("transform")) - if options.get("transform") - else None + urllib.parse.urlencode(t) if (t := options.get("transform")) else None ) if transformation_query: @@ -322,7 +324,7 @@ def copy(self, from_path: str, to_path: str) -> dict[str, str]: ) return res.json() - def remove(self, paths: list) -> list[dict[str, Any]]: + def remove(self, paths: list[str]) -> list[dict[str, Any]]: """ Deletes files within the same bucket @@ -341,7 +343,7 @@ def remove(self, paths: list) -> list[dict[str, Any]]: def info( self, path: str, - ) -> list[dict[str, str]]: + ) -> dict[str, Any]: """ Lists info for a particular file. @@ -381,7 +383,7 @@ def list( self, path: Optional[str] = None, options: Optional[ListBucketFilesOptions] = None, - ) -> list[dict[str, str]]: + ) -> list[dict[str, Any]]: """ Lists all the files within a bucket. diff --git a/src/storage/src/storage3/exceptions.py b/src/storage/src/storage3/exceptions.py index d589ca86..9efcd2dd 100644 --- a/src/storage/src/storage3/exceptions.py +++ b/src/storage/src/storage3/exceptions.py @@ -6,6 +6,7 @@ class StorageApiErrorDict(TypedDict): name: str message: str + code: str status: int diff --git a/src/storage/src/storage3/py.typed b/src/storage/src/storage3/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/src/storage/tests/_async/test_client.py b/src/storage/tests/_async/test_client.py index 6b98d65e..2bfd4bac 100644 --- a/src/storage/tests/_async/test_client.py +++ b/src/storage/tests/_async/test_client.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import AsyncGenerator, Generator from dataclasses import dataclass from typing import TYPE_CHECKING from unittest.mock import AsyncMock, Mock, patch @@ -22,7 +23,7 @@ # Global variable to track the ids from the buckets created in the tests run -temp_test_buckets_ids = [] +temp_test_buckets_ids: list[str] = [] @pytest.fixture @@ -56,14 +57,10 @@ async def afinalizer(): request.addfinalizer(AsyncFinalizerFactory(afinalizer).finalizer) -async def bucket_factory( - storage: AsyncStorageClient, uuid_factory: Callable[[], str], public: bool -) -> str: - """Creates a test bucket which will be used in the whole storage tests run and deleted at the end""" - - @pytest.fixture -async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) -> str: +async def bucket( + storage: AsyncStorageClient, uuid_factory: Callable[[], str] +) -> AsyncGenerator[str]: """Creates a test bucket which will be used in the whole storage tests run and deleted at the end""" bucket_id = uuid_factory() @@ -84,7 +81,7 @@ async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) - @pytest.fixture async def public_bucket( storage: AsyncStorageClient, uuid_factory: Callable[[], str] -) -> str: +) -> AsyncGenerator[str]: """Creates a test public bucket which will be used in the whole storage tests run and deleted at the end""" bucket_id = uuid_factory() @@ -103,7 +100,9 @@ async def public_bucket( @pytest.fixture -def storage_file_client(storage: AsyncStorageClient, bucket: str) -> AsyncBucketProxy: +def storage_file_client( + storage: AsyncStorageClient, bucket: str +) -> Generator[AsyncBucketProxy]: """Creates the storage file client for the whole storage tests run""" yield storage.from_(bucket) @@ -111,7 +110,7 @@ def storage_file_client(storage: AsyncStorageClient, bucket: str) -> AsyncBucket @pytest.fixture def storage_file_client_public( storage: AsyncStorageClient, public_bucket: str -) -> AsyncBucketProxy: +) -> Generator[AsyncBucketProxy]: """Creates the storage file client for the whole storage tests run""" yield storage.from_(public_bucket) @@ -281,6 +280,7 @@ async def test_client_upload( image_info = next((f for f in files if f.get("name") == file.name), None) assert image == file.file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -308,6 +308,7 @@ async def test_client_update( ) assert image == two_files[1].file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == two_files[1].mime_type @@ -339,6 +340,7 @@ async def test_client_upload_to_signed_url( image_info = next((f for f in files if f.get("name") == file.name), None) assert image == file.file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == file.mime_type # Test with file_options=None @@ -586,6 +588,7 @@ async def test_client_copy( copied_info = next( (f for f in files if f.get("name") == f"copied_{file.name}"), None ) + assert copied_info is not None assert copied_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -612,6 +615,7 @@ async def test_client_move( # Verify metadata was preserved files = await storage_file_client.list(file.bucket_folder) moved_info = next((f for f in files if f.get("name") == f"moved_{file.name}"), None) + assert moved_info is not None assert moved_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -628,7 +632,7 @@ async def test_client_remove( assert await storage_file_client.exists(file.bucket_path) # Remove file - await storage_file_client.remove(file.bucket_path) + await storage_file_client.remove([file.bucket_path]) # Verify file no longer exists assert not await storage_file_client.exists(file.bucket_path) diff --git a/src/storage/tests/_sync/conftest.py b/src/storage/tests/_sync/conftest.py index fe5064a7..254715f5 100644 --- a/src/storage/tests/_sync/conftest.py +++ b/src/storage/tests/_sync/conftest.py @@ -14,18 +14,7 @@ def pytest_configure(config) -> None: load_dotenv(dotenv_path="tests/tests.env") -@pytest.fixture(scope="package") -def event_loop() -> Generator[asyncio.AbstractEventLoop]: - """Returns an event loop for the current thread""" - try: - loop = asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - yield loop - loop.close() - - -@pytest.fixture(scope="package") +@pytest.fixture def storage() -> Generator[SyncStorageClient]: url = os.environ.get("SUPABASE_TEST_URL") assert url is not None, "Must provide SUPABASE_TEST_URL environment variable" diff --git a/src/storage/tests/_sync/test_client.py b/src/storage/tests/_sync/test_client.py index 3d3b71e3..72f18035 100644 --- a/src/storage/tests/_sync/test_client.py +++ b/src/storage/tests/_sync/test_client.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections.abc import Generator from dataclasses import dataclass from typing import TYPE_CHECKING from unittest.mock import Mock, patch @@ -22,10 +23,10 @@ # Global variable to track the ids from the buckets created in the tests run -temp_test_buckets_ids = [] +temp_test_buckets_ids: list[str] = [] -@pytest.fixture(scope="module") +@pytest.fixture def uuid_factory() -> Callable[[], str]: def method() -> str: """Generate a 8 digits long UUID""" @@ -34,7 +35,7 @@ def method() -> str: return method -@pytest.fixture(scope="module", autouse=True) +@pytest.fixture def delete_left_buckets( request: pytest.FixtureRequest, storage: SyncStorageClient, @@ -56,14 +57,10 @@ def afinalizer(): request.addfinalizer(SyncFinalizerFactory(afinalizer).finalizer) -def bucket_factory( - storage: SyncStorageClient, uuid_factory: Callable[[], str], public: bool -) -> str: - """Creates a test bucket which will be used in the whole storage tests run and deleted at the end""" - - -@pytest.fixture(scope="module") -def bucket(storage: SyncStorageClient, uuid_factory: Callable[[], str]) -> str: +@pytest.fixture +def bucket( + storage: SyncStorageClient, uuid_factory: Callable[[], str] +) -> Generator[str]: """Creates a test bucket which will be used in the whole storage tests run and deleted at the end""" bucket_id = uuid_factory() @@ -81,8 +78,10 @@ def bucket(storage: SyncStorageClient, uuid_factory: Callable[[], str]) -> str: temp_test_buckets_ids.remove(bucket_id) -@pytest.fixture(scope="module") -def public_bucket(storage: SyncStorageClient, uuid_factory: Callable[[], str]) -> str: +@pytest.fixture +def public_bucket( + storage: SyncStorageClient, uuid_factory: Callable[[], str] +) -> Generator[str]: """Creates a test public bucket which will be used in the whole storage tests run and deleted at the end""" bucket_id = uuid_factory() @@ -100,16 +99,18 @@ def public_bucket(storage: SyncStorageClient, uuid_factory: Callable[[], str]) - temp_test_buckets_ids.remove(bucket_id) -@pytest.fixture(scope="module") -def storage_file_client(storage: SyncStorageClient, bucket: str) -> SyncBucketProxy: +@pytest.fixture +def storage_file_client( + storage: SyncStorageClient, bucket: str +) -> Generator[SyncBucketProxy]: """Creates the storage file client for the whole storage tests run""" yield storage.from_(bucket) -@pytest.fixture(scope="module") +@pytest.fixture def storage_file_client_public( storage: SyncStorageClient, public_bucket: str -) -> SyncBucketProxy: +) -> Generator[SyncBucketProxy]: """Creates the storage file client for the whole storage tests run""" yield storage.from_(public_bucket) @@ -279,6 +280,7 @@ def test_client_upload( image_info = next((f for f in files if f.get("name") == file.name), None) assert image == file.file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -306,6 +308,7 @@ def test_client_update( ) assert image == two_files[1].file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == two_files[1].mime_type @@ -337,6 +340,7 @@ def test_client_upload_to_signed_url( image_info = next((f for f in files if f.get("name") == file.name), None) assert image == file.file_content + assert image_info is not None assert image_info.get("metadata", {}).get("mimetype") == file.mime_type # Test with file_options=None @@ -582,6 +586,7 @@ def test_client_copy( copied_info = next( (f for f in files if f.get("name") == f"copied_{file.name}"), None ) + assert copied_info is not None assert copied_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -608,6 +613,7 @@ def test_client_move( # Verify metadata was preserved files = storage_file_client.list(file.bucket_folder) moved_info = next((f for f in files if f.get("name") == f"moved_{file.name}"), None) + assert moved_info is not None assert moved_info.get("metadata", {}).get("mimetype") == file.mime_type @@ -624,7 +630,7 @@ def test_client_remove( assert storage_file_client.exists(file.bucket_path) # Remove file - storage_file_client.remove(file.bucket_path) + storage_file_client.remove([file.bucket_path]) # Verify file no longer exists assert not storage_file_client.exists(file.bucket_path) diff --git a/uv.lock b/uv.lock index 1a57c612..7e637721 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.9" resolution-markers = [ "python_full_version >= '3.13'", @@ -1611,7 +1611,7 @@ wheels = [ [[package]] name = "postgrest" -version = "1.1.1" +version = "2.19.0" source = { editable = "src/postgrest" } dependencies = [ { name = "deprecation" }, @@ -2181,7 +2181,7 @@ wheels = [ [[package]] name = "realtime" -version = "2.7.0" +version = "2.19.0" source = { editable = "src/realtime" } dependencies = [ { name = "pydantic" }, @@ -2761,7 +2761,7 @@ wheels = [ [[package]] name = "storage3" -version = "0.12.1" +version = "2.19.0" source = { editable = "src/storage" } dependencies = [ { name = "deprecation" }, @@ -2772,16 +2772,20 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "pre-commit" }, + { name = "pylsp-mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "python-dotenv" }, + { name = "python-lsp-ruff" }, + { name = "python-lsp-server" }, { name = "ruff" }, { name = "sphinx", version = "7.4.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-press-theme" }, { name = "sphinx-toolbox" }, + { name = "types-deprecated" }, { name = "unasync" }, ] docs = [ @@ -2793,7 +2797,11 @@ docs = [ ] lints = [ { name = "pre-commit" }, + { name = "pylsp-mypy" }, + { name = "python-lsp-ruff" }, + { name = "python-lsp-server" }, { name = "ruff" }, + { name = "types-deprecated" }, { name = "unasync" }, ] tests = [ @@ -2813,14 +2821,18 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pylsp-mypy", specifier = ">=0.7.0,<0.8.0" }, { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-asyncio", specifier = ">=0.21.0" }, { name = "pytest-cov", specifier = ">=6.1.0" }, { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "python-lsp-ruff", specifier = ">=2.2.2,<3.0.0" }, + { name = "python-lsp-server", specifier = ">=1.12.2,<2.0.0" }, { name = "ruff", specifier = ">=0.12.1" }, { name = "sphinx", specifier = ">=7.1.2" }, { name = "sphinx-press-theme", specifier = ">=0.9.1" }, { name = "sphinx-toolbox", specifier = ">=3.4.0" }, + { name = "types-deprecated", specifier = ">=1.2.15" }, { name = "unasync", specifier = ">=0.6.0" }, ] docs = [ @@ -2830,7 +2842,11 @@ docs = [ ] lints = [ { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pylsp-mypy", specifier = ">=0.7.0,<0.8.0" }, + { name = "python-lsp-ruff", specifier = ">=2.2.2,<3.0.0" }, + { name = "python-lsp-server", specifier = ">=1.12.2,<2.0.0" }, { name = "ruff", specifier = ">=0.12.1" }, + { name = "types-deprecated", specifier = ">=1.2.15" }, { name = "unasync", specifier = ">=0.6.0" }, ] tests = [ @@ -2851,7 +2867,7 @@ wheels = [ [[package]] name = "supabase" -version = "2.18.1" +version = "2.19.0" source = { editable = "src/supabase" } dependencies = [ { name = "httpx" }, @@ -2924,7 +2940,7 @@ tests = [ [[package]] name = "supabase-auth" -version = "2.12.3" +version = "2.19.0" source = { editable = "src/auth" } dependencies = [ { name = "httpx", extra = ["http2"] }, @@ -2993,7 +3009,7 @@ tests = [ [[package]] name = "supabase-functions" -version = "0.10.1" +version = "2.19.0" source = { editable = "src/functions" } dependencies = [ { name = "httpx", extra = ["http2"] }, @@ -3004,14 +3020,20 @@ dependencies = [ dev = [ { name = "pre-commit" }, { name = "pyjwt" }, + { name = "pylsp-mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, + { name = "python-lsp-ruff" }, + { name = "python-lsp-server" }, { name = "ruff" }, { name = "unasync" }, ] lints = [ { name = "pre-commit" }, + { name = "pylsp-mypy" }, + { name = "python-lsp-ruff" }, + { name = "python-lsp-server" }, { name = "ruff" }, { name = "unasync" }, ] @@ -3032,14 +3054,20 @@ requires-dist = [ dev = [ { name = "pre-commit", specifier = ">=3.4,<5.0" }, { name = "pyjwt", specifier = ">=2.8.0" }, + { name = "pylsp-mypy", specifier = ">=0.7.0,<0.8.0" }, { name = "pytest", specifier = ">=7.4.2,<9.0.0" }, { name = "pytest-asyncio", specifier = ">=0.21.1,<1.2.0" }, { name = "pytest-cov", specifier = ">=4,<7" }, + { name = "python-lsp-ruff", specifier = ">=2.2.2,<3.0.0" }, + { name = "python-lsp-server", specifier = ">=1.12.2,<2.0.0" }, { name = "ruff", specifier = ">=0.12.1" }, { name = "unasync", specifier = ">=0.6.0" }, ] lints = [ { name = "pre-commit", specifier = ">=3.4,<5.0" }, + { name = "pylsp-mypy", specifier = ">=0.7.0,<0.8.0" }, + { name = "python-lsp-ruff", specifier = ">=2.2.2,<3.0.0" }, + { name = "python-lsp-server", specifier = ">=1.12.2,<2.0.0" }, { name = "ruff", specifier = ">=0.12.1" }, { name = "unasync", specifier = ">=0.6.0" }, ] @@ -3125,6 +3153,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, ] +[[package]] +name = "types-deprecated" +version = "1.2.15.20250304" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/67/eeefaaabb03b288aad85483d410452c8bbcbf8b2bd876b0e467ebd97415b/types_deprecated-1.2.15.20250304.tar.gz", hash = "sha256:c329030553029de5cc6cb30f269c11f4e00e598c4241290179f63cda7d33f719", size = 8015, upload-time = "2025-03-04T02:48:17.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/e3/c18aa72ab84e0bc127a3a94e93be1a6ac2cb281371d3a45376ab7cfdd31c/types_deprecated-1.2.15.20250304-py3-none-any.whl", hash = "sha256:86a65aa550ea8acf49f27e226b8953288cd851de887970fbbdf2239c116c3107", size = 8553, upload-time = "2025-03-04T02:48:16.666Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"