From 0953224d7759318efb1d2518db0f450cce255470 Mon Sep 17 00:00:00 2001 From: RapidPoseidon Date: Fri, 24 Apr 2026 09:09:13 +0000 Subject: [PATCH] fix(upload): detect URL assets case-insensitively MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_asset_uploader.upload_asset`, and `_asset_upload_orchestrator`'s `_separate_urls_and_files` / `_filter_uncached` all classified URLs with `re.match(r\"^https?://\", asset)` — case-sensitive. An asset string like `HTTPS://cdn.example.com/image.png` was treated as a local file path and fell through to `FileNotFoundError` in the file-cache key lookup. Swap the raw `re.match` calls for a shared module-level compiled regex with `re.IGNORECASE`. Session: https://session-bc38cc85.poseidon.rapidata.internal/ Co-Authored-By: Claude Opus 4.7 Co-Authored-By: lino --- .../datapoints/_asset_upload_orchestrator.py | 9 ++++++--- .../rapidata_client/datapoints/_asset_uploader.py | 6 +++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/rapidata/rapidata_client/datapoints/_asset_upload_orchestrator.py b/src/rapidata/rapidata_client/datapoints/_asset_upload_orchestrator.py index a3503c3f5..aa9240d8e 100644 --- a/src/rapidata/rapidata_client/datapoints/_asset_upload_orchestrator.py +++ b/src/rapidata/rapidata_client/datapoints/_asset_upload_orchestrator.py @@ -113,6 +113,9 @@ def upload_all_assets( self._log_upload_results(failed_uploads) return failed_uploads + # Keep the URL scheme check case-insensitive to match AssetUploader. + _URL_SCHEME_RE = re.compile(r"^https?://", re.IGNORECASE) + def _separate_urls_and_files(self, assets: set[str]) -> tuple[set[str], set[str]]: """ Separate assets into URLs and file paths. @@ -123,8 +126,8 @@ def _separate_urls_and_files(self, assets: set[str]) -> tuple[set[str], set[str] Returns: Tuple of (urls, files). """ - urls = {a for a in assets if re.match(r"^https?://", a)} - files = {a for a in assets if not re.match(r"^https?://", a)} + urls = {a for a in assets if self._URL_SCHEME_RE.match(a)} + files = {a for a in assets if not self._URL_SCHEME_RE.match(a)} logger.debug(f"Asset breakdown: {len(urls)} URL(s), {len(files)} file(s)") return urls, files @@ -257,7 +260,7 @@ def _filter_uncached(self, assets: set[str], cache: SingleFlightCache) -> set[st for asset in assets: try: # Try to get cache key using centralized methods - if re.match(r"^https?://", asset): + if self._URL_SCHEME_RE.match(asset): cache_key = self.asset_uploader.get_url_cache_key(asset) else: cache_key = self.asset_uploader.get_file_cache_key(asset) diff --git a/src/rapidata/rapidata_client/datapoints/_asset_uploader.py b/src/rapidata/rapidata_client/datapoints/_asset_uploader.py index 38993309b..e5826e8ca 100644 --- a/src/rapidata/rapidata_client/datapoints/_asset_uploader.py +++ b/src/rapidata/rapidata_client/datapoints/_asset_uploader.py @@ -114,11 +114,15 @@ def upload_file() -> str: self.get_file_cache_key(file_path), upload_file ) + # Accept http / https / HTTP / HTTPS / mixed case — people type URLs + # by hand or copy them from docs with varying capitalisation. + _URL_SCHEME_RE = re.compile(r"^https?://", re.IGNORECASE) + def upload_asset(self, asset: str) -> str: logger.debug("Uploading asset: %s", asset) assert isinstance(asset, str), "Asset must be a string" - if re.match(r"^https?://", asset): + if self._URL_SCHEME_RE.match(asset): return self._upload_url_asset(asset) return self._upload_file_asset(asset)