Let’s consider a simple web game tictactoe that has the following resources:
Create a pyramid application using pcreate -t starter tictactoe.You will now have basic structure with some files and __init__.py among them. Remove all the lines in main except the first and last two, leaving only config object, scan and return statement.
In your Pyramid project’s __init__.py add the following:
config.include('yarhp') root = config.get_root_resource() user = root.add('user', 'users', model='yarhp.NoModel') user.add('score', 'scores', model='yarhp.NoModel')Obviously you have to have yarhp pip’d.
Create view files for each resource. By default, yarhp expects view file for each resource under your_project/views module. In our case we create the following files with following content:
tictactoe/views/users.py
from yarhp.view import BaseView class UsersView(BaseView): def index(self): return []tictactoe/views/user_scores.py
from yarhp.view import BaseView class UserScoresView(BaseView): def index(self, user_id): return [] def show(self, user_id, id): return 'show score %s for user %s ' % (id, user_id)Notice the class naming is in plural form (the collection name of the resource) followed by View in CamelCase. This would be different for models in the examples bellow. Models use the Capitalized singular form.
You should get a json response that looks like {"total": 0, "users": []}. Similarly you can navigate to http://localhost:6543/users/100/scores and should get the same empty result.
In the previous section the application was bare minimum. Now lets have some persistence.
Yarhp expects that each resource has by default its model under project/model module. (dont forget to create the __init__.py file to make the folder a python module.)
tictactoe/model/user.py
import shelve
class User(object):
db = shelve.open('user')
def __init__(self, **kw):
self.__dict__.update(kw)
def save(self):
self.db[self.id] = self.__dict__
return self
tictactoe/model/user_score.py
import shelve
class UserScore(object):
db = shelve.open('user_score')
def __init__(self, **kw):
self.__dict__.update(kw)
def save(self):
#key is "composite"
self.db['%s_%s' % (self.user['id'], self.id)] = self.__dict__
return self
In project’s __init__.py file remove model='yarhp.NoModel', since we have models now. Add some fixtures in the main like so:
#test data user1 = User(id='1', name='Bob').save() user2 = User(id='2', name='Alice').save() UserScore(id='1', user=user1.__dict__, score='100').save() UserScore(id='2', user=user1.__dict__, score='200').save() UserScore(id='3', user=user1.__dict__, score='300').save() UserScore(id='1', user=user1.__dict__, score='1000').save() UserScore(id='2', user=user2.__dict__, score='2000').save()
The views now look little different too:
from yarhp.view import BaseView from tictactoe.model.user import User class UsersView(BaseView): def index(self): return User.db.values() def show(self, id): return User.db[id]and
from yarhp.view import BaseView from tictactoe.model.user_score import UserScore class UserScoresView(BaseView): def index(self, user_id): return [score for score in UserScore.db.values() if score['user']['id'] == user_id] def show(self, user_id, id): return UserScore.db["%s_%s" % (user_id, id)]
Run the server and check in the browser the resources if they work.
The model files can be manually specified in __init__.py file during the declaration of the resource if its not the default location:
user.add('score', 'scores', model='tictactoe.custom_model.MyFunkyScoreModel')
Same goes for the view files as well. view param passed to add method will do the trick. In both cases you can pass either dotted path to the model class or the class object itself, obviously importing it first.
Basic idea behind validators is to, well, validate. In yarhp you can either use built-in validators or write your own to validate inputs coming from the request as well as output going into to response.
Lets assume we want to validate the creation of the user resource.
from yarhp.wrappers import validator class UserView(BaseView): @validator(name={'type':str, 'required':True}, age={'type':int, 'required':False}, gender={'type':str, 'required':False}) def create(self): User(name=self.request.params['name'], gender=self.request.params.get('gender')) return HTTPCreated()
As you can see, validator decorator does 2 things. It forces required fields to be present in the request and also checks for the types. In the example above, name is required field and it must have a type str, age is an int and required, gender is a str but could be ommited.
In case the model is based on SQL Alchemy (or Elixir for that matter), you have an option of deriving your view from yarhp.sqla.SQLAView class, which will do bunch of validations for you and also implement basic behaviours of all actions. You, of course, can define your own action in your view. Validation with SQLAView works by checking your sql schema and validating your input against that. For example if you have the following elixir entity for User model:
class User(Entity): using_options(tablename='user', identity='1', inheritance='multi') id = Field(BigInteger, primary_key=True, autoincrement=False) name = Field(Unicode(128), required=True) gender = Field(Unicode(10), required=False) age = Field(Integer, required=False)
And the following view:
class UserView(SQLAView): ... def create(self): User(**self.request.params).save() return HTTPCreated()
The create method will be validated against your model defined in User automatically. That validation would be equivalent of the validator decorator explained in the pervious section.
Since user resource is a simple root resource, you could just skip the whole view all together and yarhp would implement all actions and validations for you. In case of nested resources, yarhp is still in experimental phase.