.. _tutorial:

taskman --- A tutorial
======================

Our tutorial is a simple API that lets you create tasks, edit them, and 
mark them as finished.

The basic application
---------------------

Let's go ahead and create our app and declare our resources. They won't do 
anything for now, but will help us get a firm idea of how the API is laid out.

Add the following to a file and save it as *taskman.py*

::

    from findig.json import App

    app = App()

    @app.route("/tasks/<id>")
    @app.resource
    def task(id):
        return {}

    @app.route("/tasks/")
    @task.collection
    def tasks():
        return []

We want our API to serve JSON resources, so we've imported and initialized
:class:`findig.json.App`. We could've supported a different data format, but 
we'd have to write the code to convert the resources ourselves, since Findig
only includes converters for JSON. For this tutorial we'll just stick to JSON
(it's pretty darn good!).

Next, we declare a resource called ``task``. This resource is going to 
represent a single task, and provide an interface for us to delete and update
individual tasks. There's quite a bit going on here:

* ``@app.route("/tasks/<id>")`` assigns a URL rule to our task resource.
  The URL rule tells findig what sort of URLs should route to our resource. 
  The second part of the rule is interesting; by enclosing ``id`` in angle
  brackets, we've created a variable part of the URL rule. When matching a
  URL, Findig will match the static part of the rule exactly, but try to match
  other parts of the URL to the variables. So for example,
  ``/tasks/foo`` and ``/tasks/38`` will route to this resource, using ``'foo'``
  and ``'38'`` as the id (variable part) respectively. This kinda important, 
  because we can use just one resource to group all of our tasks together, but 
  use the variable part URL to identify the exact task that we're referring to.
  By the way, here's the documentation for 
  :meth:`app.route <findig.dispatcher.Dispatcher.route>`.

* ``@app.resource`` declares a resource. 
  :meth:`app.resource <findig.dispatcher.Dispatcher.resource>` wraps a
  resource function and returns a :class:`findig.resource.Resource`. This
  means that in our code, ``task`` will be replaced by a Resource object
  (Resources act like functions, so you can still call it if you 
  want to).

* ``task(id)`` is our resource function for a task. Notice that it takes an argument
  called ``id``. This is because of our URL rule; for every variable part in a
  URL rule, a resource function must accept a named argument (keyword arguments
  work too) that matches the variable part's name. So when task is called by 
  Findig, it will be called with an id that's extracted from the URL. Resource
  functions are supposed to return the resource's data (in other words, what
  should be sent back on a GET request). Our resource function returns an empty 
  dictionary. That's because the JSON converters know how to work with 
  dictionaries; if we were to send a GET request to any task right now 
  (example: ``GET /tasks/foo``) we would receive an empty JSON object as the
  response.

The next block declares our ``tasks`` collection. We'll use it to get a list
of all of our tasks, and to add new tasks to that list. Bit by bit, here's
what is going on in that code:

* ``@app.route("/tasks/")`` should be familiar. We're once again assigning a URL
  rule, only this time there are no variable parts so it will match only one
  URL (``/tasks/``).

* ``@task.collection`` declares tasks as a collection containing ``task``
  resource instances. Remember how ``app.resource`` turned our resource function
  into a :class:`~findig.resource.Resource`? Well that's where the collection
  function comes from. Here, :meth:`~findig.resource.Resource.collection` wraps a 
  resource function and turns it into a :class:`~findig.resource.Collection` 
  (which are callable, just like a Resource---in fact, they *are* Resources).

* ``tasks()`` is our resource function for the tasks collection. We return an
  empty list because the JSON converters know how to turn an iterable into a
  JSON list; if we sent ``GET /tasks`` to our API, we'd get an empty JSON list
  as the response.

Serving it up
-------------

If we were deploying a production application, we'd be using a web server like
Apache to serve up our API. But since this is a measly tutorial, we can get
away with using Werkzeug's built-in development server (see that 'development'
in front of server? It means you should only use it during development ;)).

Go ahead and add this to the bottom of *taskman.py*::

    if __name__ == '__main__':
        from werkzeug.serving import run_simple
        run_simple("localhost", 5000, app, use_reloader=True, use_debugger=False)

This serves up our application on port 5000 on the local machine. 
``use_reloader=True`` is a handy setting that reloads the application anytime
your change the source file. You might be tempted to set ``use_debugger=True``,
but don't; we set it to ``False`` (the default) deliberately to make the point
that since the werkzeug debugger is tailored for HTML, it is almost certainly 
useless for debugging a Findig app.

Adding our data models
----------------------

We're going to need to store our tasks somewhere. Findig uses data models to
figure out how to interface with stored resource data. This section is a little
long-winded, because it presents the roundabout way of declaring models, and then
promptly throws all of that away and uses a shorter method instead (don't
hate, okay? This is still a tutorial, so it's important for you to grasp the
underlying concepts).

Explicit data models
~~~~~~~~~~~~~~~~~~~~

We can declare data model functions to instruct Findig on how to access stored
data for *each* resource. Whenever we do that, we're using explicit data models.
That's what we'll cover in this section.

We won't use any of the code we add in this section in our final application,
but it's important that we go through it anyway so that you grasp the 
underlying concepts. If you don't care for any of that, you can probably
skip ahead to :ref:`data-sets-tut` (but don't blame me if you don't understand
how they work!).

Let's start with the ``task`` resource. Remember that we want to use that
resource to update and delete individual tasks. Add this code to *taskman.py*

::

    TASKS = []

    @task.model("write")
    def write_task(data):
        TASKS[task.id] = data

    @task.model("delete")
    def delete_task():
        del TASKS[task.id]

``TASKS = []`` sets up a global module-level list that tracks all of our tasks.
Since this is throwaway code anyway, there's no harm in storing our tasks in
memory like this; it'll never really get used! Were this a production application,
then you would be fully expected to use a more responsible data storage backend.
And if you didn't, well, you'd just have to face the consequences, wouldn't you?

Now, the first interesting thing happening here is the ``@task.model("write")``
declaration. This is declaring a write_tasks as a function that can write new data
for a specific task. It gets passed a mapping of fields, directly converted
from data send by the requesting client. The next interesting thing is
``task.id``. During a request to our task resource, ``task.id`` will bind to
the value of the ``id`` URL variable.

.. tip:: Anytime a URL rule with variable parts is used to route to a resource,
   Findig binds the values of those variables to the resource for the duration
   of the request. This binding is completely context safe, meaning that even
   when requests are running on multiple threads, ``{resource}.{var}`` will always
   bind to the correct value.

Similarly, ``task.model("delete")`` declares delete_task as a function that deletes
a task. Delete model functions don't take any arguments.

Whenever we introduce model functions, Findig will usually enable additional
request methods which correspond somewhat to the model functions. This table
gives the model functions, their signatures, and corresponding request methods:

=============================== ==============          ====================================
Model function                  Request method          Supported resource type
=============================== ==============          ====================================
``write(data:mapping)``         PUT                     :class:`~findig.resource.Resource`
------------------------------- --------------          ------------------------------------
``delete()``                    DELETE                  :class:`~findig.resource.Resource`
------------------------------- --------------          ------------------------------------
``make(data:mapping) -> token`` POST                    :class:`~findig.resource.Collection`
=============================== ==============          ====================================

You may notice that a "make" model is to be attached to a 
:class:`~findig.resource.Collection` rather than a 
:class:`~findig.resource.Resource`. It must however, create a resource instance
of the :class:`~findig.resource.Resource` that the collection collects.
The token returned from the "make" model function is a special mapping with
enough data to identify the resource instance that was created. By default,
you should make sure that it has at least the same fields as the arguments to
the resource instance's resource function.

Anyway, from the table, you should be able to see that our ``task`` resource
now supports ``PUT`` and ``DELETE``  requests. Go ahead and test them out 
(Remember to send ``application/json`` content with your PUT requests)!

But wait, we're still not done. Remember that ``GET /tasks/<id>`` still always
returns an empty JSON object, no matter if we've already ``PUT`` a task there.
We need to fix that by updating the resource function to return the appropriate
task data; change you definition of *task* to look like this::

    @app.route("/tasks/<id>")
    @app.resource
    def task(id):
        return TASKS[id]

But what if we get a URL that includes an id that is not in TASKS? That's okay!
Findig automatically converts a :class:`LookupError` into an HTTP 404 response.
So when the invalid id throws a :class:`KeyError` 
(a :class:`LookupError` subclass), it won't crash; it'll tell the requesting
client that it doesn't know what task it's asking about. Of course, if you're
still not convinced, you can go ahead and catch that :class:`KeyError` and
raise a :class:`werkzeug.exceptions.NotFound` error yourself.

Next, we'll add the model for ``tasks``::

    @tasks.model("make")
    def make_task(data):
        token = {"id": str(uuid4())}
        TASKS[token['id']] = data
        return token

update the resource function for our ``tasks`` collection::

    @app.route("/tasks/")
    @task.collection
    def tasks():
        return TASKS

and finally add the following import to the top of the file::

    from uuid import uuid4

Not a whole lot new is going on; In our "make" model, we're using the built-in :func:`uuid.uuid4` 
function to generate random ids for our tasks (nobody ever said our ids had
to be numeric!), and we're storing the data receive with that id. Finally, we 
return the id as part of the token (remember that the token needs to contain
at least enough data to identify the task instance, and here, all we need is
id!).

And that's it! We've built out our explicit data model. Now, let's go throw
it away...


.. _data-sets-tut:

Data sets
~~~~~~~~~

:ref:`data-sets` are an alternative to explicit data model functions. They have the advantage
of being far less verbose, but aren't quite as flexible. However, for most
resources, you may find that you don't need that extra bit of flexibility, so
a data set is perfectly fine.

Essentially, a data set is a special collection of records, each corresponding to a 
single resource instance. Instead of returning a straight-up list from a
collection resource function, we can return a data set instead. Since a
data-set is already an iterable object, we don't actually lose anything by
dropping one in where we would normally return a list or a generator. However,
with a little coaxing, we can get Findig to inspect the data set and derive a
model from it, so you don't have to type one out.

We're going to be using the included 
:class:`findig.extras.sql.SQLASet` (which requires SQLAlchemy) with an SQLite
table for our tasks. There's also a :class:`findig.extras.redis.RedisSet`, but
it relies on a redis server which you may not have on your system (that's a
little beyond the scope of this tutorial). Unlike RedisSet, SQLASet does
require a table schema to be declared, so the code is a little more verbose.

Let's dig in! Add this to *taskman.py* right after your app initialization:

.. literalinclude:: ../examples/taskman.py
    :language: python
    :start-after: app =
    :end-before: @

and add the following imports::

    from sqlalchemy.schema import *
    from sqlalchemy.types import *
    from findig.extras.sql import SQLA, SQLASet

.. tip:: If the above import gives you an ``ImportError``, it means that you
   don't have ``SQLAlchemy`` installed. You'll need to install it to continue
   (try: ``pip install sqlalchemy`` in your shell, if you have pip).

All we've done here is declare an SQLAlchemy orm schema. 
:class:`findig.extras.sql.SQLA` is a helper class for using SQLAlchemy inside
a findig application. The first argument we pass here sets up the database
engine (we store them in an SQLite database called 'tasks.sqlite'; you'll need
to make sure that your application process has write permission to the working 
directory so that it can create that file), and we pass our app as a keyword
argument.

After that, we declare our tasks table and its ORM mapping. We set up our schema
with three columns (id, title and desc).

Next up, let's use that schema to create an SQLA data set. Replace the 
declaration for your tasks collection with this code::

    @app.route("/tasks/")
    @task.collection(lazy=True)
    def tasks():
        return SQLASet(Task)

So some interesting changes. First up, we've added the ``lazy=True`` argument
to ``task.collection``. This gives Findig the heads-up that this resource
function returns a data set (meaning that simply calling it does not make any
queries to the database). As a result, Findig is able to inspect the return 
value when setting things up. Since it is a data set, Findig uses that to add
our model functions for us.

To complete our transition, replace the resource declaration for ``task`` with
this code::

    @app.route("/tasks/<id>")
    @app.resource(lazy=True)
    def task(id):
        return tasks().fetch(id=id)

:meth:`findig.extras.sql.SQLASet.fetch` can be thought of as a query. It returns
the first matching item as a 
:class:`~findig.tools.dataset.MutableRecord`, which Findig also knows how to
extract data model functions from. 

As a result, we don't need our data model functions anymore, so you should go
ahead and delete them.

Validating data
---------------

At this point, we've developed a working web application, but it's still
incomplete. Why, you ask? Because we haven't actually put an constraints on
the data that we receive. As it stands, we could send any old data to our API
and get away with it, without a peep in protest from the application. 

.. note:: Well that's not strictly true; since we've added an SQL schema for the
          tasks, SQLASet will try to make sure that any data that it receives
          conforms to the schema at the very least. Still, it doesn't perform
          any checks on the actual values or do any type conversions for us,
          and so we need to do that ourselves.

So what sort of constraints are we talking? Let's look at the fields in our
task schema again, to get a better idea:

.. literalinclude:: ../examples/taskman.py
    :language: python
    :start-after: db = 
    :end-before: @

First, let's look at the ``id`` field. This field is an integer primary key, so
the engine will automatically generate one for us (as it does with all 
integer primary key fields :D); we don't need to put any constraints here
because we won't be asking for one from the client.

Next there is the ``title`` field. It's a string with a maximum length of 
150 characters. It's tempting to have our validation engine enforce this for us,
but if we pass a string longer than 150 characters to the database engine, it
will truncate it for us. I think that's a reasonable compromise. We also see 
that the field is marked as ``nullable=False``; this meaans that it is 
required.

Our ``desc`` field doesn't have much in the way of constraints; it's probably
okay to just let our user put any old thing in there.

Finally, our ``due`` field is meant to store a date/time. We should make sure 
that whatever we receive from the client for 'due' can be parsed into a 
date/time. Also, note that this field is also required.

Great! So let's go set all of this up. The first thing we need to do is
create a :class:`~findig.tools.validator.Validator` for our application.
Add this code right after you initialize your app::

    validator = Validator(app)

and add this import::

    from findig.tools.validator import Validator

Next up, let's use the validator to enforce the constraints that we've 
identified. First up, I think it's a good idea to make sure that we don't
get any extra fields. We can do that by adding this decorator at the top
of our resource declaration for ``task``:

.. code-block:: python

    @validator.restrict("desc", "*due", "*title")

So what's happening? We're telling
the validator to only accept the fields ``desc``, ``due`` and ``title``. But
what's with the ``*``? If you guessed that the field is required, you're right!
:meth:`~findig.tools.validator.Validator.restrict` accepts a variable number of
field names, so we can restrict our resource input data to any number of
fields we want.

.. tip:: We've only set a validation rule for ``task``, but what about ``tasks``?
    Since ``tasks`` is a collection of ``task`` instances, the validator will
    check input for ``tasks`` with the rules we've defined for ``task`` by
    default. If you want to disable this behavior, you can pass
    ``include_collections=False`` to the validator constructor.

All we have to do now is check that the due date is date/time string, and
parse it into a :class:`~datetime.datetime` object. 
With :meth:`Validator.enforce<findig.tools.validator.Validator.enforce>`, we
can supply a converter for the due field. A converter can by a simple type, a
magic string, or an application-defined function that takes a string as input
and returns the parsed output. Here's what such a function can look like for
a date/time field like ``due``::

    def convert_date(string):
        import datetime
        format = "%Y-%m-%d %H:%M:%S%z"
        return datetime.datetime.strptime(string, format)

In fact, :meth:`Validator.date <findig.tools.validator.Validator.date>` is a
static method that simplifies this pattern; it takes a date/time format as its
argument and returns a converter function that parses a 
:class:`~datetime.datetime` object using that format. That's what we'll use
to check our ``due`` field. Add this decorator to our task declaration:


.. code-block:: python

    @validator.enforce(due=validator.date("%Y-%m-%d %H:%M:%S%z"))

With that, we've set up validation for our request input. You should go ahead
and try sending requests to the API we've created.

Calling our API
---------------

By now, we have a pretty decent API, but how exactly do we use it? First,
let's start our development server::

    $ python taskman.py
     * Running on http://localhost:5000/ (Press CTRL+C to quit)
     * Restarting with stat

Our development server is running on port 5000. Calling our API is a
matter of sending ``application/json`` HTTP requests to the server. For testing, 
you'll need a program that can send ``application/json`` requests, since your
browser probably doesn't provide an interface for this. The examples in this
section will use the command-line tool `cURL <http://http://curl.haxx.se//>`_,
but they'll include all the details your need to send the requests with any
tool you prefer to use.

.. tip::
    If you prefer a graphical interface, you might want to try the
    `Postman <https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop>`_
    Google Chrome extension (pictured below).

    .. image:: _static/postman.png

Listing our tasks
~~~~~~~~~~~~~~~~~

Send a ``GET`` request to ``/tasks/``. Here's how to do it in cURL::

    $ curl localhost:5000/tasks/
    []

The response is a JSON list containing all of the tasks that we have created.
Since we haven't created any yet, we get an empty list.

Creating a new task
~~~~~~~~~~~~~~~~~~~

To create a new task, we send a ``POST`` request to ``/tasks/``. The request
should have a ``Content-Type: application/json`` header, and the request body
must be a JSON object containing the attributes for our new task::

    $ curl -i -X POST -H "Content-Type: application/json" -d '{"title": "My Task"}' localhost:5000/tasks/
    HTTP/1.0 400 BAD REQUEST
    Content-Type: application/json
    Content-Length: 91
    Server: Werkzeug/0.10.4 Python/3.4.2
    Date: Sat, 18 Jul 2015 03:33:58 GMT

    {"message": "The browser (or proxy) sent a request that this server could not understand."}

What's with the error here? Well, remember that we've set up a validator for
our ``tasks`` resource to require a 'due' field with a parseable date/time. Let's
modify our request to include one::

    $ curl -i -X POST -H "Content-Type: application/json" -d '{"title": "My Task", "due": "2015-07-19 00:00:00+0400"}' localhost:5000/tasks/
    HTTP/1.0 201 CREATED
    Content-Length: 9
    Content-Type: application/json
    Date: Sat, 18 Jul 2015 03:40:01 GMT
    Location: http://localhost:5000/tasks/1
    Server: Werkzeug/0.10.4 Python/3.4.2

    {"id": 1}

Notably, the status code returned is ``201 CREATED`` and *not* ``200 OK``.
Additionally, Findig will try to fill the ``Location`` header, as long as 
the data returned from the collection resource function is enough to build
a URL for the created resource instance. Our resource function uses
:class:`~findig.extras.sql.SQLASet`, which returns the primary key fields.

Editing a task
~~~~~~~~~~~~~~

For this one, we send a ``PUT`` request to the task URL. Just like when creating
a task, The request should have a ``Content-Type: application/json`` header, and 
the request body must be a JSON object containing the attributes for our updated 
task. We must send *all* fields, including the ones that we're not updating, 
since this request type overwrites all of the task's data (unfortunately,
Findig `doesn't support PATCH <https://github.com/geniphi/findig/issues/9>`_ 
yet)::

    $ curl -i -X POST -H "Content-Type: application/json" -d '{"title": "My Task", "due": "2015-07-19 00:00:00+0400", "desc": "My awesome task dawg."}' localhost:5000/tasks/1
    HTTP/1.0 200 OK
    Content-Type: text/plain; charset=utf-8
    Content-Length: 0
    Server: Werkzeug/0.10.4 Python/3.4.2
    Date: Sat, 18 Jul 2015 03:47:00 GMT

Deleting a task
~~~~~~~~~~~~~~~

You can probably guess this one; to do this, we send a DELETE request to the
task's URL. Let's delete that task we just created; we're fickle like that::

    $ curl -i -X DELETE localhost:5000/tasks/1
    HTTP/1.0 200 OK
    Content-Type: text/plain; charset=utf-8
    Content-Length: 0
    Server: Werkzeug/0.10.4 Python/3.4.2
    Date: Sat, 18 Jul 2015 03:52:12 GMT

It works! It all works!


Customizing error output
------------------------

Remember when we sent a POST request to ``/tasks/`` without a required
field and it gave us a cryptic error message? We should probably do something
about that. We're gonna return a little bit more information to let the
client know what exactly has gone wrong.

To do this, we have to override the application's default error handler, which
Findig allows us to do for specific exception types by default [#f1]_. The key
is realising that :class:`~findig.tools.validator.Validator` raises specific
exceptions when something goes wrong, all resolving to ``400 BAD REQUEST``:

* :class:`findig.tools.validator.MissingFields` -- Raised when the validator 
  expects one or more required fields, but the client does not send them.

* :class:`findig.tools.validator.UnexpectedFields` -- Raised when the 
  validator receives one or more fields that it does not expect.

* :class:`findig.tools.validator.InvalidFields` -- Raised when the validator 
  receives one or more fields that can't be converted using the supplied 
  converters.

Knowing this, we can update the app to send a more detailed error whenever
a missing field is encountered:

.. literalinclude:: ../examples/taskman.py
    :language: python
    :start-after: return SQLASet
    :end-before: if __name__

You'll also want to import :class:`~finding.tools.validator.MissingFields`::

    from findig.tools.validator import MissingFields

Now, let's send another request omitting a field::

    $ curl -i -X POST -H "Content-Type: application/json" -d '{"title": "My Task"}' localhost:5000/tasks/
    HTTP/1.0 400 BAD REQUEST
    Content-Type: application/json
    Content-Length: 114
    Server: Werkzeug/0.10.4 Python/3.4.2
    Date: Sat, 18 Jul 2015 05:09:34 GMT

    {
      "error": {
        "type": "missing_fields",
        "fields": [
          "due"
        ]
      },
      "message": "The input is missing one or more parameters."
    }

As expected, this time we get a more detailed error response.

Here's a little exercise for you; why don't you go ahead and update the app
to provide detailed messages for when the client sends an unrecognized field,
and for when the client sends badly formed data for the ``due`` field?

.. [#f1] This can change in very specific circumstances. In particular, if you
    supply an ``error_handler`` argument to the application constructor, then
    this method is no longer available; you would have to check for specific
    exceptions in the function body of your custom ``error_handler`` instead.

Wrapping up
-----------

Whew! Here's the full source code for the app we've built:

.. literalinclude:: ../examples/taskman.py
    :language: python

We've designed an built a functioning API, but we've only used a subset
of what Findig has to offer. Have a look at 
:class:`~findig.tools.counter.Counter` for a tool that counts hits to your
resources (this is more useful than it sounds upfront). The 
:mod:`findig.tools.protector` module provides utilies for restrict access to
your API to authorized users/clients.

If you're interested in supporting custom content-types, rather than just
JSON, have a look at :ref:`custom-applications`. The process is very similar
to the custom error handler we built in this tutorial.