Skip to content

Commit 846e1c9

Browse files
committed
First attempt at REST-API documentation
Also fix operator of patch to be '@op', not 'op'. Example for retire/restore using both, DELETE and PATCH with @op=action.
1 parent 00bdfe1 commit 846e1c9

File tree

2 files changed

+155
-8
lines changed

2 files changed

+155
-8
lines changed

doc/rest.txt

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
2+
====================
3+
REST API for Roundup
4+
====================
5+
6+
.. contents::
7+
:local:
8+
9+
Introduction
10+
------------
11+
12+
After the last 1.6.0 Release, a REST-API developed in 2015 during a Google
13+
Summer of Code (GSOC) by Chau Nguyen, supervised by Ezio Melotti was
14+
integrated. The code was then updated by John Rouillard and Ralf
15+
Schlatterbeck to fix some shortcomings and provide the necessary
16+
functions for a single page web application, e.g. etag support among
17+
others.
18+
19+
Enabling the REST API
20+
---------------------
21+
22+
The REST API can be disabled in the ``[web]`` section of ``config.ini``
23+
via the variable ``enable_rest`` which is ``yes`` by default.
24+
25+
The REST api is reached via the ``/rest/`` endpoint of the tracker URL.
26+
27+
28+
Client API
29+
----------
30+
31+
The top-level REST url ``/rest/`` will display the current version of
32+
the REST API (Version 1 as of this writing) and some links to relevant
33+
endpoints of the API. In the following the ``/rest`` prefix is ommitted
34+
from relative REST-API links for brevety.
35+
36+
Summary
37+
=======
38+
39+
A Summary page can be reached via ``/data/summary`` via the ``GET`` method.
40+
This is currently hard-coded for the standard tracker schema shipped
41+
with roundup and will display a summary of open issues.
42+
43+
Data
44+
====
45+
46+
All the links mentioned in the following support the http method ``GET``.
47+
Results of a ``GET`` request will always return the results as a
48+
dictionary with the entry ``data`` referring to the returned data.
49+
50+
The ``/data`` link will display a set of classes of the tracker. All
51+
classes can be reached via ``/data/<classname>`` where ``<classname>``
52+
is replace with the name of the class to query, e.g. ``/data/issue``.
53+
Individual items of a class (e.g. a single issue) can be queried by
54+
giving the issue-id, e.g., ``/data/issue/42``. Individual properties of
55+
an item can be queried by appending the property, e.g.,
56+
``/data/issue/42/title``.
57+
58+
When performing the ``GET`` method on a class (e.g. ``/data/issue``), the
59+
number of items is returned in ``@total_size``. Then a ``collection``
60+
list follows which contains the id and link to the respective item.
61+
62+
When performing the ``GET`` method on an item (e.g. ``/data/issue/42``), a
63+
``link`` attribute contains the link to the item, ``id`` contains the
64+
id, type contains the class name (e.g. ``issue`` in the example) and an
65+
``etag`` property can be used to detect modifications since the last
66+
query. The individual properties of the item are returned in an
67+
``attributes`` dictionary. The properties returned depend on the
68+
permissions of the account used for the query.
69+
70+
A ``GET`` method on a property (e.g. ``/data/issue/42/title``) returns the
71+
link, an ``@etag``, the type of the property (e.g. "<type str>") the id
72+
of the item and the content of the property in ``data``.
73+
74+
Only class links support the ``POST`` method for creation of new items
75+
of a class, e.g., a new issue via the ``/data/issue`` link. The post
76+
gets a dictionary of keys/values for the new item. It returns the same
77+
parameters as the GET method after successful creation.
78+
79+
All endpoints support an ``OPTIONS`` method for determining which
80+
methods are allowed on a given endpoint.
81+
82+
The method ``PUT`` is allowed on individual items, e.g.
83+
``/data/issue/42`` as well as properties, e.g.,
84+
``/data/issue/42/title``. On success it returns the same parameters as
85+
the respective ``GET`` method. Note that for ``PUT`` an Etag has to be
86+
supplied, either in the request header or as an @etag parameter.
87+
88+
The method ``DELETE`` is allowed on items, e.g., ``/data/issue/42`` and
89+
will retire (mark as deleted) the respective item. On success it will
90+
only return a status code. It is also possible to call ``DELETE`` on a
91+
property of an item, e.g., ``/data/issue/42/nosy`` to delete the nosy
92+
list. The same effect can be achieved with a ``PUT`` request and an
93+
empty new value.
94+
95+
Finally the ``PATCH`` method can be applied to individual items, e.g.,
96+
``/data/issue/42`` and to properties, e.g., ``/data/issue/42/title``.
97+
This method gets an operator ``@op=<method>`` where ``<method`` is one
98+
of ``add``, ``replace``, ``remove``, only for an item (not for a
99+
property) an additional operator ``action`` is supported. If no operator
100+
is specified, the default is ``replace``. The first three operators are
101+
self explanatory. For an ``action`` operator an ``@action_name`` and
102+
optional ``@action_argsXXX`` parameters have to be supplied. Currently
103+
there are only two actions without parameters, namely ``retire`` and
104+
``restore``. The ``retire`` action on an item is the same as a
105+
``DELETE`` method, it retires the item. The ``restore`` action is the
106+
inverse of ``retire``, the item is again visible.
107+
On success the returned value is the same as the respective ``GET``
108+
method.
109+
110+
sample python client
111+
====================
112+
113+
The client uses the python ``requests`` library for easier interaction
114+
with a REST API supporting JSON encoding::
115+
116+
117+
>>> import requests
118+
>>> u = 'http://user:[email protected]/demo/rest/data/'
119+
>>> s = requests.session()
120+
>>> r = s.get(u + 'issue/42/title')
121+
>>> if r.status_code != 200:
122+
... print("Failed: %s: %s" % (r.status_code, r.reason))
123+
... exit(1)
124+
>>> print (r.json() ['data']['data']
125+
TEST Title
126+
>>> r = s.post (u + 'issue', data = dict (title = 'TEST Issue'))
127+
>>> if not 200 <= r.status_code <= 201:
128+
... print("Failed: %s: %s" % (r.status_code, r.reason))
129+
... exit(1)
130+
>>> print(r.json())
131+
132+
Retire/Restore::
133+
>>> r = s.delete (u + 'issue/42')
134+
>>> print (r.json())
135+
>>> r = s.get (u + 'issue/42')
136+
>>> etag = r.headers['ETag']
137+
>>> print("ETag: %s" % etag)
138+
>>> etag = r.json()['data']['@etag']
139+
>>> print("@etag: %s" % etag)
140+
>>> h = dict(ETag = etag)
141+
>>> d = {'@op:'action', '@action_name':'retire'}
142+
>>> r = s.patch(u + 'issue/42', data = d, headers = h)
143+
>>> print(r.json())
144+
>>> d = {'@op:'action', '@action_name':'restore'}
145+
>>> r = s.patch(u + 'issue/42', data = d, headers = h)
146+
>>> print(r.json())
147+

roundup/rest.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def put_element(self, class_name, item_id, input):
786786
class_name,
787787
item_id):
788788
raise PreconditionFailed("Etag is missing or does not match."
789-
"Retreive asset and retry modification if valid.")
789+
" Retrieve asset and retry modification if valid.")
790790
result = class_obj.set(item_id, **props)
791791
self.db.commit()
792792
except (TypeError, IndexError, ValueError) as message:
@@ -841,7 +841,7 @@ def put_attribute(self, class_name, item_id, attr_name, input):
841841
obtain_etags(self.client.request.headers, input),
842842
class_name, item_id):
843843
raise PreconditionFailed("Etag is missing or does not match."
844-
"Retreive asset and retry modification if valid.")
844+
" Retrieve asset and retry modification if valid.")
845845
result = class_obj.set(item_id, **props)
846846
self.db.commit()
847847
except (TypeError, IndexError, ValueError) as message:
@@ -932,7 +932,7 @@ def delete_element(self, class_name, item_id, input):
932932
class_name,
933933
item_id):
934934
raise PreconditionFailed("Etag is missing or does not match."
935-
"Retreive asset and retry modification if valid.")
935+
" Retrieve asset and retry modification if valid.")
936936

937937
class_obj.retire (item_id)
938938
self.db.commit()
@@ -982,7 +982,7 @@ def delete_attribute(self, class_name, item_id, attr_name, input):
982982
class_name,
983983
item_id):
984984
raise PreconditionFailed("Etag is missing or does not match."
985-
"Retreive asset and retry modification if valid.")
985+
" Retrieve asset and retry modification if valid.")
986986

987987
class_obj.set(item_id, **props)
988988
self.db.commit()
@@ -1022,7 +1022,7 @@ def patch_element(self, class_name, item_id, input):
10221022
if class_name not in self.db.classes:
10231023
raise NotFound('Class %s not found' % class_name)
10241024
try:
1025-
op = input['op'].value.lower()
1025+
op = input['@op'].value.lower()
10261026
except KeyError:
10271027
op = self.__default_patch_op
10281028
class_obj = self.db.getclass(class_name)
@@ -1032,7 +1032,7 @@ def patch_element(self, class_name, item_id, input):
10321032
class_name,
10331033
item_id):
10341034
raise PreconditionFailed("Etag is missing or does not match."
1035-
"Retreive asset and retry modification if valid.")
1035+
" Retrieve asset and retry modification if valid.")
10361036

10371037
# if patch operation is action, call the action handler
10381038
action_args = [class_name + item_id]
@@ -1121,7 +1121,7 @@ def patch_attribute(self, class_name, item_id, attr_name, input):
11211121
if class_name not in self.db.classes:
11221122
raise NotFound('Class %s not found' % class_name)
11231123
try:
1124-
op = input['op'].value.lower()
1124+
op = input['@op'].value.lower()
11251125
except KeyError:
11261126
op = self.__default_patch_op
11271127

@@ -1141,7 +1141,7 @@ def patch_attribute(self, class_name, item_id, attr_name, input):
11411141
class_name,
11421142
item_id):
11431143
raise PreconditionFailed("Etag is missing or does not match."
1144-
"Retreive asset and retry modification if valid.")
1144+
" Retrieve asset and retry modification if valid.")
11451145

11461146
props = {
11471147
prop: self.prop_from_arg(

0 commit comments

Comments
 (0)