1
1
'''
2
2
dimensions.py: Dimensions communication code for Sidewall
3
3
4
+ Implementation notes
5
+ --------------------
6
+
7
+ Sidewall caches the returned values of Dimensions queries as well as the data
8
+ objects created by Sidewall. Not all objects are persisted, because some
9
+ objects only make sense when interpreted in the context of a query. Only
10
+ those classes that use the mixin "Persistable" are stored.
11
+
12
+ Currently, the cache is simply a Python dict and it is not saved to disk. A
13
+ future enhancement would be to provide a way to save the values to disk so
14
+ that they can be retained across invocations of Sidewall.
15
+
4
16
Authors
5
17
-------
6
18
@@ -98,6 +110,13 @@ def login(self, username = None, password = None,
98
110
'''Store credentials for using the Dimensions network API.
99
111
If values for 'username' and 'password' are not provided, this will
100
112
ask the user for them interactively. Values will be stored in the
113
+ user's keychain/keyring, unless the parameter use_keyring = False.
114
+
115
+ To reset the user name and password (e.g., if a mistake was made the
116
+ last time and the wrong credentials were stored in the
117
+ keyring/keychain system), invoke this method with reset_keyring = True.
118
+ It will then query for the user name and password again, even if values
119
+ already exist in the keyring or keychain.
101
120
'''
102
121
if __debug__ : log ('user = {}, pass = {}' , username , 'X' if password else '' )
103
122
self ._use_keyring = use_keyring
@@ -121,35 +140,6 @@ def login(self, username = None, password = None,
121
140
raise AuthenticationFailure ('Dimensions did not return a token' )
122
141
123
142
124
- _strip_whitespace = str .maketrans ('' , '' , string .whitespace )
125
-
126
- def record_search (self , query , id , retry = 1 ):
127
- if __debug__ : log ('initiating record search involving {}' .format (id ))
128
- search = 'search ' + query .format (id )
129
- key = search .translate (self ._strip_whitespace )
130
- if key in self ._cache :
131
- if __debug__ : log ("returning cached value for '{}'" , search )
132
- return self ._cache [key ]
133
-
134
- data = self ._post (search )
135
- if __debug__ : log ('response: {}' , data )
136
- self ._cache [key ] = data
137
- if data == {}:
138
- return {}
139
- # Due to the fact that the results may not be unique and contain a
140
- # single record, we end up having to search for the record matching
141
- # the id we're interested in. The type results from Dimensions will
142
- # be of the form {'_stats': ..., 'TYPE': [...]} where TYPE is the
143
- # kind of entity in question.
144
- result_keys = list_diff (list (data .keys ()), ['_stats' ])
145
- if len (result_keys ) > 1 :
146
- raise DataMismatch ('Unexpected keys in Dimensions results: {}'
147
- .format (list (data .keys ())))
148
- if len (result_keys ) == 0 :
149
- return {}
150
- return matching_record (data , result_keys [0 ], id )
151
-
152
-
153
143
def query (self , query_string , limit_results = None , fetch_size = _FETCH_SIZE ):
154
144
'''Issue the DSL 'query_string' to Dimensions and return an iterator
155
145
for the results. Each item in the results will be an object such as
@@ -214,8 +204,39 @@ def query(self, query_string, limit_results = None, fetch_size = _FETCH_SIZE):
214
204
total , data , result_type , fetch_size )
215
205
216
206
207
+ _strip_whitespace = str .maketrans ('' , '' , string .whitespace )
208
+
209
+ def record_search (self , query , id , retry = 1 ):
210
+ '''Internal method for filling in missing data field values.'''
211
+ if __debug__ : log ('initiating record search involving {}' .format (id ))
212
+ search = 'search ' + query .format (id )
213
+ key = search .translate (self ._strip_whitespace )
214
+ if key in self ._cache :
215
+ if __debug__ : log ("returning cached value for '{}'" , search )
216
+ return self ._cache [key ]
217
+
218
+ data = self ._post (search )
219
+ if __debug__ : log ('response: {}' , data )
220
+ self ._cache [key ] = data
221
+ if data == {}:
222
+ return {}
223
+ # Due to the fact that the results may not be unique and contain a
224
+ # single record, we end up having to search for the record matching
225
+ # the id we're interested in. The type results from Dimensions will
226
+ # be of the form {'_stats': ..., 'TYPE': [...]} where TYPE is the
227
+ # kind of entity in question.
228
+ result_keys = list_diff (list (data .keys ()), ['_stats' ])
229
+ if len (result_keys ) > 1 :
230
+ raise DataMismatch ('Unexpected keys in Dimensions results: {}'
231
+ .format (list (data .keys ())))
232
+ if len (result_keys ) == 0 :
233
+ return {}
234
+ return matching_record (data , result_keys [0 ], id )
235
+
236
+
217
237
def _post (self , query , retry = 1 ):
218
- '''Post the 'query' to the server and return the result as a dict.
238
+ '''Internal method to post the 'query' string to the server and return
239
+ the result as a dict.
219
240
'''
220
241
if __debug__ : log ("posting query to server: '{}'" , query )
221
242
headers = {'Authorization' : "JWT " + self ._dimensions_token }
@@ -276,6 +297,7 @@ def _credentials(self, user, pswd):
276
297
277
298
278
299
def _password (self , prompt ):
300
+ '''Ask the user for a password interactively.'''
279
301
# If it's a tty, use the version that doesn't echo the password.
280
302
if sys .stdin .isatty ():
281
303
return getpass .getpass (prompt )
0 commit comments