From 54271d2118f7500a48b9cadd4d020e2474b1ffb2 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 24 Sep 2025 09:21:59 -0700 Subject: [PATCH] feat: add legacy exception compatibility aliases This adds backward compatibility for users importing exceptions from `replicate.exceptions` instead of directly from the `replicate` module. The new `replicate/exceptions.py` module re-exports all exception classes from the internal `_exceptions` module, allowing both import patterns to work: - `from replicate import ModelError` (existing) - `from replicate.exceptions import ModelError` (legacy compatibility) This ensures backward compatibility with code that uses the legacy import pattern shown in documentation examples. Includes comprehensive tests to verify all exception classes are available through the legacy import path. --- requirements-dev.lock | 3 +- requirements.lock | 2 + src/replicate/exceptions.py | 47 ++++++++++++++++++++ tests/test_legacy_exceptions.py | 78 +++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/replicate/exceptions.py create mode 100644 tests/test_legacy_exceptions.py diff --git a/requirements-dev.lock b/requirements-dev.lock index 839ba5d..bebf15e 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -78,9 +78,10 @@ mypy-extensions==1.0.0 nodeenv==1.8.0 # via pyright nox==2023.4.22 -packaging==23.2 +packaging==25.0 # via nox # via pytest + # via replicate platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 diff --git a/requirements.lock b/requirements.lock index 9c126e0..9a52dad 100644 --- a/requirements.lock +++ b/requirements.lock @@ -52,6 +52,8 @@ idna==3.4 multidict==6.4.4 # via aiohttp # via yarl +packaging==25.0 + # via replicate propcache==0.3.1 # via aiohttp # via yarl diff --git a/src/replicate/exceptions.py b/src/replicate/exceptions.py new file mode 100644 index 0000000..7c77c70 --- /dev/null +++ b/src/replicate/exceptions.py @@ -0,0 +1,47 @@ +""" +Legacy compatibility module for exception imports. + +This module provides backward compatibility for users importing exceptions +from `replicate.exceptions` instead of directly from `replicate`. + +All exceptions are also available directly from the `replicate` module. +""" + +from __future__ import annotations + +# Import all exceptions from the internal module +from ._exceptions import ( + APIError, + ModelError, + ConflictError, + NotFoundError, + APIStatusError, + RateLimitError, + ReplicateError, + APITimeoutError, + BadRequestError, + APIConnectionError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, + UnprocessableEntityError, + APIResponseValidationError, +) + +__all__ = [ + "APIConnectionError", + "APIError", + "APIResponseValidationError", + "APIStatusError", + "APITimeoutError", + "AuthenticationError", + "BadRequestError", + "ConflictError", + "InternalServerError", + "ModelError", + "NotFoundError", + "PermissionDeniedError", + "RateLimitError", + "ReplicateError", + "UnprocessableEntityError", +] diff --git a/tests/test_legacy_exceptions.py b/tests/test_legacy_exceptions.py new file mode 100644 index 0000000..e07ee72 --- /dev/null +++ b/tests/test_legacy_exceptions.py @@ -0,0 +1,78 @@ +"""Tests for legacy exception import compatibility.""" + +from __future__ import annotations + + +def test_legacy_exception_imports(): + """Test that exceptions can be imported from replicate.exceptions.""" + # Test importing individual exceptions from replicate.exceptions + # All imports are intentionally kept to test that they can be imported + # Test that imported exceptions are the same as the ones from replicate + import replicate + from replicate.exceptions import ( + APIError, + ModelError, + ConflictError, # noqa: F401 # pyright: ignore[reportUnusedImport] + NotFoundError, + APIStatusError, # noqa: F401 # pyright: ignore[reportUnusedImport] + RateLimitError, + ReplicateError, + APITimeoutError, + BadRequestError, + APIConnectionError, + AuthenticationError, + InternalServerError, + PermissionDeniedError, # noqa: F401 # pyright: ignore[reportUnusedImport] + UnprocessableEntityError, # noqa: F401 # pyright: ignore[reportUnusedImport] + APIResponseValidationError, # noqa: F401 # pyright: ignore[reportUnusedImport] + ) + + assert ModelError is replicate._exceptions.ModelError + assert APIError is replicate.APIError + assert ReplicateError is replicate.ReplicateError + assert BadRequestError is replicate.BadRequestError + assert AuthenticationError is replicate.AuthenticationError + assert NotFoundError is replicate.NotFoundError + assert RateLimitError is replicate.RateLimitError + assert InternalServerError is replicate.InternalServerError + assert APIConnectionError is replicate.APIConnectionError + assert APITimeoutError is replicate.APITimeoutError + + +def test_readme_example_import(): + """Test that the import pattern shown in README works correctly.""" + # This is the exact import pattern shown in the README + import replicate + from replicate.exceptions import ModelError + + # Verify ModelError is the correct class + assert ModelError is replicate._exceptions.ModelError + + +def test_exception_module_all_exports(): + """Test that replicate.exceptions.__all__ contains all expected exceptions.""" + import replicate.exceptions + + expected_exceptions = [ + "APIConnectionError", + "APIError", + "APIResponseValidationError", + "APIStatusError", + "APITimeoutError", + "AuthenticationError", + "BadRequestError", + "ConflictError", + "InternalServerError", + "ModelError", + "NotFoundError", + "PermissionDeniedError", + "RateLimitError", + "ReplicateError", + "UnprocessableEntityError", + ] + + assert set(replicate.exceptions.__all__) == set(expected_exceptions) + + # Also verify they can all be accessed + for exc_name in expected_exceptions: + assert hasattr(replicate.exceptions, exc_name) \ No newline at end of file