Skip to main content

A mini-framework for Django for creating RESTful APIs.

Project description

YAPI == Yet/Why Another API Framework

This is a mini-framework for creating RESTful APIs in Django.

Installation

  1. Download dependencies:
    • Python 2.6+

    • Django 1.5+

  2. pip install django-yapi or easy_install django-yapi

Configuration

settings.py

  1. Add “yapi” to your INSTALLED_APPS setting like this:

    INSTALLED_APPS = (
        # all other installed apps
        'yapi',
    )
  2. Add logger handler:

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            # all other handlers
            'log_file_yapi': {
                'level': 'DEBUG',
                'class': 'logging.handlers.RotatingFileHandler',
                'filename': os.path.join(os.path.join(os.path.dirname( __file__ ), '..'), 'logs/yapi.log'),
                'maxBytes': '16777216', # 16megabytes
             },
        },
        'loggers': {
            # all other loggers
            'yapi': {
                'handlers': ['log_file_yapi'],
                'propagate': True,
                'level': 'DEBUG',
            }
        }
    }
  3. Make sure you have a ‘HOST_URL’ setting containing the address in which the app is deployed:

    HOST_URL = 'http://localhost:8000' (example)

  4. If you want to use the middleware that enables CORS, you have to configure it by adding the following settings:

    MIDDLEWARE_CLASSES = (
        ...
        'yapi.middleware.XsSharing'
    )
    
    YAPI = {
        ...
    
        'XS_SHARING_ALLOWED_ORIGINS': '*', # The allowed domains
        'XS_SHARING_ALLOWED_METHODS': ['POST','GET','OPTIONS', 'PUT', 'DELETE'] # ... and methods
    }
  5. If you wish to disable ENTIRELY (be careful! :P) CSRF Validation, add the following settings:

    YAPI = {
        ...
    
        'CSRFValidation': False
    }

Logs

Create a ‘logs’ folder in your project’s root folder (if you don’t have one already). Your project folder should look something like this:

myproject/
    __init__.py
    settings.py
    urls.py
    wsgi.py
logs/
manage.py

Database

Run python manage.py syncdb to create the yapi models.

API Namespace

Now, you will have to decide the URL “namespace” from which your API will be available and add it to your top-level urls.py.

Yapi’s convention is that everything that regards to the API (urls, handlers, serializers, etc) should be in a package named “api” inside the respective app package. This way, the API namespace should point to a urls.py inside an “api” package in your project’s main package:

myapp/
    api/
        __init__.py
        handlers.py
        serializers.py
        urls.py
    __init__.py
    models.py
    urls.py
    views.py
myproject/
    api/
        __init__.py
        urls.py
    __init__.py
    settings.py
    urls.py
    wsgi.py
logs/
manage.py

Add namespace to the top-level urls.py:

# myproject/urls.py
# ============

urlpatterns = patterns('',
   # all other url mappings
   url(r'^api', include('myproject.api.urls', namespace='api')),
)

In this example, we have an app called “myapp” which has an API. In order for it to be “acessible”, its URLs must be added to the top-level API namespace:

# myproject/api/urls.py
# ============

urlpatterns = patterns('',
    # all other api url mappings
    url(r'^/myapp', include('myapp.api.urls', namespace='myapp')),
)

Resources

A “Resource” maps an URL to the code that will handle the requests made to it.

By convention, Resource handlers reside in a file called handlers.py in the api package:

# myapp/api/handlers.py
# ============

    from yapi.resource import Resource
from yapi.response import HTTPStatus, Response

class ResourceIndex(Resource):
    """
    API endpoint handler.
    """

    # HTTP methods allowed.
    allowed_methods = ['GET']

    def get(self, request):
        """
        Process GET request.
        """

        # Return.
        return Response(request=request,
                        data={ 'hello': 'world' },
                        serializer=None,
                        status=HTTPStatus.SUCCESS_200_OK)

Now we map the handler to a given URL:

# myapp/api/urls.py
# ============

from django.conf.urls import patterns, url
from yapi.resource import Resource

from handlers import ResourceIndex

urlpatterns = patterns('',
    url(r'^/?$', ResourceIndex.as_view(), name='index'),
)

This way, if put http://localhost:8000/api/myapp in the address bar of your browser, you should get a JSON object in return containing { 'hello': 'world' }.

Basic Schema

From the example above we can see how easy it is to write a Resource class. You just need to set the allowed_methods array with the HTTP verbs that the handler supports and then, for each allowed verb, write the respective method.

Yapi’s convention is to use POST/GET/PUT/DELETE to CREATE/READ/UPDATE/DELETE.

  • POST -> def post(request)

  • GET -> def get(request)

  • PUT -> def put(request)

  • DELETE -> def delete(request)

IMPORTANT: In this example there isn’t any additional value being passed by the URL, therefore the only data received by the methods is the standard Django request. Make sure to include in the method any other additional parameter that may be passed by the URL.

Authentication & Authorization

If the resource should only be accessible via authenticated users, then a variable authentication should be set with an array of the valid authentication types. Yapi ships with the following authentication methods:

  • yapi.authentication.SessionAuthentication -> Validates if the request is made by a browser with a valid Django session (i.e. user is logged in to the site)

  • yapi.authentication.ApiKeyAuthentication -> Validates if the request is made with a valid api_key provided as a GET parameter.

When several authentication methods are accepted, the request is considered authenticated as soon as one checks (e.g. SessionAuthentication fails, but APIKeyAuthentication validates). If the user is authenticated, it is added to the request object and can be accessed by request.auth['user'].

If the resource should only be acessible by authenticated users that match a specifc ruleset, then permissions should be set with an array of all the authorization credentials required. Yapi ships with the following authorization methods:

  • yapi.permissions.IsStaff -> Checks if user has Staff permission.

In order for the authorization to be validated all authorization classes must check.

If we wanted to make the Resource in the example above only available to authenticated staff users, it would look something like this:

# myapp/api/handlers.py
# ============

from yapi.authentication import SessionAuthentication, ApiKeyAuthentication
from yapi.resource import Resource
from yapi.response import HTTPStatus, Response

class ResourceIndex(Resource):
    """
    API endpoint handler.
    """

    # HTTP methods allowed.
    allowed_methods = ['GET']

    # Authentication & Authorization.
    authentication = [SessionAuthentication, ApiKeyAuthentication]
    permissions = [IsStaff]

    def get(self, request):
        """
        Process GET request.
        """

        # Return.
        return Response(request=request,
                        data={ 'hello': 'world' },
                        serializer=None,
                        status=HTTPStatus.SUCCESS_200_OK)

Request Body

When the request is a POST or a PUT, it is assumed that there is a request body and, if it isn’t present or fails parsing, the request fails.

IMPORTANT Currently, the only format accepted for the request body is a JSON payload.

The request body is parsed into a native Python dict and can be acessible in request.data.

Resource Listing

In trying to follow some HATEOAS principles, we suggest that the API’s root URL should return a listing of the available resources and respective URLs:

# myproject/api/resources.py
# ============

from django.conf import settings
from django.core.urlresolvers import reverse

def get_api_resources_list(user):
    return {
        'url': settings.HOST_URL + reverse('api:index'),
        'resources': {
            'myapp': {
                'url': settings.HOST_URL + reverse('api:myapp:index')
            }
        }
    }

Now, add it to the API’s root URL:

# myproject/api/urls.py
# ============

urlpatterns = patterns('',
    # all other api url mappings
    url(r'^/?$', ResourcesListHandler.as_view(), name='index'),
    url(r'^/myapp', include('myapp.api.urls', namespace='myapp')),
)

And write the respective handler:

# myproject/api/handlers.py
# ============

    from yapi.resource import Resource
from yapi.response import HTTPStatus, Response
from resources import get_api_resources_list

class ResourcesListHandler(Resource):
    """
    API endpoint handler.
    """

    # HTTP methods allowed.
    allowed_methods = ['GET']

    def get(self, request):
        """
        Process GET request.
        """

        # Return.
        return Response(request=request,
                        data=get_api_resources_list(request.auth['user']),
                        serializer=None,
                        status=HTTPStatus.SUCCESS_200_OK)

Don’t forget to keep this list updated everytime you make changes to your resources.

Response

yapi.response.Response is the prefered way of returning a call to a given handler (Django’s HTTPResponse also works)

  • request -> The request that originated this response.

  • data -> The raw response data (a Python dict, with all data in native types)

  • serializer -> The serializer that will be used to serialize the data.

  • status -> The HTTP status code of the response (preferably from yapi.response.HTTPStatus)

  • pagination (optional) -> When the response data is a QuerySet, this states if the response should be paginated or not. Default is True.

  • filters (optional) -> When the response data is a QuerySet and it was filtered by given parameters, they are provided in this field.

Serializers

When the response data is a complex Python object, it must first be serialized to native Python types. This way, each for every resource that may be returned, a serializer that implements yapi.serializers.BaseSerializer must be written.

Basically, a to_simple(self, obj, user=None) method has to be implemented.

  • obj -> The object instance that will be serialized.

  • user (optional) -> The user that made the request. This is useful when the instance representation varies according to the user/permissions.

Lets look at an example for serializing a user:

from apps.api.serializers import BaseSerializer

class UserSerializer(BaseSerializer):
    """
    Adds methods required for instance serialization.
    """

    def to_simple(self, obj, user=None):
        """
        Please refer to the interface documentation.
        """
        # Build response.
        simple = {
            'email': obj.email,
            'name': obj.name,
            'last_login': obj.last_login.strftime("%Y-%m-%d %H:%M:%S")
        }

        # Return.
        return simple

In this case, an example response could be:

return Response(request=request,
                data=request.auth['user'],
                serializer=UserSerializer,
                status=HTTPStatus.SUCCESS_200_OK)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-yapi-0.3.4.tar.gz (14.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page