Installation — Microdot documentation (2024)

This section describes the main features of Microdot in an informal manner.

For detailed reference information, consult the API Reference.

If you are familiar with releases of Microdot before 2.x, review theMigration Guide.

A Simple Microdot Web Server

The following is an example of a simple web server:

from microdot import Microdotapp = Microdot()@app.route('/')async def index(request): return 'Hello, world!'app.run()

The script imports the Microdot class and createsan application instance from it.

The application instance provides a route()decorator, which is used to define one or more routes, as needed by theapplication.

The route() decorator takes the path portion of the URL as anargument, and maps it to the decorated function, so that the function is calledwhen the client requests the URL.

When the function is called, it is passed a Requestobject as an argument, which provides access to the information passed by theclient. The value returned by the function is sent back to the client as theresponse.

Microdot is an asynchronous framework that uses the asyncio package. Routehandler functions can be defined as async def or def functions, butasync def functions are recommended for performance.

The run() method starts the application’s webserver on port 5000 by default. This method blocks while it waits forconnections from clients.

Running with CPython

Required Microdot source files

Required external dependencies

None

Examples

When using CPython, you can start the web server by running the script thathas the app.run() call at the bottom:

python main.py

After starting the script, open a web browser and navigate tohttp://localhost:5000/ to access the application at the default address forthe Microdot web server. From other computers in the same network, use the IPaddress or hostname of the computer running the script instead oflocalhost.

Running with MicroPython

Required Microdot source files

Required external dependencies

None

Examples

When using MicroPython, you can upload a main.py file containing the webserver code to your device, along with the required Microdot files, as definedin the MicroPython Installation section.

MicroPython will automatically run main.py when the device is powered on, sothe web server will automatically start. The application can be accessed onport 5000 at the device’s IP address. As indicated above, the port can bechanged by passing the port argument to the run() method.

Note

Microdot does not configure the network interface of the device in which itis running. If your device requires a network connection to be made inadvance, for example to a Wi-Fi access point, this must be configured beforethe run() method is invoked.

Web Server Configuration

The run() method supports a few arguments toconfigure the web server.

  • port: The port number to listen on. Pass the desired port number in thisargument to use a port different than the default of 5000. For example:

    app.run(port=6000)
  • host: The IP address of the network interface to listen on. By defaultthe server listens on all available interfaces. To listen only on the localloopback interface, pass '127.0.0.1' as value for this argument.

  • debug: when set to True, the server ouputs logging information to theconsole. The default is False.

  • ssl: an SSLContext instance that configures the server to use TLSencryption, or None to disable TLS use. The default is None. Thefollowing example demonstrates how to configure the server with an SSLcertificate stored in cert.pem and key.pem files:

    import ssl# ...sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)sslctx.load_cert_chain('cert.pem', 'key.pem')app.run(port=4443, debug=True, ssl=sslctx)

Note

When using CPython, the certificate and key files must be given in PEMformat. When using MicroPython, these files must be given in DER format.

Defining Routes

The route() decorator is used to associate anapplication URL with the function that handles it. The only required argumentto the decorator is the path portion of the URL.

The following example creates a route for the root URL of the application:

@app.route('/')async def index(request): return 'Hello, world!'

When a client requests the root URL (for example, http://localhost:5000/),Microdot will call the index() function, passing it aRequest object. The return value of the functionis the response that is sent to the client.

Below is another example, this one with a route for a URL with two componentsin its path:

@app.route('/users/active')async def active_users(request): return 'Active users: Susan, Joe, and Bob'

The complete URL that maps to this route ishttp://localhost:5000/users/active.

An application can include multiple routes. Microdot uses the path portion ofthe URL to determine the correct route function to call for each incomingrequest.

Choosing the HTTP Method

All the example routes shown above are associated with GET requests, whichare the default. Applications often need to define routes for other HTTPmethods, such as POST, PUT, PATCH and DELETE. The route()decorator takes a methods optional argument, in which the application canprovide a list of HTTP methods that the route should be associated with on thegiven path.

The following example defines a route that handles GET and POSTrequests within the same function:

@app.route('/invoices', methods=['GET', 'POST'])async def invoices(request): if request.method == 'GET': return 'get invoices' elif request.method == 'POST': return 'create an invoice'

As an alternative to the example above, in which a single function is used tohandle multiple HTTP methods, sometimes it may be desirable to write a separatefunction for each HTTP method. The above example can be implemented with tworoutes as follows:

@app.route('/invoices', methods=['GET'])async def get_invoices(request): return 'get invoices'@app.route('/invoices', methods=['POST'])async def create_invoice(request): return 'create an invoice'

Microdot provides the get(),post(), put(),patch(), anddelete() decorators as shortcuts for thecorresponding HTTP methods. The two example routes above can be written moreconcisely with them:

@app.get('/invoices')async def get_invoices(request): return 'get invoices'@app.post('/invoices')async def create_invoice(request): return 'create an invoice'

Including Dynamic Components in the URL Path

The examples shown above all use hardcoded URL paths. Microdot also supportsthe definition of routes that have dynamic components in the path. For example,the following route associates all URLs that have a path following the patternhttp://localhost:5000/users/<username> with the get_user() function:

As shown in the example, a path component that is enclosed in angle bracketsis considered a placeholder. Microdot accepts any values for that portion ofthe URL path, and passes the value received to the function as an argumentafter the request object.

Routes are not limited to a single dynamic component. The following route showshow multiple dynamic components can be included in the path:

@app.get('/users/<firstname>/<lastname>')async def get_user(request, firstname, lastname): return 'User: ' + firstname + ' ' + lastname

Dynamic path components are considered to be strings by default. An explicittype can be specified as a prefix, separated from the dynamic component name bya colon. The following route has two dynamic components declared as an integerand a string respectively:

@app.get('/users/<int:id>/<string:username>')async def get_user(request, id, username): return 'User: ' + username + ' (' + str(id) + ')'

If a dynamic path component is defined as an integer, the value passed to theroute function is also an integer. If the client sends a value that is not aninteger in the corresponding section of the URL path, then the URL will notmatch and the route will not be called.

A special type path can be used to capture the remainder of the path as asingle argument. The difference between an argument of type path and one oftype string is that the latter stops capturing when a / appears in theURL:

@app.get('/tests/<path:path>')async def get_test(request, path): return 'Test: ' + path

For the most control, the re type allows the application to provide acustom regular expression for the dynamic component. The next example definesa route that only matches usernames that begin with an upper or lower caseletter, followed by a sequence of letters or numbers:

@app.get('/users/<re:[a-zA-Z][a-zA-Z0-9]*:username>')async def get_user(request, username): return 'User: ' + username

Note

Dynamic path components are passed to route functions as keyword arguments,so the names of the function arguments must match the names declared in thepath specification.

Before and After Request Handlers

It is common for applications to need to perform one or more actions before arequest is handled. Examples include authenticating and/or authorizing theclient, opening a connection to a database, or checking if the requestedresource can be obtained from a cache. Thebefore_request() decorator registersa function to be called before the request is dispatched to the route function.

The following example registers a before-request handler that ensures that theclient is authenticated before the request is handled:

@app.before_requestasync def authenticate(request): user = authorize(request) if not user: return 'Unauthorized', 401 request.g.user = user

Before-request handlers receive the request object as an argument. If thefunction returns a value, Microdot sends it to the client as the response, anddoes not invoke the route function. This gives before-request handlers thepower to intercept a request if necessary. The example above uses thistechnique to prevent an unauthorized user from accessing the requestedroute.

After-request handlers registered with theafter_request() decorator are calledafter the route function returns a response. Their purpose is to perform anycommon closing or cleanup tasks. The next example shows a combination ofbefore- and after-request handlers that print the time it takes for a requestto be handled:

@app.before_requestasync def start_timer(request): request.g.start_time = time.time()@app.after_requestasync def end_timer(request, response): duration = time.time() - request.g.start_time print(f'Request took {duration:0.2f} seconds')

After-request handlers receive the request and response objects as arguments,and they can return a modified response object to replace the original. Ifno value is returned from an after-request handler, then the original responseobject is used.

The after-request handlers are only invoked for successful requests. Theafter_error_request()decorator can be used to register a function that is called after an erroroccurs. The function receives the request and the error response and isexpected to return an updated response object after performing any necessarycleanup.

Note

The request.g object used in many of the aboveexamples is a special object that allows the before- and after-requesthandlers, as well as the route function to share data during the life of therequest.

Error Handlers

When an error occurs during the handling of a request, Microdot ensures thatthe client receives an appropriate error response. Some of the common errorsautomatically handled by Microdot are:

  • 400 for malformed requests.

  • 404 for URLs that are unknown.

  • 405 for URLs that are known, but not implemented for the requested HTTPmethod.

  • 413 for requests that are larger than the allowed size.

  • 500 when the application raises an unhandled exception.

While the above errors are fully complaint with the HTTP specification, theapplication might want to provide custom responses for them. Theerrorhandler() decorator registersfunctions to respond to specific error codes. The following example shows acustom error handler for 404 errors:

@app.errorhandler(404)async def not_found(request): return {'error': 'resource not found'}, 404

The errorhandler() decorator has a second form, in which it takes anexception class as an argument. Microdot will invoke the handler when anunhandled exception that is an instance of the given class is raised. The nextexample provides a custom response for division by zero errors:

@app.errorhandler(ZeroDivisionError)async def division_by_zero(request, exception): return {'error': 'division by zero'}, 500

When the raised exception class does not have an error handler defined, butone or more of its parent classes do, Microdot makes an attempt to invoke themost specific handler.

Mounting a Sub-Application

Small Microdot applications can be written as a single source file, but thisis not the best option for applications that past a certain size. To make itsimpler to write large applications, Microdot supports the concept ofsub-applications that can be “mounted” on a larger application, possibly witha common URL prefix applied to all of its routes. For developers familiar withthe Flask framework, this is a similar concept to Flask’s blueprints.

Consider, for example, a customers.py sub-application that implementsoperations on customers:

from microdot import Microdotcustomers_app = Microdot()@customers_app.get('/')async def get_customers(request): # return all customers@customers_app.post('/')async def new_customer(request): # create a new customer

Similar to the above, the orders.py sub-application implements operations oncustomer orders:

from microdot import Microdotorders_app = Microdot()@orders_app.get('/')async def get_orders(request): # return all orders@orders_app.post('/')async def new_order(request): # create a new order

Now the main application, which is stored in main.py, can import and mountthe sub-applications to build the larger combined application:

from microdot import Microdotfrom customers import customers_appfrom orders import orders_appdef create_app(): app = Microdot() app.mount(customers_app, url_prefix='/customers') app.mount(orders_app, url_prefix='/orders') return appapp = create_app()app.run()

The resulting application will have the customer endpoints available at/customers/ and the order endpoints available at /orders/.

Note

Before-request, after-request and error handlers defined in thesub-application are also copied over to the main application at mount time.Once installed in the main application, these handlers will apply to thewhole application and not just the sub-application in which they werecreated.

Shutting Down the Server

Web servers are designed to run forever, and are often stopped by sending theman interrupt signal. But having a way to gracefully stop the server issometimes useful, especially in testing environments. Microdot provides ashutdown() method that can be invokedduring the handling of a route to gracefully shut down the server when thatrequest completes. The next example shows how to use this feature:

@app.get('/shutdown')async def shutdown(request): request.app.shutdown() return 'The server is shutting down...'

The request that invokes the shutdown() method will complete, and then theserver will not accept any new requests and stop once any remaining requestscomplete. At this point the app.run() call will return.

The Request Object

The Request object encapsulates all the informationpassed by the client. It is passed as an argument to route handlers, as well asto before-request, after-request and error handlers.

Request Attributes

The request object provides access to the request attributes, including:

  • method: The HTTP method of the request.

  • path: The path of the request.

  • args: The query string parameters of therequest, as a MultiDict object.

  • headers: The headers of the request, as adictionary.

  • cookies: The cookies that the client sentwith the request, as a dictionary.

  • content_type: The content typespecified by the client, or None if no content type was specified.

  • content_length: The contentlength of the request, or 0 if no content length was specified.

  • client_addr: The network address ofthe client, as a tuple (host, port).

  • app: The application instance that created therequest.

  • g: The g object, where handlers can storerequest-specific data to be shared among handlers. See The “g” Objectfor details.

JSON Payloads

When the client sends a request that contains JSON data in the body, theapplication can access the parsed JSON data using thejson attribute. The following example shows howto use this attribute:

@app.post('/customers')async def create_customer(request): customer = request.json # do something with customer return {'success': True}

Note

The client must set the Content-Type header to application/json forthe json attribute of the request object to be populated.

URLEncoded Form Data

The request object also supports standard HTML form submissions through theform attribute, which presents the form dataas a MultiDict object. Example:

@app.route('/', methods=['GET', 'POST'])async def index(req): name = 'Unknown' if req.method == 'POST': name = req.form.get('name') return f'Hello {name}'

Note

Form submissions are only parsed when the Content-Type header is set bythe client to application/x-www-form-urlencoded. Form submissions usingthe multipart/form-data content type are currently not supported.

Accessing the Raw Request Body

For cases in which neither JSON nor form data is expected, thebody request attribute returns the entire bodyof the request as a byte sequence.

If the expected body is too large to fit safely in memory, the application canuse the stream request attribute to read thebody contents as a file-like object. Themax_body_length attribute of therequest object defines the size at which bodies are streamed instead of loadedinto memory.

Cookies

Cookies that are sent by the client are made available through thecookies attribute of the request object indictionary form.

The “g” Object

Sometimes applications need to store data during the lifetime of a request, sothat it can be shared between the before- and after-request handlers, theroute function and any error handlers. The request object provides theg attribute for that purpose.

In the following example, a before request handler authorizes the client andstores the username so that the route function can use it:

@app.before_requestasync def authorize(request): username = authenticate_user(request) if not username: return 'Unauthorized', 401 request.g.username = username@app.get('/')async def index(request): return f'Hello, {request.g.username}!'

Request-Specific After-Request Handlers

Sometimes applications need to perform operations on the response objectbefore it is sent to the client, for example to set or remove a cookie. A goodoption to use for this is to define a request-specific after-request handlerusing the after_request decorator.Request-specific after-request handlers are called by Microdot after the routefunction returns and all the application-wide after-request handlers have beencalled.

The next example shows how a cookie can be updated using a request-specificafter-request handler defined inside a route function:

@app.post('/logout')async def logout(request): @request.after_request def reset_session(request, response): response.set_cookie('session', '', http_only=True) return response return 'Logged out'

Request Limits

To help prevent malicious attacks, Microdot provides some configuration optionsto limit the amount of information that is accepted:

  • max_content_length: Themaximum size accepted for the request body, in bytes. When a client sends arequest that is larger than this, the server will respond with a 413 error.The default is 16KB.

  • max_body_length: The maximumsize that is loaded in the body attribute, inbytes. Requests that have a body that is larger than this size but smallerthan the size set for max_content_length can only be accessed through thestream attribute. The default is also 16KB.

  • max_readline: The maximum allowedsize for a request line, in bytes. The default is 2KB.

The following example configures the application to accept requests withpayloads up to 1MB in size, but prevents requests that are larger than 8KB frombeing loaded into memory:

from microdot import RequestRequest.max_content_length = 1024 * 1024Request.max_body_length = 8 * 1024

Responses

The value or values that are returned from the route function are used byMicrodot to build the response that is sent to the client. The followingsections describe the different types of responses that are supported.

The Three Parts of a Response

Route functions can return one, two or three values. The first or only value isalways returned to the client in the response body:

@app.get('/')async def index(request): return 'Hello, World!'

In the above example, Microdot issues a standard 200 status code response, andinserts default headers.

The application can provide its own status code as a second value returned fromthe route to override the 200 default. The example below returns a 202 statuscode:

@app.get('/')async def index(request): return 'Hello, World!', 202

The application can also return a third value, a dictionary with additionalheaders that are added to, or replace the default ones included by Microdot.The next example returns an HTML response, instead of a default text response:

@app.get('/')async def index(request): return '<h1>Hello, World!</h1>', 202, {'Content-Type': 'text/html'}

If the application needs to return custom headers, but does not need to changethe default status code, then it can return two values, omitting the statuscode:

@app.get('/')async def index(request): return '<h1>Hello, World!</h1>', {'Content-Type': 'text/html'}

The application can also return a Response objectcontaining all the details of the response as a single value.

JSON Responses

If the application needs to return a response with JSON formatted data, it canreturn a dictionary or a list as the first value, and Microdot willautomatically format the response as JSON.

Example:

@app.get('/')async def index(request): return {'hello': 'world'}

Note

A Content-Type header set to application/json is automatically addedto the response.

Redirects

The redirect function is a helper thatcreates redirect responses:

from microdot import redirect@app.get('/')async def index(request): return redirect('/about')

File Responses

The send_file function builds a responseobject for a file:

from microdot import send_file@app.get('/')async def index(request): return send_file('/static/index.html')

A suggested caching duration can be returned to the client in the max_ageargument:

from microdot import send_file@app.get('/')async def image(request): return send_file('/static/image.jpg', max_age=3600) # in seconds

Note

Unlike other web frameworks, Microdot does not automatically configure aroute to serve static files. The following is an example route that can beadded to the application to serve static files from a static directory inthe project:

@app.route('/static/<path:path>')async def static(request, path): if '..' in path: # directory traversal is not allowed return 'Not found', 404 return send_file('static/' + path, max_age=86400)

Streaming Responses

Instead of providing a response as a single value, an application can opt toreturn a response that is generated in chunks, by returning a Python generator.The example below returns all the numbers in the fibonacci sequence below 100:

@app.get('/fibonacci')async def fibonacci(request): async def generate_fibonacci(): a, b = 0, 1 while a < 100: yield str(a) + '\n' a, b = b, a + b return generate_fibonacci()

Note

Under CPython, the generator function can be a def or async deffunction, as well as a class-based generator.

Under MicroPython, asynchronous generator functions are not supported, soonly def generator functions can be used. Asynchronous class-basedgenerators are supported.

Changing the Default Response Content Type

Microdot uses a text/plain content type by default for responses that donot explicitly include the Content-Type header. The application can changethis default by setting the desired content type in thedefault_content_type attributeof the Response class.

The example that follows configures the application to use text/html asdefault content type:

from microdot import ResponseResponse.default_content_type = 'text/html'

Setting Cookies

Many web applications rely on cookies to maintain client state betweenrequests. Cookies can be set with the Set-Cookie header in the response,but since this is such a common practice, Microdot provides theset_cookie() method in the responseobject to add a properly formatted cookie header to the response.

Given that route functions do not normally work directly with the responseobject, the recommended way to set a cookie is to do it in arequest-specific after-request handler.

Example:

@app.get('/')async def index(request): @request.after_request async def set_cookie(request, response): response.set_cookie('name', 'value') return response return 'Hello, World!'

Another option is to create a response object directly in the route function:

@app.get('/')async def index(request): response = Response('Hello, World!') response.set_cookie('name', 'value') return response

Note

Standard cookies do not offer sufficient privacy and security controls, sonever store sensitive information in them unless you are adding additionalprotection mechanisms such as encryption or cryptographic signing. Thesession extension implements signedcookies that prevent tampering by malicious actors.

Concurrency

Microdot implements concurrency through the asyncio package. Applicationsmust ensure their handlers do not block, as this will prevent other concurrentrequests from being handled.

When running under CPython, async def handler functions run as nativeasyncio tasks, while def handler functions are executed in athread executorto prevent them from blocking the asynchronous loop.

Under MicroPython the situation is different. Most microcontroller boardsimplementing MicroPython do not have threading support or executors, so defhandler functions in this platform can only run in the main and only thread.These functions will block the asynchronous loop when they take too long tocomplete so async def handlers properly written to allow other handlers torun in parallel should be preferred.

Installation — Microdot  documentation (2024)

References

Top Articles
Latest Posts
Article information

Author: Cheryll Lueilwitz

Last Updated:

Views: 6174

Rating: 4.3 / 5 (54 voted)

Reviews: 93% of readers found this page helpful

Author information

Name: Cheryll Lueilwitz

Birthday: 1997-12-23

Address: 4653 O'Kon Hill, Lake Juanstad, AR 65469

Phone: +494124489301

Job: Marketing Representative

Hobby: Reading, Ice skating, Foraging, BASE jumping, Hiking, Skateboarding, Kayaking

Introduction: My name is Cheryll Lueilwitz, I am a sparkling, clean, super, lucky, joyous, outstanding, lucky person who loves writing and wants to share my knowledge and understanding with you.