33
44import click
55import requests
6+ from requests_toolbelt .multipart import encoder
7+
68from progress .spinner import Spinner
9+ from progress .bar import Bar
710
811from arcsecond .api .constants import (
912 ARCSECOND_API_URL_DEV ,
1417 API_AUTH_PATH_REGISTER )
1518
1619from arcsecond .api .error import ArcsecondConnectionError , ArcsecondError
20+ from arcsecond .api .helpers import transform_payload_for_multipart_encoder_fields
1721from arcsecond .config import config_file_read_api_key , config_file_read_organisation_memberships
1822from arcsecond .options import State
1923
2024SAFE_METHODS = ['GET' , 'OPTIONS' ]
2125WRITABLE_MEMBERSHIPS = ['superadmin' , 'admin' , 'member' ]
2226
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+
2332
2433class APIEndPoint (object ):
2534 name = None
@@ -84,22 +93,23 @@ def _check_organisation_membership_and_permission(self, method_name, organisatio
8493 memberships = config_file_read_organisation_memberships (self .state .config_section ())
8594 if self .state .organisation not in memberships .keys ():
8695 raise ArcsecondError ('No membership found for organisation {}' .format (organisation ))
96+
8797 membership = memberships [self .state .organisation ]
8898 if method_name not in SAFE_METHODS and membership not in WRITABLE_MEMBERSHIPS :
8999 raise ArcsecondError ('Membership for organisation {} has no write permission' .format (organisation ))
90100
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 ):
93103 try :
94- storage ['response' ] = method (url , json = payload , files = files , headers = headers )
104+ storage ['response' ] = method (url , json = payload , headers = headers )
95105 except requests .exceptions .ConnectionError :
96106 storage ['error' ] = ArcsecondConnectionError (self ._get_base_url ())
97107 except Exception as e :
98108 storage ['error' ] = ArcsecondError (str (e ))
99109
100110 storage = {}
101111 thread = threading .Thread (target = _async_perform_request_store_response ,
102- args = (storage , method , url , payload , files , headers ))
112+ args = (storage , method , url , payload , headers ))
103113 thread .start ()
104114
105115 spinner = Spinner ()
@@ -115,31 +125,59 @@ def _async_perform_request_store_response(storage, method, url, payload, files,
115125
116126 return storage .get ('response' , None )
117127
118- def _perform_request (self , url , method , payload , ** headers ):
128+ def _prepare_request (self , url , method , payload , ** headers ):
119129 assert (url and method )
120130
121131 if not isinstance (method , str ) or callable (method ):
122132 raise ArcsecondError ('Invalid HTTP request method {}. ' .format (str (method )))
123133
124- # Check API key, hence login state. Must do before check for org.
125- headers = self ._check_and_set_api_key (headers , url )
126-
127134 # Put method name aside in its own var.
128135 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
131136
132137 if self .state and self .state .organisation :
133138 self ._check_organisation_membership_and_permission (method_name , self .state .organisation )
134139
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+
135144 if payload :
145+ # Filtering None values out of payload.
136146 payload = {k : v for k , v in payload .items () if v is not None }
137147
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+
138156 if self .state .verbose :
139157 click .echo ('Sending {} request to {}' .format (method_name , url ))
140- click .echo ('Payload: {}' .format (payload ))
141158
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 )
143181
144182 if response is None :
145183 raise ArcsecondConnectionError (url )
@@ -153,16 +191,16 @@ def _perform_request(self, url, method, payload, **headers):
153191 return None , response .text
154192
155193 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 )
157195
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 )
160198
161199 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 )
163201
164202 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 )
166204
167205 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