Adding migrations from alembic
Browse files- Dockerfile +12 -3
- alembic/versions/6936c1095473_adding_changes_stated_from_app_.py +26 -0
- orders/models.py +8 -1
- orders/routes.py +23 -5
- orders/schemas.py +11 -0
- services/face_match.py +43 -9
- services/facial_processing.py +17 -3
- services/recommendation_service.py +28 -35
- users/models.py +0 -1
- users/routes.py +12 -2
- users/schemas.py +0 -2
- users/services.py +0 -1
Dockerfile
CHANGED
@@ -1,6 +1,12 @@
|
|
1 |
# Use a lightweight Python image
|
2 |
FROM python:3.10.12
|
3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
# Create a non-root user
|
5 |
RUN useradd -m -u 1000 user
|
6 |
|
@@ -15,13 +21,16 @@ RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
|
15 |
COPY . .
|
16 |
|
17 |
# Create and set permissions for the cache directory
|
18 |
-
RUN mkdir /.cache
|
|
|
|
|
19 |
|
20 |
-
# Set up the environment variables
|
21 |
ENV ALEMBIC_CONFIG=alembic.ini
|
|
|
22 |
|
23 |
# Switch to the non-root user
|
24 |
USER user
|
25 |
|
26 |
# Run the application, including the migration step
|
27 |
-
CMD ["bash", "-c", "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 7860"]
|
|
|
1 |
# Use a lightweight Python image
|
2 |
FROM python:3.10.12
|
3 |
|
4 |
+
# Install system dependencies
|
5 |
+
RUN apt-get update && apt-get install -y \
|
6 |
+
libgl1-mesa-glx \
|
7 |
+
libglib2.0-0 \
|
8 |
+
&& rm -rf /var/lib/apt/lists/*
|
9 |
+
|
10 |
# Create a non-root user
|
11 |
RUN useradd -m -u 1000 user
|
12 |
|
|
|
21 |
COPY . .
|
22 |
|
23 |
# Create and set permissions for the cache directory
|
24 |
+
RUN mkdir -p /.cache /app/.cache && \
|
25 |
+
chown -R user:user /.cache /app/.cache && \
|
26 |
+
chmod -R u+w,go-w /.cache /app/.cache
|
27 |
|
28 |
+
# Set up the environment variables
|
29 |
ENV ALEMBIC_CONFIG=alembic.ini
|
30 |
+
ENV TORCH_HOME=/app/.cache/torch
|
31 |
|
32 |
# Switch to the non-root user
|
33 |
USER user
|
34 |
|
35 |
# Run the application, including the migration step
|
36 |
+
CMD ["bash", "-c", "alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 7860"]
|
alembic/versions/6936c1095473_adding_changes_stated_from_app_.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Adding Changes stated from APP developer side
|
2 |
+
|
3 |
+
Revision ID: 6936c1095473
|
4 |
+
Revises: b55dbab76bb4
|
5 |
+
Create Date: 2024-08-31 04:14:27.218586
|
6 |
+
|
7 |
+
"""
|
8 |
+
from typing import Sequence, Union
|
9 |
+
|
10 |
+
from alembic import op
|
11 |
+
import sqlalchemy as sa
|
12 |
+
|
13 |
+
|
14 |
+
# revision identifiers, used by Alembic.
|
15 |
+
revision: str = '6936c1095473'
|
16 |
+
down_revision: Union[str, None] = 'b55dbab76bb4'
|
17 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
18 |
+
depends_on: Union[str, Sequence[str], None] = None
|
19 |
+
|
20 |
+
|
21 |
+
def upgrade() -> None:
|
22 |
+
pass
|
23 |
+
|
24 |
+
|
25 |
+
def downgrade() -> None:
|
26 |
+
pass
|
orders/models.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
# models.py
|
2 |
-
from sqlalchemy import Column, ForeignKey, Integer, String, Float, DateTime
|
3 |
from sqlalchemy.orm import relationship
|
4 |
from core.database import Base
|
5 |
from datetime import datetime
|
@@ -26,3 +26,10 @@ class Order(Base):
|
|
26 |
|
27 |
user = relationship("User", back_populates="orders")
|
28 |
meal = relationship("Meal", back_populates="orders")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# models.py
|
2 |
+
from sqlalchemy import Column, ForeignKey, Integer, String, Float, DateTime, LargeBinary
|
3 |
from sqlalchemy.orm import relationship
|
4 |
from core.database import Base
|
5 |
from datetime import datetime
|
|
|
26 |
|
27 |
user = relationship("User", back_populates="orders")
|
28 |
meal = relationship("Meal", back_populates="orders")
|
29 |
+
|
30 |
+
class RecommendationModel(Base):
|
31 |
+
__tablename__ = "recommendation_models"
|
32 |
+
|
33 |
+
id = Column(Integer, primary_key=True, index=True)
|
34 |
+
model = Column(LargeBinary, nullable=False)
|
35 |
+
created_at = Column(DateTime, default=datetime.utcnow)
|
orders/routes.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1 |
-
from fastapi import APIRouter, status, Depends, HTTPException
|
|
|
2 |
from typing import List, Optional
|
3 |
from core.database import get_db
|
4 |
from core.security import get_current_user
|
5 |
from orders.services import create_meal, get_meals, get_meal, update_meal, delete_meal, create_user_order, get_user_orders
|
6 |
from orders.schemas import OrderCreate, OrderBase, Order, MealBase, MealCreate, MealUpdate, Meal
|
|
|
7 |
from sqlalchemy.orm import Session
|
8 |
from services.recommendation_service import MealRecommender
|
9 |
|
@@ -28,7 +30,12 @@ def health_check():
|
|
28 |
|
29 |
@meal_router.post("/", response_model=MealBase, status_code=status.HTTP_201_CREATED)
|
30 |
async def meal_create(data: MealCreate, db: Session = Depends(get_db)):
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
|
34 |
@meal_router.get("/", response_model=List[MealBase])
|
@@ -62,7 +69,11 @@ def meal_delete(meal_id: int, db: Session = Depends(get_db)):
|
|
62 |
|
63 |
@order_router.post("/", response_model=OrderBase, status_code=status.HTTP_201_CREATED)
|
64 |
def create_order(order: OrderCreate, current_user: OrderBase = Depends(get_current_user), db: Session = Depends(get_db)):
|
65 |
-
|
|
|
|
|
|
|
|
|
66 |
|
67 |
|
68 |
@order_router.get("/", response_model=List[OrderBase])
|
@@ -71,8 +82,15 @@ def get_orders(current_user: OrderBase = Depends(get_current_user), skip: int =
|
|
71 |
|
72 |
|
73 |
@meal_router.get("/recommendations/", response_model=List[MealBase])
|
74 |
-
async def get_recommendations(
|
|
|
|
|
|
|
|
|
75 |
recommender = MealRecommender(db)
|
76 |
recommendations = recommender.get_recommendations(current_user)
|
|
|
|
|
|
|
|
|
77 |
return recommendations
|
78 |
-
|
|
|
1 |
+
from fastapi import APIRouter, status, Depends, BackgroundTasks, HTTPException
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
from typing import List, Optional
|
4 |
from core.database import get_db
|
5 |
from core.security import get_current_user
|
6 |
from orders.services import create_meal, get_meals, get_meal, update_meal, delete_meal, create_user_order, get_user_orders
|
7 |
from orders.schemas import OrderCreate, OrderBase, Order, MealBase, MealCreate, MealUpdate, Meal
|
8 |
+
from users.schemas import User
|
9 |
from sqlalchemy.orm import Session
|
10 |
from services.recommendation_service import MealRecommender
|
11 |
|
|
|
30 |
|
31 |
@meal_router.post("/", response_model=MealBase, status_code=status.HTTP_201_CREATED)
|
32 |
async def meal_create(data: MealCreate, db: Session = Depends(get_db)):
|
33 |
+
new_meal = await create_meal(db, data)
|
34 |
+
id, name = new_meal.id, new_meal.name
|
35 |
+
return JSONResponse(
|
36 |
+
content={"id": id, "name": name},
|
37 |
+
status_code=status.HTTP_200_OK
|
38 |
+
)
|
39 |
|
40 |
|
41 |
@meal_router.get("/", response_model=List[MealBase])
|
|
|
69 |
|
70 |
@order_router.post("/", response_model=OrderBase, status_code=status.HTTP_201_CREATED)
|
71 |
def create_order(order: OrderCreate, current_user: OrderBase = Depends(get_current_user), db: Session = Depends(get_db)):
|
72 |
+
new_order = create_user_order(db, order, current_user.id)
|
73 |
+
return JSONResponse(
|
74 |
+
content={"id": new_order.id, "meal_id": new_order.meal_id, "quantity": new_order.quantity},
|
75 |
+
status_code=status.HTTP_200_OK
|
76 |
+
)
|
77 |
|
78 |
|
79 |
@order_router.get("/", response_model=List[OrderBase])
|
|
|
82 |
|
83 |
|
84 |
@meal_router.get("/recommendations/", response_model=List[MealBase])
|
85 |
+
async def get_recommendations(
|
86 |
+
background_tasks: BackgroundTasks,
|
87 |
+
current_user: User = Depends(get_current_user),
|
88 |
+
db: Session = Depends(get_db)
|
89 |
+
):
|
90 |
recommender = MealRecommender(db)
|
91 |
recommendations = recommender.get_recommendations(current_user)
|
92 |
+
|
93 |
+
# Schedule model retraining in the background
|
94 |
+
background_tasks.add_task(recommender.train_model)
|
95 |
+
|
96 |
return recommendations
|
|
orders/schemas.py
CHANGED
@@ -30,3 +30,14 @@ class Order(OrderBase):
|
|
30 |
|
31 |
class Config:
|
32 |
orm_mode = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
class Config:
|
32 |
orm_mode = True
|
33 |
+
|
34 |
+
|
35 |
+
class Recommendation(BaseModel):
|
36 |
+
meal_id: int
|
37 |
+
name: str
|
38 |
+
description: str
|
39 |
+
price: float
|
40 |
+
score: float
|
41 |
+
|
42 |
+
class Config:
|
43 |
+
orm_mode = True
|
services/face_match.py
CHANGED
@@ -3,6 +3,9 @@ import numpy as np
|
|
3 |
from sqlalchemy.orm import Session
|
4 |
from users.models import UserEmbeddings
|
5 |
from core.config import get_settings
|
|
|
|
|
|
|
6 |
|
7 |
settings = get_settings()
|
8 |
|
@@ -10,28 +13,59 @@ class FaceMatch:
|
|
10 |
def __init__(self, db: Session):
|
11 |
self.db = db
|
12 |
self.threshold = settings.FACE_RECOGNITION_THRESHOLD
|
|
|
|
|
13 |
|
14 |
def load_embeddings_from_db(self):
|
15 |
user_embeddings = self.db.query(UserEmbeddings).all()
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
def match_faces(self, new_embeddings, saved_embeddings):
|
|
|
|
|
|
|
19 |
new_embeddings = np.array(new_embeddings)
|
20 |
-
|
21 |
-
identity = None
|
22 |
|
23 |
for user_id, stored_embeddings in saved_embeddings.items():
|
24 |
similarity = cosine_similarity(new_embeddings.reshape(1, -1), stored_embeddings.reshape(1, -1))[0][0]
|
25 |
-
|
26 |
-
max_similarity = similarity
|
27 |
-
identity = user_id
|
28 |
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
def new_face_matching(self, new_embeddings):
|
32 |
embeddings_dict = self.load_embeddings_from_db()
|
33 |
if not embeddings_dict:
|
34 |
-
return {'status': 'Error', 'message': 'No embeddings available in the database'}
|
|
|
|
|
|
|
35 |
|
36 |
identity, similarity = self.match_faces(new_embeddings, embeddings_dict)
|
37 |
if identity:
|
@@ -43,5 +77,5 @@ class FaceMatch:
|
|
43 |
}
|
44 |
return {
|
45 |
'status': 'Error',
|
46 |
-
'message': 'No matching face found'
|
47 |
}
|
|
|
3 |
from sqlalchemy.orm import Session
|
4 |
from users.models import UserEmbeddings
|
5 |
from core.config import get_settings
|
6 |
+
import logging
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
|
10 |
settings = get_settings()
|
11 |
|
|
|
13 |
def __init__(self, db: Session):
|
14 |
self.db = db
|
15 |
self.threshold = settings.FACE_RECOGNITION_THRESHOLD
|
16 |
+
self.max_matches = 1 # Only allow one match
|
17 |
+
self.embedding_shape = None # Will be set when loading embeddings
|
18 |
|
19 |
def load_embeddings_from_db(self):
|
20 |
user_embeddings = self.db.query(UserEmbeddings).all()
|
21 |
+
embeddings_dict = {}
|
22 |
+
for ue in user_embeddings:
|
23 |
+
embedding = np.array(ue.embeddings)
|
24 |
+
if self.embedding_shape is None:
|
25 |
+
self.embedding_shape = embedding.shape
|
26 |
+
elif embedding.shape != self.embedding_shape:
|
27 |
+
logger.warning(f"Inconsistent embedding shape for user {ue.user_id}. Expected {self.embedding_shape}, got {embedding.shape}")
|
28 |
+
continue # Skip this embedding
|
29 |
+
embeddings_dict[ue.user_id] = embedding
|
30 |
+
return embeddings_dict
|
31 |
+
|
32 |
+
def validate_embedding(self, embedding):
|
33 |
+
if self.embedding_shape is None:
|
34 |
+
logger.warning("No reference embedding shape available")
|
35 |
+
return False
|
36 |
+
if np.array(embedding).shape != self.embedding_shape:
|
37 |
+
logger.warning(f"Invalid embedding shape. Expected {self.embedding_shape}, got {np.array(embedding).shape}")
|
38 |
+
return False
|
39 |
+
return True
|
40 |
|
41 |
def match_faces(self, new_embeddings, saved_embeddings):
|
42 |
+
if not self.validate_embedding(new_embeddings):
|
43 |
+
return None, 0
|
44 |
+
|
45 |
new_embeddings = np.array(new_embeddings)
|
46 |
+
similarities = []
|
|
|
47 |
|
48 |
for user_id, stored_embeddings in saved_embeddings.items():
|
49 |
similarity = cosine_similarity(new_embeddings.reshape(1, -1), stored_embeddings.reshape(1, -1))[0][0]
|
50 |
+
similarities.append((user_id, similarity))
|
|
|
|
|
51 |
|
52 |
+
# Sort similarities in descending order
|
53 |
+
similarities.sort(key=lambda x: x[1], reverse=True)
|
54 |
+
|
55 |
+
# Check if the top match exceeds the threshold and if there's a significant gap to the second-best match
|
56 |
+
if similarities and similarities[0][1] > self.threshold:
|
57 |
+
if len(similarities) == 1 or similarities[0][1] - similarities[1][1] > 0.1:
|
58 |
+
return similarities[0]
|
59 |
+
|
60 |
+
return None, 0
|
61 |
|
62 |
def new_face_matching(self, new_embeddings):
|
63 |
embeddings_dict = self.load_embeddings_from_db()
|
64 |
if not embeddings_dict:
|
65 |
+
return {'status': 'Error', 'message': 'No valid embeddings available in the database'}
|
66 |
+
|
67 |
+
if not self.validate_embedding(new_embeddings):
|
68 |
+
return {'status': 'Error', 'message': 'Invalid embedding shape'}
|
69 |
|
70 |
identity, similarity = self.match_faces(new_embeddings, embeddings_dict)
|
71 |
if identity:
|
|
|
77 |
}
|
78 |
return {
|
79 |
'status': 'Error',
|
80 |
+
'message': 'No matching face found or multiple potential matches detected'
|
81 |
}
|
services/facial_processing.py
CHANGED
@@ -22,12 +22,16 @@ class FacialProcessing:
|
|
22 |
# Detect faces
|
23 |
boxes, _ = self.mtcnn.detect(img)
|
24 |
|
25 |
-
if boxes is None:
|
26 |
logger.warning(f"No face detected in image: {image_path}")
|
27 |
return None
|
28 |
|
|
|
|
|
|
|
|
|
29 |
# Get the largest face
|
30 |
-
largest_box =
|
31 |
face = self.mtcnn(img, return_prob=False)
|
32 |
|
33 |
if face is None:
|
@@ -35,9 +39,19 @@ class FacialProcessing:
|
|
35 |
return None
|
36 |
|
37 |
# Extract embeddings
|
38 |
-
|
|
|
39 |
return embeddings.tolist()
|
40 |
|
41 |
except Exception as e:
|
42 |
logger.error(f"An error occurred while extracting embeddings: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
return None
|
|
|
22 |
# Detect faces
|
23 |
boxes, _ = self.mtcnn.detect(img)
|
24 |
|
25 |
+
if boxes is None or len(boxes) == 0:
|
26 |
logger.warning(f"No face detected in image: {image_path}")
|
27 |
return None
|
28 |
|
29 |
+
if len(boxes) > 1:
|
30 |
+
logger.warning(f"Multiple faces detected in image: {image_path}")
|
31 |
+
return None
|
32 |
+
|
33 |
# Get the largest face
|
34 |
+
largest_box = boxes[0]
|
35 |
face = self.mtcnn(img, return_prob=False)
|
36 |
|
37 |
if face is None:
|
|
|
39 |
return None
|
40 |
|
41 |
# Extract embeddings
|
42 |
+
with torch.no_grad():
|
43 |
+
embeddings = self.resnet(face).cpu().numpy().flatten()
|
44 |
return embeddings.tolist()
|
45 |
|
46 |
except Exception as e:
|
47 |
logger.error(f"An error occurred while extracting embeddings: {e}")
|
48 |
+
return None
|
49 |
+
|
50 |
+
def preprocess_image(self, image_path):
|
51 |
+
try:
|
52 |
+
img = Image.open(image_path)
|
53 |
+
img = img.convert('RGB')
|
54 |
+
return img
|
55 |
+
except Exception as e:
|
56 |
+
logger.error(f"Error opening image: {e}")
|
57 |
return None
|
services/recommendation_service.py
CHANGED
@@ -1,70 +1,63 @@
|
|
1 |
-
# recommender_system.py
|
2 |
import pandas as pd
|
3 |
from surprise import Dataset, Reader, SVD
|
4 |
from surprise.model_selection import train_test_split
|
5 |
-
import joblib
|
6 |
from datetime import datetime, timedelta
|
7 |
-
import
|
8 |
import random
|
9 |
from sqlalchemy.orm import Session
|
|
|
10 |
from typing import List
|
11 |
-
from orders.models import Order, Meal
|
12 |
from users.models import User
|
13 |
|
14 |
-
|
15 |
class MealRecommender:
|
16 |
def __init__(self, db: Session):
|
17 |
self.db = db
|
18 |
-
self.model_path = 'recommendation_model.joblib'
|
19 |
-
self.last_train_path = 'last_train_time.txt'
|
20 |
self.retrain_interval = timedelta(days=1)
|
21 |
self.algo = self.load_or_train_model()
|
22 |
|
23 |
def fetch_data(self):
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
def train_model(self):
|
29 |
data = self.fetch_data()
|
30 |
if data.empty:
|
31 |
-
self.algo = None
|
32 |
return None
|
33 |
-
|
34 |
reader = Reader(rating_scale=(1, 5))
|
35 |
dataset = Dataset.load_from_df(data[['user_id', 'meal_id', 'quantity']], reader)
|
36 |
-
|
37 |
trainset = dataset.build_full_trainset()
|
38 |
algo = SVD()
|
39 |
algo.fit(trainset)
|
40 |
-
|
41 |
-
|
42 |
-
|
|
|
|
|
|
|
|
|
43 |
return algo
|
44 |
|
45 |
def load_or_train_model(self):
|
46 |
-
|
47 |
-
if self._should_retrain():
|
48 |
-
return self.train_model()
|
49 |
-
return joblib.load(self.model_path)
|
50 |
-
except FileNotFoundError:
|
51 |
-
return self.train_model()
|
52 |
-
|
53 |
-
def _should_retrain(self):
|
54 |
-
if not os.path.exists(self.last_train_path):
|
55 |
-
return True
|
56 |
-
with open(self.last_train_path, 'r') as f:
|
57 |
-
last_train_time = datetime.fromisoformat(f.read().strip())
|
58 |
-
return datetime.now() - last_train_time > self.retrain_interval
|
59 |
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
63 |
|
64 |
def get_recommendations(self, user: User):
|
65 |
-
if self._should_retrain():
|
66 |
-
self.algo = self.train_model()
|
67 |
-
|
68 |
if self.algo is None:
|
69 |
return self.get_random_recommendations()
|
70 |
|
|
|
|
|
1 |
import pandas as pd
|
2 |
from surprise import Dataset, Reader, SVD
|
3 |
from surprise.model_selection import train_test_split
|
|
|
4 |
from datetime import datetime, timedelta
|
5 |
+
import pickle
|
6 |
import random
|
7 |
from sqlalchemy.orm import Session
|
8 |
+
from sqlalchemy import func
|
9 |
from typing import List
|
10 |
+
from orders.models import Order, Meal, RecommendationModel
|
11 |
from users.models import User
|
12 |
|
|
|
13 |
class MealRecommender:
|
14 |
def __init__(self, db: Session):
|
15 |
self.db = db
|
|
|
|
|
16 |
self.retrain_interval = timedelta(days=1)
|
17 |
self.algo = self.load_or_train_model()
|
18 |
|
19 |
def fetch_data(self):
|
20 |
+
# Fetch data in batches to handle large datasets
|
21 |
+
batch_size = 1000
|
22 |
+
offset = 0
|
23 |
+
data = []
|
24 |
+
while True:
|
25 |
+
batch = self.db.query(Order.user_id, Order.meal_id, Order.quantity).offset(offset).limit(batch_size).all()
|
26 |
+
if not batch:
|
27 |
+
break
|
28 |
+
data.extend(batch)
|
29 |
+
offset += batch_size
|
30 |
+
return pd.DataFrame(data, columns=['user_id', 'meal_id', 'quantity'])
|
31 |
|
32 |
def train_model(self):
|
33 |
data = self.fetch_data()
|
34 |
if data.empty:
|
|
|
35 |
return None
|
36 |
+
|
37 |
reader = Reader(rating_scale=(1, 5))
|
38 |
dataset = Dataset.load_from_df(data[['user_id', 'meal_id', 'quantity']], reader)
|
39 |
+
|
40 |
trainset = dataset.build_full_trainset()
|
41 |
algo = SVD()
|
42 |
algo.fit(trainset)
|
43 |
+
|
44 |
+
# Save model to database
|
45 |
+
model_binary = pickle.dumps(algo)
|
46 |
+
model_record = RecommendationModel(model=model_binary, created_at=datetime.now())
|
47 |
+
self.db.add(model_record)
|
48 |
+
self.db.commit()
|
49 |
+
|
50 |
return algo
|
51 |
|
52 |
def load_or_train_model(self):
|
53 |
+
latest_model = self.db.query(RecommendationModel).order_by(RecommendationModel.created_at.desc()).first()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
+
if latest_model and datetime.now() - latest_model.created_at <= self.retrain_interval:
|
56 |
+
return pickle.loads(latest_model.model)
|
57 |
+
else:
|
58 |
+
return self.train_model()
|
59 |
|
60 |
def get_recommendations(self, user: User):
|
|
|
|
|
|
|
61 |
if self.algo is None:
|
62 |
return self.get_random_recommendations()
|
63 |
|
users/models.py
CHANGED
@@ -8,7 +8,6 @@ class User(Base):
|
|
8 |
|
9 |
id = Column(Integer, primary_key=True, index=True)
|
10 |
email = Column(String, unique=True, index=True, nullable=False)
|
11 |
-
username = Column(String, unique=True, index=True, nullable=False)
|
12 |
password = Column(String, nullable=False)
|
13 |
first_name = Column(String, nullable=True)
|
14 |
last_name = Column(String, nullable=True)
|
|
|
8 |
|
9 |
id = Column(Integer, primary_key=True, index=True)
|
10 |
email = Column(String, unique=True, index=True, nullable=False)
|
|
|
11 |
password = Column(String, nullable=False)
|
12 |
first_name = Column(String, nullable=True)
|
13 |
last_name = Column(String, nullable=True)
|
users/routes.py
CHANGED
@@ -25,8 +25,18 @@ router = APIRouter(
|
|
25 |
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserBase)
|
26 |
async def create_user(data: UserCreate, db: Session = Depends(get_db)):
|
27 |
new_user = await create_user_account(data, db)
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
@router.get("/me/", response_model=UserBase)
|
31 |
async def read_users_me(current_user: User = Depends(get_current_user)):
|
32 |
return current_user
|
|
|
25 |
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserBase)
|
26 |
async def create_user(data: UserCreate, db: Session = Depends(get_db)):
|
27 |
new_user = await create_user_account(data, db)
|
28 |
+
access_token_expires = timedelta(minutes=int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30")))
|
29 |
+
payload = {"id": new_user.id, "sub": new_user.email}
|
30 |
+
access_token = await create_access_token(data=payload, expiry=access_token_expires)
|
31 |
+
refresh_token = await create_refresh_token(data=payload)
|
32 |
+
|
33 |
+
return JSONResponse(content={
|
34 |
+
"access_token": access_token,
|
35 |
+
"refresh_token": refresh_token,
|
36 |
+
"token_type": "Bearer",
|
37 |
+
"expires_in": access_token_expires.seconds
|
38 |
+
}, status_code=status.HTTP_200_OK)
|
39 |
+
|
40 |
@router.get("/me/", response_model=UserBase)
|
41 |
async def read_users_me(current_user: User = Depends(get_current_user)):
|
42 |
return current_user
|
users/schemas.py
CHANGED
@@ -3,7 +3,6 @@ from typing import Optional, List
|
|
3 |
from datetime import datetime
|
4 |
|
5 |
class UserBase(BaseModel):
|
6 |
-
username: str = Field(..., min_length=3, max_length=50)
|
7 |
first_name: str = Field(..., min_length=1, max_length=50)
|
8 |
last_name: str = Field(..., min_length=1, max_length=50)
|
9 |
email: EmailStr
|
@@ -14,7 +13,6 @@ class UserCreate(UserBase):
|
|
14 |
password: str = Field(..., min_length=8)
|
15 |
|
16 |
class UserUpdate(BaseModel):
|
17 |
-
username: Optional[str] = Field(None, min_length=3, max_length=50)
|
18 |
first_name: Optional[str] = Field(None, min_length=1, max_length=50)
|
19 |
last_name: Optional[str] = Field(None, min_length=1, max_length=50)
|
20 |
email: Optional[EmailStr] = None
|
|
|
3 |
from datetime import datetime
|
4 |
|
5 |
class UserBase(BaseModel):
|
|
|
6 |
first_name: str = Field(..., min_length=1, max_length=50)
|
7 |
last_name: str = Field(..., min_length=1, max_length=50)
|
8 |
email: EmailStr
|
|
|
13 |
password: str = Field(..., min_length=8)
|
14 |
|
15 |
class UserUpdate(BaseModel):
|
|
|
16 |
first_name: Optional[str] = Field(None, min_length=1, max_length=50)
|
17 |
last_name: Optional[str] = Field(None, min_length=1, max_length=50)
|
18 |
email: Optional[EmailStr] = None
|
users/services.py
CHANGED
@@ -12,7 +12,6 @@ async def create_user_account(data: UserCreate, db: Session):
|
|
12 |
|
13 |
new_user = User(
|
14 |
email=data.email,
|
15 |
-
username=data.username,
|
16 |
first_name=data.first_name,
|
17 |
last_name=data.last_name,
|
18 |
age=data.age,
|
|
|
12 |
|
13 |
new_user = User(
|
14 |
email=data.email,
|
|
|
15 |
first_name=data.first_name,
|
16 |
last_name=data.last_name,
|
17 |
age=data.age,
|