33import logging
44import urllib
55import urllib2
6+ import urlparse
7+
8+ import xmltodict
69
710from codebase import logger
11+ from codebase .settings import Settings
812
913
1014class Auth (object ):
11-
15+ CTYPE_JSON = 'json'
16+ CTYPE_XML = 'xml'
1217 API_ENDPOINT = 'https://api3.codebasehq.com'
1318
14- def _default_settings (self ):
15- # prevent import error on AppEngine
16- from codebase .settings import Settings
17- settings = Settings ()
18- self .username = settings .CODEBASE_USERNAME
19- self .apikey = settings .CODEBASE_APIKEY
20-
2119 def __init__ (self , project = None , username = None , apikey = None , ** kwargs ):
2220 super (Auth , self ).__init__ (** kwargs )
2321
24- if username and apikey :
25- self .username = username
26- self .apikey = apikey
27- else :
28- self ._default_settings ()
22+ if not (username or apikey ):
23+ settings = self ._get_settings ()
24+ username = settings .username
25+ apikey = settings .apikey
26+
27+ self .username = username
28+ self .apikey = apikey
2929
3030 self .project = project
3131
32- def get_headers (self ):
32+ def _get_settings (self ):
33+ settings = Settings ()
34+ settings .import_settings ()
35+ return settings
36+
37+ def get_absolute_url (self , path ):
38+ return urlparse .urljoin (self .API_ENDPOINT , path )
39+
40+ def get_headers (self , ctype ):
3341 return {
34- " Content-type" : " application/json" ,
35- " Accept" : " application/json" ,
36- " Authorization" : base64 .b64encode (
42+ ' Content-type' : ' application/{}' . format ( ctype ) ,
43+ ' Accept' : ' application/{}' . format ( ctype ) ,
44+ ' Authorization' : base64 .b64encode (
3745 '{}:{}' .format (self .username , self .apikey )
3846 )
3947 }
4048
41- def get_absolute_url (self , path ):
42- return self .API_ENDPOINT + path
49+ def get_data (self , raw_data , ctype ):
50+ if ctype == self .CTYPE_XML :
51+ return xmltodict .unparse (raw_data )
4352
44- def get (self , url ):
45- absolute_url = self .get_absolute_url (url )
46- headers = self .get_headers ()
47- request = urllib2 .Request (
48- url = absolute_url ,
49- headers = headers ,
50- )
51- logging .info ('Making request to {} with headers {}' .format (
52- request .get_full_url (),
53- request .headers ,
54- ))
55- response = urllib2 .urlopen (request )
56- return self .handle_response (response )
53+ # Encodes the parameters by default (i.e. using json).
54+ return urllib .urlencode (raw_data )
5755
58- def post (self , url , data ):
59- absolute_url = self .get_absolute_url (url )
60- headers = self .get_headers ()
61- request = urllib2 .Request (
62- url = absolute_url ,
63- headers = headers ,
64- data = data ,
65- )
66- response = urllib2 .urlopen (request )
67- return self .handle_response (response )
68-
69- def handle_response (self , response ):
56+ def _handle_response (self , response , ctype ):
7057 try :
7158 status_code = response .getcode ()
7259 content = response .read ()
7360 logger .debug ('{} returned status code {}' .format (
7461 response .url ,
7562 status_code
7663 ))
64+
65+ if ctype == self .CTYPE_XML :
66+ return xmltodict .parse (content )
7767 return json .loads (content )
7868 except Exception as e :
79- logging .exception (e )
69+ logging .exception ('%s: %s' , e .__class__ .__name__ , e .message )
70+
71+ def _send_request (self , url , ctype = None , data = None ):
72+ if not ctype :
73+ ctype = self .CTYPE_JSON
74+
75+ absolute_url = self .get_absolute_url (url )
76+ headers = self .get_headers (ctype )
77+ req_params = {
78+ 'url' : absolute_url ,
79+ 'headers' : headers ,
80+ }
81+ if data :
82+ data = self .get_data (data , ctype )
83+ req_params ['data' ] = data
84+
85+ request = urllib2 .Request (** req_params )
86+ logging .info ('Making request to {} with headers {}' .format (
87+ request .get_full_url (),
88+ request .headers ,
89+ ))
90+
91+ response = urllib2 .urlopen (request )
92+ return self ._handle_response (response , ctype )
93+
94+ def get (self , url , ctype = None ):
95+ return self ._send_request (url , ctype = ctype )
96+
97+ def post (self , url , data , ctype = None ):
98+ return self ._send_request (url , data = data , ctype = ctype )
8099
81100
82101class CodeBaseAPI (Auth ):
@@ -99,13 +118,42 @@ def types(self):
99118 def milestones (self ):
100119 return self .get ('/%s/milestones' % self .project )
101120
102- def search (self , term ):
103- terms = term .split (':' )
104- if len (terms ) == 1 :
105- escaped_term = urllib2 .quote (terms [0 ])
106- else :
107- escaped_term = '{}:"{}"' .format (terms [0 ], urllib2 .quote (terms [1 ]))
108- return self .get ('/%s/tickets?query=%s' % (self .project , escaped_term ))
121+ def search (self , term = None , page = None , ** kwargs ):
122+ queries = []
123+ if term :
124+ queries .append (term .strip ())
125+
126+ for term_name , term_value in kwargs .iteritems ():
127+ if term_name .startswith ('not_' ):
128+ term_name = term_name .replace ('not_' , 'not-' )
129+ queries .append ('{}:"{}"' .format (term_name , term_value .strip ()))
130+
131+ params = {'query' : ' ' .join (queries )}
132+ if page and page > 1 and type (page ) is int :
133+ params ['page' ] = page
134+
135+ url = '/{}/tickets?{}' .format (self .project , urllib .urlencode (params ))
136+ return self .get (url )
137+
138+ def search_all (self , term = None , ** kwargs ):
139+ page = 1
140+ tickets = []
141+ while True :
142+ try :
143+ tickets .extend (self .search (term = term , page = page , ** kwargs ))
144+ page += 1
145+ except urllib2 .HTTPError :
146+ page -= 1
147+ break
148+ except Exception , e :
149+ logger .error (
150+ u'An error occured while searching for "%s" '
151+ u'(current page: %s):\n %s' , term , page , e
152+ )
153+ page -= 1
154+ break
155+
156+ return tickets
109157
110158 def watchers (self , ticket_id ):
111159 return self .get ('/%s/tickets/%s/watchers' % (self .project , ticket_id ))
@@ -137,6 +185,15 @@ def discussions(self):
137185 def discussion_categories (self ):
138186 return self .get ('/%s/discussions/categories' % self .project )
139187
188+ def create_ticket (self , data ):
189+ # It looks like that only XML requests are accepted for creating new
190+ # tickets.
191+ return self .post (
192+ '/%s/tickets' % self .project ,
193+ data ,
194+ ctype = self .CTYPE_XML ,
195+ )
196+
140197 def create_discussion (self , data ):
141198 return self .post ('/%s/discussions' % self .project , data )
142199
@@ -172,4 +229,4 @@ def hooks(self, repository):
172229 return self .get ('/%s/%s/hooks' % (self .project , repository ))
173230
174231 def add_hook (self , repository , data ):
175- return self .get ('/%s/%s/hooks' % (self .project , repository ), data )
232+ return self .post ('/%s/%s/hooks' % (self .project , repository ), data )
0 commit comments