Routing and handlers ==================== MicroPie maps incoming HTTP requests to methods on your :class:`~micropie.App` subclass. This section explains how the mapping works and how your handlers receive input from the URL path, query strings, form data and sessions. URL to function mapping ----------------------- When an HTTP request arrives, MicroPie extracts the path portion of the URL and uses the first path segment to determine which method should handle the request. For example, a GET request to ``/greet`` calls ``greet`` on your :class:`~micropie.App` subclass. A request to the root URL ``/`` calls ``index``. Only methods that do not start with an underscore are exposed as route handlers. Prefacing a method name with an underscore makes it private and hides it from external requests. Handlers may be synchronous or asynchronous functions. They return either a string, bytes, a JSON‑serialisable object, a tuple of ``(status_code, body)`` or ``(status_code, body, headers)``, or an iterator/generator for streaming responses. See :doc:`../howto/streaming` for details on streaming. Parameters and arguments ------------------------ MicroPie automatically populates handler arguments from several sources in the following order: 1. **Path parameters** – Additional path segments after the first map to positional parameters. For example: .. code-block:: python class AppExample(App): async def greet(self, name): return f"Hello, {name}!" Visiting ``/greet/Alice`` passes ``"Alice"`` as the ``name`` argument. You can also use ``*args`` to capture an arbitrary number of path segments. 2. **Query parameters** – Named parameters in the query string fill keyword arguments when path parameters are exhausted. For example, ``/greet?name=Alice`` also passes ``"Alice"`` to the ``name`` parameter. 3. **Body/form data** – For POST, PUT and PATCH requests, form fields populate handler arguments. JSON bodies are decoded into a dictionary of key/value pairs. 4. **Files** – If the request is multipart/form‑data, uploaded files appear in :attr:`~micropie.Request.files`. You can declare a file argument in your handler signature to receive a file object. 5. **Session data** – Values stored in :attr:`~micropie.Request.session` fill remaining parameters. See :doc:`../howto/sessions` for details. 6. **Default values** – If no other source provides a value, default values in your function signature are used. If MicroPie cannot determine a value for a required parameter, it returns a ``400 Bad Request``. Choosing an input access style ------------------------------ MicroPie gives you three levels of request input access. Prefer the simplest level that fits the handler. 1. **Handler arguments** are the most concise option. Use them when an endpoint needs a small number of named values and the normal binding order is acceptable. .. code-block:: python class MyApp(App): async def search(self, q="", page="1"): return {"q": q, "page": int(page)} This works for ``/search?q=micropie&page=2`` without touching ``self.request`` directly. 2. **Helper methods** are best when the handler needs explicit control over where a value comes from, a default value, or the full JSON payload. .. code-block:: python class MyApp(App): async def search(self): q = self.request.query("q", "") page = int(self.request.query("page", "1")) return {"q": q, "page": page} async def submit(self): username = self.request.form("username", "Anonymous") return {"submitted_by": username} async def api_submit(self): payload = self.request.json() username = self.request.json("username", "Anonymous") return {"submitted_by": username, "raw": payload} The helper methods are: * ``self.request.query(name, default=None)``: first query-string value for ``name``. * ``self.request.form(name, default=None)``: first parsed body value for ``name`` from form data, or from a top-level JSON object mirrored into body parameters. * ``self.request.json(name=None, default=None)``: full parsed JSON payload when called with no name, or one top-level JSON object value when ``name`` is provided. 3. **Raw attributes** are useful when you need the lower-level parsed structures, such as repeated query parameters, all submitted form values, uploaded files, or middleware decisions. * ``self.request.path_params``: list of positional path segments. * ``self.request.query_params``: ``dict[str, list[str]]`` parsed from the query string. * ``self.request.body_params``: ``dict[str, list[str]]`` parsed from form-urlencoded payloads, multipart text fields, or mirrored from top-level JSON objects. * ``self.request.session``: session dictionary. * ``self.request.files``: uploaded multipart files. * ``self.request.headers``: lower-case request header mapping. For example, use ``query_params`` when repeated values matter: .. code-block:: python class MyApp(App): async def filter(self): tags = self.request.query_params.get("tag", []) return {"tags": tags} Do not await request helpers ---------------------------- The request helper methods are synchronous accessors over data MicroPie has already parsed for the current request. Do not use ``await`` with ``query()``, ``form()`` or ``json()``: .. code-block:: python class MyApp(App): async def submit(self): data = self.request.json() username = self.request.form("username", "") q = self.request.query("q", "") return {"data": data, "username": username, "q": q} For JSON and URL-encoded form requests, the body has been read before your handler runs. Multipart file uploads are the main exception: uploaded file content is exposed as an ``asyncio.Queue`` in ``self.request.files`` and should be read asynchronously. Examples -------- The following examples illustrate common patterns: .. code-block:: python class MyApp(App): async def greet(self, name="Guest"): """Return a greeting for a named visitor. If the ``name`` argument is not provided via the path or query parameters, ``"Guest"`` is used as a default. """ return f"Hello, {name}!" async def add(self, x, y): """Add two numbers provided via path segments. Example: ``/add/2/3`` returns ``5``. """ return str(int(x) + int(y)) async def submit(self, username="Anonymous"): """Handle a form submission. This handler accepts POST requests and uses the ``username`` field from the request body. """ return f"Form submitted by: {username}" See :ref:`Request objects ` for the attributes available on the current request and :ref:`App class ` for details about the ``App`` class. HTTP methods and responses -------------------------- Handlers run for any HTTP method unless you explicitly check ``self.request.method`` inside the handler. It is your responsibility to dispatch based on the method if your endpoint should behave differently for GET and POST. Handlers may return: * A string or bytes – sent as the body of the response with a ``200 OK`` status. * A JSON‑serialisable object – automatically encoded into JSON and returned with a ``Content‑Type`` of ``application/json``. * A tuple ``(status_code, body)`` – sets the HTTP status and body. * A tuple ``(status_code, body, headers)`` – sets status, body and additional headers. Headers should be a list of ``(name, value)`` pairs. * A generator or async generator – streams chunks to the client. Use this for large responses or server‑sent events. Advanced routing ---------------- You can implement explicit routing, path prefixing or complex dispatching by writing a custom :class:`~micropie.HttpMiddleware`. See the :doc:`../howto/middleware` guide and the examples in the ``examples/explicit_routing`` directory of the source distribution.