From 2e8e1b004c795891a08d2b610d5c6bab333665aa Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Sat, 3 Feb 2024 21:07:48 +0100 Subject: [PATCH 1/7] fixing minor issue in scrap_regex_from_issue method --- atlassian/jira.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 6a1250779..b195908ac 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -1548,15 +1548,16 @@ def scrap_regex_from_issue(self, issue, regex): comments = issue_output["fields"]["comment"]["comments"] try: - description_matches = [x.group(0) for x in re.finditer(regex, description)] - if description_matches: - regex_output.extend(description_matches) - - for comment in comments: - comment_html = comment["body"] - comment_matches = [x.group(0) for x in re.finditer(regex, comment_html)] - if comment_matches: - regex_output.extend(comment_matches) + if description is not None: + description_matches = [x.group(0) for x in re.finditer(regex, description)] + if description_matches: + regex_output.extend(description_matches) + + for comment in comments: + comment_html = comment["body"] + comment_matches = [x.group(0) for x in re.finditer(regex, comment_html)] + if comment_matches: + regex_output.extend(comment_matches) return regex_output except HTTPError as e: From 715463b82e5642ba969d333dc9b89e747b4220d4 Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Sun, 4 Feb 2024 17:31:52 +0100 Subject: [PATCH 2/7] new Confluence method scrap_regex_from_page+ docs + examples --- atlassian/confluence.py | 28 ++++++++++++++++++- docs/confluence.rst | 5 +++- .../confluence_scrap_regex_from_page.py | 13 +++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 examples/confluence/confluence_scrap_regex_from_page.py diff --git a/atlassian/confluence.py b/atlassian/confluence.py index ed0137841..f8d102f4b 100644 --- a/atlassian/confluence.py +++ b/atlassian/confluence.py @@ -3,7 +3,7 @@ import os import time import json - +import re from requests import HTTPError import requests from deprecated import deprecated @@ -397,6 +397,32 @@ def get_tables_from_page(self, page_id): except Exception as e: log.error("Error occured", e) + def scrap_regex_from_page(self, page_id, regex): + """ + Method scraps regex patterns from a Confluence page_id. + + :param page_id: The ID of the Confluence page. + :param regex: The regex pattern to scrape. + :return: A list of regex matches. + """ + regex_output = [] + page_output = self.get_page_by_id(page_id, expand="body.storage")["body"]["storage"]["value"] + try: + if page_output is not None: + description_matches = [x.group(0) for x in re.finditer(regex, page_output)] + if description_matches: + regex_output.extend(description_matches) + return regex_output + except HTTPError as e: + if e.response.status_code == 404: + # Raise ApiError as the documented reason is ambiguous + log.error("couldn't find page_id : ", page_id) + raise ApiNotFoundError( + "There is no content with the given page id," + "or the calling user does not have permission to view the page", + reason=e, + ) + def get_page_labels(self, page_id, prefix=None, start=None, limit=None): """ Returns the list of labels on a piece of Content. diff --git a/docs/confluence.rst b/docs/confluence.rst index 8b01cd136..00fb3d90e 100644 --- a/docs/confluence.rst +++ b/docs/confluence.rst @@ -156,7 +156,10 @@ Page actions confluence.add_comment(page_id, text) # Fetch tables from Confluence page - confluence.get_page_tables(page_id) + confluence.get_tables_from_page(page_id) + + # Get regex matches from Confluence page + confluence.scrap_regex_from_page(page_id, regex) Template actions ---------------- diff --git a/examples/confluence/confluence_scrap_regex_from_page.py b/examples/confluence/confluence_scrap_regex_from_page.py new file mode 100644 index 000000000..03225875b --- /dev/null +++ b/examples/confluence/confluence_scrap_regex_from_page.py @@ -0,0 +1,13 @@ +from atlassian import Confluence + + +confluence = Confluence( + url="", + username="", + password="api_key", +) +page_id = 393464 +ipv4_regex = r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" +confluence.scrap_regex_from_page( + page_id, ipv4_regex +) # method returns list of matches of ipv4 addresses from page content. From 9fd7ae4cec5ded8a7364e967c18d6b57e13c1d84 Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Thu, 8 Feb 2024 16:26:56 +0100 Subject: [PATCH 3/7] added method get_attachments_ids_from_page to jira.py --- atlassian/jira.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 958a5e19b..9b80a881c 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -162,7 +162,17 @@ def get_application_role(self, role_key): Attachments Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/attachment """ - + def get_attachments_ids_from_page(self, issue_id): + """ + Get attachments from page + :param issue_id: int + :return: list of attachments + """ + test = self.get_issue(issue_id)['fields']['attachment'] + output = [] + for i in test: + output.append({"filename": i['filename'], "attachment_id": i['id']}) + return output def get_attachment(self, attachment_id): """ Returns the meta-data for an attachment, including the URI of the actual attached file From f69534626b821d39bc35f7e3fdac4bd53adfc911 Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Thu, 8 Feb 2024 22:54:22 +0100 Subject: [PATCH 4/7] added method download_attachments_from_issue --- atlassian/jira.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/atlassian/jira.py b/atlassian/jira.py index 9b80a881c..414434f60 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -1,6 +1,7 @@ # coding=utf-8 import logging import re +import os from warnings import warn from deprecated import deprecated from requests import HTTPError @@ -183,6 +184,30 @@ def get_attachment(self, attachment_id): url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) + def downlaod_all_attachments_from_page(self, page_id, path=None): + + """ + Downloads all attachments from a page + :param page_id: + :param path: path to directory where attachments will be saved. If None, current working directory will be used. + :return info message: number of saved attachments + path to directory where attachments were saved: + """ + if path is None: + path = os.getcwd() + + issue_id = self.issue(page_id, fields='id')['id'] + # test_ur https://gregstestinginstance666.atlassian.net/secure/issueAttachments/10003.zip + url = self.url + f"/secure/issueAttachments/{issue_id}.zip" + r = self._session.get(url) + attachment_name = f"{page_id}_attachments.zip" + print(path) + file_path = os.path.join(path, attachment_name) + + with open(file_path, "wb") as f: + f.write(r.content) + + return "test" + def get_attachment_content(self, attachment_id): """ Returns the content for an attachment From 6f3c1f60b1c9608f41425edcf69c24f0a48eee82 Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Thu, 8 Feb 2024 23:35:26 +0100 Subject: [PATCH 5/7] refactoring download_all_attachments_from_page method --- atlassian/jira.py | 54 +++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 414434f60..c23386f10 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -163,17 +163,19 @@ def get_application_role(self, role_key): Attachments Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/attachment """ + def get_attachments_ids_from_page(self, issue_id): """ Get attachments from page :param issue_id: int :return: list of attachments """ - test = self.get_issue(issue_id)['fields']['attachment'] + test = self.get_issue(issue_id)["fields"]["attachment"] output = [] for i in test: - output.append({"filename": i['filename'], "attachment_id": i['id']}) + output.append({"filename": i["filename"], "attachment_id": i["id"]}) return output + def get_attachment(self, attachment_id): """ Returns the meta-data for an attachment, including the URI of the actual attached file @@ -184,29 +186,41 @@ def get_attachment(self, attachment_id): url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def downlaod_all_attachments_from_page(self, page_id, path=None): - - """ - Downloads all attachments from a page - :param page_id: - :param path: path to directory where attachments will be saved. If None, current working directory will be used. - :return info message: number of saved attachments + path to directory where attachments were saved: - """ - if path is None: - path = os.getcwd() - - issue_id = self.issue(page_id, fields='id')['id'] - # test_ur https://gregstestinginstance666.atlassian.net/secure/issueAttachments/10003.zip + def download_all_attachments_from_page(self, issue_id, path=None): + """ + Downloads all attachments from a Jira issue. + :param issue_id: The ID of the Jira issue. + :param path: Path to directory where attachments will be saved. If None, current working directory will be used. + :return: A message indicating the result of the download operation. + """ + try: + issue_id = self.issue(issue_id, fields="id")["id"] url = self.url + f"/secure/issueAttachments/{issue_id}.zip" - r = self._session.get(url) - attachment_name = f"{page_id}_attachments.zip" - print(path) + response = self._session.get(url) + attachment_name = f"{issue_id}_attachments.zip" file_path = os.path.join(path, attachment_name) + file_size = sum(len(chunk) for chunk in response.iter_content(8196)) + + # if Jira issue doesn't have any attachments _session.get request response will return 22 bytes of PK zip format + if file_size == 22: + return "No attachments found on the Jira issue" + + if os.path.isfile(file_path): + return "File already exists" with open(file_path, "wb") as f: - f.write(r.content) + f.write(response.content) - return "test" + return "Attachments downloaded successfully" + + except FileNotFoundError: + raise FileNotFoundError("Verify if directory path is correct and/or if directory exists") + except PermissionError: + raise PermissionError( + "Directory found, but there is a problem with saving file to this directory. Check directory permissions" + ) + except Exception as e: + raise e def get_attachment_content(self, attachment_id): """ From 7fd73d85daf0dbdc32d1400d0960323ca5b3011b Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Fri, 9 Feb 2024 15:02:21 +0100 Subject: [PATCH 6/7] finished download_attachments_from_issue --- atlassian/jira.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index c23386f10..2a3d22e97 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -164,17 +164,17 @@ def get_application_role(self, role_key): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/attachment """ - def get_attachments_ids_from_page(self, issue_id): + def get_attachments_ids_from_issue(self, issue): """ - Get attachments from page - :param issue_id: int - :return: list of attachments + Get attachments IDs from jira issue + :param jira issue key: str + :return: list of integers attachment IDs """ - test = self.get_issue(issue_id)["fields"]["attachment"] - output = [] - for i in test: - output.append({"filename": i["filename"], "attachment_id": i["id"]}) - return output + issue_id = self.get_issue(issue)["fields"]["attachment"] + list_attachments_id = [] + for attachment in issue_id: + list_attachments_id.append({"filename": attachment["filename"], "attachment_id": attachment["id"]}) + return list_attachments_id def get_attachment(self, attachment_id): """ @@ -186,31 +186,33 @@ def get_attachment(self, attachment_id): url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def download_all_attachments_from_page(self, issue_id, path=None): + def download_attachments_from_issue(self, issue, path=None, cloud=True): """ Downloads all attachments from a Jira issue. - :param issue_id: The ID of the Jira issue. + :param issue: The issue-key of the Jira issue :param path: Path to directory where attachments will be saved. If None, current working directory will be used. + :param cloud: Use True for Jira Cloud, false when using Jira Data Center or Server :return: A message indicating the result of the download operation. """ try: - issue_id = self.issue(issue_id, fields="id")["id"] - url = self.url + f"/secure/issueAttachments/{issue_id}.zip" + if path is None: + path = os.getcwd() + issue_id = self.issue(issue, fields="id")["id"] + if cloud: + url = self.url + f"/secure/issueAttachments/{issue_id}.zip" + else: + url = self.url + f"/secure/attachmentzip/{issue_id}.zip" response = self._session.get(url) attachment_name = f"{issue_id}_attachments.zip" file_path = os.path.join(path, attachment_name) + # if Jira issue doesn't have any attachments _session.get request response will return 22 bytes of PKzip format file_size = sum(len(chunk) for chunk in response.iter_content(8196)) - - # if Jira issue doesn't have any attachments _session.get request response will return 22 bytes of PK zip format if file_size == 22: return "No attachments found on the Jira issue" - if os.path.isfile(file_path): return "File already exists" - with open(file_path, "wb") as f: f.write(response.content) - return "Attachments downloaded successfully" except FileNotFoundError: From ef752abc5ec665fbc5af331d33413c5837e47bb5 Mon Sep 17 00:00:00 2001 From: gkowalc <> Date: Fri, 9 Feb 2024 15:58:57 +0100 Subject: [PATCH 7/7] added two new methods: download_attachments.from_issue and get_attachments_ids_from_issue --- docs/jira.rst | 6 ++++++ examples/jira/jira_download_attachments.from_issue.py | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 examples/jira/jira_download_attachments.from_issue.py diff --git a/docs/jira.rst b/docs/jira.rst index 5e8e3be05..e6c761b5b 100644 --- a/docs/jira.rst +++ b/docs/jira.rst @@ -489,6 +489,12 @@ Attachments actions # Add attachment (IO Object) to issue jira.add_attachment_object(issue_key, attachment) + # Download attachments from the issue + jira.download_attachments_from_issue(issue, path=None, cloud=True): + + # Get list of attachments ids from issue + jira.get_attachments_ids_from_issue(issue_key) + Manage components ----------------- diff --git a/examples/jira/jira_download_attachments.from_issue.py b/examples/jira/jira_download_attachments.from_issue.py new file mode 100644 index 000000000..6dc6f6cdf --- /dev/null +++ b/examples/jira/jira_download_attachments.from_issue.py @@ -0,0 +1,9 @@ +from atlassian import Jira + +jira_cloud = Jira(url="", username="username", password="password") +jira_dc = Jira(url="url", token=">") +path = "/Users/>/PycharmProjects/api_python_atlassian_features/api_python_atlassian_features/atlassian-python-api/attachments" +# JIRA DC using custom directory path +jira_dc.download_attachments_from_issue("TEST-1", path=path, cloud=False) +# Jira cloud. Attachemtns will be saved to same director where script is being executed. +jira_cloud.get_attachments_ids_from_page("SC-1", cloud=True)