# Contributing to Open Notebook Thank you for your interest in contributing to Open Notebook! We welcome contributions from developers of all skill levels. This guide will help you get started and understand our development workflow. ## ๐ŸŽฏ Quick Start for Contributors ### 1. Fork and Clone ```bash # Fork the repository on GitHub, then clone your fork git clone https://github.com/YOUR_USERNAME/open-notebook.git cd open-notebook # Add the original repository as upstream git remote add upstream https://github.com/lfnovo/open-notebook.git ``` ### 2. Set Up Development Environment ```bash # Install dependencies using uv (recommended) uv sync # Or using pip pip install -e . # Start the development environment make start-all ``` ### 3. Verify Setup ```bash # Check that the API is running curl http://localhost:5055/health # Check that the frontend is accessible open http://localhost:8502 ``` ## ๐Ÿ—๏ธ Development Workflow ### Branch Strategy We use a **feature branch workflow**: 1. **Main Branch**: `main` - production-ready code 2. **Feature Branches**: `feature/description` - new features 3. **Bug Fixes**: `fix/description` - bug fixes 4. **Documentation**: `docs/description` - documentation updates ### Making Changes 1. **Create a feature branch**: ```bash git checkout -b feature/amazing-new-feature ``` 2. **Make your changes** following our coding standards 3. **Test your changes**: ```bash # Run tests uv run pytest # Run linting uv run ruff check . # Run formatting uv run ruff format . ``` 4. **Commit your changes**: ```bash git add . git commit -m "feat: add amazing new feature" ``` 5. **Push and create PR**: ```bash git push origin feature/amazing-new-feature # Then create a Pull Request on GitHub ``` ### Keeping Your Fork Updated ```bash # Fetch upstream changes git fetch upstream # Switch to main and merge git checkout main git merge upstream/main # Push to your fork git push origin main ``` ## ๐Ÿ“ Code Standards ### Python Style Guide We follow **PEP 8** with some specific guidelines: #### Code Formatting - Use **Ruff** for linting and formatting - Maximum line length: **88 characters** - Use **double quotes** for strings - Use **trailing commas** in multi-line structures #### Type Hints Always use type hints for function parameters and return values: ```python from typing import List, Optional, Dict, Any from pydantic import BaseModel async def process_content( content: str, options: Optional[Dict[str, Any]] = None ) -> ProcessedContent: """Process content with optional configuration.""" # Implementation ``` #### Async/Await Patterns Use async/await consistently: ```python # Good async def fetch_data(url: str) -> Dict[str, Any]: async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.json() # Bad - mixing sync and async def fetch_data(url: str) -> Dict[str, Any]: loop = asyncio.get_event_loop() return loop.run_until_complete(async_fetch(url)) ``` #### Error Handling Use structured error handling with custom exceptions: ```python from open_notebook.exceptions import DatabaseOperationError, InvalidInputError async def create_notebook(name: str, description: str) -> Notebook: """Create a new notebook with validation.""" if not name.strip(): raise InvalidInputError("Notebook name cannot be empty") try: notebook = Notebook(name=name, description=description) await notebook.save() return notebook except Exception as e: raise DatabaseOperationError(f"Failed to create notebook: {str(e)}") ``` #### Documentation Use **Google-style docstrings**: ```python async def vector_search( query: str, limit: int = 10, minimum_score: float = 0.2 ) -> List[SearchResult]: """Perform vector search across embedded content. Args: query: Search query string limit: Maximum number of results to return minimum_score: Minimum similarity score for results Returns: List of search results sorted by relevance score Raises: InvalidInputError: If query is empty or limit is invalid DatabaseOperationError: If search operation fails """ ``` ### FastAPI Standards #### Router Organization Organize endpoints by domain: ```python # api/routers/notebooks.py from fastapi import APIRouter, HTTPException, Query from typing import List, Optional router = APIRouter() @router.get("/notebooks", response_model=List[NotebookResponse]) async def get_notebooks( archived: Optional[bool] = Query(None, description="Filter by archived status"), order_by: str = Query("updated desc", description="Order by field and direction"), ): """Get all notebooks with optional filtering and ordering.""" ``` #### Request/Response Models Use Pydantic models for validation: ```python from pydantic import BaseModel, Field from typing import Optional class NotebookCreate(BaseModel): name: str = Field(..., description="Name of the notebook", min_length=1) description: str = Field(default="", description="Description of the notebook") class NotebookResponse(BaseModel): id: str name: str description: str archived: bool created: str updated: str ``` #### Error Handling Use consistent error responses: ```python from fastapi import HTTPException from loguru import logger try: result = await some_operation() return result except InvalidInputError as e: raise HTTPException(status_code=400, detail=str(e)) except DatabaseOperationError as e: logger.error(f"Database error: {str(e)}") raise HTTPException(status_code=500, detail="Internal server error") ``` ### Database Standards #### SurrealDB Patterns Use the repository pattern consistently: ```python from open_notebook.database.repository import repo_create, repo_query, repo_update # Create records async def create_notebook(data: Dict[str, Any]) -> Dict[str, Any]: """Create a new notebook record.""" return await repo_create("notebook", data) # Query with parameters async def find_notebooks_by_user(user_id: str) -> List[Dict[str, Any]]: """Find notebooks for a specific user.""" return await repo_query( "SELECT * FROM notebook WHERE user_id = $user_id", {"user_id": user_id} ) # Update records async def update_notebook(notebook_id: str, data: Dict[str, Any]) -> Dict[str, Any]: """Update a notebook record.""" return await repo_update("notebook", notebook_id, data) ``` #### Schema Management Use migrations for schema changes: ```surrealql -- migrations/8.surrealql DEFINE TABLE IF NOT EXISTS new_feature SCHEMAFULL; DEFINE FIELD IF NOT EXISTS name ON TABLE new_feature TYPE string; DEFINE FIELD IF NOT EXISTS description ON TABLE new_feature TYPE option; DEFINE FIELD IF NOT EXISTS created ON TABLE new_feature TYPE datetime DEFAULT time::now(); DEFINE FIELD IF NOT EXISTS updated ON TABLE new_feature TYPE datetime DEFAULT time::now(); ``` ## ๐Ÿงช Testing Guidelines ### Test Structure We use **pytest** with async support: ```python import pytest from httpx import AsyncClient from open_notebook.domain.notebook import Notebook @pytest.mark.asyncio async def test_create_notebook(): """Test notebook creation.""" notebook = Notebook(name="Test Notebook", description="Test description") await notebook.save() assert notebook.id is not None assert notebook.name == "Test Notebook" assert notebook.created is not None @pytest.mark.asyncio async def test_api_create_notebook(): """Test notebook creation via API.""" async with AsyncClient(app=app, base_url="http://test") as client: response = await client.post( "/api/notebooks", json={"name": "Test Notebook", "description": "Test description"} ) assert response.status_code == 200 data = response.json() assert data["name"] == "Test Notebook" ``` ### Test Categories 1. **Unit Tests**: Test individual functions and methods 2. **Integration Tests**: Test component interactions 3. **API Tests**: Test HTTP endpoints 4. **Database Tests**: Test data persistence and queries ### Running Tests ```bash # Run all tests uv run pytest # Run specific test file uv run pytest tests/test_notebooks.py # Run with coverage uv run pytest --cov=open_notebook # Run only unit tests uv run pytest tests/unit/ # Run only integration tests uv run pytest tests/integration/ ``` ### Test Fixtures Use pytest fixtures for common setup: ```python @pytest.fixture async def test_notebook(): """Create a test notebook.""" notebook = Notebook(name="Test Notebook", description="Test description") await notebook.save() yield notebook await notebook.delete() @pytest.fixture async def api_client(): """Create an API test client.""" async with AsyncClient(app=app, base_url="http://test") as client: yield client ``` ## ๐Ÿ“š Documentation Standards ### Code Documentation #### Module Docstrings ```python """ Notebook domain model and operations. This module contains the core Notebook class and related operations for managing research notebooks within the Open Notebook system. """ ``` #### Class Docstrings ```python class Notebook(BaseModel): """A research notebook containing sources, notes, and chat sessions. Notebooks are the primary organizational unit in Open Notebook, allowing users to group related research materials and maintain separate contexts for different projects. Attributes: name: The notebook's display name description: Optional description of the notebook's purpose archived: Whether the notebook is archived (default: False) created: Timestamp of creation updated: Timestamp of last update """ ``` #### Function Docstrings ```python async def create_notebook( name: str, description: str = "", user_id: Optional[str] = None ) -> Notebook: """Create a new notebook with validation. Args: name: The notebook name (required, non-empty) description: Optional notebook description user_id: Optional user ID for multi-user deployments Returns: The created notebook instance Raises: InvalidInputError: If name is empty or invalid DatabaseOperationError: If creation fails Example: ```python notebook = await create_notebook( name="AI Research", description="Research on AI applications" ) ``` """ ``` ### API Documentation Use FastAPI's automatic documentation features: ```python @router.post( "/notebooks", response_model=NotebookResponse, summary="Create a new notebook", description="Create a new notebook with the specified name and description.", responses={ 201: {"description": "Notebook created successfully"}, 400: {"description": "Invalid input data"}, 500: {"description": "Internal server error"} } ) async def create_notebook(notebook: NotebookCreate): """Create a new notebook.""" ``` ### README Updates When adding new features, update relevant documentation: - **Feature documentation** in `docs/features/` - **API documentation** in `docs/development/api-reference.md` - **Architecture documentation** if adding new components - **User guide** if adding user-facing features ## ๐Ÿš€ Development Environment ### Prerequisites - **Python 3.11+** - **uv** (recommended) or **pip** - **SurrealDB** (via Docker or binary) - **Docker** (optional, for containerized development) ### Environment Variables Create a `.env` file in the project root: ```bash # Database SURREAL_URL=ws://localhost:8000/rpc SURREAL_USER=root SURREAL_PASSWORD=password SURREAL_NAMESPACE=open_notebook SURREAL_DATABASE=development # AI Providers (add your API keys) OPENAI_API_KEY=sk-... ANTHROPIC_API_KEY=sk-ant-... GOOGLE_API_KEY=AI... # Application APP_PASSWORD= # Optional password protection DEBUG=true LOG_LEVEL=DEBUG ``` ### Local Development Setup ```bash # Start SurrealDB docker run -d --name surrealdb -p 8000:8000 \ surrealdb/surrealdb:v1-latest start \ --user root --pass password \ --bind 0.0.0.0:8000 memory # Install dependencies uv sync # Run database migrations uv run python -m open_notebook.database.async_migrate # Start the API server uv run python run_api.py # Start the Next.js frontend (in another terminal) cd frontend && npm run dev ``` ### Development Tools We use these tools for development: - **Ruff**: Linting and formatting - **Pytest**: Testing framework - **MyPy**: Type checking - **Pre-commit**: Git hooks for code quality Install pre-commit hooks: ```bash uv run pre-commit install ``` ## ๐Ÿ”ง Common Development Tasks ### Adding a New API Endpoint 1. **Create the endpoint** in the appropriate router: ```python # api/routers/notebooks.py @router.post("/notebooks/{notebook_id}/archive") async def archive_notebook(notebook_id: str): """Archive a notebook.""" # Implementation ``` 2. **Add request/response models** if needed: ```python # api/models.py class ArchiveRequest(BaseModel): reason: Optional[str] = Field(None, description="Reason for archiving") ``` 3. **Update the domain model** if needed: ```python # open_notebook/domain/notebook.py async def archive(self, reason: Optional[str] = None) -> None: """Archive this notebook.""" # Implementation ``` 4. **Write tests**: ```python # tests/test_notebooks.py @pytest.mark.asyncio async def test_archive_notebook(): """Test notebook archiving.""" # Test implementation ``` 5. **Update documentation** in `docs/development/api-reference.md` ### Adding a New Domain Model 1. **Create the model**: ```python # open_notebook/domain/new_model.py from open_notebook.domain.base import BaseModel class NewModel(BaseModel): """New domain model.""" # Fields and methods ``` 2. **Create database migration**: ```surrealql -- migrations/N.surrealql DEFINE TABLE IF NOT EXISTS new_model SCHEMAFULL; -- Field definitions ``` 3. **Add API endpoints**: ```python # api/routers/new_model.py # Router implementation ``` 4. **Write comprehensive tests** ### Adding AI Processing Features 1. **Create the graph**: ```python # open_notebook/graphs/new_feature.py from langgraph import create_graph @create_graph async def new_feature_graph(state: NewFeatureState): """New AI processing feature.""" # Implementation ``` 2. **Add service layer**: ```python # api/new_feature_service.py # Service implementation ``` 3. **Create API endpoints**: ```python # api/routers/new_feature.py # Router implementation ``` 4. **Test with multiple AI providers** ## ๐ŸŒŸ Feature Contribution Guidelines ### Current Priority Areas We're actively looking for contributions in these areas: 1. **Frontend Enhancement**: Help improve the Next.js/React UI with real-time updates and better UX 2. **Testing**: Expand test coverage across all components 3. **Performance**: Async processing improvements and caching 4. **Documentation**: API examples and user guides 5. **Integrations**: New content sources and AI providers ### Feature Proposal Process 1. **Check existing issues** to avoid duplicates 2. **Open a discussion** on GitHub for large features 3. **Create an issue** with detailed requirements 4. **Get approval** from maintainers before starting work 5. **Implement in phases** for large features ### Code Review Process All contributions go through code review: 1. **Automated checks** must pass (linting, tests) 2. **Manual review** by maintainers 3. **Documentation review** for user-facing changes 4. **Integration testing** for complex features ## ๐Ÿ› Bug Reports and Issues ### Reporting Bugs When reporting bugs, please include: 1. **Clear description** of the issue 2. **Steps to reproduce** the problem 3. **Expected vs actual behavior** 4. **Environment details** (OS, Python version, etc.) 5. **Relevant logs** and error messages ### Bug Fix Process 1. **Reproduce the issue** locally 2. **Write a failing test** that demonstrates the bug 3. **Fix the issue** with minimal changes 4. **Verify the fix** passes all tests 5. **Update documentation** if needed ## ๐Ÿ“ž Getting Help ### Community Support - **Discord**: [Join our Discord server](https://discord.gg/37XJPXfz2w) for real-time help - **GitHub Discussions**: For longer-form questions and ideas - **GitHub Issues**: For bug reports and feature requests ### Mentorship New contributors are welcome! We offer: - **First-time contributor** guidance - **Code review** and feedback - **Architecture discussions** - **Career development** advice ## ๐Ÿ† Recognition We recognize contributions through: - **GitHub credits** on releases - **Community recognition** in Discord - **Contribution statistics** in project analytics - **Maintainer consideration** for active contributors ## ๐Ÿ“œ Code of Conduct We follow the [Contributor Covenant](https://www.contributor-covenant.org/). Please: - **Be respectful** and inclusive - **Help others** learn and grow - **Give constructive feedback** - **Focus on the code**, not the person ## ๐ŸŽ‰ Thank You! Thank you for contributing to Open Notebook! Your contributions help make research more accessible and private for everyone. Whether you're fixing a typo, adding a feature, or helping with documentation, every contribution matters. Join our community and let's build something amazing together! ๐Ÿš€ --- For questions about this guide or contributing in general, please reach out on [Discord](https://discord.gg/37XJPXfz2w) or open a GitHub Discussion.