In order to run the application one must first install:
- Python 3
- Sqlite3 - https://sqlite.org/download.html
- virtualenv - https://virtualenv.pypa.io/en/stable/installation/
$ git clone https://github.com/piotrm/airsorted_address_book.git
Access the app's directory and follow the steps
- Create virtual env - ONLY if it does not yet exist
$ virtualenv airsorted
- Activate virtual env
$ . airsorted/bin/activate
To install the dependencies form requirements.txt
do the following:
$ pip install -r requirements.txt
- Allow the application to be discovered
$ export FLASK_APP=airsorted_address_book/airsorted_address_book.py
- Set the environment
$ export ADDRESS_BOOK_SETTINGS=development
- Set the path to DB
NOTE: if it is not set the default path will be used
$ export SQLALCHEMY_DATABASE_URI=/whatever/path
- Set secret key
$ export SECRET_KEY=secretkey
$ python manage.py seed
In order to run test one may either run them separately:
$ python contacts_test.py
or run a script that will execute all of them:
$ ./test.sh
or use nose (https://nose.readthedocs.io/en/latest/) to execute all tests in one go:
$ nosetests
NOTE: if the above ends up with failure try:
$ <name_of_your_virtualenv>/bin/nosetests
$ flask run
The application exposes Contacts API. It can be accessed by cURL or via browser (index, show)
All contacts:
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://localhost:5000/api/v1/contacts
All contacts, paginated (default attributes: page: 1, per_page: 10):
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://localhost:5000/api/v1/contacts?page=1&per_page=2
Search by email:
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://localhost:5000/api/v1/contacts?email=rysiek@gmail.com
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://localhost:5000/api/v1/contacts/1
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X PUT -d '{"emails":["another@email.com"]}' http://localhost:5000/api/v1/contacts/1
curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X POST -d '{"first_name":"Jack","last_name":"Sparrow","emails":["jack@disney.com", "jsparrow@gmail.com"],"company":"Disney"}' http://localhost:5000/api/v1/contacts
$ curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X DELETE http://localhost:5000/api/v1/contacts/1
It has been my first project in Python and Flask. Some of the things were not yet obvious for me, especially in comparison to what I got used to working with Rails. I have tried to do my best to follow the documentation and guidelines. Here is the rationale behind some of the decisions I have made.
I have chosen to build the app in form of a package instead of module as it was not going to fit into one or two files.
I have chosen SQLAlchemy ORM instead of going pure sql because I wanted to check how it worked. It also looked like less overhead in terms of table creation etc.
models.py consist of two models, Contact and Email, staying in a relationship. It also consist of two schemas (Marshmallow), that act as data serializers. They should probably be moved out to a separate file.
views.py consist of all the view functions that builds the Contacts API. Most of the functions are probably too big, and could be refactored, so that some of the functionality is move to the outside module - but the main focus was not the optimization.
Params here are usually validated for presence (in case there are none at all) - with one exception, email - that is also validated for format by a custom module EmailParamsValidator
.
Questionable decision that I have made is the way the emails are handled during the update. The mechanism implemented replaces the current set of emails for given contact with those that are provided via params. This is not an ideal solution - the alternative would be merging - but this is also something that would require additional discussion.
Another thing that may look suspicious is from IPython import embed
. It has been used during the development to enable easy debugging.
Apparently SQLAlchemy offers eager loading out-of-the box, so there is no action needed (https://www.sqlalchemy.org/features.html)
There is almost no validation at all except for beforementioned email validation. There is no presence requirement for any particular fields for contact, nor there is any special formatting required. It may be the field for discussion. If I were to add the validation I would probably start from extending Marshmallow schemas in model.py as described here: http://marshmallow.readthedocs.io/en/latest/quickstart.html#validation
There are two separate files with tests: validators_test.py and contacts_test.py. First of them consist of tests for custom email validator and the other consist of the tests for view functions. They can be either run separately or together with test.sh. It is just a makeshift solution, as I was supposed to use nose runner (https://nose.readthedocs.io/en/latest/) but I have had some problems with setting it up to on my machine.
There is also one helper method in contacts_test.py that should probably be moved outside of ContactsTestCase class, but I have yet to figure out where to put it.
Update: nose has been installed - it is now usable. In required one additional change in ContactsTestCase - the helper method creating mock contacts had to be renamed from create_test_contact to create_mock_contact because apparently nose treats any method containing test placed anywhere in it's name as a test method. This is unusual, as unittest's TestLoader only looks for test prefix.
I have used Flask-script in order to prepare a mechanism for seeding the DB. It seemed to be the most reasonable solution that was available out of the box. The mechanism loads the data from seeds.json file and creates objects in the DB.
Add pagination to indexMake sure that query in index is not prone to n+1 query issue- Utilize marshmallow-jsonapi (https://github.com/marshmallow-code/marshmallow-jsonapi) in order to deliver JSON:API compliant responses
Replacetest.sh
with nose (https://nose.readthedocs.io/en/latest/)