Travailler avec une base de données dans Flask: de juin à juin

J'ai été inspiré pour écrire cet article par le désir d'aider les mêmes nouveaux arrivants en Python en général et de travailler avec Flask en particulier, comme moi-même. Tout en travaillant sur la tâche d'une explication holistique et compréhensible dans le style que nous, les nouveaux arrivants, n'avons pas trouvé, comme. J'ai dû chercher les informations petit à petit. Il n'y aura pas de photos. Un article purement technique. Les personnes expérimentées seront reconnaissantes pour leurs commentaires et leurs conseils sur l'amélioration du code.

Alors, commençons.

J'ai commencé à apprendre Python juste après la nouvelle année. Après quatre mois, je me suis rendu compte que la théorie, ainsi que la formation à des tâches de fiction qui ne vont pas dans la prod, vous n'apprendrez pas particulièrement, j'ai décidé de chercher des tâches de "combat". Pour ce faire, j'ai demandé à mes amis s'ils avaient de vraies tâches à programmer. Un "mon ami ami" m'a demandé d'implémenter un bot pour Telegram (l'essence du bot me manque; cela n'a pas d'importance - j'avais besoin de prendre n'importe quelle tâche qui a un client spécifique et de l'implémenter comme le souhaite le client).

À la recherche d'une solution, il est naturel que la première chose avec laquelle j'ai commencé soit les fameux frameworks Python Telegram Bot et pyTelegramBotAPI.. Au début, en tant que débutant, il me semblait juste une trouvaille - il était possible surtout sans comprendre les nuances du code de commencer rapidement à «scier» le vrai bot. Pendant quelques jours, je suis tombé sur le fait que je ne pouvais pas créer les fonctionnalités dont j'avais besoin. Il semble qu'il ait tout fait selon la documentation, mais cela n'a pas été décidé. Ensuite, j'ai réalisé que je ne comprenais pas du tout comment le cadre fonctionnait «sous le capot» et pourquoi quelque chose qui devrait fonctionner ne fonctionne pas pour moi; où et quelles commandes doivent être appelées et avec quelles méthodes. En général, j'ai décidé de mettre de côté le cadre et d'essayer de comprendre plus profondément comment l'API Telegram elle-même fonctionne et comment vous pouvez travailler directement avec elle, ce qui me donnera plus de contrôle sur la situation et me permettra également d'étudier de plus près toute la cuisine de travailler avec l'API. Il est possible que je ne revienne pas à l'utilisation des frameworks Python Telegram Bot et pyTelegramBotAPI comme inutiles.Ou peut-être que je reviendrai pour simplifier mon travail sur la création de mon propre vélo avec l'API Telegram. Mais même si je reviens, je comprendrai beaucoup plus sur le travail de ces cadres.

J'ai eu un court cours non révisé sur Udemy juste pour créer un bot pour Telegram. Au moment d'écrire ces lignes, c'était le seul cours Udemy où une personne résoudrait un problème sans le Python Telegram Bot et pyTelegramBotAPI (je ne donnerai pas de lien, afin que ce ne soit pas une publicité). Pour le résoudre, il a utilisé Flask. Ici, au fait, après avoir parcouru une certaine «voie militaire», j'ai eu l'occasion d'écrire mon cours sur ce sujet, bien que, bien sûr, il soit trop tôt - je n'apporterai aucune valeur particulière. Mais si vous, un programmeur plus expérimenté lisant cet article, en savez beaucoup à ce sujet, alors vous pouvez créer votre propre cours et je serai heureux de vous l'acheter pour 10,99 $ (un prix "discount" typique pour Udemy) pour apprendre quelque chose de nouveau.

En général, à partir du cours, j'ai réalisé que Flask me permettra de faciliter la vie pour le traitement des demandes GET et POST.

Étant donné que cet article est spécifiquement consacré à l'utilisation de la base de données, j'en parlerai. Bien que, tout le temps, il soit tentant de décrire d'autres subtilités, telles que: le transfert des paramètres de connexion vers un fichier «secret», la réception et le traitement des données reçues de l'API Telegram, comment recevoir des webhooks de Telegram sur un ordinateur local sans certificat SSL. Si vous êtes intéressé, faites-le moi savoir et j'écrirai un article séparé.

Environ 3-4 jours de mon temps de juin, il m'a fallu comprendre que je devais m'éloigner du cadre avant de pouvoir recevoir en toute confiance des données du télégramme, traiter le texte (analyse) et en fonction de ce que l'utilisateur a écrit, lui envoyer les commandes nécessaires, ainsi que les boutons (y compris écraser le message et changer les boutons).

La prochaine étape pour moi a été de connecter la base de données pour stocker les utilisateurs qui y travaillent avec le bot, ainsi que les données sur leur interaction dans le bot dans le cadre de la tâche à laquelle il était confronté.

La première chose que j'ai faite dans Google a été un article d'Habré avec une traduction du méga-manuel Miguel Grinberg (l' original de son blog est ici ).

Le quatrième chapitre traite de la connexion à une base de données à l'aide de Flask et SQLAlchemy ORM. Puis j'ai pensé: "Wow, comme c'est cool, maintenant le problème est résolu." Curieusement, mais la structure de fichiers suggérée par l'auteur ne fonctionnait pas pour moi.

J'ai fait par analogie comme la sienne:

microblog\
  venv\
  app\
    __init__.py
    models.py
  main.py

Dans main.py, je fais tout le travail principal avec l'API Telegram.
Dans app \ models.py, je crée des classes pour la base de données.
Dans l'application \ __ init__.py, tout a été fait comme l'a écrit Miguel Grinberg.

Mais pour une raison quelconque, dans main.py, ils ne voulaient pas me retirer de l'application import db, app. J'ai passé environ une heure à chercher des problèmes, des solutions sur Internet. En conséquence, Oleg Molchanov est tombé sur une chaîne YouTube et sa vidéo "Création d'un blog sur Flask (leçons) - Création de publications (modèles) et SQLAlchemy". Là, j'ai regardé comment il établit une connexion à la base de données et j'ai essayé de procéder de cette façon (sans mettre le fichier models.py dans le répertoire de l'application, sans créer __init__.py.

En général, maintenant la structure de mon projet est simple à déshonorer (et un peu moche, ce qui m'embrouille, peut-être à l'avenir, je comprendrai comment améliorer la structure):

image

Comme vous pouvez le voir, j'ai app.py, models.py, main.py, app.sqlite (le reste des fichiers ne se rapporte pas au sujet actuel).

Dans app.py, j'ai ce code:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from config import Config

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)

Dans models.py:

from datetime import datetime
from app import db

class Users(db.Model):
    #   
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(120), index=True, unique=True)
    last_name = db.Column(db.String(128))
    first_name = db.Column(db.String(128))
    created = db.Column(db.DateTime, default=datetime.now())
    tasks = db.relationship('Tasks', backref='tasks')

    #     
    # def __init__(self, *args, **kwargs):
    #     super(Users, self).__init__(*args, **kwargs)

    def __init__(self, username, last_name, first_name):
        self.username = username
        self.last_name = last_name
        self.first_name = first_name

    def __repr__(self):
        return '<User {}>'.format(self.username)


class Tasks(db.Model):
    #    
    __tablename__ = 'tasks'
    id = db.Column(db.Integer, primary_key=True)
    owner_id = db.Column(db.Integer(), db.ForeignKey('users.id'))
    name = db.Column(db.String(120), index=True)
    start = db.Column(db.Boolean, default=False)
    finish = db.Column(db.Boolean, default=False)
    created_on = db.Column(db.DateTime, default=datetime.now())
    updated_on = db.Column(db.DateTime(), default=datetime.utcnow, onupdate=datetime.utcnow)

    #     
    def __init__(self, *args, **kwargs):
        super(Tasks, self).__init__(*args, **kwargs)

    # def __init__(self, name, last_name, first_name):
    #     self.name = name
    #     self.last_name = last_name
    #     self.first_name = first_name

    def __repr__(self):
        return '<Tasks {}>'.format(self.name)

Dans config.py:

import os
from dotenv import load_dotenv
load_dotenv()

basedir = os.path.abspath(os.path.dirname(__file__))


#    
class Config(object):
    DEBUG = True
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    # SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format(user=os.environ.get('POSTGRES_USER'),
    #                                                                                 pw=os.environ.get('POSTGRES_PW'),
    #                                                                                 url=os.environ.get('POSTGRES_URL'),
    #
    #                                                                                 db=os.environ.get('POSTGRES_DB'))
    SQLALCHEMY_DATABASE_URI = (os.environ.get('DATABASE_URL') or
                               'sqlite:///' + os.path.join(basedir, 'app.sqlite')) + '?check_same_thread=False'
    SQLALCHEMY_TRACK_MODIFICATIONS = False  # silence the deprecation warning

Comme vous l'avez déjà compris, dans les paramètres, j'ai supprimé les valeurs directes de la connexion et les ai rendues en .env afin qu'elles ne brillent pas sur le prod ou dans SVN.

Tout d'abord, app.db (où app.sqlite est maintenant) m'a été écrit dans SQLALCHEMY_DATABASE_URI en tant que base de données. Oui, oui, et écrit, comme indiqué dans les instructions Miguel Grinberg. J'espère que vous comprenez que vous devez le remplacer par .sqlite. Avant cela, j'ai pensé à une heure après de douloureuses tentatives pour exécuter le code.

Vous pouvez créer des bases de données et des tables dans sqlite en accédant à la console de débogage (par exemple, dans PyCharm, j'ai Outils -> Python ou Debug Console:

image

Ici, nous connectons d'abord la méthode dont nous avons besoin dans le fichier souhaité (je l'ai depuis les modèles import db), et après une connexion réussie, spécifiez la commande db.create_all (). Après cela, une base de données avec toutes les tables nécessaires sera créée. Cela vaut la peine de savoir que si je fais depuis app import db et que j'exécute la commande db.create_all (), alors le fichier de base de données semble être créé, mais ce sera une sorte de non-sens, et pas la base de données (je n'ai pas compris pourquoi).

Lorsque j'ai résolu ce problème, j'ai pensé à nouveau qu'il ne restait plus de difficultés. Il ne reste plus qu'à écrire une fonction pour main.py, qui, sous certains événements, écrira des données du télégramme dans la base de données. Eh bien, je me suis connecté à partir de modèles d'importation d'utilisateurs et au bon endroit appelé la fonction:

try:
    find_user_in_db(username, first_name, last_name)
except NameError:
    print("user  ")
except:
    print("An exception occurred")

def add_users_to_db(username, last_name, first_name):
    """
        
    :return:
    """
    data = Users(username, last_name, first_name)
    db.session.add(data)
    db.session.commit()

def find_user_in_db(username, first_name, last_name):
    """
               
    :param username:
    :param first_name:
    :param last_name:
    :return:
    """
    user = db.session.query(Users).filter(Users.username == f'{username}').all()
    if not user:
        add_users_to_db(username, first_name, last_name)

Dans la dernière fonction, d'abord au lieu de user = db.session.query (Users) .filter (Users.username == f '{username}'). First_or_404 () se tenait Users.query.filter_by (username = '{username}'). first_or_404 (). Ici, j'ai également passé environ une demi-heure pour comprendre que cette requête dans la base de données ne fonctionne pas et ne reçoit aucune donnée, de sorte que la requête vers la base de données n'est pas envoyée. Google a lancé un petit article dans lequel les gens disaient qu'il valait mieux utiliser db.session.query (Users). Pourquoi donc, je xs, n'a pas perdu de temps à analyser. Après cela, les données ont commencé à être écrites dans la base de données.

Ceci conclut cet article car j'ai décrit ce que je voulais décrire et résolu le problème de connexion de la base de données à Flask.

Cet article est écrit uniquement pour la raison que nous aimons «June» lors de la lecture des instructions. J'ai moi-même également essayé de trouver des instructions pour connecter la base de données à Flask. Je n'ai pas trouvé d'instructions complètes, j'ai donc dû traverser les obstacles. Eh bien, j'ai décidé de décrire mon expérience comme la prochaine à chercher des instructions toutes faites.

PS Qui est à la recherche d'une solution prête à l'emploi, alors je demande au référentiel sur GitHub

PSS je serai heureux si quelqu'un fait une revue de code et donne ses recommandations d'amélioration.

Merci pour l'attention.

All Articles