Welcome¶

Welcome to Flask-Jeroboam’s documentation.
Flask-Jeroboam is a Flask extension modelled after FastAPI. Like the former, it uses Pydantic to provide easy-to-configure data validation in request parsing and response serialization, as well as OpenAPI-compliant documentation auto-generation.
Start with Installation, then jump right in with our Getting Started Guide. Next, the In-depth Features Tour dive deep into how to use the extension, while the Tutorial walks you through a comprehensive example. Finally, the API section gives you details on the extension’s internals.
Note
This documentation assumes a certain familiarity with Flask and Pydantic. If you’re new to either, please refer to their respective documentation. They are both fantastic.
User’s Guide¶
This guide will walk you through how to use Flask-Jeroboam.
Installation¶
Install Flask-Jeroboam¶
I publish Flask-Jeroboam to PyPI, the Python Package Index, and as such, it is easily installable using one of the following commands, depending and your tooling for dependency management:
$ poetry add flask-jeroboam
$ pip install flask-jeroboam
With that command, you have installed Flask-Jeroboam with its two direct dependencies, Flask and Pydantic and their own dependencies tree (check it out here).
Note
We highly recommend installing Flask-Jeroboam in a virtual environment. If you need directions on how to do that check out the Setting Things Up section of our tutorial for more information.
Dependencies¶
Installing Flask-Jeroboam will automatically install these packages along with their dependencies:
Flask the web framework heavy lifting is still performed by Flask.
Pydantic to provide data validation using Python type annotations.
These two direct dependencies come with their own dependencies tree. In total, you will have up to 9 new packages installed. There is a nice poetry command to explore that tree. It goes like this:
$ poetry show flask-jeroboam --tree
flask-jeroboam 0.1.0b0 A Flask extension, inspired by FastAPI that uses Pydantic to provide easy-to-configure data validation for request parsing and response serialization.
├── flask >=2.1.3,<3.0.0
│ ├── click >=8.0
│ │ └── colorama *
│ ├── itsdangerous >=2.0
│ ├── jinja2 >=3.0
│ │ └── markupsafe >=2.0
│ └── werkzeug >=2.2.2
│ └── markupsafe >=2.1.1 (circular dependency aborted here)
└── pydantic >=1.10.2,<2.0.0
└── typing-extensions >=4.2.0
Testing your installation¶
Let’s make sure you set up everything correctly. Create and open a simple file at the root of your project: app.py
.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam(__name__)
5
6
7@app.get("/ping")
8def ping():
9 return "pong"
10
11
12if __name__ == "__main__":
13 app.run(port=5000)
Running this file should start a server on localhost:5000
. You can hit that endpoint with the command curl 'http://localhost:5000/ping'
or directly in your browser by going to http://localhost:5000/ping. If either answer with “pong”, your installation is functional, and you are ready to jump to our Getting Started Guide.
Uninstall Flask-Jeroboam¶
Removing Flask-Jeroboam from your project’s dependencies is as straightforward as adding it to your project:
$ poetry remove flask-jeroboam
$ pip uninstall flask-jeroboam
Getting Started¶
Let’s walk you through a simple example step by step.
In this guide, we will:
create a Jeroboam app
register a view function
add some view argument configuration to it for inbound data validation
add a response_model to the endpoint for outbound data validation
register the OpenAPI blueprint to get a look at the generated documentation
Create a Jeroboam App¶
Let’s start with creating the application object.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Getting Started App")
5app.init_app()
6
7
8if __name__ == "__main__":
9 app.run(host="localhost", port=5000)
As you can see, there is nothing special about the app creation on line 4. The Jeroboam class from flask_jeroboam subclasses flask’s Flask application object, and you can use it as a drop-in replacement of the former.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Getting Started App")
5app.init_app()
6
7
8if __name__ == "__main__":
9 app.run(host="localhost", port=5000)
On line 5, we are calling the init_app method of the app instance. You should call this method after loading the configuration to your app: it will register OpenAPI blueprints and generic error handlers. You can always opt-out of these with appropriate configuration values (see here).
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Getting Started App")
5app.init_app()
6
7
8if __name__ == "__main__":
9 app.run(host="localhost", port=5000)
Finally, lines 8 and 9 are a convenient way to start the app by running the file directly.
Note
The application factory pattern is usually a good practice [1] and should be followed when you start an actual project.
Register a view function¶
Registering a view function means binding a python function to an URL. Whenever a request sent to your server matches the rule you defined, the registered function, called a view function, will be run.
You can register a view function in several ways in Flask. The preferred way to do it in Flask_Jeroboam is to use method decorators, like on line 8 in the example below:
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Getting Started App")
5app.init_app()
6
7
8@app.get("/health")
9def get_health():
10 return {"status": "Ok"}
11
12if __name__ == "__main__":
13 app.run(host="localhost", port=5000)
Here we are telling the app instance that when it receives an incoming GET Request to the URL /health
, it should call the get_health
function and return the result to the client. Let’s try it. Run your file and start poking.
$ curl http://localhost:5000/health
{"status": "ok"}
Note
Although you can register view functions directly on the app instance, any project beyond the size of a classical tutorial will benefit from using the modularity of Blueprints [2], and you will find yourself using blueprints more than your app instance. The good news is that you register view functions on blueprints as you do on the app instance.
Adding View Arguments¶
Let’s try something more interesting. So far, our Jeroboam application behaves like a regular Flask application.
Let’s register a view function that takes parameters. On line 13, you will find the method decorator we saw in the previous section. But on line 14, the view function takes two parameters with type hints and default values. It then returns them without modifying them.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Getting Started App")
5app.init_app()
6
7
8@app.get("/health")
9def get_health():
10 return {"status": "Ok"}
11
12
13@app.get("/query_string_arguments")
14def get_query_string_arguments(page: int = 1, per_page: int = 10):
15 return {"page": page, "per_page": per_page}
16
17
18if __name__ == "__main__":
19 app.run(host="localhost", port=5000)
This view function ‘s only purpose is to help us inspecting the values the function actually receives when it is called and this is precisely what we will do.
$ curl 'http://localhost:5000/query_string_arguments'
{"page":1,"per_page":10}
So far, so good. The result was predictable. The function received the default values for the parameters. Let’s try something else.
$ curl 'http://localhost:5000/query_string_arguments?page=2&per_page=50'
{"page":2,"per_page":50}
Let’s take a closer look at the url we’re hitting: /query_string_arguments?page=2&per_page=50
. The part after the ?
is called a query string. It is a way to pass parameters through a URL. page=2&per_page=50
translates to two parameters, page
and per_page
with respective values of 2
and 50
. Luckily that’s exactly what our view function is expecting. Flask-Jeroboam will parse the query string, validate the values (check they can be cast as int) and inject them into the view function.
The previous example showed us the parsing and injecting part. Let’s take a look at its validation capabilities by passing a page value that can’t be cast to an int. We will add the -w 'Status Code: %{http_code}\n'
option to our curl command to print the status code of the response.
$ curl -w 'Status Code: %{http_code}\n' 'http://localhost:5000/query_string_arguments?page=not_a_int&per_page=50'
{"detail":[{"loc":["query","page"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
Status Code: 400
Here, we got a 400 Bad Request response, with details about the error, telling us that the value of the page
argument located in the query
(“loc”:[“query”,”page”]) is not a valid integer (“msg”:”value is not a valid integer”).
Note
By default, the arguments of a GET
view function are expected to get their values from the query string. It is their location
. You can explicitly set the location of the arguments by using argument functions as default values (Exemple: page: int = Query(1)
). Possible location for parameters include Path
, Query
, Header
and Cookie
. For request bodies, you can set locations to Body
, Form
and File
. Body
is the default location for POST
and PUT
requests.
You will find more information about this mechanics here.
Now that we have covered the basics of inbound handling, let’s look at the outbound parsing and validation. This is done by adding a response_model
to our decorator.
Response Models¶
We start by defining a Pydantic BaseModel for our response. This model will be used to validate the outbound data of our view function. We first import BaseModel and Field from pydantic on line 1 and 2. On line 11-14, we define a subclass of pydnatic’s BaseModel named Item
with three fields: name
, price
and count
. The name
field is a string, the price
field is a float and the count
field is an int with a default value of 1.
1from pydantic import BaseModel
2from pydantic import Field
3
4from flask_jeroboam import Jeroboam
5
6
7app = Jeroboam("Jeroboam Getting Started App")
8app.init_app()
9
10
11class Item(BaseModel):
12 name: str
13 price: float
14 count: int = Field(1)
15
16
17@app.get("/item", response_model=Item)
18def get_an_item():
19 return {"name": "Bottle", "price": 5}
20
21
22if __name__ == "__main__":
23 app.run(host="localhost", port=5000)
We then pass the Item
model as the response_model
argument of the @app.get
decorator on line 17. Our view function’s purpose is to demonstrate that our return value will be processed through the Item
model and not simply returning the {"name": "Bottle", "price": 5}
dictionnary, casting the price into a float, and adding a default value of 1 to the count field.
1from pydantic import BaseModel
2from pydantic import Field
3
4from flask_jeroboam import Jeroboam
5
6
7app = Jeroboam("Jeroboam Getting Started App")
8app.init_app()
9
10
11class Item(BaseModel):
12 name: str
13 price: float
14 count: int = Field(1)
15
16
17@app.get("/item", response_model=Item)
18def get_an_item():
19 return {"name": "Bottle", "price": 5}
20
21
22if __name__ == "__main__":
23 app.run(host="localhost", port=5000)
Let’s try it out.
$ curl 'http://localhost:5000/item'
{"name": "Bottle", "price": 5.0, "count": 1}%
What happened is that the return value of the view function has been fed to the Item
model. The price have been casted as a float, and the missing key-value of count has been added with its default value. The values have been validated and finally serialized into a JSON string.
Finally, to wrap up this first tour of Flask-Jeroboam, let’s take a look at the OpenAPI-complaint documentation our app was able to produce.
OpenAPI Documentation¶
When you visit http://localhost:5000/docs in your browser you should see the OpenAPI documentation for your API. It will look something like this:

Wrapping Up¶
In this page, we covered the three main features of Flask-Jeroboam:
Inbound data parsing and validation based on view function signatures
Outbound data validation and serialization based on response models
OpenAPI auto-documentation
To go further¶
If you want to learn more, you can check out our in-depth features tour.
We also mentioned:
Complete example code for this page can be found here. Examples are tested in this file.
In-Depth Features Tour¶
In this part of the documentation, we will cover Flask-Jeroboam’s features in-depth to give you a solid understanding of how the package works and how to best use it.
Flask-Jeroboam’s purpose is to let you focus on your web app business logic by providing a way to easily define your endpoints inbound and outbound interfaces. The inbound interface defines how clients can interact with your endpoints, while the outbound interface defines what clients can expect from them.
Defining endpoints’ inbound interface with view function arguments¶
You define the parsing, validation, and injection of inbound data into your views functions simply through their arguments, using a combination of type hints, default values, and sensible implicit values to make it as concise as possible.
Learn how to define your endpoints’ inbound interface to make them more concise and robust.
Inbound interface & view function arguments¶
We will focus here on how to use your view functions arguments to define an endpoint’s inbound interface. By inbound interface, we refer to the data a client can send to the server and how.
With Flask-Jeroboam, you use a combination of type hints and default values on your view function’s arguments to define the inbound interface of your endpoints. This feature enables you to delegate the parsing, validation, and injection of inbound data into your views functions to the extension and focus on your endpoints’ business logic.
Flask-Jeroboam will handle inbound data based on their location, type and additional validation options. The location refers to how the client passes data over an incoming HTTP request (e.g. query strings, headers, request body…). The type defines the expected shape or schema of the incoming data. Additionally, you can specify extra validation options.
Location¶
The location concept refers to how the client passes data over an incoming HTTP request. First, we will look into the seven possible locations. Then we will look at the implicit location Flask-Jeroboam will assume, and finally at how you can explicitly define the location of every argument of your view functions.
What are locations¶
There are various ways to pass data with an HTTP request. Flask and Werkzeug are already parsing the request’s raw incoming data to populate members of the request
object accordingly. Defining the location of an argument is a way to tell Flask-Jeroboam which request
’s member to use to retrieve the incoming data and inject them into your view functions.
There are seven possible locations for view functions arguments.
Four parameters:
PATH
: Path parameters are the dynamic parts of an URL found before any?
separator (e.g./items/12
). They are typically used to pass ids. Flask already injects them into your view function.QUERY
: Query Strings are equal-sign-separated key-value pairs found in the part of an URL after the?
separator (e.g.?page=1
). They serve a variety of purposes. We retrived them from Flask’srequest.args
HEADER
: Header parameters are fields destined to pass additional context or metadata about the request to the server. They are colon-separated key-value pair like thisX-My-Header: 42
. We retrived them ffrom Flask’srequest.headers
COOKIE
: Cookies are stored on the client side to keep track of a state in a stateless protocol. They look like thisCookie: username=john
, and we retrived them from Flask’srequest.cookies
Three variations of the request body:
BODY
: The request body of an HTTP request is the optional content of the request. Request fetching resources [1] usually don’t have one. The request body has a content-type property (like:application/json
) that gives the server indication on how to parse them. We retrieve the reuqest body from Flask’srequest.data
(orrequest.json
when its mimetype isapplication/json
).FORM
: A Request Body with a content-type value ofapplication/x-www-form-urlencoded
. We retrieved data from Flask’srequest.form
FILE
: Request Body with a enctype ofmultipart/form-data
and We retrieved data from Flask’srequest.files
Solving Location¶
Most of the time, you won’t have to explicitly define your arguments’ location, thanks to Flask-Jeroboam’s implicit location mechanism. However, you also can explicitly define the location for each argument of your view functions by using special functions on your arguments’ default values.
Implicit Location¶
We can boiled down Flask-Jeroboam’s heuristic to determine the location of an argument to a few simple rules:
the argument’s name will be checked against the the path parameters’ names from the endpoint’s rule
arguments of ressource fetching verbs are assumed to be
QUERY
parametersarguments of ressource creation verbs are assumed to be
BODY
parameters
Warning
This is slighly different from FastAPI’s heuristic, where singular values are assumed to be QUERY
parameters no matter the verb, and pydantic Models are assumed to be BODY
parameters.
Apart from Path parameters, Flask-Jeroboam derives the implicit location of an argument from the HTTP verb of your view function, based on the assumption that for a GET
request, the client typically pass parameters through the query string and that for PUT
and POST
requests the client will mainly use the request body.
So if you look at these view functions on line 9 and 14, without any explicit location definition, Flask-Jeroboam will assume a QUERY
location for the GET
endpoint and BODY
for the POST
endpoint.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Inbound Features App")
5app.init_app()
6
7
8@app.get("/implicit_location_is_query_string")
9def get_implicit_argument(page: int):
10 return f"Received Page Argument is : {page}"
11
12
13@app.post("/implicit_location_is_body")
14def post_implicit_argument(page: int):
15 return f"Received Page Argument is : {page}"
16
17
18if __name__ == "__main__":
19 app.run(host="localhost", port=5000)
If you run the above file, you can test it out. The /implicit_location_is_query_string
endpoint will expect a page parameter in the query string.
$ curl 'localhost:5000/implicit_location_is_query_string?page=42'
Received Page Argument is : 42
while the /implicit_location_is_body
endpoint will expect a page field in the request body.
$ curl -X POST 'localhost:5000/implicit_location_is_body' -d '{"page": 42}' -H "Content-Type: application/json"
Received Page Argument is : 42
Although the two view functions received the same parameter values, notice that we build our request differently by hosting the parameters in two different locations.
In addition to this verb-based mechanism, Flask-Jeroboam will automatically detect Path parameters. In the following example, Flask-Jeroboam will be recognized the argument id
as a Path Parameter. Indeed, it is first declared on line 8 with the <int:id>
part of the rule, so when Flask-Jeroboam comes across an argument in the decorated function with the same name but without any explicit location definition, it will safely assume that this is a Path Parameter.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Inbound Features App")
5app.init_app()
6
7
8@app.get("/item/<int:id>/implicit")
9def get_path_parameter(id: int):
10 return f"Received id Argument is : {id}"
11
12
13if __name__ == "__main__":
14 app.run(host="localhost", port=5000)
You can test it out:
$ curl 'localhost:5000/item/42/implicit'
Received id Argument is : 42
It also works with other HTTP verbs and overrides the verb-based location. Flask-Jeroboam will also recognized the argument id
as a Path Parameter in the following example.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Inbound Features App")
5app.init_app()
6
7
8@app.post("/item/<int:id>/implicit")
9def post_path_parameter(id: int):
10 return f"Received id Argument is : {id}"
11
12
13if __name__ == "__main__":
14 app.run(host="localhost", port=5000)
$ curl -X POST 'localhost:5000/item/42/implicit'
Received id Argument is : 42
Note
This implicit location mechanism is one of the reasons why the method decorator (@app.get/put/post VS @app.route) is the preferred way to register a view function in Flask-Jeroboam. It enforces the good practice of having a single HTTP verb per view function. View functions assigned to more than one HTTP verb tends to be split up in two mostly independent branches, which depletes their readability.
Although the implicit location will cover most of the cases, you can also define them explicitly.
Explicit Locations¶
To define explicit locations, you must use one of Flask-Jeroboam’s special functions (Path
, Query
, Cookie
, Header
, Body
, Form
or File
) to assign default values to your arguments.
For example, these two endpoints will behave the same way, line 10 (implicit) and 15 (explicit) are equivalent:
1from flask_jeroboam import Jeroboam
2from flask_jeroboam import Query
3
4
5app = Jeroboam("Jeroboam Inbound Features App")
6app.init_app()
7
8
9@app.get("/implicit_location_is_query_string")
10def get_implicit_argument(page: int):
11 return f"Received Page Argument is : {page}"
12
13
14@app.get("/explicit_location_is_query_string")
15def get_explicit_argument(page: int = Query()):
16 return f"Received Page Argument is : {page}"
17
18
19if __name__ == "__main__":
20 app.run(host="localhost", port=5000)
And same goes for POST
(or PUT
) view functions. Line 10 and 15 are equivalent.
1from flask_jeroboam import Body
2from flask_jeroboam import Jeroboam
3
4
5app = Jeroboam("Jeroboam Inbound Features App")
6app.init_app()
7
8
9@app.post("/implicit_location_is_body")
10def post_implicit_argument(page: int):
11 return f"Received Page Argument is : {page}"
12
13
14@app.post("/explicit_location_is_body")
15def post_explicit_argument(page: int = Body()):
16 return f"Received Page Argument is : {page}"
17
18
19if __name__ == "__main__":
20 app.run(host="localhost", port=5000)
Let’s test it out.
1$ curl 'localhost:5000/implicit_location_is_query_string?page=42'
2Received Page Argument is : 42
3$ curl 'localhost:5000/explicit_location_is_query_string?page=42'
4Received Page Argument is : 42
5$ curl -X POST 'localhost:5000/implicit_location_is_body' -d '{"page": 42}' -H "Content-Type: application/json"
6Received Page Argument is : 42
7$ curl -X POST 'localhost:5000/explicit_location_is_body' -d '{"page": 42}' -H "Content-Type: application/json"
8Received Page Argument is : 42
You can also point to another location than the default one, and define different locations for each argument and mix implicit locations with explicit locations. In the following example, we define an explicit Cookie
location for the argument username
. On line 11, it shares the signature with another explicitly-query-located page argument, but on line 16 we define a similar view function in which page is implicitly located.
1from flask_jeroboam import Cookie
2from flask_jeroboam import Jeroboam
3from flask_jeroboam import Query
4
5
6app = Jeroboam("Jeroboam Inbound Features App")
7app.init_app()
8
9
10@app.get("/explicit_location_is_query_string_and_cookie")
11def get_explicit_arguments(page: int = Query(), username: str = Cookie()):
12 return f"Received Page Argument is : {page}. Username is : {username}"
13
14
15@app.get("/implicit_and_explicit")
16def get_implicit_and_explicit_arguments(page: int, username: str = Cookie()):
17 return f"Received Page Argument is : {page}. Username is : {username}"
18
19
20if __name__ == "__main__":
21 app.run(host="localhost", port=5000)
Let’s test it out.
1$ curl 'localhost:5000/explicit_location_is_query_string_and_cookie?page=42' --cookie "username=john"
2Received Page Argument is : 42. Username is : john
3$ curl 'localhost:5000/implicit_and_explicit?page=42' --cookie "username=john"
4Received Page Argument is : 42. Username is : john
Note
Linters like Flake8 will likely complain about making a function call in an argument default. While this is good advice, it won’t cause any unwanted effect in this particular case. You should consider disabling B008
warnings for the files in which you define your view functions.
Assigning default values¶
As you may have guessed, the special functions are highjacking the default value mechanism to let you easily define an explicit location for your arguments. As a result, their returned value won’t be used as a fallback when the client don’t provide any argument. In fact, so far, all arguments we have defined are implicitly required because they have no default values to fall back to when the request does not provided them.
Don’t take my word for it, let’s try it out on the previously defined /implicit_location_is_query_string
endpoint.
$ curl -w 'Status Code: %{http_code}\n' 'localhost:5000/implicit_location_is_query_string'
{"detail":[{"loc":["query","page"],"msg":"field required","type":"value_error.missing"}]}
Status Code: 400
We received a 400 Bad Request response because we did not provide the required parameter page in our query string. What if we want to define our default value for the page parameter to 1 ? They are two ways to go about it:
If you go with the implicit location, you can define a default value as you normally would, as shown on line 10.
If you use an explicit definition, you must pass the default value as the first argument of your function call, like in line 15.
1from flask_jeroboam import Jeroboam
2from flask_jeroboam import Query
3
4
5app = Jeroboam("Jeroboam Inbound Features App")
6app.init_app()
7
8
9@app.get("/implicit_location_with_default_value")
10def get_implicit_argument_with_default(page: int = 1):
11 return f"Received Page Argument is : {page}"
12
13
14@app.get("/explicit_location_with_default_value")
15def get_explicit_argument_with_default(page: int = Query(1)):
16 return f"Received Page Argument is : {page}"
17
18
19if __name__ == "__main__":
20 app.run(host="localhost", port=5000)
Let’s test it out.
1$ curl 'localhost:5000/implicit_location_with_default_value'
2Received Page Argument is : 1
3$ curl 'localhost:5000/implicit_location_with_default_value?page=42'
4Received Page Argument is : 42
5$ curl 'localhost:5000/explicit_location_with_default_value'
6Received Page Argument is : 1
7$ curl 'localhost:5000/explicit_location_with_default_value?page=42'
8Received Page Argument is : 42
The default value is correctly inserted when you don’t provide the parameter in the query string for either endpoint. The server returns a valid response meaning the page parameter is no longer required. We also check that the parsing-validation-injection mechanism is still working on lines 3 and 7.
The special functions also provide a way to define additional validation options, but first, let’s take a closer look at defining the second part of our inbound arguments: the type.
Type¶
The type refers to the shape or schema of the data you expect. You can assign a type to an argument using type hints. Types can be python built-in types (e.g. str
, int
, float
), pydantic’s BaseModel
subclasses or a container of the previous two (e.g. List[str]
)
In addition to using type hints, you can also use the first argument of special functions like Query
.
Let’s look at an example. First, on lines 12 to 14, we define the Item
class inheriting from pydantic’s BaseModel
.
1from typing import List
2
3from pydantic import BaseModel
4from flask_jeroboam import Jeroboam
5from flask_jeroboam import Query
6
7
8app = Jeroboam("Jeroboam Inbound Features App")
9app.init_app()
10
11
12class Item(BaseModel):
13 name: str
14 count: int
15
16
17@app.get("/defining_type_with_type_hints")
18def get_type_hints(page: int, search: List[str], item: Item, price=Query(float)):
19 return (
20 f"Received arguments are :\n"
21 f"page : {page}\n"
22 f"search : {search}\n"
23 f"price : {price}\n"
24 f"item : {item}"
25 )
26
27
28if __name__ == "__main__":
29 app.run(host="localhost", port=5000)
Then on line 18, we demonstrate the full extent of how to define types of arguments. We define the page
, search
and item
arguments’ types with a type hint. For the price
argument type, on the other hand, we pass float
as the first argument of the Query
function call assigned as a default value. page
and price
are built-in python types, int
and float
respectively. search
is a list of strings and item
is a pydantic BaseModel
.
1from typing import List
2
3from pydantic import BaseModel
4from flask_jeroboam import Jeroboam
5from flask_jeroboam import Query
6
7
8app = Jeroboam("Jeroboam Inbound Features App")
9app.init_app()
10
11
12class Item(BaseModel):
13 name: str
14 count: int
15
16
17@app.get("/defining_type_with_type_hints")
18def get_type_hints(page: int, search: List[str], item: Item, price=Query(float)):
19 return (
20 f"Received arguments are :\n"
21 f"page : {page}\n"
22 f"search : {search}\n"
23 f"price : {price}\n"
24 f"item : {item}"
25 )
26
27
28if __name__ == "__main__":
29 app.run(host="localhost", port=5000)
Let’s test it out.
$ curl 'localhost:5000/defining_type_with_type_hints?page=42&search=foo&search=bar&price=42.42&name=test&count=3'
Received arguments are :
page : 42
search : ['foo', 'bar']
price : 42.42
item : name='test' count=3
You’ll notice we didn’t attempt to pass a dictionary to an item
field to the query string. Instead, we passed two arguments, name
and count
, corresponding to the inner fields of Item. Query Strings are usually not a good way to pass nested data structures. If you need to pass a complex data structure, use a different location like a JSON body or a form.
With request bodies, you can choose between embeded or flat arguments.
Note
Type hints are not initially supposed to provide run-time functionality. But this principle was spearheaded by pydantic and later picked up by FastAPI. So we are in good company.
Validation Options¶
View function arguments are essentially pydantic model fields, meaning that when you define them, you can leverage every validation feature pydantic offers on model fields.
For number types for instance, you can add ge
(meaning greater or equal to) or lt
(less than) values to define validation conditions on your parameters.
Let’s see a simple example in which we want to make sure that the page
argument is greater or equal to 1 (Query(ge=1)
)
1from flask_jeroboam import Jeroboam
2from flask_jeroboam import Query
3
4
5app = Jeroboam("Jeroboam Inbound Features App")
6app.init_app()
7
8
9@app.get("/argument_with_validation")
10def get_validation_option(page: int = Query(ge=1)):
11 return f"Received page argument is : {page}"
12
13
14if __name__ == "__main__":
15 app.run(host="localhost", port=5000)
Let’s see what happens when we pass a page value of 0. Note that 0 is a valid int, but it is not greater or equal to 1.
$ curl -w 'Status Code: %{http_code}\n' 'localhost:5000/argument_with_validation?page=0'
{"detail":[{"ctx":{"limit_value":1},"loc":["query","page"],"msg":"ensure this value is greater than or equal to 1","type":"value_error.number.not_ge"}]}
Status Code: 400
The server returns a 400 status code with a body giving you direction about the error: it didn’t pass the ge=1
validation.
Note
Although you can do it using a special function, I believe that this would lead to bloated view function signatures code in many cases. Beyond a couple of elementary arguments, my preference goes to defining a pydantic BaseModel first with full-blown fields and validation conditions on each of them, and then use that BaseModel as a type hint to define the shape of an inbound argument.
Cheat Sheet¶
In summary, your inbound arguments are defined by:
their location: defined either implicitly or explicitly using special functions as default values (
page:int = Query()
)their optional default values: defined either as regular default values
page:int = 1``or explicitly using special functions (``page:int = Query(1)
)their type: using type hint (
page: int
) or the first argument of special functions (page = Query(int)
)their optional validation options: passing additional named arguments to special functions calls (
page:int = Query(ge=1)
)
Next, check out how outbound interfaces are defined.
Defining endpoints’ outbound interface with route decorators¶
You define your endpoints’ outbound interface through the route decorators, typically by passing a response_model
to the decorator. Flask-Jeroboam will use it to validate and serialize your view function’s returned value.
Learn how to define your endpoints’ outbound interface and be confident that the data you send back follows the schema you choose for them.
Outbound interface & route decorators parameters¶
You’ve seen in the previous section how to lay out your view functions’ argument to define your endpoint’s inbound interface. Now we will focus on defining the outbound interface with named arguments to your route decorators.
By outbound interface, we mean the shape of the response your server sends back when hit by the client. It encompasses both the response’s payload schema and status code.
Response Model¶
The response model defines your response payload schema, or in other words, the shape of the data you’re sending back to the client. The preferred way to do that is to pass a Pydantic BaseModel to your route decorator’s response_model
argument. Alternatively, your view function return value can implicitly define this response model.
Explicit Response Model¶
The most straightforward way of defining the outbound interface of your endpoint is to use the response_model
argument of your route decorator like this @app.get("/tasks/<int:task_id>", response_model=Task)
. This argument takes a Pydantic model as a value and will use it to validate and serialize the data returned by your view function.
Let’s say that you have a GET
endpoint that returns a Task
. First, we define a Task
model, inheriting from pydantic’s BaseModel
. Our Task
model has three fields: id
, name
, and description
. The description
field is optional and has a default value of Just here to make a point.
that will help us understand the mechanics later on.
1from typing import Optional
2
3from pydantic import BaseModel
4from pydantic import Field
5
6from flask_jeroboam import Jeroboam
7
8
9app = Jeroboam("Jeroboam Outbound Features App")
10app.init_app()
11
12
13class Task(BaseModel):
14 id: int
15 name: str
16 description: Optional[str] = Field("Just here to make a point.")
17
18
19@app.get("/tasks/<int:task_id>", response_model=Task)
20def read_explicit_task(task_id: int):
21 return {"id": task_id, "name": "Find the answer."}
22
23
24if __name__ == "__main__":
25 app.run(host="localhost", port=5000)
Then on line 19, we feed it to the response_model
argument of our route decorator on line 19. Note that on line 21 we only return a dictionary with an id
and a name
field. The description
field is missing, but that’s okay. Flask-Jeroboam will add it for us through the Task
model.
1from typing import Optional
2
3from pydantic import BaseModel
4from pydantic import Field
5
6from flask_jeroboam import Jeroboam
7
8
9app = Jeroboam("Jeroboam Outbound Features App")
10app.init_app()
11
12
13class Task(BaseModel):
14 id: int
15 name: str
16 description: Optional[str] = Field("Just here to make a point.")
17
18
19@app.get("/tasks/<int:task_id>", response_model=Task)
20def read_explicit_task(task_id: int):
21 return {"id": task_id, "name": "Find the answer."}
22
23
24if __name__ == "__main__":
25 app.run(host="localhost", port=5000)
Flask-Jeroboam takes the view function returned value and feeds it into your reponse_model, validates the data, serialize it into JSON, and finally wraps it into a Response
object before handling it back to Flask.
Let’s test it out:
$ curl http://localhost:5000/tasks/42
{"id": 42, "name": "Find the answer.", "description": "Just here to make a point."}
As you can see, the endpoint uses the data returned by this view function but also adds the default value of the description
field. This is because Flask-Jeroboam uses the Task
model to validate the data returned by the view function. It will add any missing fields and fill them with their default values.
To demonstrate this, let’s define another endpoint that returns the same dictionary without the response_model
argument.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Outbound Features App")
5app.init_app()
6
7
8@app.get("/tasks/<int:task_id>/no_response_model")
9def read_task_dictionnary_without_response_mode(task_id: int):
10 return {"id": task_id, "name": "I'm from the dictionary."}
11
12
13if __name__ == "__main__":
14 app.run(host="localhost", port=5000)
and test it out:
$ curl http://localhost:5000/tasks/42/no_response_model
{"id":42,"name":"I'm from the dictionary."}
This time, Flask receive a plain dictionary. It will not add any default value or try to validate it against any schema. It just returns it.
Pydantic’s BaseModels are a compelling way to define a complex schema. They are highly reusable and have proven an excellent tool for defining data models. For example, you can nest models, assigning a BaseModel as the type of a parent model field. You can also define validation rules, such as minimum and maximum values, regex patterns… You can even define custom validation rules. For more information on Pydantic models, check out the Pydantic documentation.
Alternatively to explicit declarations, you can also let Flask-Jeroboam infer the response model from the return values of your view function.
Implicit Response Model¶
Flask-Jeroboam can also derive your response model from the view function return type, but it has to be from annotation. In the following examples, the first endpoint will work similarly to the one from the previous section, but the second one will raise an error because Flask doesn’t know what to do with the Task
object.
1from typing import Optional
2
3from pydantic import BaseModel
4from pydantic import Field
5
6from flask_jeroboam import Jeroboam
7
8
9app = Jeroboam("Jeroboam Outbound Features App")
10app.init_app()
11
12
13class Task(BaseModel):
14 id: int
15 name: str
16 description: Optional[str] = Field("Just here to make a point.")
17
18
19@app.get("/tasks/<int:task_id>/implicit_from_annotation")
20def read_implicit_task(task_id: int) -> Task:
21 return Task.parse_obj({"id": task_id, "name": "Implicit from Annotation"})
22
23
24@app.get("/tasks/<int:task_id>/implicit_no_annotation")
25def read_implicit_no_annotation(task_id: int):
26 return Task.parse_obj({"id": task_id, "name": "Implicit from Annotation"})
27
28
29if __name__ == "__main__":
30 app.run(host="localhost", port=5000)
Let’s test it out.
$ curl http://localhost:5000/tasks/42/implicit_from_annotation
{"id": 42, "name": "Implicit from Annotation", "description": "Just here to make a point."}%
$ curl -w 'Status Code: %{http_code}\n' http://localhost:5000/tasks/42/implicit_no_annotation
{"message":"Internal Error"}
Status Code: 500
However, explicit is better than implicit, so you should prefer the response_model
argument over this approach. Plus, creating the Task
instance feels unnecessarily wordy because, as seen before, you can directly return the dictionary. Speaking of this, let’s take a look at allowed return values.
Allowed return values¶
When a response model is defined, Flask-Jeroboam can accept the following body from view functions’ return values:
a dictionary
a dataclass instance
a list
a Pydantic model instance
a Flask response instance (although it will skip the serialization part of its algorithm)
Note that, in addition to the above, you can also return a tuple of the form (body, status_code)
, (body, headers)
, (body, status_code, headers)
. Both the status code and headers will be used in the response. Notably, the Status Code will be overridden.
Turning it off¶
If you don’t want to use Flask-Jeroboam’s outbound features, turn it off by setting the response_model
argument to None
. It will make Flask-Jeroboam ignore the outbound interface of your endpoint.
1from flask_jeroboam import Jeroboam
2
3
4app = Jeroboam("Jeroboam Outbound Features App")
5app.init_app()
6
7
8@app.get("/tasks/<int:task_id>/response_model_off", response_model=None)
9def read_response_model_off(task_id: int):
10 return {"id": task_id, "name": "Response Model is off."}
11
12
13if __name__ == "__main__":
14 app.run(host="localhost", port=5000)
The endpoint still works.
$ curl http://localhost:5000/tasks/42/response_model_off
{"id": 1, "name": "Response Model is off."}
Next, let’s look at another aspect of the outbound interface of an endpoint: the successful status code.
Status Code¶
Flask-Jeroboam supports both registration-time status codes and return values status codes.
Registration-time status code¶
When you register your view function, Flask-Jeroboam will try to solve the status code of the successful response. It will first look at the parameter status_code of the route decorator, then at the package-defined default value for the HTTP verb of the route decorator, and finally, the status code attribute of the response class, if any.
Warning
Flask-Jeroboam will only be able to use this registration-time status code in the OpenAPI documentation of your operation.
We use the following default values for each HTTP verb:
GET
: 200HEAD
: 200POST
: 201PUT
: 201DELETE
: 204CONNECT
: 200OPTIONS
: 200TRACE
: 200PATCH
: 200
As you can see, you won’t have to set an explicit status code most of the time.
For example, the following endpoint will have a registration-time status code of 201. As the view function does not return any status code, a successful put request will give us a 201 status code.
@app.put("/tasks", response_model=TaskOut)
def create_task(task: TaskIn):
return {"task_id": task.id}
$ curl -w 'Status Code: %{http_code}\n' -PUT http://localhost:5000/tasks -d '{"name": "My Task"}'
Status Code: 201
{"task_id": 1}
Now, let’s say we define a second endpoint that takes a task and starts running it. In that case, you might want to override the default (201
) with a more appropriate 202
standing for “Accepted but not done” (see RFC 7231). You would do it this way:
@app.put("/tasks", response_model=TaskOut, status_code=202)
def create_task(task: TaskIn):
# Save the Task and Launch it
return {"task_id": task.id}
This time, when we make a successful request, we will get a 202
status code in the response.
$ curl -PUT http://localhost:5000/tasks -d '{"name": "My Task"}'
Status Code: 202
{"task_id": 1}
Return-value status code¶
Flask-Jeroboam also supports returning a status code as a tuple, just like in Flask. It will override the registration-time status code, but Flask-Jeroboam won’t be able to adjust the documentation. This could lead to inconsistencies between your documentation and the actual behaviour of the API.
If we revisit the previous example, you could achieve the same request-handling result with the following code:
@app.put("/tasks", response_model=TaskOut)
def create_task(task: TaskIn):
# Save the Task and Launch it
return {"task_id": task.id}, 202
The successful PUT request will still give us a 202
status code in the response.
$ curl -PUT http://localhost:5000/tasks -d '{"name": "My Task"}'
Status Code: 202
{"task_id": 1}
However, the resulting documentation would be different. See OpenAPI’s Auto-Documentation for more details.
In summary, when Flask-Jeroboam handles the request, it will use the status code inferred at registration time unless the view function returns a value containing a status code.
If you use the OpenAPI documentation, the preferred way is to add a registration-time status code to guarantee consistency between your documentation and your API. Also returned status code is also supported to avoid breaking existing code.
Cheatsheet¶
To define your responses’ payload schema, you pass a pydantic BaseModel to the route decorator named argument
response_model
(eg.@app.get("/task/<int:id>", response_model=TaskOut)
).If you want to override the implicit status code, you can use the named argument
status_code
(e.g.@app.put("/task/<int:id>/run", status_code=202)
).If you want to disable the implicit response model, use the named argument
response_model=None
. (eg.@app.get("/task/<int:id>", response_model=None)
)
Next, check out how to get the most of OpenAPI’s documentation auto-generation.
Planned Features
Templated views (https://github.com/jcbianic/flask-jeroboam/issues/105)
Support Serialization options (e.g. exclude_unset) (https://github.com/jcbianic/flask-jeroboam/issues/105)
OpenAPI AutoDocumentation¶
While defining the inbound and outbound interfaces’ primary purpose is to provide run-time parsing, validation, and de/serialization of inbound and outbound data for your endpoint, they also offer an excellent opportunity to generate an OpenAPI documentation automatically for your API.
Although most of it will happen without you having to write a single line of code, learn how you can improve your documentation.
OpenAPI’s Auto-Documentation¶
In addition to providing request-handling functionalities, defining inbound and outbound interfaces lets you benefit from auto-generated documentation with little extra effort. It is as easy as calling the init_app
method on your Jeroboam app instance. This will register an internal blueprint with two endpoints. One serves the OpenaAPI documentation in JSON format, and another serves the Swagger UI.
1from typing import List
2from typing import Optional
3
4from pydantic import BaseModel
5
6from flask_jeroboam import Blueprint
7from flask_jeroboam import Jeroboam
8from flask_jeroboam import Path
9
10
11app = Jeroboam("Jeroboam Inbound Features App")
12app.init_app()
13
14
15class TaskIn(BaseModel):
16 name: str
17 description: Optional[str] = None
18
19
20class TaskOut(TaskIn):
21 id: int
22
23
24@app.get("/health")
25def get_health():
26 return {"status": "Ok"}
27
28
29blueprint = Blueprint("tasks", __name__, tags=["tasks"])
30
31
32@blueprint.get("/tasks", response_model=List[TaskOut])
33def get_tasks(page: int = 1, per_page: int = Path(10), search: Optional[str] = None):
34 return [{"id": 1, "name": "Task 1"}, {"id": 2, "name": "Task 2"}]
35
36
37@blueprint.get("/tasks/<int:item_id>", response_model=TaskOut)
38def get_task(item_id: int):
39 return {"id": 1, "name": "Task 1"}
40
41
42@blueprint.put("/tasks", status_code=202, tags=["sensitive"])
43def create_task(task: TaskIn):
44 return {}
45
46
47@blueprint.post("/tasks/<int:item_id>", tags=["sensitive"])
48def edit_task(item_id: int, task: TaskIn):
49 return {}
50
51
52app.register_blueprint(blueprint)
53
54if __name__ == "__main__":
55 app.run(host="localhost", port=5000)
You can check it out at localhost:5000/openapi and localhost:5000/docs.
Turning it off¶
If you don’t want to use the auto-documentation feature, turn it off by setting the configuration JEROBOAM_REGISTER_OPENAPI
flag to False
.
Configuration¶
Configuration options let you:
Opt out of high-level features (e.g. OpenAPI AutoDocumentation)
Handle OpeanAPI MetaData (e.g. API title, version, description, etc.)
We prefixed them with JEROBOAM_
to avoid name collisions with other packages.
Configuration¶
Configuration options let you:
Opt out of high-level features (e.g. OpenAPI AutoDocumentation)
Handle OpeanAPI MetaData (e.g. API title, version, description, etc.)
They are prefixed with JEROBOAM_
to avoid name collisions with other packages.
General Options¶
General Options let you opt out of some of the Flask-Jeroboam’s features, in case you don’t need them, need to customize, or if they are interfering with the rest of your app.
- JEROBOAM_REGISTER_OPENAPI¶
It controls whether the OpenAPI Blueprint will be registered when you call the
init_app
method on the app instance.Set to
False
if you don’t want the OpenAPI Blueprint to be registered or if you want to plug in your own view functions to serve OpenAPI functionnalities.Default:
True
- JEROBOAM_REGISTER_ERROR_HANDLERS¶
It controls whether package-defined error handlers of Flask-Jeroboam’s Exceptions will be registered when you call the
init_app
method on the app instance.Set to
False
if you don’t want the package-defined error handlers registered. Note that if you do this, you will need to define your own error handlers for the following exception:RessourceNotFound
,InvalidRequest
andResponseValidationError
.Default:
True
OpenAPI MetaData¶
OpenAPI MetaData Configuration options let you control the MetaData of your OpenAPI Documentation, like its title, versions, contact information, etc… Setting these is optional, meaning you will have an OpenAPI page up and running before setting these options.
- JEROBOAM_TITLE¶
The title of your API. It will appear as the main title of your OpenAPI documentation page.
Default:
app.name
- JEROBOAM_VERSION¶
The version of your API. Not to be mistaken with the OPENAPI version. It will appear in the small grey tag next to your title.
Default:
0.1.0
- JEROBOAM_DESCRIPTION¶
A short description of your API. It will appear in the small grey tag next to your title.
Default:
None
- JEROBOAM_TERMS_OF_SERVICE¶
A link to the terms of service of your API. It will appear in the footer of your OpenAPI documentation page.
Default:
None
- JEROBOAM_CONTACT¶
A dictionary containing the contact information of your API. It will appear in the footer of your OpenAPI documentation page.
Default:
None
- JEROBOAM_LICENCE_INFO¶
A dictionary containing the licence information of your API. It will appear in the footer of your OpenAPI documentation page.
Default:
None
- JEROBOAM_OPENAPI_VERSION¶
The version of the OpenAPI specification that your API is compliant with. It will appear in the footer of your OpenAPI documentation page.
Default:
3.0.2
- JEROBOAM_SERVERS¶
A list of dictionaries containing the servers that your API is available on. It will appear in the footer of your OpenAPI documentation page.
Default:
[]
- JEROBOAM_OPENAPI_URL¶
The URL of your OpenAPI documentation page.
Default:
/docs
Tutorial (coming soon)¶
This tutorial will walk you through creating a wine-tasting API with Flask-Jeroboam. We will focus on the specifics of Flask-Jeroboam, and will only touch on the general aspects of Flask as they are already excellent resources on the matter. If you’re new to the latter, we can only recommend that you go over the Flask Tutorial, which is excellent and will put you on the right track. You will be in a much better place to appreciate what Flask-Jeroboam actually brings to the table and decide wether it’s for you or not.
(coming soon)
Setting Things up¶
If you already have dependency management in Python figured out, skip our next section and jump to the walkthrough. If not, check it out or go to the Documentation Overview.
About Dependency Management¶
Virtual environments¶
Virtual environments are essential to isolate your project’s dependencies from the system-wide packages, preventing version conflicts and providing reproducibility and ease of deployment to contributors and yourself. Additionally, it allows for using different versions of packages for various projects on the same machine without conflicts.
It’s a good practice, a necessary one even.
I find the combination of pyenv and poetry to work very well together, and I will walk you through an example. But anything that’s already working for you is fine.
Python Version¶
Your first dependency, and the main one at that, is your Python installation. When you overlook this, you end up using your system’s default, often outdated, Python installation.
The best practice is to use the latest stable version of Python, which is 3.11 as I write this. see how to install a specific python version. The Python core team is doing a fantastic job, and it would be a shame to miss out on all the improvement they bring to the game release after release.
That being said, Flask-Jeroboam supports Python down to its 3.8 installment. It means that the CI/CD pipeline tests the package from Python 3.8 to the most recent release. As python versions reach their end of life, we will drop supporting them but keep up with the newest releases.
A complete installation walkthrough¶
To follow this section, you must have pyenv and poetry installed on your system. If this is not the case, follow the following instructions: installing pyenv and installing poetry.
Install the latest Python version¶
First, you want to pick a specific Python version to install and activate. As said above, the latest stable version is your best option. Let’s install it using pyenv:
# Output may vary
# Install the latest version of Python
$ pyenv install 3.11
Downloading Python-3.11.1.tar.xz...
-> https://www.python.org/ftp/python/3.11.1/Python-3.11.1.tar.xz
Installing Python-3.11.1...
Installed Python-3.11.1 to XXXX/.pyenv/versions/3.11.1
# Activate it
$ pyenv local 3.11
# Check if it worked
$ python --version
Python 3.11.1
Once you’ve secured the correct Python version, you can create a virtual environment for your project.
Create an environment¶
The poetry CLI can either start the project from scratch (with minimal scaffolding) or hook to an existing project.
In the latter case, the poetry CLI will prompt you for meta information like your project’s title,
description, author, and license. Don’t worry too much about it now: you can edit any of this information
in the `pyproject.toml`
file later.
Let’s assume you’re starting a new project without using poetry’s scaffolding capabilities.
# Make root dir and move to it
$ mdir jeroboam-demo && cd jeroboam-demo
# Create a poetry environment
$ poetry init
# Make sure you hooked the env to the intended version of Python
$ poetry use 3.11
Activate the environment¶
Before you do anything on your project, you must activate the corresponding environment:
$ poetry shell
If configured with the right plugins, your shell prompt will change to show the name of the activated environment, which will come in handy.
Note
Alternatively, you can use shell plugins to activate automatically virtual environments created by Poetry like zsh-poetry.
Add & Install Flask-Jeroboam in your environment¶
Now you are ready to install Flask-Jeroboam. As we’ve seen before, this would go like this:
$ poetry add flask-jeroboam
Other Ressources¶
Michael Grinberg’s Mega Tutorial is a must read. It’s a great introduction to Flask and covers a lot of ground.
FastAPI Documentation is in my opinion a gold standard. It’s very well written, and thorough and the examples are easy to follow. You should definitely check it out. It holds value even if you’re not going to use FastAPI.
API Reference¶
If you are looking for information on a specific function, class or method, this part of the documentation is for you.
API Reference¶
In this part of the documentation, we will cover Flask-Jeroboam’s internals.
App & Blueprint Objects¶
- class flask_jeroboam.jeroboam.Jeroboam(*args, **kwargs)¶
Bases:
JeroboamScaffoldOverRide
,Flask
A Flask Object with extra functionalities.
The route method is overriden by a custom flask_jeroboam route decorator.
- Parameters:
args (Any) –
kwargs (Any) –
- init_app(app=None)¶
Setup is performed after app has received all its configuration.
- Parameters:
app (Jeroboam | None) –
- Return type:
None
- property openapi: OpenAPI¶
Get the OpenApi object.
- query_string_key_transformer: Callable | None = None¶
- response_class¶
alias of
JSONResponse
- url_rule_class¶
alias of
JeroboamRule
- class flask_jeroboam.blueprint.Blueprint(*args, tags=None, include_in_openapi=True, **kwargs)¶
Bases:
JeroboamScaffoldOverRide
,Blueprint
Regular Blueprint with extra behavior on route definition.
- Parameters:
args (Any) –
tags (List[str] | None) –
include_in_openapi (bool) –
kwargs (Any) –
Jeroboam View¶
- class flask_jeroboam.view.JeroboamView(rule, original_view_func, options, response_class=<class 'flask_jeroboam.responses.JSONResponse'>)¶
Bases:
object
Adds flask-jeroboam features to a regular flask view function.
The InboundHandler and OutboundHandler are configured here. The as_view property returns an augmented view function ready to be used by Flask, adding inbound and outbound data handling behavior if needed. The resulting view function is a regular flask view function, and any overhead related to figuring out what needs to be done is spent at registration time.
- Parameters:
rule (str) –
original_view_func (Callable[[...], Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication] | Callable[[...], Awaitable[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]]) –
options (Dict[str, Any]) –
response_class (Type | None) –
- property as_view: Callable[[...], Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication] | Callable[[...], Awaitable[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int] | Tuple[Response | DataClassType | BaseModel | str | bytes | List[Any] | List[BaseModel] | Mapping[str, Any] | Iterator[str] | Iterator[bytes] | DataclassProtocol, int, Headers | Mapping[str, str | List[str] | Tuple[str, ...]] | Sequence[Tuple[str, str | List[str] | Tuple[str, ...]]]] | WSGIApplication]]¶
Decorate the orinal view function with handlers..
TODO: Deal with name, docs, before decorating
- property main_method: str¶
Return the main HTTP verb of the Endpoint.
- property parameters: List[SolvedArgument]¶
Return the main HTTP verb of the Endpoint.
API Reference¶
- flask_jeroboam.view_arguments.functions.Path(*args, **kwargs)¶
Declare A Path parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.Query(*args, **kwargs)¶
Declare A Query parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.Header(*args, **kwargs)¶
Declare A Header parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.Cookie(*args, **kwargs)¶
Declare A Cookie parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.Body(*args, **kwargs)¶
Declare A Body parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.Form(*args, **kwargs)¶
Declare A Form parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
- flask_jeroboam.view_arguments.functions.File(*args, **kwargs)¶
Declare A File parameter.
- Parameters:
args (Any) –
kwargs (Any) –
- Return type:
Any
Additional Notes¶
Contributor Guide¶
I appreciate your interest in improving this project. This project is open-source under the MIT license and welcomes contributions in the form of bug reports, feature requests, and pull requests. Currently, our focus is on improving documentation and hunting for bugs.
We intend to use the Issue Tracker to coordinate the community and provide templates for bug reports, feature requests, documentation updates, and implementation improvements. So be sure to use the appropriate template with further instructions on writing any.
Ressources¶
Here is a list of important resources for contributors:
How to set up your development environment¶
You need Python 3.8+ and the following tools:
Install the package with development requirements:
$ poetry install
You can now run an interactive Python session, or the command-line interface:
$ poetry run python
$ poetry run flask-jeroboam
How to test the project¶
Run the full test suite:
$ nox
List the available Nox sessions:
$ nox --list-sessions
You can also run a specific Nox session. For example, invoke the unit test suite like this:
$ nox --session=tests
Unit tests are located in the tests directory, and are written using the pytest testing framework.
How to submit changes¶
Open a pull request to submit changes to this project.
Your pull request needs to meet the following guidelines for acceptance:
The Nox test suite must pass without errors and warnings.
Include unit tests. This project maintains 100% code coverage.
If your changes add functionality, update the documentation accordingly.
Feel free to submit early, though—we can always iterate on this.
To run linting and code formatting checks before committing your change, you can install pre-commit as a Git hook by running the following command:
$ nox --session=pre-commit -- install
It is recommended to open an issue before starting work on anything. This will allow a chance to talk it over with the owners and validate your approach.
Contributor Covenant Code of Conduct¶
Our Pledge¶
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
Our Standards¶
Examples of behavior that contributes to a positive environment for our community include:
Demonstrating empathy and kindness toward other people
Being respectful of differing opinions, viewpoints, and experiences
Giving and gracefully accepting constructive feedback
Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
The use of sexualized language or imagery, and sexual attention or advances of any kind
Trolling, insulting or derogatory comments, and personal or political attacks
Public or private harassment
Publishing others’ private information, such as a physical or email address, without their explicit permission
Other conduct which could reasonably be considered inappropriate in a professional setting
Enforcement Responsibilities¶
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
Scope¶
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
Enforcement¶
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at jc.bianic@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
Enforcement Guidelines¶
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
1. Correction¶
Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
2. Warning¶
Community Impact: A violation through a single incident or series of actions.
Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
3. Temporary Ban¶
Community Impact: A serious violation of community standards, including sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
4. Permanent Ban¶
Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within the community.
Attribution¶
This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozilla’s code of conduct enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
License¶
MIT License
Copyright © 2022 Jean-Christophe Bianic
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Changes¶
beta releases¶
The beta releases mark a turning point and the intention to outgrow Flask-Jeroboam’s initial internal use. The 0.1.0 version is primarily based on forking FastAPI and departs from the first naive implementation of alpha releases.
Version 0.1.0.beta2¶
Released March, 9th 2023
Writing Documentation
Fixing bugs
Breaking Change: Fixing the embed mechnism for request bodies
Version 0.1.0.beta¶
Released February, 22nd 2023
Support for explicit location of inbound arguments with special functions
Support for validation options on explicit inbound arguments
Response Serialization mechanism is improved
OpenAPI Documentation Auto-generation
You can add Tags to endpoints and blueprints
Extensive Refactoring of the codebase
alpha releases¶
The alpha releases share a minimalist implementation of a tiny portion of the targetted features set. They are a packaged version of helper functions that I used to level up one particular flask project and are largely overfitted to this specific context.
Version 0.0.3.alpha¶
Released January, 16th 2023
Improved request parsing
Introducing Model Utils (Parsers and Serializers)
Improvement of type hinting
Version 0.0.2.alpha¶
Released January, 9th 2023
Upgrade Dependencies
Version 0.0.1.alpha¶
Released August, 16th 2022
First public version
Basic request parsing-validation-injection using type hints of view function arguments
Basic response serialization using response_model on route decorators