Repository: unum-cloud/usearch (language: rust)

Issue:
Bug: Download failed: usearch_sqlite_macos_arm64_2.10.0.dylib could not be found
### Describe the bug

Got this after trying to upgrade to latest version to try the code from this issue again:
- #371
```bash
python -m pip install usearch -U
```
That worked:
```
Collecting usearch
  Downloading usearch-2.10.0-cp310-cp310-macosx_11_0_arm64.whl (398 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 398.2/398.2 kB 2.6 MB/s eta 0:00:00
Requirement already satisfied: numpy in /opt/homebrew/Caskroom/miniconda/base/lib/python3.10/site-packages (from usearch) (1.26.2)
Requirement already satisfied: tqdm in /opt/homebrew/Caskroom/miniconda/base/lib/python3.10/site-packages (from usearch) (4.65.0)
Installing collected packages: usearch
  Attempting uninstall: usearch
    Found existing installation: usearch 2.9.2
    Uninstalling usearch-2.9.2:
      Successfully uninstalled usearch-2.9.2
Successfully installed usearch-2.10.0
```
But then:
```pycon
Python 3.10.10 (main, Mar 21 2023, 13:41:05) [Clang 14.0.6 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import usearch
/opt/homebrew/Caskroom/miniconda/base/lib/python3.10/site-packages/usearch/__init__.py:100: UserWarning: Will download `usearch_sqlite` binary from GitHub.
  warnings.warn("Will download `usearch_sqlite` binary from GitHub.", UserWarning)
/opt/homebrew/Caskroom/miniconda/base/lib/python3.10/site-packages/usearch/__init__.py:107: UserWarning: Download failed: https://github.com/unum-cloud/usearch/releases/download/v2.10.0/usearch_sqlite_macos_arm64_2.10.0.dylib could not be found.
  warnings.warn(f"Download failed: {e.url} could not be found.", UserWarning)
```

### Steps to reproduce

Try to use `import usearch` against latest release on an M2 Mac.

### Expected behavior

The SQLite extension should be downloaded - this should not be a 404:

https://github.com/unum-cloud/usearch/releases/download/v2.10.0/usearch_sqlite_macos_arm64_2.10.0.dylib

### USearch version

2.10.0

### Operating System

macOS Sonoma 14.1.1 (23B81) on 64GB Apple M2 Max

### Hardware architecture

Arm

### Which interface are you using?

Python bindings

### Contact Details

_No response_

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct


Discussion / hints:
The error is coming from here: https://github.com/unum-cloud/usearch/blob/708fedecf516687b19df3b5f9be4bf3b1c91b824/python/usearch/__init__.py#L103-L110

Here's where that download URL is constructed:

https://github.com/unum-cloud/usearch/blob/708fedecf516687b19df3b5f9be4bf3b1c91b824/python/usearch/__init__.py#L25-L51

Fix patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4d0478bb..112b8c35 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -77,7 +77,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
         with:
-          persist-credentials: false
+          ref: "main"
       - run: git submodule update --init --recursive
 
       - name: Get version
@@ -115,7 +115,7 @@ jobs:
           touch "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/DEBIAN/control"
           mkdir -p "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/usr/local/lib"
           mkdir "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/usr/local/include"
-          cp include/usearch/usearch.h "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/usr/local/include/"
+          cp include/c/usearch.h "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/usr/local/include/"
           cp build_release/libusearch_c.so "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/usr/local/lib/"
           echo -e "Package: usearch\nVersion: ${{ steps.set_version.outputs.version }}\nMaintainer: Ash Vardanian\nArchitecture: ${{ matrix.arch }}\nDescription: Faster & Smaller Single-File Search Engine for Vectors & Texts" > "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}/DEBIAN/control"
           dpkg-deb --build "usearch_linux_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}"
@@ -147,7 +147,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
         with:
-          persist-credentials: false
+          ref: "main"
       - run: git submodule update --init --recursive
 
       - name: Setup CMake
@@ -195,7 +195,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
         with:
-          persist-credentials: false
+          ref: "main"
       - run: git submodule update --init --recursive
 
       - uses: actions/setup-go@v4
@@ -210,7 +210,13 @@ jobs:
 
       - name: Build library
         run: |
-          cmake -DCMAKE_BUILD_TYPE=Release -DUSEARCH_BUILD_LIB_C=1 -DUSEARCH_BUILD_SQLITE=1 -DUSEARCH_BUILD_TEST_CPP=0 -DUSEARCH_BUILD_BENCH_CPP=0 -B ./build_release
+          cmake -DCMAKE_BUILD_TYPE=Release \
+                -DUSEARCH_BUILD_LIB_C=1 \
+                -DUSEARCH_BUILD_SQLITE=1 \
+                -DUSEARCH_BUILD_TEST_CPP=0 \
+                -DUSEARCH_BUILD_BENCH_CPP=0 \
+                -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} \
+                -B ./build_release
           cmake --build ./build_release --config Release        
           zip -r usearch_macos_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}.zip build_release/libusearch_c.dylib c/usearch.h
           mv build_release/libusearch_sqlite.dylib usearch_sqlite_macos_${{ matrix.arch }}_${{ steps.set_version.outputs.version }}.dylib
@@ -242,7 +248,7 @@ jobs:
     steps:
       - uses: actions/checkout@v4
         with:
-          persist-credentials: false
+          ref: "main"
       - run: git submodule update --init --recursive
 
       - name: Get version
diff --git a/python/usearch/__init__.py b/python/usearch/__init__.py
index 2da30e71..bb1bf13d 100644
--- a/python/usearch/__init__.py
+++ b/python/usearch/__init__.py
@@ -20,20 +20,23 @@ class BinaryManager:
     def __init__(self, version: Optional[str] = None):
         if version is None:
             version = __version__
-        self.version = version
-
-    def sqlite_download_url(self) -> str:
-        """
-        Constructs a download URL for the `usearch_sqlite` binary based on the operating system, architecture, and version.
-
-        Args:
-            version (str): The version of the binary to download.
+        self.version = version or __version__
+        self.download_dir = self.determine_download_dir()
+
+    @staticmethod
+    def determine_download_dir():
+        # Check if running within a virtual environment
+        virtual_env = os.getenv("VIRTUAL_ENV")
+        if virtual_env:
+            # Use a subdirectory within the virtual environment for binaries
+            return os.path.join(virtual_env, "bin", "usearch_binaries")
+        else:
+            # Fallback to a directory in the user's home folder
+            home_dir = os.path.expanduser("~")
+            return os.path.join(home_dir, ".usearch", "binaries")
 
-        Returns:
-            A string representing the download URL.
-        """
+    def sqlite_file_name(self) -> str:
         version = self.version
-        base_url = "https://github.com/unum-cloud/usearch/releases/download"
         os_map = {"Linux": "linux", "Windows": "windows", "Darwin": "macos"}
         arch_map = {
             "x86_64": "amd64" if platform.system() != "Darwin" else "x86_64",
@@ -47,6 +50,12 @@ def sqlite_download_url(self) -> str:
         arch_part = arch_map.get(arch, "")
         extension = {"Linux": "so", "Windows": "dll", "Darwin": "dylib"}.get(platform.system(), "")
         filename = f"usearch_sqlite_{os_part}_{arch_part}_{version}.{extension}"
+        return filename
+
+    def sqlite_download_url(self) -> str:
+        version = self.version
+        filename = self.sqlite_file_name()
+        base_url = "https://github.com/unum-cloud/usearch/releases/download"
         url = f"{base_url}/v{version}/{filename}"
         return url
 
@@ -66,7 +75,6 @@ def download_binary(self, url: str, dest_folder: str) -> str:
         urllib.request.urlretrieve(url, dest_path)
         return dest_path
 
-    @property
     def sqlite_found_or_downloaded(self) -> Optional[str]:
         """
         Attempts to locate the pre-installed `usearch_sqlite` binary.
@@ -86,23 +94,19 @@ def sqlite_found_or_downloaded(self) -> Optional[str]:
             for root, _, files in os.walk(directory):
                 for file in files:
                     if file.endswith(file_extension) and "usearch_sqlite" in file:
-                        return os.path.join(root, file)
+                        return os.path.join(root, file).removesuffix(file_extension)
 
         # Check a temporary directory (assuming the binary might be downloaded from a GitHub release)
-        temp_dir = tempfile.gettempdir()
-        for root, _, files in os.walk(temp_dir):
-            for file in files:
-                if file.endswith(file_extension) and "usearch_sqlite" in file:
-                    return os.path.join(root, file)
+        local_path = os.path.join(self.download_dir, self.sqlite_file_name())
+        if os.path.exists(local_path):
+            return local_path.removesuffix(file_extension)
 
         # If not found locally, warn the user and download from GitHub
-        temp_dir = tempfile.gettempdir()
         warnings.warn("Will download `usearch_sqlite` binary from GitHub.", UserWarning)
-
-        # If the download fails due to HTTPError (e.g., 404 Not Found), like a missing lib version
         try:
-            binary_path = self.download_binary(self.sqlite_download_url(), temp_dir)
+            binary_path = self.download_binary(self.sqlite_download_url(), self.download_dir)
         except HTTPError as e:
+            # If the download fails due to HTTPError (e.g., 404 Not Found), like a missing lib version
             if e.code == 404:
                 warnings.warn(f"Download failed: {e.url} could not be found.", UserWarning)
             else:
@@ -111,12 +115,15 @@ def sqlite_found_or_downloaded(self) -> Optional[str]:
 
         # Handle the case where binary_path does not exist after supposed successful download
         if os.path.exists(binary_path):
-            return binary_path
+            return binary_path.removesuffix(file_extension)
         else:
             warnings.warn("Failed to download `usearch_sqlite` binary from GitHub.", UserWarning)
             return None
 
 
-# Use the function to set the `sqlite` computed property
-binary_manager = BinaryManager()
-sqlite = binary_manager.sqlite_found_or_downloaded
+def sqlite_path(version: str = None) -> str:
+    manager = BinaryManager(version=version)
+    result = manager.sqlite_found_or_downloaded()
+    if result is None:
+        raise FileNotFoundError("Failed to find or download `usearch_sqlite` binary.")
+    return result
diff --git a/sqlite/README.md b/sqlite/README.md
index a74d105c..21b313e8 100644
--- a/sqlite/README.md
+++ b/sqlite/README.md
@@ -30,7 +30,7 @@ import usearch
 
 conn = sqlite3.connect(":memory:")
 conn.enable_load_extension(True)
-conn.load_extension(usearch.sqlite)
+conn.load_extension(usearch.sqlite_path())
 ```
 
 Afterwards, the following script should work fine.


Test patch:
diff --git a/python/scripts/test_sqlite.py b/python/scripts/test_sqlite.py
index 8ecb5dcc..9e77cbf6 100644
--- a/python/scripts/test_sqlite.py
+++ b/python/scripts/test_sqlite.py
@@ -9,7 +9,11 @@
 import usearch
 
 
-found_sqlite_path = usearch.sqlite
+try:
+    found_sqlite_path = usearch.sqlite_path()
+except FileNotFoundError:
+    found_sqlite_path = None
+
 if found_sqlite_path is None:
     pytest.skip(reason="Can't find an SQLite installation", allow_module_level=True)