Endpoints is a lightweight REST api framework written in python and used in multiple production systems that handle millions of requests daily.

Overview

Endpoints

Quickest API builder in the West!

Endpoints is a lightweight REST api framework written in python and used in multiple production systems that handle millions of requests daily.

5 Minute Getting Started

Installation

First, install endpoints with the following command.

$ pip install endpoints

If you want the latest and greatest you can also install from source:

$ pip install -U "git+https://github.com/jaymon/endpoints#egg=endpoints"

Note: if you get the following error

$ pip: command not found

you will need to install pip.

Set Up Your Controller File

Create a controller file with the following command:

$ touch controllers.py

Add the following code to your new Controller file. These classes are examples of possible endpoints.

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"

Start a Server

Now that you have your controllers.py, let's use the built-in WSGI server to serve them, we'll set our controllers.py file as the controller prefix so Endpoints will know where to find the Controller classes we just defined:

$ endpoints --prefix=controllers --host=localhost:8000

Test it out

Using curl:

$ curl http://localhost:8000
"boom"
$ curl http://localhost:8000/foo
"bang"
$ curl http://localhost:8000/ -d "name=Awesome you"
"hello Awesome you"

That's it. Easy peasy!

Can you figure out what path endpoints was following in each request?

We see in the first request that the Controller module was accessed, then the Default class, and then the GET method.

In the second request, the Controller module was accessed, then the Foo class as specified, and then the GET method.

Finally, in the last request, the Controller module was accessed, then the Default class, and finally the POST method with the passed in argument as JSON.

How does it work?

Endpoints translates requests to python modules without any configuration.

It uses the following convention.

METHOD /module/class/args?kwargs

Endpoints will use the base module you set as a reference point to find the correct submodule using the path specified by the request.

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 the class named Default will be used.

This makes it easy to bundle your controllers into something like a "Controllers" module.

Below are some examples of HTTP requests and how they would be interpreted using endpoints.

Note: prefix refers to the name of the base module that you set.

HTTP Request Path Followed
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)

As shown above, we see that endpoints essentially travels the path from the base module down to the appropriate submodule according to the request given.

One more example

Let's say your site had the following setup:

site/controllers/__init__.py

and the file 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:

HTTP Request Path Followed
GET / controllers.Default.GET()
GET /foo controllers.Foo.GET()

If you have gotten to this point, congratulations. You understand the basics of endpoints. If you don't understand endpoints then please go back and read from the top again before reading any further.

Learn more about Endpoints

Now you should dive into some of the other features discussed in the docs folder.

Comments
  • endpoints is not accessible outside aws

    endpoints is not accessible outside aws

    I ran endpoints example in aws but it is not accessible outisde the ec2 instance. But when I tried running other applications it is accessible so it is not the problem of security groups related issue

    opened by Rafi993 5
  • error in simple post request

    error in simple post request

    When I give simple post request I have the following error

    {
      "errno": 404,
      "errmsg": "/Foo not found because of TypeError \"not indexable\" on call.py:278"
    }
    

    my controller is as follows

    class Foo(Controller,CorsMixin):
        def GET(self): pass
        def POST(self,**params):
            return "bang"
    
    
    opened by Rafi993 4
  • args, or path, param decorator enhancement

    args, or path, param decorator enhancement

    allow param to also do path bits, so something like:

    decorators.param("/action", type=int)
    

    would make something like: /foo/1 valid, but /foo?action=1 would be invalid, this would be cool.

    Biggest thing would be to make sure order was preserved:

    decorators.param("/first", choice=["foo"])
    decorators.param("/second", choice=["bar"])
    

    would allow: /foo/bar but not /bar/foo and also work with *args parameter.

    enhancement 
    opened by Jaymon 4
  • Force the body to be a string.

    Force the body to be a string.

    When running endpoints from within a docker container for some reason the body portion is being decoded as a bytes-like object b''. The same code running locally works fine. I added a string conversion explicitly to force the body to be a string.

    We may want to look into support for the Content-Type header's charset attribute so we are not explicitly attempting to convert a non utf-8 string to utf-8. If that is desired I can put a little more effort into this PR to support that as well. I ran into this issue which prevented me from moving forward. Maybe you have a better approach.

    Traceback (most recent call last):
      File "/usr/lib/python3.5/wsgiref/handlers.py", line 137, in run
        self.result = application(self.environ, self.start_response)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/wsgi/__init__.py", line 30, in __call__
        return self.handle_http_response(environ, start_response)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/wsgi/__init__.py", line 33, in handle_http_response
        c = self.create_call(environ)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/__init__.py", line 239, in create_call
        req = request if request else self.create_request(raw_request, **kwargs)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/wsgi/__init__.py", line 77, in create_request
        self.create_request_body(r, raw_request, **kwargs)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/wsgi/__init__.py", line 98, in create_request_body
        body_args, body_kwargs = self.get_request_body_json(body, **kwargs)
      File "/usr/local/lib/python3.5/dist-packages/endpoints/interface/__init__.py", line 182, in get_request_body_json
        b = json.loads(body)
      File "/usr/lib/python3.5/json/__init__.py", line 312, in loads
        s.__class__.__name__))
    TypeError: the JSON object must be str, not 'bytes'
    
    opened by dsandor 3
  • an idea about subparams

    an idea about subparams

    Right now, say you had a controller that could have a path:

    class Foo(Controller):
        def GET(self, action, **kwargs): pass
    

    where action can be bar or che, so valid requests would be:

    • /example.com/foo/bar
    • /example.com/foo/che

    I'm wondering if we could codify this with camel casing:

    class FooBar(Controller):
        """handle /foo/bar"""
        def GET(self, **kwargs): pas
    
    class FooChe(Controller):
        """handle /foo/che"""
        def GET(self, **kwargs): pas
    

    So camel casing class names would move through path bits. This is inspired by some comments Jarid made about us having to set a few params as optional because we had multiple action path bits that needed different attributes:

    class Foo(Controller):
        @param(0, choices=["one", "two"], help="the action value")
        @param("bar", default=None, help="only needed if action is 'one' but needs to be optional")
        @param("che", default=None, help="only needed if action is 'two' but needs to be optional")
        def GET(self, action, **kwargs): pass
    
    opened by Jaymon 3
  • sudo pm2 endpoints shows error

    sudo pm2 endpoints shows error

    endpoints dosent start and shows error when tried to start with pm2 in linux I tried following the instructions given here

    sudo pm2 start endpoints -- --prefix=index --host=localhost:2002

    opened by Rafi993 3
  • multiple controller prefixes

    multiple controller prefixes

    it would be great to have support for multiple prefixes, so you could have:

    app.controllers
    package.controllers
    package2.controllers
    

    and the router would check all the registered prefixes to find the controller for the request, this would be the first step in supporting plugins. Basically you could have a package that has some built in controllers that you could register in your main app, something like:

    from endpoints import register
    
    register("app.controllers")
    register("package.controllers")
    

    This would provide flexibility for things like using the package controllers as-is or extending them in your own app, basically if you didn't register them then they wouldn't be used. Likewise the package could register them automatically so just by importing something of the package it could call the endpoints.register thing

    the above is all just speculation on my part, I'm not sure how I want to do it yet or what would be the best approach.

    opened by Jaymon 2
  • Routing variable URI bits

    Routing variable URI bits

    So we have an api structure like this we want to support:

    /foo/bar
    /foo
    /
    

    where foo and bar can contain any value (or not be there at all). Currently, we would have one controller that has to handle all three variations:

    class Default(Controller):
        def GET(self, *args, **kwargs):
            if len(args) == 2:
                # /foo/bar
            elif len(args) == 1:
                # /foo
            elif len(args) == 0:
                # /
    

    I don't like this because we can't use the decorators to define the boundaries for foo and bar.

    I propose we allow a numeric modifier to the class name:

    class Default2(Controller):
        # /foo/bar
        def GET(self, foo, bar): pass
    
    class Default1(Controller):
        # /foo
        def GET(self, foo): pass
    
    class Default(Controller):
        # /
        def GET(self): pass
    

    I think this supersedes #56 also, since this will allow us to break up the individual requests enough to use our decorators for each unique request and we can handle what #56 talks about by just adding one more submodule with 2 controller classes, so I think all the bases are covered?

    I think this will be a relatively easy (and backwards compatible) change by just having the router check for class_name + len(args) from 1 to len(args) and take the first classname that matches.

    Where might this be ambiguous?

    If your class name ends with a number:

    
    class Foo2(Controller):
        def GET(self): pass
    
    class Foo(Controller):
        def GET(self, *args): pass
    

    So /foo2 and /foo/1/2 will both go to Foo2.

    opened by Jaymon 2
  • add http cache decorator

    add http cache decorator

    basically, the decorator should take a ttl and then set the appropriate headers: Cache-Control, Pragma, and Expires.

    Likewise, there should be a nocache decorator that makes the page uncacheable:

    "Cache-Control": "no-cache, no-store, must-revalidate",
    "Pragma": "no-cache", 
    "Expires": "0"
    
    enhancement 
    opened by Jaymon 2
  • response should set encoding to utf-8 if it isn't already set

    response should set encoding to utf-8 if it isn't already set

    right now we don't automatically set the content encoding of the response but we should. I'm thinking it could even be an environment variable if needed

    enhancement 
    opened by Jaymon 2
  • Code cleanup from adding datatypes

    Code cleanup from adding datatypes

    Both the compat module and the _property stuff needs to be switched over fully to using the datatypes versions and then removed from the endpoint's codebase.

    opened by Jaymon 1
  • InterfaceRequest

    InterfaceRequest

    Currently, we basically make all the interfaces look a bit like a WSGI request, then http.Request can use .environ and stuff to find information like IP address and host.

    I think it would be better to have an InterfaceRequest wrapper that would take the raw request and then the interface could implement the supported methods, and then http.Request would be instantiated like this:

    request = Request(InterfaceRequest(raw_request))
    

    I think the class would be something like:

    class InterfaceRequest(object):
        def __init__(self, raw_request):
            self.raw = raw_request
    
        def get_headers(self):
            raise NotImplementedError()
    
        ...
    

    This was my original note:

    create an interface request class that can handle bridging between the interface (uwsgi, asgi) and the request object. So basically, the interface would create an InterfaceRequest class and pass that to Request, then Request would use InterfaceRequest for things like .ip and .scheme

    So the big question is, why would we do this over just having these things set directly on http.request in the interface's create_request method like we kind of do now? This solution would make it so the code is run on demand instead of all ran in the create_request method. Likewise, it would provide a standard template for what needs to be implemented, right now to add a new interface I usually look at what a previous interface's create_request method is doing.

    Biggest annoyance would be all the tests that just create a request object, I'd need to change all those to take the InterfaceRequest instance, and there are a lot of tests.

    opened by Jaymon 0
  • Asyncio interfaces

    Asyncio interfaces

    Now that python2 support has been dropped I think I will add asyncio support with the ASGI interface. After some tests, it looks like I can have the async and sync interfaces be the same, the sync will just extend the async interface and wrap all the methods, something like:

    class AsyncFoo(object):
        async def foo(self, ret_value, sleep=1):
            print("async")
            await asyncio.sleep(sleep)
            print("foo")
            return value
    
    
    class SyncFoo(AsyncFoo):
        def foo(self, *args, **kwargs):
            return asyncio.run(super().foo(*args, **kwargs))
    
    
    f = SyncFoo()
    print(f.foo(6))
    

    This works in my tests and so, hopefully, something similar to my tests will actually work when implemented so I can keep the interfaces the same and then it's just a choice on what to use.

    Links I had open

    Search

    • does python asyncio need monkey patching
    • python async
    opened by Jaymon 2
  • log when posting with an empty body

    log when posting with an empty body

    Right now, if the body is empty then the it won't log the body at all, it would be better to say something like BODY: <EMPTY> or something like that, so you can see in the logs that endpoints thought the body was empty.

    opened by Jaymon 0
  • ASGI interface module

    ASGI interface module

    From the docs:

    ASGI (Asynchronous Server Gateway Interface) is a spiritual successor to WSGI, intended to provide a standard interface between async-capable Python web servers, frameworks, and applications.

    Heard about this from here:

    If you are starting a new project, you might benefit from a newer and faster framework based on ASGI instead of WSGI (Flask and Django are WSGI-based).

    ...Also, if you want to use new technologies like WebSockets it would be easier (and possible) with a newer framework based on ASGI, like FastAPI or Starlette. As the standard ASGI was designed to be able to handle asynchronous code like the one needed for WebSockets.

    Check out Starlette, Uvicorn, or FastAPI to see how it is implemented. It looks like FastAPI depends on Starlette, and Starlette is using Anyio as an underlying library. This module: asgiref also looks useful.

    The current reference server is Daphne:

    The current ASGI reference server, written in Twisted and maintained as part of the Django Channels project. Supports HTTP/1, HTTP/2, and WebSockets.

    opened by Jaymon 3
  • Request parse user agent

    Request parse user agent

    Found this in some old application code, could probably be moved into Request core:

        def parse_user_agent(self, user_agent):
            """parses any user agent string to the best of its ability and tries not
            to error out"""
            d = {}
    
            regex = "^([^/]+)" # 1 - get everything to first slash
            regex += "\/" # ignore the slash
            regex += "(\d[\d.]*)" # 2 - capture the numeric version or build
            regex += "\s+\(" # ignore whitespace before parens group
            regex += "([^\)]+)" # 3 - capture the full paren body
            regex += "\)\s*" # ignore the paren and any space if it is there
            regex += "(.*)$" # 4 - everything else (most common in browsers)
            m = re.match(regex, user_agent)
            if m:
                application = m.group(1)
                version = m.group(2)
                system = m.group(3)
                system_bits = re.split("\s*;\s*", system)
                tail = m.group(4)
    
                # common
                d['client_application'] = application
                d['client_version'] = version
                d['client_device'] = system_bits[0]
    
                if application.startswith("Mozilla"):
                    for browser in ["Chrome", "Safari", "Firefox"]:
                        browser_m = re.search("{}\/(\d[\d.]*)".format(browser), tail)
                        if browser_m:
                            d['client_application'] = browser
                            d['client_version'] = browser_m.group(1)
                            break
    
            return d
    

    and the test:

        def test_user_agent(self):
            user_agents = [
                (
                    "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
                    {
                        'client_application': "Chrome",
                        'client_version': "44.0.2403.157",
                        'client_device': "Windows NT 6.3"
                    }
                ),
                (
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36",
                    {
                        'client_application': "Chrome",
                        'client_version': "44.0.2403.157",
                        'client_device': "Macintosh"
                    }
                ),
                (
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:40.0) Gecko/20100101 Firefox/40.0",
                    {
                        'client_application': "Firefox",
                        'client_version': "40.0",
                        'client_device': "Macintosh"
                    }
                ),
                (
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12", # Safari
                    {
                        'client_application': "Safari",
                        'client_version': "600.7.12",
                        'client_device': "Macintosh"
                    }
                ),
                (
                    "curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8x zlib/1.2.5",
                    {
                        'client_application': "curl",
                        'client_version': "7.24.0",
                        'client_device': "x86_64-apple-darwin12.0"
                    }
                )
            ]
    
            for user_agent in user_agents:
                d = self.user_agent(user_agent[0])
                self.assertDictContainsSubset(user_agent[1], d)
    
    opened by Jaymon 0
  • Logging improvements

    Logging improvements

    Body doesn't have the request id:

    [I] Request 0c2f934a-f9f5-4ffd-ba6f-4735488587d0 header Accept-Encoding: gzip, deflate, br
    [D] BODY: {'foo': 1111}
    

    The request id could probably be shortened to the last part, so 0c2f934a-f9f5-4ffd-ba6f-4735488587d0 would become 4735488587d0

    Maybe get rid of the Request part also, so something like:

    req4735488587d0
    

    It would be great to have the full request in the summary:

    Request 0c2f934a-f9f5-4ffd-ba6f-4735488587d0 response 429 UNKNOWN in 13.3 ms
    

    Could become something like:

    Request <REQUEST ID> <HTTP METHOD> <PATH> response <CODE> in <TIME>
    

    so...

    Request 4735488587d0 GET /foo/bar response 429 UNKNOWN in 13.3 ms
    
    opened by Jaymon 0
Owner
Jay Marcyes
I build things, some of those things end up here, others don't
Jay Marcyes
An alternative serializer implementation for REST framework written in cython built for speed.

drf-turbo An alternative serializer implementation for REST framework written in cython built for speed. Free software: MIT license Documentation: htt

Mng 74 Dec 30, 2022
Lemon is an async and lightweight API framework for python

Lemon is an async and lightweight API framework for python . Inspired by Koa and Sanic .

Joway 29 Nov 20, 2022
APIFlask is a lightweight Python web API framework based on Flask and marshmallow-code projects

APIFlask APIFlask is a lightweight Python web API framework based on Flask and marshmallow-code projects. It's easy to use, highly customizable, ORM/O

Grey Li 705 Jan 4, 2023
FastAPI framework, high performance, easy to learn, fast to code, ready for production

FastAPI framework, high performance, easy to learn, fast to code, ready for production Documentation: https://fastapi.tiangolo.com Source Code: https:

Sebastián Ramírez 53k Jan 2, 2023
REST API framework designed for human beings

Eve Eve is an open source Python REST API framework designed for human beings. It allows to effortlessly build and deploy highly customizable, fully f

eve 6.6k Jan 7, 2023
REST API framework designed for human beings

Eve Eve is an open source Python REST API framework designed for human beings. It allows to effortlessly build and deploy highly customizable, fully f

eve 6.3k Feb 17, 2021
The no-nonsense, minimalist REST and app backend framework for Python developers, with a focus on reliability, correctness, and performance at scale.

The Falcon Web Framework Falcon is a reliable, high-performance Python web framework for building large-scale app backends and microservices. It encou

Falconry 9k Jan 1, 2023
Sierra is a lightweight Python framework for building and integrating web applications

A lightweight Python framework for building and Integrating Web Applications. Sierra is a Python3 library for building and integrating web applications with HTML and CSS using simple enough syntax. You can develop your web applications with Python, taking advantage of its functionalities and integrating them to the fullest.

null 83 Sep 23, 2022
TinyAPI - 🔹 A fast & easy and lightweight WSGI Framework for Python

TinyAPI - ?? A fast & easy and lightweight WSGI Framework for Python

xArty 3 Apr 8, 2022
A public API written in Python using the Flask web framework to determine the direction of a road sign using AI

python-public-API This repository is a public API for solving the problem of the final of the AIIJC competition. The task is to create an AI for the c

Lev 1 Nov 8, 2021
Goblet is an easy-to-use framework that enables developers to quickly spin up fully featured REST APIs with python on GCP

GOBLET Goblet is a framework for writing serverless rest apis in python in google cloud. It allows you to quickly create and deploy python apis backed

Austen 78 Dec 27, 2022
PipeLayer is a lightweight Python pipeline framework

PipeLayer is a lightweight Python pipeline framework. Define a series of steps, and chain them together to create modular applications

greaterthan 64 Jul 21, 2022
Django Ninja - Fast Django REST Framework

Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints.

Vitaliy Kucheryaviy 3.8k Jan 2, 2023
A Flask API REST to access words' definition

A Flask API to access words' definitions

Pablo Emídio S.S 9 Jul 22, 2022
Free & open source Rest API for YTDislike

RestAPI Free & open source Rest API for YTDislike, read docs.ytdislike.com for implementing. Todo Add websockets Installation Git clone git clone http

null 1 Nov 25, 2021
Free and open source full-stack enterprise framework for agile development of secure database-driven web-based applications, written and programmable in Python.

Readme web2py is a free open source full-stack framework for rapid development of fast, scalable, secure and portable database-driven web-based applic

null 2k Dec 31, 2022
An abstract and extensible framework in python for building client SDKs and CLI tools for a RESTful API.

django-rest-client An abstract and extensible framework in python for building client SDKs and CLI tools for a RESTful API. Suitable for APIs made wit

Certego 4 Aug 25, 2022
Containers And REST APIs Workshop

Containers & REST APIs Workshop Containers vs Virtual Machines Ferramentas Podman: https://podman.io/ Docker: https://www.docker.com/ IBM CLI: https:/

Vanderlei Munhoz 8 Dec 16, 2021
A minimal, extensible, fast and productive API framework for Python 3.

molten A minimal, extensible, fast and productive API framework for Python 3. Changelog: https://moltenframework.com/changelog.html Community: https:/

Bogdan Popa 980 Nov 28, 2022