|
| 1 | +# AI Agent Guidelines for MPT API Python Client |
| 2 | + |
| 3 | +This document provides guidelines for AI coding assistants working on the SoftwareOne Marketplace API Python Client. Following these guidelines ensures consistent, maintainable, and high-quality code contributions. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +The MPT API Python Client is a modern Python library that provides both synchronous and asynchronous interfaces for interacting with the SoftwareOne Marketplace Platform API. The project emphasizes: |
| 8 | + |
| 9 | +- **Type Safety**: Comprehensive type hints using modern Python typing features |
| 10 | +- **Async/Sync Duality**: Full support for both synchronous and asynchronous operations |
| 11 | +- **Code Quality**: Strict linting, formatting, and testing standards |
| 12 | +- **Developer Experience**: Clear APIs, comprehensive testing, and good documentation |
| 13 | + |
| 14 | +## Project Structure |
| 15 | + |
| 16 | +- Code: `mpt_api_client/` |
| 17 | + - `mpt_api_client/http/` - HTTP client to interact with HTTP endpoints |
| 18 | + - `mpt_api_client/models/` - Base models |
| 19 | + - `mpt_api_client/resources/` - Services that interact with the API resources |
| 20 | + - `mpt_api_client/rql/` - RQL query builder |
| 21 | + - `mpt_api_client/mpt_client.py` - Main entrypoint for the client class for sync and async operations |
| 22 | +- Unit tests: `tests/unit` |
| 23 | +- E2E tests: `tests/e2e` |
| 24 | +- Tests seeding: `tests/seed` |
| 25 | +- Configuration: `pyproject.toml` `.env` `setup.cfg` |
| 26 | +- Docker: `docker-compose.yml` `dev.Dockerfile` `prod.Dockerfile` |
| 27 | + |
| 28 | +## Build, test, and development |
| 29 | + |
| 30 | +- Linting and formatting: `ruff format` and `ruff check` |
| 31 | +- Type checking: `mypy` and `flake8` |
| 32 | +- Dependencies: `uv lock --check` |
| 33 | +- Testing: `pytest tests/unit` |
| 34 | + |
| 35 | +## Code Standards and Conventions |
| 36 | + |
| 37 | +### Python Version and Features |
| 38 | +- **Target Python Version**: 3.12+ |
| 39 | +- **Modern Python Features**: Use generics, type annotations, pattern matching, and other modern Python features |
| 40 | +- **Type Hints**: All functions, methods, and variables must have type annotations |
| 41 | +- **Strict Mode**: The project uses `mypy --strict` - ensure all code passes strict type checking |
| 42 | + |
| 43 | +### Code Style and Formatting |
| 44 | + |
| 45 | +- Follow PEP 8 and project-specific linting rules |
| 46 | +- Use meaningful variable and function names |
| 47 | +- Keep functions focused and single-purpose |
| 48 | +- Document complex logic with clear comments |
| 49 | +- Use docstrings for public APIs |
| 50 | + |
| 51 | +### Architecture Patterns |
| 52 | + |
| 53 | +#### Mixin-Based Design |
| 54 | +The project uses mixins to compose functionality: |
| 55 | + |
| 56 | +```python |
| 57 | +class ProductsService( |
| 58 | + CreateMixin["Product"], |
| 59 | + ReadMixin["Product"], |
| 60 | + UpdateMixin["Product"], |
| 61 | + DeleteMixin, |
| 62 | + BaseService["Product"], |
| 63 | +): |
| 64 | + """Synchronous Products service.""" |
| 65 | +``` |
| 66 | + |
| 67 | +**When adding new functionality:** |
| 68 | +1. Create appropriate mixins in `mpt_api_client/http/mixins.py` |
| 69 | +2. Apply mixins to both sync and async service classes |
| 70 | +3. Ensure type safety with proper generic type parameters |
| 71 | + |
| 72 | +#### Sync/Async Duality |
| 73 | +Every service must have both synchronous and asynchronous versions: |
| 74 | + |
| 75 | +```python |
| 76 | +# Synchronous service |
| 77 | +class ProductsService(BaseMixins, BaseService["Product"]): |
| 78 | + pass |
| 79 | + |
| 80 | +# Asynchronous service |
| 81 | +class AsyncProductsService(AsyncBaseMixins, AsyncBaseService["Product"]): |
| 82 | + pass |
| 83 | +``` |
| 84 | + |
| 85 | +**Key principles:** |
| 86 | +- Maintain API parity between sync and async versions |
| 87 | +- Use the same method signatures (except `async`/`await`) |
| 88 | +- Share test patterns and documentation |
| 89 | + |
| 90 | +### Testing Standards |
| 91 | + |
| 92 | +#### Test Structure |
| 93 | +- **Unit Tests**: Located in `tests/unit/` with parallel structure to source code |
| 94 | +- **Integration Tests**: Use `respx` for HTTP mocking |
| 95 | +- **Test Markers**: Use `pytest.mark.dataseed` for data seeding tests |
| 96 | +- **Async Testing**: Do not use `pytest.mark.asyncio` mark on async tests. |
| 97 | + |
| 98 | +#### Test Requirements |
| 99 | +1. **Both Sync and Async**: Every feature must have tests for both sync and async variants |
| 100 | +2. **Comprehensive Coverage**: Aim for 99% test coverage on new code |
| 101 | +3. **Mock External Calls**: Use `respx` to mock HTTP requests |
| 102 | +4. **Temporary Files**: Use `tmp_path` fixture for file operations |
| 103 | + |
| 104 | +Example test pattern: |
| 105 | +```python |
| 106 | +def test_feature_sync(service, tmp_path): |
| 107 | + """Test synchronous feature.""" |
| 108 | + with respx.mock: |
| 109 | + mock_route = respx.post("https://api.example.com/endpoint").mock( |
| 110 | + return_value=httpx.Response(200, json=expected_response) |
| 111 | + ) |
| 112 | + |
| 113 | + result = service.feature(data) |
| 114 | + |
| 115 | + |
| 116 | + assert mock_route.call_count == 1 |
| 117 | + assert result.to_dict() == expected_response |
| 118 | + |
| 119 | +async def test_feature_sync(service, tmp_path): |
| 120 | + """Test async feature.""" |
| 121 | + with respx.mock: |
| 122 | + mock_route = respx.post("https://api.example.com/endpoint").mock( |
| 123 | + return_value=httpx.Response(200, json=expected_response) |
| 124 | + ) |
| 125 | + |
| 126 | + result = await service.feature(data) |
| 127 | + |
| 128 | + |
| 129 | + assert mock_route.call_count == 1 |
| 130 | + assert result.to_dict() == expected_response |
| 131 | +``` |
| 132 | + |
| 133 | +### Error Handling |
| 134 | + |
| 135 | +- **Custom Exceptions**: Use exceptions from `mpt_api_client.exceptions` |
| 136 | +- **HTTP Errors**: Let the HTTP client handle HTTP status codes |
| 137 | +- **Type Safety**: Ensure exceptions are properly typed and documented |
| 138 | + |
| 139 | +### Documentation Standards |
| 140 | + |
| 141 | +#### Docstrings |
| 142 | +- **Style**: Google docstring format |
| 143 | +- **Coverage**: All public classes, methods, and functions must have docstrings |
| 144 | +- **Type Information**: Include parameter and return types in docstrings |
| 145 | +- **Examples**: Provide usage examples where helpful |
| 146 | + |
| 147 | +#### Code Comments |
| 148 | +- **Purpose**: Explain why, not what |
| 149 | +- **Complex Logic**: Document non-obvious business logic |
| 150 | +- **TODO Items**: Use `# TODO:` for future improvements |
| 151 | + |
| 152 | +## Common Patterns and Best Practices |
| 153 | + |
| 154 | +### Resource Services Pattern |
| 155 | +```python |
| 156 | +class ResourceService( |
| 157 | + CreateMixin["Resource"], |
| 158 | + ReadMixin["Resource"], |
| 159 | + UpdateMixin["Resource"], |
| 160 | + DeleteMixin, |
| 161 | + BaseService["Resource"], |
| 162 | +): |
| 163 | + """Service for managing resources.""" |
| 164 | + |
| 165 | + _model_class = Resource |
| 166 | + _path = "/endpoint/path" |
| 167 | + |
| 168 | + def custom_action(self, resource_id: str, data: dict) -> Resource: |
| 169 | + """Custom resource action.""" |
| 170 | + response = self._resource_do_request( |
| 171 | + resource_id, "POST", path_suffix="/action", json=data |
| 172 | + ) |
| 173 | + return self._model_class.from_response(response) |
| 174 | +``` |
| 175 | + |
| 176 | +### Model Definition Pattern |
| 177 | +```python |
| 178 | +from mpt_api_client.models import Model |
| 179 | + |
| 180 | +class Resource(Model): |
| 181 | + """Resource model.""" |
| 182 | + |
| 183 | + @property |
| 184 | + def computed_field(self) -> str: |
| 185 | + """Computed field based on other attributes.""" |
| 186 | + return f"{self.name}-{self.id}" |
| 187 | +``` |
| 188 | + |
| 189 | +### Type Safety Guidelines |
| 190 | +- Use generics for reusable components |
| 191 | +- Prefer `typing.Self` for methods returning instance type |
| 192 | +- Use union types (`|`) over `Union` for Python 3.10+ |
| 193 | +- Avoid `Any` type - use proper type constraints |
| 194 | + |
| 195 | +### Performance Considerations |
| 196 | +- Use async methods for I/O-bound operations |
| 197 | +- Implement proper streaming for large responses |
| 198 | +- Cache expensive computations appropriately |
| 199 | +- Use lazy loading patterns where beneficial |
| 200 | + |
| 201 | +## Testing Anti-Patterns to Avoid |
| 202 | + |
| 203 | +1. **Don't test implementation details** - test behavior, not internal structure |
| 204 | +2. **Don't use real HTTP calls** - always mock external dependencies |
| 205 | +3. **Don't ignore async variants** - every sync test needs an async equivalent |
| 206 | +4. **Don't skip error cases** - test both success and failure scenarios |
| 207 | +5. **Don't use hardcoded values** - use fixtures and parameterized tests |
| 208 | + |
| 209 | +## Security Considerations |
| 210 | + |
| 211 | +- **No secrets in code**: Use environment variables or secure vaults |
| 212 | +- **Input validation**: Validate all inputs, especially from external sources |
| 213 | +- **Path traversal**: Be careful with file operations and path handling |
| 214 | +- **Dependency scanning**: Regular updates and security scanning |
| 215 | + |
| 216 | +## Contributing Guidelines |
| 217 | + |
| 218 | +When adding new features: |
| 219 | + |
| 220 | +1. **Plan the API**: Design both sync and async interfaces |
| 221 | +2. **Implement mixins first**: Create reusable mixin components |
| 222 | +3. **Add comprehensive tests**: Unit tests for both sync and async |
| 223 | +4. **Update documentation**: Include docstrings and examples |
| 224 | +5. **Check type safety**: Ensure `mypy --strict` passes |
| 225 | +6. **Validate with linters**: Run `ruff check` and `ruff format` |
0 commit comments