In dit deel gaan we een Postgres-database opzetten om de resultaten van onze woordentellingen op te slaan, evenals SQLAlchemy, een Object Relational Mapper en Alembic om databasemigraties af te handelen.
Gratis bonus: Klik hier om toegang te krijgen tot een gratis Flask + Python-videozelfstudie die u stap voor stap laat zien hoe u de Flask-webapp bouwt.
Updates:
- 09/02/2020:geüpgraded naar Python-versie 3.8.1 en de nieuwste versies van Psycopg2, Flask-SQLAlchemy en Flask-Migrate. Zie hieronder voor details. Installeer en gebruik Flask-Script expliciet vanwege de wijziging van de interne interface van Flask-Migrate.
- 22-03-2016:geüpgraded naar Python-versie 3.5.1 en de nieuwste versies van Psycopg2, Flask-SQLAlchemy en Flask-Migrate. Zie hieronder voor details.
- 22/02/2015:ondersteuning voor Python 3 toegevoegd.
Onthoud:dit is wat we aan het bouwen zijn:een Flask-app die woordfrequentieparen berekent op basis van de tekst van een bepaalde URL.
- Deel één:zet een lokale ontwikkelomgeving op en implementeer vervolgens zowel een staging- als een productieomgeving op Heroku.
- Deel twee:Stel een PostgreSQL-database samen met SQLAlchemy en Alembic om migraties af te handelen. (huidig )
- Deel drie:voeg de back-endlogica toe om het aantal woorden van een webpagina te schrapen en vervolgens te verwerken met behulp van de bibliotheken met verzoeken, BeautifulSoup en Natural Language Toolkit (NLTK).
- Deel vier:Implementeer een Redis-taakwachtrij om de tekstverwerking af te handelen.
- Deel vijf:stel Angular in aan de front-end om continu de back-end te pollen om te zien of het verzoek is verwerkt.
- Deel zes:push naar de staging-server op Heroku - Redis instellen en beschrijven hoe twee processen (web en worker) op een enkele Dyno kunnen worden uitgevoerd.
- Deel zeven:werk de front-end bij om deze gebruiksvriendelijker te maken.
- Deel acht:maak een aangepaste hoekrichtlijn om een frequentieverdelingsdiagram weer te geven met JavaScript en D3.
Code nodig? Pak het uit de repo.
Installatievereisten
Tools die in dit deel worden gebruikt:
- PostgreSQL (11,6)
- Psycopg2 (2.8.4) - een Python-adapter voor Postgres
- Flask-SQLAlchemy (2.4.1) - Flask-extensie die SQLAlchemy-ondersteuning biedt
- Flask-Migrate (2.5.2) - extensie die SQLAlchemy databasemigraties via Alembic ondersteunt
Installeer om te beginnen Postgres op uw lokale computer, als u deze nog niet heeft. Aangezien Heroku Postgres gebruikt, is het goed voor ons om lokaal op dezelfde database te ontwikkelen. Als Postgres niet is geïnstalleerd, is Postgres.app een gemakkelijke manier om aan de slag te gaan voor Mac OS X-gebruikers. Raadpleeg de downloadpagina voor meer info.
Zodra Postgres is geïnstalleerd en actief is, maakt u een database met de naam wordcount_dev
om te gebruiken als onze lokale ontwikkelingsdatabase:
$ psql
# create database wordcount_dev;
CREATE DATABASE
# \q
Om onze nieuw aangemaakte database in de Flask-app te gebruiken, moeten we een paar dingen installeren:
$ cd flask-by-example
cd
Als u de directory ingaat, moet de virtuele omgeving worden geactiveerd en moeten de omgevingsvariabelen in de.env
worden ingesteld bestand via autoenv, dat we in deel 1 hebben ingesteld.
$ python -m pip install psycopg2==2.8.4 Flask-SQLAlchemy===2.4.1 Flask-Migrate==2.5.2
$ python -m pip freeze > requirements.txt
Als je OS X gebruikt en problemen hebt met het installeren van psycopg2, bekijk dan dit Stack Overflow-artikel.
Mogelijk moet u
psycopg2-binary
. installeren in plaats vanpsycopg2
als uw installatie mislukt.
Configuratie bijwerken
SQLALCHEMY_DATABASE_URI
toevoegen veld naar de Config()
class in uw config.py bestand om uw app in te stellen om de nieuw gemaakte database te gebruiken in ontwikkeling (lokaal), fasering en productie:
import os
class Config(object):
...
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
Uw config.py bestand zou er nu als volgt uit moeten zien:
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
DEBUG = False
TESTING = False
CSRF_ENABLED = True
SECRET_KEY = 'this-really-needs-to-be-changed'
SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
class ProductionConfig(Config):
DEBUG = False
class StagingConfig(Config):
DEVELOPMENT = True
DEBUG = True
class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True
class TestingConfig(Config):
TESTING = True
Wanneer onze configuratie nu in onze app wordt geladen, wordt de juiste database er ook mee verbonden.
Vergelijkbaar met hoe we een omgevingsvariabele in de laatste post hebben toegevoegd, gaan we een DATABASE_URL
toevoegen variabel. Voer dit uit in de terminal:
$ export DATABASE_URL="postgresql:///wordcount_dev"
En voeg dan die regel toe aan je .env bestand.
In uw app.py bestand importeren SQLAlchemy en verbinding maken met de database:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
from models import Result
@app.route('/')
def hello():
return "Hello World!"
@app.route('/<name>')
def hello_name(name):
return "Hello {}!".format(name)
if __name__ == '__main__':
app.run()
Gegevensmodel
Stel een basismodel in door een models.py . toe te voegen bestand:
from app import db
from sqlalchemy.dialects.postgresql import JSON
class Result(db.Model):
__tablename__ = 'results'
id = db.Column(db.Integer, primary_key=True)
url = db.Column(db.String())
result_all = db.Column(JSON)
result_no_stop_words = db.Column(JSON)
def __init__(self, url, result_all, result_no_stop_words):
self.url = url
self.result_all = result_all
self.result_no_stop_words = result_no_stop_words
def __repr__(self):
return '<id {}>'.format(self.id)
Hier hebben we een tabel gemaakt om de resultaten van het aantal woorden op te slaan.
We importeren eerst de databaseverbinding die we hebben gemaakt in onze app.py bestand evenals JSON uit de PostgreSQL-dialecten van SQLAlchemy. JSON-kolommen zijn vrij nieuw voor Postgres en zijn niet beschikbaar in elke database die wordt ondersteund door SQLAlchemy, dus we moeten deze specifiek importeren.
Vervolgens hebben we een Result()
. gemaakt class en gaf het een tabelnaam van results
. Vervolgens stellen we de attributen in die we willen opslaan voor een resultaat-
- de
id
van het resultaat dat we hebben opgeslagen - de
url
dat we de woorden telden van - een volledige lijst met woorden die we hebben geteld
- een lijst met woorden die we hebben geteld minus stopwoorden (hierover later meer)
Vervolgens hebben we een __init__()
. gemaakt methode die wordt uitgevoerd de eerste keer dat we een nieuw resultaat maken en tot slot een __repr__()
methode om het object weer te geven wanneer we ernaar vragen.
Lokale migratie
We gaan Alembic, een onderdeel van Flask-Migrate, gebruiken om databasemigraties te beheren om het schema van een database bij te werken.
Opmerking: Flask-Migrate maakt gebruik van de nieuwe CLI-tool van Flasks. Dit artikel gebruikt echter de interface van Flask-Script, die eerder door Flask-Migrate werd gebruikt. Om het te gebruiken, moet je het installeren via:
$ python -m pip install Flask-Script==2.0.6 $ python -m pip freeze > requirements.txt
Maak een nieuw bestand met de naam manage.py :
import os
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from app import app, db
app.config.from_object(os.environ['APP_SETTINGS'])
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
Om Flask-Migrate te gebruiken hebben we Manager
. geïmporteerd evenals Migrate
en MigrateCommand
naar onze manage.py het dossier. We hebben ook app
geïmporteerd en db
dus we hebben er toegang toe vanuit het script.
Eerst hebben we onze configuratie zo ingesteld dat onze omgeving - op basis van de omgevingsvariabele - een migratie-instantie heeft gemaakt, met app
en db
als de argumenten, en stel een manager
in commando om een Manager
te initialiseren bijvoorbeeld voor onze app. Ten slotte hebben we de db
. toegevoegd commando aan de manager
zodat we de migraties vanaf de opdrachtregel kunnen uitvoeren.
Om de migraties uit te voeren, initialiseert u Alembic:
$ python manage.py db init
Creating directory /flask-by-example/migrations ... done
Creating directory /flask-by-example/migrations/versions ... done
Generating /flask-by-example/migrations/alembic.ini ... done
Generating /flask-by-example/migrations/env.py ... done
Generating /flask-by-example/migrations/README ... done
Generating /flask-by-example/migrations/script.py.mako ... done
Please edit configuration/connection/logging settings in
'/flask-by-example/migrations/alembic.ini' before proceeding.
Nadat u de database-initialisatie hebt uitgevoerd, ziet u een nieuwe map met de naam "migraties" in het project. Dit bevat de instellingen die Alembic nodig heeft om migraties tegen het project uit te voeren. Binnenin "migraties" ziet u dat het een map heeft met de naam "versies", die de migratiescripts zal bevatten zoals ze zijn gemaakt.
Laten we onze eerste migratie maken door de migrate
commando.
$ python manage.py db migrate
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'results'
Generating /flask-by-example/migrations/versions/63dba2060f71_.py
... done
Nu zult u merken dat er in uw map "versies" een migratiebestand is. Dit bestand wordt automatisch gegenereerd door Alembic op basis van het model. U zou dit bestand zelf kunnen genereren (of bewerken); in de meeste gevallen is het automatisch gegenereerde bestand echter voldoende.
Nu passen we de upgrades toe op de database met behulp van de db upgrade
commando:
$ python manage.py db upgrade
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 63dba2060f71, empty message
De database is nu klaar voor gebruik in onze app:
$ psql
# \c wordcount_dev
You are now connected to database "wordcount_dev" as user "michaelherman".
# \dt
List of relations
Schema | Name | Type | Owner
--------+-----------------+-------+---------------
public | alembic_version | table | michaelherman
public | results | table | michaelherman
(2 rows)
# \d results
Table "public.results"
Column | Type | Modifiers
----------------------+-------------------+------------------------------------------------------
id | integer | not null default nextval('results_id_seq'::regclass)
url | character varying |
result_all | json |
result_no_stop_words | json |
Indexes:
"results_pkey" PRIMARY KEY, btree (id)
Migratie op afstand
Laten we tot slot de migraties toepassen op de databases op Heroku. Maar eerst moeten we de details van de staging- en productiedatabases toevoegen aan de config.py bestand.
Om te controleren of we een database hebben ingesteld op de staging-server:
$ heroku config --app wordcount-stage
=== wordcount-stage Config Vars
APP_SETTINGS: config.StagingConfig
Zorg ervoor dat u
wordcount-stage
vervangt met de naam van je staging-app.
Omdat we geen database-omgevingsvariabele zien, moeten we de Postgres-add-on toevoegen aan de staging-server. Voer hiervoor de volgende opdracht uit:
$ heroku addons:create heroku-postgresql:hobby-dev --app wordcount-stage
Creating postgresql-cubic-86416... done, (free)
Adding postgresql-cubic-86416 to wordcount-stage... done
Setting DATABASE_URL and restarting wordcount-stage... done, v8
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Use `heroku addons:docs heroku-postgresql` to view documentation.
hobby-dev
is de gratis laag van de Heroku Postgres-add-on.
Wanneer we nu heroku config --app wordcount-stage
opnieuw zouden we de verbindingsinstellingen voor de database moeten zien:
=== wordcount-stage Config Vars
APP_SETTINGS: config.StagingConfig
DATABASE_URL: postgres://azrqiefezenfrg:Zti5fjSyeyFgoc-U-yXnPrXHQv@ec2-54-225-151-64.compute-1.amazonaws.com:5432/d2kio2ubc804p7
Vervolgens moeten we de wijzigingen die je hebt aangebracht in git en push naar je staging-server doorvoeren:
$ git push stage master
Voer de migraties uit die we hebben gemaakt om onze staging-database te migreren met behulp van de heroku run
commando:
$ heroku run python manage.py db upgrade --app wordcount-stage
Running python manage.py db upgrade on wordcount-stage... up, run.5677
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 63dba2060f71, empty message
Merk op hoe we alleen de
upgrade
hebben uitgevoerd , niet deinit
ofmigrate
commando's zoals voorheen. We hebben ons migratiebestand al ingesteld en klaar voor gebruik; we hoeven het alleen maar toe te passen op de Heroku-database.
Laten we nu hetzelfde doen voor de productie.
- Stel een database in voor uw productie-app op Heroku, net zoals u deed voor de staging:
heroku addons:create heroku-postgresql:hobby-dev --app wordcount-pro
- Push uw wijzigingen naar uw productiesite:
git push pro master
Merk op hoe u geen wijzigingen hoeft aan te brengen in het configuratiebestand - het stelt de database in op basis van de nieuw aangemaakteDATABASE_URL
omgevingsvariabele. - Pas de migraties toe:
heroku run python manage.py db upgrade --app wordcount-pro
Nu hebben zowel onze staging- als productiesites hun databases ingesteld en zijn ze gemigreerd - en klaar voor gebruik!
Wanneer u een nieuwe migratie toepast op de productiedatabase, kan er sprake zijn van uitvaltijd. Als dit een probleem is, kunt u databasereplicatie instellen door een "volger" (algemeen bekend als een slave) database toe te voegen. Bekijk de officiële Heroku-documentatie voor meer informatie.
Conclusie
Dat was het voor deel 2. Als je dieper in Flask wilt graven, bekijk dan onze bijbehorende videoserie:
Gratis bonus: Klik hier om toegang te krijgen tot een gratis Flask + Python-videozelfstudie die u stap voor stap laat zien hoe u de Flask-webapp bouwt.
In deel 3 gaan we de functie voor het tellen van woorden bouwen en deze naar een takenwachtrij sturen om de langer lopende verwerking van het tellen van woorden af te handelen.
Tot de volgende keer. Proost!
Dit is een samenwerkingsstuk tussen Cam Linke, mede-oprichter van Startup Edmonton, en de mensen van Real Python.