My favorites | Sign in
Project Home Downloads Wiki Issues Source
New issue   Search
for
  Advanced search   Search tips   Subscriptions

Issue 167 attachment: simple_redirect_patch.py (7.8 KB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
Index: googlecl/base.py
===================================================================
--- googlecl/base.py (revision 458)
+++ googlecl/base.py (working copy)
@@ -18,6 +18,7 @@
import logging
import re
import urllib
+import time

# Renamed here to reduce verbosity in other sections
safe_encode = googlecl.safe_encode
@@ -70,6 +71,15 @@
'max_results',
default=large_max_results,
option_type=int)
+ self.max_retries = googlecl.get_config_option(section,
+ 'max_retries',
+ default=1,
+ option_type=int)
+ self.retry_delay = googlecl.get_config_option(section,
+ 'retry_delay',
+ default=0,
+ option_type=float)
+
try:
service_name = self.auth_service
except AttributeError:
@@ -79,14 +89,15 @@
LOG.warning('You are requesting only ' + str(self.max_results) +
' results per query -- this may be slow')

- def delete_entry_list(self, entries, entry_type, delete_default):
+ def delete_entry_list(self, entries, entry_type, delete_default,
+ callback=None):
"""Extends Delete to handle a list of entries.

Keyword arguments:
entries: List of entries to delete.
entry_type: String describing the thing being deleted (e.g. album, post).
delete_default: Whether or not the default action should be deletion.
-
+ callback: function which takes entry as an argument and deletes it
"""
if delete_default and self.prompt_for_delete:
prompt_str = '(Y/n)'
@@ -104,12 +115,16 @@
delete = True
if delete:
try:
- # Later versions are defined with lowercase function names.
- # These versions take GDataEntry objects, older takes the edit link.
- if hasattr(self, 'delete'):
- self.delete(item)
+ if callback:
+ # if callback is provided then deletion is done by calling it
+ callback(item)
else:
- self.Delete(item.GetEditLink().href)
+ # Later versions are defined with lowercase function names.
+ # These versions take GDataEntry objects, older takes the edit link.
+ if hasattr(self, 'delete'):
+ self.delete(item)
+ else:
+ self.Delete(item.GetEditLink().href)
except self.request_error, err:
LOG.warning('Could not delete ' + entry_type + ': ' + str(err))

@@ -188,7 +203,8 @@
"""
# XXX: Should probably go through all code and make sure title can only be
# NoneType or list, not also maybe a string.
- uri = set_max_results(uri, self.max_results)
+ if self.max_results is not None:
+ uri = set_max_results(uri, self.max_results)
if isinstance(uri, unicode):
uri = uri.encode('utf-8')
feed = None
@@ -326,7 +342,34 @@
raise NotImplementedError('request_access must be defined!')
RequestAccess = request_access

+ def retry_operation(self, *args, **kwargs):
+ try_forever = self.max_retries >= 0
+ attempts_remaining = self.max_retries
+ err = None
+ while try_forever or attempts_remaining:
+ try:
+ return self.original_operation(*args, **kwargs)
+ except self.request_error, err:
+ error = str(err)
+ if (error.find('Moved Temporarily') != -1 or
+ error.find('Redirect received, but redirects_remaining <= 0') != -1):
+ attempts_remaining -= 1
+ LOG.debug('Retrying when you would have failed otherwise!')
+ LOG.debug('Arguments: %s' % args)
+ LOG.debug('Keyword arguments: %s' % kwargs)
+ LOG.debug('Error: %s' % err)
+ time.sleep(self.retry_delay)
+ else:
+ raise err
+ except Exception, unexpected:
+ LOG.debug('unexpected exception: %s' % unexpected)
+ LOG.debug('Arguments: %s' % args)
+ LOG.debug('Keyword arguments: %s' % kwargs)
+ raise unexpected
+ # Can only leave above loop if err is set at least once.
+ raise err

+
def set_max_results(uri, max):
"""Set max-results parameter if it is not set already."""
max_str = str(max)
Index: googlecl/client.py
===================================================================
--- googlecl/client.py (revision 458)
+++ googlecl/client.py (working copy)
@@ -36,6 +36,11 @@
*args, **kwargs):
super(BaseClientCL, self).__init__(section, request_error_class,
*args, **kwargs)
+ # Used for automatic retries of requests that fail due to 302 errors.
+ # See BaseCL.retry_operation.
+ self.original_request = self.request
+ self.request = self.retry_request
+
LOG.debug('Initialized googlecl.client.BaseClientCL')

def is_token_valid(self, test_uri):
@@ -48,6 +53,10 @@

IsTokenValid = is_token_valid

+ def retry_request(self, *args, **kwargs):
+ self.original_operation = self.original_request
+ return self.retry_operation(*args, **kwargs)
+
def request_access(self, domain, hostid, scopes=None):
"""Do all the steps involved with getting an OAuth access token.

Index: googlecl/service.py
===================================================================
--- googlecl/service.py (revision 458)
+++ googlecl/service.py (working copy)
@@ -37,8 +37,24 @@
**kwargs)
# Most services using old gdata API have to disable ssl.
self.ssl = False
+
+ # Used for automatic retries of Get/Delete requests that fail due to 302
+ # errors. See BaseCL.retry_operation.
+ self.original_get = self.Get
+ self.Get = self.retry_get
+ self.original_delete = self.Delete
+ self.Delete = self.retry_delete
+
LOG.debug('Initialized googlecl.service.BaseServiceCL')

+ def retry_get(self, *args, **kwargs):
+ self.original_operation = self.original_get
+ return self.retry_operation(*args, **kwargs)
+
+ def retry_delete(self, *args, **kwargs):
+ self.original_operation = self.original_delete
+ return self.retry_operation(*args, **kwargs)
+
def request_access(self, domain, hostid, scopes=None):
"""Do all the steps involved with getting an OAuth access token.

Index: googlecl/__init__.py
===================================================================
--- googlecl/__init__.py (revision 458)
+++ googlecl/__init__.py (working copy)
@@ -259,17 +259,19 @@
_youtube = {'max_results': '50'}
_contacts = {'fields': 'name,email'}
_calendar = {'fields': 'title,when'}
_picasa = {'access': 'public'}
- _general = {'regex': 'True',
- 'delete_by_default': 'False',
- 'delete_prompt': 'True',
- 'tags_prompt': 'False',
- 'url_field': 'site',
- 'fields': 'title,url-site',
- 'missing_field_value': 'N/A',
- 'date_print_format': '%b %d %H:%M',
- 'cap_results': 'False',
- 'hostid': default_hostid}
+ _general = {'max_retries': '2',
+ 'retry_delay': '0.5',
+ 'regex': 'True',
+ 'delete_by_default': 'False',
+ 'delete_prompt': 'True',
+ 'tags_prompt': 'False',
+ 'url_field': 'site',
+ 'fields': 'title,url-site',
+ 'missing_field_value': 'N/A',
+ 'date_print_format': '%b %d %H:%M',
+ 'cap_results': 'False',
+ 'hostid': default_hostid}
_docs = {'document_format': 'txt',
'spreadsheet_format': 'xls',
'presentation_format': 'ppt',
Powered by Google Project Hosting