Skip to content

Commit a6ec781

Browse files
Cursor files refinery-updater (#119)
* Cursor files refinery-updater * submodules merge
1 parent f8c8e63 commit a6ec781

File tree

10 files changed

+945
-2
lines changed

10 files changed

+945
-2
lines changed

.cursor/rules/api-models.mdc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
---
2+
description: Rules for Pydantic models and request/response validation
3+
globs: ["app.py"]
4+
alwaysApply: true
5+
---
6+
7+
# API Models Guidelines
8+
9+
Pydantic models validate request bodies and ensure type safety. Models are defined directly in `app.py` or imported when needed.
10+
11+
## Model Definition
12+
13+
**Basic structure:**
14+
```python
15+
from pydantic import BaseModel
16+
17+
class UpgradeToAWS(BaseModel):
18+
only_existing: bool
19+
remove_from_minio: bool
20+
force_overwrite: bool
21+
```
22+
23+
## Naming Conventions
24+
25+
- Use descriptive names that reflect the operation: `UpgradeToAWS`, `MigrationRequest`
26+
- No need for `Body` suffix in this service (simpler naming)
27+
28+
## Usage in Routes
29+
30+
```python
31+
from pydantic import BaseModel
32+
33+
class UpgradeToAWS(BaseModel):
34+
only_existing: bool
35+
remove_from_minio: bool
36+
force_overwrite: bool
37+
38+
@app.post("/migrate_minio_to_aws")
39+
def migrate_minio_to_aws(request: UpgradeToAWS) -> int:
40+
from upgrade_logic.pre_redesign.upgrade_from_minio_to_aws import upgrade
41+
upgrade(
42+
request.only_existing,
43+
request.remove_from_minio,
44+
request.force_overwrite
45+
)
46+
return 0
47+
```
48+
49+
## Field Types
50+
51+
**Basic types:**
52+
```python
53+
from pydantic import BaseModel
54+
from typing import Optional, List
55+
56+
class MigrationRequest(BaseModel):
57+
service_name: str
58+
target_version: str
59+
dry_run: bool = False # Default value
60+
options: Optional[List[str]] = None # Optional field
61+
```
62+
63+
## Field Validation
64+
65+
```python
66+
from pydantic import BaseModel, Field, field_validator
67+
68+
class UpgradeToAWS(BaseModel):
69+
only_existing: bool = Field(description="Only migrate existing organization buckets")
70+
remove_from_minio: bool = Field(description="Remove files from MinIO after migration")
71+
force_overwrite: bool = Field(default=False, description="Overwrite existing files")
72+
73+
@field_validator('only_existing')
74+
@classmethod
75+
def validate_only_existing(cls, v):
76+
if not isinstance(v, bool):
77+
raise ValueError('only_existing must be a boolean')
78+
return v
79+
```
80+
81+
## Best Practices
82+
83+
1. **Simple models**: Keep models simple and focused on the request data
84+
2. **Type hints**: Use proper type hints for all fields
85+
3. **Defaults**: Provide sensible defaults for optional fields
86+
4. **Descriptive names**: Use clear, descriptive model names
87+
5. **Validation**: Add validators when input validation is needed
88+
6. **Documentation**: Use `Field(description=...)` for API documentation
89+
7. **Location**: Define models in `app.py` near their usage, or create separate file if many models
90+
91+
## Example: Complete Model with Validation
92+
93+
```python
94+
from pydantic import BaseModel, Field, field_validator
95+
96+
class VersionUpdateRequest(BaseModel):
97+
service: str = Field(description="Service name to update")
98+
target_version: Optional[str] = Field(None, description="Target version (defaults to latest)")
99+
force: bool = Field(False, description="Force update even if already at version")
100+
101+
@field_validator('service')
102+
@classmethod
103+
def validate_service(cls, v):
104+
if not v or not v.strip():
105+
raise ValueError('Service name cannot be empty')
106+
return v.strip().upper()
107+
```

.cursor/rules/exceptions.mdc

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
description: Rules for exception handling and custom exceptions
3+
globs: ["**/*.py"]
4+
alwaysApply: true
5+
---
6+
7+
# Exceptions Guidelines
8+
9+
## Exception Locations
10+
11+
**Submodule exceptions:**
12+
```python
13+
from submodules.model.exceptions import EntityNotFoundException, EntityAlreadyExistsException
14+
```
15+
16+
**Standard Python exceptions:**
17+
- `ValueError`: Invalid input values
18+
- `Exception`: Generic exceptions (use sparingly)
19+
20+
## Usage Patterns
21+
22+
**Raising exceptions:**
23+
```python
24+
# Validation
25+
if not version:
26+
raise ValueError("Version is required")
27+
28+
# Version format validation
29+
def is_newer(v1: str, v2: str) -> bool:
30+
a = [__get_int_from_string(x) for x in v1.split(".")]
31+
b = [__get_int_from_string(x) for x in v2.split(".")]
32+
if len(a) != len(b) and len(a) != 3:
33+
raise Exception("invalid version format")
34+
return __is_newer_int(a, b)
35+
```
36+
37+
**Handling in routes:**
38+
```python
39+
@app.post("/endpoint")
40+
def endpoint():
41+
session_token = general.get_ctx_token()
42+
try:
43+
result = util.some_operation()
44+
return responses.JSONResponse(
45+
status_code=status.HTTP_200_OK,
46+
content=jsonable_encoder(result),
47+
)
48+
except ValueError as e:
49+
return responses.PlainTextResponse(
50+
status_code=status.HTTP_400_BAD_REQUEST,
51+
content=f"Invalid input: {str(e)}",
52+
)
53+
except Exception as e:
54+
print(f"Error: {e}", flush=True)
55+
return responses.PlainTextResponse(
56+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
57+
content=f"Error: {str(e)}",
58+
)
59+
finally:
60+
general.remove_and_refresh_session(session_token)
61+
```
62+
63+
**Handling in utility functions:**
64+
```python
65+
def __last_tag(repo_link: str) -> str:
66+
try:
67+
g = git.cmd.Git()
68+
blob = g.ls_remote(repo_link, sort="-v:refname", tags=True)
69+
if len(blob) == 0:
70+
return "0.0.0"
71+
tag = blob.split("\n")[0].split("/")[-1]
72+
if tag[0] == "v":
73+
return tag[1:]
74+
return tag
75+
except Exception:
76+
# Return safe default instead of raising
77+
return "0.0.0"
78+
```
79+
80+
**Handling in upgrade functions:**
81+
```python
82+
def gateway_1_14_0_add_cognition_strategy_complexity() -> bool:
83+
cognition_url = os.getenv("COGNITION_GATEWAY")
84+
if not cognition_url:
85+
print("No cognition gateway url found. Skipping...")
86+
return False # Return False instead of raising
87+
88+
try:
89+
response = requests.post(f"{cognition_url}/api/v1/strategies/internal/...")
90+
if response.status_code != 200:
91+
print(f"Failed to update. Status code: {response.status_code}")
92+
return False
93+
return True
94+
except Exception as e:
95+
print(f"Error calling external service: {e}", flush=True)
96+
return False
97+
```
98+
99+
## HTTP Status Code Mapping
100+
101+
- `400`: `ValueError` - Invalid input parameters
102+
- `500`: `Exception` - Unexpected errors
103+
104+
## Best Practices
105+
106+
1. **Return False for failures**: Upgrade functions should return `False` instead of raising exceptions
107+
2. **Safe defaults**: Return safe default values (e.g., `"0.0.0"` for version) when operations fail
108+
3. **Log errors**: Use `print()` with `flush=True` to log errors and important information
109+
4. **Handle external calls**: Always handle exceptions from external service calls gracefully
110+
5. **Validate inputs**: Use `ValueError` for invalid input validation
111+
6. **Don't swallow silently**: Log errors even when returning safe defaults
112+
7. **Session cleanup**: Always use `finally` blocks to ensure session cleanup even on exceptions

.cursor/rules/fastapi-routes.mdc

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
description: Rules for FastAPI route definitions and HTTP handling
3+
globs: ["app.py"]
4+
alwaysApply: true
5+
---
6+
7+
# FastAPI Routes Guidelines
8+
9+
Routes handle HTTP request/response logic. Keep routes thin - validate input, call utility functions, format responses.
10+
11+
## Route Structure
12+
13+
Routes are defined directly in `app.py`:
14+
15+
```python
16+
from fastapi import FastAPI, responses, status
17+
from fastapi.encoders import jsonable_encoder
18+
from submodules.model.business_objects import general
19+
import util
20+
21+
app = FastAPI(title="refinery-updater")
22+
23+
@app.post("/update_to_newest")
24+
def update_to_newest() -> responses.PlainTextResponse:
25+
session_token = general.get_ctx_token()
26+
if util.update_to_newest():
27+
msg = "updated to current version"
28+
else:
29+
msg = "nothing to update"
30+
general.remove_and_refresh_session(session_token)
31+
return responses.PlainTextResponse(status_code=status.HTTP_200_OK, content=msg)
32+
```
33+
34+
## Response Patterns
35+
36+
**Plain text responses:**
37+
```python
38+
@app.post("/update_to_newest")
39+
def update_to_newest() -> responses.PlainTextResponse:
40+
# ... logic ...
41+
return responses.PlainTextResponse(
42+
status_code=status.HTTP_200_OK,
43+
content="updated to current version"
44+
)
45+
```
46+
47+
**JSON responses:**
48+
```python
49+
@app.get("/version_overview")
50+
def version_overview() -> responses.JSONResponse:
51+
session_token = general.get_ctx_token()
52+
return_values = util.version_overview()
53+
general.remove_and_refresh_session(session_token)
54+
return responses.JSONResponse(
55+
status_code=status.HTTP_200_OK,
56+
content=jsonable_encoder(return_values),
57+
)
58+
```
59+
60+
**Conditional response types:**
61+
```python
62+
@app.get("/has_updates")
63+
def has_updates(as_html_response: bool = False) -> responses.JSONResponse:
64+
session_token = general.get_ctx_token()
65+
return_value = util.has_updates()
66+
general.remove_and_refresh_session(session_token)
67+
if as_html_response:
68+
return responses.HTMLResponse(content=str(return_value))
69+
return responses.JSONResponse(
70+
status_code=status.HTTP_200_OK,
71+
content=return_value,
72+
)
73+
```
74+
75+
## Session Management
76+
77+
**Always manage database sessions:**
78+
```python
79+
@app.post("/endpoint")
80+
def endpoint():
81+
session_token = general.get_ctx_token()
82+
try:
83+
# ... database operations ...
84+
return responses.JSONResponse(...)
85+
finally:
86+
general.remove_and_refresh_session(session_token)
87+
```
88+
89+
## Request Body Models
90+
91+
Use Pydantic models for request validation:
92+
93+
```python
94+
from pydantic import BaseModel
95+
96+
class UpgradeToAWS(BaseModel):
97+
only_existing: bool
98+
remove_from_minio: bool
99+
force_overwrite: bool
100+
101+
@app.post("/migrate_minio_to_aws")
102+
def migrate_minio_to_aws(request: UpgradeToAWS) -> int:
103+
from upgrade_logic.pre_redesign.upgrade_from_minio_to_aws import upgrade
104+
upgrade(request.only_existing, request.remove_from_minio, request.force_overwrite)
105+
return 0
106+
```
107+
108+
## Query Parameters
109+
110+
```python
111+
@app.get("/has_updates")
112+
def has_updates(as_html_response: bool = False) -> responses.JSONResponse:
113+
# Query parameter with default value
114+
pass
115+
```
116+
117+
## Health Check Pattern
118+
119+
```python
120+
@app.get("/healthcheck")
121+
def healthcheck() -> responses.PlainTextResponse:
122+
text = ""
123+
status_code = status.HTTP_200_OK
124+
database_test = general.test_database_connection()
125+
if not database_test.get("success"):
126+
error_name = database_test.get("error")
127+
text += f"database_error:{error_name}:"
128+
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
129+
if not text:
130+
text = "OK"
131+
return responses.PlainTextResponse(text, status_code=status_code)
132+
```
133+
134+
## Error Handling
135+
136+
Handle errors gracefully and return appropriate status codes:
137+
138+
```python
139+
@app.post("/endpoint")
140+
def endpoint():
141+
session_token = general.get_ctx_token()
142+
try:
143+
result = util.some_operation()
144+
return responses.JSONResponse(
145+
status_code=status.HTTP_200_OK,
146+
content=jsonable_encoder(result),
147+
)
148+
except Exception as e:
149+
print(f"Error: {e}", flush=True)
150+
return responses.PlainTextResponse(
151+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
152+
content=f"Error: {str(e)}",
153+
)
154+
finally:
155+
general.remove_and_refresh_session(session_token)
156+
```
157+
158+
## Best Practices
159+
160+
1. **Keep routes thin**: Delegate business logic to `util.py` or `upgrade_logic/`
161+
2. **Session management**: Always use `get_ctx_token()` and `remove_and_refresh_session()`
162+
3. **Type hints**: Use return type annotations (`-> responses.PlainTextResponse`)
163+
4. **Status codes**: Use appropriate HTTP status codes from `status` module
164+
5. **JSON encoding**: Use `jsonable_encoder()` for complex objects
165+
6. **Error handling**: Handle exceptions and return appropriate error responses
166+
7. **Route naming**: Use snake_case for route paths: `/update_to_newest`, `/version_overview`
167+
8. **Health checks**: Include health check endpoints that test critical dependencies

0 commit comments

Comments
 (0)