Features

Easy APIs

Create a model and then decorated it with the endpoint decorator.

from ray.endpoint import endpoint
from ray_sqlalchemy import AlchemyModel
from sqlalchemy import Column, Integer, String

Base = declarative_base()

@endpoint('/user')
class UserModel(AlchemyModel):
    name = StringProperty()
    age = IntegerProperty()

Now, you have the http methods to interact with your model using the urls:

HTTP Verb Path Description
GET /api/user List all users
GET /api/user/{id} Get one user
POST /api/user Create an user
PUT /api/user/{id} Update an user
DELETE /api/user/{id} Delete an user

Also, Ray provides a simple way to your search for records. Check it how to do it:

curl -X GET "http://localhost:8081/api/user?name=john&age=40"

By the default, all columns of your Model can be used to filter.

Hooks

Hooks are really useful to add validations in different moments of your application. Hook is a class that connect with your model and will be executed before save the model, after the model be saved or before the model be deleted.

from ray.hooks import DatabaseHook

class AgeValidationHook(DatabaseHook):

    def before_save(self, user):
        if user.age < 18:
            raise Exception('The user must have more than 18 years')
        return True

@endpoint('/user')
class UserModel(AlchemyModel):
    hooks = [AgeValidationHook]

    name = StringProperty()
    age = IntegerProperty()

Available Hooks:

Then, if you call the .put() method of UserModel and the user doesn't has age bigger than 18, an Exception will be raised.

Actions

Actions provide a simple way to you create behavior in your models through your api. After writing the code bellow, you can use the url /api/user/<user_id>/activate to invoke the activate_user method.

When you create an action method, the parameters are:

As an example:

When the url /api/user/1/activate?name=john is called with GET. The parameters will be: model_id = 1 and parameters a dict {'name': 'john'}.

from ray.actions import Action, action

class ActionUser(Action):
    __model__ = UserModel

    @action("/activate")
    def activate_user(self, model_id, parameters):
        user = storage.get(UserModel, model_id)
        user.activate = True
        storage.put(user)

Action with Authentication

If you wanna that the Actions method can only be called when the user is authenticated, use the authentication=True parameters in the @action decorator.

Example:

from ray.actions import Action, action

class ActionUser(Action):
    __model__ = UserModel

    @action("/activate", authentication=True)
    def activate_user(self, model_id, parameters):
        pass

Authentication

Ray has a built-in authentication module. To use it, you just need to inherit the Authentication class and implement the method authenticate and the method salt_key. In the authenticate method, you'll check the data in the database and then return if the user can login or not. Remember that this method must return a dictionary if the authentication succeeded. PS: You can create one (and just one) class that inherit from the Authentication class. In the salt_key method you should return a salt string used to compose the authentication token.

Also, the Authentication expects that you implement:

from ray.authentication import Authentication, register

@register
class MyAuth(Authentication):

    expiration_time = 5  # in minutes

    @classmethod
    def authenticate(cls, login_data):
        user = User.query(User.username == login_data['username'],
                          User.password == login_data['password']).one()
        return {'username': 'ray'} if user else None

    @classmethod
    def salt_key(cls):
        return 'salt_key'

If you want protect all the operations in this endpoint, you can just add this:

@endpoint('/person', authentication=MyAuth)
class PersonModel(ModelInterface):
    pass

After protect your endpoint via an Authentication, you will need to be logged in to don't get a 403 status code. To do this:

Login

curl -X POST -H "Content-Type: application/json" '{"username": "username", "password": "password"}' "http://localhost:8080/api/_login"

Logout

curl -X GET "http://localhost:8080/api/_logout"

Shields

Ray has an option to you protect just some HTTP methods of your endpoint: using Shields. How it works? You inherit from the Shield class and implement just the http method that you want to protect.

class PersonShield(Shield):
    __model__ = PersonModel

    def get(self, user_data, model_id, parameters):
        return user_data['profile'] == 'admin'

    # def put(self, user_data, model_id, parameters): pass

    # def post(self, user_data, model_id, parameters): pass

    # def delete(self, user_data, model_id, parameters): pass

This shield protects the GET method of /api/person. The parameter user_data in the get method on the shield, is the dictionary returned on your Authentication class. So, all Shields's methods receive this parameter. When you overwrite a method, Ray will assume that method is under that Shield protection.

Shields with Actions

If you wanna to protect an action you can do this with a Shield. To do this, you just need to implement a @staticmethod method in your Shield, that doesnt has one of these names: get, delete, post or put. If this Action is not used by an authenticated user, the parameter user_data in your Shield's method will be None. The parameters user_id and model_id follow the same rule that the Actions.


class UserShield(Shield):
    __model__ = UserModel

    @staticmethod
    def protect_enable(user_data, model_id, parameters):
        return user_data['profile'] == 'admin'


class ActionUser(Action):
    __model__ = UserModel

    @action('/enable', protection=UserShield.protect_enable)
    def enable_user(self, model_id, parameters):
        user = session.get_user()
        user.enabled = True
        user.save()

Enable cross domain calls

Ray doesn't has its own way to enable cross-domain, however it's easy to do it by your self. You just need to implement a bottle plugin just like that:

from bottle import response, request

class EnableCors(object):
    def apply(self, function, context):
        def __enable_cors(*args, **kwargs):
            response.headers['Access-Control-Allow-Origin'] = '*'
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, DELETE, PUT, OPTIONS'
            response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
            return function(*args, **kwargs)
        return __enable_cors

After that, you just need to use it in your app:

from ray.wsgi.wsgi import application
application.install(EnableCors())
application.run()

And it's done!

Serving static files

Ray uses bottle under the hood, so you can use bottle to serve your static files. However, this is only for development enviroment. In production enviroment, you should use nginx, apache or something like that to serve your static files.

Serve your static files just adding:

from bottle import static_file
@application.route('/static/<filepath:path>')
def server_static(filepath):
    return static_file(filepath, root='static')

Running the application

To run a Ray application, you just need to import from ray.wsgi.wsgi import application and then, run:

python app.py # the name of the file which imported the application variable