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