Skip to content

Commit

Permalink
api: upgrade python packages
Browse files Browse the repository at this point in the history
Enable support for `pydantic v2` along
with the latest `fastapi-pagination` package.
To enable the upgrade `fastapi` and `fastapi-users`
packages are also required to be upgraded.

Signed-off-by: Jeny Sadadia <[email protected]>
  • Loading branch information
Jeny Sadadia committed Oct 18, 2024
1 parent 8431443 commit 96fd1b6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 30 deletions.
3 changes: 2 additions & 1 deletion api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

"""Module settings"""

from pydantic import BaseSettings, EmailStr
from pydantic import EmailStr
from pydantic_settings import BaseSettings


# pylint: disable=too-few-public-methods
Expand Down
2 changes: 1 addition & 1 deletion api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def serialize_paginated_data(model, data: list):
"""
serialized_data = []
for obj in data:
serialized_data.append(model(**obj).dict())
serialized_data.append(model(**obj).model_dump(mode='json'))
return serialized_data


Expand Down
84 changes: 66 additions & 18 deletions api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
"""Server-side model definitions"""

from datetime import datetime
from typing import Optional, TypeVar
from typing import Optional, TypeVar, Dict, Any, List
from pydantic import (
BaseModel,
conlist,
Field,
model_serializer,
field_validator,
)
from typing_extensions import Annotated
from fastapi import Query
from fastapi_pagination import LimitOffsetPage, LimitOffsetParams
from fastapi_users.db import BeanieBaseUser
Expand All @@ -27,7 +29,7 @@
Document,
PydanticObjectId,
)
from bson import ObjectId
# from bson import ObjectId
from kernelci.api.models_base import DatabaseModel, ModelId

Check failure on line 33 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Unable to import 'kernelci.api.models_base'


Expand Down Expand Up @@ -56,6 +58,7 @@ class SubscriptionStats(Subscription):
description='Timestamp of connection creation'
)
last_poll: Optional[datetime] = Field(
default=None,
description='Timestamp when connection last polled for data'
)

Expand All @@ -79,12 +82,20 @@ def get_indexes(cls):
class User(BeanieBaseUser, Document, # pylint: disable=too-many-ancestors
DatabaseModel):
"""API User model"""
username: Indexed(str, unique=True)
groups: conlist(UserGroup, unique_items=True) = Field(
username: Annotated[str, Indexed(unique=True)]
groups: List[UserGroup] = Field(
default=[],
description="A list of groups that user belongs to"
description="A list of groups that the user belongs to"
)

@field_validator('groups')
def validate_groups(cls, groups): # pylint: disable=no-self-argument

Check warning on line 92 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Method could be a function
"""Unique group constraint"""
unique_names = {group.name for group in groups}
if len(unique_names) != len(groups):
raise ValueError("Groups must have unique names.")
return groups

class Settings(BeanieBaseUser.Settings):
"""Configurations"""
# MongoDB collection name for model
Expand All @@ -97,23 +108,66 @@ def get_indexes(cls):
cls.Index('email', {'unique': True}),
]

@model_serializer(when_used='json')
def serialize_model(self) -> Dict[str, Any]:
"""Serialize model by converting PyObjectId to string"""
values = self.__dict__.copy()
for field_name, value in values.items():
if isinstance(value, PydanticObjectId):
values[field_name] = str(value)
return values


class UserRead(schemas.BaseUser[PydanticObjectId], ModelId):

Check failure on line 121 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Inheriting 'schemas.BaseUser[PydanticObjectId]', which is not a class.
"""Schema for reading a user"""
username: Indexed(str, unique=True)
groups: conlist(UserGroup, unique_items=True)
username: Annotated[str, Indexed(unique=True)]
groups: List[UserGroup] = Field(default=[])

@field_validator('groups')
def validate_groups(cls, groups): # pylint: disable=no-self-argument

Check warning on line 127 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Method could be a function
"""Unique group constraint"""
unique_names = {group.name for group in groups}
if len(unique_names) != len(groups):
raise ValueError("Groups must have unique names.")
return groups

@model_serializer(when_used='json')
def serialize_model(self) -> Dict[str, Any]:
"""Serialize model by converting PyObjectId to string"""
values = self.__dict__.copy()
for field_name, value in values.items():
if isinstance(value, PydanticObjectId):
values[field_name] = str(value)
return values


class UserCreate(schemas.BaseUserCreate):
"""Schema for creating a user"""
username: Indexed(str, unique=True)
groups: Optional[conlist(str, unique_items=True)]
username: Annotated[str, Indexed(unique=True)]
groups: List[str] = Field(default=[])

@field_validator('groups')
def validate_groups(cls, groups): # pylint: disable=no-self-argument

Check warning on line 150 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Method could be a function
"""Unique group constraint"""
unique_names = set(groups)
if len(unique_names) != len(groups):
raise ValueError("Groups must have unique names.")
return groups


class UserUpdate(schemas.BaseUserUpdate):
"""Schema for updating a user"""
username: Optional[Indexed(str, unique=True)]
groups: Optional[conlist(str, unique_items=True)]
username: Annotated[Optional[str], Indexed(unique=True),
Field(default=None)]
groups: List[str] = Field(default=[])

@field_validator('groups')
def validate_groups(cls, groups): # pylint: disable=no-self-argument

Check warning on line 165 in api/models.py

View workflow job for this annotation

GitHub Actions / Lint

Method could be a function
"""Unique group constraint"""
unique_names = set(groups)
if len(unique_names) != len(groups):
raise ValueError("Groups must have unique names.")
return groups


# Pagination models
Expand All @@ -133,9 +187,3 @@ class PageModel(LimitOffsetPage[TypeVar("T")]):
This model is required to serialize paginated model data response"""

__params_type__ = CustomLimitOffsetParams

class Config:
"""Configuration attributes for PageNode"""
json_encoders = {
ObjectId: str,
}
5 changes: 3 additions & 2 deletions api/user_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""User Manager"""

from typing import Optional, Any, Dict
from fastapi import Depends, Request
from fastapi import Depends, Request, Response
from fastapi.security import OAuth2PasswordRequestForm
from fastapi_users import BaseUserManager
from fastapi_users.db import (
Expand Down Expand Up @@ -68,7 +68,8 @@ async def on_after_verify(self, user: User,
self.email_sender.create_and_send_email(subject, content, user.email)

async def on_after_login(self, user: User,
request: Optional[Request] = None):
request: Optional[Request] = None,
response: Optional[Response] = None):
"""Handler to execute after successful user login"""
print(f"User {user.id} {user.username} logged in.")

Expand Down
12 changes: 8 additions & 4 deletions docker/api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
cloudevents==1.9.0
fastapi[all]==0.99.1
fastapi-pagination==0.9.3
fastapi-users[beanie, oauth]==10.4.0
# fastapi[all]==0.99.1
fastapi[all]==0.115.0
# fastapi-pagination==0.9.3
fastapi-pagination==0.12.30
# fastapi-users[beanie, oauth]==10.4.0
fastapi-users[beanie, oauth]==13.0.0
fastapi-versioning==0.10.0
MarkupSafe==2.0.1
motor==3.6.0
pymongo==4.9.0
passlib==1.7.4
pydantic==1.10.13
# pydantic==1.10.13
pydantic==2.9.2
pymongo-migrate==0.11.0
python-jose[cryptography]==3.3.0
redis==5.0.1
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ requires-python = ">=3.10"
license = {text = "LGPL-2.1-or-later"}
dependencies = [
"cloudevents == 1.9.0",
"fastapi[all] == 0.99.1",
"fastapi-pagination == 0.9.3",
"fastapi-users[beanie, oauth] == 10.4.0",
"fastapi[all] == 0.115.0",
"fastapi-pagination == 0.12.30",
"fastapi-users[beanie, oauth] == 13.0.0",
"fastapi-versioning == 0.10.0",
"MarkupSafe == 2.0.1",
"motor == 3.6.0",
"pymongo == 4.9.0",
"passlib == 1.7.4",
"pydantic == 1.10.13",
"pydantic == 2.9.2",
"pymongo-migrate == 0.11.0",
"python-jose[cryptography] == 3.3.0",
"redis == 5.0.1",
Expand Down

2 comments on commit 96fd1b6

@moto-timo
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snyk is highlighting python-jose as having High vulnerabilities. One of the suggested replacements is PyJWT.

@nuclearcat
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened issue #558 about that. Thanks for pointing to that!

Please sign in to comment.