From ebde048f8a85ddd4e99f9e688294b04765322211 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 3 Sep 2025 09:05:21 -0700 Subject: [PATCH 1/6] feat: add get_path_url() function support for migration compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for get_path_url() function to maintain compatibility with old replicate-python client. This function extracts remote URLs from file output objects, enabling users to reuse URLs in model inputs. Key changes: - Export get_path_url from main module (__init__.py) - Add module-level function proxy in _module_client.py - Enhance FileOutput classes with __url__ attribute for compatibility - Add comprehensive test coverage for all supported object types - Maintain backward compatibility with same API as old client 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/replicate/__init__.py | 5 ++ src/replicate/_module_client.py | 9 +++ src/replicate/lib/_files.py | 4 ++ tests/lib/test_get_path_url.py | 103 ++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 tests/lib/test_get_path_url.py diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index 90fcf8f..ad1b957 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -89,6 +89,7 @@ "Model", "Version", "ModelVersionIdentifier", + "get_path_url", ] if not _t.TYPE_CHECKING: @@ -104,6 +105,9 @@ for __name in __all__: if not __name.startswith("__"): try: + # Skip get_path_url as it's imported later + if __name == "get_path_url": + continue __locals[__name].__module__ = "replicate" except (TypeError, AttributeError): # Some of our exported symbols are builtins which we can't set attributes for. @@ -253,4 +257,5 @@ def _reset_client() -> None: # type: ignore[reportUnusedFunction] collections as collections, deployments as deployments, predictions as predictions, + get_path_url as get_path_url, ) diff --git a/src/replicate/_module_client.py b/src/replicate/_module_client.py index a3e8ab4..db84fe5 100644 --- a/src/replicate/_module_client.py +++ b/src/replicate/_module_client.py @@ -82,6 +82,9 @@ def __load__(self) -> PredictionsResource: __client: Replicate = cast(Replicate, {}) run = __client.run use = __client.use + + # Import get_path_url for type checking + from .lib._predictions_use import get_path_url else: def _run(*args, **kwargs): @@ -100,8 +103,14 @@ def _use(ref, *, hint=None, streaming=False, use_async=False, **kwargs): return use(Replicate, ref, hint=hint, streaming=streaming, **kwargs) + def _get_path_url(path): + from .lib._predictions_use import get_path_url + + return get_path_url(path) + run = _run use = _use + get_path_url = _get_path_url files: FilesResource = FilesResourceProxy().__as_proxied__() models: ModelsResource = ModelsResourceProxy().__as_proxied__() diff --git a/src/replicate/lib/_files.py b/src/replicate/lib/_files.py index 7c6f485..ad49a4c 100644 --- a/src/replicate/lib/_files.py +++ b/src/replicate/lib/_files.py @@ -139,6 +139,8 @@ class FileOutput(httpx.SyncByteStream): def __init__(self, url: str, client: Replicate) -> None: self.url = url self._client = client + # Add __url__ attribute for compatibility with get_path_url() + self.__url__ = url def read(self) -> bytes: if self.url.startswith("data:"): @@ -184,6 +186,8 @@ class AsyncFileOutput(httpx.AsyncByteStream): def __init__(self, url: str, client: AsyncReplicate) -> None: self.url = url self._client = client + # Add __url__ attribute for compatibility with get_path_url() + self.__url__ = url async def read(self) -> bytes: if self.url.startswith("data:"): diff --git a/tests/lib/test_get_path_url.py b/tests/lib/test_get_path_url.py new file mode 100644 index 0000000..0a3940e --- /dev/null +++ b/tests/lib/test_get_path_url.py @@ -0,0 +1,103 @@ +from pathlib import Path + +import replicate +from replicate.lib._files import FileOutput, AsyncFileOutput +from replicate.lib._predictions_use import URLPath, get_path_url + + +def test_get_path_url_with_urlpath(): + """Test get_path_url returns the URL for URLPath instances.""" + url = "https://example.com/test.jpg" + path_proxy = URLPath(url) + + result = get_path_url(path_proxy) + assert result == url + + +def test_get_path_url_with_fileoutput(): + """Test get_path_url returns the URL for FileOutput instances.""" + url = "https://example.com/test.jpg" + file_output = FileOutput(url, replicate.Replicate()) + + result = get_path_url(file_output) + assert result == url + + +def test_get_path_url_with_async_fileoutput(): + """Test get_path_url returns the URL for AsyncFileOutput instances.""" + url = "https://example.com/test.jpg" + async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate()) + + result = get_path_url(async_file_output) + assert result == url + + +def test_get_path_url_with_regular_path(): + """Test get_path_url returns None for regular Path instances.""" + regular_path = Path("test.txt") + + result = get_path_url(regular_path) + assert result is None + + +def test_get_path_url_with_object_without_target(): + """Test get_path_url returns None for objects without __url__.""" + + # Test with a string + result = get_path_url("not a path") + assert result is None + + # Test with a dict + result = get_path_url({"key": "value"}) + assert result is None + + # Test with None + result = get_path_url(None) + assert result is None + + +def test_get_path_url_module_level_import(): + """Test that get_path_url can be imported at module level.""" + from replicate import get_path_url as module_get_path_url + + url = "https://example.com/test.jpg" + file_output = FileOutput(url, replicate.Replicate()) + + result = module_get_path_url(file_output) + assert result == url + + +def test_get_path_url_direct_module_access(): + """Test that get_path_url can be accessed directly from replicate module.""" + url = "https://example.com/test.jpg" + file_output = FileOutput(url, replicate.Replicate()) + + result = replicate.get_path_url(file_output) + assert result == url + + +def test_fileoutput_has_url_attribute(): + """Test that FileOutput instances have __url__ attribute.""" + url = "https://example.com/test.jpg" + file_output = FileOutput(url, replicate.Replicate()) + + assert hasattr(file_output, "__url__") + assert file_output.__url__ == url + + +def test_async_fileoutput_has_url_attribute(): + """Test that AsyncFileOutput instances have __url__ attribute.""" + url = "https://example.com/test.jpg" + async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate()) + + assert hasattr(async_file_output, "__url__") + assert async_file_output.__url__ == url + + +def test_urlpath_has_url_attribute(): + """Test that URLPath instances have __url__ attribute.""" + url = "https://example.com/test.jpg" + url_path = URLPath(url) + + assert hasattr(url_path, "__url__") + assert url_path.__url__ == url From 2a70529a00aef02dd6d93dbec6bfc709bd3b51be Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 3 Sep 2025 11:24:21 -0700 Subject: [PATCH 2/6] refactor: simplify get_path_url import chain by removing intermediary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove unnecessary lib/_get_path_url.py intermediary layer and import get_path_url directly from lib/_predictions_use.py in _module_client.py. This eliminates 32 lines of proxy code while maintaining identical functionality and API compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/replicate/__init__.py | 4 ++-- src/replicate/_module_client.py | 10 +--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index ad1b957..8debd05 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -105,8 +105,8 @@ for __name in __all__: if not __name.startswith("__"): try: - # Skip get_path_url as it's imported later - if __name == "get_path_url": + # Skip symbols that are imported later from _module_client + if __name in ("get_path_url", "run", "use"): continue __locals[__name].__module__ = "replicate" except (TypeError, AttributeError): diff --git a/src/replicate/_module_client.py b/src/replicate/_module_client.py index db84fe5..f1dec69 100644 --- a/src/replicate/_module_client.py +++ b/src/replicate/_module_client.py @@ -18,6 +18,7 @@ from . import _load_client from ._utils import LazyProxy +from .lib._predictions_use import get_path_url # noqa: F401 # pyright: ignore[reportUnusedImport] class FilesResourceProxy(LazyProxy["FilesResource"]): @@ -82,9 +83,6 @@ def __load__(self) -> PredictionsResource: __client: Replicate = cast(Replicate, {}) run = __client.run use = __client.use - - # Import get_path_url for type checking - from .lib._predictions_use import get_path_url else: def _run(*args, **kwargs): @@ -103,14 +101,8 @@ def _use(ref, *, hint=None, streaming=False, use_async=False, **kwargs): return use(Replicate, ref, hint=hint, streaming=streaming, **kwargs) - def _get_path_url(path): - from .lib._predictions_use import get_path_url - - return get_path_url(path) - run = _run use = _use - get_path_url = _get_path_url files: FilesResource = FilesResourceProxy().__as_proxied__() models: ModelsResource = ModelsResourceProxy().__as_proxied__() From 4b88305a8d7cc6c0232dc3f976157dcef71a6b30 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 3 Sep 2025 11:27:33 -0700 Subject: [PATCH 3/6] fix: add bearer tokens to get_path_url tests to prevent auth errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FileOutput and AsyncFileOutput classes require authenticated clients. Added TEST_TOKEN constant and provided bearer_token parameter to all client instantiations in tests to fix CI authentication failures. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tests/lib/test_get_path_url.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/lib/test_get_path_url.py b/tests/lib/test_get_path_url.py index 0a3940e..9d6fda8 100644 --- a/tests/lib/test_get_path_url.py +++ b/tests/lib/test_get_path_url.py @@ -4,6 +4,9 @@ from replicate.lib._files import FileOutput, AsyncFileOutput from replicate.lib._predictions_use import URLPath, get_path_url +# Test token for client instantiation +TEST_TOKEN = "test-bearer-token" + def test_get_path_url_with_urlpath(): """Test get_path_url returns the URL for URLPath instances.""" @@ -17,7 +20,7 @@ def test_get_path_url_with_urlpath(): def test_get_path_url_with_fileoutput(): """Test get_path_url returns the URL for FileOutput instances.""" url = "https://example.com/test.jpg" - file_output = FileOutput(url, replicate.Replicate()) + file_output = FileOutput(url, replicate.Replicate(bearer_token=TEST_TOKEN)) result = get_path_url(file_output) assert result == url @@ -26,7 +29,7 @@ def test_get_path_url_with_fileoutput(): def test_get_path_url_with_async_fileoutput(): """Test get_path_url returns the URL for AsyncFileOutput instances.""" url = "https://example.com/test.jpg" - async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate()) + async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate(bearer_token=TEST_TOKEN)) result = get_path_url(async_file_output) assert result == url @@ -61,7 +64,7 @@ def test_get_path_url_module_level_import(): from replicate import get_path_url as module_get_path_url url = "https://example.com/test.jpg" - file_output = FileOutput(url, replicate.Replicate()) + file_output = FileOutput(url, replicate.Replicate(bearer_token=TEST_TOKEN)) result = module_get_path_url(file_output) assert result == url @@ -70,7 +73,7 @@ def test_get_path_url_module_level_import(): def test_get_path_url_direct_module_access(): """Test that get_path_url can be accessed directly from replicate module.""" url = "https://example.com/test.jpg" - file_output = FileOutput(url, replicate.Replicate()) + file_output = FileOutput(url, replicate.Replicate(bearer_token=TEST_TOKEN)) result = replicate.get_path_url(file_output) assert result == url @@ -79,7 +82,7 @@ def test_get_path_url_direct_module_access(): def test_fileoutput_has_url_attribute(): """Test that FileOutput instances have __url__ attribute.""" url = "https://example.com/test.jpg" - file_output = FileOutput(url, replicate.Replicate()) + file_output = FileOutput(url, replicate.Replicate(bearer_token=TEST_TOKEN)) assert hasattr(file_output, "__url__") assert file_output.__url__ == url @@ -88,7 +91,7 @@ def test_fileoutput_has_url_attribute(): def test_async_fileoutput_has_url_attribute(): """Test that AsyncFileOutput instances have __url__ attribute.""" url = "https://example.com/test.jpg" - async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate()) + async_file_output = AsyncFileOutput(url, replicate.AsyncReplicate(bearer_token=TEST_TOKEN)) assert hasattr(async_file_output, "__url__") assert async_file_output.__url__ == url From 31536a8bf5781b65d291d6e8b61cec304ff0574e Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Wed, 3 Sep 2025 20:27:43 -0700 Subject: [PATCH 4/6] refactor: use cleaner import pattern for get_path_url re-export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace lint suppression comments with explicit `import get_path_url as get_path_url` pattern to make the re-export intent clearer and avoid lint suppressions. Addresses PR feedback from RobertCraigle. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/replicate/_module_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/replicate/_module_client.py b/src/replicate/_module_client.py index f1dec69..ac18960 100644 --- a/src/replicate/_module_client.py +++ b/src/replicate/_module_client.py @@ -18,7 +18,7 @@ from . import _load_client from ._utils import LazyProxy -from .lib._predictions_use import get_path_url # noqa: F401 # pyright: ignore[reportUnusedImport] +from .lib._predictions_use import get_path_url as get_path_url class FilesResourceProxy(LazyProxy["FilesResource"]): From 7a3f9811335442bf05f743f98d9a6b5226eaa430 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 4 Sep 2025 09:16:47 -0700 Subject: [PATCH 5/6] refactor: import get_path_url directly in __init__.py instead of via module_client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move get_path_url import to follow the same pattern as other lib/ exports (FileOutput, Model, etc.) which are imported directly in __init__.py. The _module_client.py is specifically for resource proxies and module-level access patterns like replicate.models.list(), not for all top-level exports. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/replicate/__init__.py | 2 +- src/replicate/_module_client.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index 8debd05..497d6c3 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -43,6 +43,7 @@ from .lib._models import Model as Model, Version as Version, ModelVersionIdentifier as ModelVersionIdentifier from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging +from .lib._predictions_use import get_path_url as get_path_url __all__ = [ "types", @@ -257,5 +258,4 @@ def _reset_client() -> None: # type: ignore[reportUnusedFunction] collections as collections, deployments as deployments, predictions as predictions, - get_path_url as get_path_url, ) diff --git a/src/replicate/_module_client.py b/src/replicate/_module_client.py index ac18960..a3e8ab4 100644 --- a/src/replicate/_module_client.py +++ b/src/replicate/_module_client.py @@ -18,7 +18,6 @@ from . import _load_client from ._utils import LazyProxy -from .lib._predictions_use import get_path_url as get_path_url class FilesResourceProxy(LazyProxy["FilesResource"]): From 5b646a7483591ef506b19ab4870c71356a703669 Mon Sep 17 00:00:00 2001 From: Zeke Sikelianos Date: Thu, 4 Sep 2025 09:19:05 -0700 Subject: [PATCH 6/6] refactor: remove get_path_url from module_client skip list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since get_path_url is now imported directly in __init__.py (not from _module_client), it should not be in the skip list for symbols that are imported later from _module_client. This ensures get_path_url gets proper __module__ attribution like other direct imports. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/replicate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/replicate/__init__.py b/src/replicate/__init__.py index 497d6c3..f424c6b 100644 --- a/src/replicate/__init__.py +++ b/src/replicate/__init__.py @@ -107,7 +107,7 @@ if not __name.startswith("__"): try: # Skip symbols that are imported later from _module_client - if __name in ("get_path_url", "run", "use"): + if __name in ("run", "use"): continue __locals[__name].__module__ = "replicate" except (TypeError, AttributeError):