Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ poetry run pytest
`extraPaths` in `pyproject.toml`). Imports use bare module names:
`from database.database import init_db`.

## LLM Navigation Quick-Start

Fastest paths to understand and navigate the codebase:

- **All services at a glance:** Read `src/services/__init__.py` — re-exports
`CollectionService`, `EnvironmentService`, `ImportService`, and key
TypedDicts (`RequestLoadDict`, `VariableDetail`, `LocalOverride`).
- **HTTP subsystem:** Read `src/services/http/__init__.py` — re-exports
`HttpService`, `GraphQLSchemaService`, `SnippetGenerator`,
`HttpResponseDict`, `parse_header_dict`.
- **All DB models:** Read `src/database/database.py` — re-exports all four
ORM models (`CollectionModel`, `RequestModel`, `SavedResponseModel`,
`EnvironmentModel`).
- **Collection CRUD vs queries:** Mutations live in
`collection_repository.py`; read-only tree/breadcrumb/ancestor queries
live in `collection_query_repository.py`.
- **Signal flow:** Load the `signal-flow` skill for complete wiring diagrams.
- **TypedDicts:** Cross-module dict schemas live in the service that owns
them (e.g. `RequestLoadDict` in `collection_service.py`,
`HttpResponseDict` in `http_service.py`).
- **Test fixtures:** `make_collection_with_request` (root `conftest.py`) and
`make_request_dict` (`tests/ui/request/conftest.py`) reduce setup
boilerplate.

## Architecture

```
Expand All @@ -118,6 +142,7 @@ src/
│ ├── base.py # DeclarativeBase
│ ├── collections/
│ │ ├── collection_repository.py # CRUD for collections + requests
│ │ ├── collection_query_repository.py # Read-only tree/breadcrumb/ancestor queries
│ │ ├── import_repository.py # Atomic bulk-import of parsed data
│ │ └── model/
│ │ ├── collection_model.py # CollectionModel (folders)
Expand All @@ -134,7 +159,8 @@ src/
│ ├── http/ # HTTP request/response handling
│ │ ├── http_service.py # HttpService (httpx) + response TypedDicts
│ │ ├── graphql_schema_service.py # GraphQL introspection + schema parsing
│ │ └── snippet_generator.py # Code snippet generation (cURL/Python/JS)
│ │ ├── snippet_generator.py # Code snippet generation (cURL/Python/JS)
│ │ └── header_utils.py # Shared header parsing utility
│ └── import_parser/ # Parser sub-package
│ ├── models.py # TypedDict schemas for parsed data
│ ├── postman_parser.py # Postman collection/environment parser
Expand Down Expand Up @@ -251,6 +277,7 @@ tests/
│ ├── test_console_panel.py
│ └── test_history_panel.py
└── request/ # Request/response editing tests
├── conftest.py # make_request_dict fixture factory
├── test_folder_editor.py
├── test_http_worker.py
├── test_request_editor.py
Expand Down
4 changes: 3 additions & 1 deletion .github/instructions/architecture.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ them. Service validation errors (empty names, missing parents) are silently
discarded.

**If you add a new service method**, its errors will be invisible unless you
also add explicit UI feedback (e.g. a `QMessageBox`).
also add explicit UI feedback (e.g. a `QMessageBox`). For user-visible
errors, pair the service call with `QMessageBox.warning()` or emit a status
signal instead of relying on `_safe_svc_call`.

### 5. Sort ordering

Expand Down
14 changes: 14 additions & 0 deletions .github/instructions/sqlalchemy.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,20 @@ with get_session() as session:
All models inherit from `Base` defined in `src/database/models/base.py`.
Do not create a second base class.

## Detached-object decision checklist

After the `get_session()` block exits, the ORM object is *detached*.
Use this quick checklist to decide whether to return the object or a dict:

| What you need after session close | Safe? | Action |
|---|---|---|
| Scalar attributes only (`id`, `name`, `body`, ...) | Yes | Return the ORM object directly |
| One-level eager relation (`collection.requests`) | Yes | `selectin` loads it during the query |
| Two+ levels deep (`coll.children[0].children`) | **No** | Convert to dict inside session |
| Relationship on an object from a different query | **No** | Re-query or join-load explicitly |

When in doubt, convert to a dict inside the open session.

## Lightweight schema migration — forward-only column additions

`database.py` contains `_migrate_add_missing_columns(engine)`, called by
Expand Down
45 changes: 39 additions & 6 deletions .github/skills/service-repository-reference/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,42 @@ cross-layer data interchange.

## Repository function catalogue

### Collection repository (`collection_repository.py`)
### Collection repository — CRUD (`collection_repository.py`)

| Function | Returns | Purpose |
|----------|---------|---------|
| `fetch_all_collections()` | `dict[str, Any]` | All root collections as nested dict |
| `create_new_collection(name, parent_id?)` | `CollectionModel` | Create a folder |
| `rename_collection(collection_id, new_name)` | `None` | Update name |
| `delete_collection(collection_id)` | `None` | Delete + cascade children and requests |
| `get_collection_by_id(collection_id)` | `CollectionModel \| None` | PK lookup |
| `create_new_request(collection_id, method, url, name, ...)` | `RequestModel` | Create a request |
| `rename_request(request_id, new_name)` | `None` | Update name |
| `delete_request(request_id)` | `None` | Delete a single request |
| `update_request_collection(request_id, new_collection_id)` | `None` | Move request |
| `update_collection_parent(collection_id, new_parent_id)` | `None` | Move collection |
| `save_response(request_id, ...)` | `int` | Persist a response snapshot, return its ID |
| `update_collection(collection_id, **fields)` | `None` | Generic field update on a collection |
| `update_request(request_id, **fields)` | `None` | Generic field update on a request |

### Collection query repository (`collection_query_repository.py`)

| Function | Returns | Purpose |
|----------|---------|---------|
| `fetch_all_collections()` | `dict[str, Any]` | All root collections as nested dict |
| `get_collection_by_id(collection_id)` | `CollectionModel \| None` | PK lookup |
| `get_request_by_id(request_id)` | `RequestModel \| None` | PK lookup |
| `get_request_auth_chain(request_id)` | `dict[str, Any] \| None` | Walk parent chain for auth config |
| `get_request_variable_chain(request_id)` | `dict[str, str]` | Collect variables up the parent chain |
| `get_request_variable_chain_detailed(request_id)` | `dict[str, tuple[str, int]]` | Variables with source collection IDs |
| `get_request_breadcrumb(request_id)` | `list[dict[str, Any]]` | Ancestor path for breadcrumb bar |
| `get_collection_breadcrumb(collection_id)` | `list[dict[str, Any]]` | Ancestor path for collection breadcrumb |
| `get_saved_responses_for_request(request_id)` | `list[dict[str, Any]]` | Saved responses for a request |
| `save_response(request_id, ...)` | `int` | Persist a response snapshot, return its ID |
| `update_collection(collection_id, **fields)` | `None` | Generic field update on a collection |
| `update_request(request_id, **fields)` | `None` | Generic field update on a request |
| `count_collection_requests(collection_id)` | `int` | Total request count in folder subtree |
| `get_recent_requests_for_collection(collection_id, ...)` | `list[dict[str, Any]]` | Recently modified requests in subtree |

### Import repository (`import_repository.py`)

| Function | Returns | Purpose |
|----------|---------|---------|
| `import_collection_tree(parsed)` | `dict[str, int]` | Atomic bulk-import of a full collection tree |

### Environment repository (`environment_repository.py`)
Expand Down Expand Up @@ -146,6 +156,12 @@ All methods are `@staticmethod`.
| `available_languages()` | List of supported language names |
| `generate(language, method, url, headers, body)` | Dispatch to language-specific generator |

### Shared HTTP utilities (`services/http/header_utils.py`)

| Function | Returns | Purpose |
|----------|---------|---------|
| `parse_header_dict(raw)` | `dict[str, str]` | Parse `Key: Value\n` lines into a dict |

## TypedDict schemas

### HttpService TypedDicts (`services/http/http_service.py`)
Expand Down Expand Up @@ -185,6 +201,23 @@ class HttpResponseDict(TypedDict):
network: NotRequired[NetworkDict]
```

### CollectionService TypedDicts (`services/collection_service.py`)

```python
class RequestLoadDict(TypedDict, total=False):
name: str
method: str
url: str
body: str | None
request_parameters: str | list[dict[str, Any]] | None
headers: str | list[dict[str, Any]] | None
description: str | None
scripts: dict[str, str] | None
body_mode: str | None
body_options: dict[str, Any] | None
auth: dict[str, Any] | None
```

### EnvironmentService TypedDicts (`services/environment_service.py`)

```python
Expand Down
Loading