Skip to content

Commit bad5beb

Browse files
Cursor rules refinery gateway (#377)
* First draft cursor rules for refinery gateway * Submodules update * Database migrations rules * Database migrations rules * PR comments * PR comments * PR comments
1 parent 5399917 commit bad5beb

File tree

10 files changed

+628
-2
lines changed

10 files changed

+628
-2
lines changed

.cursor/rules/api-models.mdc

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
description: Rules for Pydantic models and request/response validation
3+
globs: ["fast_api/models.py"]
4+
alwaysApply: true
5+
---
6+
7+
# API Models Guidelines
8+
9+
Pydantic models validate request bodies and ensure type safety.
10+
11+
## Model Definition
12+
13+
**Basic structure:**
14+
```python
15+
from pydantic import BaseModel, StrictBool, StrictFloat, StrictInt, StrictStr
16+
from typing import Optional, List, Dict, Any
17+
18+
class CreateProjectBody(BaseModel):
19+
name: StrictStr
20+
description: Optional[StrictStr] = None
21+
organization_id: StrictStr
22+
23+
class CreateLabelsBody(BaseModel):
24+
labelingTaskId: StrictStr
25+
labels: List[StrictStr]
26+
27+
class SearchRecordsExtendedBody(BaseModel):
28+
filterData: Any = None
29+
offset: StrictInt
30+
limit: StrictInt
31+
```
32+
33+
## Naming Conventions
34+
35+
- Request bodies: `CreateProjectBody`, `UpdateProjectBody`, `DeleteRecordBody`
36+
- Use descriptive names ending in `Body`
37+
38+
## Usage in Routes
39+
40+
```python
41+
from fast_api.models import CreateProjectBody
42+
43+
@router.post("/create-project")
44+
def create_project(body: CreateProjectBody):
45+
project = manager.create_project(
46+
name=body.name,
47+
description=body.description,
48+
organization_id=body.organization_id,
49+
)
50+
return pack_json_result(sql_alchemy_to_dict(project))
51+
```
52+
53+
## Field Validation
54+
55+
```python
56+
from pydantic import field_validator, Field
57+
58+
class CreateProjectBody(BaseModel):
59+
name: StrictStr = Field(min_length=1, max_length=100)
60+
61+
@field_validator('name')
62+
@classmethod
63+
def validate_name(cls, v):
64+
if not v or not v.strip():
65+
raise ValueError('Project name cannot be empty')
66+
return v.strip()
67+
```
68+
69+
## Best Practices
70+
71+
1. Use strict types (`StrictStr`, `StrictInt`, etc.) for validation
72+
2. Provide defaults for optional fields
73+
3. Use descriptive model names ending in `Body`
74+
4. Validate inputs using validators or Field constraints
75+
5. Use proper type hints for all fields

.cursor/rules/controllers.mdc

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
description: Rules for controller modules and business logic
3+
globs: ["controller/**/*.py"]
4+
alwaysApply: true
5+
---
6+
7+
# Controllers Guidelines
8+
9+
Controllers contain business logic and orchestrate operations between routes, submodules, and external services.
10+
11+
## Import Patterns
12+
13+
```python
14+
# Submodules
15+
from submodules.model.business_objects import project, record
16+
from submodules.model import Project, enums
17+
from submodules.model.exceptions import EntityNotFoundException
18+
from submodules.s3 import controller as s3
19+
20+
# Other controllers
21+
from controller.auth import manager as auth_manager
22+
23+
# Utilities
24+
from util import notification
25+
from util.miscellaneous_functions import chunk_list
26+
```
27+
28+
## Function Patterns
29+
30+
**Get operations:**
31+
```python
32+
def get_project(project_id: str) -> Project:
33+
proj = project.get(project_id)
34+
if not proj:
35+
raise EntityNotFoundException(f"Project {project_id} not found")
36+
return proj
37+
```
38+
39+
**Create operations:**
40+
```python
41+
def create_project(name: str, organization_id: str, user_id: str, with_commit: bool = True) -> Project:
42+
if not name or not name.strip():
43+
raise ValueError("Project name cannot be empty")
44+
45+
org = organization.get(organization_id)
46+
if not org:
47+
raise EntityNotFoundException(f"Organization {organization_id} not found")
48+
49+
return project.create(name=name, organization_id=organization_id, created_by=user_id, with_commit=with_commit)
50+
```
51+
52+
**Update operations:**
53+
```python
54+
def update_project(project_id: str, changes: Dict[str, Any], with_commit: bool = True) -> Project:
55+
proj = project.get(project_id)
56+
if not proj:
57+
raise EntityNotFoundException(f"Project {project_id} not found")
58+
59+
for key, value in changes.items():
60+
setattr(proj, key, value)
61+
62+
if with_commit:
63+
general.commit()
64+
return proj
65+
```
66+
67+
## Business Logic Patterns
68+
69+
**Validation:**
70+
```python
71+
def create_attribute(project_id: str, name: str, data_type: str) -> Attribute:
72+
proj = project.get(project_id)
73+
if not proj:
74+
raise EntityNotFoundException(f"Project {project_id} not found")
75+
76+
if not name or not name.strip():
77+
raise ValueError("Attribute name cannot be empty")
78+
79+
existing = attribute.get_by_name(project_id, name)
80+
if existing:
81+
raise ValueError(f"Attribute '{name}' already exists")
82+
83+
return attribute.create(project_id=project_id, name=name, data_type=data_type, with_commit=True)
84+
```
85+
86+
**Transaction management:**
87+
```python
88+
def bulk_create_records(project_id: str, records_data: List[Dict[str, Any]], with_commit: bool = True):
89+
created_records = []
90+
for data in records_data:
91+
rec = record.create(project_id=project_id, data=data, with_commit=False)
92+
created_records.append(rec)
93+
if with_commit:
94+
general.commit()
95+
return created_records
96+
```
97+
98+
**Async operations:**
99+
```python
100+
from submodules.model import daemon
101+
102+
def run_weak_supervision(project_id: str, user_id: str) -> None:
103+
daemon.run_with_db_token(weak_supervision_service.initiate_weak_supervision, project_id, user_id)
104+
notification.create_notification(user_id=user_id, project_id=project_id, type=NotificationType.WEAK_SUPERVISION_STARTED)
105+
```
106+
107+
## Constants
108+
109+
```python
110+
ALL_PROJECTS_WHITELIST = {"id", "name", "description", "tokenizer", "status", "created_at", "created_by"}
111+
```
112+
113+
## Best Practices
114+
115+
1. Single responsibility per function
116+
2. Always validate inputs
117+
3. Use type hints for all parameters
118+
4. Use `with_commit` parameter appropriately
119+
5. Use submodule business objects, never SQLAlchemy directly
120+
6. Keep business logic in controllers, not routes

.cursor/rules/exceptions.mdc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
description: Rules for exception handling and custom exceptions
3+
globs: ["exceptions/**/*.py", "**/*.py"]
4+
alwaysApply: true
5+
---
6+
7+
# Exceptions Guidelines
8+
9+
## Exception Locations
10+
11+
**Gateway exceptions** (`exceptions/exceptions.py`):
12+
- `MissingArgumentsException`, `ProjectAccessError`, `ServiceRequestsError`
13+
- `DatabaseSessionError`, `AuthManagerError`, `EmbeddingConnectorError`
14+
15+
**Submodule exceptions**:
16+
```python
17+
from submodules.model.exceptions import EntityNotFoundException, EntityAlreadyExistsException
18+
```
19+
20+
## Usage Patterns
21+
22+
**Raising exceptions:**
23+
```python
24+
# Validation
25+
if not name:
26+
raise MissingArgumentsException("Project name is required")
27+
28+
# Not found
29+
proj = project.get(project_id)
30+
if not proj:
31+
raise EntityNotFoundException(f"Project {project_id} not found")
32+
33+
# Business logic
34+
if not has_access(user_id, project_id):
35+
raise ProjectAccessError(f"User {user_id} does not have access")
36+
```
37+
38+
**Handling in routes:**
39+
```python
40+
try:
41+
result = manager.operation(project_id)
42+
return pack_json_result(result)
43+
except EntityNotFoundException as e:
44+
return pack_json_result({"error": str(e)}, status_code=404)
45+
except ProjectAccessError as e:
46+
return pack_json_result({"error": str(e)}, status_code=403)
47+
except Exception as e:
48+
logger.error(f"Error: {e}", exc_info=True)
49+
return GENERIC_FAILURE_RESPONSE
50+
```
51+
52+
## HTTP Status Code Mapping
53+
54+
- `400`: `ValueError`, `MissingArgumentsException`
55+
- `403`: `ProjectAccessError`
56+
- `404`: `EntityNotFoundException`
57+
- `409`: `EntityAlreadyExistsException`
58+
- `500`: `ServiceRequestsError`, `DatabaseSessionError`
59+
60+
## Best Practices
61+
62+
1. Use specific exception types, not generic `Exception`
63+
2. Provide clear error messages with context
64+
3. Log exceptions before raising or handling
65+
4. Map exceptions to appropriate HTTP status codes
66+
5. Don't swallow exceptions silently

.cursor/rules/fastapi-routes.mdc

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
---
2+
description: Rules for FastAPI route definitions and HTTP handling
3+
globs: ["fast_api/routes/**/*.py"]
4+
alwaysApply: true
5+
---
6+
7+
# FastAPI Routes Guidelines
8+
9+
Routes handle HTTP request/response logic. Keep routes thin - validate input, call controllers, format responses.
10+
11+
## Route Structure
12+
13+
```python
14+
from fastapi import APIRouter, Depends, Request
15+
from controller.auth import manager as auth_manager
16+
from fast_api.routes.client_response import pack_json_result, get_silent_success
17+
18+
router = APIRouter()
19+
20+
@router.get(
21+
"/{project_id}/data",
22+
dependencies=[Depends(auth_manager.check_project_access_dep)],
23+
)
24+
def get_data(project_id: str):
25+
data = manager.get_data(project_id)
26+
return pack_json_result(data)
27+
```
28+
29+
## Authentication
30+
31+
```python
32+
# Project access
33+
dependencies=[Depends(auth_manager.check_project_access_dep)]
34+
35+
# Admin operations
36+
dependencies=[
37+
Depends(auth_manager.check_project_access_dep),
38+
Depends(auth_manager.check_group_auth),
39+
]
40+
41+
# User context
42+
org_id = auth_manager.get_organization_id_by_info(request.state.info)
43+
```
44+
45+
## Response Patterns
46+
47+
```python
48+
# With data
49+
return pack_json_result(data)
50+
51+
# No data
52+
return get_silent_success()
53+
54+
# Custom status
55+
return pack_json_result(None, status_code=204)
56+
57+
# Error handling
58+
from fast_api.routes.client_response import GENERIC_FAILURE_RESPONSE
59+
try:
60+
result = manager.operation()
61+
return pack_json_result(result)
62+
except EntityNotFoundException:
63+
return pack_json_result({"error": str(e)}, status_code=404)
64+
except Exception as e:
65+
logger.error(f"Error: {e}")
66+
return GENERIC_FAILURE_RESPONSE
67+
```
68+
69+
## Data Transformation
70+
71+
Always use whitelists when converting SQLAlchemy objects:
72+
73+
```python
74+
from submodules.model.util import sql_alchemy_to_dict
75+
76+
ATTRIBUTES_WHITELIST = {"id", "name", "data_type", "is_primary_key"}
77+
78+
@router.get("/{project_id}/all-attributes")
79+
def get_attributes(project_id: str):
80+
data = manager.get_all_attributes(project_id)
81+
data_dict = sql_alchemy_to_dict(data, column_whitelist=ATTRIBUTES_WHITELIST)
82+
return pack_json_result(data_dict)
83+
```
84+
85+
## Request Parameters
86+
87+
```python
88+
# Path parameters
89+
@router.get("/{project_id}/record/{record_id}")
90+
def get_record(project_id: str, record_id: str):
91+
pass
92+
93+
# Query parameters
94+
from fastapi import Query
95+
from typing import List, Union
96+
97+
@router.get("/{project_id}/all-attributes")
98+
def get_attributes(
99+
project_id: str,
100+
state_filter: Union[List[str], None] = Query(default=None),
101+
):
102+
pass
103+
104+
# Request body
105+
from fast_api.models import CreateProjectBody
106+
107+
@router.post("/create-project")
108+
def create_project(body: CreateProjectBody):
109+
pass
110+
```
111+
112+
## Best Practices
113+
114+
1. Keep routes thin - delegate to controllers
115+
2. Use dependencies for authentication
116+
3. Always use `pack_json_result()` or `get_silent_success()`
117+
4. Use whitelists with `sql_alchemy_to_dict()`
118+
5. Use type hints for all parameters
119+
6. Handle exceptions appropriately
120+
7. Use kebab-case for route paths: `/all-projects`, `/project-by-project-id`

0 commit comments

Comments
 (0)