# App boilerplate... # URL/Routing boilerplate... def hello(request): # get args from some request object who = request.args[...] # our logic s = "hello %s!" % who return Response(...)
# App boilerplate... # URL/Routing boilerplate... class HelloAPI(Handler) def get(self): # get args from some request object who = request.args[...] # our logic s = "hello %s!" % who return Response(...)
as arguments Pico serialises the data we return (as JSON) Pico determines the URL from module and function name We write framework ignorant function bodies
webflicks.list_movies() KeyError: 'REMOTE_ADDR' In [3]: import pico In [4]: pico.set_dummy_request({'REMOTE_ADDR': '86.45.123.136'}) In [5]: webflicks.list_movies() Out[5]: [{'title': 'The Life of Brian', ...}, ...]
[2]: webflicks.list_movies() KeyError: 'REMOTE_ADDR' In [3]: import pico In [4]: pico.set_dummy_request({'REMOTE_ADDR': '86.45.123.136'}) In [5]: webflicks.list_movies() Out[5]: [{'title': 'The Life of Brian', ...}, ...]
database This works but our delete function is unnecessarily coupled to a user object. @pico.expose() @request_args(user=current_user) def delete_movie(id, user): if user in admin_users: # delete the movie else: raise Unauthorized()
database If the protector does not raise an exception the function executes normally. If the user is not an admin a 401 Unauthorized response is returned to the client. from pico.decorators import protected def is_admin(request, wrapped, args, kwargs): user = current_user(request) if user not in admin_users: raise Unauthorized @pico.expose() @protected(is_admin) def delete_movie(id): # delete the movie
We can still call our functions from anywhere else without passing around User objects. In [1]: import webflicks In [2]: webflicks.delete_movie(42) Out[2]:
1 URL == 1 Function This keeps life simple GET http://example.com/api/list_users POST http://example.com/api/add_user GET http://example.com/api/get_user?id=1 POST http://example.com/api/update_user?id=1 POST http://example.com/api/delete_user?id=1
not trying to be RESTful we relieve ourselves of a huge burden (and endless debate about correctness). Pico APIs are pragmatic, optimising for ease of use and ease of development over theoretical correctness.
format (JSON) Automatic JSON serialisation beyond normal json with pico.pragmaticjson Sensible simple URLs Self describing Supports passing arguments as query parameters, JSON and FormEncoded. Works with any HTTP client Includes a Python and Javascript client Easy to override various parts where necessary Deployed like any other WSGI application
environment. Was originally an app in a Django project developed to ease interacting with Python based analysis tools from Javascript based visualisation tools. Optimised for quick iterative prototyping. Main users were researchers putting a simple api in front of Numpy/ML/GIS tools, NOT web developers. Later extracted into standalone framework. Intended to be a 'smaller than micro' framework, hence Pico. Now used for a variety of production services, from singlemodule 'microservices' to multimodule projects with 100+ endpoints.
= "hello %s!" % who return {'message': s} Literally add one line of code (import pico) to your Python module to turn it into a web service — README Why do I import pico but not use it? How do I know which functions are exposed? — set(pico_users) - [me] F401'pico' imported but unused — flake8
from scratch Request, Response, HTTP Errors, Static file handling, etc Pico 2.0 was rebuilt on top of Werkzeug from werkzeug.wrappers import Request, Response from werkzeug.exceptions import * from werkzeug.serving import run_simple from werkzeug.wsgi import SharedDataMiddleware
writing basic Python modules Frameworks should help us write clean testable & maintainable code Frameworks should aim to minimise mental overhead, not increase it Minimalistic frameworks can be powerful pip install pico