- 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.
617 lines
15 KiB
Markdown
617 lines
15 KiB
Markdown
# 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
|