polymicro 0.2.0
PolyMicro is a Model Based Rest API system written around PynamoDD and Marshmallow for flask

# PolyMicro

[![Build Status](https://travis-ci.org/resilient-vitality/PolyMicro.svg?branch=master)](https://travis-ci.org/resilient-vitality/PolyMicro)
[![Requirements Status](https://requires.io/github/resilient-vitality/PolyMicro/requirements.svg?branch=master)](https://requires.io/github/resilient-vitality/PolyMicro/requirements/?branch=master)
[![codecov](https://codecov.io/gh/resilient-vitality/PolyMicro/branch/master/graph/badge.svg)](https://codecov.io/gh/resilient-vitality/PolyMicro)

PolyMicro is a Model Based Rest API system written around `PynamoDB` and `Marshmallow` for 
request and response marshalling and designed to work interchangeably with various different 
deployments.

## Installation
Installation is simple. 
```
pip install polymicro
```

See our example below for how easy it is to get up and runnning with polymicro.

## Road Map
In the future we would like to bring the following features to PolyMicro.

* Swappable Data Backends (For instance, supporting SQL Alchemy, etc.)
* Configurable Response Types (Serializing to things other than json)


## Example
It is as simple as one... two.... three.... (four).

#### Step 1: Create your PolyMicro app
```python
from flask import Flask
from polymicro import PolyMicro

def get_request_user(self):
    # Get the user however you want. If there is no user for the request, return None
    return 'abc123'

app = Flask(__name__)
pm = PolyMicro(app, req_user_func=get_request_user)
```

This step revolves around setting the basics of the application. You need to create a flask app as 
you would do in any other flask application. Then we are going to create a function that tells 
PolyMicro how to get user information from the given request and pass that into the creation of our polymicro app.


#### Step 2: Create your PynamoDB Model
```python
from datetime import datetime
from uuid import uuid4
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute, BooleanAttribute, UTCDateTimeAttribute

class Todo(Model):
    class Meta:
        table_name = 'todos'

    owner = UnicodeAttribute(hash_key=True)
    uuid = UnicodeAttribute(range_key=True, default=lambda: str(uuid4()))
    creation_date_time = UTCDateTimeAttribute(default=datetime.utcnow)

    text = UnicodeAttribute()
    done = BooleanAttribute(default=False)
    due_date_time = UTCDateTimeAttribute(null=True)
```
PynamoDB is a tool to write models in DynamoDB similar to an ORM. We use it to define information.

#### Step 3: Define Permissions (Optional)
```python
from polymicro.permissions import Permission

class TodoPermission(Permission):
    def has_object_permission(self, user, method: str, instance):
        return instance.owner == user.uuid

    def has_permission(self, user, method: str):
        return 'administrator' in user.permissions or 'therapist' in user.permissions
```

Permissions are simple sets of rules from which you can define weather or not a user can access a particular 
resource and object. Return false if the permission proves the user cannot use the object or the method and true
otherwise.

#### Step 4: Define the Resource
```python
from polymicro import ModelResource, Create, List, Retrieve, Update, Delete

class TodoResource(ModelResource, Create, List, Retrieve, Update, Delete):
    model = Todo
    route = 'todo'
    permissions = (TodoPermission)
    dump_only = ('creation_date_time', 'owner', 'uuid')
    required = ('text', )

    def list(self, user, pagination_key=None, limit=25):
        return Todo.query(user.uuid, last_evaluated_key=pagination_key, limit=limit)

    def create(self, req: dict, user):
        req['owner'] = user.uuid
        return super().create(req, user)
            
TodoResource(pm)
```

We can define resources by composition. So first, define a class that extends from `ModelResource`. This class
will form the base of the functionality that we will want to implement. Next, extending from the `Create`, `List`,
`Retrieve`, `Update`, and `Delete` class will implement standard versions of those functions. You can also override the 
function.

These functions can return several different things.

*One of these*:
* A single or collection of your model.
* Anything that is JSON serializable 

And optionally these:
* A HTTP status code as an integer.
* A string -> string dictionary representing extra headers.
* For the list operation, a 4th item that allows for a pagination key to be returned in the response for pagination.

Below are some examples:

```python
# Returning all three.
def retrieve(self, instance, user):
    return instance, 418, {'Extra': 'Something-Super-Secret'}

# Returning instance and status code
def retrieve(self, instance, user):
    return instance, 418
    
# Return just the single instance.
def retrieve(self, instance, user):
    return instance
    
# The list operation allows for a 4th item that allows for a pagination key that can be returned back later.
def list(self, user, pagination_key=None, limit=25):
    items = Todo.query(user.uuid, last_evaluated_key=pagination_key, limit=limit)
    return items, 418, {}, 'some-key'
```