Skip to content

Commit 49cdfd7

Browse files
committed
Add post_files_with_request method for python client.
1 parent 78707f8 commit 49cdfd7

File tree

5 files changed

+3720
-1
lines changed

5 files changed

+3720
-1
lines changed

servicestack/clients.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
import requests
1111
from dataclasses_json import dataclass_json
1212
from requests.exceptions import HTTPError
13-
from requests.models import Response
13+
from requests.models import Response,RequestField, encode_multipart_formdata
14+
15+
from dataclasses import dataclass
16+
from typing import BinaryIO, List, Optional, TypeVar, Union
17+
import mimetypes
1418

1519
from servicestack.dtos import IReturn, IReturnVoid, IGet, IPost, IPut, IPatch, \
1620
IDelete, ResponseStatus, EmptyResponse, GetAccessToken, GetAccessTokenResponse
@@ -195,6 +199,17 @@ class WebServiceException(Exception):
195199

196200
T = TypeVar('T')
197201

202+
@dataclass
203+
class UploadFile:
204+
field_name: Optional[str]
205+
file_name: Optional[str]
206+
content_type: Optional[str]
207+
stream: BinaryIO
208+
209+
def __post_init__(self):
210+
if not self.content_type and self.file_name:
211+
guessed_type = mimetypes.guess_type(self.file_name)[0]
212+
self.content_type = guessed_type or 'application/octet-stream'
198213

199214
class JsonServiceClient:
200215
base_url: str = None
@@ -351,6 +366,71 @@ def send(self, request, method: Any = None, body: Any = None, args: Dict[str, An
351366
args=args,
352367
response_as=response_as))
353368

369+
370+
def post_files_with_request(self, request_uri: str, request: Any,
371+
files: Union[UploadFile, List[UploadFile]]) -> T:
372+
"""
373+
Post files with a request DTO using multipart/form-data
374+
375+
:param request_uri: The request URI
376+
:param request: The request DTO
377+
:param files: Single UploadFile or List of UploadFile objects
378+
:return: Response DTO
379+
"""
380+
if isinstance(files, UploadFile):
381+
files = [files]
382+
383+
# Convert request DTO to dict
384+
request_dict = to_dict(request, key_case=clean_camelcase)
385+
386+
# Prepare the files dictionary for requests
387+
files_dict = {}
388+
for file in files:
389+
files_dict[file.field_name or 'upload'] = (
390+
file.file_name,
391+
file.stream,
392+
file.content_type or 'application/octet-stream'
393+
)
394+
395+
# Prepare headers
396+
headers = self.headers.copy()
397+
if self.bearer_token:
398+
headers['Authorization'] = f'Bearer {self.bearer_token}'
399+
400+
url = self.to_absolute_url(request_uri)
401+
402+
try:
403+
# Send the multipart request
404+
response = self._session.post(
405+
url,
406+
data=request_dict,
407+
files=files_dict,
408+
headers=headers,
409+
verify=self._session.verify
410+
)
411+
412+
# Handle errors
413+
response.raise_for_status()
414+
415+
# Parse response
416+
response_type = _resolve_response_type(request)
417+
if response_type is None:
418+
return response.json()
419+
420+
if response_type is str:
421+
return response.text
422+
423+
if response_type is bytes:
424+
return response.content
425+
426+
return from_json(response_type, response.text)
427+
428+
finally:
429+
# Close all file streams
430+
for file in files:
431+
file.stream.close()
432+
433+
354434
@staticmethod
355435
def assert_valid_batch_request(request_dtos: list):
356436
if not isinstance(request_dtos, list):

0 commit comments

Comments
 (0)