# Development Guide for Elytra PIM Client This guide provides comprehensive instructions for developers working on the Elytra PIM Client project. ## Table of Contents 1. [Environment Setup](#environment-setup) 2. [Development Workflow](#development-workflow) 3. [Code Standards](#code-standards) 4. [Testing](#testing) 5. [Common Tasks](#common-tasks) 6. [Troubleshooting](#troubleshooting) ## Environment Setup ### Prerequisites - Python 3.9 or higher - pip 20.0 or higher - Git ### Initial Setup 1. **Clone the repository** ```bash git clone https://git.him-tools.de/HIM-public/elytra_client.git cd elytra_client ``` 2. **Create virtual environment** ```bash python -m venv .venv ``` 3. **Activate virtual environment** **Windows (PowerShell):** ```powershell .\.venv\Scripts\Activate.ps1 ``` **Windows (Command Prompt):** ```cmd .\.venv\Scripts\activate.bat ``` **macOS/Linux:** ```bash source .venv/bin/activate ``` 4. **Install project with development dependencies** ```bash pip install -e ".[dev]" ``` 5. **Verify installation** ```bash pytest tests/ -v ``` ### Environment Configuration 1. **Copy example environment file** ```bash cp .env.example .env ``` 2. **Edit `.env` with your settings** ``` ELYTRA_API_URL=https://your-api-url/api/v1 ELYTRA_API_KEY=your-api-key-here ``` 3. **Never commit `.env` file** (it's in `.gitignore`) ## Development Workflow ### Daily Development Cycle 1. **Start your work session** ```bash # Activate virtual environment .venv\Scripts\activate # Windows source .venv/bin/activate # macOS/Linux # Create a feature branch git checkout -b feature/your-feature-name ``` 2. **Write tests first (TDD)** ```bash # Create test in tests/test_client.py or new test file # Implement your feature # Run tests frequently pytest tests/ -v ``` 3. **Before commits: Run all checks** ```bash # Auto-format code black elytra_client tests # Organize imports isort elytra_client tests # Lint code flake8 elytra_client tests # Type check mypy elytra_client # Run tests pytest tests/ -v --cov=elytra_client ``` 4. **Commit your changes** ```bash git add . git commit -m "feat: add new feature description" ``` 5. **Push and create pull request** ```bash git push origin feature/your-feature-name ``` ### Quick Workflow Commands For convenience, run all checks in one command: ```bash # Complete pre-commit check black elytra_client tests && isort elytra_client tests && flake8 elytra_client tests && mypy elytra_client && pytest tests/ -v ``` Or create an alias: ```bash # Add to .bashrc, .zshrc, or PowerShell profile alias check-all='black elytra_client tests && isort elytra_client tests && flake8 elytra_client tests && mypy elytra_client && pytest tests/ -v' # Then just run: check-all ``` ## Code Standards ### Language: English Only - **All code must be in English** - **All documentation must be in English** - **All comments must be in English** - **All variable/function/class names must be in English** ### Style Guide #### Line Length - Maximum 100 characters - Enforced by Black formatter - Configure in editor: View → Toggle Rulers #### Imports - Group: Standard library → Third-party → Local - Use absolute imports - Sort with isort (Black-compatible) #### Type Hints ```python # Good def get_product(product_id: str) -> ProductResponse: """Get a product by ID.""" pass # Bad def get_product(product_id): return response # Good - with Optional from typing import Optional def find_product(name: Optional[str] = None) -> Optional[ProductResponse]: """Find product by name.""" pass # Good - with Union (Python 3.10+) def process_response(data: dict | list) -> str: """Process response data.""" pass ``` #### Docstrings (Google Style) ```python def create_product( name: str, description: str, lang: str = "en", ) -> SingleProductResponse: """Create a new product. Creates a new product in the Elytra PIM system with the provided information. The product will be created with the specified language. Args: name: Product name (required) description: Product description (required) lang: Language code for the product (default: "en") Returns: SingleProductResponse: The created product with assigned ID Raises: ElytraAuthenticationError: If authentication fails ElytraValidationError: If required fields are missing ElytraAPIError: For other API errors Example: >>> client = ElytraClient(base_url="...", api_key="...") >>> product = client.create_product("Widget", "A useful widget") >>> print(product.id) """ pass ``` #### Error Handling ```python # Good - specific exceptions with context from .exceptions import ElytraAPIError try: response = requests.get(url, timeout=self.timeout) response.raise_for_status() except requests.HTTPError as e: status_code = e.response.status_code if status_code == 404: raise ElytraNotFoundError("Resource not found", status_code) from e raise ElytraAPIError(f"API error: {status_code}", status_code) from e except requests.RequestException as e: raise ElytraAPIError(f"Network error: {str(e)}") from e # Bad - too broad exception handling try: response = requests.get(url) except: print("Error occurred") ``` #### Naming Conventions ```python # Constants: UPPER_CASE API_TIMEOUT = 30 MAX_RETRIES = 3 # Classes: PascalCase class ElytraClient: pass class ProductResponse: pass # Functions/Methods: snake_case def get_products(self): pass def create_new_product(self): pass # Private methods: _leading_underscore def _make_request(self): pass def _handle_error(self, error): pass # Variables: snake_case product_id = "123" api_key = "secret" response_data = {} ``` ## Testing ### Test Structure ``` tests/ ├── test_client.py # Client functionality tests ├── test_models.py # Model validation tests (if needed) ├── test_exceptions.py # Exception handling tests (if needed) └── conftest.py # Pytest fixtures ``` ### Writing Tests ```python import pytest from unittest.mock import Mock, patch from elytra_client import ElytraClient from elytra_client.exceptions import ElytraAPIError class TestElytraClient: """Tests for ElytraClient.""" @pytest.fixture def client(self): """Create a test client.""" return ElytraClient( base_url="https://api.example.com", api_key="test-key-123" ) def test_get_products_success(self, client): """Test successful product retrieval.""" with patch.object(client.session, 'request') as mock_request: # Setup mock response mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "items": [ {"id": "1", "name": "Product 1"}, {"id": "2", "name": "Product 2"} ] } mock_request.return_value = mock_response # Execute result = client.get_products() # Assert assert isinstance(result, dict) assert "items" in result assert len(result["items"]) == 2 def test_get_products_not_found(self, client): """Test product retrieval when not found.""" with patch.object(client.session, 'request') as mock_request: # Setup mock for 404 mock_request.side_effect = Mock( side_effect=requests.HTTPError("404 Not Found") ) mock_request.return_value.status_code = 404 # Assert exception is raised with pytest.raises(ElytraNotFoundError): client.get_products() def test_authentication_error(self, client): """Test authentication error handling.""" with patch.object(client.session, 'request') as mock_request: mock_request.side_effect = requests.HTTPError("401 Unauthorized") with pytest.raises(ElytraAuthenticationError): client.get_products() ``` ### Running Tests ```bash # Run all tests pytest tests/ -v # Run with coverage report pytest tests/ -v --cov=elytra_client --cov-report=html # Run specific test file pytest tests/test_client.py -v # Run specific test class pytest tests/test_client.py::TestElytraClient -v # Run specific test function pytest tests/test_client.py::TestElytraClient::test_get_products_success -v # Run tests matching pattern pytest tests/ -k "test_get" -v # Run with minimal output pytest tests/ -q # Run with detailed output (show local variables on failure) pytest tests/ -vv ``` ### Test Coverage ```bash # Generate HTML coverage report pytest tests/ --cov=elytra_client --cov-report=html # Open the report # Open htmlcov/index.html in your browser # Target minimum coverage pytest tests/ --cov=elytra_client --cov-fail-under=80 ``` ## Common Tasks ### Adding a New API Method 1. **Add test first** in `tests/test_client.py`: ```python def test_get_product_by_id(self, client): """Test getting a product by ID.""" with patch.object(client.session, 'request') as mock_request: mock_response = Mock() mock_response.status_code = 200 mock_response.json.return_value = { "id": "123", "name": "Test Product" } mock_request.return_value = mock_response result = client.get_product(product_id="123") assert result.id == "123" assert result.name == "Test Product" ``` 2. **Create Pydantic model** if needed in `elytra_client/models.py`: ```python class ProductDetailsResponse(BaseModel): """Response model for detailed product information.""" id: str name: str description: Optional[str] = None ``` 3. **Implement method** in `elytra_client/client.py`: ```python def get_product(self, product_id: str) -> ProductDetailsResponse: """Get product details by ID. Args: product_id: The product ID Returns: ProductDetailsResponse: The product details Raises: ElytraNotFoundError: If product not found ElytraAuthenticationError: If authentication fails """ endpoint = f"/products/{product_id}" return self._make_request( method="GET", endpoint=endpoint, response_model=ProductDetailsResponse ) ``` 4. **Run tests**: ```bash pytest tests/test_client.py::TestElytraClient::test_get_product_by_id -v ``` 5. **Run all checks**: ```bash black elytra_client tests && isort elytra_client tests && flake8 elytra_client tests && mypy elytra_client && pytest tests/ -v ``` ### Fixing a Bug 1. **Create failing test** that reproduces the bug 2. **Verify the test fails** 3. **Fix the bug** 4. **Verify the test passes** 5. **Run full test suite** to ensure no regressions 6. **Run all code checks** 7. **Commit with clear message**: ```bash git commit -m "fix: resolve issue with product validation" ``` ### Updating OpenAPI Spec 1. **Update** `openapi.yaml` (source of truth) 2. **Update models** in `models.py` to match spec 3. **Update methods** in `client.py` if endpoints changed 4. **Write/update tests** 5. **Run all checks** ### Creating a Release 1. **Update version** in `pyproject.toml` 2. **Update CHANGELOG** (if exists) 3. **Commit changes**: ```bash git commit -m "chore: bump version to X.Y.Z" ``` 4. **Tag release**: ```bash git tag vX.Y.Z git push origin vX.Y.Z ``` ## Troubleshooting ### Issue: Import Errors **Problem**: `ModuleNotFoundError: No module named 'elytra_client'` **Solution**: ```bash # Ensure virtual environment is activated .venv\Scripts\activate # Windows source .venv/bin/activate # macOS/Linux # Install package in development mode pip install -e . ``` ### Issue: Type Checking Failures **Problem**: `error: Cannot find implementation or library stub for module named "module_name"` **Solution**: ```bash # Install type stubs pip install types-requests # Run mypy with more details mypy elytra_client --show-error-codes # Check specific file mypy elytra_client/client.py ``` ### Issue: Tests Fail with "Connection Error" **Problem**: Tests are making real API calls instead of using mocks **Solution**: - Verify you're using `patch.object(client.session, 'request')` - Check that mock is set up before calling client methods - Use `mock_request.side_effect` for exceptions ```python # Correct way to mock with patch.object(client.session, 'request') as mock_request: mock_request.return_value = Mock(status_code=200, ...) # Now this won't make real request result = client.get_products() ``` ### Issue: Formatting Conflicts **Problem**: Black and isort produce different results or conflicts **Solution**: ```bash # Always run in this order black elytra_client tests isort elytra_client tests ``` If you encounter persistent conflicts, check `.isort.cfg` or `pyproject.toml` for Black-compatible settings. ### Issue: Virtual Environment Issues **Problem**: Changes to venv not reflected, or "python: command not found" **Solution**: ```bash # Deactivate current environment deactivate # Remove old venv rm -rf .venv # macOS/Linux rmdir /s .venv # Windows # Create fresh venv python -m venv .venv # Activate and reinstall .venv\Scripts\activate # Windows pip install -e ".[dev]" ``` ### Issue: Pydantic Validation Errors **Problem**: `ValidationError: X validation error(s) for ModelName` **Solution**: - Check API response matches model definition in `models.py` - Verify OpenAPI spec matches response data - Use `response_model.model_validate(data)` to see detailed error - Consider using `Field(default=None)` for optional fields ## VS Code Integration ### Recommended Extensions - Python (ms-python.python) - Pylance (ms-python.vscode-pylance) - Black Formatter (ms-python.black-formatter) - Flake8 (ms-python.flake8) - Mypy Type Checker (ms-python.mypy-type-checker) - Prettier (esbenp.prettier-vscode) ### Keyboard Shortcuts - Format document: `Shift+Alt+F` - Sort imports: Run command palette `isort: sort imports` - Run tests: `Ctrl+; Ctrl+T` (with Python extension) - Quick fix: `Ctrl+.` ### Command Palette Commands - `Python: Create Terminal` - Creates Python terminal with venv activated - `Test: Run All Tests` - Runs all tests - `Python: Run Selection/Line in Python Terminal` - Test code snippets ## Additional Resources - **Project README**: [README.md](README.md) - **Copilot Instructions**: [.copilot-instructions.md](.copilot-instructions.md) - **Agent Instructions**: [.agent-instructions.md](.agent-instructions.md) - **Pydantic Docs**: https://docs.pydantic.dev/latest/ - **Pytest Docs**: https://docs.pytest.org/ - **Type Hints Docs**: https://docs.python.org/3/library/typing.html