-
Notifications
You must be signed in to change notification settings - Fork 1
Local server, mocked responses and testing of notebooks #348
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
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # Mock Server for Local Development | ||
|
|
||
| The mock server is a local test server that mimics the DeepOrigin Platform API. It's useful for local development and testing without making real API calls to the platform. | ||
|
|
||
| ## Overview | ||
|
|
||
| The mock server (`tests/mock_server.py`) provides mock responses for all API endpoints used by the DeepOriginClient. It runs locally using FastAPI and uvicorn, and serves responses based on fixture data stored in `tests/fixtures/`. | ||
|
|
||
| ## Running the Mock Server | ||
|
|
||
| ### Standalone Script | ||
|
|
||
| To run the mock server standalone for local development: | ||
|
|
||
| ```bash | ||
| python scripts/run_mock_server.py [PORT] | ||
| ``` | ||
|
|
||
| Where `PORT` is the port number to run the server on (default: 8000). | ||
|
|
||
| Example: | ||
|
|
||
| ```bash | ||
| python scripts/run_mock_server.py 8000 | ||
| ``` | ||
|
|
||
| The server will start and display: | ||
|
|
||
| ```bash | ||
| Mock server running at http://127.0.0.1:8000 | ||
| Press Ctrl+C to stop... | ||
| ``` | ||
|
|
||
| Press `Ctrl+C` to stop the server. | ||
|
|
||
| ### Using in Tests | ||
|
|
||
| The mock server is automatically started when running tests with the `--mock` flag: | ||
|
|
||
| ```bash | ||
| pytest --mock | ||
| ``` | ||
|
|
||
| This starts the server for the duration of the test session and automatically stops it when tests complete. | ||
|
|
||
| ## Configuring Your Client | ||
|
|
||
| To use the mock server with your code, configure the `DeepOriginClient` to point to the mock server URL: | ||
|
|
||
| ```python | ||
| from deeporigin.platform.client import DeepOriginClient | ||
|
|
||
| client = DeepOriginClient( | ||
| token="test-token", # Any token works with the mock server | ||
| org_key="deeporigin", # Use any org_key | ||
| base_url="http://127.0.0.1:8000", # Mock server URL | ||
| env="local", | ||
| ) | ||
| ``` | ||
|
|
||
| ## Available Endpoints | ||
|
|
||
| The mock server implements the following endpoints: | ||
|
|
||
| - **Files API**: List, upload, download, and delete files | ||
| - **Tools API**: List tools and tool definitions | ||
| - **Functions API**: List functions and run function executions | ||
| - **Executions API**: List executions, get execution details, cancel/confirm executions | ||
| - **Clusters API**: List available clusters | ||
| - **Organizations API**: List organization users | ||
| - **Health Check**: `/health` endpoint | ||
|
|
||
| ## Fixtures | ||
|
|
||
| The mock server uses fixture files from `tests/fixtures/` to provide realistic responses. Key fixtures include: | ||
|
|
||
| - `execution_example.json`: Template for execution responses | ||
| - `molprops_serotonin.json`: Molecular properties data for testing | ||
| - `abfe/progress-reports.json`: ABFE progress report data | ||
|
|
||
| ## Extending the Mock Server | ||
|
|
||
| To add new endpoints or modify existing ones, edit `tests/mock_server.py`: | ||
|
|
||
| 1. Add a new route handler in the `_setup_routes()` method | ||
| 2. Optionally add fixture files in `tests/fixtures/` if you need realistic data | ||
| 3. Use `_load_fixture()` to load JSON fixtures | ||
|
|
||
| Example: | ||
|
|
||
| ```{.python notest} | ||
| @self.app.get("/tools/{org_key}/custom-endpoint") | ||
| def custom_endpoint(org_key: str) -> dict[str, Any]: | ||
| """Custom endpoint handler.""" | ||
| fixture_data = self._load_fixture("custom_fixture") | ||
| return fixture_data | ||
| ``` | ||
|
|
||
| ## Limitations | ||
|
|
||
| The mock server is designed for testing and development purposes. It has some limitations: | ||
|
|
||
| - Authentication is not validated (any token works) | ||
| - File storage is in-memory and lost when the server stops | ||
| - Some complex API behaviors may not be fully replicated | ||
| - Rate limiting and other production features are not implemented | ||
|
|
||
| For production use, always use the real DeepOrigin Platform API. | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| #!/usr/bin/env python3 | ||
| """Standalone script to run the mock server for local development and testing. | ||
|
|
||
| This script starts a local mock server that mimics the DeepOrigin Platform API. | ||
| It's useful for local development and testing without making real API calls. | ||
|
|
||
| Usage: | ||
| python scripts/run_mock_server.py [OPTIONS] | ||
|
|
||
| Options: | ||
| PORT: Port number to run the server on (default: 8000) | ||
| --abfe-duration SECONDS: Duration for ABFE executions in seconds (default: 300) | ||
|
|
||
| Examples: | ||
| python scripts/run_mock_server.py 8000 | ||
| python scripts/run_mock_server.py --abfe-duration 600 | ||
| python scripts/run_mock_server.py 8080 --abfe-duration 120 | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import importlib.util | ||
| from pathlib import Path | ||
|
|
||
| # Load mock_server module directly using importlib instead of modifying sys.path | ||
| # This is cleaner than sys.path manipulation and avoids polluting the import system | ||
| project_root = Path(__file__).parent.parent | ||
| mock_server_path = project_root / "tests" / "mock_server.py" | ||
| if not mock_server_path.exists(): | ||
| raise FileNotFoundError( | ||
| f"Could not find mock_server.py at {mock_server_path}. " | ||
| "Make sure you're running this script from the project root." | ||
| ) | ||
| spec = importlib.util.spec_from_file_location("mock_server", mock_server_path) | ||
| if spec is None or spec.loader is None: | ||
| raise ImportError(f"Could not create module spec for {mock_server_path}") | ||
| mock_server_module = importlib.util.module_from_spec(spec) | ||
| spec.loader.exec_module(mock_server_module) | ||
| MockServer = mock_server_module.MockServer | ||
|
|
||
|
|
||
| def main() -> None: | ||
| """Run the mock server.""" | ||
| parser = argparse.ArgumentParser( | ||
| description="Run the DeepOrigin Platform API mock server", | ||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||
| epilog=__doc__, | ||
| ) | ||
| parser.add_argument( | ||
| "port", | ||
| type=int, | ||
| nargs="?", | ||
| default=8000, | ||
| help="Port number to run the server on (default: 8000)", | ||
| ) | ||
| parser.add_argument( | ||
| "--abfe-duration", | ||
| type=float, | ||
| default=300.0, | ||
| help="Duration for ABFE executions in seconds (default: 300)", | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Create MockServer instance | ||
| server = MockServer(port=args.port) | ||
|
|
||
| # Set execution durations | ||
| server._mock_execution_durations["deeporigin.abfe-end-to-end"] = args.abfe_duration | ||
|
|
||
| # Run with uvicorn directly - this blocks, which is fine for a dev script | ||
| import uvicorn | ||
|
|
||
| print(f"Starting mock server on http://127.0.0.1:{args.port}") | ||
| print(f"ABFE execution duration: {args.abfe_duration} seconds") | ||
| print("Press Ctrl+C to stop...") | ||
| print() | ||
| uvicorn.run(server.app, host="127.0.0.1", port=args.port, log_level="info") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -181,17 +181,6 @@ def sync(self): | |
| self._attributes = result | ||
| self.status = result.get("status") | ||
|
|
||
| # Quick and dirty logging for analysis - append progressReport to file | ||
| try: | ||
| progress_data = json.loads(result["progressReport"]) | ||
| except Exception: | ||
| progress_data = result["progressReport"] | ||
|
|
||
| log_file = Path(f"{self._id}.txt") | ||
| with open(log_file, "a") as f: | ||
| f.write(json.dumps(progress_data, indent=2)) | ||
| f.write("\n\n") | ||
|
|
||
| def _get_running_time(self) -> Optional[int]: | ||
| """Get the running time of the job. | ||
|
|
||
|
|
@@ -419,7 +408,7 @@ def show(self): | |
| rendered_html = self._render_job_view() | ||
| display(HTML(rendered_html)) | ||
|
|
||
| def watch(self): | ||
| def watch(self, *, interval: float = 5.0): | ||
| """Start monitoring job progress in real-time. | ||
|
Comment on lines
-422
to
412
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. support for faster updates, e.g., in a demo |
||
|
|
||
| This method initiates a background task that periodically updates | ||
|
|
@@ -458,7 +447,7 @@ async def update_progress_report(): | |
| """Update and display job progress at regular intervals. | ||
|
|
||
| This coroutine runs in the background, updating the display | ||
| with the latest job status and progress every 5 seconds. | ||
| with the latest job status and progress every `interval` seconds. | ||
| It automatically stops when the job reaches a terminal state. | ||
| """ | ||
| try: | ||
|
|
@@ -487,7 +476,7 @@ async def update_progress_report(): | |
| ) | ||
|
|
||
| # Always sleep 5 seconds before next attempt | ||
| await asyncio.sleep(5) | ||
| await asyncio.sleep(interval) | ||
| finally: | ||
| # Perform a final non-blocking refresh and render to clear spinner | ||
| if self._display_id is not None: | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
need to pass this to make sure this uses the correct client (context)