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_countFlask 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,749