Development¶
This section covers everything you need to know to contribute to Eclair, set up a development environment, and extend the platform with new features.
Quick Start for Contributors¶
Prerequisites¶
- Python 3.8 or higher
- Git
- Virtual environment tool (venv, conda, etc.)
Setup Development Environment¶
- 
Clone the Repository 
- 
Create Virtual Environment 
- 
Install Development Dependencies 
- 
Run Tests (Optional) Running the tests will also startup an Eclair server. Hence you don't need to start it beforehand. 
- 
Start Eclair Server 
Project Structure¶
eclair/
├── src/eclair/                 # Main package
│   ├── client/                # Client libraries
│   │   ├── cli.py            # Command line interface
│   │   ├── client.py         # Python client
│   │   └── gemini/           # Gemini integration
│   └── server/               # Server implementation
│       ├── server.py         # Main server
│       ├── tools.py          # MCP tools
│       └── validation.py     # Data validation
├── tests/                    # Test suite
├── docs/                     # Documentation
├── config.json               # Configuration
├── mkdocs.yml                # Documentation building
├── requirements.txt          # Production dependencies
├── requirements-dev.txt      # Development dependencies
└── pyproject.toml            # Package configuration
Testing¶
We use pytest with comprehensive test coverage:
# tests/test_client.py
import pytest
from eclair.client import EclairClient
@pytest.fixture
def client():
    return EclairClient("http://localhost:8080")
def test_search_datasets(client):
    """Test dataset search functionality."""
    results = client.search_datasets("test query")
    assert isinstance(results, list)
def test_download_dataset(client):
    """Test dataset download."""
    path = client.download_dataset("test_collection", "test_dataset")
    assert isinstance(path, str)
    assert path.endswith("test_dataset")
Run tests with coverage:
# Run all tests
pytest
# With coverage report
pytest --cov=src/eclair --cov-report=html
# Run specific test file
pytest tests/test_client.py
# Run with verbose output
pytest -v
Documentation¶
Documentation is built with MkDocs and Material theme:
# Install docs dependencies
pip install mkdocs mkdocs-material
# Serve docs locally
mkdocs serve
# Build docs
mkdocs build
# Deploy to GitHub Pages
mkdocs gh-deploy
Adding New Features¶
Adding a New MCP Tool¶
- 
Define Tool Schema # src/eclair/server/tools.py def get_tool_schema(self, tool_name: str) -> Dict[str, Any]: schemas = { "my-new-tool": { "name": "my-new-tool", "description": "Description of what this tool does", "inputSchema": { "type": "object", "properties": { "param1": { "type": "string", "description": "Description of parameter" } }, "required": ["param1"] } } } return schemas.get(tool_name)
- 
Implement Tool Function 
- 
Register Tool 
- 
Add Client Method 
- 
Write Tests 
- 
Update Documentation 
Testing Strategy¶
Unit Tests¶
Test individual components in isolation:
# tests/unit/test_tools.py
import pytest
from unittest.mock import AsyncMock, patch
from eclair.server.tools import EclairTools
@pytest.fixture
def tools():
    return EclairTools()
@pytest.mark.asyncio
async def test_search_datasets_unit(tools):
    """Unit test for search functionality."""
    with patch('eclair.server.tools.search_engine') as mock_search:
        mock_search.search.return_value = [{"name": "test", "collection": "test"}]
        result = await tools.search_datasets("test query")
        assert len(result) == 1
        assert result[0]["name"] == "test"
        mock_search.search.assert_called_once_with("test query")
Integration Tests¶
Test component interactions:
# tests/integration/test_server.py
import pytest
import httpx
from eclair.server.server import app
@pytest.fixture
def client():
    return httpx.AsyncClient(app=app, base_url="http://test")
@pytest.mark.asyncio
async def test_search_endpoint_integration(client):
    """Integration test for search endpoint."""
    response = await client.post("/mcp", json={
        "method": "tools/call",
        "params": {
            "name": "search-datasets",
            "arguments": {"query": "test"}
        }
    })
    assert response.status_code == 200
    data = response.json()
    assert "content" in data
End-to-End Tests¶
Test complete workflows:
# tests/e2e/test_workflow.py
import pytest
from eclair.client import EclairClient
@pytest.mark.e2e
def test_complete_workflow():
    """End-to-end test of search, download, analyze workflow."""
    client = EclairClient()
    # Search for datasets
    results = client.search_datasets("test datasets")
    assert len(results) > 0
    # Download first dataset
    first_dataset = results[0]
    path = client.download_dataset(
        first_dataset["collection"],
        first_dataset["name"]
    )
    assert path is not None
    # Get metadata
    metadata = client.get_dataset_metadata(
        first_dataset["collection"],
        first_dataset["name"]
    )
    assert "name" in metadata
Performance Tests¶
Test performance characteristics:
# tests/performance/test_load.py
import pytest
import asyncio
import time
from eclair.client import AsyncEclairClient
@pytest.mark.performance
@pytest.mark.asyncio
async def test_concurrent_searches():
    """Test performance under concurrent load."""
    client = AsyncEclairClient()
    start_time = time.time()
    # Run 10 concurrent searches
    tasks = [
        client.search_datasets(f"query {i}")
        for i in range(10)
    ]
    results = await asyncio.gather(*tasks)
    end_time = time.time()
    total_time = end_time - start_time
    # Should complete within reasonable time
    assert total_time < 5.0
    assert all(len(result) >= 0 for result in results)