blog.notmyidea.org/introducing-cornice.html

201 lines
No EOL
15 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<title>
Introducing&nbsp;Cornice - Alexis Métaireau </title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://blog.notmyidea.org/theme/css/main.css?v2"
type="text/css" />
<link href="https://blog.notmyidea.org/feeds/all.atom.xml"
type="application/atom+xml"
rel="alternate"
title="Alexis Métaireau ATOM Feed" />
</head>
<body>
<div id="content">
<section id="links">
<ul>
<li>
<a class="main" href="/">Alexis Métaireau</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/journal/index.html">Journal</a>
</li>
<li>
<a class="selected"
href="https://blog.notmyidea.org/code/">Code, etc.</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/weeknotes/">Notes hebdo</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/lectures/">Lectures</a>
</li>
<li>
<a class=""
href="https://blog.notmyidea.org/projets.html">Projets</a>
</li>
</ul>
</section>
<header>
<h1 class="post-title">Introducing&nbsp;Cornice</h1>
<time datetime="2011-12-07T00:00:00+01:00">07 décembre 2011</time>
</header>
<article>
<p>Wow, already my third working day at Mozilla. Since Monday, I&#8217;ve been
working with <a href="http://ziade.org">Tarek Ziadé</a>, on a pyramid <span class="caps">REST</span>-ish
toolkit named <a href="https://github.com/mozilla-services/cornice">Cornice</a>.</p>
<p>Its goal is to take care for you of what you&#8217;re usually missing so you
can focus on what&#8217;s important. Cornice provides you facilities for
validation of any&nbsp;kind.</p>
<p>The goal is to simplify your work, but we don&#8217;t want to reinvent the
wheel, so it is easily pluggable with validations frameworks, such as
<a href="http://docs.pylonsproject.org/projects/colander/en/latest/">Colander</a>.</p>
<h2 id="handling-errors-and-validation">Handling errors and&nbsp;validation</h2>
<p>Here is how it&nbsp;works:</p>
<div class="highlight"><pre><span></span><code><span class="n">service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&quot;service&quot;</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s2">&quot;/service&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_awesome</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="s1">&#39;awesome&#39;</span> <span class="ow">in</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="p">:</span>
<span class="n">request</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s1">&#39;query&#39;</span><span class="p">,</span> <span class="s1">&#39;awesome&#39;</span><span class="p">,</span>
<span class="s1">&#39;the awesome parameter is required&#39;</span><span class="p">)</span>
<span class="nd">@service</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">validator</span><span class="o">=</span><span class="n">is_awesome</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get1</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span><span class="s2">&quot;test&quot;</span><span class="p">:</span> <span class="s2">&quot;yay!&quot;</span><span class="p">}</span>
</code></pre></div>
<p>All the errors collected during the validation process, or after, are
collected before returning the request. If any, a error 400 is fired up,
with the list of problems encountered returned as a nice json list
response (we plan to support multiple formats in the&nbsp;future)</p>
<p>As you might have seen, request.errors.add takes three parameters:
<strong>location</strong>, <strong>name</strong> and <strong>description</strong>.</p>
<p><strong>location</strong> is where the error is located in the request. It can either
be &#8220;body&#8221;, &#8220;query&#8221;, &#8220;headers&#8221; or &#8220;path&#8221;. <strong>name</strong> is the name of the
variable causing problem, if any, and <strong>description</strong> contains a more
detailed&nbsp;message.</p>
<p>Let&#8217;s run this simple service and send some queries to&nbsp;it:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>curl<span class="w"> </span>-v<span class="w"> </span>http://127.0.0.1:5000/service
&gt;<span class="w"> </span>GET<span class="w"> </span>/service<span class="w"> </span>HTTP/1.1
&gt;<span class="w"> </span>Host:<span class="w"> </span><span class="m">127</span>.0.0.1:5000
&gt;<span class="w"> </span>Accept:<span class="w"> </span>*/*
&gt;
*<span class="w"> </span>HTTP<span class="w"> </span><span class="m">1</span>.0,<span class="w"> </span>assume<span class="w"> </span>close<span class="w"> </span>after<span class="w"> </span>body
&lt;<span class="w"> </span>HTTP/1.0<span class="w"> </span><span class="m">400</span><span class="w"> </span>Bad<span class="w"> </span>Request
&lt;<span class="w"> </span>Content-Type:<span class="w"> </span>application/json<span class="p">;</span><span class="w"> </span><span class="nv">charset</span><span class="o">=</span>UTF-8
<span class="o">[{</span><span class="s2">&quot;location&quot;</span>:<span class="w"> </span><span class="s2">&quot;query&quot;</span>,<span class="w"> </span><span class="s2">&quot;name&quot;</span>:<span class="w"> </span><span class="s2">&quot;awesome&quot;</span>,<span class="w"> </span><span class="s2">&quot;description&quot;</span>:<span class="w"> </span><span class="s2">&quot;You lack awesomeness!&quot;</span><span class="o">}</span>
</code></pre></div>
<p>I&#8217;ve removed the extra clutter from the curl&#8217;s output, but you got the
general&nbsp;idea.</p>
<p>The content returned is in <span class="caps">JSON</span>, and I know exactly what I have to do:
add an &#8220;awesome&#8221; parameter in my query. Let&#8217;s do it&nbsp;again:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>curl<span class="w"> </span>http://127.0.0.1:5000/service?awesome<span class="o">=</span>yeah
<span class="o">{</span><span class="s2">&quot;test&quot;</span>:<span class="w"> </span><span class="s2">&quot;yay!&quot;</span><span class="o">}</span>
</code></pre></div>
<p>Validators can also convert parts of the request and store the converted
value in request.validated. It is a standard dict automatically attached
to the&nbsp;requests.</p>
<p>For instance, in our validator, we can chose to validate the parameter
passed and use it in the body of the&nbsp;webservice:</p>
<div class="highlight"><pre><span></span><code><span class="n">service</span> <span class="o">=</span> <span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">&quot;service&quot;</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="s2">&quot;/service&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">is_awesome</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="s1">&#39;awesome&#39;</span> <span class="ow">in</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="p">:</span>
<span class="n">request</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s1">&#39;query&#39;</span><span class="p">,</span> <span class="s1">&#39;awesome&#39;</span><span class="p">,</span>
<span class="s1">&#39;the awesome parameter is required&#39;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">request</span><span class="o">.</span><span class="n">validated</span><span class="p">[</span><span class="s1">&#39;awesome&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;awesome &#39;</span> <span class="o">+</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="p">[</span><span class="s1">&#39;awesome&#39;</span><span class="p">]</span>
<span class="nd">@service</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">validator</span><span class="o">=</span><span class="n">is_awesome</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get1</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span><span class="s2">&quot;test&quot;</span><span class="p">:</span> <span class="n">request</span><span class="o">.</span><span class="n">validated</span><span class="p">[</span><span class="s1">&#39;awesome&#39;</span><span class="p">]}</span>
</code></pre></div>
<p>The output would look like&nbsp;this:</p>
<div class="highlight"><pre><span></span><code>curl http://127.0.0.1:5000/service?awesome=yeah
{&quot;test&quot;: &quot;awesome yeah&quot;}
</code></pre></div>
<h2 id="dealing-with-accept-headers">Dealing with &#8220;Accept&#8221;&nbsp;headers</h2>
<p>The <span class="caps">HTTP</span> spec defines a <strong>Accept</strong> header the client can send so the
response is encoded the right way. A resource, available at an <span class="caps">URL</span>, can
be available in different formats. This is especially true for web&nbsp;services.</p>
<p>Cornice can help you dealing with this. The services you define can tell
which Content-Type values they can deal with and this will be checked
against the <strong>Accept</strong> headers sent by the&nbsp;client.</p>
<p>Let&#8217;s refine a bit our previous example, by specifying which
content-types are supported, using the accept&nbsp;parameter:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@service</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">validator</span><span class="o">=</span><span class="n">is_awesome</span><span class="p">,</span> <span class="n">accept</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;application/json&quot;</span><span class="p">,</span> <span class="s2">&quot;text/json&quot;</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">get1</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span><span class="s2">&quot;test&quot;</span><span class="p">:</span> <span class="s2">&quot;yay!&quot;</span><span class="p">}</span>
</code></pre></div>
<p>Now, if you specifically ask for <span class="caps">XML</span>, Cornice will throw a 406 with the
list of accepted Content-Type&nbsp;values:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>curl<span class="w"> </span>-vH<span class="w"> </span><span class="s2">&quot;Accept: application/xml&quot;</span><span class="w"> </span>http://127.0.0.1:5000/service
&gt;<span class="w"> </span>GET<span class="w"> </span>/service<span class="w"> </span>HTTP/1.1
&gt;<span class="w"> </span>Host:<span class="w"> </span><span class="m">127</span>.0.0.1:5000
&gt;<span class="w"> </span>Accept:<span class="w"> </span>application/xml
&gt;
&lt;<span class="w"> </span>HTTP/1.0<span class="w"> </span><span class="m">406</span><span class="w"> </span>Not<span class="w"> </span>Acceptable
&lt;<span class="w"> </span>Content-Type:<span class="w"> </span>application/json<span class="p">;</span><span class="w"> </span><span class="nv">charset</span><span class="o">=</span>UTF-8
&lt;<span class="w"> </span>Content-Length:<span class="w"> </span><span class="m">33</span>
&lt;
<span class="o">[</span><span class="s2">&quot;application/json&quot;</span>,<span class="w"> </span><span class="s2">&quot;text/json&quot;</span><span class="o">]</span>
</code></pre></div>
<h2 id="building-your-documentation-automatically">Building your documentation&nbsp;automatically</h2>
<p>writing documentation for web services can be painful, especially when
your services evolve. Cornice provides a sphinx directive to
automatically document your <span class="caps">API</span> in your&nbsp;docs.</p>
<div class="highlight"><pre><span></span><code><span class="nx">rst</span>
<span class="p">..</span><span class="w"> </span><span class="nx">services</span><span class="o">::</span>
<span class="w"> </span><span class="p">:</span><span class="kn">package</span><span class="p">:</span><span class="w"> </span><span class="nx">coolapp</span>
<span class="w"> </span><span class="p">:</span><span class="nx">service</span><span class="p">:</span><span class="w"> </span><span class="nx">quote</span>
</code></pre></div>
<p>Here is an example of what a generated page looks like:
<a href="http://packages.python.org/cornice/exampledoc.html">http://packages.python.org/cornice/exampledoc.html</a></p>
<h2 id="yay-how-can-i-get-it">Yay! How can I get&nbsp;it?</h2>
<p>We just cut a 0.4 release, so it&#8217;s available at
<a href="http://pypi.python.org/pypi/cornice">http://pypi.python.org/pypi/cornice</a> You can install it easily using
pip, for&nbsp;instance:</p>
<div class="highlight"><pre><span></span><code>$<span class="w"> </span>pip<span class="w"> </span>install<span class="w"> </span>cornice
</code></pre></div>
<p>You can also have a look at the documentation at
<a href="http://packages.python.org/cornice/">http://packages.python.org/cornice/</a></p>
<h2 id="whats-next">What&#8217;s&nbsp;next?</h2>
<p>We try to make our best to find how Cornice can help you build better
web services. Cool features we want for the future include the automatic
publication of a static definition of the services, so it can be used by
clients to discover services in a nice&nbsp;way.</p>
<p>Of course, we are open to all your ideas and patches! If you feel
haskish and want to see the sources, <a href="https://github.com/mozilla-services/cornice">go grab them on
github</a> , commit and send
us a pull&nbsp;request!</p>
</article>
<footer>
<a id="feed" href="/feeds/all.atom.xml">
<img alt="RSS Logo" src="/theme/rss.svg" />
</a>
</footer>
</div>
</body>
</html>