On to the first commit

Well now that the Setup part is already done. Lets move into looking at what the first commit consists of following files

git checkout e84f8510c8d5686ae0a76d76102b6898adc26621

We find the responder is the package cause it has the __init__.py. Now this file runs as soon as responder package is imported. The only code that __init__.py has importing API from core module in the current path.

from .core import API

Lets see what is present in core.py

from .api import API

from . import status

Okies so core module imports the API from api module. So lets see what the api module contains .

class BaseAPI:

def __init__(self, *, yaml_allowed):

self.yaml_allowed = yaml_allowed

self.routes = {}



@property

def should_yaml(self):

return self.yaml_allowed





class API(BaseAPI):

def __init__(self, *, yaml_allowed=True):

super().__init__(yaml_allowed=yaml_allowed)



def add_route(self, route, view, *, check_existing=True):

if check_existing:

assert route not in self.routes



self.routes[route] = view



def route(self, route, **options):

def decorator(f):

self.add_route(route, f)

return f



return decorator

Effectively API class is available once you import the responder package.

Few observations about the BaseAPI class

constructor args ( self , *, yaml_allowed): Basically making sure that all the argument passed to constructor are keyword only You can verify that in the constructor for API class when BaseAPI constructor is called using super().__init__() , we pass the keyword only parameter yaml_allowed to it.

class API(BaseAPI):

def __init__(self, *, yaml_allowed=True):

super().__init__(yaml_allowed=yaml_allowed)

3. self.routes in the BaseAPI is a dictionary that contains mapping of routes to views

4. When we look at the route method in the API class , we find that its returning a decorator and its pretty standard way of passing arguments to decorator. Since the decorator function is defined inside the route method , it can access the route parameter [ ie: the path ] in the route method

5. This makes sure that when we define view function and decorate it with route , the dictionary mapping of route to view function is created like shown below

6. There is explicit add_route method provided on the API class which is pretty self explanatory. Again it updates the self.routes dictionary

import responder



api = responder.API(yaml_allowed=True)

# api.mount('/subapp', other_wsgi_app)





@api.route("/")

def hello(req, resp):

resp.status = responder.http_status.ok

resp.media = {"hello": "world"}

resp.text = ""

resp.content = ""





class ThingsResource:

def on_request(self, req, resp):

resp.status = responder.status.HTTP_200





# Alerntatively,

api.add_route("/{hello}", ThingsResource)

print(api.routes)

We can see the dictionary mapping when you run the app.py

{‘/’ : <function hello at 0x104b3f378>,

‘/{hello}’: <class ‘__main__.ThingsResource’>}

7. At this moment cli.py / http.py / server.py are empty

8. status.py contains the mapping of HTTP codes to strings

codes = {

# Informational.

100: ("continue",),

101: ("switching_protocols",),

102: ("processing",),

103: ("checkpoint",),

122: ("uri_too_long", "request_uri_too_long"),

200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"),

201: ("created",),

202: ("accepted",),

203: ("non_authoritative_info", "non_authoritative_information"),

204: ("no_content",),

205: ("reset_content", "reset"),

206: ("partial_content", "partial"),

207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),

208: ("already_reported",),

226: ("im_used",),

# Redirection.

300: ("multiple_choices",),

301: ("moved_permanently", "moved", "\\o-"),

302: ("found",),

303: ("see_other", "other"),

304: ("not_modified",),

305: ("use_proxy",),

306: ("switch_proxy",),

307: ("temporary_redirect", "temporary_moved", "temporary"),

308: (

"permanent_redirect",

"resume_incomplete",

"resume",

), # These 2 to be removed in 3.0

# Client Error.

400: ("bad_request", "bad"),

401: ("unauthorized",),

402: ("payment_required", "payment"),

403: ("forbidden",),

404: ("not_found", "-o-"),

405: ("method_not_allowed", "not_allowed"),

406: ("not_acceptable",),

407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),

408: ("request_timeout", "timeout"),

409: ("conflict",),

410: ("gone",),

411: ("length_required",),

412: ("precondition_failed", "precondition"),

413: ("request_entity_too_large",),

414: ("request_uri_too_large",),

415: ("unsupported_media_type", "unsupported_media", "media_type"),

416: (

"requested_range_not_satisfiable",

"requested_range",

"range_not_satisfiable",

),

417: ("expectation_failed",),

418: ("im_a_teapot", "teapot", "i_am_a_teapot"),

421: ("misdirected_request",),

422: ("unprocessable_entity", "unprocessable"),

423: ("locked",),

424: ("failed_dependency", "dependency"),

425: ("unordered_collection", "unordered"),

426: ("upgrade_required", "upgrade"),

428: ("precondition_required", "precondition"),

429: ("too_many_requests", "too_many"),

431: ("header_fields_too_large", "fields_too_large"),

444: ("no_response", "none"),

449: ("retry_with", "retry"),

450: ("blocked_by_windows_parental_controls", "parental_controls"),

451: ("unavailable_for_legal_reasons", "legal_reasons"),

499: ("client_closed_request",),

# Server Error.

500: ("internal_server_error", "server_error", "/o\\", "✗"),

501: ("not_implemented",),

502: ("bad_gateway",),

503: ("service_unavailable", "unavailable"),

504: ("gateway_timeout",),

505: ("http_version_not_supported", "http_version"),

506: ("variant_also_negotiates",),

507: ("insufficient_storage",),

509: ("bandwidth_limit_exceeded", "bandwidth"),

510: ("not_extended",),

511: ("network_authentication_required", "network_auth", "network_authentication"),

}



for number in codes:

locals()[f"HTTP_{number}"] = number



for label in codes[number]:

locals()[label] = number

All in all this commit allows us to create instance of API class using package name and whenever we mark view functions with route decorator , it maintains the mapping of those routes to view functions as shown in #6

It allows explicitly adding route to view mapping using the add_routes() method on the API class .

import responder api = responder.API(yaml_allowed=True) @api.route("/")

def hello(req, resp):

resp.status = responder.http_status.ok

resp.media = {"hello": "world"}

resp.text = ""

resp.content = "" class ThingsResource:

def on_request(self, req, resp):

resp.status = responder.status.HTTP_200





# Alerntatively,

api.add_route("/{hello}", ThingsResource)

print(api.routes)

Thats it for this post. Stick with me and we will go through the next commit in the next post.

Things that stood out:

Making sure the keyword only argument to constructor How the API class which is in api module, is made available to package so that the api instance in created just from the package. This is just beautiful.