Latest Posts

Showing posts with label flask. Show all posts
Showing posts with label flask. Show all posts

Hello guys, it's me again and again, Wicaksono as a backend engineer from Allocateam. This time I want to share you about our method to test-first our database in development. TDD is one of our project requirement, so we must apply TDD in every aspect of our project, and it's kinda frustrating for me as one of my tasks is to implement the database models. There are a lot of pros and cons when searching for the idea of implementing the TDD for database development, there are more cons written on the internet though! 

Most people said that the idea of TDD is great, but implementing it in the database is hard, impractical, and doesn't worth it (comparing the benefits and the efforts). According to Greg Lucas who has been working on SQL and database related works for more than 14 years, he said that, yes it is hard, and even in most agile teams, TDD for databases, if implemented at all, is often the last thing to be put into action. Nevertheless, he also pointed some of the advantages of implementing TDD and why we should bother. You can read more about his writing here.

Thanks to his writing, now it's not about is it possible or not. It's now about how can we implement it on our project. Following his idea in his next article, I kinda figure it out how.

On the previous article, I only use built-in python unittest for testing, but now I think I need an extension called flask-testing to help me. Install it using pip with:

1
pip install Flask-Testing

So here is the example case, I want to create a profile model that contains the name (required) and a phone number. Quite simple right? Well, you can watch our repo for our real implementation of the database models TDD in a more complex structure.

Our project uses SQLAlchemy as our ORM and we placed our model on a file called models.py. Now let's make our model stub first:

1
2
3
4
5
6
7
8
from flask_sqlalchemy import SQLAlchemy


db = SQLAlchemy()


class Profile(db.Model):
    id = db.Column(db.Integer, primary_key=True)

This is how I create our stub for our models, passing it none will cause unreadable error telling the model is broken, so I put a primary key as it's a must-have attribute for a model class. Remember, this is only a stub and we haven't implement anything for the profile model.

Now let's create our test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from flask import Flask
from flask_testing import TestCase
from models import db
from sqlalchemy import exc

from models import (
    Profile
)

class TestDatabase(TestCase):

    def create_app(self):
        app = Flask(__name__)
        app.config['TESTING'] = True
        app.config.from_object('config.TestingConfig')
        app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
        db.init_app(app)
        return app

    def setUp(self):
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

    def test_profile_ok(self):
        profile = Profile()
        profile.name = 'Wicaksono'
     profile.phone = '08111710107'
        db.session.add(profile)
        db.session.commit()

        self.assertIn(profile, db.session)

    def test_profile_no_name(self):
        profile = Profile()
     profile.phone = '08111710107'
        db.session.add(profile)

        with self.assertRaises(exc.IntegrityError):
            db.session.commit()

    def test_profile_no_phone(self):
        profile = Profile()
        profile.name = 'Wicaksono'
        db.session.add(profile)
        db.session.commit()

        self.assertIn(profile, db.session)

Here is the explanation:

create_app: is to create an app with a custom configuration. Sometimes we need to define a different configuration for our testing environment, here, for example, I define a different testing database address so it doesn't create a conflict with my local development database.

  • setUp: is to tell the test how we initiate our app, here I tell the test to create the database schema according to the model.
  • tearDown: is to tell the test what should it do after it done testing, here I tell the test to remove the session and destroy all of the database schemas inside the test database.
  • test_profile_ok: is to test if we passed all of the attributes for the profile model, the model should be successfully committed.
  • test_profile_no_name: is to test if we didn't pass a name for the profile model, the model should raise IntegrityError at commit.
  • test_profile_no_phone: is to test if we didn't pass a phone number for the profile model, the model should be successfully committed because the phone number is not a must.
When we run the test we should expect all of those tests would fail, if it's, that our [RED]. Now let's implement our model:

1
2
3
4
5
6
7
db = SQLAlchemy()


class Profile(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(15), nullable=False)
    phone = db.Column(db.String(15))

If our tests have passed after we run it when we have implemented the model, that means our model have met our criteria. That's our [GREEN], well done!

It's quite confusing and time taking for me and maybe you guys to reverse our thinking to apply test-first for more complex model structure, but it just needs some practice! On every next model that I have to implement with TDD, it getting faster and easier to do, so keep it up! Hope my experience will help some of you guys, see you next time!

Hello guys, it's me again, Wicaksono. This time I will share you about database schema migration and why we're using it.

Schema migration is like version control for your database. Imagine how hard it is to maintain your code while working with the team. A database is also the same, especially when you are working on a scrum development cycle that most likely force you to change your database over time. Without migrations, modifying and sharing the application's database schema is a lot of mess. In short, schema migration refers to the management of incremental, reversible changes to relational database schemas. With schema migration, you can easily update or revert the database schema if there is an update or trouble for the database schema. Database migration is an essential part of software evolution, especially in agile environments.

Ok, enough for the theory, you can google it if you want to know more.
Let's step into how we implement database migration on our project!

We are using flask-sqlalchemy for our Object Relational Mapper (ORM), so we can use flask-migrate that will handle our SQLAlchemy database migrations using Alembic. Install flask-migrate via pip:

1
pip install Flask-Migrate

If you are a Django developer, you may head command like 'python manage.py db init'. Flask also supports that kind of command via flask-script CLI module and still suggested by the flask-migrate documentation. But according to the flask-script webpage, the flask-script is no more active on developing features since flask 0.11, flask includes a built-in CLI tool. So here we configure our flask CLI for database migration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import os

from flask import Flask
from flask_migrate import Migrate, MigrateCommand
from models import db

app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
api = Api(app)
db.init_app(app)
migrate = Migrate(app, db)

@app.cli.command()
def db():
    return MigrateCommand

The first command to use the CLI is 'flask', to run the app is 'flask run', because we have imported the command from flask_migrate into the CLI command, now we can use:

1
2
3
4
flask db init
flask db migrate
flask db upgrade
flask db --help

For the first time only, to initialize the database migrations you can run:

1
flask db init

The structure will look like:

1
2
3
4
5
6
7
yourproject/
    alembic/
        env.py
        README
        script.py.mako
        versions/
            3512b954651e_add_account.py

After you run the command, there will be a new migration file on the 'versions' folder that contains SQLAlchemy command on how to upgrade the database and how to downgrade the database. Here is the example of our migration file when we init our database models.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
"""empty message

Revision ID: 7cc2eee2d907
Revises: 
Create Date: 2018-03-15 06:23:16.913448

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '7cc2eee2d907'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('branch',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=15), nullable=False),
    sa.Column('address', sa.String(length=30), nullable=True),
    sa.Column('address_territory_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['address_territory_id'], ['territory.id'], name='fk_address_territory_id', use_alter=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('ketua_arisan',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('visit_count', sa.Integer(), nullable=True),
    sa.Column('is_candidate', sa.Boolean(), nullable=True),
    sa.Column('latitude', sa.String(length=20), nullable=True),
    sa.Column('longitude', sa.String(length=20), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('role',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=20), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('territory',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('province', sa.String(length=20), nullable=False),
    sa.Column('city', sa.String(length=20), nullable=False),
    sa.Column('subdistrict', sa.String(length=20), nullable=False),
    sa.Column('village', sa.String(length=20), nullable=False),
    sa.Column('branch_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['branch_id'], ['branch.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_table('profile',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('first_name', sa.String(length=15), nullable=False),
    sa.Column('last_name', sa.String(length=15), nullable=True),
    sa.Column('phone_number', sa.String(length=15), nullable=True),
    sa.Column('address', sa.String(length=30), nullable=True),
    sa.Column('territory_id', sa.Integer(), nullable=True),
    sa.Column('role_id', sa.Integer(), nullable=False),
    sa.Column('ketua_arisan_id', sa.Integer(), nullable=True),
    sa.Column('manager_branch_id', sa.Integer(), nullable=True),
    sa.Column('penyuluh_branch_id', sa.Integer(), nullable=True),
    sa.ForeignKeyConstraint(['ketua_arisan_id'], ['ketua_arisan.id'], ),
    sa.ForeignKeyConstraint(['manager_branch_id'], ['branch.id'], name='fk_manager_branch_id'),
    sa.ForeignKeyConstraint(['penyuluh_branch_id'], ['branch.id'], ),
    sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
    sa.ForeignKeyConstraint(['territory_id'], ['territory.id'], ),
    sa.PrimaryKeyConstraint('id')
    )
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('profile')
    op.drop_table('territory')
    op.drop_table('role')
    op.drop_table('ketua_arisan')
    op.drop_table('branch')
    # ### end Alembic commands ###

To apply/upgrade you database migration changes, you can run:

1
flask db upgrade

Then you can see in your database that your database has been modified. Here is also a migration example when we want to add new table and column to our models. First, after you have changed your model's structure, run:

1
flask db migrate

There will be a new file created in the 'version' folder (again). Here is the content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
"""empty message

Revision ID: 7fd8c310a783
Revises: 7cc2eee2d907
Create Date: 2018-03-19 07:00:21.365847

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '7fd8c310a783'
down_revision = '7cc2eee2d907'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('revision',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.add_column('branch', sa.Column('revision_id', sa.Integer(), nullable=False))
    op.create_foreign_key('fk_address_territory_id', 'branch', 'territory', ['address_territory_id'], ['id'], use_alter=True)
    op.create_foreign_key('fk_branch_revision_id', 'branch', 'revision', ['revision_id'], ['id'])
    op.add_column('profile', sa.Column('revision_id', sa.Integer(), nullable=False))
    op.create_foreign_key('fk_profile_revision_id', 'profile', 'revision', ['revision_id'], ['id'])
    op.add_column('territory', sa.Column('revision_id', sa.Integer(), nullable=False))
    op.create_foreign_key('fk_territory_revision_id', 'territory', 'revision', ['revision_id'], ['id'])
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_constraint('fk_territory_revision_id', 'territory', type_='foreignkey')
    op.drop_column('territory', 'revision_id')
    op.drop_constraint('fk_profile_revision_id', 'profile', type_='foreignkey')
    op.drop_column('profile', 'revision_id')
    op.drop_constraint('fk_branch_revision_id', 'branch', type_='foreignkey')
    op.drop_constraint('fk_address_territory_id', 'branch', type_='foreignkey')
    op.drop_column('branch', 'revision_id')
    op.drop_table('revision')
    # ### end Alembic commands ###

After that, just run:

1
flask db upgrade

And your job has done!

Easy right? Thank you for reading and hope it will help you guys!


Hi Everyone!

I'm Wicaksono, one of the hacker of allocateam development team.
Time to share about what I've learned like all of my mates right?

This time is about Backend Test Driven Development. We use Flask as the backend framework and the steps to test it is quite simple. Here is the overview:

Write the test - We create test first to break down our app requirements [RED].
Run the test - Obviously, the test should fail because we haven't code anything related to it.
Write the code - With an objective to make all the test pass.
Run the test (again) - If it passed, we can be sure that our code has met the requirements [GREEN].

That's all! Pretty easy for sure!

Because we think our application is not really that complicated to test, we won't use any Flask extension to perform the test, instead, we use unittest package that comes pre-installed with python.

The implementation is quite straightforward as the Flask documentation is well written, so you can read here for more in-depth flask testing knowledge and how-to.

So let's go back to our business on how we implement simple TDD our backend project. For today example, we want to replicate our way to make our RESTful hello world API.

First, let's create our test! Oops, but our test need to get a result or response from a method that we haven't create yet. At least we need the method name, what kind of input it needs, so we can construct our test right? That what stub is for. We can create an empty and unimplemented method called stub, so we can draw more picture about our requirements. We placed our restful API on resources/api that will be imported by our main app. Here is the code:

1
2
3
4
5
6
from flask_restful import Resource


class HelloWorld(Resource):
    def get(self):
        pass


Simple right? Just pass it! So what it's used for?
Because of this kind of thing, now we can be sure that our method:

  • Called Hello World
  • Has a route which is /api/v1/hello
  • Only use get request
  • No input

We still don't know the logic is because we haven't constructed what kind of output that we need, which we'll define on our test.

Now we can create our test easily! We put our tests into a file called tests.py. Here is the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import unittest
import json
import mario


class TestApi(unittest.TestCase):

    __api_path = '/api/v1'
    def setUp(self):
        self.app = mario.app.test_client()
        self.app.testing = True
    def test_hello(self):
        response = self.app.get('{}/hello'.format(self.__api_path))
        self.assertEqual(
            json.loads(
                response.get_data().decode()), {'hello': 'world'}
            )

if __name__ == "__main__":
    unittest.main()

We create a single class for a specific purpose. For this example, we create a TestApi class to test all API calls on our backend service.

Our setUp() method is for, you know, set up. For now, we only create a new test client.

Our API needs to return something right? We have constructed the stub before so we can be sure which API and method that we'll use. Now, whatever that method will do, I want its input to match our expectation right? That's what tests are for!

Here We put our method result on a variable called 'result'. Our requirements said that our method needs to return a JSON string with key 'hello' and 'world' as the value. So we need to compare our method output with our expected output using assert and if it's the same, it shall pass.

Let's run the test!

BOOM! What's wrong?

The output doesn't match? Your test just failed? Have you realize (I know you have, I just want to make this post more dramatic), for all the time you have spent from the beginning, you only make the test. You haven't created the implementation. Now it's time for you to do the "work". All of the stubs and tests you have made will guide you though. Luckily our requirements only need us to return that JSON object literally without any processing. You know what to do. Here is the code:

1
2
3
4
5
6
from flask_restful import Resource


class HelloWorld(Resource):
    def get(self):
        return {'hello': 'world'}

Done, it's done. Run the test again. It should pass now. If it's not, don't be panic, take a breath and read again :)

Fun, right? See ya on the next post!

Hello allocaters, I am Ahmad Yazid, but people call me AY(which is pronounced AYE not AIY). I am one of the members of the allocateam development team. For this blog post I am going to share my experience of my first ever task which is to study the Flask framework. What is flask? Why did we choose it? Is it really the best? That is just some of the questions that is going to be answered in this blog post. Do you want to know the answer? if you do then read on my loyal readers.

So, the first questions is what is Flask? released on April 1, 2010 (which was seven years ago) and created by Armin Ronacher of Pocco (an international group of Python enthusiasts) Flask was and still is a microframework created for Python which is based on Werkzeug and Jinja 2 it also BSD licensed. Quoted from https://www.hackster.io/mjrobot/python-webserver-with-flask-and-raspberry-pi-41b5fc  "Flask is called a micro framework because it does not require particular tools or libraries. It has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions. However, Flask supports extensions that can add application features as if they were implemented in Flask itself.". Which means it is highly volatile so we as a team can add any features and application extension as we go. It would be good for us because we really need add-ons to complete our project.

The second question is, why did we choose it? To be completely honest Flask was not our first choice. It was assigned to us by our project owner, because MAPAN uses flask for all of their systems. But there must have been an explanation of why MAPAN chooses flask for their systems. Let's talk about Flask's advantages over other frameworks out there. From what I have read Flask has been the fastest (performance wise) if compared to other frameworks (such as pyramid or django),the simplicity that it offers makes it one of the easier ones to learn out of any other frameworks and of course as we have said before the volatility that it offers makes it one of the better frameworks that is around right now.

We have answered the first two answers, now it is time to answer the most anticipated question. Is it really the best? For that we actually can't really say. Maybe it is one of the faster one out there, and it is said to be one of the simpler one's compared to other frame works despite all that no one can really say we can't really say that Flask is the best but one thing for sure Flask  is the best for our specific project.

If you are really interested in flask and want to discover it for your self go visit http://flask.pocoo.org/ there you could see all the documentation,community,extensions that flask offers (it also has the Hello,world tutorial for building your first flask project). 

In conclusion this week I have studied all about flask, it's history,it's advantages and all the things mentioned before,read their documentation and also tried a few simple projects using the flask framework. Now I am ready to tell my other scrum members about what I have learned and also ready to implement what I have learned to allocateam project. For the next session I have picked creating dummy data for our project more on that on the next blog post.

 That is it for now allocaters leave any questions or coments in the comment section below , I am Ahmad Yazid Harharah hustler of allocateam Signing out :)