GOOGLE ADS

mardi 3 mai 2022

Comment implémenter la pagination pour fastapi avec mongo db (Motor)

J'ai une API REST simple qui est une librairie créée avec FastAPI et mongo db comme backend (j'ai utilisé Motorcomme bibliothèque au lieu de Pymongo). J'ai un GETpoint de terminaison pour obtenir tous les livres de la base de données qui prend également en charge les chaînes de requête (par exemple: un utilisateur peut rechercher des livres avec un seul auteur ou avec un type de genre, etc.).

Vous trouverez ci-dessous les codes correspondants pour ce point de terminaison :
routers.py


@router.get("/books", response_model=List[models.AllBooksResponse])
async def get_the_list_of_all_books(
authors: Optional[str] = None,
genres: Optional[str] = None,
published_year: Optional[str] = None,
) -> List[Dict[str, Any]]:
if authors is None and genres is None and published_year is None:
all_books = [book for book in await mongo.BACKEND.get_all_books()]
else:
all_books = [
book
for book in await mongo.BACKEND.get_all_books(
authors=authors.strip('"').split(",") if authors is not None else None,
genres=genres.strip('"').split(",") if genres is not None else None,
published_year=datetime.strptime(published_year, "%Y")
if published_year is not None
else None,
)
]
return all_books

Le modèle correspondant:

class AllBooksResponse(BaseModel):
name: str
author: str
link: Optional[str] = None
def __init__(self, name, author, **data):
super().__init__(
name=name, author=author, link=f"{base_uri()}book/{data['book_id']}"
)

Et la fonction backend pour obtenir les données :

class MongoBackend:
def __init__(self, uri: str) -> None:
self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
async def get_all_books(
self,
authors: Optional[List[str]] = None,
genres: Optional[List[str]] = None,
published_year: Optional[datetime] = None,
) -> List[Dict[str, Any]]:
find_condition = {}
if authors is not None:
find_condition["author"] = {"$in": authors}
if genres is not None:
find_condition["genres"] = {"$in": genres}
if published_year is not None:
find_condition["published_year"] = published_year
cursor = self._client[DB][BOOKS_COLLECTION].find(find_condition, {"_id": 0})
return [doc async for doc in cursor]

Maintenant, je veux implémenter la pagination pour ce point de terminaison. Ici j'ai quelques questions:

  • Est-il bon de faire de la pagination au niveau de la base de données ou au niveau de l'application?

  • Avons-nous des bibliothèques prêtes à l'emploi qui peuvent m'aider à le faire dans fastapi? J'ai vérifié la documentation pour https://pypi.org/project/fastapi-pagination/, mais cela semble être plus ciblé vers les bases de données SQL

  • J'ai aussi consulté ce lien: https://www.codementor.io/@arpitbhayani/fast-and-efficient-pagination-in-mongodb-9095flbqr qui parle de différentes façons de faire cela dans Mongo db mais je ne pense que le premier l'option (utiliser limitet skip) fonctionnerait pour moi, car je veux également le faire fonctionner lorsque j'utilise d'autres paramètres de filtre (par exemple pour l'auteur et le genre) et il n'y a aucun moyen de connaître les ObjectId à moins que je fasse la première requête à obtenir les données et ensuite je veux faire la pagination.

  • Mais le problème est partout où je vois utiliser limitet skipest découragé.

    Quelqu'un peut-il me faire savoir quelles sont les meilleures pratiques ici et quelque chose peut-il s'appliquer à mes besoins et à mon cas d'utilisation ?

    Merci d'avance.


    Solution du problème

    Il n'y a pas de bonne ou de mauvaise réponse à une telle question. Cela dépend beaucoup de la pile technologique que vous utilisez, ainsi que du contexte dont vous disposez, compte tenu également des orientations futures du logiciel que vous avez écrit ainsi que du logiciel que vous utilisez (mongo).

    Répondre à vos questions:

  • Cela dépend de la charge que vous devez gérer et de la pile de développement que vous utilisez. Habituellement, cela se fait au niveau de la base de données, car récupérer les 110 premiers et supprimer les 100 premiers est assez stupide et consomme des ressources (la base de données le fera pour vous).


  • Pour moi, c'est assez simple sur la façon de le faire via fastapi: il suffit d'ajouter à votre getfonction les paramètres limit: int = 10et skip: int = 0de les utiliser dans la fonction de filtrage de votre base de données. Fastapivérifiera les types de données pour vous, tandis que vous pourrez vérifier que la limite n'est pas négative ou supérieure, par exemple, à 100.


  • Il dit qu'il n'y a pas de solution miracle et que puisque skipla fonction de mongo ne fonctionne pas bien. Ainsi, il pense que la deuxième option est meilleure, juste pour les performances. Si vous avez des milliards et des milliards de documents (par exemple amazon), eh bien, il peut être judicieux d'utiliser quelque chose de différent, bien qu'au moment où votre site Web aura autant grandi, je suppose que vous aurez l'argent pour payer toute une équipe de des experts pour faire le tri et éventuellement développer votre propre base de données.


  • TL ; RD

    En conclusion, l' approche limitet skipest la plus courante. Cela se fait généralement au niveau de la base de données, afin de réduire la quantité de travail de l'application et la bande passante.

    Mongo n'est pas très efficace pour sauter et limiter les résultats. Si votre base de données contient, disons, un million de documents, je ne pense pas que vous le remarquerez même. Vous pouvez même utiliser une base de données relationnelle pour une telle charge de travail. Vous pouvez toujours comparer les options dont vous disposez et choisir la plus appropriée.

    Je ne connais pas grand-chose à mongo, mais je sais qu'en général, les index peuvent aider à limiter et à ignorer les enregistrements (docs dans ce cas), mais je ne sais pas si c'est également le cas pour mongo.

    Aucun commentaire:

    Enregistrer un commentaire

    Comment utiliseriez-vous .reduce() sur des arguments au lieu d'un tableau ou d'un objet spécifique ?

    Je veux définir une fonction.flatten qui aplatit plusieurs éléments en un seul tableau. Je sais que ce qui suit n'est pas possible, mais...