A boilerplate for a Flask server with a GraphQL API. It is based on Audrey Roy Greenfeld's pypackage cookiecutter. The Flask configuration and the file layout have been adapted from the ones in Miguel Grinberg's Web Development with Flask, Second Edition (O'Reilly, 2018).
Ensure that cookiecutter is installed on your computer.
You can then create a new project by using cookiecutter with this template.
cookiecutter https://github.com/saltastro/cookiecutter-flask-graphql.git
Assuming all the project's requirements have been installed, you need to define a couple of environment variables and then can start the server using a make
target.
cd /path/to/your/project
export FLASK_CONFIG=development
export DEV_DATABASE_URI="mysql+pymysql://user:[email protected]/dev_db_name"
export TEST_DATABASE_URI="mysql+pymysql://user:[email protected]/test_db_name"
export DATABASE_URI="mysql+pymysql://user:[email protected]/db_name"
export JWT_SECRET_KEY=topsecret
export LOG_FILE_PATH=/path/to/log_file
export DSN_KEY=https://[email protected]/somenumber # this is optional
make start
Of course you can also define the environment variables and launch the server from your IDE.
To test the server you may point your browser to http://localhost:5000/graphql.
When using the template, cookiecutter asks you for various input:
full_name
. Your full name, as it should appear in the license file and in setup.py
.
email
. Your email address. This is included as the value of author_email
in setup.py
.
project_name
. The name of the project. This should be a human-friendly name and may contain spaces.
project_slug
. The name of the package. This must be a valid Python package name.
project_short_description
. A brief description of the project.
version
. The version.
log_to_file
. Whether to log to file. Logging to file requires the LOG_FILE_PATH
environment variable to be defined. See the Python documentation on logging in general and that on rotating file handlers in particular for details on how logging is done.
log_file_max_bytes
. The maximum file size of the log file, in bytes. The file content will be removed (or moved to a backup) once this limit is reached.
log_file_backup_count
. The number of backup files for the log file.
use_sentry
. Whether to use Sentry. You also have to define a SENTRY_DSN
environment variable if you are planning to use Sentry.
github_username
. Your Github username. This is used for generating the URL of the project's repository.
github_repository
. The Github repository for the project.
open_source_license
. The open source license to use for the project. For all choices other than "Not open source" a license file will be generated.
install_requiremnts
. Whether to install this project's requirements. The project will be installed into a virtual environment venv
in the project's root folder.
Once the project has been created, you should tweak various parts of it. Take note of the warning in the section on documentation below before making any changes.
The created Python code includes two GraphQl queries, authToken
and whoAmI
. authToken
makes use of the AuthType
object type defined in app/graphql/schema/auth_token.py
. You have to modify its resolve_token
method.
You should remove or modify the whoAmI
query and its object type. The relevant files are app/graphql/schema/query.py
and app/graphql/schema/who_am_i.py
. Remember to remove or modify the tests in tests/test_who_am_i.py
as well.
Application error handlers are included in app/main/errors.py
. Feel free to modify them as you deem fit.
If you need to add a blueprint ('rest', say), you should:
-
Create a folder
app/rest
. -
Create a file
app/rest/__init__.py
with the following content.
from flask import Blueprint
main = Blueprint('rest', __name__)
from . import views
-
Create a file
app/rest/views.py
defining the routes for the new blueprint. -
Register the blueprint in
app/__init__.py
:
app.register_blueprint('rest', __name__)
The project's /tests
folder contains two example test file, which need to be removed or modified. You may add other files in the /tests
folder. Their name should start with test_
so that pytest can access their tests.
Also see the section on testing below.
While this might not be necessary, you may tweak the configuration of tox by updating its configuration file tox.ini
. In particular, you should add testing for Python 2.7 if you are planning to support this version.
Python 3.7 is included in tox.ini
but not in the .travis.yml
file. The reason for the latter is that Travis does not support Python 3.7 "out of the box". If you want to include it, you have to modify the configuration file in line with the recipe given at the end of travis-ci/travis-ci#9069 (comment).
First, a warning. When Sphinx generates the documentation, it will import the Python modules. Make sure that all code with side effects such as changing files or updating a database is protected by an if __name__ == '__main__'
condition.
Documentation should be created in three places.
-
Add docstrings to your code. All the docstrings should follow the Numpy or Google conventions. An API reference is automatically generated from your docstrings; see the file
docs/api.rst
. -
Add documentation in the
docs/
folder. When adding new files to this folder, you should remember to add them to thetoctree
directive indocs/index.rst
. Both reStructuredText and Markdown (CommonMark, to be precise) files are supported, and they must have the suffix.rst
and.md
, respectively. -
And of course you should update the
README.md
file. While this file is not included in the documentation generated by Sphinx, it is what users will see when they navigate to your project's Github repository.
Use make format
to format the Python code with black.
Flake8 is used for linting. If you need to configure any of its rules, modify the Flake8 section in setup.cfg
accordingly. One rule which is tweaked in the configuration file is the maximum line length. It is set to 88 characters.
Optionally, you may hook up your project with Travis and Read the Docs. When using Travis, make sure you define the required environment variables in your project's settings on Travis.
All unit tests should be included in the tests/
folder.
For convenience, the pytest configuration file tests/conftest.py
includes a fixture graphql
that facilitates writing tests for GraphQL queries. When used in a test, this fixture takes a GraphQL query and a user id, and sends the query to the GraphQL endpoint, along with the correct Authorization
header for the user with the given user id. The server response is parsed as a JSON object, and the corresponding dict
is returned.
The snapshottest
package is included in the project requirements. It allows to use snapshots with GraphQL tests.
The following example illustrates the use of the graphql
fixture and snapshottest
.
def test_returns_user_id(graphql, snapshot):
"""The user id is returned for the user making the request."""
query = '''query {
whoAmI {
userId
}
}'''
res = graphql(query, 42)
snapshot.assert_match(res)
You may update the snapshots by running:
python -m pytest --snapshot-update
The generated Python code requires several environment variables to be defined.
Name | Description | Example value |
---|---|---|
FLASK_APP | Path to the app file | {{ cookiecutter.project_slug }}.py |
FLASK_CONFIG | Flask configuration environment ('development', 'test' or 'production') | 'development' |
DEV_DATABASE_URI | Database URI for development | mysql+pymysql://user:[email protected]/dev_db_name |
TEST_DATABASE_URI | Database URI for tests | mysql+pymysql://user:[email protected]/test_db_name |
DATABASE_URI | Database URI for production | mysql+pymysql://user:[email protected]/db_name |
JWT_SECRET_KEY | Secret key for encoding JWT tokens | topsecret |
LOG_FILE_PATH | Path to the log file | /path/to/log_file |
SENTRY_DSN | Sentry DSN | https://[email protected]/somenumber |
Note that the database URIs start with mysql+pymysql
- the +pymysql
is necessary.
In case you are starting the server by means of make start
, you do not have to define FLASK_APP
.
If FLASK_CONFIG
is not set, 'development' is used as the configuration environment.
The Sentry DSN is only required if you want to use Sentry. You should obtain it from the settings of your Sentry project.
In addition to these variables you may also wish to define FLASK_ENV
, as explained in Flask's configuration documentation.
While this is not strictly necessary, it is a good idea to use a dedicated virtual environment when doing development work on this project. Chances are that one has been set up in a venv
folder within the project's root folder when the project files were generated. Remember that you have to activate it.
source venv/bin/activate
You may deactivate it again when you are finished working.
deactivate
If no virtual environment has been created, you may do this yourself and install the project requirements.
python3 -m venv venv
source venv/bin/activate
pip install -r requirements-to-freeze.txt
The project's root folder contains a Makefile
defining vartious useful tasks. You run a task (\some_task', say) by using the make
command.
make some_task
The most important tasks are:
help
. List all the available tasks.
clean
. Remove all automatically generated artifacts.
test
. Run pytest.
format
. Format the code with black.
lint
. Run Flake8 on the files in the src/
and tests/
folders (and their subfolders).
coverage
. Check the test coverage and display the results in the terminal and in a browser.
tox
. Run tox, using pytest and Flake8 for several Python versions.
docs
. Create html documentation from the files in the docs/
folder and open it in a browser. The documentation includes an API reference generated from the docstrings in the source code. See the warning in the section on documentation before using this task.
start
. Start the server in development mode.
These tasks may clean up existing artificats before they run.
The project version is included in app/__init__.py
file. Rather than manually updating it, you should use bumpversion.
bumpversion part
part
must be 'major', 'minor' or 'patch', depending on the kind of version change you want to make. For example:
# current version is 0.1.0
bump patch # version is now 0.1.1
bump minor # version is now 0.2.0
bump major # version is now 1.0.0
bumpversion takes the current version from setup.cfg
and replaces it with the new version after updating the other files.
This cookiecutter is completely agnostic as to how you should deploy the Flask server. See http://flask.pocoo.org/docs/1.0/deploying/#deployment for various options you have.