microdot — minimal HTTP framework#

Microdot is a small, Flask-inspired HTTP framework for MicroPython. It runs on top of asyncio, handles multiple concurrent clients without threads, and exposes a familiar route-decorator API. A minimal application looks like:

from microdot import Microdot

app = Microdot()

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

app.run(host='0.0.0.0', port=80)

class Microdot#

class microdot.Microdot#

An HTTP application instance. One instance is constructed near the top of the script and decorated with route handlers; calling run() or awaiting start_server() begins serving.

Route registration

route(url_pattern: str, methods: list | None = None) Callable#

Decorator that registers a handler for url_pattern under the listed HTTP methods (default ['GET']). Returns the decorator that, when applied to a function, registers it as the handler and returns the function unchanged.

url_pattern

A path pattern. Supports static segments (/users) and dynamic segments enclosed in < / >. Dynamic segments accept an optional type prefix separated by :<int:id>, <path:rest>, <re:[0-9a-f]+:hex>. The default type is string.

methods

List of HTTP method names. If omitted, only GET is matched.

The handler is called with the request object first, then any captured dynamic segments as keyword arguments. The handler’s return value becomes the HTTP response: a Response, a string, a tuple (body, status_code[, headers]), or a dict / list (sent as JSON).

get(url_pattern: str) Callable#

Convenience alias for route(url_pattern, methods=['GET']) – register the decorated function as a GET handler for url_pattern. Returns the decorator.

post(url_pattern: str) Callable#

Convenience alias for route(url_pattern, methods=['POST']) – register the decorated function as a POST handler for url_pattern. Returns the decorator.

put(url_pattern: str) Callable#

Convenience alias for route(url_pattern, methods=['PUT']) – register the decorated function as a PUT handler for url_pattern. Returns the decorator.

patch(url_pattern: str) Callable#

Convenience alias for route(url_pattern, methods=['PATCH']) – register the decorated function as a PATCH handler for url_pattern. Returns the decorator.

delete(url_pattern: str) Callable#

Convenience alias for route(url_pattern, methods=['DELETE']) – register the decorated function as a DELETE handler for url_pattern. Returns the decorator.

Lifecycle hooks

before_request(f: Callable) Callable#

Decorator that registers f to run before every request. f takes the request object; its return value is normally ignored. To short-circuit the request, return a Response (or a value that converts to one) – the rest of the pipeline is then skipped and that response is sent. Returns f unchanged.

after_request(f: Callable) Callable#

Decorator that registers f to run after every successful request. f takes (request, response) and must return the (possibly modified) response object. Returns f unchanged.

after_error_request(f: Callable) Callable#

Decorator that registers f to run after Microdot generates an error response (404, 500, raised exception, etc.). f takes (request, response) and must return the (possibly modified) response object. Returns f unchanged.

errorhandler(status_code_or_exception_class) Callable#

Decorator that registers a custom handler for an HTTP status code or a Python exception class. For status codes, the handler takes only the request; for exception classes, (request, exception). Returns the decorator.

Mounting and aborting

mount(subapp: Microdot, url_prefix: str = '', local: bool = False) None#

Attach another Microdot instance’s routes under url_prefix. When local is False (default), the sub-app’s before / after / error handlers are also attached to the parent. When True, those handlers run only for sub-app routes. Returns None.

static abort(status_code: int, reason: str | None = None) None#

Raise HTTPException to abort the current request with the given status code. Never returns normally – the framework catches the exception and turns it into the corresponding error response. Convenient inside route bodies:

from microdot import abort

@app.get('/users/<int:id>')
def get_user(request, id):
    user = lookup(id)
    if user is None:
        abort(404)
    return user.to_dict()

Running the server

run(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None) None#

Block the calling thread, run asyncio.run() on start_server(). Returns None only after shutdown() has been called and the listening loop exits. The simplest way to launch a standalone app:

app.run(host='0.0.0.0', port=80)
async start_server(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None, start_serving: bool = True) None#

Start the server as a coroutine. Use this when integrating with an existing asyncio event loop alongside other tasks. The coroutine does not return until shutdown() is called:

async def main():
    await asyncio.gather(
        app.start_server(port=80),
        capture_loop(),
    )
host

Network interface to listen on. '0.0.0.0' (default) means all interfaces; '127.0.0.1' means loopback only.

port

TCP port to listen on. Default 5000 – pick 80 for plain HTTP, 443 for HTTPS.

debug

If True, log every request and dump tracebacks to stdout.

ssl

An ssl.SSLContext to wrap incoming connections in TLS. None (default) means plain HTTP.

start_serving

Only relevant on CPython; ignored on MicroPython.

shutdown() None#

Request a graceful server shutdown. Safe to call from a route handler – the current request completes before the loop exits. Returns immediately; the actual shutdown happens once the in-flight request finishes.

Attributes

url_map: list#

List of registered routes as (methods, URLPattern, handler, url_prefix, subapp) tuples.

before_request_handlers: list#

List of callables registered with before_request(), in registration order. Each runs with the request object before the route handler. Mutating the list directly works but is rarely needed – prefer the decorator.

after_request_handlers: list#

List of callables registered with after_request(), in registration order. Each runs with (request, response) after a successful request and must return the (possibly modified) response.

after_error_request_handlers: list#

List of callables registered with after_error_request(), in registration order. Each runs with (request, response) after an error response is generated (by the framework or by an application error handler) and must return the response.

error_handlers: dict#

Mapping of error keys to handler callables, populated by errorhandler(). Keys are either HTTP status codes (int) or Python exception classes; values are the registered handlers. Status-code handlers take (request); exception-class handlers take (request, exception).

debug: bool#

True while the server is running with debug=True.

class Request#

class microdot.Request#

An incoming HTTP request. Instances are passed to route handlers as the first positional argument. Applications do not construct Request directly.

Class attributes

max_content_length: int#

Reject requests whose Content-Length exceeds this many bytes with a 413 response. Default 16 KB.

max_body_length: int#

Largest body that is buffered into memory and exposed via body. Larger bodies (up to max_content_length) remain on the socket and must be read through stream. Default 16 KB.

max_readline: int#

Maximum length of a single request line / header line in bytes. Default 2 KB.

Instance attributes

app: Microdot#

The Microdot instance handling the request.

client_addr: tuple#

The client’s address as (host, port).

method: str#

HTTP method string ('GET', 'POST', …).

scheme: str#

'http' or 'https'.

url: str#

The full request URL path and query string (everything after the host).

path: str#

Just the path portion.

query_string: str | None#

The raw query string portion, or None.

args: MultiDict#

Parsed query string as a MultiDict.

headers: NoCaseDict#

Request headers as a NoCaseDict (case-insensitive lookup).

cookies: dict#

Parsed Cookie header as a dict.

content_length: int#

The integer Content-Length value, or 0 if absent.

content_type: str | None#

The Content-Type header value, or None.

g: object#

A free-form per-request container (a bare object). Assign attributes to it to pass values between hooks and handlers (request.g.user = ...).

route: Callable#

The handler function that matched this request.

url_prefix: str#

Prefix the route was mounted under, or ''.

subapp: Microdot | None#

The mounted sub-app instance, or None.

Body access

body: bytes#

The full request body as bytes. Empty when the body is being streamed (see stream).

stream: object#

An async stream object exposing read() over the body. Use this for bodies larger than max_body_length.

json: dict | list | str | int | float | bool | None#

The body parsed as JSON (a dict, list, str, int, float, or bool – whatever the payload encodes), or None if the Content-Type is not application/json.

form: MultiDict | None#

URL-encoded form fields as a MultiDict, or None. For multipart/form-data, decorate the route with microdot.multipart.with_form_data().

files: dict | None#

Uploaded files as {name: FileUpload}, populated by microdot.multipart.with_form_data(). None until that decorator runs.

after_request(f: Callable) Callable#

Register a request-local after-request hook – runs after the application-level after-request handlers, only on success. f takes (request, response) and must return the (possibly modified) response object. Returns f unchanged.

class Response#

class microdot.Response(body=b'', status_code: int = 200, headers: dict | None = None, reason: str | None = None)#

An HTTP response. Most handlers return values that Microdot converts to a Response automatically; construct one directly when you need custom headers or a specific status code.

body

Response body. str is UTF-8-encoded; dict / list is JSON-encoded and the Content-Type set accordingly; bytes is sent as-is; a file-like object or async generator streams.

status_code

Numeric HTTP status. Default 200.

headers

Dict of response headers (case-insensitive).

reason

Custom reason phrase. Defaults to "OK" for 200 and "N/A" otherwise.

Class attributes

default_content_type: str#

Content-Type used when none is set explicitly. Default 'text/plain'.

default_send_file_max_age: int | None#

Cache-Control: max-age value for send_file() when max_age is not given. None (default) means no Cache-Control header.

send_file_buffer_size: int#

Chunk size for send_file() streaming. Default 1024.

already_handled: None#

Sentinel returned from handlers that have already written the response directly (used by WebSocket upgrades). Always None; the identity is what matters.

types_map: dict#

Extension-to-mime-type map used by send_file() for content- type inference. Maps css, gif, html, jpg, js, json, png, txt, svg.

Methods

Add a Set-Cookie header. expires may be a pre-formatted string or a datetime-like object with timetuple(). Returns None; the header is appended to this response in place.

Set a Set-Cookie that expires the given cookie immediately. kwargs accepts the same options as set_cookie() (path, domain, secure, http_only, partitioned); expires and max_age are ignored. Returns None; the header is appended to this response in place.

classmethod redirect(location: str, status_code: int = 302) Response#

Return a redirect response:

@app.get('/old')
def old(request):
    return Response.redirect('/new')
classmethod send_file(filename: str, status_code: int = 200, content_type: str | None = None, stream=None, max_age: int | None = None, compressed: bool | str = False, file_extension: str = '') Response#

Stream a file from the filesystem as the response body. content_type is inferred from the extension via types_map if not given. compressed=True sets Content-Encoding: gzip (the file must already be compressed).

Warning

filename is opened directly. Never pass an unsanitized user-supplied path – doing so allows arbitrary file disclosure.

Exceptions#

exception microdot.HTTPException#

Raised by abort() to short-circuit a request with a specific status code. Microdot catches this and turns it into the corresponding error response.

status_code: int#

Numeric HTTP status code to return – the value passed to abort().

reason: str#

Reason phrase to include in the error response. If not given to abort(), defaults to "<status_code> error" (e.g. "404 error").

class URLPattern#

class microdot.URLPattern(url_pattern: str)#

The compiled form of a route’s URL pattern. Constructed automatically by Microdot.route(); applications rarely instantiate one directly.

classmethod register_type(type_name: str, pattern: str = '[^/]+', parser: Callable | None = None) None#

Register a new dynamic-segment type for use in route patterns. Returns None; the type is added to the class-level type registry. For example to add a <uuid:...> type:

URLPattern.register_type('uuid', '[0-9a-f-]{36}')

parser is an optional callable that converts the captured string before it reaches the handler.

match(path: str) dict | None#

Match path against the pattern and return a dict of captured groups, or None on no match.

Helper classes#

class microdot.MultiDict(initial_dict: dict | None = None)#

A dict subclass that stores multiple values per key. Used for query strings and URL-encoded form bodies where the same key can appear more than once (?tag=a&tag=b).

get(key, default=None, type: Callable | None = None)#

Return the first value for key, optionally converted with type (a callable). Returns default if the key is missing or the conversion fails; otherwise returns the (optionally converted) value.

getlist(key, type: Callable | None = None) list#

Return all values for key as a list, optionally with each value converted by type. Returns an empty list if key is not present.

class microdot.NoCaseDict#

A dict subclass with case-insensitive string keys. Used for Request.headers and Response.headers.

Module-level functions#

microdot.abort(status_code: int, reason: str | None = None) None#

Shortcut for Microdot.abort(). Never returns normally – raises HTTPException. Importable as from microdot import abort.

microdot.redirect(location: str, status_code: int = 302) Response#

Shortcut for Response.redirect(). Importable as from microdot import redirect.

microdot.send_file(filename: str, **kwargs) Response#

Shortcut for Response.send_file().

microdot.urlencode(s: str) str#

Percent-encode a URL component. Replaces characters that have reserved meaning in a URL (/, ?, &, =, #, space, …) with their %xx hex escapes so the result can safely sit inside a path segment or query value. Returns the encoded str.

microdot.urldecode(s: str) str#

Percent-decode a URL component – the inverse of urlencode(). %xx escapes are replaced with the byte they encode, and + is converted to a space (the historical query-string convention). Returns the decoded str.

Submodules#