elytra_client/DEVELOPMENT.md
claudi 459838b2e6 Add comprehensive development and style guides for Elytra PIM Client
- Introduced .copilot-instructions.md for GitHub Copilot usage guidelines.
- Created DEVELOPMENT.md detailing environment setup, workflow, and common tasks.
- Established STYLE_GUIDE.md as a quick reference for code style, formatting, and conventions.
2026-02-20 09:14:40 +01:00

15 KiB

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
  2. Development Workflow
  3. Code Standards
  4. Testing
  5. Common Tasks
  6. Troubleshooting

Environment Setup

Prerequisites

  • Python 3.9 or higher
  • pip 20.0 or higher
  • Git

Initial Setup

  1. Clone the repository

    git clone https://git.him-tools.de/HIM-public/elytra_client.git
    cd elytra_client
    
  2. Create virtual environment

    python -m venv .venv
    
  3. Activate virtual environment

    Windows (PowerShell):

    .\.venv\Scripts\Activate.ps1
    

    Windows (Command Prompt):

    .\.venv\Scripts\activate.bat
    

    macOS/Linux:

    source .venv/bin/activate
    
  4. Install project with development dependencies

    pip install -e ".[dev]"
    
  5. Verify installation

    pytest tests/ -v
    

Environment Configuration

  1. Copy example environment file

    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

    # 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)

    # 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

    # 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

    git add .
    git commit -m "feat: add new feature description"
    
  5. Push and create pull request

    git push origin feature/your-feature-name
    

Quick Workflow Commands

For convenience, run all checks in one command:

# 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:

# 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

# 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)

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

# 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

# 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

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

# 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

# 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:

    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:

    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:

    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:

    pytest tests/test_client.py::TestElytraClient::test_get_product_by_id -v
    
  5. Run all checks:

    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:
    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:
    git commit -m "chore: bump version to X.Y.Z"
    
  4. Tag release:
    git tag vX.Y.Z
    git push origin vX.Y.Z
    

Troubleshooting

Issue: Import Errors

Problem: ModuleNotFoundError: No module named 'elytra_client'

Solution:

# 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:

# 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
# 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:

# 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:

# 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

  • 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