API REST Python avec Flask, Connexion et SQLAlchemy – Partie 3 – Real Python

By | avril 10, 2019

Cours Python en ligne

Dans la partie 2 de cette série, vous avez ajouté la possibilité d'enregistrer les modifications apportées via l'API REST dans une base de données à l'aide de SQLAlchemy et vous avez appris à sérialiser ces données pour l'API REST à l'aide de Marshmallow. Connecter l'API REST à une base de données afin que l'application puisse modifier les données existantes et en créer de nouvelles est une excellente chose et rend l'application beaucoup plus utile et robuste.

Ce n’est cependant qu’un des atouts de la base de données. Une fonctionnalité encore plus puissante est la R partie de SGBDR systèmes: des relations. Dans une base de données, une relation est la capacité de connecter deux tables ou plus de manière significative. Dans cet article, vous apprendrez à mettre en œuvre des relations et à transformer votre La personne base de données dans une application Web de mini-blogging.

Dans cet article, vous apprendrez:

  • Pourquoi plus d'une table dans une base de données est utile et importante
  • Comment les tableaux sont liés les uns aux autres
  • Comment SQLAlchemy peut vous aider à gérer les relations
  • Comment les relations vous aident-elles à créer une application de mini-blogage?

À qui s'adresse cet article

La première partie de cette série vous a guidé dans la construction d'une API REST et la deuxième partie vous a montré comment connecter cette API à une base de données.

Cet article développe davantage votre ceinture d’outils de programmation. Vous apprendrez à créer des structures de données hiérarchiques représentées sous la forme de relations un à plusieurs par SQLAlchemy. En outre, vous allez étendre l’API REST que vous avez déjà créée pour assurer la prise en charge CRUD (Créer, Lire, Mettre à jour et Supprimer) des éléments de cette structure hiérarchique.

Les applications HTML et JavaScript de l’application Web présentée dans la deuxième partie seront modifiées de façon majeure afin de créer une application de mini-blogage plus fonctionnelle. Vous pouvez consulter la version finale du code de la partie 2 dans le référentiel GitHub pour cet article.

Accrochez-vous lorsque vous commencez à créer des relations et votre application de mini-blogage!

Dépendances supplémentaires

Il n'y a pas de nouvelles dépendances Python autres que celles requises pour l'article de la partie 2. Cependant, vous utiliserez deux nouveaux modules JavaScript dans l'application Web pour rendre les choses plus simples et plus cohérentes. Les deux modules sont les suivants:

  1. Guidon.js est un moteur de templates pour JavaScript, un peu comme Jinja2 pour Flask.
  2. Moment.js est un module d'analyse de date / heure et de formatage qui facilite l'affichage des horodatages UTC.

Vous n’avez pas à télécharger l’un ou l’autre de ces logiciels, car l’application Web les obtiendra directement à partir du CDN (réseau de distribution de contenu) Cloudflare, comme vous le faites déjà pour le module jQuery.

People Data Extended pour les blogs

Dans la partie 2, le Personnes les données existaient sous forme de dictionnaire dans le build_database.py Code python. C'est ce que vous utilisiez pour remplir la base de données avec des données initiales. Vous allez modifier le Personnes structure de données pour donner à chaque personne une liste des notes qui leur sont associées. Le nouveau Personnes la structure de données ressemblera à ceci:

# Données pour initialiser la base de données avec
PERSONNES = [[[[
    
        "fname": "Doug",
        "lname": "Farrell",
        "Remarques": [[[[
            ("Cool, une application de mini-blogging!", "2019-01-06 22:17:54"),
            ("Cela pourrait être utile", "2019-01-08 22:17:54"),
            ("Bien, en quelque sorte utile", "2019-03-06 22:17:54"),
        ],
    ,
    
        "fname": "Kent",
        "lname": "Brockman",
        "Remarques": [[[[
            (
                "Je vais faire des observations très profondes",
                "2019-01-07 22:17:54",
            ),
            (
                "Peut-être qu'ils seront plus évidents que je ne le pensais",
                "2019-02-06 22:17:54",
            ),
        ],
    ,
    
        "fname": "Lapin",
        "lname": "Pâques",
        "Remarques": [[[[
            ("Est-ce que quelqu'un a vu mes oeufs de Pâques?", "2019-01-07 22:47:54"),
            ("Je suis vraiment en retard pour les livrer!", "2019-04-06 22:17:54"),
        ],
    ,
]

Chaque personne du Personnes le dictionnaire inclut maintenant une clé appelée Remarques, qui est associé à une liste contenant des nuplets de données. Chaque tuple dans le Remarques la liste représente un seul Remarque contenant le contenu et un horodatage. Les horodatages sont initialisés (plutôt que créés dynamiquement) pour illustrer les commandes ultérieures dans l'API REST.

Chaque personne est associée à plusieurs notes et chaque note est associée à une seule personne. Cette hiérarchie de données est connue sous le nom de relation un-à-plusieurs, dans laquelle un seul objet parent est associé à plusieurs objets enfants. Vous verrez comment cette relation un à plusieurs est gérée dans la base de données avec SQLAlchemy.

Approche de la force brute

La base de données que vous avez construite a stocké les données dans une table et une table est un tableau à deux dimensions de lignes et de colonnes. Le peut Personnes dictionnaire ci-dessus être représenté dans une seule table de lignes et de colonnes? Cela peut être, de la manière suivante, dans votre la personne table de base de données. Malheureusement, inclure toutes les données réelles dans l'exemple crée une barre de défilement pour le tableau, comme vous le verrez ci-dessous:

person_id lname fname horodatage contenu note_horodatage
1 Farrell Doug 2018-08-08 21:16:01 Cool, une application de mini-blogging! 2019-01-06 22:17:54
2 Farrell Doug 2018-08-08 21:16:01 Cela pourrait être utile 2019-01-08 22:17:54
3 Farrell Doug 2018-08-08 21:16:01 Bien, en quelque sorte utile 2019-03-06 22:17:54
4 Brockman Kent 2018-08-08 21:16:01 Je vais faire des observations très profondes 2019-01-07 22:17:54
5 Brockman Kent 2018-08-08 21:16:01 Peut-être seront-ils plus évidents que je ne le pensais 2019-02-06 22:17:54
6 Pâques lapin 2018-08-08 21:16:01 Est-ce que quelqu'un a vu mes oeufs de Pâques? 2019-01-07 22:47:54
7 Pâques lapin 2018-08-08 21:16:01 Je suis vraiment en retard pour les livrer! 2019-04-06 22:17:54

La table ci-dessus fonctionnerait réellement. Toutes les données sont représentées et une seule personne est associée à une collection de notes différentes.

Avantages

Conceptuellement, la structure de la table ci-dessus présente l'avantage d'être relativement simple à comprendre. Vous pouvez même affirmer que les données pourraient être conservées dans un fichier plat au lieu d'une base de données.

En raison de la structure de table bidimensionnelle, vous pouvez stocker et utiliser ces données dans une feuille de calcul. Les tableurs ont été mis au service du stockage de données un peu.

Désavantages

Bien que la structure de tableau ci-dessus puisse fonctionner, elle présente de réels inconvénients.

Afin de représenter la collection de notes, toutes les données de chaque personne sont répétées pour chaque note unique. Les données de personne sont donc redondantes. Ce n’est pas si grave pour vos données personnelles car il n’ya pas autant de colonnes. Mais imaginez si une personne avait beaucoup plus de colonnes. Même avec de gros disques, cela peut poser un problème de stockage si vous traitez avec des millions de lignes de données.

De telles données redondantes peuvent entraîner des problèmes de maintenance au fil du temps. Par exemple, si le lapin de Pâques décidait de changer de nom, ce serait une bonne idée. Pour ce faire, chaque enregistrement contenant le nom du lapin de Pâques devrait être mis à jour afin de maintenir la cohérence des données. Ce type de travail sur la base de données peut entraîner des incohérences dans les données, en particulier si le travail est effectué par une personne qui exécute manuellement une requête SQL.

Nommer des colonnes devient gênant. Dans le tableau ci-dessus, il y a un horodatage colonne utilisée pour suivre le temps de création et de mise à jour d’une personne dans la table. Vous souhaitez également disposer de fonctionnalités similaires pour la création et la mise à jour d'une note, mais parce que horodatage est déjà utilisé, un nom artificiel de note_horodatage est utilisé.

Et si vous vouliez ajouter des relations un-à-plusieurs supplémentaires à la la personne table? Par exemple, inclure les enfants ou les numéros de téléphone d’une personne. Chaque personne peut avoir plusieurs enfants et plusieurs numéros de téléphone. Cela pourrait être fait relativement facilement au Python Personnes dictionnaire ci-dessus en ajoutant les enfants et les numéros de téléphone clés avec de nouvelles listes contenant les données.

Cependant, représentant ces nouvelles relations un-à-plusieurs dans votre la personne La table de base de données ci-dessus devient beaucoup plus difficile. Chaque nouvelle relation un-à-plusieurs augmente considérablement le nombre de lignes nécessaires pour la représenter pour chaque entrée des données enfants. En outre, les problèmes liés à la redondance des données deviennent plus importants et plus difficiles à gérer.

Enfin, les données que vous obtiendrez de la structure de table ci-dessus ne seraient pas très pythoniques: ce serait juste une longue liste de listes. SQLAlchemy ne pourrait pas vous aider beaucoup car la relation n’est pas là.

Base de données relationnelle

En vous basant sur ce que vous avez vu ci-dessus, il devient clair que tenter de représenter même un jeu de données moyennement complexe dans une seule table devient très vite ingérable. Dans ces conditions, quelle alternative offre une base de données? C'est là que le R partie de SGBDR les bases de données entrent en jeu. Représenter des relations supprime les inconvénients décrits ci-dessus.

Au lieu d'essayer de représenter des données hiérarchiques dans une seule table, les données sont divisées en plusieurs tables, avec un mécanisme pour les relier les unes aux autres. Les tableaux sont répartis le long des lignes de collecte, donc pour votre Personnes dictionnaire ci-dessus, cela signifie qu’il y aura un tableau représentant les personnes et un autre représentant les notes. Cela ramène votre original la personne table, qui ressemble à ceci:

person_id lname fname horodatage
1 Farrell Doug 2018-08-08 21: 16: 01.888444
2 Brockman Kent 2018-08-08 21: 16: 01.889060
3 Pâques lapin 2018-08-08 21: 16: 01.886834

Pour représenter les nouvelles informations de note, vous allez créer une nouvelle table appelée Remarque. (N'oubliez pas notre convention de nommage de table singulière.) La table ressemble à ceci:

note_id person_id contenu horodatage
1 1 Cool, une application de mini-blogging! 2019-01-06 22:17:54
2 1 Cela pourrait être utile 2019-01-08 22:17:54
3 1 Bien, en quelque sorte utile 2019-03-06 22:17:54
4 2 Je vais faire des observations très profondes 2019-01-07 22:17:54
5 2 Peut-être seront-ils plus évidents que je ne le pensais 2019-02-06 22:17:54
6 3 Est-ce que quelqu'un a vu mes oeufs de Pâques? 2019-01-07 22:47:54
7 3 Je suis vraiment en retard pour les livrer! 2019-04-06 22:17:54

Notez que, comme le la personne table, la Remarque la table a un identifiant unique appelé note_id, qui est la clé primaire du Remarque table. Une chose qui n’est pas évidente est l’inclusion du person_id valeur dans la table. À quoi sert-il? C’est ce qui crée la relation avec le la personne table. Tandis que note_id est la clé primaire de la table, person_id est ce que l’on appelle une clé étrangère.

La clé étrangère donne chaque entrée dans la Remarque table la clé primaire du la personne enregistrer, il est associé à. SQLAlchemy peut ainsi rassembler toutes les notes associées à chaque personne en connectant le person.person_id clé primaire à la note.person_id clé étrangère, créant une relation.

Avantages

En divisant le jeu de données en deux tableaux et en introduisant le concept de clé étrangère, vous avez compliqué la réflexion des données et résolu les inconvénients d’une représentation sous forme de tableau unique. SQLAlchemy vous aidera à coder assez facilement la complexité accrue.

Les données ne sont plus redondantes dans la base de données. Il n'y a qu'une seule entrée de personne pour chaque personne que vous souhaitez stocker dans la base de données. Cela résout immédiatement le problème de stockage et simplifie considérablement les problèmes de maintenance.

Si le lapin de Pâques veut toujours changer de nom, il vous suffira de changer une seule ligne de la liste. la personne table, et tout ce qui concerne cette ligne (comme le Remarque table) profiterait immédiatement du changement.

La dénomination des colonnes est plus cohérente et significative. Etant donné que les données de personne et de note existent dans des tables distinctes, l'horodatage de création et de mise à jour peut être nommé de manière cohérente dans les deux tables, car il n'y a pas de conflit de noms entre les tables.

De plus, vous n’êtes plus obligé de créer des permutations de chaque ligne pour les nouvelles relations un à plusieurs que vous voudrez peut-être représenter. Prenez notre les enfants et les numéros de téléphone exemple de plus tôt. La mise en œuvre nécessiterait enfant et numéro de téléphone les tables. Chaque table contiendrait une clé étrangère de person_id reliant à la la personne table.

Avec SQLAlchemy, les données que vous récupériez dans les tables ci-dessus seraient plus immédiatement utiles, car vous obtiendriez un objet pour chaque ligne de personne. Cet objet a des attributs nommés équivalents aux colonnes de la table. L'un de ces attributs est une liste Python contenant les objets de note associés.

Désavantages

Là où l'approche de la force brute était plus simple à comprendre, le concept de clés étrangères et de relations rend la réflexion sur les données un peu plus abstraite. Cette abstraction doit être réfléchie pour chaque relation que vous établissez entre les tables.

Utiliser des relations signifie s’engager à utiliser un système de base de données. Ceci est un autre outil pour installer, apprendre et maintenir au-delà de l'application qui utilise réellement les données.

Modèles SQLAlchemy

Pour utiliser les deux tables ci-dessus et leur relation, vous devez créer des modèles SQLAlchemy prenant en compte les deux tables et leur relation. Voici la SQLAlchemy La personne modèle de la partie 2, mis à jour pour inclure une relation avec une collection de Remarques:

    1 classe La personne(db.Modèle):
    2     __nom de la table__ = 'la personne'
    3     person_id = db.Colonne(db.Entier, clé primaire=Vrai)
    4     lname = db.Colonne(db.Chaîne(32))
    5     fname = db.Colonne(db.Chaîne(32))
    6     horodatage = db.Colonne(
    7         db.DateTime, défaut=date / heure.maintenant, à jour=date / heure.maintenant
    8     )
    9     Remarques = db.relation(
dix         'Remarque',
11         backref='la personne',
12         Cascade='tous, supprimer, supprimer-orphelin',
13         parent célibataire=Vrai,
14         commandé par='desc (Note.timestamp)'
15     )

Les lignes 1 à 8 de la classe Python ci-dessus ressemblent exactement à ce que vous avez créé précédemment dans la partie 2. Les lignes 9 à 16 créent un nouvel attribut dans la La personne classe appelée Remarques. Cette nouvelle Remarques attributs est défini dans les lignes de code suivantes:

  • Ligne 9: Comme les autres attributs de la classe, cette ligne crée un nouvel attribut appelé Remarques et le définit égal à une instance d'un objet appelé db.relationship. Cet objet crée la relation que vous ajoutez au La personne et est créé avec tous les paramètres définis dans les lignes qui suivent.

  • Ligne 10: Le paramètre de chaîne 'Remarque' définit la classe SQLAlchemy que la La personne la classe sera liée à. le Remarque La classe n’est pas encore définie, c’est pourquoi il s’agit d’une chaîne ici. Il s’agit d’une référence en aval qui aide à résoudre les problèmes que l’ordre des définitions peut causer lorsque quelque chose est nécessaire qui n’est défini que plus tard dans le code. le 'Remarque' chaîne permet la La personne classe pour trouver le Remarque classe à l'exécution, qui est après les deux La personne et Remarque ont été définis.

  • Ligne 11: le backref = 'personne' paramètre est plus délicat. Il crée ce que l’on appelle une référence en arrière dans Remarque objets. Chaque instance d'un Remarque objet contiendra un attribut appelé la personne. le la personne attribut fait référence à l'objet parent qu'un particulier Remarque instance est associée à. Avoir une référence à l'objet parent (la personne dans ce cas) dans l’enfant peut être très utile si votre code itère sur des notes et doit inclure des informations sur le parent. Cela arrive étonnamment souvent dans le code de rendu d'affichage.

  • Ligne 12: le cascade = 'tout, supprimer, supprimer-orphelin' paramètre détermine comment traiter les occurrences d'objet note lorsque des modifications sont apportées au parent La personne exemple. Par exemple, quand un La personne l'objet est supprimé, SQLAlchemy crée le SQL nécessaire pour supprimer le La personne de la base de données. De plus, ce paramètre lui dit de supprimer également tous les Remarque cas associés à elle. Vous pouvez en savoir plus sur ces options dans la documentation SQLAlchemy.

  • Ligne 13: le single_parent = True paramètre est requis si supprimer orphelin fait partie de la précédente Cascade paramètre. Ceci dit à SQLAlchemy de ne pas autoriser les orphelins Remarque cas (un Remarque sans parent La personne objet) d'exister parce que chaque Remarque a un seul parent.

  • Ligne 14: le order_by = 'desc (Note.timestamp)' paramètre indique à SQLAlchemy comment trier le Remarque cas associés à un La personne. Lorsqu'un La personne l'objet est récupéré, par défaut le Remarques liste d'attributs contiendra Remarque objets dans un ordre inconnu. Le SQLAlchemy desc (...) La fonction triera les notes par ordre décroissant du plus récent au plus ancien. Si cette ligne était à la place order_by = 'Note.timestamp', SQLAlchemy utiliserait par défaut le asc (...) fonction et triez les notes par ordre croissant, du plus ancien au plus récent.

Maintenant que votre La personne le modèle a la nouvelle Remarques attribut, et cela représente la relation un-à-plusieurs à Remarque des objets, vous devrez définir un modèle SQLAlchemy pour une Remarque:

    1 classe Remarque(db.Modèle):
    2     __nom de la table__ = 'Remarque'
    3     note_id = db.Colonne(db.Entier, clé primaire=Vrai)
    4     person_id = db.Colonne(db.Entier, db.Clé étrangère('person.person_id'))
    5     contenu = db.Colonne(db.Chaîne, nullable=Faux)
    6     horodatage = db.Colonne(
    7         db.DateTime, défaut=date / heure.maintenant, à jour=date / heure.maintenant
    8     )

le Remarque classe définit les attributs constituant une note comme dans notre exemple Remarque table de base de données ci-dessus. Les attributs sont définis ici:

  • Ligne 1 crée le Remarque classe, héritant de db.Model, exactement comme vous l’avez fait lors de la création du La personne classe.

  • Ligne 2 indique à la classe quelle table de base de données utiliser pour stocker Remarque objets.

  • Ligne 3 crée le note_id attribut, le définissant comme une valeur entière et comme clé primaire pour le Remarque objet.

  • Ligne 4 crée le person_id attribut, et le définit comme la clé étrangère, reliant le Remarque classe à la La personne classe en utilisant le person.person_id clé primaire. Ceci et le Notes de personne attribut, sont comment SQLAlchemy sait quoi faire lorsqu’il interagit avec La personne et Remarque objets.

  • Ligne 5 crée le contenu attribut, qui contient le texte réel de la note. le nullable = False Ce paramètre indique que vous pouvez créer de nouvelles notes sans contenu.

  • Ligne 6 crée le horodatage attribut, et exactement comme le La personne classe, elle contient le temps de création ou de mise à jour pour tout Remarque exemple.

Initialiser la base de données

Maintenant que vous avez mis à jour le La personne et créé le Remarque modèles, vous les utiliserez pour reconstruire la base de test people.db. Vous ferez cela en mettant à jour le build_database.py code de la partie 2. Voici à quoi le code ressemblera:

    1 importation os
    2 de date / heure importation date / heure
    3 de config importation db
    4 de des modèles importation La personne, Remarque
    5 
    6 # Données pour initialiser la base de données avec
    7 PERSONNES = [[[[
    8     
    9         "fname": "Doug",
dix         "lname": "Farrell",
11         "Remarques": [[[[
12             ("Cool, une application de mini-blogging!", "2019-01-06 22:17:54"),
13             ("Cela pourrait être utile", "2019-01-08 22:17:54"),
14             ("Bien, en quelque sorte utile", "2019-03-06 22:17:54"),
15         ],
16     ,
17     
18         "fname": "Kent",
19         "lname": "Brockman",
20         "Remarques": [[[[
21             (
22                 "Je vais faire des observations très profondes",
23                 "2019-01-07 22:17:54",
24             ),
25             (
26                 "Peut-être qu'ils seront plus évidents que je ne le pensais",
27                 "2019-02-06 22:17:54",
28             ),
29         ],
30     ,
31     
32         "fname": "Lapin",
33         "lname": "Pâques",
34         "Remarques": [[[[
35             ("Est-ce que quelqu'un a vu mes oeufs de Pâques?", "2019-01-07 22:47:54"),
36             ("Je suis vraiment en retard pour les livrer!", "2019-04-06 22:17:54"),
37         ],
38     ,
39 ]
40 
41 # Supprimer le fichier de base de données s'il existe actuellement
42 si os.chemin.existe("people.db"):
43     os.retirer("people.db")
44 
45 # Créer la base de données
46 db.create_all()
47 
48 # Parcourez la structure PEOPLE et remplissez la base de données
49 pour la personne dans PERSONNES:
50     p = La personne(lname=la personne.obtenir("lname"), fname=la personne.obtenir("fname"))
51 
52     # Ajouter les notes pour la personne
53     pour Remarque dans la personne.obtenir("Remarques"):
54         contenu, horodatage = Remarque
55         p.Remarques.ajouter(
56             Remarque(
57                 contenu=contenu,
58                 horodatage=date / heure.temps de repos(horodatage, "% Y-% m-%ré    % H:% M:% S "),
59             )
60         )
61     db.session.ajouter(p)
62 
63 db.session.commettre()

Le code ci-dessus provient de la partie 2, avec quelques modifications pour créer la relation un-à-plusieurs entre La personne et Remarque. Voici les lignes mises à jour ou nouvelles ajoutées au code:

  • Ligne 4 a été mis à jour pour importer le Remarque classe définie précédemment.

  • Lignes 7 à 39 contenir la mise à jour PERSONNES dictionnaire contenant nos données personnelles, ainsi que la liste des notes associées à chaque personne. Ces données seront insérées dans la base de données.

  • Lignes 49 à 61 itérer sur le PERSONNES dictionnaire, obtenir chacun la personne à son tour et l'utiliser pour créer un La personne objet.

  • Ligne 53 itère sur le notes de personne liste, obtenir chacun Remarque à son tour.

  • Ligne 54 déballe le contenu et horodatage de chaque Remarque tuple.

  • Ligne 55 à 60 crée un Remarque objet et l'ajoute à la collection de notes de personne à l'aide de p.notes.append ().

  • Ligne 61 ajoute le La personne objet p à la session de base de données.

  • Ligne 63 valide toute l'activité de la session dans la base de données. C’est à ce stade que toutes les données sont écrites sur le la personne et Remarque tables dans le people.db fichier de base de données.

Vous pouvez voir que travailler avec le Remarques collection dans le La personne instance d'objet p est comme travailler avec une autre liste en Python. SQLAlchemy prend en charge les informations de relation un à plusieurs sous-jacentes lorsque le db.session.commit () appel est fait.

Par exemple, comme un La personne l'instance a son champ clé primaire person_id initialisée par SQLAlchemy lorsqu’elle est validée dans la base de données, des instances de Remarque verront leurs champs de clé primaires initialisés. De plus, le Remarque clé étrangère person_id sera également initialisé avec la valeur de clé primaire du La personne par exemple, il est associé à.

Voici un exemple de cas de La personne objet avant la db.session.commit () dans une sorte de pseudocode:

La personne (
    person_id = None
    lname = 'Farrell'
    fname = 'Doug'
    horodatage = aucun
    notes = [
        Note (
            note_id = None
            person_id = None
            content = 'Cool, a mini-blogging application!'
            timestamp = '2019-01-06 22:17:54'
        ),
        Note (
            note_id = None
            person_id = None
            content = 'This could be useful'
            timestamp = '2019-01-08 22:17:54'
        ),
        Note (
            note_id = None
            person_id = None
            content = 'Well, sort of useful'
            timestamp = '2019-03-06 22:17:54'
        )
    ]
)

Voici l'exemple La personne objet après la db.session.commit ():

La personne (
    person_id = 1
    lname = 'Farrell'
    fname = 'Doug'
    timestamp = '2019-02-02 21: 27: 10.336'
    notes = [
        Note (
            note_id = 1
            person_id = 1
            content = 'Cool, a mini-blogging application!'
            timestamp = '2019-01-06 22:17:54'
        ),
        Note (
            note_id = 2
            person_id = 1
            content = 'This could be useful'
            timestamp = '2019-01-08 22:17:54'
        ),
        Note (
            note_id = 3
            person_id = 1
            content = 'Well, sort of useful'
            timestamp = '2019-03-06 22:17:54'
        )
    ]
)

La différence importante entre les deux est que la clé primaire de la La personne et Remarque les objets ont été initialisés. Le moteur de base de données s'en est occupé, car les objets ont été créés en raison de la fonctionnalité d'auto-incrémentation des clés primaires décrite dans la partie 2.

De plus, le person_id clé étrangère dans tous les Remarque instances a été initialisée pour référencer son parent. Cela se produit en raison de l'ordre dans lequel le La personne et Remarque les objets sont créés dans la base de données.

SQLAlchemy est conscient de la relation entre La personne et Remarque objets. Lorsqu'un La personne l'objet est commis à la la personne table de base de données, SQLAlchemy obtient le person_id valeur de clé primaire. Cette valeur est utilisée pour initialiser la valeur de clé étrangère de person_id dans un Remarque objet avant qu'il ne soit engagé dans la base de données.

SQLAlchemy s’occupe de ce travail d’entretien de la base de données en raison des informations que vous avez transmises lors de la Notes de personne attribut a été initialisé avec le db.relationship (...) objet.

De plus, le Person.timestamp L'attribut a été initialisé avec l'horodatage actuel.

Lancer le build_database.py programme à partir de la ligne de commande (dans l’environnement virtuel, la nouvelle base de données sera recréée avec les nouveaux ajouts, ce qui la préparera pour une utilisation avec l’application Web. Cette ligne de commande reconstruira la base de données:

$ python build_database.py

le build_database.py programme utilitaire ne génère aucun message s’il s’exécute correctement. S'il y a une exception, une erreur sera imprimée à l'écran.

Mettre à jour l'API REST

Vous avez mis à jour les modèles SQLAlchemy et les avez utilisés pour mettre à jour le people.db base de données. Il est maintenant temps de mettre à jour l’API REST pour permettre l’accès aux nouvelles informations de notes. Voici l’API REST que vous avez construite dans la partie 2:

action HTTP Verb Chemin de l'URL La description
Créer POSTER / api / personnes URL pour créer une nouvelle personne
Lis OBTENIR / api / personnes URL pour lire une collection de personnes
Lis OBTENIR / api / people / person_id URL pour lire une seule personne par person_id
Mettre à jour METTRE / api / people / person_id URL pour mettre à jour une personne existante en person_id
Effacer EFFACER / api / people / person_id URL pour supprimer une personne existante par person_id

L'API REST ci-dessus fournit des chemins d'accès URL HTTP à des collections d'objets et aux objets eux-mêmes. Vous pouvez obtenir une liste de personnes ou interagir avec une seule personne à partir de cette liste. Ce style de chemin affine ce qui est retourné de gauche à droite, devenant plus granulaire au fur et à mesure.

Vous continuerez ce modèle de gauche à droite pour obtenir plus de détails et accéder aux collections de notes. Voici l’API REST étendue que vous allez créer afin de fournir des notes à l’application Web des mini-blogs:

action HTTP Verb Chemin de l'URL La description
Créer POSTER / api / people / person_id / notes URL pour créer une nouvelle note
Lis OBTENIR / api / people / person_id / notes / note_id URL permettant de lire la note d’une personne
Mettre à jour METTRE api / people / person_id / notes / note_id URL permettant de mettre à jour la note d’une seule personne
Effacer EFFACER api / people / person_id / notes / note_id URL permettant de supprimer la note d’une personne
Lis OBTENIR / api / notes URL pour obtenir toutes les notes pour toutes les personnes triées par note.horodatage

Il y a deux variations dans le Remarques partie de l'API REST par rapport à la convention utilisée dans le personnes section:

  1. Il n'y a pas d'URL définie pour obtenir tous les Remarques associé à une personne, seulement une URL pour obtenir une seule note. Cela aurait rendu l’API REST complète d’une certaine manière, mais l’application Web que vous créerez plus tard n’a pas besoin de cette fonctionnalité. Par conséquent, il a été laissé de côté.

  2. Il y a l'inclusion de la dernière URL / api / notes. Il s'agit d'une méthode pratique créée pour l'application Web. Il sera utilisé dans le mini-blog de la page d'accueil pour afficher toutes les notes du système. Il n’existe pas de moyen d’obtenir facilement ces informations en utilisant le style de cheminement de l’API REST tel que conçu. raccourci a été ajouté.

Comme dans la partie 2, l’API REST est configurée dans le swagger.yml fichier.

Implémenter l'API

Avec l’API REST mise à jour définie dans la swagger.yml fichier, vous devez mettre à jour l’implémentation fournie par les modules Python. Cela signifie mettre à jour les fichiers de module existants, comme models.py et people.pyet créer un nouveau fichier de module appelé notes.py mettre en place un soutien pour Remarques dans l'API REST étendue.

Mettre à jour la réponse JSON

L'API REST a pour objectif d'extraire des données JSON utiles de la base de données. Maintenant que vous avez mis à jour SQLAlchemy La personne et créé le Remarque modèles, vous devez également mettre à jour les modèles de schéma Marshmallow. Comme vous vous en souvenez peut-être, Marshmallow est le module qui traduit les objets SQLAlchemy en objets Python adaptés à la création de chaînes JSON.

Les schémas Marshmallow mis à jour et nouvellement créés se trouvent dans la models.py module, qui sont expliqués ci-dessous, et qui ressemble à ceci:

    1 classe PersonSchema(ma.ModelSchema):
    2     classe Méta:
    3         modèle = La personne
    4         sqla_session = db.session
    5     Remarques = des champs.Imbriqué('PersonNoteSchema', défaut=[], beaucoup=Vrai)
    6 
    7 classe PersonNoteSchema(ma.ModelSchema):
    8     "" "
    9                 Cette classe existe pour contourner un problème de récursivité
dix                 "" "
11     note_id = des champs.Int()
12     person_id = des champs.Int()
13     contenu = des champs.Str()
14     horodatage = des champs.Str()
15 
16 classe NoteSchema(ma.ModelSchema):
17     classe Méta:
18         modèle = Remarque
19         sqla_session = db.session
20     la personne = des champs.Imbriqué('NotePersonSchema', défaut=Aucun)
21 
22 classe NotePersonSchema(ma.ModelSchema):
23     "" "
24                 Cette classe existe pour contourner un problème de récursivité
25                 "" "
26     person_id = des champs.Int()
27     lname = des champs.Str()
28     fname = des champs.Str()
29     horodatage = des champs.Str()

Il y a des choses intéressantes dans les définitions ci-dessus. le PersonSchema la classe a une nouvelle entrée: le Remarques attribut défini à la ligne 5. Cela le définit comme une relation imbriquée au PersonNoteSchema. Il affichera par défaut une liste vide si rien n'est présent dans SQLAlchemy. Remarques relation. le plusieurs = vrai Ce paramètre indique qu'il s'agit d'une relation un-à-plusieurs. Marshmallow va donc sérialiser tous les Remarques.

le PersonNoteSchema classe définit ce qu'un Remarque objet ressemble à Marshmallow sérialise le Remarques liste. le NoteSchema définit ce qu'est une SQLAlchemy Remarque objet ressemble en termes de guimauve. Notez qu'il a un la personne attribut. Cet attribut provient de SQLAlchemy db.relationship (...) paramètre de définition backref = 'personne'. le la personne La définition de guimauve est imbriquée, mais parce qu’elle n’a pas le plusieurs = vrai paramètre, il n'y a qu'un seul la personne connecté.

le NotePersonSchema classe définit ce qui est imbriqué dans le NoteSchema.person attribut.

Personnes

Maintenant que vous avez les schémas en place pour travailler avec la relation un-à-plusieurs entre La personne et Remarque, vous devez mettre à jour le person.py et créer le note.py modules afin de mettre en œuvre une API REST fonctionnelle.

le people.py module a besoin de deux changements. La première consiste à importer le Remarque classe, avec le La personne classe en haut du module. Alors seulement read_one (person_id) doit changer afin de gérer la relation. Cette fonction ressemblera à ceci:

    1 def read_one(person_id):
    2     "" "
    3                 Cette fonction répond à une requête pour / api / people / person_id
    4                 avec une personne correspondante de personnes
    5 
    6                 : param person_id: Id de la personne à trouver
    7                 : return: identifiant correspondant
    8                 "" "
    9     # Construire la requête initiale
dix     la personne = (
11         La personne.question.filtre(La personne.person_id == person_id)
12         .outerjoin(Remarque)
13         .one_or_none()
14     )
15 
16     # Did we find a person?
17     si la personne est ne pas Aucun:
18 
19         # Serialize the data for the response
20         person_schema = PersonSchema()
21         Les données = person_schema.déverser(la personne).Les données
22         revenir Les données
23 
24     # Otherwise, nope, didn't find that person
25     autre:
26         avorter(404, F"Person not found for Id: person_id")

The only difference is line 12: .outerjoin(Note). An outer join (left outer join in SQL terms) is necessary for the case where a user of the application has created a new la personne object, which has no Remarques related to it. The outer join ensures that the SQL query will return a la personne object, even if there are no Remarque rows to join with.

At the start of this article, you saw how person and note data could be represented in a single, flat table, and all of the disadvantages of that approach. You also saw the advantages of breaking that data up into two tables, la personne et Remarque, with a relationship between them.

Until now, we’ve been working with the data as two distinct, but related, items in the database. But now that you’re actually going to use the data, what we essentially want is for the data to be joined back together. This is what a database join does. It combines data from two tables together using the primary key to foreign key relationship.

A join is kind of a boolean et operation because it only returns data if there is data in both tables to combine. If, for example, a la personne row exists but has no related Remarque row, then there is nothing to join, so nothing is returned. This isn’t what you want for read_one(person_id).

This is where the outer join comes in handy. It’s a kind of boolean ou opération. It returns la personne data even if there is no associated Remarque data to combine with. This is the behavior you want for read_one(person_id) to handle the case of a newly created La personne object that has no notes yet.

You can see the complete people.py in the article repository.

Remarques

You’ll create a notes.py module to implement all the Python code associated with the new note related REST API definitions. In many ways, it works like the people.py module, except it must handle both a person_id et un note_id as defined in the swagger.yml configuration file. As an example, here is read_one(person_id, note_id):

    1 def read_one(person_id, note_id):
    2     "" "
    3                 This function responds to a request for
    4                 /api/people/person_id/notes/note_id
    5                 with one matching note for the associated person
    6 
    7                 :param person_id:       Id of person the note is related to
    8                 :param note_id:         Id of the note
    9                 :return:                json string of note contents
dix                 "" "
11     # Query the database for the note
12     Remarque = (
13         Remarque.question.joindre(La personne, La personne.person_id == Remarque.person_id)
14         .filtre(La personne.person_id == person_id)
15         .filtre(Remarque.note_id == note_id)
16         .one_or_none()
17     )
18 
19     # Was a note found?
20     si Remarque est ne pas Aucun:
21         note_schema = NoteSchema()
22         Les données = note_schema.déverser(Remarque).Les données
23         revenir Les données
24 
25     # Otherwise, nope, didn't find that note
26     autre:
27         avorter(404, F"Note not found for Id: note_id")

The interesting parts of the above code are lines 12 to 17:

  • Line 13 begins a query against the Remarque SQLAlchemy objects and joins to the related La personne SQLAlchemy object comparing person_id from both La personne et Remarque.
  • Line 14 filters the result down to the Remarque objects that has a Person.person_id equal to the passed in person_id parameter.
  • Line 15 filters the result further to the Remarque object that has a Note.note_id equal to the passed in note_id parameter.
  • Line 16 renvoie le Remarque object if found, or Aucun if nothing matching the parameters is found.

You can check out the complete notes.py.

Updated Swagger UI

The Swagger UI has been updated by the action of updating the swagger.yml file and creating the URL endpoint implementations. Below is a screenshot of the updated UI showing the Notes section with the GET /api/people/person_id/notes/note_id expanded:

Swagger UI with notes part 3

Mini-Blogging Web Application

The web application has been substantially changed to show its new purpose as a mini-blogging application. It has three pages:

  1. The home page (localhost:5000/), which shows all of the blog messages (notes) sorted from newest to oldest

  2. The people page (localhost:5000/people), which shows all the people in the system, sorted by last name, and also allows the user to create a new person and update or delete an existing one

  3. The notes page (localhost:5000/people/person_id/notes), which shows all the notes associated with a person, sorted from newest to oldest, and also allows the user to create a new note and update or delete an existing one

There are two buttons on every page of the application:

  1. le Home bouton will navigate to the home screen.
  2. le Personnes bouton navigates to the /people screen, showing all people in the database.

These two buttons are present on every screen in the application as a way to get back to a starting point.

Page d'accueil

Below is a screenshot of the home page showing the initialized database contents:

Flask Connexion Rest Home Page Part 3

The functionality of this page works like this:

  • Double-clicking on a person’s name will take the user to the /people/person_id page, with the editor section filled in with the person’s first and last names and the update and reset buttons enabled.

  • Double-clicking on a person’s note will take the user to the /people/person_id/notes/note_id page, with the editor section filled in with the note’s contents and the Mettre à jour et Reset buttons enabled.

People Page

Below is a screenshot of the people page showing the people in the initialized database:

Flask Connexion Rest People Page Part 3

The functionality of this page works like this:

  • Single-clicking on a person’s name will populate the editor section of the page with the person’s first and last name, disabling the Create button, and enabling the Mettre à jour et Delete buttons.

  • Double clicking on a person’s name will navigate to the notes pages for that person.

The functionality of the editor works like this:

  • If the first and last name fields are empty, the Create et Reset buttons are enabled. Entering a new name in the fields and clicking Create will create a new person and update the database and re-render the table below the editor. Clicking Reset will clear the editor fields.

  • If the first and last name fields have data, the user navigated here by double-clicking the person’s name from the home screen. Dans ce cas, le Mettre à jour, Delete, et Reset buttons are enabled. Changing the first or last name and clicking Mettre à jour will update the database and re-render the table below the editor. Clicking Delete will remove the person from the database and re-render the table.

Notes Page

Below is a screenshot of the notes page showing the notes for a person in the initialized database:

Flask Connexion Rest Notes Page Part 3

The functionality of this page works like this:

  • Single-clicking on a note will populate the editor section of the page with the notes content, disabling the Create button, and enabling the Mettre à jour et Delete buttons.

  • All other functionality of this page is in the editor section.

The functionality of the editor works like this:

  • If the note content field is empty, then the Create et Reset buttons are enabled. Entering a new note in the field and clicking Create will create a new note and update the database and re-render the table below the editor. Clicking Reset will clear the editor fields.

  • If the note field has data, the user navigated here by double-clicking the person’s note from the home screen. Dans ce cas, le Mettre à jour, Delete, et Reset buttons are enabled. Changing the note and clicking Mettre à jour will update the database and re-render the table below the editor. Clicking Delete will remove the note from the database and re-render the table.

Web Application

This article is primarily focused on how to use SQLAlchemy to create relationships in the database, and how to extend the REST API to take advantage of those relationships. As such, the code for the web application didn’t get much attention. When you look at the web application code, keep an eye out for the following features:

  • Each page of the application is a fully formed single page web application.

  • Each page of the application is driven by JavaScript following an MVC (Model/View/Controller) style of responsibility delegation.

  • The HTML that creates the pages takes advantage of the Jinja2 inheritance functionality.

  • The hardcoded JavaScript table creation has been replaced by using the Handlebars.js templating engine.

  • The timestamp formating in all of the tables is provided by Moment.js.

You can find the following code in the repository for this article:

All of the example code for this article is available in the GitHub repository for this article. This contains all of the code related to this article, including all of the web application code.

Conclusion

Congratulations are in order for what you’ve learned in this article! Knowing how to build and use database relationships gives you a powerful tool to solve many difficult problems. There are other relationship besides the one-to-many example from this article. Other common ones are one-to-one, many-to-many, and many-to-one. All of them have a place in your toolbelt, and SQLAlchemy can help you tackle them all!

For more information about databases, you can check out these tutorials. You can also set up Flask to use SQLAlchemy. You can check out Model-View-Controller (MVC) more information about the pattern used in the web application JavaScript code.

In Part 4 of this series, you’ll focus on the HTML, CSS, and JavaScript files used to create the web application.