Pouring Django world into Flask
Przemek Lewandowski
@haxoza
One drop at a time - case study
About me
Overview
- Choosing the right tools
- Flask way
- Building basic API
- Flask vs Django
Feeding the troll
is not intended
Disclaimer
The project to build
- Scraping a few RSS feeds (10 rows/min)
- One bag of data
- Basic API with one endpoint
- One table view
Do we need a big framework for this?
Chosen toolset
- Flask
- PostgreSQL
- Celery
- Angular
Let's start with Flask!
Database?
No prob! SQLAlchemy FTW!
Not exactly…
SQLAlchemy with Flask
- Pure SQLAlchemy
- Flask-SQLAlchemy library
- Alembic for migrations
We went for Flask-SQLAlchemy
Flask-SQLAlchemy
db = SQLAlchemy()
class Document(db.Model):
id = Column(Integer(), primary_key=True)
data = Column(MutableDict.as_mutable(JSONB), nullable=False)
def __init__(self, data):
self.data = data
Alembic with Flask
- When to create database?
- How to define data mappers or ORM?
- How to run migrations?
Do you know manage.py in Django?
flask-script as entry point
- migrations with Alembic
- flask-migrate
- dev server
- tests entry point
- flask app creation
flask-script example
#!/usr/bin/env python
from flask.ext.script import Manager, Command
from flask.ext.migrate import MigrateCommand
from app import create_app
from grabber import grab_feeds
from tasks import run_celery
from tests.command import PytestCommand
manager = Manager(create_app)
manager.add_option('-c', '--config', dest='config_file', required=False)
manager.add_command('db', MigrateCommand)
manager.add_command('test', PytestCommand)
manager.add_command('runcelery', Command(run_celery))
manager.command(grab_feeds)
if __name__ == '__main__':
manager.run()
flask app creation
def create_app(config_file=None, settings_override=None):
app = Flask(__name__)
if config_file:
app.config.from_pyfile(config_file)
else:
app.config.from_envvar(config_variable_name)
if settings_override:
app.config.update(settings_override)
init_app(app)
return app
def init_app(app):
db.init_app(app)
migrate.init_app(app, db)
api.init_app(app)
What about tests?
Issues with tests
- App initialization
- Test database
- Rollback after test
- HTTP client
py.test fixtures
to the rescue!
py.test app fixture
@pytest.yield_fixture(scope='session')
def app():
app = create_app(config_file='config/testing.py')
app.response_class = ApiTestingResponse
ctx = app.app_context()
ctx.push()
yield app
ctx.pop()
py.test db and client fixtures
@pytest.yield_fixture(scope='session')
def db(app):
_db.create_all()
yield _db
_db.drop_all()
@pytest.fixture(scope='session')
def client(app):
return app.test_client()
py.test db session fixture
@pytest.yield_fixture(scope='function')
def db_session(db):
connection = db.engine.connect()
transaction = connection.begin()
options = dict(bind=connection)
session = db.create_scoped_session(options=options)
db.session = session
yield session
transaction.rollback()
connection.close()
session.remove()
Let's do REST API
Flask options to REST API
- Pure Flask
- Flask-RESTful library
- Flask-RESTless library
- Eve library
- Flask API library
- ... and more
All suck at some point but
we went for Flask-RESTful
Basic REST API
class FeedsResource(Resource):
def get(self):
# request parsing
return {
'data': 'transformed to json',
}
api = FeedsApi()
api.add_resource(FeedsResource, '/feeds')
py.test with API
def test_get_should_return_feed_data(client, session):
feed_entries_count = 3
factories.FeedDataFactory.create_batch(feed_entries_count)
response = client.get('/feeds')
assert response.status_code == 200
assert response.json['records_total'] == feed_entries_count
assert len(response.json['data']) == feed_entries_count
Flask boilerplate
Flask vs Django
Django philosophy
- Loose coupling
- Less code / Fullstack
- DRY
- Active Record design pattern
Common Django pitfalls
- Replacing default ORM
- Too fat models
- Business logic in forms / views
- Mixing application layers
Flask philosophy
- Small and simple core
- Flexibility
- No decisions for developers
- No database layer
- No form validations layer
- ...
Common Flask pitfalls
- Reinventing the wheel
- First hours spent on basic stuff
- A need for your own conventions
Micro-frameworks encourage to
- Modular structure
- Design architecture from the beginning
- Omit pitfalls
- i.e. Active Record pattern for bigger apps
- Use POPO (Plain Old Python Object)
Is that true?
It's not about code size.
It's about common sense.
Resources
- http://flask.pocoo.org/docs/0.10/foreword/#what-does-micro-mean
- https://docs.djangoproject.com/en/1.9/misc/design-philosophies/
- https://www.dabapps.com/blog/django-models-and-encapsulation/
Thanks!
Questions?
@haxoza
Pouring Django world to Flask - one drop at a time
By Przemek Lewandowski
Pouring Django world to Flask - one drop at a time
- 3,400