Skip to content

Commit b3021bc

Browse files
authored
Merge branch 'main' into sidebar-toggle
2 parents d124d3e + a3e3979 commit b3021bc

File tree

38 files changed

+1261
-933
lines changed

38 files changed

+1261
-933
lines changed

.env.example

Lines changed: 0 additions & 2 deletions
This file was deleted.

.github/workflows/build-and-release.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,16 @@ jobs:
271271
matrix:
272272
include:
273273
- platform: "macos-latest"
274-
args: "--target aarch64-apple-darwin"
274+
# Enable CI feature so prod() spawns backend + sync in release builds
275+
args: "--target aarch64-apple-darwin --features ci"
275276
server-artifact: "PictoPy-MacOS"
276277
sync-artifact: "PictoPy-Sync-MacOS"
277278
- platform: "ubuntu-22.04"
278-
args: ""
279+
args: "--features ci"
279280
server-artifact: "PictoPy-Ubuntu"
280281
sync-artifact: "PictoPy-Sync-Ubuntu"
281282
- platform: "windows-latest"
282-
args: ""
283+
args: "--features ci"
283284
server-artifact: "PictoPy-Windows"
284285
sync-artifact: "PictoPy-Sync-Windows"
285286
runs-on: ${{ matrix.platform }}

.github/workflows/pr-check-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ jobs:
1212
matrix:
1313
include:
1414
- platform: "macos-latest"
15-
args: "--target aarch64-apple-darwin"
15+
args: "--target aarch64-apple-darwin --features ci"
1616
- platform: "ubuntu-22.04"
17-
args: ""
17+
args: "--features ci"
1818
- platform: "windows-latest"
19-
args: ""
19+
args: "--features ci"
2020
runs-on: ${{ matrix.platform }}
2121
steps:
2222
- uses: actions/checkout@v4

__pycache__/app.cpython-313.pyc

-591 Bytes
Binary file not shown.

app.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

backend/app/config/settings.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from platformdirs import user_data_dir
2+
import os
3+
14
# Model Exports Path
25
MODEL_EXPORTS_PATH = "app/models/ONNX_Exports"
36

@@ -20,6 +23,9 @@
2023

2124
TEST_INPUT_PATH = "tests/inputs"
2225
TEST_OUTPUT_PATH = "tests/outputs"
23-
DATABASE_PATH = "app/database/PictoPy.db"
24-
THUMBNAIL_IMAGES_PATH = "./images/thumbnails"
26+
if os.getenv("GITHUB_ACTIONS") == "true":
27+
DATABASE_PATH = os.path.join(os.getcwd(), "test_db.sqlite3")
28+
else:
29+
DATABASE_PATH = os.path.join(user_data_dir("PictoPy"), "database", "PictoPy.db")
30+
THUMBNAIL_IMAGES_PATH = os.path.join(user_data_dir("PictoPy"), "thumbnails")
2531
IMAGES_PATH = "./images"

backend/app/routes/shutdown.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import asyncio
2+
import os
3+
import platform
4+
import signal
5+
from fastapi import APIRouter
6+
from pydantic import BaseModel
7+
from app.logging.setup_logging import get_logger
8+
9+
logger = get_logger(__name__)
10+
11+
router = APIRouter(tags=["Shutdown"])
12+
13+
14+
class ShutdownResponse(BaseModel):
15+
"""Response model for shutdown endpoint."""
16+
17+
status: str
18+
message: str
19+
20+
21+
async def _delayed_shutdown(delay: float = 0.5):
22+
"""
23+
Shutdown the server after a short delay to allow the response to be sent.
24+
25+
Args:
26+
delay: Seconds to wait before signaling shutdown
27+
"""
28+
await asyncio.sleep(delay)
29+
logger.info("Backend shutdown initiated, exiting process...")
30+
31+
if platform.system() == "Windows":
32+
# Windows: SIGTERM doesn't work reliably with uvicorn subprocesses
33+
os._exit(0)
34+
else:
35+
# Unix (Linux/macOS): SIGTERM allows cleanup handlers to run
36+
os.kill(os.getpid(), signal.SIGTERM)
37+
38+
39+
@router.post("/shutdown", response_model=ShutdownResponse)
40+
async def shutdown():
41+
"""
42+
Gracefully shutdown the PictoPy backend.
43+
44+
This endpoint schedules backend server termination after response is sent.
45+
The frontend is responsible for shutting down the sync service separately.
46+
47+
Returns:
48+
ShutdownResponse with status and message
49+
"""
50+
logger.info("Shutdown request received for PictoPy backend")
51+
52+
# Define callback to handle potential exceptions in the background task
53+
def _handle_shutdown_exception(task: asyncio.Task):
54+
try:
55+
task.result()
56+
except Exception as e:
57+
logger.error(f"Shutdown task failed: {e}")
58+
59+
# Schedule backend shutdown after response is sent
60+
shutdown_task = asyncio.create_task(_delayed_shutdown())
61+
shutdown_task.add_done_callback(_handle_shutdown_exception)
62+
63+
return ShutdownResponse(
64+
status="shutting_down",
65+
message="PictoPy backend shutdown initiated.",
66+
)

backend/main.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import json
88

9+
from app.config.settings import DATABASE_PATH, THUMBNAIL_IMAGES_PATH
910
from uvicorn import Config, Server
1011
from fastapi import FastAPI
1112
from fastapi.middleware.cors import CORSMiddleware
@@ -25,6 +26,7 @@
2526
from app.routes.images import router as images_router
2627
from app.routes.face_clusters import router as face_clusters_router
2728
from app.routes.user_preferences import router as user_preferences_router
29+
from app.routes.shutdown import router as shutdown_router
2830
from fastapi.openapi.utils import get_openapi
2931
from app.logging.setup_logging import (
3032
configure_uvicorn_logging,
@@ -38,6 +40,11 @@
3840
# Configure Uvicorn logging to use our custom formatter
3941
configure_uvicorn_logging("backend")
4042

43+
path = os.path.dirname(DATABASE_PATH)
44+
os.makedirs(path, exist_ok=True)
45+
46+
os.makedirs(THUMBNAIL_IMAGES_PATH, exist_ok=True)
47+
4148

4249
@asynccontextmanager
4350
async def lifespan(app: FastAPI):
@@ -130,6 +137,7 @@ async def root():
130137
app.include_router(
131138
user_preferences_router, prefix="/user-preferences", tags=["User Preferences"]
132139
)
140+
app.include_router(shutdown_router, tags=["Shutdown"])
133141

134142

135143
# Entry point for running with: python3 main.py
@@ -138,7 +146,6 @@ async def root():
138146
logger = get_logger(__name__)
139147
logger.info("Starting PictoPy backend server...")
140148

141-
# Create a simple config with log_config=None to disable Uvicorn's default logging
142149
config = Config(
143150
app=app,
144151
host="localhost",

backend/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ ruff>=0.0.241
7171
psutil>=5.9.5
7272
pytest-asyncio>=1.0.0
7373
setuptools==66.1.1
74+
platformdirs==4.5.1

docs/backend/backend_python/openapi.json

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,14 +1117,9 @@
11171117
"in": "query",
11181118
"required": false,
11191119
"schema": {
1120-
"allOf": [
1121-
{
1122-
"$ref": "#/components/schemas/InputType"
1123-
}
1124-
],
1120+
"$ref": "#/components/schemas/InputType",
11251121
"description": "Choose input type: 'path' or 'base64'",
1126-
"default": "path",
1127-
"title": "Input Type"
1122+
"default": "path"
11281123
},
11291124
"description": "Choose input type: 'path' or 'base64'"
11301125
}
@@ -1237,7 +1232,7 @@
12371232
"content": {
12381233
"application/json": {
12391234
"schema": {
1240-
"$ref": "#/components/schemas/app__schemas__user_preferences__ErrorResponse"
1235+
"$ref": "#/components/schemas/ErrorResponse"
12411236
}
12421237
}
12431238
}
@@ -1277,7 +1272,7 @@
12771272
"content": {
12781273
"application/json": {
12791274
"schema": {
1280-
"$ref": "#/components/schemas/app__schemas__user_preferences__ErrorResponse"
1275+
"$ref": "#/components/schemas/ErrorResponse"
12811276
}
12821277
}
12831278
}
@@ -1287,7 +1282,7 @@
12871282
"content": {
12881283
"application/json": {
12891284
"schema": {
1290-
"$ref": "#/components/schemas/app__schemas__user_preferences__ErrorResponse"
1285+
"$ref": "#/components/schemas/ErrorResponse"
12911286
}
12921287
}
12931288
}
@@ -1304,6 +1299,29 @@
13041299
}
13051300
}
13061301
}
1302+
},
1303+
"/shutdown": {
1304+
"post": {
1305+
"tags": [
1306+
"Shutdown",
1307+
"Shutdown"
1308+
],
1309+
"summary": "Shutdown",
1310+
"description": "Gracefully shutdown the PictoPy backend.\n\nThis endpoint schedules backend server termination after response is sent.\nThe frontend is responsible for shutting down the sync service separately.\n\nReturns:\n ShutdownResponse with status and message",
1311+
"operationId": "shutdown_shutdown_post",
1312+
"responses": {
1313+
"200": {
1314+
"description": "Successful Response",
1315+
"content": {
1316+
"application/json": {
1317+
"schema": {
1318+
"$ref": "#/components/schemas/ShutdownResponse"
1319+
}
1320+
}
1321+
}
1322+
}
1323+
}
1324+
}
13071325
}
13081326
},
13091327
"components": {
@@ -1619,6 +1637,30 @@
16191637
],
16201638
"title": "DeleteFoldersResponse"
16211639
},
1640+
"ErrorResponse": {
1641+
"properties": {
1642+
"success": {
1643+
"type": "boolean",
1644+
"title": "Success"
1645+
},
1646+
"error": {
1647+
"type": "string",
1648+
"title": "Error"
1649+
},
1650+
"message": {
1651+
"type": "string",
1652+
"title": "Message"
1653+
}
1654+
},
1655+
"type": "object",
1656+
"required": [
1657+
"success",
1658+
"error",
1659+
"message"
1660+
],
1661+
"title": "ErrorResponse",
1662+
"description": "Error response model"
1663+
},
16221664
"FaceSearchRequest": {
16231665
"properties": {
16241666
"path": {
@@ -2204,6 +2246,7 @@
22042246
"metadata": {
22052247
"anyOf": [
22062248
{
2249+
"additionalProperties": true,
22072250
"type": "object"
22082251
},
22092252
{
@@ -2425,6 +2468,25 @@
24252468
],
24262469
"title": "RenameClusterResponse"
24272470
},
2471+
"ShutdownResponse": {
2472+
"properties": {
2473+
"status": {
2474+
"type": "string",
2475+
"title": "Status"
2476+
},
2477+
"message": {
2478+
"type": "string",
2479+
"title": "Message"
2480+
}
2481+
},
2482+
"type": "object",
2483+
"required": [
2484+
"status",
2485+
"message"
2486+
],
2487+
"title": "ShutdownResponse",
2488+
"description": "Response model for shutdown endpoint."
2489+
},
24282490
"SuccessResponse": {
24292491
"properties": {
24302492
"success": {
@@ -2897,30 +2959,6 @@
28972959
"error"
28982960
],
28992961
"title": "ErrorResponse"
2900-
},
2901-
"app__schemas__user_preferences__ErrorResponse": {
2902-
"properties": {
2903-
"success": {
2904-
"type": "boolean",
2905-
"title": "Success"
2906-
},
2907-
"error": {
2908-
"type": "string",
2909-
"title": "Error"
2910-
},
2911-
"message": {
2912-
"type": "string",
2913-
"title": "Message"
2914-
}
2915-
},
2916-
"type": "object",
2917-
"required": [
2918-
"success",
2919-
"error",
2920-
"message"
2921-
],
2922-
"title": "ErrorResponse",
2923-
"description": "Error response model"
29242962
}
29252963
}
29262964
}

0 commit comments

Comments
 (0)