Database
 sql >> Base de données >  >> RDS >> Database

Créer une application Web simple avec Bottle, SQLAlchemy et l'API Twitter

En octobre dernier, nous avons mis au défi notre public PyBites de créer une application Web pour mieux naviguer dans le flux Daily Python Tip. Dans cet article, je vais partager ce que j'ai construit et appris en cours de route.

Dans cet article, vous apprendrez :

  1. Comment cloner le dépôt du projet et configurer l'application.
  2. Comment utiliser l'API Twitter via le module Tweepy pour charger les tweets.
  3. Comment utiliser SQLAlchemy pour stocker et gérer les données (conseils et hashtags).
  4. Comment créer une application Web simple avec Bottle, un micro-framework Web similaire à Flask.
  5. Comment utiliser le framework pytest pour ajouter des tests.
  6. Comment les conseils de Better Code Hub ont conduit à un code plus facile à gérer.

Si vous voulez suivre, lire le code en détail (et éventuellement contribuer), je vous suggère de bifurquer le dépôt. Commençons.


Configuration du projet

Tout d'abord, Les espaces de noms sont une excellente idée alors faisons notre travail dans un environnement virtuel. En utilisant Anaconda, je le crée comme ceci :

$ virtualenv -p <path-to-python-to-use> ~/virtualenvs/pytip

Créez une base de données de production et de test dans Postgres :

$ psql
psql (9.6.5, server 9.6.2)
Type "help" for help.

# create database pytip;
CREATE DATABASE
# create database pytip_test;
CREATE DATABASE

Nous aurons besoin d'informations d'identification pour nous connecter à la base de données et à l'API Twitter (créez d'abord une nouvelle application). Selon les meilleures pratiques, la configuration doit être stockée dans l'environnement, pas dans le code. Placez les variables d'environnement suivantes à la fin de ~/virtualenvs/pytip/bin/activate , le script qui gère l'activation/désactivation de votre environnement virtuel en veillant à mettre à jour les variables de votre environnement :

export DATABASE_URL='postgres://postgres:password@localhost:5432/pytip'
# twitter
export CONSUMER_KEY='xyz'
export CONSUMER_SECRET='xyz'
export ACCESS_TOKEN='xyz'
export ACCESS_SECRET='xyz'
# if deploying it set this to 'heroku'
export APP_LOCATION=local

Dans la fonction de désactivation du même script, je les ai désactivées afin que nous gardions les choses hors de la portée du shell lors de la désactivation (quittant) l'environnement virtuel :

unset DATABASE_URL
unset CONSUMER_KEY
unset CONSUMER_SECRET
unset ACCESS_TOKEN
unset ACCESS_SECRET
unset APP_LOCATION

C'est le bon moment pour activer l'environnement virtuel :

$ source ~/virtualenvs/pytip/bin/activate

Clonez le référentiel et, avec l'environnement virtuel activé, installez les exigences :

$ git clone https://github.com/pybites/pytip && cd pytip
$ pip install -r requirements.txt

Ensuite, nous importons la collection de tweets avec :

$ python tasks/import_tweets.py

Ensuite, vérifiez que les tableaux ont été créés et que les tweets ont été ajoutés :

$ psql

\c pytip

pytip=# \dt
          List of relations
 Schema |   Name   | Type  |  Owner
--------+----------+-------+----------
 public | hashtags | table | postgres
 public | tips     | table | postgres
(2 rows)

pytip=# select count(*) from tips;
 count
-------
   222
(1 row)

pytip=# select count(*) from hashtags;
 count
-------
    27
(1 row)

pytip=# \q

Exécutons maintenant les tests :

$ pytest
========================== test session starts ==========================
platform darwin -- Python 3.6.2, pytest-3.2.3, py-1.4.34, pluggy-0.4.0
rootdir: realpython/pytip, inifile:
collected 5 items

tests/test_tasks.py .
tests/test_tips.py ....

========================== 5 passed in 0.61 seconds ==========================

Et enfin lancez l'application Bottle avec :

$ python app.py

Accédez à http://localhost:8080 et voilà :vous devriez voir les conseils triés par ordre décroissant de popularité. En cliquant sur un lien hashtag à gauche ou en utilisant le champ de recherche, vous pouvez facilement les filtrer. Ici, nous voyons les pandas conseils par exemple :

Le design que j'ai réalisé avec MUI - un cadre CSS léger qui suit les directives de conception de matériaux de Google.



Détails de mise en œuvre


La base de données et SQLAlchemy

J'ai utilisé SQLAlchemy pour s'interfacer avec la base de données afin d'éviter d'avoir à écrire beaucoup de SQL (redondant).

Dans tips/models.py , nous définissons nos modèles - Hashtag et Tip - que SQLAlchemy mappera sur les tables DB :

from sqlalchemy import Column, Sequence, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Hashtag(Base):
    __tablename__ = 'hashtags'
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    name = Column(String(20))
    count = Column(Integer)

    def __repr__(self):
        return "<Hashtag('%s', '%d')>" % (self.name, self.count)


class Tip(Base):
    __tablename__ = 'tips'
    id = Column(Integer, Sequence('id_seq'), primary_key=True)
    tweetid = Column(String(22))
    text = Column(String(300))
    created = Column(DateTime)
    likes = Column(Integer)
    retweets = Column(Integer)

    def __repr__(self):
        return "<Tip('%d', '%s')>" % (self.id, self.text)

Dans tips/db.py , on importe ces modèles, et maintenant c'est facile de travailler avec la BD, par exemple pour s'interfacer avec le Hashtag modèle :

def get_hashtags():
    return session.query(Hashtag).order_by(Hashtag.name.asc()).all()

Et :

def add_hashtags(hashtags_cnt):
    for tag, count in hashtags_cnt.items():
        session.add(Hashtag(name=tag, count=count))
    session.commit()


Interroger l'API Twitter

Nous devons récupérer les données de Twitter. Pour cela, j'ai créé tasks/import_tweets.py . J'ai emballé cela sous tâches car il doit être exécuté dans un cronjob quotidien pour rechercher de nouveaux conseils et mettre à jour les statistiques (nombre de likes et de retweets) sur les tweets existants. Par souci de simplicité, je fais recréer les tables quotidiennement. Si nous commençons à nous fier aux relations FK avec d'autres tables, nous devons définitivement choisir les instructions de mise à jour plutôt que supprimer + ajouter.

Nous avons utilisé ce script dans la configuration du projet. Voyons ce qu'il fait plus en détail.

Tout d'abord, nous créons un objet de session API que nous transmettons à tweepy.Cursor. Cette fonctionnalité de l'API est vraiment sympa :elle s'occupe de la pagination, en parcourant la chronologie. Pour le nombre de pourboires - 222 au moment où j'écris ceci - c'est vraiment rapide. Le exclude_replies=True et include_rts=False les arguments sont pratiques car nous ne voulons que les propres tweets de Daily Python Tip (et non les re-tweets).

Extraire les hashtags des conseils nécessite très peu de code.

Tout d'abord, j'ai défini une regex pour une balise :

TAG = re.compile(r'#([a-z0-9]{3,})')

Ensuite, j'ai utilisé findall pour obtenir toutes les balises.

Je les ai passés à collections.Counter qui renvoie un objet de type dict avec les balises comme clés et compte comme des valeurs, classées par ordre décroissant de valeurs (les plus courantes). J'ai exclu la balise python trop courante qui fausserait les résultats.

def get_hashtag_counter(tips):
    blob = ' '.join(t.text.lower() for t in tips)
    cnt = Counter(TAG.findall(blob))

    if EXCLUDE_PYTHON_HASHTAG:
        cnt.pop('python', None)

    return cnt

Enfin, le import_* fonctions dans tasks/import_tweets.py faire l'importation réelle des tweets et des hashtags, en appelant add_* Méthodes DB des astuces répertoire/package.



Créer une application Web simple avec Bottle

Une fois ce travail préalable effectué, créer une application Web est étonnamment facile (ou pas si surprenant si vous avez déjà utilisé Flask).

Tout d'abord, rencontrez Bottle :

Bottle est un micro-framework Web WSGI rapide, simple et léger pour Python. Il est distribué en tant que module de fichier unique et n'a aucune dépendance autre que la bibliothèque standard Python.

Joli. L'application Web résultante comprend <30 LOC et peut être trouvée dans app.py.

Pour cette application simple, une seule méthode avec un argument de balise facultatif suffit. Semblable à Flask, le routage est géré avec des décorateurs. S'il est appelé avec une balise, il filtre les conseils sur la balise, sinon il les affiche tous. Le décorateur de vue définit le modèle à utiliser. Comme Flask (et Django), nous renvoyons un dict à utiliser dans le modèle.

@route('/')
@route('/<tag>')
@view('index')
def index(tag=None):
    tag = tag or request.query.get('tag') or None
    tags = get_hashtags()
    tips = get_tips(tag)

    return {'search_tag': tag or '',
            'tags': tags,
            'tips': tips}

Selon la documentation, pour travailler avec des fichiers statiques, vous ajoutez cet extrait en haut, après les importations :

@route('/static/<filename:path>')
def send_static(filename):
    return static_file(filename, root='static')

Enfin, nous voulons nous assurer que nous ne fonctionnons qu'en mode débogage sur localhost, d'où le APP_LOCATION variable env que nous avons définie dans la configuration du projet :

if os.environ.get('APP_LOCATION') == 'heroku':
    run(host="0.0.0.0", port=int(os.environ.get("PORT", 5000)))
else:
    run(host='localhost', port=8080, debug=True, reloader=True)


Modèles de bouteilles

Bottle est livré avec un moteur de modèle intégré rapide, puissant et facile à apprendre appelé SimpleTemplate.

Dans le sous-répertoire des vues, j'ai défini un header.tpl , index.tpl , et footer.tpl . Pour le nuage de tags, j'ai utilisé un simple CSS en ligne augmentant la taille des tags en nombre, voir header.tpl :

% for tag in tags:
  <a style="font-size: {{ tag.count/10 + 1 }}em;" href="/{{ tag.name }}">#{{ tag.name }}</a>&nbsp;&nbsp;
% end

Dans index.tpl on boucle sur les astuces :

% for tip in tips:
  <div class='tip'>
    <pre>{{ !tip.text }}</pre>
    <div class="mui--text-dark-secondary"><strong>{{ tip.likes }}</strong> Likes / <strong>{{ tip.retweets }}</strong> RTs / {{ tip.created }} / <a href="https://twitter.com/python_tip/status/{{ tip.tweetid }}" target="_blank">Share</a></div>
  </div>
% end
{{ tip.likes }} J'aime / {{ tip.retweets }} RT / {{ tip.created }} / Partager
% fin

Si vous connaissez Flask et Jinja2, cela devrait vous sembler très familier. L'intégration de Python est encore plus simple, avec moins de saisie—(% ... contre {% ... %} ).

Tous les CSS, images (et JS si nous l'utilisons) vont dans le sous-dossier statique.

Et c'est tout ce qu'il y a à faire pour créer une application Web de base avec Bottle. Une fois que vous avez correctement défini la couche de données, c'est assez simple.



Ajouter des tests avec pytest

Rendons maintenant ce projet un peu plus robuste en ajoutant quelques tests. Tester la base de données a nécessité de creuser un peu plus dans le framework pytest, mais j'ai fini par utiliser le décorateur pytest.fixture pour configurer et supprimer une base de données avec quelques tweets de test.

Au lieu d'appeler l'API Twitter, j'ai utilisé des données statiques fournies dans tweets.json .Et, plutôt que d'utiliser la base de données en direct, dans tips/db.py , je vérifie si pytest est l'appelant (sys.argv[0] ). Si c'est le cas, j'utilise la base de données de test. Je vais probablement refactoriser cela, car Bottle prend en charge le travail avec les fichiers de configuration.

La partie hashtag était plus facile à tester (test_get_hashtag_counter ) car je pourrais simplement ajouter des hashtags à une chaîne multiligne. Aucun appareil nécessaire.



La qualité du code est importante – Better Code Hub

Better Code Hub vous guide dans l'écriture, eh bien, un meilleur code. Avant d'écrire les tests, le projet a obtenu un 7 :

Pas mal, mais on peut mieux faire :

  1. Je l'ai fait passer à 9 en rendant le code plus modulaire, en retirant la logique de base de données de l'app.py (application Web), en la plaçant dans le dossier/paquet conseils (refactorisations 1 et 2)

  2. Ensuite, avec les tests en place, le projet a obtenu un 10 :




Conclusion et apprentissage

Notre Code Challenge #40 proposait quelques bonnes pratiques :

  1. J'ai créé une application utile qui peut être étendue (je souhaite ajouter une API).
  2. J'ai utilisé des modules intéressants qui valent la peine d'être explorés :Tweepy, SQLAlchemy et Bottle.
  3. J'ai appris un peu plus sur pytest parce que j'avais besoin d'appareils pour tester l'interaction avec la base de données.
  4. Surtout, devant rendre le code testable, l'application est devenue plus modulaire, ce qui a facilité sa maintenance. Better Code Hub a été d'une grande aide dans ce processus.
  5. J'ai déployé l'application sur Heroku à l'aide de notre guide étape par étape.

Nous vous défions

La meilleure façon d'apprendre et d'améliorer vos compétences en codage est de pratiquer. Chez PyBites, nous avons solidifié ce concept en organisant des défis de code Python. Découvrez notre collection grandissante, bifurquez le référentiel et obtenez le codage !

Faites-nous savoir si vous construisez quelque chose de cool en faisant une demande d'extraction de votre travail. Nous avons vu des gens se surpasser pour relever ces défis, et nous aussi.

Bon codage !