@@ -255,14 +255,68 @@ def get_attachment(self, attachment_id: T_id) -> T_resp_json:
255255 url = f"{ base_url } /{ attachment_id } "
256256 return self .get (url )
257257
258- def download_issue_attachments (self , issue : T_id , path : Optional [str ] = None ) -> Optional [str ]:
258+ def download_issue_attachments (
259+ self ,
260+ issue : str ,
261+ path : Optional [str ] = None ,
262+ overwrite : bool = False ,
263+ stream : bool = False ,
264+ block_size : Optional [int ] = 16384 ,
265+ timeout : Optional [int ] = None ,
266+ ) -> Optional [str ]:
259267 """
260268 Downloads all attachments from a Jira issue.
261269 :param issue: The issue-key of the Jira issue
262270 :param path: Path to directory where attachments will be saved. If None, current working directory will be used.
271+ :param overwrite: If True, always download and create new zip file.
272+ If False (default), download will be skipped when zip file already exists in path.
273+ :param stream: If True, request stream mode will be used to download and write files.
274+ If False (default), whole attachment content will be downloaded into memory first, then will be written to disk afterwards.
275+ :param block_size: Block size of each stream content chunks. This option is only applicable when stream=True is set.
276+ Default size of 16 KiB is used to balance speed and memory usage.
277+ Smaller value will decrease memory usage, but may also decrease download speed if too small.
278+ :param timeout: Request timeout parameter in seconds. None (default) will never cause timeout.
263279 :return: A message indicating the result of the download operation.
264280 """
265- return self .download_attachments_from_issue (issue = issue , path = path , cloud = self .cloud )
281+ try :
282+ if path is None :
283+ path = os .getcwd ()
284+ issue_id = self .issue (issue , fields = "id" )["id" ]
285+ attachment_name = f"{ issue_id } _attachments.zip"
286+ file_path = os .path .join (path , attachment_name )
287+ if not overwrite and os .path .isfile (file_path ):
288+ return "File already exists"
289+
290+ if self .cloud :
291+ url = self .url + f"/secure/issueAttachments/{ issue_id } .zip"
292+ else :
293+ url = self .url + f"/secure/attachmentzip/{ issue_id } .zip"
294+ response = self ._session .get (url , stream = stream , timeout = timeout )
295+ response .raise_for_status ()
296+
297+ # if Jira issue doesn't have any attachments _session.get
298+ # request response will return 22 bytes of PKzip format
299+ file_size = int (response .headers .get ("Content-Length" , 0 ))
300+ if file_size == 22 :
301+ return "No attachments found on the Jira issue"
302+
303+ with open (file_path , "wb" ) as file :
304+ if not stream :
305+ file .write (response .content )
306+ else :
307+ for data in response .iter_content (block_size ):
308+ file .write (data )
309+
310+ return "Attachments downloaded successfully"
311+
312+ except FileNotFoundError :
313+ raise FileNotFoundError ("Verify if directory path is correct and/or if directory exists" )
314+ except PermissionError :
315+ raise PermissionError (
316+ "Directory found, but there is a problem with saving file to this directory. Check directory permissions"
317+ )
318+ except Exception as e :
319+ raise e
266320
267321 @deprecated (version = "3.41.20" , reason = "Use download_issue_attachments instead" )
268322 def download_attachments_from_issue (
@@ -312,9 +366,17 @@ def get_attachment_content(self, attachment_id: T_id) -> bytes:
312366 :param attachment_id: int
313367 :return: content as bytes
314368 """
315- base_url = self .resource_url ("attachment" )
316- url = f"{ base_url } /content/{ attachment_id } "
317- return self .get (url , not_json_response = True )
369+ attachment_info = self .get_attachment (attachment_id )
370+ # Type check for mypy. If attachment is not found, or unavailable, it would raise HTTPError anyways.
371+ if attachment_info is None :
372+ return b""
373+ url = attachment_info ["content" ]
374+ return self .get (
375+ url ,
376+ not_json_response = True ,
377+ absolute = True ,
378+ headers = {"Accept" : "*/*" },
379+ )
318380
319381 def remove_attachment (self , attachment_id : T_id ) -> T_resp_json :
320382 """
0 commit comments