Getting Started
In this chapter we'll setup basic structure for our projects to help our test driven development flow.
Setup
Create new project and requirements with
$ mkdir flask-todo
$ cd flask-todo
We need to initialize our project directory as git repository with command
$ git init
Then we'll use python virtual environment to keep dependencies required by our projects isolated.
$ python -m venv venv
$ source venv/bin/activate
After activating python virtual environment, install required package
(venv)$ pip install flask==2.2.2
To save our project dependencies we can create requirements.txt
containing
Flask==2.2.2
Create app
package inside our project directory with command
(venv)$ mkdir app
(venv)$ touch app/__init__.py
Create Route
helper class in http
package inside our app
(venv)$ mkdir app/http
(venv)$ touch app/http/__init__.py
# app/http/route.py
from __future__ import annotations
from typing import List, Optional, Type, Union
from flask import Blueprint
from flask.typing import RouteCallable
from flask.views import View
class Route:
def __init__(
self,
url_rule: str,
view: Type[View],
method: Optional[str] = None,
name_alias: Optional[str] = None,
) -> None:
self.url_rule = url_rule
self.view = view
self.method = method
self.name_alias = name_alias
def view_func(self) -> RouteCallable:
view_name = self.name_alias or self.view.__name__
return self.view.as_view(view_name)
def methods(self) -> List[str]:
if self.method is None:
methods = getattr(self.view, "methods", None) or ("GET",)
else:
methods = [self.method]
return methods
@classmethod
def get(cls, url_rule: str, view: Type[View], name_alias: Optional[str] = None):
return cls(url_rule=url_rule, view=view, method="GET", name_alias=name_alias)
@classmethod
def post(cls, url_rule: str, view: Type[View], name_alias: Optional[str] = None):
return cls(url_rule=url_rule, view=view, method="POST", name_alias=name_alias)
@classmethod
def put(cls, url_rule: str, view: Type[View], name_alias: Optional[str] = None):
return cls(url_rule=url_rule, view=view, method="PUT", name_alias=name_alias)
@classmethod
def delete(cls, url_rule: str, view: Type[View], name_alias: Optional[str] = None):
return cls(url_rule=url_rule, view=view, method="DELETE", name_alias=name_alias)
@staticmethod
def group(url_prefix_group: str, routes: List[Union[Route, Blueprint]], **kwargs):
name = kwargs.pop("name", None) or url_prefix_group.replace("/", "")
url_prefix = kwargs.pop("url_prefix", None) or url_prefix_group
blueprint = Blueprint(name, __name__, url_prefix=url_prefix, **kwargs)
for route in routes:
if isinstance(route, Blueprint):
blueprint.register_blueprint(route)
else:
blueprint.add_url_rule(
route.url_rule, view_func=route.view_func(), methods=route.methods()
)
return blueprint
To isolate our application configuration create Config
object inside our app
# app/config.py
import os
from functools import lru_cache
class Config:
DEBUG = False
class ProductionConfig(Config):
pass
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
pass
@lru_cache
def get_config():
config = {
"production": ProductionConfig,
"development": DevelopmentConfig,
"testing": TestingConfig,
}
env = os.getenv("APP_ENV", "development")
return config.get(env, DevelopmentConfig)
Then we can create factory.py
file inside app directory
# app/factory.py
from typing import List, Type, Union
from flask import Blueprint, Flask
from app.config import Config
from app.http.route import Route
def create_app(
app_name: str, config: Type[Config], routes: List[Union[Route, Blueprint]]
):
app = Flask(app_name)
app.config.from_object(config)
for route in routes:
if isinstance(route, Blueprint):
app.register_blueprint(route)
else:
app.add_url_rule(
route.url_rule, view_func=route.view_func(), methods=route.methods()
)
return app
Next we will create basic handler for our application, by creating package views
with main_api
module
# app/views/main_api.py
from flask import jsonify
from flask.typing import ResponseReturnValue
from flask.views import View
class MainView(View):
def dispatch_request(self) -> ResponseReturnValue:
resp = {"message": "Application running."}
return jsonify(resp)
Then we can create basic routing for our application, first create routes
package with api
module
# app/routes/api.py
from app.http.route import Route
from app.views.main_api import MainView
api_routes = [
Route.get("/", MainView),
]
Next update our app
package __init__.py
scripts to glue all our code before
# app/__init__.py
from app.config import get_config
from app.factory import create_app
from app.routes.api import api_routes
app = create_app("flask-todo", config=get_config(), routes=api_routes)
Next we'll create script to run our application on root of project
# serve.py
from app import app
if __name__ == "__main__":
app.run()
Run project by invoke command
(venv)$ python serve.py
Navigate to http://localhost:5000 then you should see
{
"message": "Application running."
}
To exclude unwanted files in our codebase repository, we can create .gitignore
file containing
__pycache__/
venv/
Your final project structure should look like this
flask-todo
├── app
│ ├── config.py
│ ├── factory.py
│ ├── http
│ │ ├── __init__.py
│ │ └── route.py
│ ├── __init__.py
│ ├── routes
│ │ ├── api.py
│ │ └── __init__.py
│ └── views
│ ├── __init__.py
│ └── main_api.py
├── .gitignore
├── requirements.txt
└── serve.py
4 directories, 12 files
We can commit our works before continuing to next chapter.
(venv)$ git add .
(venv)$ git commit -m "Initialize project structure"