Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Python Microservices Development – 2nd edition
Python Microservices Development – 2nd edition

Python Microservices Development – 2nd edition: Build efficient and lightweight microservices using the Python tooling ecosystem , Second Edition

eBook
€20.98 €29.99
Paperback
€36.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Python Microservices Development – 2nd edition

Discovering Quart

Quart was started in 2017 as an evolution of the popular Flask framework. Quart shares many of the same design decisions as Flask, and so a lot of the advice for one will work with the other. This book will focus on Quart to allow us to support asynchronous operations and to explore features such as WebSockets and HTTP/2 support.

Quart and Flask are not the only Python frameworks. There is a long history of projects aimed at providing services on the web, such as Bottle, cherrypy, and Django. All of these tools are used around the web, and they all share a similar goal: to offer the Python community simple tools for building web applications quickly.

The smaller frameworks, such as Quart and Bottle, are often called microframeworks; however, the term can be a bit misleading. It does not mean you can only create micro-applications. Using those tools, you can build any application, large or small. The prefix "micro" means that the framework tries to make as few decisions as possible. It lets you freely organize your application code and use whichever libraries you want.

A microframework acts as the glue code that delivers requests to your system and sends back responses. It does not enforce any particular paradigm on your project.

A typical example of this philosophy is when you need to interact with a SQL database. A framework such as Django is batteries-included and provides everything you need to build your web app, including an Object-Relational Mapper (ORM) to bind objects with database query results.

If you want to use an alternative ORM such as SQLAlchemy in Django to benefit from some of its great features, you'd be choosing a difficult path that would involve rewriting a lot of the Django library you are hoping to make use of, because of the tight integration Django has with the ORM it comes with. For certain applications, that's a good thing, but not necessarily for producing a microservice.

Quart, on the other hand, does not have a built-in library to interact with your data, leaving you free to choose your own. The framework will only attempt to make sure it has enough hooks to be extended by external libraries to provide various kinds of features. In other words, using an ORM in Quart, and making sure you're doing the right thing with SQL sessions and transactions, will mostly consist of adding a package such as SQLAlchemy to your project. If you don't like how a particular library integrates, you're free to use another one or to build your own integration. Quart can also make use of the more common Flask extensions, although there is a performance risk there as they are unlikely to be asynchronous and could block your application's work.

Of course, that's not a silver bullet. Being completely free in your choices also means that it is easier to make poor decisions and build an application that relies on defective libraries, or one that is not well designed. But fear not! This chapter will make sure you know what Quart has to offer, and how to organize your code for building microservices.

This chapter covers the following topics:

  • Making sure we have Python
  • How Quart handles requests
  • Quart's built-in features
  • A microservice skeleton

The goal of this chapter is to give you all the information needed to build microservices with Quart. By doing so, it inevitably duplicates some of the information you can find in Quart's official documentation, but focuses on providing interesting details and anything relevant when building microservices. Quart and Flask have good online documentation.

Make sure you take a look at Quart's and Flask's documentation, listed respectively:

Both should serve as a great complement to this chapter. The source code is located at https://gitlab.com/pgjones/quart.

This is worth being aware of, as the source code is always the ultimate truth when you need to understand how the software works.

Making sure we have Python

Before we start digging into its features, we should make sure that we have Python installed and working!

You might see some documentation or posts online that mention Python version 2. There was a long transition from Python 2 to Python 3, and had this book been written a few years earlier, we would be discussing the merits of each. However, Python 3 is fully capable of everything the majority of people need to do, and Python 2 stopped being supported by the core Python team in 2020. This book uses the latest Python 3.9 stable release for all its code examples, but they are likely to work on Python 3.7 or later, as that's the minimum version that Quart requires in order to work.

If your computer does not have at least Python 3.7, you can download a new version from Python's own website, where installation instructions are provided: https://www.python.org/downloads/.

You will find it easier if all the code examples in this book are run in a virtual environment, or virtualenv (https://docs.python.org/3/library/venv.html). A virtual environment is Python's way of keeping each project separate, as it means you can install Quart and any other libraries you need; it will only affect the application you are currently working on. Other applications and projects can have different libraries, or different versions of the same library, without them getting in the way of each other. Using a virtualenv also means that you can easily recreate your project's dependencies somewhere else, which will be very useful when we deploy a microservice in a later chapter.

Some code editors, such as PyCharm or Visual Studio, may manage a virtual environment for you. Every code example in the book runs in a terminal, and so we will use a terminal to create our virtualenv. This also shows how things work in more detail than viewing a program's output on the web, or in log files, and will be helpful when fixing any problems in the future.

In a terminal, such as a macOS Terminal application, or a Windows Subsystem for Linux, change to the directory you would like to work in and run the following command:

python -m venv my-venv

Depending on how you installed Python, you may need to use python3 to create the virtual environment.

This creates a new virtual environment called my-venv in the current directory. You could give it another path if you like, but it's important to remember where it is. To use the virtual environment, you must activate it:

source my-venv/bin/activate

For most of the command-line examples in this book, we assume you are running on Linux, as that is what most services online use, so it is good to be familiar with it. This means that most of the commands will also work on macOS or on Windows using the Windows Subsystem for Linux. It's also possible to run Docker containers on all these systems, and we will describe containers later on when we discuss deploying your microservice.

Now, let's install Quart so that we can run our example code:

pip install quart

To stop using the virtual environment without closing the terminal, you can type deactivate. For now, though, let's keep the virtualenv active and look at how Quart will work.

How Quart handles requests

The framework entry point is the Quart class in the quart.app module. Running a Quart application means running one single instance of this class, which will take care of handling incoming Asynchronous Server Gateway Interface (ASGI) and Web Server Gateway Interface (WSGI) requests, dispatch them to the right code, and then return a response. Remember that in Chapter 1, Understanding Microservices, we discussed ASGI and WSGI, and how they define the interface between a web server and a Python application.

The Quart class offers a route method, which can decorate your functions. When you decorate a function this way, it becomes a view and is registered in the routing system.

When a request arrives, it will be to a specific endpoint—usually a web address (such as https://duckduckgo.com/?q=quart) or part of an address, such as /api. The routing system is how Quart connects an endpoint to the view—the bit of code that will run to process the request.

Here's a very basic example of a fully functional Quart application:

# quart_basic.py
from quart import Quart
app = Quart(__name__)
@app.route("/api")
def my_microservice():
    return {"Hello": "World!"}
if __name__ == "__main__":
    app.run()

All the code samples are available on GitHub at https://github.com/PacktPublishing/Python-Microservices-Development-2nd-Edition/tree/main/CodeSamples.

We see that our function returns a dictionary, and Quart knows that this should be encoded as a JSON object to be transferred. However, only querying the /api endpoint returns the value. Every other endpoint would return a 404 Error, indicating that it can't find the resource you requested because we haven't told it about any!

The __name__ variable, whose value will be __main__ when you run that single Python module, is the name of the application package. It's used by Quart to create a new logger with that name to format all the log messages, and to find where the file is located on the disk. Quart will use the directory as the root for helpers, such as the configuration that is associated with your app, and to determine default locations for the static and templates directories, which we will discuss later.

If you run that module in a terminal, the Quart app will run its own development web server, and start listening to incoming connections on port 5000. Here, we assume that you are still in the virtual environment created earlier and that the code above is in a file called quart_basic.py:

$ python quart_basic.py 
 * Serving Quart app 'quart_basic'
 * Environment: production
 * Please use an ASGI server (e.g. Hypercorn) directly in production
 * Debug mode: False
 * Running on http://localhost:5000 (CTRL + C to quit)
[2020-12-10 14:05:18,948] Running on http://localhost:5000 (CTRL + C to quit)

Visiting http://localhost:5000/api in your browser or with the curl command will return a valid JSON response with the right headers:

$ curl -v http://localhost:5000/api 
*   Trying localhost...
...
< HTTP/1.1 200
< content-type: application/json
< content-length: 18
< date: Wed, 02 Dec 2020 20:29:19 GMT
< server: hypercorn-h11
<
* Connection #0 to host localhost left intact
{"Hello":"World!"}* Closing connection 0

The curl command is going to be used a lot in this book. If you are under Linux or macOS, it should be pre-installed; refer to https://curl.haxx.se/.

If you are not developing your application on the same computer as the one that you are testing it on, you may need to adjust some of the settings, such as which IP addresses it should use to listen for connections. When we discuss deploying a microservice, we will cover some of the better ways of changing its configuration, but for now, the app.run line can be changed to use a different host and port:

app.run(host="0.0.0.0", port=8000)

While many web frameworks explicitly pass a request object to your code, Quart provides a global request variable, which points to the current request object it built for the incoming HTTP request.

This design decision makes the code for the simpler views very concise. As in our example, if you don't have to look at the request content to reply, there is no need to have it around. As long as your view returns what the client should get and Quart can serialize it, everything happens as you would hope. For other views, they can just import that variable and use it.

The request variable is global, but it is unique to each incoming request and is thread-safe. Let's add some print method calls here and there so that we can see what's happening under the hood. We will also explicitly make a Response object using jsonify, instead of letting Quart do that for us, so that we can examine it:

# quart_details.py
from quart import Quart, request, jsonify
app = Quart(__name__)
@app.route("/api", provide_automatic_options=False)
async def my_microservice():
    print(dir(request))
    response = jsonify({"Hello": "World!"})
    print(response)
    print(await response.get_data())
    return response
if __name__ == "__main__":
    print(app.url_map)
    app.run()

Running that new version in conjunction with the curl command in another terminal, you get a lot of details, including the following:

$ python quart_details.py 
QuartMap([<QuartRule '/api' (HEAD, GET, OPTIONS) -> my_microservice>,
 <QuartRule '/static/<filename>' (HEAD, GET, OPTIONS) -> static>])
Running on http://localhost:5000 (CTRL + C to quit)
  
[… '_load_field_storage', '_load_form_data', '_load_json_data', '_send_push_promise', 'accept_charsets', 'accept_encodings', 'accept_languages', 'accept_mimetypes', 'access_control_request_headers', 'access_control_request_method', 'access_route', 'args', 'authorization', 'base_url', 'blueprint', 'body', 'body_class', 'body_timeout', 'cache_control', 'charset', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date', 'dict_storage_class', 'encoding_errors', 'endpoint', 'files', 'form', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'http_version', 'if_match', 'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'is_json', 'is_secure', 'json', 'list_storage_class', 'max_forwards', 'method', 'mimetype', 'mimetype_params', 'on_json_loading_failed', 'origin', 'parameter_storage_class', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr', 'root_path', 'routing_exception', 'scheme', 'scope', 'send_push_promise', 'url', 'url_charset', 'url_root', 'url_rule', 'values', 'view_args']
Response(200)
b'{"Hello":"World!"}'

Let's explore what's happening here:

  • Routing: When the service starts, Quart creates the QuartMap object, and we can see here what it knows about endpoints and the associated views.
  • Request: Quart creates a Request object and my_microservice is showing us that it is a GET request to /api.
  • dir() shows us which methods and variables are in a class, such as get_data() to retrieve any data that was sent with the request.
  • Response: A Response object to be sent back to the client; in this case, curl. It has an HTTP response code of 200, indicating that everything is fine, and its data is the 'Hello world' dictionary we told it to send.

Routing

Routing happens in app.url_map, which is an instance of the QuartMap class that uses a library called Werkzeug. That class uses regular expressions to determine whether a function decorated by @app.route matches the incoming request. The routing only looks at the path you provided in the route call to see whether it matches the client's request.

By default, the mapper will only accept GET, OPTIONS, and HEAD methods on a declared route. Sending an HTTP request to a valid endpoint with an unsupported method will return a 405 Method Not Allowed response together with a list of supported methods in the allow header:

$ curl -v -XDELETE  http://localhost:5000/api
**   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> DELETE /api HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 405
< content-type: text/html
< allow: GET, OPTIONS, HEAD
< content-length: 137
< date: Wed, 02 Dec 2020 21:14:36 GMT
< server: hypercorn-h11
<
<!doctype html>
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
Specified method is invalid for this resource
* Connection #0 to host 127.0.0.1 left intact
    * Closing connection 0

If you want to support specific methods allowing you to POST to an endpoint or DELETE some data, you can pass them to the route decorator with the methods argument, as follows:

@app.route('/api', methods=['POST', 'DELETE', 'GET']) 
def my_microservice(): 
    return {'Hello': 'World!'}

Note that the OPTIONS and HEAD methods are implicitly added in all rules since it is automatically managed by the request handler. You can deactivate this behavior by giving the provide_automatic_options=False argument to the route function. This can be useful when you want to add custom headers to the response when OPTIONS is called, such as when dealing with Cross-Origin Resource Sharing (CORS), in which you need to add several Access-Control-Allow-* headers.

For more information regarding HTTP request methods, a good resource is the Mozilla Developer Network: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods.

Variables and converters

A common requirement for an API is the ability to specify exactly which data we want to request. For example, if you have a system where each person has a unique number to identify them, you might want to create a function that handles all requests sent to the /person/N endpoint, so that /person/3 only deals with ID number 3, and /person/412 only affects the person with ID 412.

You can do this with variables in the route, using the <VARIABLE_NAME> syntax. This notation is pretty standard (Bottle also uses it), and allows you to describe endpoints with dynamic values. If we create a route such as /person/<person_id>, then, when Quart calls your function, it converts the value it finds in the URL to a function argument with the same name:

@app.route('/person/<person_id>') 
def person(person_id): 
    return {'Hello': person_id}
 
$ curl localhost:5000/person/3 
{"Hello": "3"} 

If you have several routes that match the same URL, the mapper uses a particular set of rules to determine which one it calls. Quart and Flask both use Werkzeug to organize their routing; this is the implementation description taken from Werkzeug's routing module:

  1. Rules without any arguments come first for performance. This is because we expect them to match faster and some common rules usually don't have any arguments (index pages, and so on).
  2. The more complex rules come first, so the second argument is the negative length of the number of weights.
  3. Lastly, we order by the actual weights.

Werkzeug's rules have, therefore, weights that are used to sort them, and this is not used or made visible in Quart. So, it boils down to picking views with more variables first, and then the others, in order of appearance, when Python imports the different modules. The rule of thumb is to make sure that every declared route in your app is unique, otherwise tracking which one gets picked will give you a headache.

This also means that our new route will not respond to queries sent to /person, or /person/3/help, or any other variation—only to /person/ followed by some set of characters. Characters include letters and punctuation, though, and we have already decided that /api/apiperson_id is a number! This is where converters are useful.

We can tell the route that a variable has a specific type. Since /api/apiperson_id is an integer, we can use <int:person_id>, as in the previous example, so that our code only responds when we give a number, and not when we give a name. You can also see that instead of the string "3", person_id is a number, with no quotes:

@app.route('/person/<int:person_id>') 
def person(person_id): 
    return {'Hello': person_id}
$ curl localhost:5000/person/3 
{ 
  "Hello": 3 
} 
$ curl localhost:5000/person/simon
<!doctype html>
<title>404 Not Found</title>
<h1>Not Found</h1>
Nothing matches the given URI

If we had two routes, one for /person/<int:person_id> and one for /person/<person_id> (with different function names!), then the more specific one, which needs an integer, would get all the requests that had a number in the right place, and the other function would get the remaining requests.

Built-in converters are string (the default is a Unicode string), int, float, path, any, and uuid.

The path converter is like the default converter, but includes forward slashes, so that a request to a URL, /api/some/path/like/this, would match the route /api/<path:my_path>, and the function would get an argument called my_path containing some/path/like/this. If you are familiar with regular expressions, it's similar to matching [^/].*?.

int and float are for integers and floating-point—decimal—numbers. The any converter allows you to combine several values. It can be a bit confusing to use at first, but it might be useful if you need to route several specific strings to the same place. A route of /<any(about, help, contact):page_name> will match requests to /about, /help, or /contact, and which one was chosen will be in the page_name variable passed to the function.

The uuid converter matches the UUID strings, such as those that you get from Python's uuid module, providing unique identifiers. Examples of all these converters in action are also in the code samples for this chapter on GitHub.

It's quite easy to create your custom converter. For example, if you want to match user IDs with usernames, you could create a converter that looks up a database and converts the integer into a username. To do this, you need to create a class derived from the BaseConverter class, which implements two methods: the to_python() method to convert the value to a Python object for the view, and the to_url() method to go the other way (used by url_for(), which is described in the next section):

# quart_converter.py
from quart import Quart, request 
from werkzeug.routing import BaseConverter, ValidationError
_USERS = {"1": "Alice", "2": "Bob"}
_IDS = {val: user_id for user_id, val in _USERS.items()}
class RegisteredUser(BaseConverter):
    def to_python(self, value):
        if value in _USERS:
            return _USERS[value]
        raise ValidationError()
    def to_url(self, value):
        return _IDS[value]
app = Quart(__name__)
app.url_map.converters["registered"] = RegisteredUser
@app.route("/api/person/<registered:name>")
def person(name):
    return {"Hello": name}
if __name__ == "__main__":
    app.run()

The ValidationError method is raised in case the conversion fails, and the mapper will consider that the route simply does not match that request. Let's try a few calls to see how that works in practice:

$ curl localhost:5000/api/person/1 
{ 
  "Hello hey": "Alice" 
}
 
$ curl localhost:5000/api/person/2 
{ 
  "Hello hey": "Bob" 
}
 
$ curl localhost:5000/api/person/3 
 
<!doctype html>
<title>404 Not Found</title>
<h1>Not Found</h1>
Nothing matches the given URI

Be aware that the above is just an example of demonstrating the power of converters—an API that handles personal information in this way could give a lot of information away to malicious people. It can also be painful to change all the routes when the code evolves, so it is best to only use this sort of technique when necessary.

The best practice for routing is to keep it as static and straightforward as possible. This is especially true as moving all the endpoints requires changing all of the software that connects to them! It is often a good idea to include a version in the URL for an endpoint so that it is immediately clear that the behavior will be different between, for example, /v1/person and /v2/person.

The url_for function

The last interesting feature of Quart's routing system is the url_for() function. Given any view, it will return its actual URL. Here's an example of using Python interactively:

>>> from quart_converter import app 
>>> from quart import url_for 
>>> import asyncio
>>> async def run_url_for():
...     async with app.test_request_context("/", method="GET"):
...         print(url_for('person', name='Alice')) 
... 
>>> loop = asyncio.get_event_loop()
>>> loop.run_until_complete(run_url_for())
/api/person/1  

The previous example uses the Read-Eval-Print Loop (REPL), which you can get by running the Python executable directly. There is also some extra code there to set up an asynchronous program because here, Quart is not doing that for us.

The url_for feature is quite useful in templates when you want to display the URLs of some views—depending on the execution context. Instead of hardcoding some links, you can just point the function name to url_for to get it.

Request

When a request comes in, Quart calls the view and uses a Request Context to make sure that each request has an isolated environment, specific to that request. We saw an example of that in the code above, where we were testing things using the helper method, test_request_context(). In other words, when you access the global request object in your view, you are guaranteed that it is unique to the handling of your specific request.

As we saw earlier when calling dir(request), the Request object contains a lot of methods when it comes to getting information about what is happening, such as the address of the computer making the request, what sort of request it is, and other information such as authorization headers. Feel free to experiment with some of these request methods using the example code as a starting point.

In the following example, an HTTP Basic Authentication request that is sent by the client is always converted to a base64 form when sent to the server. Quart will detect the Basic prefix and will parse it into username and password fields in the request.authorization attribute:

# quart_auth.py
from quart import Quart, request
app = Quart(__name__)
@app.route("/")
def auth():
    print("Quart's Authorization information")
    print(request.authorization)
    return ""
if __name__ == "__main__":
    app.run()
$ python quart_auth.py 
* Running on http://localhost:5000/ (Press CTRL+C to quit) 
Quart's Authorization information
{'username': 'alice', 'password': 'password'} 
[2020-12-03 18:34:50,387] 127.0.0.1:55615 GET / 1.1 200 0 3066
$ curl http://localhost:5000/ --user alice:password

This behavior makes it easy to implement a pluggable authentication system on top of the request object. Other common request elements, such as cookies and files, are all accessible via other attributes, as we will discover throughout this book.

Response

In many of the previous examples, we have simply returned a Python dictionary and left Quart to produce a response for us that the client will understand. Sometimes, we have called jsonify() to ensure that the result is a JSON object.

There are other ways to make a response for our web application, along with some other values that are automatically converted to the proper object for us. We could return any of the following, and Quart would do the right thing:

  • Response(): Creates a Response object manually.
  • str: A string will be encoded as a text/html object in the response. This is especially useful for HTML pages.
  • dict: A dictionary will be encoded as application/json using jsonify().
  • A generator or asynchronous generator object can be returned so that data can be streamed to the client.
  • A (response, status) tuple: The response will be converted to a response object if it matches one of the preceding data types, and the status will be the HTTP response code used.
  • A (response, status, headers) tuple: The response will be converted, and the response object will use a dictionary provided as headers that should be added to the response.

In most cases, a microservice will be returning data that some other software will interpret and choose how to display, and so we will be returning Python dictionaries or using jsonify() if we want to return a list or other object that can be serialized as JSON.

Here's an example with YAML, another popular way of representing data: the yamlify() function will return a (response, status, headers) tuple, which will be converted by Quart into a proper Response object:

# yamlify.py
from quart import Quart
import yaml  # requires PyYAML
app = Quart(__name__)
def yamlify(data, status=200, headers=None):
    _headers = {"Content-Type": "application/x-yaml"}
    if headers is not None:
        _headers.update(headers)
    return yaml.safe_dump(data), status, _headers
@app.route("/api")
def my_microservice():
    return yamlify(["Hello", "YAML", "World!"])
if __name__ == "__main__":
    app.run()

The way Quart handles requests can be summarized as follows:

  1. When the application starts, any function decorated with @app.route() is registered as a view and stored in app.url_map.
  2. A call is dispatched to the right view depending on its endpoint and method.
  3. A Request object is created in a local, isolated execution context.
  4. A Response object wraps the content to send back.

These four steps are roughly all you need to know to start building apps using Quart. The next section will summarize the most important built-in features that Quart offers, alongside this request-response mechanism.

Quart's built-in features

The previous section gave us a good understanding of how Quart processes a request, and that's good enough to get you started. There are more helpers that will prove useful. We'll discover the following main ones in this section:

  • The session object: Cookie-based data
  • Globals: Storing data in the request context
  • Signals: Sending and intercepting events
  • Extensions and middleware: Adding features
  • Templates: Building text-based content
  • Configuring: Grouping your running options in a config file
  • Blueprints: Organizing your code in namespaces
  • Error handling and debugging: Dealing with errors in your app

The session object

Like the request object, Quart creates a session object, which is unique to the request context. It's a dict-like object, which Quart serializes into a cookie on the user side. The data contained in the session mapping is dumped into a JSON mapping, then compressed using zlib to make it smaller, and finally encoded in base64.

When the session gets serialized, the itsdangerous (https://pythonhosted.org/itsdangerous/) library signs the content using a secret_key value defined in the application. The signing uses HMAC (https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) and SHA1.

This signature, which is added to the data as a suffix, ensures that the client cannot tamper with the data that is stored in a cookie unless they know the secret key to sign the session value. Note that the data itself is not encrypted. Quart will let you customize the signing algorithm to use, but HMAC + SHA1 is good enough when you need to store data in cookies.

However, when you're building microservices that are not producing HTML, you rarely rely on cookies as they are specific to web browsers. However, the idea of keeping a volatile key-value storage for each user can be extremely useful for speeding up some of the server-side work. For instance, if you need to perform some database look-ups to get some information pertaining to a user every time they connect, caching this information in a session-like object on the server side and retrieving the values based on their authentication details makes a lot of sense.

Globals

As discussed earlier in this chapter, Quart provides a mechanism for storing global variables that are unique to a particular request context. That is used for request and session, but is also available to store any custom object.

The quart.g variable contains all globals, and you can set whatever attributes you want on it. In Quart, the @app.before_request decorator can be used to point to a function that the app will call every time a request is made, just before it dispatches the request to a view.

It's a typical pattern in Quart to use before_request to set values in the globals. That way, all the functions that are called within the request context can interact with the special global variable called g and get the data. In the following example, we copy the username provided when the client performs an HTTP Basic Authentication in the user attribute:

# globals.py
from quart import Quart, g, request
app = Quart(__name__)
@app.before_request
def authenticate():
    if request.authorization:
        g.user = request.authorization["username"]
    else:
        g.user = "Anonymous"
@app.route("/api")
def my_microservice():
    return {"Hello": g.user}
if __name__ == "__main__":
    app.run()

When a client requests the /api view, the authenticate function will set g.user depending on the headers provided:

$ curl http://localhost:5000/api 
{ 
  "Hello": "Anonymous" 
} 
$ curl http://localhost:5000/api --user alice:password 
{ 
  "Hello": "alice" 
} 

Any data you may think of that's specific to a request context, and that would be usefully shared throughout your code, can be added to quart.g.

Signals

Sometimes in an application, we want to send a message from one place to another, when components are not directly connected. One way in which we can send such messages is to use signals. Quart integrates with Blinker (https://pythonhosted.org/blinker/), which is a signal library that lets you subscribe a function to an event.

Events are instances of the AsyncNamedSignal class, which is based on the blinker.base.NamedSignal class. It is created with a unique label, and Quart instantiates 10 of them in version 0.13. Quart triggers signals at critical moments during the processing of a request. Since Quart and Flask use the same system, we can refer to the following full list: http://flask.pocoo.org/docs/latest/api/#core-signals-list.

Registering to a particular event is done by calling the signal's connect method. Signals are triggered when some code calls the signal's send method. The send method accepts extra arguments to pass data to all the registered functions.

In the following example, we register the finished function to the request_finished signal. That function will receive the response object:

# signals.py
from quart import Quart, g, request_finished
from quart.signals import signals_available
app = Quart(__name__)
def finished(sender, response, **extra):
    print("About to send a Response")
    print(response)
request_finished.connect(finished)
@app.route("/api")
async def my_microservice():
    return {"Hello": "World"}
if __name__ == "__main__":
    app.run()

The signal feature is provided by Blinker, which is installed by default as a dependency when you install Quart.

Some signals implemented in Quart are not useful in microservices, such as the ones occurring when the framework renders a template. However, there are some interesting signals that Quart triggers throughout the request life, which can be used to log what's going on. For instance, the got_request_exception signal is triggered when an exception occurs before the framework does something with it. That's how Sentry's (https://sentry.io) Python client hooks itself in to log exceptions.

It can also be interesting to implement custom signals in your apps when you want to trigger some of your features with events and decouple the code. For example, if your microservice produces PDF reports, and you want to have the reports cryptographically signed, you could trigger a report_ready signal, and have a signer register to that event.

One important aspect of the signals implementation is that the registered functions are not called in any particular order, and so if there are dependencies between the functions that get called, this may cause trouble. If you need to do more complex or time-consuming work, then consider using a queue such as RabbitMQ (https://www.rabbitmq.com/) or one provided by a cloud platform such as Amazon Simple Queue Service or Google PubSub to send a message to another service. These message queues offer far more options than a basic signal and allow two components to communicate easily without even necessarily being on the same computer. We will cover an example of message queues in Chapter 6, Interacting with Other Services.

Extensions and middleware

Quart extensions are simply Python projects that, once installed, provide a package or a module named quart_something. They can be useful for avoiding having to reinvent anything when wanting to do things such as authentication or sending an email.

Because Quart can support some of the extensions available to Flask, you can often find something to help in Flask's list of extensions: Search for Framework::Flask in the Python package index at https://pypi.org/. To use Flask extensions, you must first import a patch module to ensure that it will work. For example, to import Flask's login extension, use the following commands:

import quart.flask_patch
import flask_login

The most up-to-date list of Flask extensions that are known to work with Quart will be at the address below. This is a good place to start looking when searching for extra features that your microservice needs: http://pgjones.gitlab.io/quart/how_to_guides/flask_extensions.html.

The other mechanism for extending Quart is to use ASGI or WSGI middleware. These extend the application by wrapping themselves around an endpoint and changing the data that goes in and comes out again.

In the example that follows, the middleware fakes an X-Forwarded-For header, so the Quart application thinks it's behind a proxy such as nginx. This is useful in a testing environment when you want to make sure your application behaves properly when it tries to get the remote IP address, since the remote_addr attribute will get the IP of the proxy, and not the real client. In this example, we have to create a new Headers object, as the existing one is immutable:

# middleware.py
from quart import Quart, request
from werkzeug.datastructures import Headers
class XFFMiddleware:
    def __init__(self, app, real_ip="10.1.1.1"):
        self.app = app
        self.real_ip = real_ip
    async def __call__(self, scope, receive, send):
        if "headers" in scope and "HTTP_X_FORWARDED_FOR" not in scope["headers"]:
            new_headers = scope["headers"].raw_items() + [
                (
                    b"X-Forwarded-For",
                    f"{self.real_ip}, 10.3.4.5, 127.0.0.1".encode(),
                )
            ]
            scope["headers"] = Headers(new_headers)
        return await self.app(scope, receive, send)
app = Quart(__name__)
app.asgi_app = XFFMiddleware(app.asgi_app)
@app.route("/api")
def my_microservice():
    if "X-Forwarded-For" in request.headers:
        ips = [ip.strip() for ip in request.headers["X-Forwarded-For"].split(",")]
        ip = ips[0]
    else:
        ip = request.remote_addr
    return {"Hello": ip}
if __name__ == "__main__":
    app.run()

Notice that we use app.asgi_app here to wrap the ASGI application. app.asgi_app is where the application is stored to let people wrap it in this way. The send and receive parameters are channels through which we can communicate. It's worth remembering that if the middleware returns a response to the client, then the rest of the Quart app will never see the request!

In most situations, we won't have to write our own middleware, and it will be enough to include an extension to add a feature that someone else has produced.

Templates

Sending back JSON or YAML documents is easy enough, as we have seen in the examples so far. It's also true that most microservices produce machine-readable data and if a human needs to read it, the frontend must format it properly, using, for example, JavaScript on a web page. In some cases, though, we might need to create documents with some layout, whether it's an HTML page, a PDF report, or an email.

For anything that's text-based, Quart integrates a template engine called Jinja (https://jinja.palletsprojects.com/). You will often find examples showing Jinja being used to create HTML documents, but it works with any text-based document. Configuration management tools such as Ansible use Jinja to create configuration files from a template so that a computer's settings can be kept up to date automatically.

Most of the time, Quart will use Jinja to produce HTML documents, email messages, or some other piece of communication meant for a human—such as an SMS message or a bot that talks to people on tools such as Slack or Discord. Quart provides helpers such as render_template, which generate responses by picking a Jinja template, and provides the output given some data.

For example, if your microservice sends emails instead of relying on the standard library's email package to produce the email content, which can be cumbersome, you could use Jinja. The following example email template should be saved as email_template.j2 in order for the later code examples to work:

Date: {{date}} 
From: {{from}} 
Subject: {{subject}} 
To: {{to}} 
Content-Type: text/plain 
 
Hello {{name}}, 
 
We have received your payment! 
 
Below is the list of items we will deliver for lunch: 
 
{% for item in items %}- {{item['name']}} ({{item['price']}} Euros) 
{% endfor %} 
 
Thank you for your business! 
 
-- 
My Fictional Burger Place

Jinja uses double brackets for marking variables that will be replaced by a value. Variables can be anything that is passed to Jinja at execution time. You can also use Python's if and for blocks directly in your templates with the {% for x in y % }... {% endfor %} and {% if x %}...{% endif %} notations.

The following is a Python script that uses the email template to produce an entirely valid RFC 822 message, which you can send via SMTP:

# email_render.py
from datetime import datetime
from jinja2 import Template
from email.utils import format_datetime
def render_email(**data):
    with open("email_template.j2") as f:
        template = Template(f.read())
    return template.render(**data)
data = {
    "date": format_datetime(datetime.now()),
    "to": "[email protected]",
    "from": "[email protected]",
    "subject": "Your Burger order",
    "name": "Bob",
    "items": [
        {"name": "Cheeseburger", "price": 4.5},
        {"name": "Fries", "price": 2.0},
        {"name": "Root Beer", "price": 3.0},
    ],
}
print(render_email(**data))

The render_email function uses the Template class to generate the email using the data provided.

Jinja is a powerful tool and comes with many features that would take too much space to describe here. If you need to do some templating work in your microservices, it is a good choice, also being present in Quart. Check out the following for full documentation on Jinja's features: https://jinja.palletsprojects.com/.

Configuration

When building applications, you will need to expose options to run them, such as the information needed to connect to a database, the contact email address to use, or any other variable that is specific to a deployment.

Quart uses a mechanism similar to Django in its configuration approach. The Quart object comes with an object called config, which contains some built-in variables, and which can be updated when you start your Quart app via your configuration objects. For example, you can define a Config class in a Python-format file as follows:

# prod_settings.py
class Config:
    DEBUG = False
    SQLURI = "postgres://username:xxx@localhost/db"

It can then be loaded from your app object using app.config.from_object:

>>> from quart import Quart
>>> import pprint
>>> pp = pprint.PrettyPrinter(indent=4)
>>> app = Quart(__name__) 
>>> app.config.from_object('prod_settings.Config') 
>>> pp.pprint(app.config) 
{   'APPLICATION_ROOT': None,
    'BODY_TIMEOUT': 60,
    'DEBUG': False,
    'ENV': 'production',
    'JSONIFY_MIMETYPE': 'application/json',
    'JSONIFY_PRETTYPRINT_REGULAR': False,
    'JSON_AS_ASCII': True,
    'JSON_SORT_KEYS': True,
    'MAX_CONTENT_LENGTH': 16777216,
    'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31),
    'PREFER_SECURE_URLS': False,
    'PROPAGATE_EXCEPTIONS': None,
    'RESPONSE_TIMEOUT': 60,
    'SECRET_KEY': None,
    'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200),
    'SERVER_NAME': None,
    'SESSION_COOKIE_DOMAIN': None,
    'SESSION_COOKIE_HTTPONLY': True,
    'SESSION_COOKIE_NAME': 'session',
    'SESSION_COOKIE_PATH': None,
    'SESSION_COOKIE_SAMESITE': None,
    'SESSION_COOKIE_SECURE': False,
    'SESSION_REFRESH_EACH_REQUEST': True,
    'SQLURI': 'postgres://username:xxx@localhost/db',
    'TEMPLATES_AUTO_RELOAD': None,
    'TESTING': False,
    'TRAP_HTTP_EXCEPTIONS': False}

However, there are two significant drawbacks when using Python modules as configuration files. Firstly, since these configuration modules are Python files, it can be tempting to add code to them as well as simple values. By doing so, you will have to treat those modules like the rest of the application code; this can be a complicated way to ensure that it always produces the right value, especially if the configuration is produced with a template! Usually, when an application is deployed, the configuration is managed separately from the code.

Secondly, if another team is in charge of managing the configuration file of your application, they will need to edit the Python code to do so. While this is usually fine, it makes it increase the chance that some problems will be introduced, as it assumes that the other people are familiar with Python and how your application is structured. It is often good practice to make sure that someone who just needs to change the configuration doesn't also need to know how the code works.

Since Quart exposes its configuration via app.config, it is quite simple to load additional options from a JSON, YAML, or other popular text-based configuration formats. All of the following examples are equivalent:

>>> from quart import Quart
>>> import yaml
>>> from pathlib import Path 
>>> app = Quart(__name__)
>>> print(Path("prod_settings.json").read_text())
{
    "DEBUG": false,
    "SQLURI":"postgres://username:xxx@localhost/db"
} 
>>> app.config.from_json("prod_settings.json")
>>> app.config["SQLURI"]
'postgres://username:xxx@localhost/db'
>>> print(Path("prod_settings.yml").read_text())
---
DEBUG: False
SQLURI: "postgres://username:xxx@localhost/db"
>>> app.config.from_file("prod_settings.yml", yaml.safe_load)

You can give from_file a function to use to understand the data, such as yaml.safe_load, toml.load, and json.load. If you prefer the INI format with [sections] along with name = value, then many extensions exist to help, and the standard library's ConfigParser is also straightforward.

Blueprints

When you write microservices that have more than a single endpoint, you will end up with a number of different decorated functions—remember those are functions with a decorator above, such as @app.route. The first logical step to organize your code is to have one module per endpoint, and when you create your app instance, make sure they get imported so that Quart registers the views.

For example, if your microservice manages a company's employees database, you could have one endpoint to interact with all employees, and one with teams. You could organize your application into these three modules:

  • app.py: To contain the Quart app object, and to run the app
  • employees.py: To provide all the views related to employees
  • teams.py: To provide all the views related to teams

From there, employees and teams can be seen as a subset of the app, and might have a few specific utilities and configurations. This is a standard way of structuring any Python application.

Blueprints take this logic a step further by providing a way to group your views into namespaces, making the structure used in separate files and giving it some special framework assistance. You can create a Blueprint object that looks like a Quart app object, and then use it to arrange some views. The initialization process can then register blueprints with app.register_blueprint to make sure that all the views defined in the blueprint are part of the app. A possible implementation of the employee's blueprint could be as follows:

# blueprints.py
from quart import Blueprint
teams = Blueprint("teams", __name__)
_DEVS = ["Alice", "Bob"]
_OPS = ["Charles"]
_TEAMS = {1: _DEVS, 2: _OPS}
@teams.route("/teams")
def get_all():
    return _TEAMS
@teams.route("/teams/<int:team_id>")
def get_team(team_id):
    return _TEAMS[team_id]

The main module (app.py) can then import this file, and register its blueprint with app.register_blueprint(teams). This mechanism is also interesting when you want to reuse a generic set of views in another application or several times in the same application—it's easy to imagine a situation where, for example, both the inventory management area and a sales area might want to have the same ability to look at current stock levels.

Error handling

When something goes wrong in your application, it is important to be able to control what responses the clients will receive. In HTML web apps, you usually get specific HTML pages when you encounter a 404 (Resource not found) or 5xx (Server error), and that's how Quart works out of the box. But when building microservices, you need to have more control of what should be sent back to the client—that's where custom error handlers are useful.

The other important feature is the ability to debug your code when an unexpected error occurs; Quart comes with a built-in debugger, which can be activated when your app runs in debug mode.

Custom error handler

When your code does not handle an exception, Quart returns an HTTP 500 response without providing any specific information, like the traceback. Producing a generic error is a safe default behavior to avoid leaking any private information to users in the body of the error. The default 500 response is a simple HTML page along with the right status code:

$ curl http://localhost:5000/api 
<!doctype html>
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
Server got itself in trouble

When implementing microservices using JSON, it is good practice to make sure that every response sent to clients, including any exception, is JSON-formatted. Consumers of your microservice will expect every response to be machine-parseable. It's far better to tell a client that you had an error and have it set up to process that message and show it to a human than to give a client something it doesn't understand and have it raise its own errors.

Quart lets you customize the app error handling via a couple of functions. The first one is the @app.errorhandler decorator, which works like @app.route. But instead of providing an endpoint, the decorator links a function to a specific error code.

In the following example, we use it to connect a function that will return a JSON-formatted error when Quart returns a 500 server response (any code exception):

# error_handler.py
from quart import Quart
app = Quart(__name__)
@app.errorhandler(500)
def error_handling(error):
    return {"Error": str(error)}, 500
@app.route("/api")
def my_microservice():
    raise TypeError("Some Exception")
if __name__ == "__main__":
    app.run()

Quart will call this error view no matter what exception the code raises. However, in case your application issues an HTTP 404 or any other 4xx or 5xx response, you will be back to the default HTML responses that Quart sends. To make sure your app sends JSON for every 4xx and 5xx response, we need to register that function to each error code.

One place where you can find the list of errors is in the abort.mapping dict. In the following code snippet, we register the error_handling function to every error using app.register_error_handler, which is similar to the @app.errorhandler decorator:

# catch_all_errors.py
from quart import Quart, jsonify, abort
from werkzeug.exceptions import HTTPException, default_exceptions
def jsonify_errors(app):
    def error_handling(error):
        if isinstance(error, HTTPException):
            result = {
                "code": error.code,
                "description": error.description,
                "message": str(error),
            }
        else:
            description = abort.mapping[ error.code].description
            result = {"code":  error.code, "description": description, "message": str(error)}
        resp = jsonify(result)
        resp.status_code = result["code"]
        return resp
    for code in default_exceptions.keys():
        app.register_error_handler(code, error_handling)
    return app
app = Quart(__name__)
app = jsonify_errors(app)
@app.route("/api")
def my_microservice():
   raise TypeError("Some Exception")
if __name__ == "__main__":
    app.run()

The jsonify_errors function modifies a Quart app instance and sets up the custom JSON error handler for every 4xx and 5xx error that might occur.

A microservice skeleton

So far in this chapter, we have looked at how Quart works, and at most of the built-in features it provides—all of which we will be using throughout this book. One topic we have not yet covered is how to organize the code in your projects, and how to instantiate your Quart app. Every example so far has used a single Python module and the app.run() call to run the service.

Having everything in a module is possible, but will create a lot of headaches unless your code is just a few lines. Since we will want to release and deploy the code, it's better to have it inside a Python package so that we can use standard packaging tools such as pip and setuptools.

It is also a good idea to organize views into blueprints, and have one module per blueprint. This lets us keep better track of what each bit of code does, and re-use code whenever possible.

Lastly, the run() call can be removed from the code since Quart provides a generic run command that looks for an application using information from the QUART_APP environment variable. Using that runner offers extra options, such as the ability to configure the host and port that will be used to run the app without going into the settings each time.

The microservice project on GitHub was created for this book and is a generic Quart project that you can use to start a microservice. It implements a simple layout, which works well for building microservices. You can install and run, and then modify it. The project can be found at https://github.com/PythonMicroservices/microservice-skeleton.

The microservice project skeleton contains the following structure:

  • setup.py: Distutils' setup file, which is used to install and release the project.
  • Makefile: A Makefile that contains a few useful targets to make, build, and run the project.
  • settings.yml: The application default settings in a YAML file.
  • requirements.txt: The project dependencies following the pip format produced by pip freeze.
  • myservices/: The actual package
    • __init__.py
    • app.py: The app module, which contains the app itself
    • views/: A directory containing the views organized in blueprints
      • __init__.py
      • home.py: The home blueprint, which serves the root endpoint
    • tests/: The directory containing all the tests
      • __init__.py
      • test_home.py: Tests for the home blueprint views

In the following code, the app.py file instantiates a Quart app using a helper function called create_app to register the blueprints and update the settings:

import os
from myservice.views import blueprints
from quart import Quart
_HERE = os.path.dirname(__file__)
_SETTINGS = os.path.join(_HERE, "settings.ini")
def create_app(name=__name__, blueprints=None, settings=None):
    app = Quart(name)
    # load configuration
    settings = os.environ.get("QUART_SETTINGS", settings)
    if settings is not None:
        app.config.from_pyfile(settings)
    # register blueprints
    if blueprints is not None:
        for bp in blueprints:
            app.register_blueprint(bp)
    return app
app = create_app(blueprints=blueprints, settings=_SETTINGS)

The home.py view uses a blueprint to create a simple route that doesn't return anything:

from quart import Blueprint
home = Blueprint("home", __name__)
@home.route("/")
def index():
    """Home view.
    This view will return an empty JSON mapping.
    """
    return {}

This example application can run via Quart's built-in command line, using the package name:

$ QUART_APP=myservice quart run
 * Serving Quart app 'myservice.app'
 * Environment: production
 * Please use an ASGI server (e.g. Hypercorn) directly in production
 * Debug mode: False
 * Running on http://localhost:5000 (CTRL + C to quit)
[2020-12-06 20:17:28,203] Running on http://127.0.0.1:5000 (CTRL + C to quit)

From there, building JSON views for your microservice consists of adding modules to microservices/views, and their corresponding tests.

Summary

This chapter gave us a detailed overview of the Quart framework and how it can be used to build microservices. The main things to remember are:

  • Quart wraps a simple request-response mechanism around the ASGI protocol, which lets you write your applications in almost vanilla Python.
  • Quart is easy to extend and can use Flask extensions if required.
  • Quart comes with some useful built-in features: blueprints, globals, signals, a template engine, and error handlers.
  • The microservice project is a Quart skeleton, which will be used to write microservices throughout this book.

The next chapter will focus on development methodology: how to continuously code, test, and document your microservices.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Become well versed with the fundamentals of building, designing, testing, and deploying Python microservices
  • Identify where a monolithic application can be split, how to secure it, and how to scale it once ready for deployment
  • Use the latest framework based on asynchronous programming to write effective microservices with Python

Description

The small scope and self-contained nature of microservices make them faster, cleaner, and more scalable than code-heavy monolithic applications. However, building microservices architecture that is efficient as well as lightweight into your applications can be challenging due to the complexity of all the interacting pieces. Python Microservices Development, Second Edition will teach you how to overcome these issues and craft applications that are built as small standard units using proven best practices and avoiding common pitfalls. Through hands-on examples, this book will help you to build efficient microservices using Quart, SQLAlchemy, and other modern Python tools In this updated edition, you will learn how to secure connections between services and how to script Nginx using Lua to build web application firewall features such as rate limiting. Python Microservices Development, Second Edition describes how to use containers and AWS to deploy your services. By the end of the book, you’ll have created a complete Python application based on microservices.

Who is this book for?

This book is for developers who want to learn how to build, test, scale, and manage Python microservices. Readers will require basic knowledge of the Python programming language, the command line, and HTTP-based application principles. No prior experience of writing microservices in Python is assumed.

What you will learn

  • Explore what microservices are and how to design them
  • Configure and package your code according to modern best practices
  • Identify a component of a larger service that can be turned into a microservice
  • Handle more incoming requests, more effectively
  • Protect your application with a proxy or firewall
  • Use Kubernetes and containers to deploy a microservice
  • Make changes to an API provided by a microservice safely and keep things working
  • Identify the factors to look for to get started with an unfamiliar cloud provider
Estimated delivery fee Deliver to Italy

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Sep 30, 2021
Length: 310 pages
Edition : 2nd
Language : English
ISBN-13 : 9781801076302
Languages :
Concepts :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Italy

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Publication date : Sep 30, 2021
Length: 310 pages
Edition : 2nd
Language : English
ISBN-13 : 9781801076302
Languages :
Concepts :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 108.97
Python Object-Oriented Programming
€35.99
Learn Python Programming, 3rd edition
€35.99
Python Microservices Development – 2nd edition
€36.99
Total 108.97 Stars icon
Banner background image

Table of Contents

13 Chapters
Understanding Microservices Chevron down icon Chevron up icon
Discovering Quart Chevron down icon Chevron up icon
Coding, Testing, and Documentation: the Virtuous Cycle Chevron down icon Chevron up icon
Designing Jeeves Chevron down icon Chevron up icon
Splitting the Monolith Chevron down icon Chevron up icon
Interacting with Other Services Chevron down icon Chevron up icon
Securing Your Services Chevron down icon Chevron up icon
Making a Dashboard Chevron down icon Chevron up icon
Packaging and Running Python Chevron down icon Chevron up icon
Deploying on AWS Chevron down icon Chevron up icon
What's Next? Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon

Customer reviews

Rating distribution
Full star icon Full star icon Full star icon Full star icon Full star icon 5
(3 Ratings)
5 star 100%
4 star 0%
3 star 0%
2 star 0%
1 star 0%
Seth Larson Dec 01, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book condensed a bunch of knowledge I've learned over multiple years of writing web services into one handy and easy-to-digest guide. From the high-level discussion of different frameworks, libraries, and services working together to form a larger service to the nitty-gritty details like Web Application Firewalls and how to deploy individual components to AWS. Highly recommended for all skill levels!
Amazon Verified review Amazon
Gigi Sayfan Nov 11, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I enjoyed "Python Microservices Development" a lot. The author covers the foundations of microservices, their pros and cons and then develops a monolithic application and demonstrates step by step how to transform it into a microservice architeture.All aspects are covered with examples: authentication, packaging, deployment to the cloud, etc.I definitely recommend it for developer interested in building microservice-based systems with Python.
Amazon Verified review Amazon
Stephan Miller Nov 11, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I have to admit. I didn't think I would learn much from reading this book. I know Python and I know microservices in other languages like JavaScript. But I was wrong. It had a lot of information that I never ran into in similar books. It starts out with a example service in Python with a library called Quart, which can do just about everything Flask can do but asynchronously and it has the same API as Flask. It then walks you through the process of breaking up that service into microservices, which is a big help if you are new to microservices. It also covers interactions between services, using OAuth tokens, deployments, and more. If you want to learn to create microservices with Python, it will show you just about everything you need to know.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela