Skip to content

Commit d7413ef

Browse files
committed
Setup copilot instructions
1 parent a7e7c13 commit d7413ef

2 files changed

Lines changed: 166 additions & 1 deletion

File tree

.github/copilot-instructions.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Copilot Instructions
2+
3+
## Project Overview
4+
5+
Python client library for the SoftwareONE Marketplace (MPT) API. Provides both synchronous and asynchronous interfaces across resource domains: accounts, billing, catalog, commerce, audit, and notifications.
6+
7+
Requires **Python 3.12+**. All development runs inside Docker via `docker compose`.
8+
9+
## Commands
10+
11+
All commands are Docker-based and run through `make`:
12+
13+
```bash
14+
make test # Run unit tests
15+
make test args="tests/unit/http/test_client.py::test_http_initialization" # Run a single test
16+
make check # Lint + type-check (ruff format, ruff check, flake8, mypy, uv lock)
17+
make format # Auto-format (ruff format + import sort)
18+
make check-all # check + test together
19+
make e2e # End-to-end tests (requires real API credentials)
20+
make bash # Open shell in container
21+
```
22+
23+
To run tests directly inside the container:
24+
```bash
25+
docker compose run --rm app pytest tests/unit/path/to/test_file.py::test_name
26+
```
27+
28+
## Architecture
29+
30+
### Entry Points
31+
32+
`MPTClient` (sync) and `AsyncMPTClient` (async) in `mpt_client.py` are the public API. They expose resource groups as properties (`client.catalog.products`, `client.commerce.orders`, etc.).
33+
34+
### Layered Structure
35+
36+
```
37+
MPTClient / AsyncMPTClient
38+
└── Resource group (e.g., Catalog, Commerce)
39+
└── Service (e.g., ProductsService) = Service base + mixins
40+
└── HTTPClient / AsyncHTTPClient (wraps httpx)
41+
```
42+
43+
### Resources
44+
45+
Each resource domain lives in `mpt_api_client/resources/<domain>/`. Every resource consists of:
46+
- A **Model** class (e.g., `Product`) extending `Model`
47+
- A **ServiceConfig** (e.g., `ProductsServiceConfig`) with `endpoint`, `model`, and key fields
48+
- A **sync Service** and an **async Service** built by composing mixins
49+
50+
### Mixins (`mpt_api_client/http/mixins/`)
51+
52+
Behavior is composed via mixins. Both sync and async versions exist for each:
53+
- CRUD: `CreateMixin`, `GetMixin`, `UpdateMixin`, `DeleteMixin`
54+
- Collections: `CollectionMixin` (iterate, paginate, filter, order_by, select)
55+
- Files: `CreateFileMixin`, `UpdateFileMixin`, `DownloadFileMixin`
56+
- Domain: `PublishableMixin`, `ActivatableMixin`, `EnableMixin`, `DisableMixin`, `TerminateMixin`, `RenderMixin`, `QueryableMixin`
57+
58+
When adding a new service, compose only the mixins it needs. Async services mirror sync ones with `Async` prefix and `async def` methods.
59+
60+
### Models (`mpt_api_client/models/`)
61+
62+
- Inherit from `Model` (or `BaseModel`)
63+
- Use type annotations; no explicit `__init__` needed
64+
- **Automatic camelCase ↔ snake_case conversion** — API uses camelCase, Python uses snake_case
65+
- Nested dicts/lists are auto-converted to model instances
66+
67+
### RQL Query Builder (`mpt_api_client/rql/`)
68+
69+
`RQLQuery` builds filter expressions for API calls. Used with `.filter()`, `.order_by()`, `.select()` on services.
70+
71+
## Key Conventions
72+
73+
### Adding a New Resource
74+
75+
1. Create `mpt_api_client/resources/<domain>/<resource>/` with:
76+
- `__init__.py` exporting the service classes
77+
- `models.py``Model` subclass
78+
- `services.py` — sync + async service classes (inherit `Service`/`AsyncService` + relevant mixins + `ServiceConfig`)
79+
2. Add a property for it on the resource group class
80+
3. Add corresponding unit tests under `tests/unit/resources/<domain>/<resource>/`
81+
82+
### Code Style
83+
84+
- Line length: **100 chars**
85+
- Quotes: **double quotes**
86+
- Docstrings: **Google style**
87+
- Strict `mypy` — all public functions need type annotations
88+
- Max McCabe complexity: **6** per function
89+
- Ruff rules enforced: D (docstrings), FBT (no bool positional args), S (security), TRY, PLR, etc.
90+
91+
### Error Handling
92+
93+
Raise `MPTHttpError` or `MPTAPIError` (from `mpt_api_client/exceptions.py`). Use `transform_http_status_exception()` to convert httpx HTTP errors into the MPT exception hierarchy. Don't catch and swallow errors silently.
94+
95+
### HTTP Client Initialization
96+
97+
Clients can be initialized from env vars:
98+
- `MPT_API_BASE_URL`
99+
- `MPT_API_TOKEN`
100+
101+
Or explicitly:
102+
```python
103+
client = MPTClient.from_config(api_token="...", base_url="https://...")
104+
```
105+
106+
## Test Patterns
107+
108+
### Mocking HTTP
109+
110+
Use `respx` to mock `httpx` calls:
111+
112+
```python
113+
import respx
114+
from httpx import Response
115+
116+
117+
@respx.mock
118+
def test_something(http_client):
119+
route = respx.get(f"{API_URL}/resource/123").mock(return_value=Response(200, json={...}))
120+
result = http_client.request("GET", "/resource/123")
121+
assert route.called
122+
```
123+
124+
### Async Tests
125+
126+
`pytest-asyncio` is configured with `asyncio_mode = "auto"` — just write `async def test_...()`.
127+
128+
### Fixtures
129+
130+
Common fixtures are in `tests/unit/conftest.py`:
131+
- `http_client``HTTPClient` instance
132+
- `async_http_client``AsyncHTTPClient` instance
133+
134+
### Test Location
135+
136+
Unit tests mirror the source tree: `tests/unit/resources/catalog/products/` for `mpt_api_client/resources/catalog/products/`.
137+
138+
### Native Commands (no Docker)
139+
140+
| Task | Command |
141+
|------|---------|
142+
| Run all unit tests | `uv run pytest tests/unit/` |
143+
| Run a single test | `uv run pytest tests/unit/path/to/test_file.py::test_name` |
144+
| Run tests with coverage | `uv run pytest tests/unit/ --cov=mpt_api_client --cov-report=term-missing` |
145+
| Format code | `uv run ruff format .` |
146+
| Fix lint issues | `uv run ruff check . --fix` |
147+
| Check formatting | `uv run ruff format --check .` |
148+
| Lint | `uv run ruff check .` |
149+
| Style check | `uv run flake8` |
150+
| Type check | `uv run mypy mpt_api_client` |
151+
| All quality checks | `uv run ruff format --check . && uv run ruff check . && uv run flake8 && uv run mypy mpt_api_client` |
152+
153+
### Verification Checklist
154+
155+
The agent **must** verify all of the following before submitting:
156+
157+
- [ ] All existing unit tests pass: `uv run pytest tests/unit/`
158+
- [ ] New unit tests written for every new function, mixin, model, or service
159+
- [ ] New tests follow AAA (Arrange–Act–Assert) pattern with no branching inside tests
160+
- [ ] Code formatting is clean: `uv run ruff format --check .`
161+
- [ ] Linting passes: `uv run ruff check . && uv run flake8`
162+
- [ ] Type checking passes: `uv run mypy mpt_api_client`
163+
- [ ] All public functions/methods/classes have Google-style docstrings
164+
- [ ] No hardcoded configuration values — all config via env vars or constructor args

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ RUN uv sync --frozen --no-cache --no-dev
1616
FROM build AS dev
1717

1818
RUN uv sync --frozen --no-cache --dev
19-
19+
RUN apt update && apt install -y curl
20+
RUN curl -fsSL https://gh.io/copilot-install | bash
2021
CMD ["bash"]
2122

2223
FROM build AS prod

0 commit comments

Comments
 (0)