Quickest api builder in the west! Lovingly crafted for First Opinion.
First, install endpoints:
$ pip install endpoints
Then create a controller file:
$ touch mycontroller.py
And add some controller classes:
# mycontroller.py
from endpoints import Controller
class Default(Controller):
def GET(self):
return "boom"
def POST(self, **kwargs):
return 'hello {}'.format(kwargs['name'])
class Foo(Controller):
def GET(self):
return "bang"
Set a couple environment variables for a simple python server:
$ export ENDPOINTS_PREFIX=mycontroller
$ export ENDPOINTS_SIMPLE_HOST=localhost:8000
Now create a server file:
$ touch myserver.py
And add the necessary code to run a simple server:
# myserver.py
import os
from endpoints.interface.simple import Server
s = Server()
s.serve_forever()
Start your server file:
$ python myserver.py
And make some requests:
$ curl "http://localhost:8000/"
boom
$ curl "http://localhost:8000/foo"
bang
$ curl -H "Content-Type: application/json" -d '{"name": "world"}' "http://localhost:8000/"
hello world
Congratulations, you've created a webservice.
Endpoints translates requests to python modules without any configuration. It uses the convention:
METHOD /module/class/args?kwargs
To find the modules, you assign a base module (a prefix) that endpoints will use as a reference point to find the correct submodule using the path. This makes it easy to bundle your controllers into something like a controllers
module. Some examples of how http requests would be interpretted:
GET / -> prefix.Default.GET()
GET /foo -> prefix.foo.Default.GET()
POST /foo/bar -> prefix.foo.Bar.POST()
GET /foo/bar/che -> prefix.foo.Bar.GET(che)
GET /foo/bar/che?baz=foo -> prefix.foo.Bar.GET(che, baz=foo)
POST /foo/bar/che with body: baz=foo -> prefix.foo.Bar.POST(che, baz=foo)
Requests are translated from the left bit to the right bit of the path (so for the path /foo/bar/che/baz
, Endpoints would check for the foo
module, then the foo.bar
module, then the foo.bar.che
module, etc. until it fails to find a valid module). Once the module is found, endpoints will then attempt to find the class with the remaining path bits. If no class is found, Default
will be used.
So, if you set up your site like this:
site/
controllers/
__init__.py
and the controllers.__init__.py
contained:
from endpoints import Controller
class Default(Controller):
def GET(self):
return "called /"
class Foo(Controller):
def GET(self):
return "called /foo"
Then, your call requests would be translated like this:
GET / -> controllers.Default.GET()
GET /foo -> controllers.Foo.GET()
You can define your controller methods to accept certain path params and to accept query params:
class Foo(Controller):
def GET(self, one, two=None, **params): pass
def POST(self, **params): pass
your call requests would be translated like this:
GET /foo/one -> prefix.Foo.GET("one")
GET /foo/one?param1=val1¶m2=val2 -> prefix.Foo.GET("one", param1="val1", param2="val2")
GET /foo -> 404, no one path param
GET /foo/one/two -> prefix.Foo.GET("one", "two")
Post requests are also merged with the **params
on the controller method, with the POST
params taking precedence:
POST /foo?param1=GET1¶m2=GET2 body: param1=POST1¶m3=val3 -> prefix.Foo.POST(param1="POST1", param2="GET2", param3="val3")
The endpoints.decorators
module gives you some handy decorators to make parameter handling and error checking easier:
from endpoints import Controller
from endpoints.decorators import param
class Foo(Controller):
@param('param1', default="some val")
@param('param2', choices=['one', 'two'])
def GET(self, **params): pass
For the most part, the param
decorator tries to act like Python's built-in argparse.add_argument() method.
There is also a get_param
decorator when you just want to make sure a query param exists and don't care about post params and a post_param
when you only care about posted parameters. There is also a require_params
decorator that is a quick way to just make sure certain parameters were passed in:
from endpoints import Controller
from endpoints.decorators import param
class Foo(Controller):
@require_params('param1', 'param2', 'param3')
def GET(self, **params): pass
That will make sure param1
, param2
, and param3
were all present in the **params
dict.
The auth
decorator tries to make user authentication easier, it takes a realm and a target callback in order to perform the authentication.
from endpoints import Controller
from endpoints.decorators import auth
def target(request):
username, password = request.get_auth_basic()
if username != "foo" or password != "bar":
raise ValueError("authentication failed")
class Foo(Controller):
@auth("Basic", target)
def GET(self, **params): pass
The auth
decorator can also be subclassed and customized.
Endpoints has support for Accept
header versioning, inspired by this series of blog posts.
You can activate versioning just by adding a new method to your controller using the format:
METHOD_VERSION
So, let's say you have your controllers set up like this:
site/
controllers/
__init__.py
and controllers.__init__.py
contained:
from endpoints import Controller
class Default(Controller):
def GET(self):
return "called version 1 /"
def GET_v2(self):
return "called version 2 /"
class Foo(Controller):
def GET(self):
return "called version 1 /foo"
def GET_v2(self):
return "called version 2 /foo"
Then, your call requests would be translated like this:
GET / with Accept: */* -> controllers.Default.GET()
GET /foo with Accept: */* -> controllers.Foo.GET()
GET / with Accept: */*;version=v2 -> controllers.Default.GET_v2()
GET /foo with Accept: */*;version=v2 -> controllers.Foo.GET_v2()
Notice how attaching the ;version=v2
to the Accept
header changes the method that is called to handle the request.
Endpoints has a CorsMixin
you can add to your controllers to support CORS requests:
from endpoints import Controller, CorsMixin
class Default(Controller, CorsMixin):
def GET(self):
return "called / supports cors"
The CorsMixin
will handle all the OPTION
requests, and setting all the headers, so you don't have to worry about them (unless you want to).
Endpoints comes with wsgi and Python Simple Server support.
import os
from endpoints.interface.wsgi import Server
os.environ['ENDPOINTS_PREFIX'] = 'mycontroller'
application = Server()
Yup, that's all you need to do to set it up, then you can start a uWSGI server to test it out:
uwsgi --http :9000 --wsgi-file YOUR_FILE_NAME.py --master --processes 1 --thunder-lock --chdir=/PATH/WITH/YOUR_FILE_NAME/FILE
Use PIP
pip install endpoints
If you want the latest and greatest, you can also install from source:
pip install "git+https://github.com/firstopinion/endpoints#egg=endpoints"
To run the tests:
python -m unittest endpoints_test
Check the tests_require
parameter in the setup.py
script to see what modules are needed to run the tests.
MIT