-
Notifications
You must be signed in to change notification settings - Fork 90
feat: Add auto-launch functionality for local Meilisearch instances #1106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Client can now automatically download and launch Meilisearch when no URL is provided - Supports automatic binary detection and download for macOS and Linux - Implements proper lifecycle management with cleanup on client destruction - Adds context manager support for automatic cleanup - Updates README with simplified onboarding experience - Adds comprehensive tests for the new functionality This feature greatly simplifies the developer experience by removing the need to manually install and run Meilisearch for local development and testing. 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
WalkthroughA new auto-launch feature was introduced, allowing the Meilisearch Python client to automatically download, start, and manage a local Meilisearch server when no URL is provided. Supporting code, documentation, and tests were added or updated to reflect this functionality, including client lifecycle management and usage instructions. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Client
participant LocalMeilisearchServer
participant MeilisearchServer
User->>Client: Instantiate Client(url=None)
Client->>LocalMeilisearchServer: Initialize and start()
LocalMeilisearchServer->>LocalMeilisearchServer: Find/download binary
LocalMeilisearchServer->>MeilisearchServer: Launch process
LocalMeilisearchServer->>Client: Return server URL and API key
Client->>User: Ready for API calls
User->>Client: Perform Meilisearch operations
Client->>MeilisearchServer: Forward API requests
User->>Client: Close or exit context
Client->>LocalMeilisearchServer: Stop server, cleanup
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…nflicts - Moved tests to tests/auto_launch/ to isolate from parent conftest.py - Added local conftest.py to override autouse fixtures - Fixed test that was failing due to no running Meilisearch instance - All auto-launch tests now pass independently 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Changed __exit__ return type from bool to None as it should not return False explicitly. This fixes the mypy type checking error. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Use 'with' statements for resource management where appropriate - Add explicit exception chaining with 'from e' - Replace broad Exception catches with specific exceptions (OSError, URLError) - Add pylint disable comment for subprocess.Popen usage where 'with' is not suitable 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Modified LocalMeilisearchServer to find an available port automatically - This prevents conflicts with existing Meilisearch instances in CI - Added small delay after document indexing to ensure search works - Tests now pass reliably in both local and CI environments 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🔭 Outside diff range comments (1)
meilisearch/client.py (1)
32-48
:⚠️ Potential issueBreaking change: URL parameter is now optional
Making the
url
parameter optional is a breaking change for code that passes arguments positionally. Users who callClient('http://localhost:7700', 'api_key')
will continue to work, but any code using keyword arguments for subsequent parameters might break.Consider documenting this as a breaking change in the changelog.
🧹 Nitpick comments (6)
tests/auto_launch/test_client_auto_launch.py (1)
34-36
: Consider replacing time.sleep with more robust waiting mechanism.The fixed sleep duration could be flaky in CI environments. Consider using a more deterministic approach like polling or waiting for specific conditions.
- # Give Meilisearch a moment to process - time.sleep(0.5) + # Wait for documents to be indexed + import time + for _ in range(10): # Max 5 seconds + try: + results = index.search("test") + if len(results["hits"]) > 0: + break + time.sleep(0.5) + except Exception: + time.sleep(0.5)meilisearch/client.py (1)
799-801
: Consider the reliability of__del__
for cleanupThe
__del__
method is not guaranteed to be called in all Python implementations and situations (e.g., circular references, interpreter shutdown). While having it as a safety net is good, users should be encouraged to use explicit cleanup viaclose()
or context managers.Consider adding a warning in the documentation about preferring explicit cleanup methods.
CLAUDE.md (1)
173-173
: Fix grammatical issue in numbered listMinor grammatical issue: the numbered list item is missing proper formatting.
-Write integration tests covering success and error cases +4. Write integration tests covering success and error cases🧰 Tools
🪛 LanguageTool
[grammar] ~173-~173: Did you mean “to Write”?
Context: ...orresponding model classes if needed 4. Write integration tests covering success and ...(MISSING_TO_BEFORE_A_VERB)
meilisearch/_local_server.py (3)
114-120
: Simplify nested context managersThe static analysis correctly identifies that nested
with
statements can be combined.-try: - with urlopen(download_url, timeout=300) as response: - with open(binary_path, "wb") as f: - f.write(response.read()) +try: + with urlopen(download_url, timeout=300) as response, \ + open(binary_path, "wb") as f: + f.write(response.read())🧰 Tools
🪛 Ruff (0.11.9)
115-116: Use a single
with
statement with multiple contexts instead of nestedwith
statements(SIM117)
217-221
: Use contextlib.suppress for cleaner error handlingAs suggested by static analysis, using
contextlib.suppress
would be more Pythonic.+from contextlib import suppress + # Clean up temporary data directory if self._temp_data_dir and os.path.exists(self.data_path): - try: - shutil.rmtree(self.data_path) - except OSError: - pass # Ignore cleanup errors + with suppress(OSError): + shutil.rmtree(self.data_path)🧰 Tools
🪛 Ruff (0.11.9)
218-221: Use
contextlib.suppress(OSError)
instead oftry
-except
-pass
(SIM105)
180-184
: Add timeout parameter to health checkThe health check timeout is hardcoded to 1 second, which might be too short in some environments. Consider making it configurable or at least using a slightly longer timeout.
-with urlopen(f"{self.url}/health", timeout=1) as response: +with urlopen(f"{self.url}/health", timeout=5) as response:
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.gitignore
(1 hunks)CLAUDE.md
(1 hunks)CONTRIBUTING.md
(1 hunks)README.md
(4 hunks)meilisearch/_local_server.py
(1 hunks)meilisearch/client.py
(5 hunks)tests/auto_launch/conftest.py
(1 hunks)tests/auto_launch/test_client_auto_launch.py
(1 hunks)
🧰 Additional context used
🪛 Ruff (0.11.9)
tests/auto_launch/test_client_auto_launch.py
77-77: Local variable local_server
is assigned to but never used
Remove assignment to unused variable local_server
(F841)
meilisearch/_local_server.py
115-116: Use a single with
statement with multiple contexts instead of nested with
statements
(SIM117)
218-221: Use contextlib.suppress(OSError)
instead of try
-except
-pass
(SIM105)
🪛 LanguageTool
CLAUDE.md
[grammar] ~173-~173: Did you mean “to Write”?
Context: ...orresponding model classes if needed 4. Write integration tests covering success and ...
(MISSING_TO_BEFORE_A_VERB)
🔇 Additional comments (8)
.gitignore (1)
151-154
: LGTM! Appropriate ignore patterns for auto-launch feature.The added ignore patterns correctly prevent Meilisearch local server data files and directories from being committed to the repository.
CONTRIBUTING.md (1)
50-58
: Excellent documentation improvement!The clear distinction between auto-launch and manual setup options helps developers understand their testing choices and streamlines the onboarding experience.
tests/auto_launch/conftest.py (1)
1-22
: Well-designed test isolation approach.The fixture overrides properly isolate the auto-launch tests from the standard test suite by disabling client initialization and cleanup behaviors that would interfere with auto-launch functionality testing.
README.md (3)
45-45
: Python version requirement update noted.The minimum Python version has been updated from 3.8 to 3.9. Ensure this change is intentional and aligns with project requirements.
57-67
: Excellent feature documentation with clear usage options.The three-tier approach (Auto-Launch, Cloud, Self-Host) provides users with clear choices, and the auto-launch explanation is comprehensive and user-friendly.
251-288
: Comprehensive auto-launch documentation with practical examples.The documentation effectively covers various usage patterns including context managers and custom API keys, with helpful notes about the auto-launch behavior and file management.
tests/auto_launch/test_client_auto_launch.py (2)
9-43
: Comprehensive test coverage for auto-launch functionality.The test effectively verifies client creation, server communication, index operations, document management, and search functionality with proper cleanup using context managers.
86-99
: Good coverage of no-authentication scenario.The test properly verifies that auto-launch works without API keys and that public operations like health checks and version retrieval function correctly.
assert client._local_server is not None | ||
|
||
# Store reference to local server | ||
local_server = client._local_server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix unused variable assignment.
The local_server
variable is assigned but never used, as flagged by static analysis.
- # Store reference to local server
- local_server = client._local_server
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.11.9)
77-77: Local variable local_server
is assigned to but never used
Remove assignment to unused variable local_server
(F841)
🤖 Prompt for AI Agents
In tests/auto_launch/test_client_auto_launch.py at line 77, the variable
local_server is assigned but never used. Remove the assignment statement to
eliminate the unused variable and clean up the code.
self._local_server: Optional[LocalMeilisearchServer] = None | ||
|
||
# If no URL is provided, start a local Meilisearch instance | ||
if url is None: | ||
self._local_server = LocalMeilisearchServer(master_key=api_key) | ||
self._local_server.start() | ||
url = self._local_server.url | ||
# Use the same API key for the client | ||
if api_key is None and self._local_server.master_key is not None: | ||
api_key = self._local_server.master_key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for local server initialization
The local server initialization could fail for various reasons (port conflicts, download failures, permissions). Consider wrapping this in a try-except block to provide more informative error messages.
self._local_server: Optional[LocalMeilisearchServer] = None
# If no URL is provided, start a local Meilisearch instance
if url is None:
- self._local_server = LocalMeilisearchServer(master_key=api_key)
- self._local_server.start()
- url = self._local_server.url
- # Use the same API key for the client
- if api_key is None and self._local_server.master_key is not None:
- api_key = self._local_server.master_key
+ try:
+ self._local_server = LocalMeilisearchServer(master_key=api_key)
+ self._local_server.start()
+ url = self._local_server.url
+ # Use the same API key for the client
+ if api_key is None and self._local_server.master_key is not None:
+ api_key = self._local_server.master_key
+ except Exception as e:
+ raise RuntimeError(f"Failed to start local Meilisearch server: {e}") from e
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
self._local_server: Optional[LocalMeilisearchServer] = None | |
# If no URL is provided, start a local Meilisearch instance | |
if url is None: | |
self._local_server = LocalMeilisearchServer(master_key=api_key) | |
self._local_server.start() | |
url = self._local_server.url | |
# Use the same API key for the client | |
if api_key is None and self._local_server.master_key is not None: | |
api_key = self._local_server.master_key | |
self._local_server: Optional[LocalMeilisearchServer] = None | |
# If no URL is provided, start a local Meilisearch instance | |
if url is None: | |
try: | |
self._local_server = LocalMeilisearchServer(master_key=api_key) | |
self._local_server.start() | |
url = self._local_server.url | |
# Use the same API key for the client | |
if api_key is None and self._local_server.master_key is not None: | |
api_key = self._local_server.master_key | |
except Exception as e: | |
raise RuntimeError(f"Failed to start local Meilisearch server: {e}") from e |
🤖 Prompt for AI Agents
In meilisearch/client.py around lines 58 to 67, the local Meilisearch server
initialization lacks error handling, which can cause unhandled exceptions on
failures like port conflicts or permission issues. Wrap the local server
creation and start calls in a try-except block, catch any exceptions, and raise
a new error or log a clear, informative message explaining the failure to help
with debugging.
def _download_meilisearch(self) -> str: | ||
"""Download Meilisearch binary for the current platform.""" | ||
system = platform.system().lower() | ||
machine = platform.machine().lower() | ||
|
||
# Map platform to Meilisearch release names | ||
if system == "darwin": | ||
if machine in ["arm64", "aarch64"]: | ||
platform_str = "apple-silicon" | ||
else: | ||
platform_str = "amd64" | ||
binary_name = f"meilisearch-macos-{platform_str}" | ||
elif system == "linux": | ||
if machine in ["x86_64", "amd64"]: | ||
platform_str = "amd64" | ||
elif machine in ["aarch64", "arm64"]: | ||
platform_str = "aarch64" | ||
else: | ||
raise RuntimeError(f"Unsupported Linux architecture: {machine}") | ||
binary_name = f"meilisearch-linux-{platform_str}" | ||
else: | ||
raise RuntimeError(f"Unsupported platform: {system}") | ||
|
||
# Download the latest release | ||
download_url = ( | ||
f"https://github.com/meilisearch/meilisearch/releases/latest/download/{binary_name}" | ||
) | ||
|
||
# Create a temporary directory for the binary | ||
binary_dir = Path.home() / ".meilisearch" / "bin" | ||
binary_dir.mkdir(parents=True, exist_ok=True) | ||
binary_path = binary_dir / "meilisearch" | ||
|
||
# Download if not already present | ||
if not binary_path.exists(): | ||
print(f"Downloading Meilisearch binary from {download_url}...") | ||
try: | ||
with urlopen(download_url, timeout=300) as response: | ||
with open(binary_path, "wb") as f: | ||
f.write(response.read()) | ||
# Make it executable | ||
binary_path.chmod(0o755) | ||
print(f"Downloaded Meilisearch to {binary_path}") | ||
except Exception as e: | ||
raise RuntimeError(f"Failed to download Meilisearch: {e}") from e | ||
|
||
return str(binary_path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security concern: Add checksum verification for downloaded binaries
Downloading and executing binaries from the internet without verifying their integrity poses a security risk. Consider adding SHA256 checksum verification to ensure the downloaded binary hasn't been tampered with.
Meilisearch provides checksums for their releases that could be used for verification.
Would you like me to help implement checksum verification for the downloaded binaries?
🧰 Tools
🪛 Ruff (0.11.9)
115-116: Use a single with
statement with multiple contexts instead of nested with
statements
(SIM117)
🤖 Prompt for AI Agents
In meilisearch/_local_server.py around lines 78 to 124, the
_download_meilisearch method downloads the Meilisearch binary without verifying
its integrity, which is a security risk. To fix this, fetch the corresponding
SHA256 checksum file from the Meilisearch releases, compute the SHA256 hash of
the downloaded binary, and compare it against the expected checksum. If the
checksums do not match, raise an error and do not proceed. This ensures the
binary has not been tampered with before making it executable and returning its
path.
# Map platform to Meilisearch release names | ||
if system == "darwin": | ||
if machine in ["arm64", "aarch64"]: | ||
platform_str = "apple-silicon" | ||
else: | ||
platform_str = "amd64" | ||
binary_name = f"meilisearch-macos-{platform_str}" | ||
elif system == "linux": | ||
if machine in ["x86_64", "amd64"]: | ||
platform_str = "amd64" | ||
elif machine in ["aarch64", "arm64"]: | ||
platform_str = "aarch64" | ||
else: | ||
raise RuntimeError(f"Unsupported Linux architecture: {machine}") | ||
binary_name = f"meilisearch-linux-{platform_str}" | ||
else: | ||
raise RuntimeError(f"Unsupported platform: {system}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Platform limitation: No Windows support
The auto-launch feature doesn't support Windows, which might surprise Windows users when they try to use it. Consider either:
- Adding Windows support
- Documenting this limitation clearly in the README
- Providing a clear error message for Windows users
else:
- raise RuntimeError(f"Unsupported platform: {system}")
+ raise RuntimeError(
+ f"Unsupported platform: {system}. "
+ "Auto-launch is currently only supported on macOS and Linux. "
+ "Windows users need to manually install and run Meilisearch."
+ )
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Map platform to Meilisearch release names | |
if system == "darwin": | |
if machine in ["arm64", "aarch64"]: | |
platform_str = "apple-silicon" | |
else: | |
platform_str = "amd64" | |
binary_name = f"meilisearch-macos-{platform_str}" | |
elif system == "linux": | |
if machine in ["x86_64", "amd64"]: | |
platform_str = "amd64" | |
elif machine in ["aarch64", "arm64"]: | |
platform_str = "aarch64" | |
else: | |
raise RuntimeError(f"Unsupported Linux architecture: {machine}") | |
binary_name = f"meilisearch-linux-{platform_str}" | |
else: | |
raise RuntimeError(f"Unsupported platform: {system}") | |
# Map platform to Meilisearch release names | |
if system == "darwin": | |
if machine in ["arm64", "aarch64"]: | |
platform_str = "apple-silicon" | |
else: | |
platform_str = "amd64" | |
binary_name = f"meilisearch-macos-{platform_str}" | |
elif system == "linux": | |
if machine in ["x86_64", "amd64"]: | |
platform_str = "amd64" | |
elif machine in ["aarch64", "arm64"]: | |
platform_str = "aarch64" | |
else: | |
raise RuntimeError(f"Unsupported Linux architecture: {machine}") | |
binary_name = f"meilisearch-linux-{platform_str}" | |
else: | |
raise RuntimeError( | |
f"Unsupported platform: {system}. " | |
"Auto-launch is currently only supported on macOS and Linux. " | |
"Windows users need to manually install and run Meilisearch." | |
) |
🤖 Prompt for AI Agents
In meilisearch/_local_server.py around lines 83 to 99, the current platform
detection logic does not support Windows, which can confuse Windows users. To
fix this, add a clear RuntimeError for Windows users indicating that Windows is
not supported by the auto-launch feature. Alternatively, if feasible, implement
Windows support by adding the appropriate platform and binary name handling.
Also, update the README to clearly document this limitation for Windows users.
This feature greatly simplifies the developer experience by removing the need to manually install and run Meilisearch for local development and testing.
🤖 Generated with Claude Code
Pull Request
Related issue
Fixes #<issue_number>
What does this PR do?
PR checklist
Please check if your PR fulfills the following requirements:
Thank you so much for contributing to Meilisearch!
Summary by CodeRabbit
New Features
Documentation
Tests
Chores