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