# flask-restless-client

[![Build Status](https://travis-ci.com/maarten-dp/flask-restless-client.svg?branch=master)](https://travis-ci.com/maarten-dp/flask-restless-client)
[![Codecov](https://codecov.io/gh/maarten-dp/flask-restless-client/branch/master/graph/badge.svg)](https://codecov.io/gh/maarten-dp/flask-restless-client)
[![PyPI](https://badge.fury.io/py/flask-restless-client.svg)](https://pypi.python.org/pypi/flask-restless-client/)

## Intoduction

The flask-restless-client is the second part of a two part library. The first being the [flask-restless-datamodel](https://github.com/maarten-dp/flask-restless-datamodel/tree/master). Together with the flask-restless-datamodel, this library serves as a goal to provide a CRUD/RPC client for Flask/SQLAlchemy applications, over HTTP using flask-restless.

Taking advantage of the easy integration offered by [Flask-Restless](https://github.com/jfinkels/flask-restless) to expose a REST CRUD interface over HTTP, this library uses its power to provide a dynamic python client. Reading the data format generated by the flask-restless-datamodel, the restless-client is able to build itself and provide you with objects that aim to mirror an SQLAlchemy-like interface on the client side.

This includes RPC possibilities to run object methods of the SQLA models defined server-side. Some setup is required to achieve a smooth interaction with the RPC part of this library.

The developer will be required to:
- write serializers from and to python natives to transfer complex objects to the server
- overwrite authentication method if the chosen authentication method is not supported by this library.

As such, it's advised to use this library as a base for a custom client for your application.
Most likely, it will be a thin layer on top of the flask-restless-client setting up some configuration.


## Installation

``` bash
    pip install flask-restless-client
```

## Setting up the client

### Exposing your model server side

The first step is to enable the flask-restless-datamodel on the server side.
You can visit [flask-restless-datamodel](https://github.com/maarten-dp/flask-restless-datamodel/tree/master) to see how to do this.

### Authenticating

As this library is intented to be useable out of the box, some built in authentication is provided.
Current out of the box authentication types are Bearer and Basic Authentication.

By default, the client will use the Bearer session, but the Basic Authentication session is importable from `restless_client.ext.auth`.

You are also able to give your own (pre-authenticated) session as a parameter when initializing the client.

Environment variables can be set to speed up authentication setup. Using the prefix `RESTLESS_CLIENT_` you can set anything involving authentication, including setting which type of session to use.

### (De)Serialization of complex objects

Part of supporting an RPC-like client is making sure the objects arrive at their destination in the same way they are sent from the source. We all know deserialisation isn't always true to what you initially put in. Therefore you can register your own (de)serializer for complex objects.

```python
from cereal_lazer import register_class
import pandas as pd

register_class(
    'DataFrame', # Register the object as this name
    pd.DataFrame, # Register the class
    lambda x: x.to_json(), # Register a serializer
    lambda x: pd.read_json(x) # Register a deserializer
)
```

These objects are registered in a global context using the `cereal_lazer` library. The client is then using the library to (de)serialize.

## Using the client


It's important tot re-iterate on the fact that this is a self-building client. That means the way you interract with this client depends on external input.
To have a practical example, consider the following SQLA models defined server-side:

```python
class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)
    birth_date = db.Column(db.Date)

class Computer(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)
    vendor = db.Column(db.Unicode)
    purchase_time = db.Column(db.DateTime)
    owner_id = db.Column(db.Integer, db.ForeignKey('person.id'))
    owner = db.relationship(
        'Person', backref=db.backref('computers', lazy='dynamic'))
    owner_name = association_proxy('owner', 'name')
    peers = association_proxy('owner', 'computers')
```

Based on these models, flask-restless-datamodel will generate the input for the client to build itself, allowing for an SQLA-like interface.

if we hook up our client to our server app, we'll be able to do all neat kinds of stuff

### C is for Crayon

Now that we have some server side models exposed, the models will be available on the client side and we can jump right in and create some objects.

```python
from restless_client import Client

c = Client(url='http://localhost:5000/api')
maurice = c.Person(name='Maurice')
roy = c.Person(name='Roy')

beast = c.Computer(name='TheBeast', vendor='Pear', owner=maurice)
server = c.Computer(name='Server', vendor='Pingu', owner=maurice)
pc = c.Computer(name='pc', vendor='Doors', owner=roy)

# Save objects on the server
c.save()

# Alternatively, you can save on a per-instance basis
beast.save()
```
Note that if we disregard the `c.save()` statement, and run `beast.save()` instead, that the `maurice` instance is a dependency of `beast.owner` and will be unsaved at the time we call `beast.save()`.
The client should be able to resolve these unsaved dependencies and will save them first

### R is for Rainbow

Loading objects can be done in serveral ways. The object models have a `query` attribute that is accessible to perform all read operations

#### Getting all instances from a given class

```python
everyone = c.Person.query.all()
```

#### Getting an instance based on the id

```python
maurice = c.Person.query.get(1)
```

#### Shorthand for all/get

Due to `all` and `get` being often used methods, they have been enabled with a shorthand on the object model itself

```python
everyone = c.Person.all()
maurice = c.Person.get(1)
```

#### Filtering

```python
maurice = c.Person.query.filter(c.Person.name == 'Maurice')
maurice = c.Person.query.filter_by(name='Maurice')
# limit the results to 3
some_people = c.Person.query.limit(3).all()
# offset results, ignoring the first 2
some_people = c.Person.query.offset(2).all()
# order by name
everyone = c.Person.query.order_by(name='asc').all()
# get the first instance
maurice = c.Person.query.first()
# get the last instance
maurice = c.Person.query.last()
# expect only one result
maurice = c.Person.query.one()
# expect only one result, or no result
maurice = c.Person.query.one_or_none()

# filtering over relations, get all people that own a computer with Pear vendor
maurice = c.Person.query.filter(c.Person.computers.has_(c.Computer.vendor == 'Pear'))
```

### U is for you and me

Updating is just as easy as creating objects. The library is built in a way that it flags dirty attributes, and only sends the necessary data to the server.

```python
cmptr = c.Computer.query.one()
cmptr.vendor = 'Robot'
cmptr.save()
```


### D is for ... delete

```python
cmptr = c.Computer.query.one()
cmptr.delete()
```

Note that executing `delete` is instant, and calling the save is not needed.

## Running remote object methods

As promised, this library provides an RPC-like feature that allows you to run the methods defined on your SQLA models. It's nearly nowhere as advanced as other RPCs out there, but it at least provides a way to emulate the interaction on models as if you were working with them on a server context.

The sending and receiving of complex objects does require some setup, but once this is done, doing remote method calls should run smoothly. (Although there are plenty of scenarios where remote execution might fail).

Anyway here's wonderwall

On the server we would define a model with the following method
```python
class Person(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Unicode, unique=True)

    def speak(self, what_to_say):
        return "Errr... uh... ehm... {}?".format(what_to_say)
```

Which we can then remotely run by doing
```python
maurice = c.Person.query.filter(c.Person.name == 'Maurice')
print(maurice.speak("I'd rather send an email"))
```

### Future plans for running remote methods

Currently the client will crash if it tries to (de)serialize a complex object that is not yet registered.
Going forward, it would be desired to apply a "no-crash" policy. The idea behind this is that the data is there, and it's not because (de)serialization failed, that the program should halt execution.

If a (de)serializer was not registered for a complex object, one will be emulated from the data available. Accessing data that is known to it will allow you to interact with it without issue, accessing functions or data that is unknown to the emulated object will result in an exception.
