.. _extending: =================== Extending ``itty3`` =================== ``itty3`` is designed to be streamlined & simple to use out of the box. However, it's relatively easy to customize as well. Custom ``HttpResponse`` ======================= In normal usage, ``app.render(...)`` is simply a shortcut/convenience method for constructing an ``itty3.HttpResponse`` object. There's nothing extraordinary about these instances, so you can make them yourself if you prefer:: import pyyaml @app.get("/whatev/") def whatev(request): data = { "greeting": "Hello", "noun": "world", "punctuation": "!", } return itty3.HttpResponse( body=pyyaml.dump(data) content_type="text/yaml" ) Or even subclass it to "bake in" complex behavior:: class YAMLResponse(itty3.HttpResponse): def __init__(self, data, **kwargs): body = pyyaml.dump(data) kwargs["content_type"] = "text/yaml" super().__init__(body, **kwargs) @app.get("/whatev/") def whatev(request): data = { "greeting": "Hello", "noun": "world", "punctuation": "!", } return YAMLResponse(data) And you can override/extend ``App.render`` if you want to automatically use your new subclass:: class MyApp(itty3.App): def render(self, request, *args, **kwargs): # Because of the mismatch in signatures (`body` vs `data`), this # may not work perfectly in all situations. return YAMLResponse(*args, **kwargs) Custom ``HttpRequest`` ====================== As with ``HttpResponse``, ``itty3.HttpRequest`` isn't particularly special. The only interesting/tricky part is automatically constructed by ``App.create_request``, so you'll need to override that method when creating a subclass. For instance, if you wanted to identify a request was secure in a different way (e.g. verifying a nonce was present in headers):: class NonceHttpRequest(itty3.HttpRequest): def is_secure(self): if "X-Secure-Nonce" not in self.headers: return False nonce = self.headers["X-Secure-Nonce"] # Check a DB for that nonce within a time range. if not verified: return False # The request is good. Carry on. return True You could then tell your ``App`` to always use this subclass with:: class SuperSecureApp(itty3.App): def create_request(self, environ): return NonceHttpRequest.from_wsgi(environ) Different Routing ================= If ``itty3``'s routing doesn't suit you, you can even define your own variant of ``Route``. As long as it conforms to a bit of expected API & with a couple minor tweaks to ``App``...:: # We want to support routes like: # # "/app/:id/:title" # # ...and attempt automatic conversion of types. # This is a naive implementation of that. class SimpleRoute(object): def __init__(self, method, path, func): self.method = method self.path = path self.func = func def split_uri(self, path): return path.split("/") def can_handle(self, method, path): if not self.method == method: return False internal_path_bits = self.split_uri(self.path) external_path_bits = self.split_uri(path) if len(internal_path_bits) != len(external_path_bits): # Without even iterating, we know it's not right. return False matched = True for offset, bit in enumerate(internal_path_bits): if not bit.startswith(":"): # We're looking for a non-variable, exact match. if bit != external_path_bits[offset]: matched = False break else: # It's a variable. Carry on. continue return matched def extract_kwargs(self, path): # This only gets called if the route can handle the URI. # So we'll take a shortcut or two here for brevity. internal_path_bits = self.split_uri(self.path) external_path_bits = self.split_uri(path) matches = {} for offset, bit in enumerate(internal_path_bits): if not bit.startswith(":"): # It's not a variable, we don't care. continue # It's a variable. Slice off the colon. var_name = bit[1:] # Extract it from the actual URI. value = external_path_bits[offset] # Try to convert the type. try: value = int(value) except ValueError: pass try: value = float(value) except ValueError: pass if value in ("true", "false"): # Store it as a boolean. value = value == "true" # Finally, track what we found. matches[var_name] = value return matches # Now, just override ``App`` to use your new routes. class SimpleRoutesApp(itty3.App): def add_route(self, method, path, func): # We swap in the custom class here. route = SimpleRoute(method, path, func) self._routes.append(route)