Skip to content

Commit 69310e0

Browse files
committed
MPT-14767 E2E seeding proof of concept
- Added seeding proof of concept - Fix create product request Run: docker compose run --rm bash python seed/main.py
1 parent 419d0d5 commit 69310e0

39 files changed

Lines changed: 1757 additions & 22 deletions

.github/copilot-instructions.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# GitHub Copilot Instructions
2+
3+
## Project Context
4+
This is the MPT API Python Client for SoftwareOne Marketplace Platform.
5+
6+
## Primary Instructions Source
7+
**⚠️ IMPORTANT**: Always reference and follow the comprehensive guidelines in `/AGENTS.md` before writing any code or tests. This file contains detailed project-specific instructions, patterns, and best practices.
8+
9+
## Quick Reference
10+
- **Main instructions**: Read `/AGENTS.md` first and follow all guidelines
11+
- **Testing patterns**: Use function-based tests (not class-based) with comprehensive coverage
12+
- **Code style**: Type hints required, use mixins, implement both sync/async variants
13+
- **Architecture**: Follow service patterns with proper HTTP client integration
14+
15+
## File References
16+
- **🔑 Primary Instructions**: `/AGENTS.md` - Complete development guidelines
17+
- **Configuration**: `/pyproject.toml` - Build and dependency configuration
18+
- **Test examples**: `/tests/` - Established testing patterns
19+
20+
## Key Workflow
21+
1. **Read** `/AGENTS.md` for complete instructions
22+
2. **Analyze** existing code patterns in the relevant modules
23+
3. **Follow** established architectural patterns (mixins, services, models)
24+
4. **Implement** both sync and async variants
25+
5. **Test** with comprehensive coverage using function-based tests
26+
6. **Validate** with type checking and linting standards

AGENTS.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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`

mpt_api_client/http/async_service.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,20 @@ async def _resource_do_request( # noqa: WPS211
3131
) -> Response:
3232
"""Perform an action on a specific resource using.
3333
34-
Request with action: `HTTP_METHOD /endpoint/{resource_id}/{action}`.
35-
Request without action: `HTTP_METHOD /endpoint/{resource_id}`.
34+
:Loading parameter group
35+
Request with action: `HTTP_METHOD /endpoint/{resource_id}/{action}`.
36+
Request without action: `HTTP_METHOD /endpoint/{resource_id}`.
3637
3738
Args:
38-
resource_id: The resource ID to operate on.
39-
method: The HTTP method to use.
40-
action: The action name to use.
41-
json: The updated resource data.
42-
query_params: Additional query parameters.
43-
headers: Additional headers.
39+
resource_id: The resource ID to operate on.
40+
method: The HTTP method to use.
41+
action: The action name to use.
42+
json: The updated resource data.
43+
query_params: Additional query parameters.
44+
headers: Additional headers.
4445
4546
Raises:
46-
HTTPError: If the action fails.
47+
HTTPError: If the action fails.
4748
"""
4849
resource_url = urljoin(f"{self.path}/", resource_id)
4950
url = urljoin(f"{resource_url}/", action) if action else resource_url

mpt_api_client/http/client.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def __init__(
4949
base_headers = {
5050
"User-Agent": "swo-marketplace-client/1.0",
5151
"Authorization": f"Bearer {api_token}",
52-
"content-type": "application/json",
5352
}
5453
self.httpx_client = Client(
5554
base_url=base_url,

mpt_api_client/http/mixins.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ def create(
8686
_json_to_file_payload(resource_data),
8787
"application/json",
8888
)
89-
9089
response = self.http_client.request("post", self.path, files=files) # type: ignore[attr-defined]
9190

9291
return self._model_class.from_response(response) # type: ignore[attr-defined, no-any-return]

mpt_api_client/models/model.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def from_response(cls, response: Response) -> Self:
5252
meta = Meta.from_response(response)
5353
return cls.new(response_data, meta)
5454

55+
@property
56+
def id(self) -> str:
57+
"""Returns the resource ID."""
58+
return str(self._resource_data.get("id", "")) # type: ignore[no-untyped-call]
59+
5560
def to_dict(self) -> dict[str, Any]:
5661
"""Returns the resource as a dictionary."""
5762
return self._resource_data.to_dict()

0 commit comments

Comments
 (0)