.. _start: =========================== Getting Started with YARHP =========================== Basic layout ------------ Let's consider a simple web game `tictactoe` that has the following resources: * /users * /users/{id}/scores #. 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: .. code-block:: python 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`` .. code-block:: python from yarhp.view import BaseView class UsersView(BaseView): def index(self): return [] ``tictactoe/views/user_scores.py`` .. code-block:: python 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. #. Run ``pserve development.ini`` in the root of the project and point your browser to ``http://localhost:6543/users``. 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. Introducing Models ------------------ 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.) #. Create models for each resource. Note that in the case of models, names are in singular form, not plural as oppose to the views. ``tictactoe/model/user.py`` .. code-block:: python 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`` .. code-block:: python 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: .. code-block:: python #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: .. code-block:: python 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 .. code-block:: python 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: .. code-block:: python 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. Validators ---------- 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. .. code-block:: python 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. Automagic Validation -------------------- 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: .. code-block:: python 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: .. code-block:: python 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.