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.
This commit is contained in:
parent
05fca294f9
commit
459838b2e6
4 changed files with 1794 additions and 0 deletions
303
.agent-instructions.md
Normal file
303
.agent-instructions.md
Normal file
|
|
@ -0,0 +1,303 @@
|
||||||
|
# Agent Instructions for Elytra PIM Client
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document provides detailed instructions for autonomous agents (automated tools, CI/CD systems, code generation tools) working with the Elytra PIM Client project.
|
||||||
|
|
||||||
|
## Project Identity
|
||||||
|
|
||||||
|
- **Name**: Elytra PIM Client
|
||||||
|
- **Description**: A fully Pythonic, Pydantic-driven client for the Elytra Product Information Management API
|
||||||
|
- **Language**: Python 3.9+
|
||||||
|
- **Type System**: Full type hints with static type checking (mypy)
|
||||||
|
- **Data Validation**: Pydantic v2
|
||||||
|
- **Package Manager**: pip with setuptools
|
||||||
|
|
||||||
|
## Mandatory Rules
|
||||||
|
|
||||||
|
### 1. Language Requirement
|
||||||
|
- **ALL code must be in English**
|
||||||
|
- **ALL docstrings must be in English**
|
||||||
|
- **ALL comments must be in English**
|
||||||
|
- **ALL variable/function/class names must be in English**
|
||||||
|
- **NO exceptions to this rule**
|
||||||
|
|
||||||
|
### 2. Code Quality Gates
|
||||||
|
- Must pass Black formatting (100 char line length)
|
||||||
|
- Must pass isort import checks
|
||||||
|
- Must pass Flake8 linting
|
||||||
|
- Must pass mypy type checking
|
||||||
|
- Must achieve >80% test coverage
|
||||||
|
- All tests must pass
|
||||||
|
|
||||||
|
### 3. Source of Truth
|
||||||
|
- **OpenAPI specification** (`openapi.yaml`) is the source of truth for API design
|
||||||
|
- All Pydantic models should reflect the OpenAPI spec
|
||||||
|
- API endpoints should match OpenAPI definitions
|
||||||
|
- Request/response models must align with OpenAPI
|
||||||
|
|
||||||
|
## Automated Task Guidelines
|
||||||
|
|
||||||
|
### Before Any Code Modification
|
||||||
|
1. Verify current state of the codebase
|
||||||
|
2. Check existing tests and coverage
|
||||||
|
3. Understand the specific change needed
|
||||||
|
4. Create a test case first if fixing a bug
|
||||||
|
5. Run full test suite to establish baseline
|
||||||
|
|
||||||
|
### When Generating Code
|
||||||
|
1. Follow Google-style docstrings strictly
|
||||||
|
2. Include comprehensive type hints
|
||||||
|
3. Add docstrings for all public APIs
|
||||||
|
4. Create corresponding tests
|
||||||
|
5. Ensure code passes all formatting tools
|
||||||
|
6. Add to appropriate module without reorganizing structure
|
||||||
|
|
||||||
|
### When Modifying Tests
|
||||||
|
1. Preserve test isolation (each test independent)
|
||||||
|
2. Use pytest fixtures for setup/teardown
|
||||||
|
3. Mock external dependencies (requests, etc.)
|
||||||
|
4. Test both success and error paths
|
||||||
|
5. Include descriptive test names: `test_<action>_<condition>_<expected_result>`
|
||||||
|
|
||||||
|
### When Running Tests
|
||||||
|
```bash
|
||||||
|
# Full test suite with coverage
|
||||||
|
pytest tests/ -v --cov=elytra_client --cov-report=term-missing
|
||||||
|
|
||||||
|
# Specific test file
|
||||||
|
pytest tests/test_client.py -v
|
||||||
|
|
||||||
|
# With markers (if defined)
|
||||||
|
pytest tests/ -v -m "not integration"
|
||||||
|
```
|
||||||
|
|
||||||
|
### When Formatting Code
|
||||||
|
Execute in this order:
|
||||||
|
```bash
|
||||||
|
1. black elytra_client tests
|
||||||
|
2. isort elytra_client tests
|
||||||
|
3. flake8 elytra_client tests
|
||||||
|
4. mypy elytra_client
|
||||||
|
5. pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Organization Rules
|
||||||
|
|
||||||
|
### Module Responsibilities
|
||||||
|
- **`client.py`**: Main `ElytraClient` class with API methods
|
||||||
|
- **`models.py`**: All Pydantic models for request/response validation
|
||||||
|
- **`exceptions.py`**: Custom exception hierarchy
|
||||||
|
- **`config.py`**: Configuration and environment setup
|
||||||
|
- **`__init__.py`**: Package-level exports
|
||||||
|
|
||||||
|
### Do Not
|
||||||
|
- ❌ Create new modules without justification
|
||||||
|
- ❌ Move code between modules without updating imports
|
||||||
|
- ❌ Reorganize directory structure
|
||||||
|
- ❌ Rename existing modules or classes
|
||||||
|
|
||||||
|
## API Method Generation Rules
|
||||||
|
|
||||||
|
When adding new API methods to `ElytraClient`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def method_name(
|
||||||
|
self,
|
||||||
|
required_param: str,
|
||||||
|
optional_param: Optional[str] = None,
|
||||||
|
lang: str = "en",
|
||||||
|
page: int = 1,
|
||||||
|
limit: int = 10,
|
||||||
|
) -> ResponseModelType | Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
One-line summary.
|
||||||
|
|
||||||
|
Longer description if needed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
required_param: Description
|
||||||
|
optional_param: Description (default: None)
|
||||||
|
lang: Language code (default: "en")
|
||||||
|
page: Page number for pagination (default: 1)
|
||||||
|
limit: Items per page (default: 10)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Pydantic model instance or dict with 'items' key for collections
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ElytraNotFoundError: If resource not found
|
||||||
|
ElytraAuthenticationError: If authentication fails
|
||||||
|
ElytraValidationError: If data validation fails
|
||||||
|
ElytraAPIError: For other API errors
|
||||||
|
"""
|
||||||
|
endpoint = f"/endpoint/{required_param}"
|
||||||
|
params = {"lang": lang, "page": page, "limit": limit}
|
||||||
|
if optional_param:
|
||||||
|
params["optional"] = optional_param
|
||||||
|
|
||||||
|
return self._make_request(
|
||||||
|
method="GET",
|
||||||
|
endpoint=endpoint,
|
||||||
|
params=params,
|
||||||
|
response_model=ResponseModelType,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model Generation Rules
|
||||||
|
|
||||||
|
When creating new Pydantic models:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
class ResourceResponse(BaseModel):
|
||||||
|
"""Response model for a single resource.
|
||||||
|
|
||||||
|
This model validates and serializes API responses according to
|
||||||
|
the OpenAPI specification.
|
||||||
|
"""
|
||||||
|
id: str = Field(..., description="Unique identifier")
|
||||||
|
name: str = Field(..., description="Resource name")
|
||||||
|
optional_field: Optional[str] = Field(None, description="Optional field")
|
||||||
|
nested_items: List[str] = Field(default_factory=list, description="List of items")
|
||||||
|
|
||||||
|
model_config = ConfigDict(str_strip_whitespace=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling Rules
|
||||||
|
|
||||||
|
### Exception Usage
|
||||||
|
- Use `ElytraAuthenticationError` for 401/403 responses
|
||||||
|
- Use `ElytraNotFoundError` for 404 responses
|
||||||
|
- Use `ElytraValidationError` for validation failures
|
||||||
|
- Use `ElytraAPIError` as fallback for other 4xx/5xx errors
|
||||||
|
|
||||||
|
### Pattern
|
||||||
|
```python
|
||||||
|
try:
|
||||||
|
response = self.session.request(...)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if response_model:
|
||||||
|
return response_model.model_validate(response.json())
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except requests.HTTPError as e:
|
||||||
|
_handle_http_error(e)
|
||||||
|
raise # Type guard for type checker
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
raise ElytraAPIError(f"Network error: {str(e)}") from e
|
||||||
|
|
||||||
|
except ValidationError as e:
|
||||||
|
raise ElytraValidationError(
|
||||||
|
f"Response validation failed: {str(e)}"
|
||||||
|
) from e
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Integration
|
||||||
|
|
||||||
|
### Pre-Commit Requirements
|
||||||
|
- All code must be formatte with Black
|
||||||
|
- All imports must be sorted with isort
|
||||||
|
- All tests must pass
|
||||||
|
- Type checking must pass
|
||||||
|
|
||||||
|
### Pull Request Checks
|
||||||
|
- Code coverage must remain ≥80%
|
||||||
|
- No type checking errors
|
||||||
|
- No linting errors
|
||||||
|
- All tests pass
|
||||||
|
|
||||||
|
## Dependencies Management
|
||||||
|
|
||||||
|
### Current Dependencies
|
||||||
|
- `requests>=2.28.0` - HTTP client
|
||||||
|
- `python-dotenv>=0.21.0` - Environment configuration
|
||||||
|
- `pydantic>=2.0.0` - Data validation
|
||||||
|
|
||||||
|
### Dev Dependencies
|
||||||
|
- `pytest>=7.0.0` - Testing
|
||||||
|
- `pytest-cov>=4.0.0` - Coverage reporting
|
||||||
|
- `black>=23.0.0` - Code formatting
|
||||||
|
- `isort>=5.12.0` - Import sorting
|
||||||
|
- `flake8>=6.0.0` - Code linting
|
||||||
|
- `mypy>=1.0.0` - Static type checking
|
||||||
|
|
||||||
|
### Rules
|
||||||
|
- ❌ Do not add dependencies without strong justification
|
||||||
|
- ❌ Do not update major versions without testing
|
||||||
|
- ✅ Keep dependency list minimal
|
||||||
|
- ✅ Pin minor versions for stability
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
### When to Update
|
||||||
|
- README.md: When features change
|
||||||
|
- Docstrings: Always for public APIs
|
||||||
|
- CHANGELOG.md (if exists): For releases
|
||||||
|
- Code comments: When logic changes
|
||||||
|
|
||||||
|
### When NOT to Update
|
||||||
|
- Do not update examples that aren't broken
|
||||||
|
- Do not add optional documentation
|
||||||
|
- Do not rewrite for style alone
|
||||||
|
|
||||||
|
## Troubleshooting for Agents
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**ImportError or ModuleNotFoundError**
|
||||||
|
- Ensure virtual environment is activated
|
||||||
|
- Verify dependencies installed: `pip install -e ".[dev]"`
|
||||||
|
- Check Python path includes project root
|
||||||
|
|
||||||
|
**Type Checking Failures**
|
||||||
|
- Review mypy errors carefully
|
||||||
|
- Use `Optional[T]` or `T | None` for nullable types
|
||||||
|
- Use proper return types on all functions
|
||||||
|
- Check imports from pydantic match v2 API
|
||||||
|
|
||||||
|
**Test Failures**
|
||||||
|
- Always mock external requests
|
||||||
|
- Verify test isolation (no shared state)
|
||||||
|
- Check assertions match actual API behavior
|
||||||
|
- Review mocked response structure matches models
|
||||||
|
|
||||||
|
**Formatting Issues**
|
||||||
|
- Run Black first: `black elytra_client tests`
|
||||||
|
- Then isort: `isort elytra_client tests`
|
||||||
|
- Line length is 100 characters
|
||||||
|
- Follow PEP 8 style guide
|
||||||
|
|
||||||
|
## Key Principles
|
||||||
|
|
||||||
|
1. **Reliability**: Every change must be tested
|
||||||
|
2. **Type Safety**: Full type hints, verified with mypy
|
||||||
|
3. **Consistency**: Follow established patterns
|
||||||
|
4. **Documentation**: All changes must be documented in English
|
||||||
|
5. **Simplicity**: Keep modifications focused and atomic
|
||||||
|
6. **Quality Gates**: Never skip formatting or testing
|
||||||
|
|
||||||
|
## Success Criteria for Automated Tasks
|
||||||
|
|
||||||
|
A task is complete when:
|
||||||
|
- ✅ All code passes Black formatting
|
||||||
|
- ✅ All imports pass isort checks
|
||||||
|
- ✅ All code passes Flake8 linting
|
||||||
|
- ✅ All code passes mypy type checking
|
||||||
|
- ✅ All tests pass
|
||||||
|
- ✅ Code coverage ≥80%
|
||||||
|
- ✅ All docstrings are complete and in English
|
||||||
|
- ✅ No warnings or errors in logs
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
When completing tasks, report:
|
||||||
|
1. Changes made (file, function, type of change)
|
||||||
|
2. Tests added/modified (test names)
|
||||||
|
3. Coverage impact (before/after percentage)
|
||||||
|
4. Any issues encountered and resolutions
|
||||||
|
5. Commands used for verification
|
||||||
284
.copilot-instructions.md
Normal file
284
.copilot-instructions.md
Normal file
|
|
@ -0,0 +1,284 @@
|
||||||
|
# GitHub Copilot Instructions for Elytra PIM Client
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
**Elytra PIM Client** is a fully Pythonic, Pydantic-driven client for the Elytra Product Information Management (PIM) API. It provides type-safe, validated interactions with the Elytra PIM API using modern Python practices.
|
||||||
|
|
||||||
|
### Key Characteristics
|
||||||
|
- Python 3.9+ project with full type hints
|
||||||
|
- Pydantic v2 for data validation and serialization
|
||||||
|
- OpenAPI specification-driven API design
|
||||||
|
- Comprehensive error handling with custom exception classes
|
||||||
|
- Bearer token authentication
|
||||||
|
- Context manager support
|
||||||
|
- Fully tested with pytest
|
||||||
|
|
||||||
|
## Code Style and Conventions
|
||||||
|
|
||||||
|
### Language
|
||||||
|
- **All code and documentation must be in English**.
|
||||||
|
- No comments, docstrings, variable names, or documentation in other languages.
|
||||||
|
- Use clear, professional American English.
|
||||||
|
|
||||||
|
### Formatting and Standards
|
||||||
|
- **Code Style**: Follow PEP 8 with line length of 100 characters
|
||||||
|
- **Formatter**: Black (configured in project)
|
||||||
|
- **Import Order**: Use isort with Black-compatible settings
|
||||||
|
- **Linting**: Flake8 for code quality checks
|
||||||
|
- **Type Checking**: mypy for static type checking
|
||||||
|
|
||||||
|
### Type Hints
|
||||||
|
- **Always include type hints** for function parameters and return types
|
||||||
|
- Use `Optional[T]` or `T | None` for nullable types
|
||||||
|
- Use `TypeVar` for generic functions
|
||||||
|
- Use union types with `|` operator (Python 3.10+)
|
||||||
|
- For APIs returning multiple response types, use `TypeVar` bound to `BaseModel`
|
||||||
|
|
||||||
|
### Docstrings
|
||||||
|
- Use Google-style docstrings for all public modules, classes, and functions
|
||||||
|
- Format:
|
||||||
|
```python
|
||||||
|
"""One-line summary.
|
||||||
|
|
||||||
|
Longer description if needed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param_name: Description of parameter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Description of return value
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExceptionType: When this exception is raised
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
- Group imports: stdlib, third-party, local
|
||||||
|
- Use absolute imports
|
||||||
|
- Keep imports at module level unless lazy loading is necessary
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
elytra_client/
|
||||||
|
├── __init__.py # Package exports
|
||||||
|
├── client.py # Main ElytraClient class
|
||||||
|
├── config.py # Configuration handling
|
||||||
|
├── models.py # Pydantic models (auto-generated from OpenAPI)
|
||||||
|
└── exceptions.py # Custom exception classes
|
||||||
|
|
||||||
|
tests/
|
||||||
|
├── test_client.py # Client tests
|
||||||
|
└── ... # Other test files
|
||||||
|
|
||||||
|
examples/
|
||||||
|
└── ... # Example usage scripts
|
||||||
|
|
||||||
|
openapi.yaml # OpenAPI specification (source of truth)
|
||||||
|
pyproject.toml # Project metadata and dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Patterns and Conventions
|
||||||
|
|
||||||
|
### Exception Handling
|
||||||
|
- Use custom exception classes from `exceptions.py`:
|
||||||
|
- `ElytraAPIError`: Base exception for all API errors
|
||||||
|
- `ElytraAuthenticationError`: Authentication failures (401, 403)
|
||||||
|
- `ElytraNotFoundError`: Resource not found (404)
|
||||||
|
- `ElytraValidationError`: Validation failures
|
||||||
|
- Always catch `requests.RequestException` in HTTP methods
|
||||||
|
- Always catch `ValidationError` when working with Pydantic models
|
||||||
|
- Provide meaningful error messages with context
|
||||||
|
|
||||||
|
### API Methods in ElytraClient
|
||||||
|
- Follow REST conventions: `get_*`, `create_*`, `update_*`, `delete_*`
|
||||||
|
- Return Pydantic models for single resources or dict with `items` list for collections
|
||||||
|
- Support pagination parameters: `page`, `limit`, `offset`
|
||||||
|
- Support language parameter: `lang` (for multi-language support)
|
||||||
|
- Use `_make_request()` helper for all HTTP calls
|
||||||
|
- Always validate responses with Pydantic models
|
||||||
|
|
||||||
|
### Data Models (Pydantic)
|
||||||
|
- All models inherit from `pydantic.BaseModel`
|
||||||
|
- Use `Field()` for validation and documentation
|
||||||
|
- Use `field_validator()` for custom validation
|
||||||
|
- Model names follow pattern: `Single{Resource}{Operation}RequestBody`, `{Resource}Response`, etc.
|
||||||
|
- Use `exclude_none=True` when serializing to avoid sending null values
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
- Use environment variables via `python-dotenv`
|
||||||
|
- Store sensitive data (API keys) in `.env` files
|
||||||
|
- Never commit `.env` files
|
||||||
|
- Provide `.env.example` as template
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
- Use Python's `logging` module
|
||||||
|
- Create loggers with `logger = logging.getLogger(__name__)`
|
||||||
|
- Log important operations and errors
|
||||||
|
- Avoid logging sensitive information (API keys, tokens)
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
- All tests in `tests/` directory
|
||||||
|
- Test file naming: `test_*.py`
|
||||||
|
- Test class naming: `Test*`
|
||||||
|
- Test function naming: `test_*`
|
||||||
|
|
||||||
|
### Coverage
|
||||||
|
- Aim for >80% code coverage
|
||||||
|
- Test both success and error paths
|
||||||
|
- Test Pydantic validation
|
||||||
|
- Use pytest fixtures for setup
|
||||||
|
|
||||||
|
### Mocking
|
||||||
|
- Use `unittest.mock` for mocking requests
|
||||||
|
- Mock external API calls
|
||||||
|
- Mock database calls if applicable
|
||||||
|
- Do not make real API calls in tests
|
||||||
|
|
||||||
|
### Example Test Pattern
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
def test_get_products(client):
|
||||||
|
"""Test successful product retrieval."""
|
||||||
|
with patch.object(client.session, 'request') as mock_request:
|
||||||
|
# Setup mock
|
||||||
|
mock_request.return_value = Mock(status_code=200, json=lambda: {...})
|
||||||
|
|
||||||
|
# Execute
|
||||||
|
result = client.get_products()
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Before Committing
|
||||||
|
- Run tests: `pytest tests/ -v`
|
||||||
|
- Check coverage: `pytest --cov=elytra_client`
|
||||||
|
- Format code: `black elytra_client tests`
|
||||||
|
- Check imports: `isort elytra_client tests`
|
||||||
|
- Lint code: `flake8 elytra_client tests`
|
||||||
|
- Type check: `mypy elytra_client`
|
||||||
|
|
||||||
|
### Creating New Features
|
||||||
|
1. Create a test first (TDD approach if possible)
|
||||||
|
2. Implement the feature
|
||||||
|
3. Ensure all tests pass
|
||||||
|
4. Run formatters and linters
|
||||||
|
5. Add documentation for public APIs
|
||||||
|
|
||||||
|
### API Changes
|
||||||
|
- Update `openapi.yaml` first (source of truth)
|
||||||
|
- Regenerate/update Pydantic models in `models.py`
|
||||||
|
- Update client methods accordingly
|
||||||
|
- Update tests
|
||||||
|
|
||||||
|
## Common Tasks and Expectations
|
||||||
|
|
||||||
|
### When Adding API Methods
|
||||||
|
1. Add method to `ElytraClient` class
|
||||||
|
2. Create corresponding Pydantic models for request/response
|
||||||
|
3. Use `_make_request()` helper
|
||||||
|
4. Add comprehensive docstrings
|
||||||
|
5. Include type hints
|
||||||
|
6. Error handling with custom exceptions
|
||||||
|
7. Add tests with mocked responses
|
||||||
|
|
||||||
|
### When Adding Pydantic Models
|
||||||
|
- Place in `models.py`
|
||||||
|
- Use descriptive names matching API structure
|
||||||
|
- Add field validators if needed
|
||||||
|
- Include docstrings for complex fields
|
||||||
|
- Use `ConfigDict` for model configuration if needed
|
||||||
|
|
||||||
|
### When Fixing Bugs
|
||||||
|
1. Add a failing test that reproduces the bug
|
||||||
|
2. Fix the bug
|
||||||
|
3. Verify the test now passes
|
||||||
|
4. Check for similar issues in codebase
|
||||||
|
|
||||||
|
### When Refactoring
|
||||||
|
1. Ensure all tests pass before starting
|
||||||
|
2. Make small, focused changes
|
||||||
|
3. Run tests frequently
|
||||||
|
4. Update documentation if interfaces change
|
||||||
|
5. Commit atomic, logical changes
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
- Reuse `requests.Session` for connection pooling
|
||||||
|
- Support pagination for large result sets
|
||||||
|
- Document timeout behavior
|
||||||
|
- Consider request/response sizes
|
||||||
|
- Log performance-critical operations
|
||||||
|
|
||||||
|
## Documentation Requirements
|
||||||
|
|
||||||
|
### README
|
||||||
|
- Keep up-to-date with current features
|
||||||
|
- Include installation and quick start
|
||||||
|
- Link to full API documentation
|
||||||
|
|
||||||
|
### Code Comments
|
||||||
|
- Explain "why" not "what" - code should be self-explanatory for "what"
|
||||||
|
- Avoid obvious comments
|
||||||
|
- Use for complex algorithms or non-obvious decisions
|
||||||
|
- Keep comments synchronized with code
|
||||||
|
|
||||||
|
### Docstrings
|
||||||
|
- Always provide docstrings for public APIs
|
||||||
|
- Include examples for complex usage patterns
|
||||||
|
- Multi-language support should be documented
|
||||||
|
|
||||||
|
## What NOT to Do
|
||||||
|
|
||||||
|
- ❌ Don't mix English with other languages in code
|
||||||
|
- ❌ Don't commit `.env` files or secrets
|
||||||
|
- ❌ Don't make real API calls in tests
|
||||||
|
- ❌ Don't skip type hints
|
||||||
|
- ❌ Don't ignore linting or formatting errors
|
||||||
|
- ❌ Don't commit code that doesn't pass tests
|
||||||
|
- ❌ Don't use bare `except:` clauses
|
||||||
|
- ❌ Don't modify response models without updating tests
|
||||||
|
- ❌ Don't log sensitive information
|
||||||
|
- ❌ Don't hardcode API keys or credentials
|
||||||
|
|
||||||
|
## Helpful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install development dependencies
|
||||||
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pytest tests/ -v
|
||||||
|
|
||||||
|
# Run with coverage
|
||||||
|
pytest --cov=elytra_client --cov-report=html
|
||||||
|
|
||||||
|
# Format code
|
||||||
|
black elytra_client tests
|
||||||
|
isort elytra_client tests
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
flake8 elytra_client tests
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
mypy elytra_client
|
||||||
|
|
||||||
|
# Run all checks (recommended before commit)
|
||||||
|
black elytra_client tests && isort elytra_client tests && flake8 elytra_client tests && mypy elytra_client && pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- **Pydantic v2 Docs**: https://docs.pydantic.dev/latest/
|
||||||
|
- **OpenAPI Specification**: See `openapi.yaml` in project root
|
||||||
|
- **Type Hints**: https://docs.python.org/3/library/typing.html
|
||||||
|
- **Pytest Documentation**: https://docs.pytest.org/
|
||||||
|
- **PEP 8 Style Guide**: https://www.python.org/dev/peps/pep-0008/
|
||||||
617
DEVELOPMENT.md
Normal file
617
DEVELOPMENT.md
Normal file
|
|
@ -0,0 +1,617 @@
|
||||||
|
# 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
|
||||||
590
STYLE_GUIDE.md
Normal file
590
STYLE_GUIDE.md
Normal file
|
|
@ -0,0 +1,590 @@
|
||||||
|
# Code Style Guide - Quick Reference
|
||||||
|
|
||||||
|
## Language: ENGLISH ONLY
|
||||||
|
|
||||||
|
All code, comments, docstrings, and variable names **MUST** be in English.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
### Line Length
|
||||||
|
- **Maximum: 100 characters**
|
||||||
|
- Configure editor ruler: `View → Toggle Rulers`
|
||||||
|
|
||||||
|
### Indentation
|
||||||
|
- **4 spaces** (no tabs)
|
||||||
|
- Auto-enforced by Black
|
||||||
|
|
||||||
|
### Blank Lines
|
||||||
|
- **2 blank lines** between module-level definitions
|
||||||
|
- **1 blank line** between class methods
|
||||||
|
- **1 blank line** inside functions for logical grouping
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Imports
|
||||||
|
|
||||||
|
### Order
|
||||||
|
```python
|
||||||
|
# 1. Standard library
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Dict, List, TypeVar, Type
|
||||||
|
|
||||||
|
# 2. Third-party
|
||||||
|
import requests
|
||||||
|
from pydantic import BaseModel, Field, ValidationError
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# 3. Local/relative
|
||||||
|
from .exceptions import ElytraAPIError
|
||||||
|
from .models import ProductResponse
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rules
|
||||||
|
- ✅ Use absolute imports
|
||||||
|
- ✅ Group by: stdlib → third-party → local
|
||||||
|
- ✅ Alphabetical within groups
|
||||||
|
- ❌ No `from module import *` (use explicit imports)
|
||||||
|
- ❌ No unused imports (checked by flake8)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type Hints
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
```python
|
||||||
|
# Good
|
||||||
|
def get_product(
|
||||||
|
product_id: str,
|
||||||
|
lang: str = "en"
|
||||||
|
) -> ProductResponse:
|
||||||
|
"""Get a product."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Good - with Optional
|
||||||
|
def find_product(
|
||||||
|
name: Optional[str] = None
|
||||||
|
) -> Optional[ProductResponse]:
|
||||||
|
"""Find product by name."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Good - with Union (Python 3.10+)
|
||||||
|
def process(data: dict | list) -> str:
|
||||||
|
"""Process data."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Bad - missing type hints
|
||||||
|
def get_products(product_id):
|
||||||
|
return response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
```python
|
||||||
|
# At module level
|
||||||
|
API_TIMEOUT: int = 30
|
||||||
|
DEFAULT_LANG: str = "en"
|
||||||
|
ALLOWED_LANGS: List[str] = ["en", "de", "fr"]
|
||||||
|
|
||||||
|
# In functions
|
||||||
|
active_products: List[Product] = []
|
||||||
|
product_map: Dict[str, Product] = {}
|
||||||
|
result: Optional[ProductResponse] = None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generic Types
|
||||||
|
```python
|
||||||
|
from typing import TypeVar, Generic, List
|
||||||
|
|
||||||
|
T = TypeVar('T', bound=BaseModel)
|
||||||
|
|
||||||
|
def validate_response(
|
||||||
|
data: dict,
|
||||||
|
model: Type[T]
|
||||||
|
) -> T:
|
||||||
|
"""Generic response validator."""
|
||||||
|
return model.model_validate(data)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docstrings (Google Style)
|
||||||
|
|
||||||
|
### Module Level
|
||||||
|
```python
|
||||||
|
"""Module description.
|
||||||
|
|
||||||
|
Longer explanation if needed.
|
||||||
|
|
||||||
|
This module handles product management operations including
|
||||||
|
creating, updating, and retrieving products from the API.
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
```python
|
||||||
|
class ElytraClient:
|
||||||
|
"""A Pythonic client for the Elytra PIM API.
|
||||||
|
|
||||||
|
This client provides convenient methods for interacting with
|
||||||
|
the Elytra PIM API, including authentication and product management.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: The base URL of the Elytra PIM API
|
||||||
|
api_key: The API key for authentication
|
||||||
|
timeout: Request timeout in seconds (default: 30)
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
session: Requests.Session instance for connection pooling
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functions/Methods
|
||||||
|
```python
|
||||||
|
def create_product(
|
||||||
|
self,
|
||||||
|
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Product name (required, max 255 chars)
|
||||||
|
description: Product description
|
||||||
|
lang: Language code (default: "en")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SingleProductResponse: The newly created product with ID
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ElytraValidationError: If validation fails
|
||||||
|
ElytraAuthenticationError: If authentication fails
|
||||||
|
ElytraAPIError: For other API errors
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> client = ElytraClient(base_url="...", api_key="...")
|
||||||
|
>>> product = client.create_product("Widget", "A useful widget")
|
||||||
|
>>> print(product.id)
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
```python
|
||||||
|
# UPPER_CASE_WITH_UNDERSCORES
|
||||||
|
API_TIMEOUT = 30
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
DEFAULT_LANGUAGE = "en"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
```python
|
||||||
|
# PascalCase
|
||||||
|
class ElytraClient:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ProductResponse:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InvalidProductError:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Functions & Methods
|
||||||
|
```python
|
||||||
|
# snake_case
|
||||||
|
def get_products():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_new_product():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_input():
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables
|
||||||
|
```python
|
||||||
|
# snake_case
|
||||||
|
product_id = "123"
|
||||||
|
api_key = "secret"
|
||||||
|
response_data = {}
|
||||||
|
is_valid = True
|
||||||
|
```
|
||||||
|
|
||||||
|
### Private Methods/Attributes
|
||||||
|
```python
|
||||||
|
# Leading underscore for private
|
||||||
|
def _make_request(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _validate_response(self, response):
|
||||||
|
pass
|
||||||
|
|
||||||
|
_cache = {}
|
||||||
|
_internal_state = None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constants vs Variables
|
||||||
|
```python
|
||||||
|
# Constants (module-level, unchanging)
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
API_VERSION = "v1"
|
||||||
|
|
||||||
|
# Variables (local, changeable)
|
||||||
|
timeout = config.get("timeout", DEFAULT_TIMEOUT)
|
||||||
|
request_id = str(uuid4())
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
```python
|
||||||
|
# f-strings (Python 3.6+) - PREFERRED
|
||||||
|
name = "Product"
|
||||||
|
print(f"Created product: {name}")
|
||||||
|
|
||||||
|
# Format method
|
||||||
|
print("Created product: {}".format(name))
|
||||||
|
|
||||||
|
# % formatting - AVOID
|
||||||
|
print("Created product: %s" % name)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quotes
|
||||||
|
```python
|
||||||
|
# Use double quotes as default
|
||||||
|
message = "This is a message"
|
||||||
|
docstring = """Multi-line docstring used with three
|
||||||
|
double quotes."""
|
||||||
|
|
||||||
|
# Use single quotes when string contains double quote
|
||||||
|
message = 'He said "Hello"'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
### Handling
|
||||||
|
```python
|
||||||
|
# Good - specific exception, with context
|
||||||
|
try:
|
||||||
|
response = self.session.request(method, url)
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.HTTPError as e:
|
||||||
|
if e.response.status_code == 404:
|
||||||
|
raise ElytraNotFoundError("Resource not found") from e
|
||||||
|
raise ElytraAPIError(f"HTTP Error: {e}") from e
|
||||||
|
except requests.RequestException as e:
|
||||||
|
raise ElytraAPIError(f"Network error: {str(e)}") from e
|
||||||
|
|
||||||
|
# Bad - too broad, no context
|
||||||
|
try:
|
||||||
|
response = requests.get(url)
|
||||||
|
except:
|
||||||
|
print("Error")
|
||||||
|
|
||||||
|
# Bad - bare raise without context
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
```
|
||||||
|
|
||||||
|
### Raising
|
||||||
|
```python
|
||||||
|
# Good - with meaningful message
|
||||||
|
raise ElytraValidationError(f"Invalid product ID: {product_id}")
|
||||||
|
|
||||||
|
# Good - with cause chain
|
||||||
|
try:
|
||||||
|
data = pydantic_model.model_validate(response)
|
||||||
|
except ValidationError as e:
|
||||||
|
raise ElytraValidationError(f"Response validation failed") from e
|
||||||
|
|
||||||
|
# Bad - generic message
|
||||||
|
raise ElytraAPIError("Error")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pydantic Models
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pydantic import BaseModel, Field, ConfigDict
|
||||||
|
|
||||||
|
class ProductResponse(BaseModel):
|
||||||
|
"""Response model for a single product.
|
||||||
|
|
||||||
|
Validates and represents a product object from the API.
|
||||||
|
"""
|
||||||
|
# Required field with description
|
||||||
|
id: str = Field(..., description="Unique product identifier")
|
||||||
|
name: str = Field(..., description="Product name", min_length=1, max_length=255)
|
||||||
|
|
||||||
|
# Optional field with default
|
||||||
|
description: Optional[str] = Field(None, description="Product description")
|
||||||
|
|
||||||
|
# Field with default value
|
||||||
|
language: str = Field("en", description="Language code")
|
||||||
|
|
||||||
|
# List field with default factory
|
||||||
|
tags: List[str] = Field(default_factory=list, description="Product tags")
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
model_config = ConfigDict(
|
||||||
|
str_strip_whitespace=True, # Automatically strip whitespace from strings
|
||||||
|
validate_default=True, # Validate default values
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comments
|
||||||
|
|
||||||
|
### Good Comments
|
||||||
|
```python
|
||||||
|
# Good - explains WHY, not WHAT
|
||||||
|
# We need to retry failed requests as the API has rate limiting
|
||||||
|
for retry in range(MAX_RETRIES):
|
||||||
|
try:
|
||||||
|
return self._make_request(...)
|
||||||
|
except RequestException:
|
||||||
|
if retry == MAX_RETRIES - 1:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Good - documents non-obvious code
|
||||||
|
# Using session instead of individual requests for connection pooling
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
# Good - TODO comments
|
||||||
|
# TODO: implement pagination for large datasets (issue #123)
|
||||||
|
def get_all_products(self):
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bad Comments
|
||||||
|
```python
|
||||||
|
# Bad - obvious, doesn't add value
|
||||||
|
# Increment counter
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
# Bad - outdated/wrong info
|
||||||
|
# This will be removed next release (said in 2020)
|
||||||
|
def deprecated_method(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# BAD - NEVER DO THIS
|
||||||
|
# código de prueba
|
||||||
|
# 这个是测试代码
|
||||||
|
# Ceci est un test
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Control Flow
|
||||||
|
|
||||||
|
### If Statements
|
||||||
|
```python
|
||||||
|
# Good - clear and readable
|
||||||
|
if user_is_authenticated and not is_rate_limited:
|
||||||
|
return process_request(data)
|
||||||
|
|
||||||
|
# Bad - too complex
|
||||||
|
if x and y or z and not w:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Good - use `in` for membership
|
||||||
|
if method in ("GET", "POST", "PUT"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Good - use `is` for None, True, False
|
||||||
|
if result is None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if is_valid is True: # or just: if is_valid:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loops
|
||||||
|
```python
|
||||||
|
# Good - enumerate for index and value
|
||||||
|
for idx, item in enumerate(items):
|
||||||
|
print(f"{idx}: {item}")
|
||||||
|
|
||||||
|
# Good - dict iteration
|
||||||
|
for key, value in config.items():
|
||||||
|
print(f"{key} = {value}")
|
||||||
|
|
||||||
|
# Avoid - unnecessary else clause
|
||||||
|
for item in items:
|
||||||
|
if match(item):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Only executes if loop completed without break
|
||||||
|
# Often confusing - better to use explicit flag
|
||||||
|
handle_not_found()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context Managers
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Good - automatic cleanup
|
||||||
|
with session.get(url) as response:
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# For custom classes
|
||||||
|
class ElytraClient:
|
||||||
|
def __enter__(self):
|
||||||
|
"""Enter context."""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""Exit context and cleanup."""
|
||||||
|
self.session.close()
|
||||||
|
return False # Don't suppress exceptions
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
with ElytraClient(base_url, api_key) as client:
|
||||||
|
products = client.get_products()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Whitespace and Blank Lines
|
||||||
|
|
||||||
|
### Module Structure
|
||||||
|
```python
|
||||||
|
"""Module docstring."""
|
||||||
|
|
||||||
|
# Imports (with blank line after)
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# Module constants (with blank line before and after)
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
API_VERSION = "v1"
|
||||||
|
|
||||||
|
# Classes (with 2 blank lines before)
|
||||||
|
|
||||||
|
|
||||||
|
class FirstClass:
|
||||||
|
"""Class docstring."""
|
||||||
|
|
||||||
|
def method_one(self):
|
||||||
|
"""First method."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def method_two(self):
|
||||||
|
"""Second method."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SecondClass:
|
||||||
|
"""Another class."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Module-level functions (with 2 blank lines before)
|
||||||
|
|
||||||
|
|
||||||
|
def module_function():
|
||||||
|
"""A module-level function."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Line Breaking
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Good - break long imports
|
||||||
|
from pydantic import (
|
||||||
|
BaseModel,
|
||||||
|
Field,
|
||||||
|
ValidationError,
|
||||||
|
ConfigDict,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Good - break long function signatures
|
||||||
|
def create_product(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
lang: str = "en",
|
||||||
|
) -> SingleProductResponse:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Good - break long lines with backslash (function calls)
|
||||||
|
result = self._make_request(
|
||||||
|
method="POST",
|
||||||
|
endpoint="/products",
|
||||||
|
json_data=product_data,
|
||||||
|
response_model=ProductResponse,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Good - break long conditionals
|
||||||
|
if (
|
||||||
|
user_is_authenticated and
|
||||||
|
not is_rate_limited and
|
||||||
|
has_valid_request_data
|
||||||
|
):
|
||||||
|
process_request()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linting and Formatting Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Format with Black
|
||||||
|
black elytra_client tests
|
||||||
|
|
||||||
|
# Sort imports with isort
|
||||||
|
isort elytra_client tests
|
||||||
|
|
||||||
|
# Lint with Flake8
|
||||||
|
flake8 elytra_client tests
|
||||||
|
|
||||||
|
# Type check with mypy
|
||||||
|
mypy elytra_client
|
||||||
|
|
||||||
|
# Run all (recommended before commit)
|
||||||
|
black elytra_client tests && \
|
||||||
|
isort elytra_client tests && \
|
||||||
|
flake8 elytra_client tests && \
|
||||||
|
mypy elytra_client && \
|
||||||
|
pytest tests/ -v
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary Checklist
|
||||||
|
|
||||||
|
Before committing code:
|
||||||
|
|
||||||
|
- [ ] All code is in **English**
|
||||||
|
- [ ] **No lines exceed 100 characters** (Black will enforce)
|
||||||
|
- [ ] **Type hints** on all functions
|
||||||
|
- [ ] **Docstrings** on all public APIs (Google style)
|
||||||
|
- [ ] **No unused imports** (Flake8 checks)
|
||||||
|
- [ ] **Proper exception handling** with meaningful messages
|
||||||
|
- [ ] **Tests** for new code
|
||||||
|
- [ ] Run Black and isort
|
||||||
|
- [ ] Run Flake8
|
||||||
|
- [ ] Run mypy
|
||||||
|
- [ ] Run pytest
|
||||||
Loading…
Add table
Add a link
Reference in a new issue