77
88from __future__ import print_function
99
10- try :
11- from urllib .parse import urlparse
12- except ImportError :
13- from urlparse import urlparse
14- import os
10+ from datetime import timedelta
11+ from hashlib import md5
12+ import hmac
1513import json
14+ import logging
15+ import os
16+ import re
1617import sys
1718import time
1819import traceback
19- import re
2020
21- import logging
21+ try :
22+ from urllib .parse import urlparse
23+ except ImportError :
24+ from urlparse import urlparse
25+
26+ from roundup import actions
27+ from roundup import date
28+ from roundup import hyperdb
29+ from roundup .anypy .strings import bs2b , b2s , u2s , is_us
30+ from roundup .cgi .exceptions import NotFound , Unauthorised , PreconditionFailed
31+ from roundup .exceptions import Reject , UsageError
32+ from roundup .i18n import _
33+ from roundup .rate_limit import RateLimit , Gcra
34+
2235logger = logging .getLogger ('roundup.rest' )
2336
2437try :
3245 # else not supported
3346 dicttoxml = None
3447
35- from hashlib import md5
36-
37- from roundup import hyperdb
38- from roundup import date
39- from roundup import actions
40- from roundup .i18n import _
41- from roundup .anypy .strings import bs2b , b2s , u2s , is_us
42- from roundup .rate_limit import RateLimit , Gcra
43- from roundup .exceptions import Reject , UsageError
44- from roundup .cgi .exceptions import NotFound , Unauthorised , PreconditionFailed
45-
46- import hmac
47- from datetime import timedelta
48-
4948# Py3 compatible basestring
5049try :
5150 basestring
@@ -118,6 +117,7 @@ def format_object(self, *args, **kwargs):
118117 format_object .wrapped_func = func
119118 return format_object
120119
120+
121121def openapi_doc (d ):
122122 """Annotate rest routes with openapi data. Takes a dict
123123 for the openapi spec. It can be used standalone
@@ -160,6 +160,7 @@ def wrapper(f):
160160 return f
161161 return wrapper
162162
163+
163164def calculate_etag (node , key , classname = "Missing" , id = "0" ,
164165 repr_format = "json" ):
165166 '''given a hyperdb node generate a hashed representation of it to be
@@ -272,7 +273,7 @@ def parse_accept_header(accept):
272273 try :
273274 typ , subtyp = media_type .split ('/' )
274275 except ValueError :
275- raise UsageError ("Invalid media type: %s" % media_type )
276+ raise UsageError ("Invalid media type: %s" % media_type )
276277 # check for a + in the sub-type
277278 if '+' in subtyp :
278279 # if it exists, determine if the subtype is a vendor-specific type
@@ -298,7 +299,7 @@ def parse_accept_header(accept):
298299 try :
299300 (key , value ) = part .lstrip ().split ("=" , 1 )
300301 except ValueError :
301- raise UsageError ("Invalid param: %s" % part .lstrip ())
302+ raise UsageError ("Invalid param: %s" % part .lstrip ())
302303 key = key .strip ()
303304 value = value .strip ()
304305 if key == "q" :
@@ -395,11 +396,11 @@ def execute(cls, instance, path, method, input):
395396 except KeyError :
396397 valid_methods = ', ' .join (sorted (funcs .keys ()))
397398 raise Reject (_ ('Method %(m)s not allowed. '
398- 'Allowed: %(a)s' )% {
399+ 'Allowed: %(a)s' ) % {
399400 'm' : method ,
400401 'a' : valid_methods
401402 },
402- valid_methods )
403+ valid_methods )
403404
404405 # retrieve the vars list and the function caller
405406 list_vars = func_obj ['vars' ]
@@ -536,7 +537,7 @@ def prop_from_arg(self, cl, key, value, itemid=None):
536537
537538 return prop
538539
539- def transitive_props (self , class_name , props ):
540+ def transitive_props (self , class_name , props ):
540541 """Construct a list of transitive properties from the given
541542 argument, and return it after permission check. Raises
542543 Unauthorised if no permission. Permission is checked by
@@ -554,7 +555,7 @@ def transitive_props (self, class_name, props):
554555 for pn in p .split ('.' ):
555556 # Tried to dereference a non-Link property
556557 if cn is None :
557- raise UsageError ("Property %(base)s can not be dereferenced in %(p)s." % { "base" : p [:- (len (pn )+ 1 )], "p" : p })
558+ raise UsageError ("Property %(base)s can not be dereferenced in %(p)s." % {"base" : p [:- (len (pn )+ 1 )], "p" : p })
558559 cls = self .db .getclass (cn )
559560 # This raises a KeyError for unknown prop:
560561 try :
@@ -580,7 +581,7 @@ def transitive_props (self, class_name, props):
580581 prop = cls .getprops (protected = True )[pn ]
581582 except KeyError :
582583 raise KeyError ("Unknown property: %s" % pn )
583- checked_props .append (p )
584+ checked_props .append (p )
584585 return checked_props
585586
586587 def error_obj (self , status , msg , source = None ):
@@ -694,7 +695,7 @@ def format_item(self, node, item_id, props=None, verbose=1):
694695 id = v = getattr (nd , p )
695696 # Handle transitive properties where something on
696697 # the road is None (empty Link property)
697- if id is None :
698+ if id is None :
698699 prop = None
699700 ok = True
700701 break
@@ -820,7 +821,7 @@ def get_collection(self, class_name, input):
820821 uid , class_name , pn
821822 ):
822823 sort .append ((ss , pn ))
823- else :
824+ else :
824825 raise (Unauthorised (
825826 'User does not have search permission on "%s.%s"'
826827 % (class_name , pn )))
@@ -844,7 +845,8 @@ def get_collection(self, class_name, input):
844845 # Call this for the side effect of validating the key
845846 # use _discard as _ is apparently a global for the translation
846847 # service.
847- _discard = self .transitive_props (class_name , [ key ])
848+ _discard = self .transitive_props (class_name , [key ]) # noqa: F841
849+
848850 # We drop properties without search permission silently
849851 # This reflects the current behavior of other roundup
850852 # interfaces
@@ -1091,8 +1093,8 @@ def get_attribute(self, class_name, item_id, attr_name, input):
10911093 class_name , item_id , repr_format = "json" )
10921094 try :
10931095 data = node .__getattr__ (attr_name )
1094- except AttributeError as e :
1095- raise UsageError (_ ("Invalid attribute %s" % attr_name ))
1096+ except AttributeError :
1097+ raise UsageError (_ ("Invalid attribute %s" % attr_name ))
10961098 result = {
10971099 'id' : item_id ,
10981100 'type' : str (type (data )),
@@ -1786,78 +1788,80 @@ def option_attribute(self, class_name, item_id, attr_name, input):
17861788 attr_name , class_name ))
17871789 return 204 , ""
17881790
1789- @openapi_doc ({"summary" : "Describe Roundup rest endpoint." ,
1790- "description " : ( "Report all supported api versions "
1791- "and default api version. "
1792- "Also report next level of link "
1793- "endpoints below /rest endpoint" ),
1794- "responses" : {
1795- "200" : {
1796- "description " : "Successful response." ,
1797- "content " : {
1798- "application/json " : {
1799- "examples " : {
1800- "success " : {
1801- "summary " : "Normal json data." ,
1802- "value " : """ {
1803- "data ": {
1804- "default_version ": 1,
1805- "supported_versions": [
1806- 1
1807- ] ,
1808- "links ": [
1809- {
1810- "uri": "https://tracker.example.com/demo/rest",
1811- "rel ": "self"
1812- },
1813- {
1814- "uri": "https://tracker.example.com/demo/rest/data",
1815- "rel ": "data"
1816- },
1817- {
1818- "uri": "https://tracker.example.com/demo/rest/summary",
1819- "rel ": "summary"
1820- }
1821- ]
1822- }
1823- }"""
1824- }
1825- }
1826- },
1827- "application/xml" : {
1828- "examples " : {
1829- "success " : {
1830- "summary " : "Normal xml data" ,
1831- "value " : """<dataf type="dict">
1832- <default_version type="int">1</default_version>
1833- <supported_versions type="list ">
1834- <item type="int">1</item >
1835- </ supported_versions>
1836- <links type="list" >
1837- <item type="dict" >
1838- <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest</uri >
1839- <rel type="str">self</rel >
1840- </item >
1841- <item type="dict" >
1842- <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/data</uri >
1843- <rel type="str">data</rel >
1844- </item >
1845- <item type="dict" >
1846- <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/summary</uri >
1847- <rel type="str">summary</rel >
1848- </item >
1849- <item type="dict" >
1850- <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/summary2</uri >
1851- <rel type="str">summary2</rel >
1852- </item >
1853- </links >
1854- </dataf>"""
1855- }
1856- }
1857- }
1791+ @openapi_doc ({
1792+ "summary " : "Describe Roundup rest endpoint." ,
1793+ "description" : (
1794+ "Report all supported api versions "
1795+ "and default api version. "
1796+ "Also report next level of link "
1797+ "endpoints below /rest endpoint" ),
1798+ "responses " : {
1799+ "200 " : {
1800+ "description " : "Successful response." ,
1801+ "content " : {
1802+ "application/json " : {
1803+ "examples " : {
1804+ "success " : {
1805+ "summary " : "Normal json data." ,
1806+ "value " : """
1807+ {
1808+ "data": {
1809+ "default_version": 1 ,
1810+ "supported_versions ": [ 1 ],
1811+ "links": [
1812+ {
1813+ "uri ": "https://tracker.example.com/demo/rest",
1814+ "rel": "self"
1815+ },
1816+ {
1817+ "uri ": "https://tracker.example.com/demo/rest/ data",
1818+ "rel": "data"
1819+ },
1820+ {
1821+ "uri ": "https://tracker.example.com/demo/rest/ summary",
1822+ "rel": "summary"
1823+ }
1824+ ]
1825+ }
1826+ }"""
1827+ }
1828+ }
1829+ },
1830+ "application/xml " : {
1831+ "examples " : {
1832+ "success " : {
1833+ "summary " : "Normal xml data" ,
1834+ "value" : """
1835+ <dataf type="dict ">
1836+ <default_version type="int">1</default_version >
1837+ < supported_versions type="list" >
1838+ <item type="int">1</item >
1839+ </supported_versions >
1840+ <links type="list" >
1841+ <item type="dict" >
1842+ <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest</uri >
1843+ <rel type="str">self</rel >
1844+ </item >
1845+ <item type="dict" >
1846+ <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/data</uri >
1847+ <rel type="str">data</rel >
1848+ </item >
1849+ <item type="dict" >
1850+ <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/summary</uri >
1851+ <rel type="str">summary</rel >
1852+ </item >
1853+ <item type="dict" >
1854+ <uri type="str">https://rouilj.dynamic-dns.net/sysadmin/rest/summary2</uri >
1855+ <rel type="str">summary2</rel >
1856+ </item>
1857+ </links>
1858+ </dataf>"""
1859+ }
18581860 }
1859- }
1860- }
1861+ }
1862+ }
1863+ }
1864+ }
18611865 }
18621866 )
18631867 @Routing .route ("/" )
@@ -1874,8 +1878,8 @@ def describe(self, input):
18741878 links = []
18751879 # p[1:-1] removes ^ and $ from regexp
18761880 # if p has only 1 /, it's a child of rest/ root.
1877- child_paths = sorted ([ p [1 :- 1 ] for p in paths if
1878- p .count ('/' ) == 1 ])
1881+ child_paths = sorted ([p [1 :- 1 ] for p in paths if
1882+ p .count ('/' ) == 1 ])
18791883 for p in child_paths :
18801884 # p.split('/')[1] is the residual path after
18811885 # removing rest/. child_paths look like:
@@ -1886,9 +1890,8 @@ def describe(self, input):
18861890 else :
18871891 rel_path = rel
18881892 rel = "self"
1889- links .append ( {"uri" : self .base_path + rel_path ,
1890- "rel" : rel
1891- })
1893+ links .append ({"uri" : self .base_path + rel_path ,
1894+ "rel" : rel })
18921895
18931896 result = {
18941897 "default_version" : self .__default_api_version ,
@@ -2158,7 +2161,7 @@ def dispatch(self, method, uri, input):
21582161 # user info. It also allows passing through larger items like
21592162 # JWT that has a final component > 6 items. This method also
21602163 # allow detection of mistyped types like jon for json.
2161- if ext_type and (len (ext_type ) < 6 ):
2164+ if ext_type and (len (ext_type ) < 6 ):
21622165 # strip extension so uri make sense
21632166 # .../issue.json -> .../issue
21642167 uri = uri [:- (len (ext_type ) + 1 )]
@@ -2189,7 +2192,7 @@ def dispatch(self, method, uri, input):
21892192 self .client .setHeader ("Access-Control-Max-Age" , "86400" )
21902193
21912194 # response may change based on Origin value.
2192- self .client .setVary ("Origin" )
2195+ self .client .setVary ("Origin" )
21932196
21942197 # Allow-Origin must match origin supplied by client. '*' doesn't
21952198 # work for authenticated requests.
@@ -2234,9 +2237,9 @@ def dispatch(self, method, uri, input):
22342237 except ValueError as msg :
22352238 output = self .error_obj (400 , msg )
22362239 else :
2237- output = self .error_obj (415 ,
2240+ output = self .error_obj (415 ,
22382241 "Unable to process input of type %s" %
2239- content_type_header )
2242+ content_type_header )
22402243
22412244 # check for pretty print
22422245 try :
0 commit comments