3
3
4
4
import click
5
5
import requests
6
+ from requests_toolbelt .multipart import encoder
7
+
6
8
from progress .spinner import Spinner
9
+ from progress .bar import Bar
7
10
8
11
from arcsecond .api .constants import (
9
12
ARCSECOND_API_URL_DEV ,
14
17
API_AUTH_PATH_REGISTER )
15
18
16
19
from arcsecond .api .error import ArcsecondConnectionError , ArcsecondError
20
+ from arcsecond .api .helpers import transform_payload_for_multipart_encoder_fields
17
21
from arcsecond .config import config_file_read_api_key , config_file_read_organisation_memberships
18
22
from arcsecond .options import State
19
23
20
24
SAFE_METHODS = ['GET' , 'OPTIONS' ]
21
25
WRITABLE_MEMBERSHIPS = ['superadmin' , 'admin' , 'member' ]
22
26
27
+ EVENT_METHOD_WILL_START = 'EVENT_METHOD_WILL_START'
28
+ EVENT_METHOD_DID_FINISH = 'EVENT_METHOD_DID_FINISH'
29
+ EVENT_METHOD_DID_FAIL = 'EVENT_METHOD_DID_FAIL'
30
+ EVENT_METHOD_PROGRESS_PERCENT = 'EVENT_METHOD_PROGRESS_PERCENT'
31
+
23
32
24
33
class APIEndPoint (object ):
25
34
name = None
@@ -84,22 +93,23 @@ def _check_organisation_membership_and_permission(self, method_name, organisatio
84
93
memberships = config_file_read_organisation_memberships (self .state .config_section ())
85
94
if self .state .organisation not in memberships .keys ():
86
95
raise ArcsecondError ('No membership found for organisation {}' .format (organisation ))
96
+
87
97
membership = memberships [self .state .organisation ]
88
98
if method_name not in SAFE_METHODS and membership not in WRITABLE_MEMBERSHIPS :
89
99
raise ArcsecondError ('Membership for organisation {} has no write permission' .format (organisation ))
90
100
91
- def _async_perform_request (self , url , method , payload = None , files = None , ** headers ):
92
- def _async_perform_request_store_response (storage , method , url , payload , files , headers ):
101
+ def _async_perform_request (self , url , method , payload = None , ** headers ):
102
+ def _async_perform_request_store_response (storage , method , url , payload , headers ):
93
103
try :
94
- storage ['response' ] = method (url , json = payload , files = files , headers = headers )
104
+ storage ['response' ] = method (url , json = payload , headers = headers )
95
105
except requests .exceptions .ConnectionError :
96
106
storage ['error' ] = ArcsecondConnectionError (self ._get_base_url ())
97
107
except Exception as e :
98
108
storage ['error' ] = ArcsecondError (str (e ))
99
109
100
110
storage = {}
101
111
thread = threading .Thread (target = _async_perform_request_store_response ,
102
- args = (storage , method , url , payload , files , headers ))
112
+ args = (storage , method , url , payload , headers ))
103
113
thread .start ()
104
114
105
115
spinner = Spinner ()
@@ -115,31 +125,59 @@ def _async_perform_request_store_response(storage, method, url, payload, files,
115
125
116
126
return storage .get ('response' , None )
117
127
118
- def _perform_request (self , url , method , payload , ** headers ):
128
+ def _prepare_request (self , url , method , payload , ** headers ):
119
129
assert (url and method )
120
130
121
131
if not isinstance (method , str ) or callable (method ):
122
132
raise ArcsecondError ('Invalid HTTP request method {}. ' .format (str (method )))
123
133
124
- # Check API key, hence login state. Must do before check for org.
125
- headers = self ._check_and_set_api_key (headers , url )
126
-
127
134
# Put method name aside in its own var.
128
135
method_name = method .upper () if isinstance (method , str ) else ''
129
- method = getattr (requests , method .lower ()) if isinstance (method , str ) else method
130
- files = payload .pop ('files' , None ) if payload else None
131
136
132
137
if self .state and self .state .organisation :
133
138
self ._check_organisation_membership_and_permission (method_name , self .state .organisation )
134
139
140
+ # Check API key, hence login state. Must do before check for org.
141
+ headers = self ._check_and_set_api_key (headers , url )
142
+ method = getattr (requests , method .lower ()) if isinstance (method , str ) else method
143
+
135
144
if payload :
145
+ # Filtering None values out of payload.
136
146
payload = {k : v for k , v in payload .items () if v is not None }
137
147
148
+ return url , method_name , method , payload , headers
149
+
150
+ def _perform_request (self , url , method , payload , callback = None , ** headers ):
151
+ if self .state .verbose :
152
+ click .echo ('Preparing request...' )
153
+
154
+ url , method_name , method , payload , headers = self ._prepare_request (url , method , payload , ** headers )
155
+
138
156
if self .state .verbose :
139
157
click .echo ('Sending {} request to {}' .format (method_name , url ))
140
- click .echo ('Payload: {}' .format (payload ))
141
158
142
- response = self ._async_perform_request (url , method , payload , files , ** headers )
159
+ payload , fields = transform_payload_for_multipart_encoder_fields (payload )
160
+ if fields :
161
+ encoded_data = encoder .MultipartEncoder (fields = fields )
162
+ bar , upload_callback = None , None
163
+
164
+ if self .state .is_using_cli is False and callback :
165
+ upload_callback = lambda m : callback (EVENT_METHOD_PROGRESS_PERCENT , m .bytes_read / m .len * 100 )
166
+ elif self .state .verbose :
167
+ bar = Bar ('Uploading ' + fields ['file' ][0 ], suffix = '%(percent)d%%' )
168
+ upload_callback = lambda m : bar .goto (m .bytes_read / m .len * 100 )
169
+
170
+ upload_monitor = encoder .MultipartEncoderMonitor (encoded_data , upload_callback )
171
+ headers .update (** {'Content-Type' : upload_monitor .content_type })
172
+ response = method (url , data = upload_monitor , headers = headers )
173
+
174
+ if self .state .verbose :
175
+ bar .finish ()
176
+ else :
177
+ if self .state .verbose :
178
+ click .echo ('Payload: {}' .format (payload ))
179
+
180
+ response = self ._async_perform_request (url , method , payload , ** headers )
143
181
144
182
if response is None :
145
183
raise ArcsecondConnectionError (url )
@@ -153,16 +191,16 @@ def _perform_request(self, url, method, payload, **headers):
153
191
return None , response .text
154
192
155
193
def list (self , name = '' , ** headers ):
156
- return self ._perform_request (self ._list_url (name ), 'get' , None , ** headers )
194
+ return self ._perform_request (self ._list_url (name ), 'get' , None , None , ** headers )
157
195
158
- def create (self , payload , ** headers ):
159
- return self ._perform_request (self ._list_url (), 'post' , payload , ** headers )
196
+ def create (self , payload , callback = None , ** headers ):
197
+ return self ._perform_request (self ._list_url (), 'post' , payload , callback , ** headers )
160
198
161
199
def read (self , id_name_uuid , ** headers ):
162
- return self ._perform_request (self ._detail_url (id_name_uuid ), 'get' , None , ** headers )
200
+ return self ._perform_request (self ._detail_url (id_name_uuid ), 'get' , None , None , ** headers )
163
201
164
202
def update (self , id_name_uuid , payload , ** headers ):
165
- return self ._perform_request (self ._detail_url (id_name_uuid ), 'put' , payload , ** headers )
203
+ return self ._perform_request (self ._detail_url (id_name_uuid ), 'put' , payload , None , ** headers )
166
204
167
205
def delete (self , id_name_uuid , ** headers ):
168
- return self ._perform_request (self ._detail_url (id_name_uuid ), 'delete' , None , ** headers )
206
+ return self ._perform_request (self ._detail_url (id_name_uuid ), 'delete' , None , None , ** headers )
0 commit comments