From 1c6bcc9ec28aefe6358ffa764cb6b0ffe8c1424a Mon Sep 17 00:00:00 2001 From: Gonchik Tsymzhitov Date: Sat, 30 Aug 2025 18:38:43 +0300 Subject: [PATCH 1/3] Confluence split classes --- atlassian/__init__.py | 5 +- atlassian/confluence/__init__.py | 4120 +---------------- atlassian/confluence/base.py | 159 + atlassian/confluence/cloud/__init__.py | 229 + atlassian/confluence/cloud/base.py | 104 + atlassian/confluence/server/__init__.py | 339 ++ atlassian/confluence/server/base.py | 105 + docs/confluence.rst | 68 + examples/confluence/README.md | 130 + .../confluence_cloud_content_management.py | 95 + .../confluence_server_content_management.py | 147 + tests/confluence/__init__.py | 4 + tests/confluence/test_confluence_cloud.py | 451 ++ tests/confluence/test_confluence_server.py | 682 +++ 14 files changed, 2535 insertions(+), 4103 deletions(-) create mode 100644 atlassian/confluence/base.py create mode 100644 atlassian/confluence/cloud/__init__.py create mode 100644 atlassian/confluence/cloud/base.py create mode 100644 atlassian/confluence/server/__init__.py create mode 100644 atlassian/confluence/server/base.py create mode 100644 examples/confluence/README.md create mode 100644 examples/confluence/cloud/confluence_cloud_content_management.py create mode 100644 examples/confluence/server/confluence_server_content_management.py create mode 100644 tests/confluence/__init__.py create mode 100644 tests/confluence/test_confluence_cloud.py create mode 100644 tests/confluence/test_confluence_server.py diff --git a/atlassian/__init__.py b/atlassian/__init__.py index 13215a75a..5c17f3eed 100644 --- a/atlassian/__init__.py +++ b/atlassian/__init__.py @@ -6,7 +6,7 @@ from .bitbucket import Bitbucket from .bitbucket import Bitbucket as Stash from .cloud_admin import CloudAdminOrgs, CloudAdminUsers -from .confluence import Confluence +from .confluence import ConfluenceCloud, ConfluenceServer, Confluence from .crowd import Crowd from .insight import Insight from .insight import Insight as Assets # used for Insight on-premise @@ -19,8 +19,11 @@ from .tempo import TempoCloud, TempoServer from .xray import Xray + __all__ = [ "Confluence", + "ConfluenceCloud", + "ConfluenceServer", "Jira", "Bitbucket", "CloudAdminOrgs", diff --git a/atlassian/confluence/__init__.py b/atlassian/confluence/__init__.py index 7c763ef1c..ebaf268e0 100644 --- a/atlassian/confluence/__init__.py +++ b/atlassian/confluence/__init__.py @@ -1,4115 +1,31 @@ # coding=utf-8 -import io -import json -import logging -import os -import re -import time -import warnings -from typing import cast +""" +Confluence API client package for Atlassian Python API. -import requests -from bs4 import BeautifulSoup -from deprecated import deprecated -from requests import HTTPError +This package provides both Cloud and Server implementations of the Confluence API. +""" -from atlassian import utils -from atlassian.errors import ( - ApiConflictError, - ApiError, - ApiNotAcceptable, - ApiNotFoundError, - ApiPermissionError, - ApiValueError, - JsonRPCError, - JsonRPCRestrictionsError, -) -from atlassian.rest_client import AtlassianRestAPI -import sys +from .cloud import Cloud as ConfluenceCloud +from .server import Server as ConfluenceServer -if sys.version_info >= (3, 8): - from typing import Literal # Python 3.8+ -else: - from typing_extensions import Literal # Python <=3.7 +# Legacy import for backward compatibility +from .base import ConfluenceBase -log = logging.getLogger(__name__) - -class Confluence(AtlassianRestAPI): - content_types = { - ".gif": "image/gif", - ".png": "image/png", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".pdf": "application/pdf", - ".doc": "application/msword", - ".xls": "application/vnd.ms-excel", - ".svg": "image/svg+xml", - } +# Legacy Confluence class for backward compatibility +class Confluence(ConfluenceBase): + """Legacy Confluence class for backward compatibility.""" def __init__(self, url, *args, **kwargs): + # Auto-detect if it's cloud or server based on URL if ("atlassian.net" in url or "jira.com" in url) and ("/wiki" not in url): - url = AtlassianRestAPI.url_joiner(url, "/wiki") if "cloud" not in kwargs: kwargs["cloud"] = True - super(Confluence, self).__init__(url, *args, **kwargs) - - @staticmethod - def _create_body(body, representation): - if representation not in [ - "atlas_doc_format", - "editor", - "export_view", - "view", - "storage", - "wiki", - ]: - raise ValueError("Wrong value for representation, it should be either wiki or storage") - - return {representation: {"value": body, "representation": representation}} - - def _get_paged( - self, - url, - params=None, - data=None, - flags=None, - trailing=None, - absolute=False, - ): - """ - Used to get the paged data - - :param url: string: The url to retrieve - :param params: dict (default is None): The parameter's - :param data: dict (default is None): The data - :param flags: string[] (default is None): The flags - :param trailing: bool (default is None): If True, a trailing slash is added to the url - :param absolute: bool (default is False): If True, the url is used absolute and not relative to the root - - :return: A generator object for the data elements - """ - - if params is None: - params = {} - - while True: - response = self.get( - url, - trailing=trailing, - params=params, - data=data, - flags=flags, - absolute=absolute, - ) - if "results" not in response: - return - - for value in response.get("results", []): - yield value - - # According to Cloud and Server documentation the links are returned the same way: - # https://developer.atlassian.com/cloud/confluence/rest/api-group-content/#api-wiki-rest-api-content-get - # https://developer.atlassian.com/server/confluence/pagination-in-the-rest-api/ - url = response.get("_links", {}).get("next") - if url is None: - break - # From now on we have relative URLs with parameters - absolute = False - # Params are now provided by the url - params = {} - # Trailing should not be added as it is already part of the url - trailing = False - - return - - def page_exists(self, space, title, type=None): - """ - Check if title exists as page. - :param space: Space key - :param title: Title of the page - :param type: type of the page, 'page' or 'blogpost'. Defaults to 'page' - :return: - """ - url = "rest/api/content" - params = {} - if space is not None: - params["spaceKey"] = str(space) - if title is not None: - params["title"] = str(title) - if type is not None: - params["type"] = str(type) - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - if response.get("results"): - return True - else: - return False - - def share_with_others(self, page_id, group, message): - """ - Notify members (currently only groups implemented) about something on that page - """ - url = "rest/share-page/latest/share" - params = { - "contextualPageId": page_id, - # "emails": [], - "entityId": page_id, - "entityType": "page", - "groups": group, - "note": message, - # "users":[] - } - r = self.post(url, json=params, headers={"contentType": "application/json; charset=utf-8"}, advanced_mode=True) - if r.status_code != 200: - raise Exception(f"failed sharing content {r.status_code}: {r.text}") - - def get_page_child_by_type(self, page_id, type="page", start=None, limit=None, expand=None): - """ - Provide content by type (page, blog, comment) - :param page_id: A string containing the id of the type content container. - :param type: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: how many items should be returned after the start index. Default: Site limit 200. - :param expand: OPTIONAL: expand e.g. history - :return: - """ - params = {} - if start is not None: - params["start"] = int(start) - if limit is not None: - params["limit"] = int(limit) - if expand is not None: - params["expand"] = expand - - url = f"rest/api/content/{page_id}/child/{type}" - log.info(url) - - try: - if not self.advanced_mode and start is None and limit is None: - return self._get_paged(url, params=params) - else: - response = self.get(url, params=params) - if self.advanced_mode: - return response - return response.get("results") - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - def get_child_title_list(self, page_id, type="page", start=None, limit=None): - """ - Find a list of Child title - :param page_id: A string containing the id of the type content container. - :param type: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: how many items should be returned after the start index. Default: Site limit 200. - :return: - """ - child_page = self.get_page_child_by_type(page_id, type, start, limit) - child_title_list = [child["title"] for child in child_page] - return child_title_list - - def get_child_id_list(self, page_id, type="page", start=None, limit=None): - """ - Find a list of Child id - :param page_id: A string containing the id of the type content container. - :param type: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: how many items should be returned after the start index. Default: Site limit 200. - :return: - """ - child_page = self.get_page_child_by_type(page_id, type, start, limit) - child_id_list = [child["id"] for child in child_page] - return child_id_list - - def get_child_pages(self, page_id): - """ - Get child pages for the provided page_id - :param page_id: - :return: - """ - return self.get_page_child_by_type(page_id=page_id, type="page") - - def get_page_id(self, space, title, type="page"): - """ - Provide content id from search result by title and space. - :param space: SPACE key - :param title: title - :param type: type of content: Page or Blogpost. Defaults to page - :return: - """ - return (self.get_page_by_title(space, title, type=type) or {}).get("id") - - def get_parent_content_id(self, page_id): - """ - Provide parent content id from page id - :type page_id: str - :return: - """ - parent_content_id = None - try: - parent_content_id = (self.get_page_by_id(page_id=page_id, expand="ancestors").get("ancestors") or {})[ - -1 - ].get("id") or None - except Exception as e: - log.error(e) - return parent_content_id - - def get_parent_content_title(self, page_id): - """ - Provide parent content title from page id - :type page_id: str - :return: - """ - parent_content_title = None - try: - parent_content_title = (self.get_page_by_id(page_id=page_id, expand="ancestors").get("ancestors") or {})[ - -1 - ].get("title") or None - except Exception as e: - log.error(e) - return parent_content_title - - def get_page_space(self, page_id): - """ - Provide space key from content id. - :param page_id: content ID - :return: - """ - return ((self.get_page_by_id(page_id, expand="space") or {}).get("space") or {}).get("key") or None - - def get_pages_by_title(self, space, title, start=0, limit=200, expand=None): - """ - Provide pages by title search - :param space: Space key - :param title: Title of the page - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of labels to return, this may be restricted by - fixed system limits. Default: 200. - :param expand: OPTIONAL: expand e.g. history - :return: The JSON data returned from searched results the content endpoint, or the results of the - callback. Will raise requests.HTTPError on bad input, potentially. - If it has IndexError then return the None. - """ - return self.get_page_by_title(space, title, start, limit, expand) - - def get_page_by_title(self, space, title, start=0, limit=1, expand=None, type="page"): - """ - Returns the first page on a piece of Content. - :param space: Space key - :param title: Title of the page - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of labels to return, this may be restricted by - fixed system limits. Default: 1. - :param expand: OPTIONAL: expand e.g. history - :param type: OPTIONAL: Type of content: Page or Blogpost. Defaults to page - :return: The JSON data returned from searched results the content endpoint, or the results of the - callback. Will raise requests.HTTPError on bad input, potentially. - If it has IndexError then return the None. - """ - url = "rest/api/content" - params = {"type": type} - if start is not None: - params["start"] = int(start) - if limit is not None: - params["limit"] = int(limit) - if expand is not None: - params["expand"] = expand - if space is not None: - params["spaceKey"] = str(space) - if title is not None: - params["title"] = str(title) - - if self.advanced_mode: - return self.get(url, params=params) - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - try: - return response.get("results")[0] - except (IndexError, TypeError) as e: - log.error(f"Can't find '{title}' page on {self.url}") - log.debug(e) - return None - - def get_page_by_id(self, page_id, expand=None, status=None, version=None): - """ - Returns a piece of Content. - Example request URI(s): - http://example.com/confluence/rest/api/content/1234?expand=space,body.view,version,container - http://example.com/confluence/rest/api/content/1234?status=any - :param page_id: Content ID - :param status: (str) list of Content statuses to filter results on. Default value: [current] - :param version: (int) - :param expand: OPTIONAL: Default value: history,space,version - We can also specify some extensions such as extensions.inlineProperties - (for getting inline comment-specific properties) or extensions. Resolution - for the resolution status of each comment in the results - :return: - """ - params = {} - if expand: - params["expand"] = expand - if status: - params["status"] = status - if version: - params["version"] = version - url = f"rest/api/content/{page_id}" - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_tables_from_page(self, page_id): - """ - Fetches html tables added to confluence page - :param page_id: integer confluence page_id - :return: json object with page_id, number_of_tables_in_page - and list of list tables_content representing scraped tables - """ - try: - page_content = self.get_page_by_id(page_id, expand="body.storage")["body"]["storage"]["value"] - - if page_content: - tables_raw = [ - [[cell.text for cell in row("th") + row("td")] for row in table("tr")] - for table in BeautifulSoup(page_content, features="lxml")("table") - ] - if len(tables_raw) > 0: - return json.dumps( - { - "page_id": page_id, - "number_of_tables_in_page": len(tables_raw), - "tables_content": tables_raw, - } - ) - else: - return { - "No tables found for page: ": page_id, - } - else: - return {"Page content is empty"} - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - log.error("Couldn't retrieve tables from page", page_id) - raise ApiError( - "There is no content with the given pageid, pageid params is not an integer " - "or the calling user does not have permission to view the page", - reason=e, - ) - except Exception as e: - log.error("Error occurred", 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. - :param page_id: A string containing the id of the labels content container. - :param prefix: OPTIONAL: The prefixes to filter the labels with {@see Label.Prefix}. - Default: None. - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of labels to return, this may be restricted by - fixed system limits. Default: 200. - :return: The JSON data returned from the content/{id}/label endpoint, or the results of the - callback. Will raise requests.HTTPError on bad input, potentially. - """ - url = f"rest/api/content/{page_id}/label" - params = {} - if prefix: - params["prefix"] = prefix - if start is not None: - params["start"] = int(start) - if limit is not None: - params["limit"] = int(limit) - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_page_comments( - self, - content_id, - expand=None, - parent_version=None, - start=0, - limit=25, - location=None, - depth=None, - ): - """ - - :param content_id: - :param expand: extensions.inlineProperties,extensions.resolution - :param parent_version: - :param start: - :param limit: - :param location: inline or not - :param depth: - :return: - """ - params = {"id": content_id, "start": start, "limit": limit} - if expand: - params["expand"] = expand - if parent_version: - params["parentVersion"] = parent_version - if location: - params["location"] = location - if depth: - params["depth"] = depth - url = f"rest/api/content/{content_id}/child/comment" - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_draft_page_by_id(self, page_id, status="draft", expand=None): - """ - Gets content by id with status = draft - :param page_id: Content ID - :param status: (str) list of content statuses to filter results on. Default value: [draft] - :param expand: OPTIONAL: Default value: history,space,version - We can also specify some extensions such as extensions.inlineProperties - (for getting inline comment-specific properties) or extensions. Resolution - for the resolution status of each comment in the results - :return: - """ - # Version not passed since draft versions don't match the page and - # operate differently between different collaborative modes - return self.get_page_by_id(page_id=page_id, expand=expand, status=status) - - def get_all_pages_by_label(self, label, start=0, limit=50, expand=None): - """ - Get all page by label - :param label: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 50 - :param expand: OPTIONAL: a comma separated list of properties to expand on the content - :return: - """ - url = "rest/api/content/search" - params = {} - if label: - params["cql"] = f'type={"page"} AND label="{label}"' - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError("The CQL is invalid or missing", reason=e) - - raise - - return response.get("results") - - def get_all_pages_from_space_raw( - self, - space, - start=0, - limit=50, - status=None, - expand=None, - content_type="page", - ): - """ - Get all pages from space - - :param space: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 50 - :param status: OPTIONAL: list of statuses the content to be found is in. - Defaults to current is not specified. - If set to 'any', content in 'current' and 'trashed' status will be fetched. - Does not support 'historical' status for now. - :param expand: OPTIONAL: a comma separated list of properties to expand on the content. - Default value: history,space,version. - :param content_type: the content type to return. Default value: page. Valid values: page, blogpost. - :return: - """ - url = "rest/api/content" - params = {} - if space: - params["spaceKey"] = space - if start: - params["start"] = start - if limit: - params["limit"] = limit - if status: - params["status"] = status - if expand: - params["expand"] = expand - if content_type: - params["type"] = content_type - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_all_pages_from_space( - self, - space, - start=0, - limit=50, - status=None, - expand=None, - content_type="page", - ): - """ - Retrieve all pages from a Confluence space. - - :param space: The space key to fetch pages from. - :param start: OPTIONAL: The starting point of the collection. Default: 0. - :param limit: OPTIONAL: The maximum number of pages per request. Default: 50. - :param status: OPTIONAL: Filter pages by status ('current', 'trashed', 'any'). Default: None. - :param expand: OPTIONAL: Comma-separated list of properties to expand. Default: history,space,version. - :param content_type: OPTIONAL: The content type to return ('page', 'blogpost'). Default: page. - :return: List containing all pages from the specified space. - """ - all_pages = [] # Initialize an empty list to store all pages - while True: - # Fetch a single batch of pages - response = self.get_all_pages_from_space_raw( - space=space, - start=start, - limit=limit, - status=status, - expand=expand, - content_type=content_type, - ) - - # Extract results from the response - results = response.get("results", []) - all_pages.extend(results) # Add the current batch of pages to the list - - # Break the loop if no more pages are available - if len(results) <= limit: - break - - # Increment the start index for the next batch - start += limit - return all_pages - - def get_all_pages_from_space_as_generator( - self, - space, - start=0, - limit=50, - status=None, - expand="history,space,version", - content_type="page", - ): - """ - Retrieve all pages from a Confluence space using pagination. - - :param space: The space key to fetch pages from. - :param start: OPTIONAL: The starting point of the collection. Default: 0. - :param limit: OPTIONAL: The maximum number of pages per request. Default: 50. - :param status: OPTIONAL: Filter pages by status ('current', 'trashed', 'any'). Default: None. - :param expand: OPTIONAL: Comma-separated list of properties to expand. Default: history,space,version. - :param content_type: OPTIONAL: The content type to return ('page', 'blogpost'). Default: page. - :return: Generator yielding pages one by one. - """ - while True: - # Fetch a single batch of pages - response = self.get_all_pages_from_space_raw( - space=space, - start=start, - limit=limit, - status=status, - expand=expand, - content_type=content_type, - ) - - # Extract results from the response - results = response.get("results", []) - yield from results # Yield each page individually - - # Break the loop if no more pages are available - if len(results) < limit: - break - start += limit - pass - - def get_all_pages_from_space_trash(self, space, start=0, limit=500, status="trashed", content_type="page"): - """ - Get list of pages from trash - :param space: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 500 - :param status: - :param content_type: the content type to return. Default value: page. Valid values: page, blogpost. - :return: - """ - return self.get_all_pages_from_space(space, start, limit, status, content_type=content_type) - - def get_all_draft_pages_from_space(self, space, start=0, limit=500, status="draft"): - """ - Get list of draft pages from space - Use case is cleanup old drafts from Confluence - :param space: - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 500 - :param status: - :return: - """ - return self.get_all_pages_from_space(space, start, limit, status) - - def get_all_draft_pages_from_space_through_cql(self, space, start=0, limit=500, status="draft"): - """ - Search list of draft pages by space key - Use case is cleanup old drafts from Confluence - :param space: Space Key - :param status: Can be changed - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 500 - :return: - """ - url = f"rest/api/content?cql=space=spaceKey={space} and status={status}" - params = {} - if limit: - params["limit"] = limit - if start: - params["start"] = start - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response.get("results") - - def get_all_pages_by_space_ids_confluence_cloud( - self, - space_ids, - batch_size=250, - sort=None, - status=None, - title=None, - body_format=None, - ): - """ - Get all pages from a set of space ids: - https://developer.atlassian.com/cloud/confluence/rest/v2/api-group-page/#api-pages-get - :param space_ids: A Set of space IDs passed as a filter to Confluence - :param batch_size: OPTIONAL: The batch size of pages to retrieve from confluence per request MAX is 250. - Default: 250 - :param sort: OPTIONAL: The order the pages are retrieved in. - Valid values: - id, -id, created-date, -created-date, modified-date, -modified-date, title, -title - :param status: OPTIONAL: Filter pages based on their status. - Valid values: current, archived, deleted, trashed - Default: current,archived - :param title: OPTIONAL: Filter pages based on their title. - :param body_format: OPTIONAL: The format of the body in the response. Valid values: storage, atlas_doc_format - :return: - """ - path = "/api/v2/pages" - params = {} - if space_ids: - params["space-id"] = ",".join(space_ids) - if batch_size: - params["limit"] = batch_size - if sort: - params["sort"] = sort - if status: - params["status"] = status - if title: - params["title"] = title - if body_format: - params["body-format"] = body_format - - _all_pages = [] - try: - while True: - response = self.get(path, params=params) - - pages = response.get("results") - _all_pages = _all_pages + pages - - links = response.get("_links") - if links is not None and "next" in links: - path = response["_links"]["next"].removeprefix("/wiki/") - params = {} - else: - break - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError( - "The configured params cannot be interpreted by Confluence" - "Check the api documentation for valid values for status, expand, and sort params", - reason=e, - ) - if e.response.status_code == 401: - raise HTTPError("Unauthorized (401)", response=response) - raise - - return _all_pages - - @deprecated(version="2.4.2", reason="Use get_all_restrictions_for_content()") - def get_all_restictions_for_content(self, content_id): - """Let's use the get_all_restrictions_for_content()""" - return self.get_all_restrictions_for_content(content_id=content_id) - - def get_all_restrictions_for_content(self, content_id): - """ - Returns info about all restrictions by operation. - :param content_id: - :return: Return the raw json response - """ - url = f"rest/api/content/{content_id}/restriction/byOperation" - return self.get(url) - - def get_all_restrictions_from_page_json_rpc(self, page_id): - """ - The JSON-RPC APIs for Confluence are provided here to help you browse and discover APIs you have access to. - JSON-RPC APIs operate differently than REST APIs. - To learn more about how to use these APIs, - please refer to the Confluence JSON-RPC documentation on Atlassian Developers. - """ - if self.api_version == "cloud" or self.cloud: - return {} - url = "rpc/json-rpc/confluenceservice-v2" - data = {"jsonrpc": "2.0", "method": "getContentPermissionSets", "id": 9, "params": [page_id]} - return self.post(url, data=data).get("result") or {} - - def update_restrictions_for_page_json_rpc(self, page_id, permission_type, content_permissions): - """ - The JSON-RPC APIs for Confluence are provided here to help you browse and discover APIs you have access to. - JSON-RPC APIs operate differently than REST APIs. - To learn more about how to use these APIs, - please refer to the Confluence JSON-RPC documentation on Atlassian Developers. - """ - if self.api_version == "cloud" or self.cloud: - return {} - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "setContentPermissions", - "id": 9, - "params": [page_id, permission_type, content_permissions], - } - return self.post(url, data=data).get("result") or {} - - def get_users_from_restricts_in_page_by_type(self, page_id: str, restriction_type: Literal["View", "Edit"]): - page_name = self.get_page_by_id(page_id=page_id)["title"] - restrictions_in_page = self.get_all_restrictions_from_page_json_rpc(page_id=page_id) - try: - if len(restrictions_in_page) > 0: - for restriction_type_in_page in restrictions_in_page: - if dict(restriction_type_in_page).get("type") == restriction_type: - users = dict(restriction_type_in_page).get("contentPermissions") - return users - else: - raise JsonRPCRestrictionsError( - f'On page "{page_name}" has no restrictions type of "{restriction_type}"' - ) - except JsonRPCError: - raise - - def create_restricts_from_from_user(self, user_name: str, restriction_type: Literal["View", "Edit"]): - content = {"type": restriction_type, "userName": user_name, "groupName": None} - - return content - - def add_user_in_restricted_page(self, user_name: str, page_id: str, restriction_type: Literal["View", "Edit"]): - page_name = self.get_page_by_id(page_id=page_id).get("title") - user_find_view_bool = False - user_find_edit_bool = False - users_content_view: list = self.get_users_from_restricts_in_page_by_type( - page_id=page_id, restriction_type="View" - ) - users_content_edit: list = self.get_users_from_restricts_in_page_by_type( - page_id=page_id, restriction_type="Edit" - ) - current_user_content_view: dict = self.create_restricts_from_from_user( - user_name=user_name, restriction_type="View" - ) - current_user_content_edit: dict = self.create_restricts_from_from_user( - user_name=user_name, restriction_type="Edit" - ) - try: - if None not in [users_content_view, users_content_edit]: - if users_content_view is not None: - for user in users_content_view: - if dict(user).get("userName") == current_user_content_view.get("userName"): - user_find_view_bool = True - if users_content_edit is not None: - for user in users_content_edit: - if dict(user).get("userName") == current_user_content_edit.get("userName"): - user_find_edit_bool = True - if restriction_type == "View": - if user_find_view_bool is False: - current_user_content = self.create_restricts_from_from_user( - user_name=user_name, restriction_type=restriction_type - ) - users_content_view.append(current_user_content) - self.update_restrictions_for_page_json_rpc( - page_id=page_id, - permission_type=restriction_type, - content_permissions=users_content_view, - ) - elif user_find_view_bool: - raise JsonRPCRestrictionsError( - f'User "{user_name}" already have restrictions type of "{restriction_type}" on page "{page_name}"' - ) - elif restriction_type == "Edit": - if not user_find_edit_bool: - current_user_content_view = self.create_restricts_from_from_user( - user_name=user_name, restriction_type="View" - ) - current_user_content_edit = self.create_restricts_from_from_user( - user_name=user_name, restriction_type=restriction_type - ) - users_content_view.append(current_user_content_view) - users_content_edit.append(current_user_content_edit) - self.update_restrictions_for_page_json_rpc( - page_id=page_id, permission_type="View", content_permissions=users_content_view - ) - self.update_restrictions_for_page_json_rpc( - page_id=page_id, permission_type=restriction_type, content_permissions=users_content_edit - ) - print( - f'User "{user_name}" granted restrictions type of "{restriction_type}" on page "{page_name}"' - ) - elif user_find_edit_bool: - raise JsonRPCRestrictionsError( - f'User "{user_name}" already have restrictions type of "{restriction_type}" on page "{page_name}"' - ) - except JsonRPCError: - raise - - def remove_user_from_restricted_page(self, user_name: str, page_id: str): - page_name = self.get_page_by_id(page_id=page_id).get("title") - user_find_bool = False - users_content_view: list = self.get_users_from_restricts_in_page_by_type( - page_id=page_id, restriction_type="View" - ) - users_content_edit: list = self.get_users_from_restricts_in_page_by_type( - page_id=page_id, restriction_type="Edit" - ) - current_user_content_view = self.create_restricts_from_from_user(user_name=user_name, restriction_type="View") - current_user_content_edit = self.create_restricts_from_from_user(user_name=user_name, restriction_type="Edit") - for user_index, user_value in enumerate(users_content_view): - if dict(user_value).get("userName") == current_user_content_view.get("userName"): - user_find_bool = True - users_content_view.pop(user_index) - for user_index, user_value in enumerate(users_content_edit): - if dict(user_value).get("userName") == current_user_content_edit.get("userName"): - user_find_bool = True - users_content_edit.pop(user_index) - try: - if user_find_bool: - self.update_restrictions_for_page_json_rpc( - page_id=page_id, permission_type="View", content_permissions=users_content_view - ) - self.update_restrictions_for_page_json_rpc( - page_id=page_id, permission_type="Edit", content_permissions=users_content_edit - ) - print(f'User "{user_name}" has been deleted from restrictions on page "{page_name}"') - elif not user_find_bool: - raise JsonRPCRestrictionsError( - f'User "{user_name}" has not founded in restrictions on page "{page_name}"' - ) - except JsonRPCError: - raise - - def remove_page_from_trash(self, page_id): - """ - This method removes a page from trash - :param page_id: - :return: - """ - return self.remove_page(page_id=page_id, status="trashed") - - def remove_page_as_draft(self, page_id): - """ - This method removes a page from trash if it is a draft - :param page_id: - :return: - """ - return self.remove_page(page_id=page_id, status="draft") - - def remove_content(self, content_id): - """ - Remove any content - :param content_id: - :return: - """ - try: - response = self.delete(f"rest/api/content/{content_id}") - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, or the calling " - "user does not have permission to trash or purge the content", - reason=e, - ) - if e.response.status_code == 409: - raise ApiConflictError( - "There is a stale data object conflict when trying to delete a draft", - reason=e, - ) - - raise - - return response - - def remove_page(self, page_id, status=None, recursive=False): - """ - This method removes a page, if it has recursive flag, method removes including child pages - :param page_id: - :param status: OPTIONAL: type of page - :param recursive: OPTIONAL: if True - will recursively delete all children pages too - :return: - """ - url = f"rest/api/content/{page_id}" - if recursive: - children_pages = self.get_page_child_by_type(page_id) - for children_page in children_pages: - self.remove_page(children_page.get("id"), status, recursive) - params = {} - if status: - params["status"] = status - - try: - response = self.delete(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, or the calling " - "user does not have permission to trash or purge the content", - reason=e, - ) - if e.response.status_code == 409: - raise ApiConflictError( - "There is a stale data object conflict when trying to delete a draft", - reason=e, - ) - - raise - - return response - - def create_page( - self, - space, - title, - body, - parent_id=None, - type="page", - representation="storage", - editor=None, - full_width=False, - status="current", - ): - """ - Create page from scratch - :param space: - :param title: - :param body: - :param parent_id: - :param type: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param editor: OPTIONAL: v2 to be created in the new editor - :param full_width: DEFAULT: False - :param status: either 'current' or 'draft' - :return: - """ - log.info('Creating %s "%s" -> "%s"', type, space, title) - url = "rest/api/content/" - data = { - "type": type, - "title": title, - "status": status, - "space": {"key": space}, - "body": self._create_body(body, representation), - "metadata": {"properties": {}}, - } - if parent_id: - data["ancestors"] = [{"type": type, "id": parent_id}] - if editor is not None and editor in ["v1", "v2"]: - data["metadata"]["properties"]["editor"] = {"value": editor} - if full_width is True: - data["metadata"]["properties"]["content-appearance-draft"] = {"value": "full-width"} - data["metadata"]["properties"]["content-appearance-published"] = {"value": "full-width"} - else: - data["metadata"]["properties"]["content-appearance-draft"] = {"value": "fixed-width"} - data["metadata"]["properties"]["content-appearance-published"] = {"value": "fixed-width"} - - try: - response = self.post(url, data=data) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def move_page( - self, - space_key, - page_id, - target_id=None, - target_title=None, - position="append", - ): - """ - Move page method - :param space_key: - :param page_id: - :param target_title: - :param target_id: - :param position: topLevel or append , above, below - :return: - """ - url = "/pages/movepage.action" - params = {"spaceKey": space_key, "pageId": page_id} - if target_title: - params["targetTitle"] = target_title - if target_id: - params["targetId"] = target_id - if position: - params["position"] = position - return self.post(url, params=params, headers=self.no_check_headers) - - def create_or_update_template( - self, - name, - body, - template_type="page", - template_id=None, - description=None, - labels=None, - space=None, - ): - """ - Creates a new or updates an existing content template. - - Note, blueprint templates cannot be created or updated via the REST API. - - If you provide a ``template_id`` then this method will update the template with the provided settings. - If no ``template_id`` is provided, then this method assumes you are creating a new template. - - :param str name: If creating, the name of the new template. If updating, the name to change - the template name to. Set to the current name if this field is not being updated. - :param dict body: This object is used when creating or updating content. - { - "storage": { - "value": "", - "representation": "view" - } - } - :param str template_type: OPTIONAL: The type of the new template. Default: "page". - :param str template_id: OPTIONAL: The ID of the template being updated. REQUIRED if updating a template. - :param str description: OPTIONAL: A description of the new template. Max length 255. - :param list labels: OPTIONAL: Labels for the new template. An array like: - [ - { - "prefix": "", - "name": "", - "id": "", - "label": "", - } - ] - :param dict space: OPTIONAL: The key for the space of the new template. Only applies to space templates. - If not specified, the template will be created as a global template. - :return: - """ - data = {"name": name, "templateType": template_type, "body": body} - - if description: - data["description"] = description - - if labels: - data["labels"] = labels - - if space: - data["space"] = {"key": space} - - if template_id: - data["templateId"] = template_id - return self.put("rest/api/template", data=json.dumps(data)) - - return self.post("rest/api/template", json=data) - - @deprecated(version="3.7.0", reason="Use get_content_template()") - def get_template_by_id(self, template_id): - """ - Get user template by id. Experimental API - Use case is get template body and create page from that - """ - url = f"rest/experimental/template/{template_id}" - - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 403: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - return response - - def get_content_template(self, template_id): - """ - Get a content template. - - This includes information about the template, like the name, the space or blueprint - that the template is in, the body of the template, and more. - :param str template_id: The ID of the content template to be returned - :return: - """ - url = f"rest/api/template/{template_id}" - - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 403: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - @deprecated(version="3.7.0", reason="Use get_blueprint_templates()") - def get_all_blueprints_from_space(self, space, start=0, limit=None, expand=None): - """ - Get all users blueprints from space. Experimental API - :param space: Space Key - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 20 - :param expand: OPTIONAL: expand e.g. body - """ - url = "rest/experimental/template/blueprint" - params = {} - if space: - params["spaceKey"] = space - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response.get("results") or [] - - def get_blueprint_templates(self, space=None, start=0, limit=None, expand=None): - """ - Gets all templates provided by blueprints. - - Use this method to retrieve all global blueprint templates or all blueprint templates in a space. - :param space: OPTIONAL: The key of the space to be queried for templates. If ``space`` is not - specified, global blueprint templates will be returned. - :param start: OPTIONAL: The starting index of the returned templates. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 25 - :param expand: OPTIONAL: A multi-value parameter indicating which properties of the template to expand. - """ - url = "rest/api/template/blueprint" - params = {} - if space: - params["spaceKey"] = space - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response.get("results") or [] - - @deprecated(version="3.7.0", reason="Use get_content_templates()") - def get_all_templates_from_space(self, space, start=0, limit=None, expand=None): - """ - Get all users templates from space. Experimental API - ref: https://docs.atlassian.com/atlassian-confluence/1000.73.0/com/atlassian/confluence/plugins/restapi\ - /resources/TemplateResource.html - :param space: Space Key - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 20 - :param expand: OPTIONAL: expand e.g. body - """ - url = "rest/experimental/template/page" - params = {} - if space: - params["spaceKey"] = space - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - raise - - return response.get("results") or [] - - def get_content_templates(self, space=None, start=0, limit=None, expand=None): - """ - Get all content templates. - Use this method to retrieve all global content templates or all content templates in a space. - :param space: OPTIONAL: The key of the space to be queried for templates. If ``space`` is not - specified, global templates will be returned. - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 25 - :param expand: OPTIONAL: A multi-value parameter indicating which properties of the template to expand. - e.g. ``body`` - """ - url = "rest/api/template/page" - params = {} - if space: - params["spaceKey"] = space - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response.get("results") or [] - - def remove_template(self, template_id): - """ - Deletes a template. - - This results in different actions depending on the type of template: - * If the template is a content template, it is deleted. - * If the template is a modified space-level blueprint template, it reverts to the template - inherited from the global-level blueprint template. - * If the template is a modified global-level blueprint template, it reverts to the default - global-level blueprint template. - Note: Unmodified blueprint templates cannot be deleted. - - :param str template_id: The ID of the template to be deleted. - :return: - """ - return self.delete(f"rest/api/template/{template_id}") - - def get_all_spaces( - self, - start=0, - limit=50, - expand=None, - space_type=None, - space_status=None, - ): - """ - Get all spaces with provided limit - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 500 - :param space_type: OPTIONAL: Filter the list of spaces returned by type (global, personal) - :param space_status: OPTIONAL: Filter the list of spaces returned by status (current, archived) - :param expand: OPTIONAL: additional info, e.g. metadata, icon, description, homepage - """ - url = "rest/api/space" - params = {} - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - if space_type: - params["type"] = space_type - if space_status: - params["status"] = space_status - return self.get(url, params=params) - - def archive_space(self, space_key): - """ - Archive space - :param space_key: - :return: - """ - url = f"rest/api/space/{space_key}/archive" - return self.put(url) - - def get_trashed_contents_by_space(self, space_key, cursor=None, expand=None, limit=100): - """ - Get trashed contents by space - :param space_key: - :param cursor: - :param expand: - :param limit: - :return: - """ - url = f"rest/api/space/{space_key}/content/trash" - params = {"limit": limit} - if cursor: - params["cursor"] = cursor - if expand: - params["expand"] = expand - return self.get(url, params=params) - - def remove_trashed_contents_by_space(self, space_key): - """ - Remove all content from the trash in the given space, - deleting them permanently.Example request URI: - :param space_key: - :return: - """ - url = f"rest/api/space/{space_key}/content/trash" - return self.delete(url) - - def add_comment(self, page_id, text): - """ - Add comment into page - :param page_id - :param text - """ - data = { - "type": "comment", - "container": {"id": page_id, "type": "page", "status": "current"}, - "body": self._create_body(text, "storage"), - } - - try: - response = self.post("rest/api/content/", data=data) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def attach_content( - self, - content, - name, - content_type="application/binary", - page_id=None, - title=None, - space=None, - comment=None, - ): - """ - Attach (upload) a file to a page, if it exists it will update automatically the - version the new file and keep the old one. - :param title: The page name - :type title: ``str`` - :param space: The space name - :type space: ``str`` - :param page_id: The page id to which we would like to upload the file - :type page_id: ``str`` - :param name: The name of the attachment - :type name: ``str`` - :param content: Contains the content which should be uploaded - :type content: ``binary`` - :param content_type: Specify the HTTP content type. - The default is "application/binary" - :type content_type: ``str`` - :param comment: A comment describing this upload/file - :type comment: ``str`` - """ - page_id = self.get_page_id(space=space, title=title) if page_id is None else page_id - type = "attachment" - if page_id is not None: - comment = comment if comment else f"Uploaded {name}." - data = { - "type": type, - "fileName": name, - "contentType": content_type, - "comment": comment, - "minorEdit": "true", - } - headers = { - "X-Atlassian-Token": "no-check", - "Accept": "application/json", - } - path = f"rest/api/content/{page_id}/child/attachment" - # Check if there is already a file with the same name - attachments = self.get(path=path, headers=headers, params={"filename": name}) - if attachments.get("size"): - path = path + "/" + attachments["results"][0]["id"] + "/data" - - try: - response = self.post( - path=path, - data=data, - headers=headers, - files={"file": (name, content, content_type)}, - ) - except HTTPError as e: - if e.response.status_code == 403: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "Attachments are disabled or the calling user does " - "not have permission to add attachments to this content", - reason=e, - ) - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "The requested content is not found, the user does not have " - "permission to view it, or the attachments exceeds the maximum " - "configured attachment size", - reason=e, - ) - - raise - - return response - else: - log.warning("No 'page_id' found, not uploading attachments") - return None - - def attach_file( - self, - filename, - name=None, - content_type=None, - page_id=None, - title=None, - space=None, - comment=None, - ): - """ - Attach (upload) a file to a page, if it exists it will update automatically the - version the new file and keep the old one. - :param title: The page name - :type title: ``str`` - :param space: The space name - :type space: ``str`` - :param page_id: The page id to which we would like to upload the file - :type page_id: ``str`` - :param filename: The file to upload (Specifies the content) - :type filename: ``str`` - :param name: Specifies name of the attachment. This parameter is optional. - Is no name give the file name is used as name - :type name: ``str`` - :param content_type: Specify the HTTP content type. The default is - The default is "application/binary" - :type content_type: ``str`` - :param comment: A comment describing this upload/file - :type comment: ``str`` - """ - # get base name of the file to get the attachment from confluence. - if name is None: - name = os.path.basename(filename) - if content_type is None: - extension = os.path.splitext(filename)[-1] - content_type = self.content_types.get(extension, "application/binary") - - with open(filename, "rb") as infile: - return self.attach_content( - infile, - name, - content_type, - page_id=page_id, - title=title, - space=space, - comment=comment, - ) - - def download_attachments_from_page(self, page_id, path=None, start=0, limit=50, filename=None, to_memory=False): - """ - Downloads attachments from a Confluence page. Supports downloading all files or a specific file. - Files can either be saved to disk or returned as BytesIO objects for in-memory handling. - - :param page_id: str - The ID of the Confluence page to fetch attachments from. - :param path: str, optional - Directory where attachments will be saved. If None, defaults to the current working directory. - Ignored if `to_memory` is True. - :param start: int, optional - The start point for paginated attachment fetching. Default is 0. Ignored if `filename` is specified. - :param limit: int, optional - The maximum number of attachments to fetch per request. Default is 50. Ignored if `filename` is specified. - :param filename: str, optional - The name of a specific file to download. If provided, only this file will be fetched. - :param to_memory: bool, optional - If True, attachments are returned as a dictionary of {filename: BytesIO object}. - If False, files are written to the specified directory on disk. - :return: - - If `to_memory` is True, returns a dictionary {filename: BytesIO object}. - - If `to_memory` is False, returns a summary dict: {"attachments_downloaded": int, "path": str}. - :raises: - - FileNotFoundError: If the specified path does not exist. - - PermissionError: If there are permission issues with the specified path. - - requests.HTTPError: If the HTTP request to fetch an attachment fails. - - Exception: For any unexpected errors. - """ - # Default path to current working directory if not provided - if not to_memory and path is None: - path = os.getcwd() - - try: - # Fetch attachments based on the specified parameters - if filename: - # Fetch specific file by filename - attachments = self.get_attachments_from_content(page_id=page_id, filename=filename)["results"] - if not attachments: - return f"No attachment with filename '{filename}' found on the page." - else: - # Fetch all attachments with pagination - attachments = self.get_attachments_from_content(page_id=page_id, start=start, limit=limit)["results"] - if not attachments: - return "No attachments found on the page." - - # Prepare to handle downloads - downloaded_files = {} - for attachment in attachments: - file_name = attachment["title"] or attachment["id"] # Use attachment ID if title is unavailable - download_link = attachment["_links"]["download"] - # Fetch the file content - response = self.get(str(download_link), not_json_response=True) - - if to_memory: - # Store in BytesIO object - file_obj = io.BytesIO(response) - downloaded_files[file_name] = file_obj - else: - # Sanitize filename if needed - if re.search(r'[<>:"/\\|?*\x00-\x1F]', file_name): - sanitized = re.sub(r'[<>:"/\\|?*\x00-\x1F]', "_", file_name) - warnings.warn( - f"File name '{file_name}' contained invalid characters and was renamed to '{sanitized}'.", - UserWarning, - ) - file_name = sanitized - file_path = os.path.join(path, file_name) - # Save file to disk - file_path = os.path.join(path, file_name) - with open(file_path, "wb") as file: - file.write(response) - - # Return results based on storage mode - if to_memory: - return downloaded_files - else: - return {"attachments_downloaded": len(attachments), "path": path} - except NotADirectoryError: - raise FileNotFoundError(f"The directory '{path}' does not exist.") - except PermissionError: - raise PermissionError(f"Permission denied when trying to save files to '{path}'.") - except requests.HTTPError as http_err: - raise requests.HTTPError( - f"HTTP error occurred while downloading attachments: {http_err}", - response=http_err.response, - request=http_err.request, - ) - except Exception as err: - raise Exception(f"An unexpected error occurred: {err}") - - def delete_attachment(self, page_id, filename, version=None): - """ - Remove completely a file if version is None or delete version - :param version: - :param page_id: file version - :param filename: - :return: - """ - params = {"pageId": page_id, "fileName": filename} - if version: - params["version"] = version - return self.post( - "json/removeattachment.action", - params=params, - headers=self.form_token_headers, - ) - - def delete_attachment_by_id(self, attachment_id, version): - """ - Remove completely a file if version is None or delete version - :param attachment_id: - :param version: file version - :return: - """ - if self.cloud: - url = f"rest/api/content/{attachment_id}/version/{version}" - else: - url = f"rest/experimental/content/{attachment_id}/version/{version}" - return self.delete(url) - - def remove_page_attachment_keep_version(self, page_id, filename, keep_last_versions): - """ - Keep last versions - :param filename: - :param page_id: - :param keep_last_versions: - :return: - """ - attachment = self.get_attachments_from_content(page_id=page_id, expand="version", filename=filename).get( - "results" - )[0] - attachment_versions = self.get_attachment_history(attachment.get("id")) - while len(attachment_versions) > keep_last_versions: - remove_version_attachment_number = attachment_versions[keep_last_versions].get("number") - self.delete_attachment_by_id( - attachment_id=attachment.get("id"), - version=remove_version_attachment_number, - ) - log.info( - "Removed oldest version for %s, now versions equal more than %s", - attachment.get("title"), - len(attachment_versions), - ) - attachment_versions = self.get_attachment_history(attachment.get("id")) - log.info("Kept versions %s for %s", keep_last_versions, attachment.get("title")) - - def get_attachment_history(self, attachment_id, limit=200, start=0): - """ - Get attachment history - :param attachment_id - :param limit - :param start - :return - """ - params = {"limit": limit, "start": start} - if self.cloud: - url = f"rest/api/content/{attachment_id}/version" - else: - url = f"rest/experimental/content/{attachment_id}/version" - return (self.get(url, params=params) or {}).get("results") - - # @todo prepare more attachments info - def get_attachments_from_content( - self, - page_id, - start=0, - limit=50, - expand=None, - filename=None, - media_type=None, - ): - """ - Get attachments for page - :param page_id: - :param start: - :param limit: - :param expand: - :param filename: - :param media_type: - :return: - """ - params = {} - if start: - params["start"] = start - if limit: - params["limit"] = limit - if expand: - params["expand"] = expand - if filename: - params["filename"] = filename - if media_type: - params["mediaType"] = media_type - url = f"rest/api/content/{page_id}/child/attachment" - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def set_page_label(self, page_id, label): - """ - Set a label on the page - :param page_id: content_id format - :param label: label to add - :return: - """ - url = f"rest/api/content/{page_id}/label" - data = {"prefix": "global", "name": label} - - try: - response = self.post(path=url, data=data) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def remove_page_label(self, page_id: str, label: str): - """ - Delete Confluence page label - :param page_id: content_id format - :param label: label name - :return: - """ - url = f"rest/api/content/{page_id}/label" - params = {"id": page_id, "name": label} - - try: - response = self.delete(path=url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The user has view permission, " "but no edit permission to the content", - reason=e, - ) - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "The content or label doesn't exist, " - "or the calling user doesn't have view permission to the content", - reason=e, - ) - - raise - - return response - - def history(self, page_id): - url = f"rest/api/content/{page_id}/history" - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_content_history(self, content_id): - return self.history(content_id) - - def get_content_history_by_version_number(self, content_id, version_number): - """ - Get content history by version number - :param content_id: - :param version_number: - :return: - """ - if self.cloud: - url = f"rest/api/content/{content_id}/version/{version_number}" - else: - url = f"rest/experimental/content/{content_id}/version/{version_number}" - return self.get(url) - - def remove_content_history(self, page_id, version_number): - """ - Remove content history. It works as experimental method - :param page_id: - :param version_number: version number - :return: - """ - if self.cloud: - url = f"rest/api/content/{page_id}/version/{version_number}" - else: - url = f"rest/experimental/content/{page_id}/version/{version_number}" - self.delete(url) - - def remove_page_history(self, page_id, version_number): - """ - Remove content history. It works as experimental method - :param page_id: - :param version_number: version number - :return: - """ - self.remove_content_history(page_id, version_number) - - def remove_content_history_in_cloud(self, page_id, version_id): - """ - Remove content history. It works in CLOUD - :param page_id: - :param version_id: - :return: - """ - url = f"rest/api/content/{page_id}/version/{version_id}" - self.delete(url) - - def remove_page_history_keep_version(self, page_id, keep_last_versions): - """ - Keep last versions - :param page_id: - :param keep_last_versions: - :return: - """ - page = self.get_page_by_id(page_id=page_id, expand="version") - page_number = page.get("version").get("number") - while page_number > keep_last_versions: - self.remove_page_history(page_id=page_id, version_number=1) - page = self.get_page_by_id(page_id=page_id, expand="version") - page_number = page.get("version").get("number") - log.info("Removed oldest version for %s, now it's %s", page.get("title"), page_number) - log.info("Kept versions %s for %s", keep_last_versions, page.get("title")) - - def has_unknown_attachment_error(self, page_id): - """ - Check has unknown attachment error on page - :param page_id: - :return: - """ - unknown_attachment_identifier = "plugins/servlet/confluence/placeholder/unknown-attachment" - result = self.get_page_by_id(page_id, expand="body.view") - if len(result) == 0: - return "" - body = ((result.get("body") or {}).get("view") or {}).get("value") or {} - if unknown_attachment_identifier in body: - return result.get("_links").get("base") + result.get("_links").get("tinyui") - return "" - - def is_page_content_is_already_updated(self, page_id, body, title=None): - """ - Compare content and check is already updated or not - :param page_id: Content ID for retrieve storage value - :param body: Body for compare it - :param title: Title to compare - :return: True if the same - """ - confluence_content = self.get_page_by_id(page_id) - if title: - current_title = confluence_content.get("title", None) - if title != current_title: - log.info("Title of %s is different", page_id) - return False - - if self.advanced_mode: - confluence_content = ( - (self.get_page_by_id(page_id, expand="body.storage").json() or {}).get("body") or {} - ).get("storage") or {} - else: - confluence_content = ((self.get_page_by_id(page_id, expand="body.storage") or {}).get("body") or {}).get( - "storage" - ) or {} - - confluence_body_content = confluence_content.get("value") - - if confluence_body_content: - # @todo move into utils - confluence_body_content = utils.symbol_normalizer(confluence_body_content) - - log.debug('Old Content: """%s"""', confluence_body_content) - log.debug('New Content: """%s"""', body) - - if confluence_body_content.strip().lower() == body.strip().lower(): - log.info("Content of %s is exactly the same", page_id) - return True - else: - log.info("Content of %s differs", page_id) - return False - - def update_existing_page( - self, - page_id, - title, - body, - type="page", - representation="storage", - minor_edit=False, - version_comment=None, - full_width=False, - ): - """Duplicate update_page. Left for the people who used it before. Use update_page instead""" - return self.update_page( - page_id=page_id, - title=title, - body=body, - type=type, - representation=representation, - minor_edit=minor_edit, - version_comment=version_comment, - full_width=full_width, - ) - - def update_page( - self, - page_id, - title, - body=None, - parent_id=None, - type="page", - representation="storage", - minor_edit=False, - version_comment=None, - always_update=False, - full_width=False, - ): - """ - Update page if already exist - :param page_id: - :param title: - :param body: - :param parent_id: - :param type: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param minor_edit: Indicates whether to notify watchers about changes. - If False then notifications will be sent. - :param version_comment: Version comment - :param always_update: Whether always to update (suppress content check) - :param full_width: OPTIONAL: Default False - :return: - """ - # update current page - params = {"status": "current"} - log.info('Updating %s "%s" with %s', type, title, parent_id) - - if not always_update and body is not None and self.is_page_content_is_already_updated(page_id, body, title): - return self.get_page_by_id(page_id) - - try: - if self.advanced_mode: - version = self.history(page_id).json()["lastUpdated"]["number"] + 1 - else: - version = self.history(page_id)["lastUpdated"]["number"] + 1 - except (IndexError, TypeError) as e: - log.error("Can't find '%s' %s!", title, type) - log.debug(e) - return None - - data = { - "id": page_id, - "type": type, - "title": title, - "version": {"number": version, "minorEdit": minor_edit}, - "metadata": {"properties": {}}, - } - if body is not None: - data["body"] = self._create_body(body, representation) - - if parent_id: - data["ancestors"] = [{"type": "page", "id": parent_id}] - if version_comment: - data["version"]["message"] = version_comment - - if full_width is True: - data["metadata"]["properties"]["content-appearance-draft"] = {"value": "full-width"} - data["metadata"]["properties"]["content-appearance-published"] = {"value": "full-width"} - else: - data["metadata"]["properties"]["content-appearance-draft"] = {"value": "fixed-width"} - data["metadata"]["properties"]["content-appearance-published"] = {"value": "fixed-width"} - try: - response = self.put( - f"rest/api/content/{page_id}", - data=data, - params=params, - ) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError( - "No space or no content type, or setup a wrong version " - "type set to content, or status param is not draft and " - "status content is current", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError("Can not find draft with current content", reason=e) - - raise - - return response - - def _insert_to_existing_page( - self, - page_id, - title, - insert_body, - parent_id=None, - type="page", - representation="storage", - minor_edit=False, - version_comment=None, - top_of_page=False, - ): - """ - Insert body to a page if already exist - :param parent_id: - :param page_id: - :param title: - :param insert_body: - :param type: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param minor_edit: Indicates whether to notify watchers about changes. - If False then notifications will be sent. - :param top_of_page: Option to add the content to the end of page body - :return: - """ - log.info('Updating %s "%s"', type, title) - # update current page - params = {"status": "current"} - - if self.is_page_content_is_already_updated(page_id, insert_body, title): - return self.get_page_by_id(page_id) - else: - version = self.history(page_id)["lastUpdated"]["number"] + 1 - previous_body = ( - (self.get_page_by_id(page_id, expand="body.storage").get("body") or {}).get("storage").get("value") - ) - previous_body = previous_body.replace("ó", "ó") - body = insert_body + previous_body if top_of_page else previous_body + insert_body - data = { - "id": page_id, - "type": type, - "title": title, - "body": self._create_body(body, representation), - "version": {"number": version, "minorEdit": minor_edit}, - } - - if parent_id: - data["ancestors"] = [{"type": "page", "id": parent_id}] - if version_comment: - data["version"]["message"] = version_comment - - try: - response = self.put( - f"rest/api/content/{page_id}", - data=data, - params=params, - ) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError( - "No space or no content type, or setup a wrong version " - "type set to content, or status param is not draft and " - "status content is current", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError("Can not find draft with current content", reason=e) - - raise - - return response - - def append_page( - self, - page_id, - title, - append_body, - parent_id=None, - type="page", - representation="storage", - minor_edit=False, - ): - """ - Append body to page if already exist - :param parent_id: - :param page_id: - :param title: - :param append_body: - :param type: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param minor_edit: Indicates whether to notify watchers about changes. - If False then notifications will be sent. - :return: - """ - log.info('Updating %s "%s"', type, title) - - return self._insert_to_existing_page( - page_id, - title, - append_body, - parent_id=parent_id, - type=type, - representation=representation, - minor_edit=minor_edit, - top_of_page=False, - ) - - def prepend_page( - self, - page_id, - title, - prepend_body, - parent_id=None, - type="page", - representation="storage", - minor_edit=False, - ): - """ - Append body to page if already exist - :param parent_id: - :param page_id: - :param title: - :param prepend_body: - :param type: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param minor_edit: Indicates whether to notify watchers about changes. - If False then notifications will be sent. - :return: - """ - log.info('Updating %s "%s"', type, title) - - return self._insert_to_existing_page( - page_id, - title, - prepend_body, - parent_id=parent_id, - type=type, - representation=representation, - minor_edit=minor_edit, - top_of_page=True, - ) - - def update_or_create( - self, - parent_id, - title, - body, - representation="storage", - minor_edit=False, - version_comment=None, - editor=None, - full_width=False, - ): - """ - Update page or create a page if it is not exists - :param parent_id: - :param title: - :param body: - :param representation: OPTIONAL: either Confluence 'storage' or 'wiki' markup format - :param minor_edit: Update page without notification - :param version_comment: Version comment - :param editor: OPTIONAL: v2 to be created in the new editor - :param full_width: OPTIONAL: Default is False - :return: - """ - space = self.get_page_space(parent_id) - - if self.page_exists(space, title): - page_id = self.get_page_id(space, title) - parent_id = parent_id if parent_id is not None else self.get_parent_content_id(page_id) - result = self.update_page( - parent_id=parent_id, - page_id=page_id, - title=title, - body=body, - representation=representation, - minor_edit=minor_edit, - version_comment=version_comment, - full_width=full_width, - ) - else: - result = self.create_page( - space=space, - parent_id=parent_id, - title=title, - body=body, - representation=representation, - editor=editor, - full_width=full_width, - ) - - log.info( - "You may access your page at: %s%s", - self.url, - ((result or {}).get("_links") or {}).get("tinyui"), - ) - return result - - def convert_wiki_to_storage(self, wiki): - """ - Convert to Confluence XHTML format from wiki style - :param wiki: - :return: - """ - data = {"value": wiki, "representation": "wiki"} - return self.post("rest/api/contentbody/convert/storage", data=data) - - def convert_storage_to_view(self, storage): - """ - Convert from Confluence XHTML format to view format - :param storage: - :return: - """ - data = {"value": storage, "representation": "storage"} - return self.post("rest/api/contentbody/convert/view", data=data) - - def set_page_property(self, page_id, data): - """ - Set the page (content) property e.g. add hash parameters - :param page_id: content_id format - :param data: data should be as json data - :return: - """ - url = f"rest/api/content/{page_id}/property" - json_data = data - - try: - response = self.post(path=url, data=json_data) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError( - "The given property has a different content id to the one in the " - "path, or the content already has a value with the given key, or " - "the value is missing, or the value is too long", - reason=e, - ) - if e.response.status_code == 403: - raise ApiPermissionError( - "The user does not have permission to " "edit the content with the given id", - reason=e, - ) - if e.response.status_code == 413: - raise ApiValueError("The value is too long", reason=e) - - raise - - return response - - def update_page_property(self, page_id, data): - """ - Update the page (content) property. - Use json data or independent keys - :param data: - :param page_id: content_id format - :data: property data in json format - :return: - """ - url = f"rest/api/content/{page_id}/property/{data.get('key')}" - try: - response = self.put(path=url, data=data) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError( - "The given property has a different content id to the one in the " - "path, or the content already has a value with the given key, or " - "the value is missing, or the value is too long", - reason=e, - ) - if e.response.status_code == 403: - raise ApiPermissionError( - "The user does not have permission to " "edit the content with the given id", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError( - "There is no content with the given id, or no property with the given key, " - "or if the calling user does not have permission to view the content.", - reason=e, - ) - if e.response.status_code == 409: - raise ApiConflictError( - "The given version is does not match the expected " "target version of the updated property", - reason=e, - ) - if e.response.status_code == 413: - raise ApiValueError("The value is too long", reason=e) - raise - return response - - def delete_page_property(self, page_id, page_property): - """ - Delete the page (content) property e.g. delete key of hash - :param page_id: content_id format - :param page_property: key of property - :return: - """ - url = f"rest/api/content/{page_id}/property/{str(page_property)}" - try: - response = self.delete(path=url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_page_property(self, page_id, page_property_key): - """ - Get the page (content) property e.g. get key of hash - :param page_id: content_id format - :param page_property_key: key of property - :return: - """ - url = f"rest/api/content/{page_id}/property/{str(page_property_key)}" - try: - response = self.get(path=url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, or no property with the " - "given key, or the calling user does not have permission to view " - "the content", - reason=e, - ) - - raise - - return response - - def get_page_properties(self, page_id): - """ - Get the page (content) properties - :param page_id: content_id format - :return: get properties - """ - url = f"rest/api/content/{page_id}/property" - - try: - response = self.get(path=url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no content with the given id, " - "or the calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response - - def get_page_ancestors(self, page_id): - """ - Provide the ancestors from the page (content) id - :param page_id: content_id format - :return: get properties - """ - url = f"rest/api/content/{page_id}?expand=ancestors" - - try: - response = self.get(path=url) - except HTTPError as e: - if e.response.status_code == 404: - raise ApiPermissionError( - "The calling user does not have permission to view the content", - reason=e, - ) - - raise - - return response.get("ancestors") - - def clean_all_caches(self): - """Clean all caches from cache management""" - headers = self.form_token_headers - return self.delete("rest/cacheManagement/1.0/cacheEntries", headers=headers) - - def clean_package_cache(self, cache_name="com.gliffy.cache.gon"): - """Clean caches from cache management - e.g. - com.gliffy.cache.gon - org.hibernate.cache.internal.StandardQueryCache_v5 - """ - headers = self.form_token_headers - data = {"cacheName": cache_name} - return self.delete("rest/cacheManagement/1.0/cacheEntries", data=data, headers=headers) - - def get_all_groups(self, start=0, limit=1000): - """ - Get all groups from Confluence User management - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of groups to return, this may be restricted by - fixed system limits. Default: 1000 - :return: - """ - url = f"rest/api/group?limit={limit}&start={start}" - - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view groups", - reason=e, - ) - - raise - - return response.get("results") - - def create_group(self, name): - """ - Create a group by given group parameter - - :param name: str - :return: New group params - """ - url = "rest/api/admin/group" - data = {"name": name, "type": "group"} - return self.post(url, data=data) - - def remove_group(self, name): - """ - Delete a group by given group parameter - If you delete a group and content is restricted to that group, the content will be hidden from all users - - :param name: str - :return: - """ - log.info("Removing group: %s during Confluence remove_group method execution", name) - url = f"rest/api/admin/group/{name}" - - try: - response = self.delete(url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no group with the given name, " - "or the calling user does not have permission to delete it", - reason=e, - ) - raise - - return response - - def get_group_members(self, group_name="confluence-users", start=0, limit=1000, expand=None): - """ - Get a paginated collection of users in the given group - :param group_name - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of users to return, this may be restricted by - fixed system limits. Default: 1000 - :param expand: OPTIONAL: A comma separated list of properties to expand on the content. status - :return: - """ - url = f"rest/api/group/{group_name}/member?limit={limit}&start={start}&expand={expand}" - - try: - response = self.get(url) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view users", - reason=e, - ) - - raise - - return response.get("results") - - def get_all_members(self, group_name="confluence-users", expand=None): - """ - Get collection of all users in the given group - :param group_name - :param expand: OPTIONAL: A comma separated list of properties to expand on the content. status - :return: - """ - limit = 50 - flag = True - step = 0 - members = [] - while flag: - values = self.get_group_members( - group_name=group_name, - start=len(members), - limit=limit, - expand=expand, - ) - step += 1 - if len(values) == 0: - flag = False - else: - members.extend(values) - if not members: - print(f"Did not get members from {group_name} group, please check permissions or connectivity") - return members - - def get_space(self, space_key, expand="description.plain,homepage", params=None): - """ - Get information about a space through space key - :param space_key: The unique space key name - :param expand: OPTIONAL: additional info from description, homepage - :param params: OPTIONAL: dictionary of additional URL parameters - :return: Returns the space along with its ID - """ - url = f"rest/api/space/{space_key}" - params = params or {} - if expand: - params["expand"] = expand - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no space with the given key, " - "or the calling user does not have permission to view the space", - reason=e, - ) - raise - return response - - def get_space_content( - self, - space_key, - depth="all", - start=0, - limit=500, - content_type=None, - expand="body.storage", - ): - """ - Get space content. - You can specify which type of content want to receive, or get all content types. - Use expand to get specific content properties or page - :param content_type: - :param space_key: The unique space key name - :param depth: OPTIONAL: all|root - Gets all space pages or only root pages - :param start: OPTIONAL: The start point of the collection to return. Default: 0. - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 500 - :param expand: OPTIONAL: by default expands page body in confluence storage format. - See atlassian documentation for more information. - :return: Returns the space along with its ID - """ - - content_type = f"{'/' + content_type if content_type else ''}" - url = f"rest/api/space/{space_key}/content{content_type}" - params = { - "depth": depth, - "start": start, - "limit": limit, - } - if expand: - params["expand"] = expand - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no space with the given key, " - "or the calling user does not have permission to view the space", - reason=e, - ) - raise - return response - - def get_home_page_of_space(self, space_key): - """ - Get information about a space through space key - :param space_key: The unique space key name - :return: Returns homepage - """ - return self.get_space(space_key, expand="homepage").get("homepage") - - def create_space(self, space_key, space_name): - """ - Create space - :param space_key: - :param space_name: - :return: - """ - data = {"key": space_key, "name": space_name} - self.post("rest/api/space", data=data) - - def delete_space(self, space_key): - """ - Delete space - :param space_key: - :return: - """ - url = f"rest/api/space/{space_key}" - - try: - response = self.delete(url) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no space with the given key, " - "or the calling user does not have permission to delete it", - reason=e, - ) - - raise - - return response - - def get_space_property(self, space_key, expand=None): - url = f"rest/api/space/{space_key}/property" - params = {} - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no space with the given key, " - "or the calling user does not have permission to view the space", - reason=e, - ) - - raise - - return response - - def get_user_details_by_username(self, username, expand=None): - """ - Get information about a user through username - :param username: The username - :param expand: OPTIONAL expand for get status of user. - Possible param is "status". Results are "Active, Deactivated" - :return: Returns the user details - """ - url = "rest/api/user" - params = {"username": username} - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view users", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError( - "The user with the given username or userkey does not exist", - reason=e, - ) - - raise - - return response - - def get_user_details_by_accountid(self, accountid, expand=None): - """ - Get information about a user through accountid - :param accountid: The account id - :param expand: OPTIONAL expand for get status of user. - Possible param is "status". Results are "Active, Deactivated" - :return: Returns the user details - """ - url = "rest/api/user" - params = {"accountId": accountid} - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view users", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError( - "The user with the given account does not exist", - reason=e, - ) - - raise - - return response - - def get_user_details_by_userkey(self, userkey, expand=None): - """ - Get information about a user through user key - :param userkey: The user key - :param expand: OPTIONAL expand for get status of user. - Possible param is "status". Results are "Active, Deactivated" - :return: Returns the user details - """ - url = "rest/api/user" - params = {"key": userkey} - if expand: - params["expand"] = expand - - try: - response = self.get(url, params=params) - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to view users", - reason=e, - ) - if e.response.status_code == 404: - raise ApiNotFoundError( - "The user with the given username or userkey does not exist", - reason=e, - ) - - raise - - return response - - def cql( - self, - cql, - start=0, - limit=None, - expand=None, - include_archived_spaces=None, - excerpt=None, - ): - """ - Get results from cql search result with all related fields - Search for entities in Confluence using the Confluence Query Language (CQL) - :param cql: - :param start: OPTIONAL: The start point of the collection to return. Default: 0. - :param limit: OPTIONAL: The limit of the number of issues to return, this may be restricted by - fixed system limits. Default by built-in method: 25 - :param excerpt: the excerpt strategy to apply to the result, one of : indexed, highlight, none. - This defaults to highlight - :param expand: OPTIONAL: the properties to expand on the search result, - this may cause database requests for some properties - :param include_archived_spaces: OPTIONAL: whether to include content in archived spaces in the result, - this defaults to false - :return: - """ - params = {} - if start is not None: - params["start"] = int(start) - if limit is not None: - params["limit"] = int(limit) - if cql is not None: - params["cql"] = cql - if expand is not None: - params["expand"] = expand - if include_archived_spaces is not None: - params["includeArchivedSpaces"] = include_archived_spaces - if excerpt is not None: - params["excerpt"] = excerpt - - try: - response = self.get("rest/api/search", params=params) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError("The query cannot be parsed", reason=e) - - raise - - return response - - def get_page_as_pdf(self, page_id): - """ - Export page as standard pdf exporter - :param page_id: Page ID - :return: PDF File - """ - headers = self.form_token_headers - url = f"spaces/flyingpdf/pdfpageexport.action?pageId={page_id}" - if self.api_version == "cloud" or self.cloud: - url = self.get_pdf_download_url_for_confluence_cloud(url) - if not url: - log.error("Failed to get download PDF url.") - raise ApiNotFoundError("Failed to export page as PDF", reason="Failed to get download PDF url.") - # To download the PDF file, the request should be with no headers of authentications. - return requests.get(url, timeout=75).content - return self.get(url, headers=headers, not_json_response=True) - - def get_page_as_word(self, page_id): - """ - Export page as standard word exporter. - :param page_id: Page ID - :return: Word File - """ - headers = self.form_token_headers - url = f"exportword?pageId={page_id}" - return self.get(url, headers=headers, not_json_response=True) - - def get_space_export(self, space_key: str, export_type: str) -> str: - """ - Export a Confluence space to a file of the specified type. - (!) This method was developed for Confluence Cloud and may not work with Confluence on-prem. - (!) This is an experimental method that does not trigger an officially supported REST endpoint. - It may break if Atlassian changes the space export front-end logic. - - :param space_key: The key of the space to export. - :param export_type: The type of export to perform. Valid values are: 'html', 'csv', 'xml', 'pdf'. - :return: The URL to download the exported file. - """ - - def get_atl_request(link: str): - # Nested function used to get atl_token used for XSRF protection. - # This is only applicable to html/csv/xml space exports - try: - response = self.get(link, advanced_mode=True) - parsed_html = BeautifulSoup(response.text, "html.parser") - atl_token = parsed_html.find("input", {"name": "atl_token"}).get("value") # type: ignore[union-attr] - return atl_token - except Exception as e: - raise ApiError("Problems with getting the atl_token for get_space_export method :", reason=e) - - # Checks if space_ke parameter is valid and if api_token has relevant permissions to space - self.get_space(space_key=space_key, expand="permissions") - - try: - log.info( - "Initiated experimental get_space_export method for export type: " - + export_type - + " from Confluence space: " - + space_key - ) - if export_type == "csv": - form_data = dict( - atl_token=get_atl_request(f"spaces/exportspacecsv.action?key={space_key}"), - exportType="TYPE_CSV", - contentOption="all", - includeComments="true", - confirm="Export", - ) - elif export_type == "html": - form_data = { - "atl_token": get_atl_request(f"spaces/exportspacehtml.action?key={space_key}"), - "exportType": "TYPE_HTML", - "contentOption": "visibleOnly", - "includeComments": "true", - "confirm": "Export", - } - elif export_type == "xml": - form_data = { - "atl_token": get_atl_request(f"spaces/exportspacexml.action?key={space_key}"), - "exportType": "TYPE_XML", - "contentOption": "all", - "includeComments": "true", - "confirm": "Export", - } - elif export_type == "pdf": - url = "spaces/flyingpdf/doflyingpdf.action?key=" + space_key - log.info("Initiated PDF space export") - return self.get_pdf_download_url_for_confluence_cloud(url) - else: - raise ValueError("Invalid export_type parameter value. Valid values are: 'html/csv/xml/pdf'") - url = self.url_joiner(url=self.url, path=f"spaces/doexportspace.action?key={space_key}") - - # Sending a POST request that triggers the space export. - response = self.session.post(url, headers=self.form_token_headers, data=form_data) - parsed_html = BeautifulSoup(response.text, "html.parser") - # Getting the poll URL to get the export progress status - try: - poll_url = cast("str", parsed_html.find("meta", {"name": "ajs-pollURI"}).get("content")) # type: ignore[union-attr] - except Exception as e: - raise ApiError("Problems with getting the poll_url for get_space_export method :", reason=e) - running_task = True - while running_task: - try: - progress_response = self.get(poll_url) or {} - log.info(f"Space {space_key} export status: {progress_response.get('message', 'None')}") - if progress_response is not {} and progress_response.get("complete"): - parsed_html = BeautifulSoup(progress_response.get("message"), "html.parser") - download_url = cast("str", parsed_html.find("a", {"class": "space-export-download-path"}).get("href")) # type: ignore - if self.url in download_url: - return download_url - else: - combined_url = self.url + download_url - # Ensure only one /wiki is included in the path - if combined_url.count("/wiki") > 1: - combined_url = combined_url.replace("/wiki/wiki", "/wiki") - return combined_url - time.sleep(30) - except Exception as e: - raise ApiError( - "Encountered error during space export status check from space " + space_key, reason=e - ) - - return "None" # Return None if the while loop does not return a value - except Exception as e: - raise ApiError("Encountered error during space export from space " + space_key, reason=e) - - def export_page(self, page_id): - """ - Alias method for export page as pdf - :param page_id: Page ID - :return: PDF File - """ - return self.get_page_as_pdf(page_id) - - def get_descendant_page_id(self, space, parent_id, title): - """ - Provide space, parent_id and title of the descendant page, it will return the descendant page_id - :param space: str - :param parent_id: int - :param title: str - :return: page_id of the page whose title is passed in argument - """ - page_id = "" - - url = f'rest/api/content/search?cql=parent={parent_id}%20AND%20space="{space}"' - - try: - response = self.get(url, {}) - except HTTPError as e: - if e.response.status_code == 400: - raise ApiValueError("The CQL is invalid or missing", reason=e) - - raise - - for each_page in response.get("results", []): - if each_page.get("title") == title: - page_id = each_page.get("id") - break - return page_id - - def reindex(self): - """ - It is not public method for reindex Confluence - :return: - """ - url = "rest/prototype/1/index/reindex" - return self.post(url) - - def reindex_get_status(self): - """ - Get reindex status of Confluence - :return: - """ - url = "rest/prototype/1/index/reindex" - return self.get(url) - - def health_check(self): - """ - Get health status - https://confluence.atlassian.com/jirakb/how-to-retrieve-health-check-results-using-rest-api-867195158.html - :return: - """ - # check as Troubleshooting & Support Tools Plugin - response = self.get("rest/troubleshooting/1.0/check/") - if not response: - # check as support tools - response = self.get("rest/supportHealthCheck/1.0/check/") - return response - - def synchrony_enable(self): - """ - Enable Synchrony - :return: - """ - headers = {"X-Atlassian-Token": "no-check"} - url = "rest/synchrony-interop/enable" - return self.post(url, headers=headers) - - def synchrony_disable(self): - """ - Disable Synchrony - :return: - """ - headers = {"X-Atlassian-Token": "no-check"} - url = "rest/synchrony-interop/disable" - return self.post(url, headers=headers) - - def check_access_mode(self): - return self.get("rest/api/accessmode") - - def anonymous(self): - """ - Get information about how anonymous is represented in confluence - :return: - """ - try: - response = self.get("rest/api/user/anonymous") - except HTTPError as e: - if e.response.status_code == 403: - raise ApiPermissionError( - "The calling user does not have permission to use Confluence", - reason=e, - ) - - raise - - return response - - def get_plugins_info(self): - """ - Provide plugins info - :return a json of installed plugins - """ - url = "rest/plugins/1.0/" - return self.get(url, headers=self.no_check_headers, trailing=True) - - def get_plugin_info(self, plugin_key): - """ - Provide plugin info - :return a json of installed plugins - """ - url = f"rest/plugins/1.0/{plugin_key}-key" - return self.get(url, headers=self.no_check_headers, trailing=True) - - def get_plugin_license_info(self, plugin_key): - """ - Provide plugin license info - :return a json specific License query - """ - url = f"rest/plugins/1.0/{plugin_key}-key/license" - return self.get(url, headers=self.no_check_headers, trailing=True) - - def upload_plugin(self, plugin_path): - """ - Provide plugin path for upload into Jira e.g. useful for auto deploy - :param plugin_path: - :return: - """ - files = {"plugin": open(plugin_path, "rb")} - upm_token = self.request( - method="GET", - path="rest/plugins/1.0/", - headers=self.no_check_headers, - trailing=True, - ).headers["upm-token"] - url = f"rest/plugins/1.0/?token={upm_token}" - return self.post(url, files=files, headers=self.no_check_headers) - - def disable_plugin(self, plugin_key): - """ - Disable a plugin - :param plugin_key: - :return: - """ - app_headers = { - "X-Atlassian-Token": "no-check", - "Content-Type": "application/vnd.atl.plugins+json", - } - url = f"rest/plugins/1.0/{plugin_key}-key" - data = {"status": "disabled"} - return self.put(url, data=data, headers=app_headers) - - def enable_plugin(self, plugin_key): - """ - Enable a plugin - :param plugin_key: - :return: - """ - app_headers = { - "X-Atlassian-Token": "no-check", - "Content-Type": "application/vnd.atl.plugins+json", - } - url = f"rest/plugins/1.0/{plugin_key}-key" - data = {"status": "enabled"} - return self.put(url, data=data, headers=app_headers) - - def delete_plugin(self, plugin_key): - """ - Delete plugin - :param plugin_key: - :return: - """ - url = f"rest/plugins/1.0/{plugin_key}-key" - return self.delete(url) - - def check_plugin_manager_status(self): - url = "rest/plugins/latest/safe-mode" - return self.request(method="GET", path=url, headers=self.safe_mode_headers) - - def update_plugin_license(self, plugin_key, raw_license): - """ - Update license for plugin - :param plugin_key: - :param raw_license: - :return: - """ - app_headers = { - "X-Atlassian-Token": "no-check", - "Content-Type": "application/vnd.atl.plugins+json", - } - url = f"/plugins/1.0/{plugin_key}/license" - data = {"rawLicense": raw_license} - return self.put(url, data=data, headers=app_headers) - - def check_long_tasks_result(self, start=None, limit=None, expand=None): - """ - Get result of long tasks - :param start: OPTIONAL: The start point of the collection to return. Default: None (0). - :param limit: OPTIONAL: The limit of the number of pages to return, this may be restricted by - fixed system limits. Default: 50 - :param expand: - :return: - """ - params = {} - if expand: - params["expand"] = expand - if start: - params["start"] = start - if limit: - params["limit"] = limit - return self.get("rest/api/longtask", params=params) - - def check_long_task_result(self, task_id, expand=None): - """ - Get result of long tasks - :param task_id: task id - :param expand: - :return: - """ - params = None - if expand: - params = {"expand": expand} - - try: - response = self.get(f"rest/api/longtask/{task_id}", params=params) - except HTTPError as e: - if e.response.status_code == 404: - # Raise ApiError as the documented reason is ambiguous - raise ApiError( - "There is no task with the given key, " "or the calling user does not have permission to view it", - reason=e, - ) - - raise - - return response - - def get_pdf_download_url_for_confluence_cloud(self, url): - """ - Confluence cloud does not return the PDF document when the PDF - export is initiated. Instead, it starts a process in the background - and provides a link to download the PDF once the process completes. - This functions polls the long-running task page and returns the - download url of the PDF. - This method is used in get_space_export() method for space-> PDF export. - :param url: URL to initiate PDF export - :return: Download url for PDF file - """ - try: - running_task = True - headers = self.form_token_headers - log.info("Initiate PDF export from Confluence Cloud") - response = self.get(url, headers=headers, not_json_response=True) - response_string = response.decode(encoding="utf-8", errors="ignore") - task_id = response_string.split('name="ajs-taskId" content="')[1].split('">')[0] - poll_url = f"/services/api/v1/task/{task_id}/progress" - while running_task: - log.info("Check if export task has completed.") - progress_response = self.get(poll_url) - percentage_complete = int(progress_response.get("progress", 0)) - task_state = progress_response.get("state") - if task_state == "FAILED": - log.error("PDF conversion not successful.") - return None - elif percentage_complete == 100: - running_task = False - log.info(f"Task completed - {task_state}") - log.debug("Extract task results to download PDF.") - task_result_url = progress_response.get("result") - else: - log.info(f"{percentage_complete}% - {task_state}") - time.sleep(3) - log.debug("Task successfully done, querying the task result for the download url") - # task result url starts with /wiki, remove it. - task_content = self.get(task_result_url[5:], not_json_response=True) - download_url = task_content.decode(encoding="utf-8", errors="strict") - log.debug("Successfully got the download url") - return download_url - except IndexError as e: - log.error(e) - return None - - def audit( - self, - start_date=None, - end_date=None, - start=None, - limit=None, - search_string=None, - ): - """ - Fetch a paginated list of AuditRecord instances dating back to a certain time - :param start_date: - :param end_date: - :param start: - :param limit: - :param search_string: - :return: - """ - url = "rest/api/audit" - params = {} - if start_date: - params["startDate"] = start_date - if end_date: - params["endDate"] = end_date - if start: - params["start"] = start - if limit: - params["limit"] = limit - if search_string: - params["searchString"] = search_string - return self.get(url, params=params) - - """ - ############################################################################################## - # Confluence whiteboards (cloud only!) # - ############################################################################################## - """ - - def create_whiteboard(self, spaceId, title=None, parentId=None): - url = "/api/v2/whiteboards" - data = {"spaceId": spaceId} - if title is not None: - data["title"] = title - if parentId is not None: - data["parentId"] = parentId - return self.post(url, data=data) - - def get_whiteboard(self, whiteboard_id): - try: - url = "/api/v2/whiteboards/%s" % (whiteboard_id) - return self.get(url) - except HTTPError as e: - # Default 404 error handling is ambiguous - if e.response.status_code == 404: - raise ApiValueError( - "Whiteboard not found. Check confluence instance url and/or if whiteboard id exists", reason=e - ) - - raise - - def delete_whiteboard(self, whiteboard_id): - try: - url = "/api/v2/whiteboards/%s" % (whiteboard_id) - return self.delete(url) - except HTTPError as e: - # # Default 404 error handling is ambiguous - if e.response.status_code == 404: - raise ApiValueError( - "Whiteboard not found. Check confluence instance url and/or if whiteboard id exists", reason=e - ) - - raise - - """ - ############################################################################################## - # Team Calendars REST API implements (https://jira.atlassian.com/browse/CONFSERVER-51003) # - ############################################################################################## - """ - - def team_calendars_get_sub_calendars(self, include=None, viewing_space_key=None, calendar_context=None): - """ - Get subscribed calendars - :param include: - :param viewing_space_key: - :param calendar_context: - :return: - """ - url = "rest/calendar-services/1.0/calendar/subcalendars" - params = {} - if include: - params["include"] = include - if viewing_space_key: - params["viewingSpaceKey"] = viewing_space_key - if calendar_context: - params["calendarContext"] = calendar_context - return self.get(url, params=params) - - def team_calendars_get_sub_calendars_watching_status(self, include=None): - url = "rest/calendar-services/1.0/calendar/subcalendars/watching/status" - params = {} - if include: - params["include"] = include - return self.get(url, params=params) - - def team_calendar_events(self, sub_calendar_id, start, end, user_time_zone_id=None): - """ - Get calendar event status - :param sub_calendar_id: - :param start: - :param end: - :param user_time_zone_id: - :return: - """ - url = "rest/calendar-services/1.0/calendar/events" - params = {} - if sub_calendar_id: - params["subCalendarId"] = sub_calendar_id - if user_time_zone_id: - params["userTimeZoneId"] = user_time_zone_id - if start: - params["start"] = start - if end: - params["end"] = end - return self.get(url, params=params) - - def get_mobile_parameters(self, username): - """ - Get mobile paramaters - :param username: - :return: - """ - url = f"rest/mobile/1.0/profile/{username}" - return self.get(url) - - def avatar_upload_for_user(self, user_key, data): - """ - - :param user_key: - :param data: json like {"avatarDataURI":"image in base64"} - :return: - """ - url = f"rest/user-profile/1.0/{user_key}/avatar/upload" - return self.post(url, data=data) - - def avatar_set_default_for_user(self, user_key): - """ - :param user_key: - :return: - """ - url = f"rest/user-profile/1.0/{user_key}/avatar/default" - return self.get(url) - - def add_user(self, email, fullname, username, password): - """ - That method related to creating user via json rpc for Confluence Server - """ - params = {"email": email, "fullname": fullname, "name": username} - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "addUser", - "params": [params, password], - } - self.post(url, data=data) - - def change_user_password(self, username, password): - """ - That method related to changing user password via json rpc for Confluence Server - """ - params = {"name": username} - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "changeUserPassword", - "params": [params, password], - } - self.post(url, data=data) - - def change_my_password(self, oldpass, newpass): - """ - That method related to changing calling user's own password via json rpc for Confluence Server - """ - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "changeMyPassword", - "params": [oldpass, newpass], - } - self.post(url, data=data) - - def add_user_to_group(self, username, group_name): - """ - Add given user to a group - - :param username: str - username of user to add to group - :param group_name: str - name of group to add user to - :return: Current state of the group - """ - url = f"rest/api/user/{username}/group/{group_name}" - return self.put(url) - - def remove_user_from_group(self, username, group_name): - """ - Remove the given {@link User} identified by username from the given {@link Group} identified by groupName. - This method is idempotent i.e. if the membership is not present then no action will be taken. - - :param username: str - username of user to add to group - :param group_name: str - name of group to add user to - :return: Current state of the group - """ - url = f"rest/api/user/{username}/group/{group_name}" - return self.delete(url) - - # Space Permissions - def get_all_space_permissions(self, space_key): - """ - Returns list of permissions granted to users and groups in the particular space. - :param space_key: - :return: - """ - url = f"rest/api/space/{space_key}/permissions" - return self.get(url) - - def set_permissions_to_multiple_items_for_space(self, space_key, user_key=None, group_name=None, operations=None): - """ - Sets permissions to multiple users/groups in the given space. - Request should contain all permissions that user/group/anonymous user will have in a given space. - If permission is absent in the request, but was granted before, it will be revoked. - If empty list of permissions passed to user/group/anonymous user, - then all their existing permissions will be revoked. - If user/group/anonymous user not mentioned in the request, their permissions will not be revoked. - - Maximum 40 different users/groups/anonymous user could be passed in the request. - :param space_key: - :param user_key: - :param group_name: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions" - params = [] - - if user_key: - params.append({"userKey": user_key, "operations": operations or []}) - - if group_name: - params.append({"groupName": group_name, "operations": operations or []}) - - if not user_key and not group_name: - params.append({"operations": operations or []}) - payload_json = json.dumps(params) - return self.post(url, data=payload_json) - - def get_permissions_granted_to_anonymous_for_space(self, space_key): - """ - Get permissions granted to anonymous user for the given space - :param space_key: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/anonymous" - return self.get(url) - - def set_permissions_to_anonymous_for_space(self, space_key, operations=None): - """ - Grant permissions to anonymous user in the given space. Operation doesn't override existing permissions, - will only add those one that weren't granted before. Multiple permissions could be passed in one request. - Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/anonymous" - data = {"operations": operations or []} - return self.put(url, data=data) - - def remove_permissions_from_anonymous_for_space(self, space_key, operations=None): - """ - Revoke permissions from anonymous user in the given space. - If anonymous user doesn't have permissions that we are trying to revoke, - those permissions will be silently skipped. Multiple permissions could be passed in one request. - Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/anonymous/revoke" - data = {"operations": operations or []} - return self.put(url, data=data) - - def get_permissions_granted_to_group_for_space(self, space_key, group_name): - """ - Get permissions granted to group for the given space - :param space_key: - :param group_name: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/group/{group_name}" - return self.get(url) - - def set_permissions_to_group_for_space(self, space_key, group_name, operations=None): - """ - Grant permissions to group in the given space. - Operation doesn't override existing permissions, will only add those one that weren't granted before. - Multiple permissions could be passed in one request. Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param group_name: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/group/{group_name}" - data = {"operations": operations or []} - return self.put(url, data=data) - - def remove_permissions_from_group_for_space(self, space_key, group_name, operations=None): - """ - Revoke permissions from a group in the given space. - If group doesn't have permissions that we are trying to revoke, - those permissions will be silently skipped. Multiple permissions could be passed in one request. - Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param group_name: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/group/{group_name}/revoke" - data = {"operations": operations or []} - return self.put(url, data=data) - - def get_permissions_granted_to_user_for_space(self, space_key, user_key): - """ - Get permissions granted to user for the given space - :param space_key: - :param user_key: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/user/{user_key}" - return self.get(url) - - def set_permissions_to_user_for_space(self, space_key, user_key, operations=None): - """ - Grant permissions to user in the given space. - Operation doesn't override existing permissions, will only add those one that weren't granted before. - Multiple permissions could be passed in one request. Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param user_key: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/user/{user_key}" - data = {"operations": operations or []} - return self.put(url, data=data) - - def remove_permissions_from_user_for_space(self, space_key, user_key, operations=None): - """ - Revoke permissions from a user in the given space. - If user doesn't have permissions that we are trying to revoke, - those permissions will be silently skipped. Multiple permissions could be passed in one request. - Supported targetType and operationKey pairs: - - space read - space administer - space export - space restrict - space delete_own - space delete_mail - page create - page delete - blogpost create - blogpost delete - comment create - comment delete - attachment create - attachment delete - :param space_key: - :param user_key: - :param operations: - :return: - """ - url = f"rest/api/space/{space_key}/permissions/user/{user_key}/revoke" - data = {"operations": operations or []} - return self.put(url, params=data) - - def add_space_permissions( - self, - space_key, - subject_type, - subject_id, - operation_key, - operation_target, - ): - """ - Add permissions to a space - - :param space_key: str - key of space to add permissions to - :param subject_type: str - type of subject to add permissions for - :param subject_id: str - id of subject to add permissions for - :param operation_key: str - key of operation to add permissions for - :param operation_target: str - target of operation to add permissions for - :return: Current permissions of space - """ - url = f"rest/api/space/{space_key}/permission" - data = { - "subject": {"type": subject_type, "identifier": subject_id}, - "operation": {"key": operation_key, "target": operation_target}, - "_links": {}, - } - - return self.post(url, data=data, headers=self.experimental_headers) - - def add_space_permission_json_rpc(self, space_key, user, permission): - """ - The JSON-RPC APIs for Confluence are provided here to help you browse and discover APIs you have access to. - JSON-RPC APIs operate differently than REST APIs. - To learn more about how to use these APIs, - please refer to the Confluence JSON-RPC documentation on Atlassian Developers. - """ - if self.api_version == "cloud" or self.cloud: - return {} - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "addPermissionToSpace", - "id": 9, - "params": [permission, user, space_key], - } - return self.post(url, data=data).get("result") or {} - - def remove_space_permission(self, space_key, user, permission): - """ - The JSON-RPC APIs for Confluence are provided here to help you browse and discover APIs you have access to. - JSON-RPC APIs operate differently than REST APIs. - To learn more about how to use these APIs, - please refer to the Confluence JSON-RPC documentation on Atlassian Developers. - """ - if self.api_version == "cloud" or self.cloud: - return {} - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "removePermissionFromSpace", - "id": 9, - "params": [permission, user, space_key], - } - return self.post(url, data=data).get("result") or {} - - def get_space_permissions(self, space_key): - """ - The JSON-RPC APIs for Confluence are provided here to help you browse and discover APIs you have access to. - JSON-RPC APIs operate differently than REST APIs. - To learn more about how to use these APIs, - please refer to the Confluence JSON-RPC documentation on Atlassian Developers. - """ - if self.api_version == "cloud" or self.cloud: - return self.get_space(space_key=space_key, expand="permissions") - url = "rpc/json-rpc/confluenceservice-v2" - data = { - "jsonrpc": "2.0", - "method": "getSpacePermissionSets", - "id": 7, - "params": [space_key], - } - return self.post(url, data=data).get("result") or {} - - def get_subtree_of_content_ids(self, page_id): - """ - Get subtree of page ids - :param page_id: - :return: Set of page ID - """ - output = list() - output.append(page_id) - children_pages = self.get_page_child_by_type(page_id) - for page in children_pages: - child_subtree = self.get_subtree_of_content_ids(page.get("id")) - if child_subtree: - output.extend([p for p in child_subtree]) - return set(output) - - def set_inline_tasks_checkbox(self, page_id, task_id, status): - """ - Set inline task element value - status is CHECKED or UNCHECKED - :return: - """ - url = f"rest/inlinetasks/1/task/{page_id}/{task_id}/" - data = {"status": status, "trigger": "VIEW_PAGE"} - return self.post(url, json=data) - - def get_jira_metadata(self, page_id): - """ - Get linked Jira ticket metadata - PRIVATE method - :param page_id: Page Id - :return: - """ - url = "rest/jira-metadata/1.0/metadata" - params = {"pageId": page_id} - return self.get(url, params=params) - - def get_jira_metadata_aggregated(self, page_id): - """ - Get linked Jira ticket aggregated metadata - PRIVATE method - :param page_id: Page Id - :return: - """ - url = "rest/jira-metadata/1.0/metadata/aggregate" - params = {"pageId": page_id} - return self.get(url, params=params) - - def clean_jira_metadata_cache(self, global_id): - """ - Clean cache for linked Jira app link - PRIVATE method - :param global_id: ID of Jira app link - :return: - """ - url = "rest/jira-metadata/1.0/metadata/cache" - params = {"globalId": global_id} - return self.delete(url, params=params) - - # Collaborative editing - def collaborative_editing_get_configuration(self): - """ - Get collaborative editing configuration - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/configuration" - return self.get(url, headers=self.no_check_headers) - - def collaborative_editing_disable(self): - """ - Disable collaborative editing - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/disable" - return self.post(url, headers=self.no_check_headers) - - def collaborative_editing_enable(self): - """ - Disable collaborative editing - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/enable" - return self.post(url, headers=self.no_check_headers) - - def collaborative_editing_restart(self): - """ - Disable collaborative editing - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/restart" - return self.post(url, headers=self.no_check_headers) - - def collaborative_editing_shared_draft_status(self): - """ - Status of collaborative editing - Related to the on-prem setup Confluence Data Center - :return: false or true parameter in json - { - "sharedDraftsEnabled": false - } - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/status" - return self.get(url, headers=self.no_check_headers) - - def collaborative_editing_synchrony_status(self): - """ - Status of collaborative editing - Related to the on-prem setup Confluence Data Center - :return: stopped or running parameter in json - { - "status": "stopped" - } - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony-interop/synchrony-status" - return self.get(url, headers=self.no_check_headers) - - def synchrony_get_configuration(self): - """ - Status of collaborative editing - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = "rest/synchrony/1.0/config/status" - return self.get(url, headers=self.no_check_headers) - - def synchrony_remove_draft(self, page_id): - """ - Status of collaborative editing - Related to the on-prem setup Confluence Data Center - :return: - """ - if self.cloud: - return ApiNotAcceptable - url = f"rest/synchrony/1.0/content/{page_id}/changes/unpublished" - return self.delete(url) - - def get_license_details(self): - """ - Returns the license detailed information - """ - url = "rest/license/1.0/license/details" - return self.get(url) - - def get_license_user_count(self): - """ - Returns the total used seats in the license - """ - url = "rest/license/1.0/license/userCount" - return self.get(url) - - def get_license_remaining(self): - """ - Returns the available license seats remaining - """ - url = "rest/license/1.0/license/remainingSeats" - return self.get(url) - - def get_license_max_users(self): - """ - Returns the license max users - """ - url = "rest/license/1.0/license/maxUsers" - return self.get(url) + super().__init__(url, *args, **kwargs) - def raise_for_status(self, response): - """ - Checks the response for an error status and raises an exception with the error message provided by the server - :param response: - :return: - """ - if response.status_code == 401 and response.headers.get("Content-Type") != "application/json;charset=UTF-8": - raise HTTPError("Unauthorized (401)", response=response) - if 400 <= response.status_code < 600: - try: - j = response.json() - error_msg = j["message"] - except Exception as e: - log.error(e) - response.raise_for_status() - else: - raise HTTPError(error_msg, response=response) +__all__ = [ + "ConfluenceCloud", + "ConfluenceServer", + "ConfluenceBase", +] diff --git a/atlassian/confluence/base.py b/atlassian/confluence/base.py new file mode 100644 index 000000000..fd5917770 --- /dev/null +++ b/atlassian/confluence/base.py @@ -0,0 +1,159 @@ +# coding=utf-8 + +import copy +import logging + +from ..rest_client import AtlassianRestAPI + +log = logging.getLogger(__name__) + + +class ConfluenceBase(AtlassianRestAPI): + """ + Base class for Confluence API operations. + """ + + def __init__(self, url, *args, **kwargs): + """ + Init the rest api wrapper + + :param url: string: The base url used for the rest api. + :param *args: list: The fixed arguments for the AtlassianRestApi. + :param **kwargs: dict: The keyword arguments for the AtlassianRestApi. + + :return: nothing + """ + self._update_data(kwargs.pop("data", {})) + if url is None: + url = self.get_link("self") + if isinstance(url, list): # Server has a list of links + url = url[0] + super().__init__(url, *args, **kwargs) + + def _sub_url(self, url): + """ + Get the full url from a relative one. + + :param url: string: The sub url + :return: The absolute url + """ + return self.url_joiner(self.url, url) + + @property + def _new_session_args(self): + """ + Get the kwargs for new objects (session, root, version,...). + + :return: A dict with the kwargs for new objects + """ + return { + "session": self._session, + "cloud": self.cloud, + "api_root": self.api_root, + "api_version": self.api_version, + } + + def _update_data(self, data): + """ + Internal function to update the data. + + :param data: dict: The new data. + :return: The updated object + """ + self.__data = data + return self + + @property + def data(self): + """ + Get the internal cached data. For data integrity a deep copy is returned. + + :return: A copy of the data cache + """ + return copy.copy(self.__data) + + def get_data(self, id, default=None): + """ + Get a data element from the internal data cache. For data integrity a deep copy is returned. + If data isn't present, the default value is returned. + + :param id: string: The data element to return + :param default: any (default is None): The value to return if id is not present + + :return: The requested data element + """ + return copy.copy(self.__data[id]) if id in self.__data else default + + def get_link(self, link): + """ + Get a link from the data. + + :param link: string: The link identifier + :return: The requested link or None if it isn't present + """ + links = self.get_data("links") + if links is None or link not in links: + return None + return links[link]["href"] + + def _get_paged( + self, + url, + params=None, + data=None, + flags=None, + trailing=None, + absolute=False, + ): + """ + Used to get the paged data + + :param url: string: The url to retrieve + :param params: dict (default is None): The parameter's + :param data: dict (default is None): The data + :param flags: string[] (default is None): The flags + :param trailing: bool (default is None): If True, a trailing slash is added to the url + :param absolute: bool (default is False): If True, the url is used absolute and not relative to the root + + :return: A generator object for the data elements + """ + if params is None: + params = {} + + while True: + response = self.get( + url, + trailing=trailing, + params=params, + data=data, + flags=flags, + absolute=absolute, + ) + if "results" not in response: + return + + yield from response.get("results", []) + + if self.cloud: + url = response.get("_links", {}).get("next", {}).get("href") + if url is None: + break + # From now on we have absolute URLs with parameters + absolute = True + # Params are now provided by the url + params = {} + # Trailing should not be added as it is already part of the url + trailing = False + else: + if response.get("_links", {}).get("next") is None: + break + # For server, we need to extract the next page URL from the _links.next.href + next_url = response.get("_links", {}).get("next", {}).get("href") + if next_url is None: + break + url = next_url + absolute = True + params = {} + trailing = False + + return diff --git a/atlassian/confluence/cloud/__init__.py b/atlassian/confluence/cloud/__init__.py new file mode 100644 index 000000000..d7c686072 --- /dev/null +++ b/atlassian/confluence/cloud/__init__.py @@ -0,0 +1,229 @@ +# coding=utf-8 + +from .base import ConfluenceCloudBase + + +class Cloud(ConfluenceCloudBase): + """ + Confluence Cloud REST API wrapper + """ + + def __init__(self, url="https://api.atlassian.com/", *args, **kwargs): + # Set default values only if not provided + if "cloud" not in kwargs: + kwargs["cloud"] = True + if "api_version" not in kwargs: + kwargs["api_version"] = "2" + if "api_root" not in kwargs: + kwargs["api_root"] = "wiki/api/v2" + url = url.strip("/") + super(Cloud, self).__init__(url, *args, **kwargs) + + # Content Management + def get_content(self, content_id, **kwargs): + """Get content by ID.""" + return self.get(f"content/{content_id}", **kwargs) + + def get_content_by_type(self, content_type, **kwargs): + """Get content by type (page, blogpost, etc.).""" + return self.get("content", params={"type": content_type, **kwargs}) + + def create_content(self, data, **kwargs): + """Create new content.""" + return self.post("content", data=data, **kwargs) + + def update_content(self, content_id, data, **kwargs): + """Update existing content.""" + return self.put(f"content/{content_id}", data=data, **kwargs) + + def delete_content(self, content_id, **kwargs): + """Delete content.""" + return self.delete(f"content/{content_id}", **kwargs) + + def get_content_children(self, content_id, **kwargs): + """Get child content.""" + return self.get(f"content/{content_id}/children", **kwargs) + + def get_content_descendants(self, content_id, **kwargs): + """Get descendant content.""" + return self.get(f"content/{content_id}/descendants", **kwargs) + + def get_content_ancestors(self, content_id, **kwargs): + """Get ancestor content.""" + return self.get(f"content/{content_id}/ancestors", **kwargs) + + # Space Management + def get_spaces(self, **kwargs): + """Get all spaces.""" + return self.get("space", **kwargs) + + def get_space(self, space_id, **kwargs): + """Get space by ID.""" + return self.get(f"space/{space_id}", **kwargs) + + def create_space(self, data, **kwargs): + """Create new space.""" + return self.post("space", data=data, **kwargs) + + def update_space(self, space_id, data, **kwargs): + """Update existing space.""" + return self.put(f"space/{space_id}", data=data, **kwargs) + + def delete_space(self, space_id, **kwargs): + """Delete space.""" + return self.delete(f"space/{space_id}", **kwargs) + + def get_space_content(self, space_id, **kwargs): + """Get space content.""" + return self.get(f"space/{space_id}/content", **kwargs) + + # User Management + def get_users(self, **kwargs): + """Get all users.""" + return self.get("user", **kwargs) + + def get_user(self, user_id, **kwargs): + """Get user by ID.""" + return self.get(f"user/{user_id}", **kwargs) + + def get_current_user(self, **kwargs): + """Get current user.""" + return self.get("user/current", **kwargs) + + # Group Management + def get_groups(self, **kwargs): + """Get all groups.""" + return self.get("group", **kwargs) + + def get_group(self, group_id, **kwargs): + """Get group by ID.""" + return self.get(f"group/{group_id}", **kwargs) + + def get_group_members(self, group_id, **kwargs): + """Get group members.""" + return self.get(f"group/{group_id}/member", **kwargs) + + # Label Management + def get_labels(self, **kwargs): + """Get all labels.""" + return self.get("label", **kwargs) + + def get_content_labels(self, content_id, **kwargs): + """Get content labels.""" + return self.get(f"content/{content_id}/label", **kwargs) + + def add_content_labels(self, content_id, data, **kwargs): + """Add labels to content.""" + return self.post(f"content/{content_id}/label", data=data, **kwargs) + + def remove_content_label(self, content_id, label_id, **kwargs): + """Remove label from content.""" + return self.delete(f"content/{content_id}/label/{label_id}", **kwargs) + + # Attachment Management + def get_attachments(self, content_id, **kwargs): + """Get content attachments.""" + return self.get(f"content/{content_id}/child/attachment", **kwargs) + + def get_attachment(self, attachment_id, **kwargs): + """Get attachment by ID.""" + return self.get(f"content/{attachment_id}", **kwargs) + + def create_attachment(self, content_id, data, **kwargs): + """Create new attachment.""" + return self.post(f"content/{content_id}/child/attachment", data=data, **kwargs) + + def update_attachment(self, attachment_id, data, **kwargs): + """Update existing attachment.""" + return self.put(f"content/{attachment_id}", data=data, **kwargs) + + def delete_attachment(self, attachment_id, **kwargs): + """Delete attachment.""" + return self.delete(f"content/{attachment_id}", **kwargs) + + # Comment Management + def get_comments(self, content_id, **kwargs): + """Get content comments.""" + return self.get(f"content/{content_id}/child/comment", **kwargs) + + def get_comment(self, comment_id, **kwargs): + """Get comment by ID.""" + return self.get(f"content/{comment_id}", **kwargs) + + def create_comment(self, content_id, data, **kwargs): + """Create new comment.""" + return self.post(f"content/{content_id}/child/comment", data=data, **kwargs) + + def update_comment(self, comment_id, data, **kwargs): + """Update existing comment.""" + return self.put(f"content/{comment_id}", data=data, **kwargs) + + def delete_comment(self, comment_id, **kwargs): + """Delete comment.""" + return self.delete(f"content/{comment_id}", **kwargs) + + # Search + def search_content(self, query, **kwargs): + """Search content.""" + return self.get("content/search", params={"cql": query, **kwargs}) + + def search_spaces(self, query, **kwargs): + """Search spaces.""" + return self.get("space/search", params={"query": query, **kwargs}) + + # Page Properties + def get_content_properties(self, content_id, **kwargs): + """Get content properties.""" + return self.get(f"content/{content_id}/property", **kwargs) + + def get_content_property(self, content_id, property_key, **kwargs): + """Get content property by key.""" + return self.get(f"content/{content_id}/property/{property_key}", **kwargs) + + def create_content_property(self, content_id, data, **kwargs): + """Create new content property.""" + return self.post(f"content/{content_id}/property", data=data, **kwargs) + + def update_content_property(self, content_id, property_key, data, **kwargs): + """Update existing content property.""" + return self.put(f"content/{content_id}/property/{property_key}", data=data, **kwargs) + + def delete_content_property(self, content_id, property_key, **kwargs): + """Delete content property.""" + return self.delete(f"content/{content_id}/property/{property_key}", **kwargs) + + # Templates + def get_templates(self, **kwargs): + """Get all templates.""" + return self.get("template", **kwargs) + + def get_template(self, template_id, **kwargs): + """Get template by ID.""" + return self.get(f"template/{template_id}", **kwargs) + + # Analytics + def get_content_analytics(self, content_id, **kwargs): + """Get content analytics.""" + return self.get(f"content/{content_id}/analytics", **kwargs) + + def get_space_analytics(self, space_id, **kwargs): + """Get space analytics.""" + return self.get(f"space/{space_id}/analytics", **kwargs) + + # Export + def export_content(self, content_id, **kwargs): + """Export content.""" + return self.get(f"content/{content_id}/export", **kwargs) + + def export_space(self, space_id, **kwargs): + """Export space.""" + return self.get(f"space/{space_id}/export", **kwargs) + + # Utility Methods + def get_metadata(self, **kwargs): + """Get API metadata.""" + return self.get("metadata", **kwargs) + + def get_health(self, **kwargs): + """Get API health status.""" + return self.get("health", **kwargs) diff --git a/atlassian/confluence/cloud/base.py b/atlassian/confluence/cloud/base.py new file mode 100644 index 000000000..c9a227d61 --- /dev/null +++ b/atlassian/confluence/cloud/base.py @@ -0,0 +1,104 @@ +# coding=utf-8 + +import logging +from requests import HTTPError + +from ..base import ConfluenceBase + +log = logging.getLogger(__name__) + + +class ConfluenceCloudBase(ConfluenceBase): + """ + Base class for Confluence Cloud API operations. + """ + + def __init__(self, url, *args, **kwargs): + """ + Init the rest api wrapper + + :param url: string: The base url used for the rest api. + :param *args: list: The fixed arguments for the AtlassianRestApi. + :param **kwargs: dict: The keyword arguments for the AtlassianRestApi. + + :return: nothing + """ + super(ConfluenceCloudBase, self).__init__(url, *args, **kwargs) + + def raise_for_status(self, response): + """ + Checks the response for errors and throws an exception if return code >= 400 + + Implementation for Confluence Cloud according to + https://developer.atlassian.com/cloud/confluence/rest/v2/intro/#about + + :param response: + :return: + """ + if 400 <= response.status_code < 600: + try: + j = response.json() + if "message" in j: + error_msg = j["message"] + if "detail" in j: + error_msg = f"{error_msg}\n{str(j['detail'])}" + else: + error_msg = f"HTTP {response.status_code}: {response.reason}" + except Exception as e: + log.error(e) + response.raise_for_status() + else: + raise HTTPError(error_msg, response=response) + else: + response.raise_for_status() + + def _get_paged( + self, + url, + params=None, + data=None, + flags=None, + trailing=None, + absolute=False, + ): + """ + Used to get the paged data for Confluence Cloud + + :param url: string: The url to retrieve + :param params: dict (default is None): The parameter's + :param data: dict (default is None): The data + :param flags: string[] (default is None): The flags + :param trailing: bool (default is None): If True, a trailing slash is added to the url + :param absolute: bool (default is False): If True, the url is used absolute and not relative to the root + + :return: A generator object for the data elements + """ + if params is None: + params = {} + + while True: + response = self.get( + url, + trailing=trailing, + params=params, + data=data, + flags=flags, + absolute=absolute, + ) + if "results" not in response: + return + + yield from response.get("results", []) + + # Confluence Cloud uses _links.next.href for pagination + url = response.get("_links", {}).get("next", {}).get("href") + if url is None: + break + # From now on we have absolute URLs with parameters + absolute = True + # Params are now provided by the url + params = {} + # Trailing should not be added as it is already part of the url + trailing = False + + return diff --git a/atlassian/confluence/server/__init__.py b/atlassian/confluence/server/__init__.py new file mode 100644 index 000000000..ed268df23 --- /dev/null +++ b/atlassian/confluence/server/__init__.py @@ -0,0 +1,339 @@ +# coding=utf-8 + +from .base import ConfluenceServerBase + + +class Server(ConfluenceServerBase): + """ + Confluence Server REST API wrapper + """ + + def __init__(self, url, *args, **kwargs): + # Set default values only if not provided + if "cloud" not in kwargs: + kwargs["cloud"] = False + if "api_version" not in kwargs: + kwargs["api_version"] = "1.0" + if "api_root" not in kwargs: + kwargs["api_root"] = "rest/api" + url = url.strip("/") + f"/{kwargs['api_root']}/{kwargs['api_version']}" + super(Server, self).__init__(url, *args, **kwargs) + + # Content Management + def get_content(self, content_id, **kwargs): + """Get content by ID.""" + return self.get(f"content/{content_id}", **kwargs) + + def get_content_by_type(self, content_type, **kwargs): + """Get content by type (page, blogpost, etc.).""" + return self.get("content", params={"type": content_type, **kwargs}) + + def create_content(self, data, **kwargs): + """Create new content.""" + return self.post("content", data=data, **kwargs) + + def update_content(self, content_id, data, **kwargs): + """Update existing content.""" + return self.put(f"content/{content_id}", data=data, **kwargs) + + def delete_content(self, content_id, **kwargs): + """Delete content.""" + return self.delete(f"content/{content_id}", **kwargs) + + def get_content_children(self, content_id, **kwargs): + """Get child content.""" + return self.get(f"content/{content_id}/child", **kwargs) + + def get_content_descendants(self, content_id, **kwargs): + """Get descendant content.""" + return self.get(f"content/{content_id}/descendant", **kwargs) + + def get_content_ancestors(self, content_id, **kwargs): + """Get ancestor content.""" + return self.get(f"content/{content_id}/ancestor", **kwargs) + + def get_content_by_title(self, space_key, title, **kwargs): + """Get content by title and space key.""" + return self.get("content", params={"spaceKey": space_key, "title": title, **kwargs}) + + def get_content_by_id(self, content_id, **kwargs): + """Get content by ID with expand options.""" + return self.get(f"content/{content_id}", **kwargs) + + def get_all_pages_from_space(self, space_key, **kwargs): + """Get all pages from space.""" + return self.get("content", params={"spaceKey": space_key, "type": "page", **kwargs}) + + def get_all_blog_posts_from_space(self, space_key, **kwargs): + """Get all blog posts from space.""" + return self.get("content", params={"spaceKey": space_key, "type": "blogpost", **kwargs}) + + def get_page_by_title(self, space_key, title, **kwargs): + """Get page by title and space key.""" + return self.get("content", params={"spaceKey": space_key, "title": title, "type": "page", **kwargs}) + + def get_blog_post_by_title(self, space_key, title, **kwargs): + """Get blog post by title and space key.""" + return self.get("content", params={"spaceKey": space_key, "title": title, "type": "blogpost", **kwargs}) + + def page_exists(self, space_key, title, **kwargs): + """Check if page exists.""" + result = self.get_page_by_title(space_key, title, **kwargs) + return len(result.get("results", [])) > 0 + + def blog_post_exists(self, space_key, title, **kwargs): + """Check if blog post exists.""" + result = self.get_blog_post_by_title(space_key, title, **kwargs) + return len(result.get("results", [])) > 0 + + def get_content_id(self, space_key, title, content_type="page"): + """Get content ID by title and space key.""" + if content_type == "page": + result = self.get_page_by_title(space_key, title) + elif content_type == "blogpost": + result = self.get_blog_post_by_title(space_key, title) + else: + raise ValueError("content_type must be 'page' or 'blogpost'") + + results = result.get("results", []) + if results: + return results[0]["id"] + return None + + def get_page_space(self, page_id): + """Get space key from page ID.""" + page = self.get_content(page_id, expand="space") + return page.get("space", {}).get("key") + + # Space Management + def get_spaces(self, **kwargs): + """Get all spaces.""" + return self.get("space", **kwargs) + + def get_space(self, space_key, **kwargs): + """Get space by key.""" + return self.get(f"space/{space_key}", **kwargs) + + def create_space(self, data, **kwargs): + """Create new space.""" + return self.post("space", data=data, **kwargs) + + def update_space(self, space_key, data, **kwargs): + """Update existing space.""" + return self.put(f"space/{space_key}", data=data, **kwargs) + + def delete_space(self, space_key, **kwargs): + """Delete space.""" + return self.delete(f"space/{space_key}", **kwargs) + + def get_space_content(self, space_key, **kwargs): + """Get space content.""" + return self.get("content", params={"spaceKey": space_key, **kwargs}) + + def get_space_permissions(self, space_key, **kwargs): + """Get space permissions.""" + return self.get(f"space/{space_key}/permission", **kwargs) + + def get_space_settings(self, space_key, **kwargs): + """Get space settings.""" + return self.get(f"space/{space_key}/settings", **kwargs) + + # User Management + def get_users(self, **kwargs): + """Get all users.""" + return self.get("user", **kwargs) + + def get_user(self, username, **kwargs): + """Get user by username.""" + return self.get("user", params={"username": username, **kwargs}) + + def get_current_user(self, **kwargs): + """Get current user.""" + return self.get("user/current", **kwargs) + + def get_user_by_key(self, user_key, **kwargs): + """Get user by key.""" + return self.get("user", params={"key": user_key, **kwargs}) + + # Group Management + def get_groups(self, **kwargs): + """Get all groups.""" + return self.get("group", **kwargs) + + def get_group(self, group_name, **kwargs): + """Get group by name.""" + return self.get("group", params={"groupname": group_name, **kwargs}) + + def get_group_members(self, group_name, **kwargs): + """Get group members.""" + return self.get(f"group/{group_name}/member", **kwargs) + + def add_user_to_group(self, group_name, username, **kwargs): + """Add user to group.""" + return self.post(f"group/{group_name}/member", data={"name": username}, **kwargs) + + def remove_user_from_group(self, group_name, username, **kwargs): + """Remove user from group.""" + return self.delete(f"group/{group_name}/member/{username}", **kwargs) + + # Label Management + def get_labels(self, **kwargs): + """Get all labels.""" + return self.get("label", **kwargs) + + def get_content_labels(self, content_id, **kwargs): + """Get content labels.""" + return self.get(f"content/{content_id}/label", **kwargs) + + def add_content_labels(self, content_id, data, **kwargs): + """Add labels to content.""" + return self.post(f"content/{content_id}/label", data=data, **kwargs) + + def remove_content_label(self, content_id, label_name, **kwargs): + """Remove label from content.""" + return self.delete(f"content/{content_id}/label/{label_name}", **kwargs) + + def get_all_pages_by_label(self, label, **kwargs): + """Get all pages by label.""" + return self.get("content", params={"label": label, "type": "page", **kwargs}) + + def get_all_blog_posts_by_label(self, label, **kwargs): + """Get all blog posts by label.""" + return self.get("content", params={"label": label, "type": "blogpost", **kwargs}) + + # Attachment Management + def get_attachments(self, content_id, **kwargs): + """Get content attachments.""" + return self.get(f"content/{content_id}/child/attachment", **kwargs) + + def get_attachment(self, attachment_id, **kwargs): + """Get attachment by ID.""" + return self.get(f"content/{attachment_id}", **kwargs) + + def create_attachment(self, content_id, data, **kwargs): + """Create new attachment.""" + return self.post(f"content/{content_id}/child/attachment", data=data, **kwargs) + + def update_attachment(self, attachment_id, data, **kwargs): + """Update existing attachment.""" + return self.put(f"content/{attachment_id}", data=data, **kwargs) + + def delete_attachment(self, attachment_id, **kwargs): + """Delete attachment.""" + return self.delete(f"content/{attachment_id}", **kwargs) + + def download_attachment(self, attachment_id, **kwargs): + """Download attachment.""" + return self.get(f"content/{attachment_id}/download", **kwargs) + + # Comment Management + def get_comments(self, content_id, **kwargs): + """Get content comments.""" + return self.get(f"content/{content_id}/child/comment", **kwargs) + + def get_comment(self, comment_id, **kwargs): + """Get comment by ID.""" + return self.get(f"content/{comment_id}", **kwargs) + + def create_comment(self, content_id, data, **kwargs): + """Create new comment.""" + return self.post(f"content/{content_id}/child/comment", data=data, **kwargs) + + def update_comment(self, comment_id, data, **kwargs): + """Update existing comment.""" + return self.put(f"content/{comment_id}", data=data, **kwargs) + + def delete_comment(self, comment_id, **kwargs): + """Delete comment.""" + return self.delete(f"content/{comment_id}", **kwargs) + + # Search + def search_content(self, query, **kwargs): + """Search content using CQL.""" + return self.get("content/search", params={"cql": query, **kwargs}) + + def search_spaces(self, query, **kwargs): + """Search spaces.""" + return self.get("space/search", params={"query": query, **kwargs}) + + # Page Properties + def get_content_properties(self, content_id, **kwargs): + """Get content properties.""" + return self.get(f"content/{content_id}/property", **kwargs) + + def get_content_property(self, content_id, property_key, **kwargs): + """Get content property by key.""" + return self.get(f"content/{content_id}/property/{property_key}", **kwargs) + + def create_content_property(self, content_id, data, **kwargs): + """Create new content property.""" + return self.post(f"content/{content_id}/property", data=data, **kwargs) + + def update_content_property(self, content_id, property_key, data, **kwargs): + """Update existing content property.""" + return self.put(f"content/{content_id}/property/{property_key}", data=data, **kwargs) + + def delete_content_property(self, content_id, property_key, **kwargs): + """Delete content property.""" + return self.delete(f"content/{content_id}/property/{property_key}", **kwargs) + + # Templates + def get_templates(self, **kwargs): + """Get all templates.""" + return self.get("template", **kwargs) + + def get_template(self, template_id, **kwargs): + """Get template by ID.""" + return self.get(f"template/{template_id}", **kwargs) + + # Draft Management + def get_draft_content(self, content_id, **kwargs): + """Get draft content.""" + return self.get(f"content/{content_id}", params={"status": "draft", **kwargs}) + + def get_all_draft_pages_from_space(self, space_key, **kwargs): + """Get all draft pages from space.""" + return self.get("content", params={"spaceKey": space_key, "type": "page", "status": "draft", **kwargs}) + + def get_all_draft_blog_posts_from_space(self, space_key, **kwargs): + """Get all draft blog posts from space.""" + return self.get("content", params={"spaceKey": space_key, "type": "blogpost", "status": "draft", **kwargs}) + + # Trash Management + def get_trash_content(self, space_key, **kwargs): + """Get trash content.""" + return self.get("content", params={"spaceKey": space_key, "status": "trashed", **kwargs}) + + def get_all_pages_from_space_trash(self, space_key, **kwargs): + """Get all pages from space trash.""" + return self.get("content", params={"spaceKey": space_key, "type": "page", "status": "trashed", **kwargs}) + + def get_all_blog_posts_from_space_trash(self, space_key, **kwargs): + """Get all blog posts from space trash.""" + return self.get("content", params={"spaceKey": space_key, "type": "blogpost", "status": "trashed", **kwargs}) + + # Export + def export_content(self, content_id, **kwargs): + """Export content.""" + return self.get(f"content/{content_id}/export", **kwargs) + + def export_space(self, space_key, **kwargs): + """Export space.""" + return self.get(f"space/{space_key}/export", **kwargs) + + # Utility Methods + def get_metadata(self, **kwargs): + """Get API metadata.""" + return self.get("metadata", **kwargs) + + def get_health(self, **kwargs): + """Get API health status.""" + return self.get("health", **kwargs) + + def reindex(self, **kwargs): + """Trigger reindex.""" + return self.post("reindex", **kwargs) + + def get_reindex_progress(self, **kwargs): + """Get reindex progress.""" + return self.get("reindex", **kwargs) diff --git a/atlassian/confluence/server/base.py b/atlassian/confluence/server/base.py new file mode 100644 index 000000000..5ae2d10c1 --- /dev/null +++ b/atlassian/confluence/server/base.py @@ -0,0 +1,105 @@ +# coding=utf-8 + +import logging +from requests import HTTPError + +from ..base import ConfluenceBase + +log = logging.getLogger(__name__) + + +class ConfluenceServerBase(ConfluenceBase): + """ + Base class for Confluence Server API operations. + """ + + def __init__(self, url, *args, **kwargs): + """ + Init the rest api wrapper + + :param url: string: The base url used for the rest api. + :param *args: list: The fixed arguments for the AtlassianRestApi. + :param **kwargs: dict: The keyword arguments for the AtlassianRestApi. + + :return: nothing + """ + super(ConfluenceServerBase, self).__init__(url, *args, **kwargs) + + def raise_for_status(self, response): + """ + Checks the response for errors and throws an exception if return code >= 400 + + Implementation for Confluence Server according to + https://developer.atlassian.com/server/confluence/rest/v1002/intro/#about + + :param response: + :return: + """ + if 400 <= response.status_code < 600: + try: + j = response.json() + if "message" in j: + error_msg = j["message"] + if "detail" in j: + error_msg = f"{error_msg}\n{str(j['detail'])}" + else: + error_msg = f"HTTP {response.status_code}: {response.reason}" + except Exception as e: + log.error(e) + response.raise_for_status() + else: + raise HTTPError(error_msg, response=response) + else: + response.raise_for_status() + + def _get_paged( + self, + url, + params=None, + data=None, + flags=None, + trailing=False, + absolute=False, + ): + """ + Used to get the paged data for Confluence Server + + :param url: string: The url to retrieve + :param params: dict (default is None): The parameter's + :param data: dict (default is None): The data + :param flags: string[] (default is None): The flags + :param trailing: bool (default is None): If True, a trailing slash is added to the url + :param absolute: bool (default is False): If True, the url is used absolute and not relative to the root + + :return: A generator object for the data elements + """ + if params is None: + params = {} + + while True: + response = self.get( + url, + trailing=trailing, + params=params, + data=data, + flags=flags, + absolute=absolute, + ) + if "results" not in response: + return + + yield from response.get("results", []) + + # Confluence Server uses _links.next.href for pagination + if response.get("_links", {}).get("next") is None: + break + # For server, we need to extract the next page URL from the _links.next.href + next_url = response.get("_links", {}).get("next", {}).get("href") + if next_url is None: + break + url = next_url + absolute = True + params = {} + trailing = False + + return diff --git a/docs/confluence.rst b/docs/confluence.rst index 0938dd239..42bcd5171 100644 --- a/docs/confluence.rst +++ b/docs/confluence.rst @@ -1,6 +1,74 @@ Confluence module ================= +The Confluence module now provides both Cloud and Server implementations +with dedicated APIs for each platform. + +New Implementation +------------------ + +The new Confluence implementation follows the same pattern as other modules +with dedicated Cloud and Server classes: + +.. code-block:: python + + from atlassian.confluence import ConfluenceCloud, ConfluenceServer + + # For Confluence Cloud + confluence_cloud = ConfluenceCloud( + url="https://your-domain.atlassian.net", + token="your-api-token" + ) + + # For Confluence Server + confluence_server = ConfluenceServer( + url="https://your-confluence-server.com", + username="your-username", + password="your-password" + ) + +Cloud vs Server Differences +--------------------------- + +| Feature | Cloud | Server | +| Authentication | API Token | Username/Password | +| API Version | v2 | v1.0 | +| API Root | `wiki/api/v2` | `rest/api/1.0` | +| Content IDs | UUID strings | Numeric IDs | +| Space IDs | UUID strings | Space keys | + +Common Operations +----------------- + +Both implementations support: + +- Content management (create, read, update, delete) +- Space management +- User and group management +- Label management +- Attachment handling +- Comment management +- Search functionality +- Page properties +- Export capabilities + +Server-Specific Features +------------------------ + +The Server implementation includes additional features: + +- Draft content management +- Trash content management +- Reindex operations +- Space permissions +- Space settings + +Legacy Implementation +--------------------- + +The original Confluence implementation is still available +for backward compatibility. + Get page info ------------- diff --git a/examples/confluence/README.md b/examples/confluence/README.md new file mode 100644 index 000000000..499e2476f --- /dev/null +++ b/examples/confluence/README.md @@ -0,0 +1,130 @@ +# Confluence Examples + +This directory contains examples demonstrating how to use the new Confluence API client with both Cloud and Server implementations. + +## Structure + +``` +examples/confluence/ +├── README.md +├── cloud/ +│ └── confluence_cloud_content_management.py +└── server/ + └── confluence_server_content_management.py +``` + +## Examples + +### Confluence Cloud + +The `confluence_cloud_content_management.py` example demonstrates: + +- Initializing the Confluence Cloud client +- Getting spaces and space content +- Retrieving pages and page details +- Working with page children, labels, comments, and attachments +- Searching for content +- Getting user information + +**Prerequisites:** +- Confluence Cloud instance +- API token (not username/password) + +**Usage:** +```bash +cd examples/confluence/cloud +python confluence_cloud_content_management.py +``` + +**Configuration:** +Update the following in the script: +- `url`: Your Confluence Cloud domain (e.g., `https://your-domain.atlassian.net`) +- `token`: Your API token + +### Confluence Server + +The `confluence_server_content_management.py` example demonstrates: + +- Initializing the Confluence Server client +- Getting spaces and space content +- Working with pages, blog posts, and drafts +- Managing page labels, comments, and attachments +- Searching with CQL (Confluence Query Language) +- User and group management +- Trash and draft content management + +**Prerequisites:** +- Confluence Server instance +- Username and password credentials + +**Usage:** +```bash +cd examples/confluence/server +python confluence_server_content_management.py +``` + +**Configuration:** +Update the following in the script: +- `url`: Your Confluence Server URL (e.g., `https://your-confluence-server.com`) +- `username`: Your username +- `password`: Your password + +## API Differences + +### Cloud vs Server + +| Feature | Cloud | Server | +|---------|-------|--------| +| Authentication | API Token | Username/Password | +| API Version | v2 | v1.0 | +| API Root | `wiki/api/v2` | `rest/api/1.0` | +| Pagination | `_links.next.href` | `_links.next.href` | +| Content IDs | UUID strings | Numeric IDs | +| Space IDs | UUID strings | Space keys | + +### Common Operations + +Both implementations support: + +- Content management (create, read, update, delete) +- Space management +- User and group management +- Label management +- Attachment handling +- Comment management +- Search functionality +- Page properties +- Export capabilities + +### Server-Specific Features + +The Server implementation includes additional features: + +- Draft content management +- Trash content management +- Reindex operations +- Space permissions +- Space settings + +## Error Handling + +All examples include basic error handling. In production applications, you should implement more robust error handling based on your specific requirements. + +## Rate Limiting + +Be aware of API rate limits: +- **Cloud**: Varies by plan, typically 1000 requests per hour +- **Server**: Depends on server configuration + +## Security Notes + +- Never commit credentials to version control +- Use environment variables or secure credential storage +- API tokens for Cloud are preferred over username/password +- Consider using OAuth 2.0 for production applications + +## Additional Resources + +- [Confluence Cloud REST API](https://developer.atlassian.com/cloud/confluence/rest/v2/intro/) +- [Confluence Server REST API](https://developer.atlassian.com/server/confluence/rest/v1002/intro/) +- [CQL (Confluence Query Language)](https://developer.atlassian.com/cloud/confluence/advanced-searching-using-cql/) diff --git a/examples/confluence/cloud/confluence_cloud_content_management.py b/examples/confluence/cloud/confluence_cloud_content_management.py new file mode 100644 index 000000000..a89c2b618 --- /dev/null +++ b/examples/confluence/cloud/confluence_cloud_content_management.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# coding=utf-8 +""" +Example: Confluence Cloud Content Management + +This example demonstrates how to use the Confluence Cloud API client +for basic content management operations. +""" + +from atlassian.confluence import ConfluenceCloud + + +def main(): + """Main function demonstrating Confluence Cloud content management.""" + + # Initialize Confluence Cloud client + # Replace with your actual Confluence Cloud URL and credentials + confluence = ConfluenceCloud( + url="https://your-domain.atlassian.net", token="your-api-token" # Use API token for Cloud + ) + + print("=== Confluence Cloud Content Management Example ===\n") + + try: + # Get all spaces + print("1. Getting all spaces...") + spaces = confluence.get_spaces() + print(f" Found {len(spaces.get('results', []))} spaces") + + if spaces.get("results"): + first_space = spaces["results"][0] + space_id = first_space["id"] + print(f" Using space: {first_space['name']} (ID: {space_id})") + + # Get space content + print("\n2. Getting space content...") + content = confluence.get_space_content(space_id) + print(f" Found {len(content.get('results', []))} content items") + + # Get content by type (pages) + print("\n3. Getting pages from space...") + pages = confluence.get_content_by_type("page") + print(f" Found {len(pages.get('results', []))} pages") + + if pages.get("results"): + first_page = pages["results"][0] + page_id = first_page["id"] + print(f" Using page: {first_page['title']} (ID: {page_id})") + + # Get page details + print("\n4. Getting page details...") + page_details = confluence.get_content(page_id) + print(f" Page title: {page_details.get('title')}") + print(f" Page type: {page_details.get('type')}") + print(f" Created: {page_details.get('createdAt')}") + + # Get page children + print("\n5. Getting page children...") + children = confluence.get_content_children(page_id) + print(f" Found {len(children.get('results', []))} child items") + + # Get page labels + print("\n6. Getting page labels...") + labels = confluence.get_content_labels(page_id) + print(f" Found {len(labels.get('results', []))} labels") + + # Get page comments + print("\n7. Getting page comments...") + comments = confluence.get_comments(page_id) + print(f" Found {len(comments.get('results', []))} comments") + + # Get page attachments + print("\n8. Getting page attachments...") + attachments = confluence.get_attachments(page_id) + print(f" Found {len(attachments.get('results', []))} attachments") + + # Search for content + print("\n9. Searching for content...") + search_results = confluence.search_content("type=page") + print(f" Found {len(search_results.get('results', []))} pages in search") + + # Get current user + print("\n10. Getting current user...") + current_user = confluence.get_current_user() + print(f" Current user: {current_user.get('displayName')} ({current_user.get('accountId')})") + + print("\n=== Example completed successfully! ===") + + except Exception as e: + print(f"\nError occurred: {e}") + print("Please check your credentials and Confluence Cloud URL.") + + +if __name__ == "__main__": + main() diff --git a/examples/confluence/server/confluence_server_content_management.py b/examples/confluence/server/confluence_server_content_management.py new file mode 100644 index 000000000..37d955da4 --- /dev/null +++ b/examples/confluence/server/confluence_server_content_management.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# coding=utf-8 +""" +Example: Confluence Server Content Management + +This example demonstrates how to use the Confluence Server API client +for basic content management operations. +""" + +from atlassian.confluence import ConfluenceServer + + +def main(): + """Main function demonstrating Confluence Server content management.""" + + # Initialize Confluence Server client + # Replace with your actual Confluence Server URL and credentials + confluence = ConfluenceServer( + url="https://your-confluence-server.com", username="your-username", password="your-password" + ) + + print("=== Confluence Server Content Management Example ===\n") + + try: + # Get all spaces + print("1. Getting all spaces...") + spaces = confluence.get_spaces() + print(f" Found {len(spaces.get('results', []))} spaces") + + if spaces.get("results"): + first_space = spaces["results"][0] + space_key = first_space["key"] + print(f" Using space: {first_space['name']} (Key: {space_key})") + + # Get space content + print("\n2. Getting space content...") + content = confluence.get_space_content(space_key) + print(f" Found {len(content.get('results', []))} content items") + + # Get all pages from space + print("\n3. Getting all pages from space...") + pages = confluence.get_all_pages_from_space(space_key) + print(f" Found {len(pages.get('results', []))} pages") + + if pages.get("results"): + first_page = pages["results"][0] + page_id = first_page["id"] + page_title = first_page["title"] + print(f" Using page: {page_title} (ID: {page_id})") + + # Check if page exists + print("\n4. Checking if page exists...") + page_exists = confluence.page_exists(space_key, page_title) + print(f" Page exists: {page_exists}") + + # Get page details + print("\n5. Getting page details...") + page_details = confluence.get_content_by_id(page_id, expand="space,version") + print(f" Page title: {page_details.get('title')}") + print(f" Page type: {page_details.get('type')}") + print(f" Version: {page_details.get('version', {}).get('number')}") + print(f" Space: {page_details.get('space', {}).get('name')}") + + # Get page children + print("\n6. Getting page children...") + children = confluence.get_content_children(page_id) + print(f" Found {len(children.get('results', []))} child items") + + # Get page labels + print("\n7. Getting page labels...") + labels = confluence.get_content_labels(page_id) + print(f" Found {len(labels.get('results', []))} labels") + + # Get page comments + print("\n8. Getting page comments...") + comments = confluence.get_comments(page_id) + print(f" Found {len(comments.get('results', []))} comments") + + # Get page attachments + print("\n9. Getting page attachments...") + attachments = confluence.get_attachments(page_id) + print(f" Found {len(attachments.get('results', []))} attachments") + + # Get page properties + print("\n10. Getting page properties...") + properties = confluence.get_content_properties(page_id) + print(f" Found {len(properties.get('results', []))} properties") + + # Get page space + print("\n11. Getting page space...") + page_space = confluence.get_page_space(page_id) + print(f" Page space key: {page_space}") + + # Get all blog posts from space + print("\n12. Getting all blog posts from space...") + blog_posts = confluence.get_all_blog_posts_from_space(space_key) + print(f" Found {len(blog_posts.get('results', []))} blog posts") + + # Get draft pages + print("\n13. Getting draft pages from space...") + draft_pages = confluence.get_all_draft_pages_from_space(space_key) + print(f" Found {len(draft_pages.get('results', []))} draft pages") + + # Get trash content + print("\n14. Getting trash content from space...") + trash_content = confluence.get_trash_content(space_key) + print(f" Found {len(trash_content.get('results', []))} trashed items") + + # Search for content using CQL + print("\n15. Searching for content using CQL...") + search_results = confluence.search_content("type=page") + print(f" Found {len(search_results.get('results', []))} pages in search") + + # Get current user + print("\n16. Getting current user...") + current_user = confluence.get_current_user() + print(f" Current user: {current_user.get('displayName')} ({current_user.get('username')})") + + # Get users + print("\n17. Getting users...") + users = confluence.get_users() + print(f" Found {len(users.get('results', []))} users") + + # Get groups + print("\n18. Getting groups...") + groups = confluence.get_groups() + print(f" Found {len(groups.get('results', []))} groups") + + # Get labels + print("\n19. Getting all labels...") + all_labels = confluence.get_labels() + print(f" Found {len(all_labels.get('results', []))} labels") + + # Get templates + print("\n20. Getting templates...") + templates = confluence.get_templates() + print(f" Found {len(templates.get('results', []))} templates") + + print("\n=== Example completed successfully! ===") + + except Exception as e: + print(f"\nError occurred: {e}") + print("Please check your credentials and Confluence Server URL.") + + +if __name__ == "__main__": + main() diff --git a/tests/confluence/__init__.py b/tests/confluence/__init__.py new file mode 100644 index 000000000..f1389746b --- /dev/null +++ b/tests/confluence/__init__.py @@ -0,0 +1,4 @@ +# coding=utf-8 +""" +Confluence tests package. +""" diff --git a/tests/confluence/test_confluence_cloud.py b/tests/confluence/test_confluence_cloud.py new file mode 100644 index 000000000..060135d97 --- /dev/null +++ b/tests/confluence/test_confluence_cloud.py @@ -0,0 +1,451 @@ +# coding=utf-8 +""" +Test cases for Confluence Cloud API client. +""" + +import pytest +from unittest.mock import patch + +from atlassian.confluence import ConfluenceCloud + + +@pytest.fixture +def confluence_cloud(): + """Fixture for ConfluenceCloud client.""" + return ConfluenceCloud(url="https://test.atlassian.net", token="test-token", cloud=True) + + +class TestConfluenceCloud: + """Test cases for ConfluenceCloud client.""" + + def test_init_defaults(self): + """Test ConfluenceCloud client initialization with default values.""" + confluence = ConfluenceCloud(url="https://test.atlassian.net", token="test-token") + assert confluence.api_version == "2" + assert confluence.api_root == "wiki/api/v2" + assert confluence.cloud is True + + def test_init_custom_values(self): + """Test ConfluenceCloud client initialization with custom values.""" + confluence = ConfluenceCloud( + url="https://test.atlassian.net", token="test-token", api_version="1", api_root="custom/api/root" + ) + # The class should respect custom values when provided + assert confluence.api_version == "1" + assert confluence.api_root == "custom/api/root" + + # Content Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_content(self, mock_get, confluence_cloud): + """Test get_content method.""" + mock_get.return_value = {"id": "123", "title": "Test Page", "type": "page"} + result = confluence_cloud.get_content("123") + mock_get.assert_called_once_with("content/123", **{}) + assert result == {"id": "123", "title": "Test Page", "type": "page"} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_by_type(self, mock_get, confluence_cloud): + """Test get_content_by_type method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_cloud.get_content_by_type("page") + mock_get.assert_called_once_with("content", params={"type": "page", **{}}) + assert result == {"results": [{"id": "123", "title": "Test Page"}]} + + @patch.object(ConfluenceCloud, "post") + def test_create_content(self, mock_post, confluence_cloud): + """Test create_content method.""" + content_data = {"title": "New Page", "type": "page", "spaceId": "TEST"} + mock_post.return_value = {"id": "456", "title": "New Page", "type": "page"} + result = confluence_cloud.create_content(content_data) + mock_post.assert_called_once_with("content", data=content_data, **{}) + assert result == {"id": "456", "title": "New Page", "type": "page"} + + @patch.object(ConfluenceCloud, "put") + def test_update_content(self, mock_put, confluence_cloud): + """Test update_content method.""" + content_data = {"title": "Updated Page"} + mock_put.return_value = {"id": "123", "title": "Updated Page"} + result = confluence_cloud.update_content("123", content_data) + mock_put.assert_called_once_with("content/123", data=content_data, **{}) + assert result == {"id": "123", "title": "Updated Page"} + + @patch.object(ConfluenceCloud, "delete") + def test_delete_content(self, mock_delete, confluence_cloud): + """Test delete_content method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.delete_content("123") + mock_delete.assert_called_once_with("content/123", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_children(self, mock_get, confluence_cloud): + """Test get_content_children method.""" + mock_get.return_value = {"results": [{"id": "789", "title": "Child Page"}]} + result = confluence_cloud.get_content_children("123") + mock_get.assert_called_once_with("content/123/children", **{}) + assert result == {"results": [{"id": "789", "title": "Child Page"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_descendants(self, mock_get, confluence_cloud): + """Test get_content_descendants method.""" + mock_get.return_value = {"results": [{"id": "999", "title": "Descendant Page"}]} + result = confluence_cloud.get_content_descendants("123") + mock_get.assert_called_once_with("content/123/descendants", **{}) + assert result == {"results": [{"id": "999", "title": "Descendant Page"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_ancestors(self, mock_get, confluence_cloud): + """Test get_content_ancestors method.""" + mock_get.return_value = {"results": [{"id": "111", "title": "Ancestor Page"}]} + result = confluence_cloud.get_content_ancestors("123") + mock_get.assert_called_once_with("content/123/ancestors", **{}) + assert result == {"results": [{"id": "111", "title": "Ancestor Page"}]} + + # Space Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_spaces(self, mock_get, confluence_cloud): + """Test get_spaces method.""" + mock_get.return_value = {"results": [{"id": "TEST", "name": "Test Space"}]} + result = confluence_cloud.get_spaces() + mock_get.assert_called_once_with("space", **{}) + assert result == {"results": [{"id": "TEST", "name": "Test Space"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_space(self, mock_get, confluence_cloud): + """Test get_space method.""" + mock_get.return_value = {"id": "TEST", "name": "Test Space"} + result = confluence_cloud.get_space("TEST") + mock_get.assert_called_once_with("space/TEST", **{}) + assert result == {"id": "TEST", "name": "Test Space"} + + @patch.object(ConfluenceCloud, "post") + def test_create_space(self, mock_post, confluence_cloud): + """Test create_space method.""" + space_data = {"name": "New Space", "key": "NEW"} + mock_post.return_value = {"id": "NEW", "name": "New Space", "key": "NEW"} + result = confluence_cloud.create_space(space_data) + mock_post.assert_called_once_with("space", data=space_data, **{}) + assert result == {"id": "NEW", "name": "New Space", "key": "NEW"} + + @patch.object(ConfluenceCloud, "put") + def test_update_space(self, mock_put, confluence_cloud): + """Test update_space method.""" + space_data = {"name": "Updated Space"} + mock_put.return_value = {"id": "TEST", "name": "Updated Space"} + result = confluence_cloud.update_space("TEST", space_data) + mock_put.assert_called_once_with("space/TEST", data=space_data, **{}) + assert result == {"id": "TEST", "name": "Updated Space"} + + @patch.object(ConfluenceCloud, "delete") + def test_delete_space(self, mock_delete, confluence_cloud): + """Test delete_space method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.delete_space("TEST") + mock_delete.assert_called_once_with("space/TEST", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceCloud, "get") + def test_get_space_content(self, mock_get, confluence_cloud): + """Test get_space_content method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Page in Space"}]} + result = confluence_cloud.get_space_content("TEST") + mock_get.assert_called_once_with("space/TEST/content", **{}) + assert result == {"results": [{"id": "123", "title": "Page in Space"}]} + + # User Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_users(self, mock_get, confluence_cloud): + """Test get_users method.""" + mock_get.return_value = {"results": [{"id": "user1", "name": "Test User"}]} + result = confluence_cloud.get_users() + mock_get.assert_called_once_with("user", **{}) + assert result == {"results": [{"id": "user1", "name": "Test User"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_user(self, mock_get, confluence_cloud): + """Test get_user method.""" + mock_get.return_value = {"id": "user1", "name": "Test User"} + result = confluence_cloud.get_user("user1") + mock_get.assert_called_once_with("user/user1", **{}) + assert result == {"id": "user1", "name": "Test User"} + + @patch.object(ConfluenceCloud, "get") + def test_get_current_user(self, mock_get, confluence_cloud): + """Test get_current_user method.""" + mock_get.return_value = {"id": "current", "name": "Current User"} + result = confluence_cloud.get_current_user() + mock_get.assert_called_once_with("user/current", **{}) + assert result == {"id": "current", "name": "Current User"} + + # Group Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_groups(self, mock_get, confluence_cloud): + """Test get_groups method.""" + mock_get.return_value = {"results": [{"id": "group1", "name": "Test Group"}]} + result = confluence_cloud.get_groups() + mock_get.assert_called_once_with("group", **{}) + assert result == {"results": [{"id": "group1", "name": "Test Group"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_group(self, mock_get, confluence_cloud): + """Test get_group method.""" + mock_get.return_value = {"id": "group1", "name": "Test Group"} + result = confluence_cloud.get_group("group1") + mock_get.assert_called_once_with("group/group1", **{}) + assert result == {"id": "group1", "name": "Test Group"} + + @patch.object(ConfluenceCloud, "get") + def test_get_group_members(self, mock_get, confluence_cloud): + """Test get_group_members method.""" + mock_get.return_value = {"results": [{"id": "user1", "name": "Test User"}]} + result = confluence_cloud.get_group_members("group1") + mock_get.assert_called_once_with("group/group1/member", **{}) + assert result == {"results": [{"id": "user1", "name": "Test User"}]} + + # Label Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_labels(self, mock_get, confluence_cloud): + """Test get_labels method.""" + mock_get.return_value = {"results": [{"id": "label1", "name": "Test Label"}]} + result = confluence_cloud.get_labels() + mock_get.assert_called_once_with("label", **{}) + assert result == {"results": [{"id": "label1", "name": "Test Label"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_labels(self, mock_get, confluence_cloud): + """Test get_content_labels method.""" + mock_get.return_value = {"results": [{"id": "label1", "name": "Test Label"}]} + result = confluence_cloud.get_content_labels("123") + mock_get.assert_called_once_with("content/123/label", **{}) + assert result == {"results": [{"id": "label1", "name": "Test Label"}]} + + @patch.object(ConfluenceCloud, "post") + def test_add_content_labels(self, mock_post, confluence_cloud): + """Test add_content_labels method.""" + label_data = {"name": "New Label"} + mock_post.return_value = {"id": "label2", "name": "New Label"} + result = confluence_cloud.add_content_labels("123", label_data) + mock_post.assert_called_once_with("content/123/label", data=label_data, **{}) + assert result == {"id": "label2", "name": "New Label"} + + @patch.object(ConfluenceCloud, "delete") + def test_remove_content_label(self, mock_delete, confluence_cloud): + """Test remove_content_label method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.remove_content_label("123", "label1") + mock_delete.assert_called_once_with("content/123/label/label1", **{}) + assert result == {"success": True} + + # Attachment Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_attachments(self, mock_get, confluence_cloud): + """Test get_attachments method.""" + mock_get.return_value = {"results": [{"id": "att1", "title": "Test Attachment"}]} + result = confluence_cloud.get_attachments("123") + mock_get.assert_called_once_with("content/123/child/attachment", **{}) + assert result == {"results": [{"id": "att1", "title": "Test Attachment"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_attachment(self, mock_get, confluence_cloud): + """Test get_attachment method.""" + mock_get.return_value = {"id": "att1", "title": "Test Attachment"} + result = confluence_cloud.get_attachment("att1") + mock_get.assert_called_once_with("content/att1", **{}) + assert result == {"id": "att1", "title": "Test Attachment"} + + @patch.object(ConfluenceCloud, "post") + def test_create_attachment(self, mock_post, confluence_cloud): + """Test create_attachment method.""" + attachment_data = {"title": "New Attachment"} + mock_post.return_value = {"id": "att2", "title": "New Attachment"} + result = confluence_cloud.create_attachment("123", attachment_data) + mock_post.assert_called_once_with("content/123/child/attachment", data=attachment_data, **{}) + assert result == {"id": "att2", "title": "New Attachment"} + + @patch.object(ConfluenceCloud, "put") + def test_update_attachment(self, mock_put, confluence_cloud): + """Test update_attachment method.""" + attachment_data = {"title": "Updated Attachment"} + mock_put.return_value = {"id": "att1", "title": "Updated Attachment"} + result = confluence_cloud.update_attachment("att1", attachment_data) + mock_put.assert_called_once_with("content/att1", data=attachment_data, **{}) + assert result == {"id": "att1", "title": "Updated Attachment"} + + @patch.object(ConfluenceCloud, "delete") + def test_delete_attachment(self, mock_delete, confluence_cloud): + """Test delete_attachment method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.delete_attachment("att1") + mock_delete.assert_called_once_with("content/att1", **{}) + assert result == {"success": True} + + # Comment Management Tests + @patch.object(ConfluenceCloud, "get") + def test_get_comments(self, mock_get, confluence_cloud): + """Test get_comments method.""" + mock_get.return_value = {"results": [{"id": "comment1", "text": "Test Comment"}]} + result = confluence_cloud.get_comments("123") + mock_get.assert_called_once_with("content/123/child/comment", **{}) + assert result == {"results": [{"id": "comment1", "text": "Test Comment"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_comment(self, mock_get, confluence_cloud): + """Test get_comment method.""" + mock_get.return_value = {"id": "comment1", "text": "Test Comment"} + result = confluence_cloud.get_comment("comment1") + mock_get.assert_called_once_with("content/comment1", **{}) + assert result == {"id": "comment1", "text": "Test Comment"} + + @patch.object(ConfluenceCloud, "post") + def test_create_comment(self, mock_post, confluence_cloud): + """Test create_comment method.""" + comment_data = {"text": "New Comment"} + mock_post.return_value = {"id": "comment2", "text": "New Comment"} + result = confluence_cloud.create_comment("123", comment_data) + mock_post.assert_called_once_with("content/123/child/comment", data=comment_data, **{}) + assert result == {"id": "comment2", "text": "New Comment"} + + @patch.object(ConfluenceCloud, "put") + def test_update_comment(self, mock_put, confluence_cloud): + """Test update_comment method.""" + comment_data = {"text": "Updated Comment"} + mock_put.return_value = {"id": "comment1", "text": "Updated Comment"} + result = confluence_cloud.update_comment("comment1", comment_data) + mock_put.assert_called_once_with("content/comment1", data=comment_data, **{}) + assert result == {"id": "comment1", "text": "Updated Comment"} + + @patch.object(ConfluenceCloud, "delete") + def test_delete_comment(self, mock_delete, confluence_cloud): + """Test delete_comment method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.delete_comment("comment1") + mock_delete.assert_called_once_with("content/comment1", **{}) + assert result == {"success": True} + + # Search Tests + @patch.object(ConfluenceCloud, "get") + def test_search_content(self, mock_get, confluence_cloud): + """Test search_content method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Search Result"}]} + result = confluence_cloud.search_content("type=page") + mock_get.assert_called_once_with("content/search", params={"cql": "type=page", **{}}) + assert result == {"results": [{"id": "123", "title": "Search Result"}]} + + @patch.object(ConfluenceCloud, "get") + def test_search_spaces(self, mock_get, confluence_cloud): + """Test search_spaces method.""" + mock_get.return_value = {"results": [{"id": "TEST", "name": "Test Space"}]} + result = confluence_cloud.search_spaces("test") + mock_get.assert_called_once_with("space/search", params={"query": "test", **{}}) + assert result == {"results": [{"id": "TEST", "name": "Test Space"}]} + + # Page Properties Tests + @patch.object(ConfluenceCloud, "get") + def test_get_content_properties(self, mock_get, confluence_cloud): + """Test get_content_properties method.""" + mock_get.return_value = {"results": [{"key": "prop1", "value": "value1"}]} + result = confluence_cloud.get_content_properties("123") + mock_get.assert_called_once_with("content/123/property", **{}) + assert result == {"results": [{"key": "prop1", "value": "value1"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_content_property(self, mock_get, confluence_cloud): + """Test get_content_property method.""" + mock_get.return_value = {"key": "prop1", "value": "value1"} + result = confluence_cloud.get_content_property("123", "prop1") + mock_get.assert_called_once_with("content/123/property/prop1", **{}) + assert result == {"key": "prop1", "value": "value1"} + + @patch.object(ConfluenceCloud, "post") + def test_create_content_property(self, mock_post, confluence_cloud): + """Test create_content_property method.""" + property_data = {"key": "prop2", "value": "value2"} + mock_post.return_value = {"key": "prop2", "value": "value2"} + result = confluence_cloud.create_content_property("123", property_data) + mock_post.assert_called_once_with("content/123/property", data=property_data, **{}) + assert result == {"key": "prop2", "value": "value2"} + + @patch.object(ConfluenceCloud, "put") + def test_update_content_property(self, mock_put, confluence_cloud): + """Test update_content_property method.""" + property_data = {"value": "updated_value"} + mock_put.return_value = {"key": "prop1", "value": "updated_value"} + result = confluence_cloud.update_content_property("123", "prop1", property_data) + mock_put.assert_called_once_with("content/123/property/prop1", data=property_data, **{}) + assert result == {"key": "prop1", "value": "updated_value"} + + @patch.object(ConfluenceCloud, "delete") + def test_delete_content_property(self, mock_delete, confluence_cloud): + """Test delete_content_property method.""" + mock_delete.return_value = {"success": True} + result = confluence_cloud.delete_content_property("123", "prop1") + mock_delete.assert_called_once_with("content/123/property/prop1", **{}) + assert result == {"success": True} + + # Template Tests + @patch.object(ConfluenceCloud, "get") + def test_get_templates(self, mock_get, confluence_cloud): + """Test get_templates method.""" + mock_get.return_value = {"results": [{"id": "template1", "name": "Test Template"}]} + result = confluence_cloud.get_templates() + mock_get.assert_called_once_with("template", **{}) + assert result == {"results": [{"id": "template1", "name": "Test Template"}]} + + @patch.object(ConfluenceCloud, "get") + def test_get_template(self, mock_get, confluence_cloud): + """Test get_template method.""" + mock_get.return_value = {"id": "template1", "name": "Test Template"} + result = confluence_cloud.get_template("template1") + mock_get.assert_called_once_with("template/template1", **{}) + assert result == {"id": "template1", "name": "Test Template"} + + # Analytics Tests + @patch.object(ConfluenceCloud, "get") + def test_get_content_analytics(self, mock_get, confluence_cloud): + """Test get_content_analytics method.""" + mock_get.return_value = {"views": 100, "likes": 10} + result = confluence_cloud.get_content_analytics("123") + mock_get.assert_called_once_with("content/123/analytics", **{}) + assert result == {"views": 100, "likes": 10} + + @patch.object(ConfluenceCloud, "get") + def test_get_space_analytics(self, mock_get, confluence_cloud): + """Test get_space_analytics method.""" + mock_get.return_value = {"totalPages": 50, "totalUsers": 25} + result = confluence_cloud.get_space_analytics("TEST") + mock_get.assert_called_once_with("space/TEST/analytics", **{}) + assert result == {"totalPages": 50, "totalUsers": 25} + + # Export Tests + @patch.object(ConfluenceCloud, "get") + def test_export_content(self, mock_get, confluence_cloud): + """Test export_content method.""" + mock_get.return_value = {"exportData": "base64_encoded_content"} + result = confluence_cloud.export_content("123") + mock_get.assert_called_once_with("content/123/export", **{}) + assert result == {"exportData": "base64_encoded_content"} + + @patch.object(ConfluenceCloud, "get") + def test_export_space(self, mock_get, confluence_cloud): + """Test export_space method.""" + mock_get.return_value = {"exportData": "base64_encoded_space"} + result = confluence_cloud.export_space("TEST") + mock_get.assert_called_once_with("space/TEST/export", **{}) + assert result == {"exportData": "base64_encoded_space"} + + # Utility Methods Tests + @patch.object(ConfluenceCloud, "get") + def test_get_metadata(self, mock_get, confluence_cloud): + """Test get_metadata method.""" + mock_get.return_value = {"version": "2.0", "buildNumber": "123"} + result = confluence_cloud.get_metadata() + mock_get.assert_called_once_with("metadata", **{}) + assert result == {"version": "2.0", "buildNumber": "123"} + + @patch.object(ConfluenceCloud, "get") + def test_get_health(self, mock_get, confluence_cloud): + """Test get_health method.""" + mock_get.return_value = {"status": "healthy"} + result = confluence_cloud.get_health() + mock_get.assert_called_once_with("health", **{}) + assert result == {"status": "healthy"} diff --git a/tests/confluence/test_confluence_server.py b/tests/confluence/test_confluence_server.py new file mode 100644 index 000000000..600e6e32b --- /dev/null +++ b/tests/confluence/test_confluence_server.py @@ -0,0 +1,682 @@ +# coding=utf-8 +""" +Test cases for Confluence Server API client. +""" + +import pytest +from unittest.mock import patch + +from atlassian.confluence import ConfluenceServer + + +@pytest.fixture +def confluence_server(): + """Fixture for ConfluenceServer client.""" + return ConfluenceServer(url="https://test.confluence.com", username="test", password="test", cloud=False) + + +class TestConfluenceServer: + """Test cases for ConfluenceServer client.""" + + def test_init_defaults(self): + """Test ConfluenceServer client initialization with default values.""" + confluence = ConfluenceServer(url="https://test.confluence.com", username="test", password="test") + assert confluence.api_version == "1.0" + assert confluence.api_root == "rest/api" + assert confluence.cloud is False + + def test_init_custom_values(self): + """Test ConfluenceServer client initialization with custom values.""" + confluence = ConfluenceServer( + url="https://test.confluence.com", + username="test", + password="test", + api_version="2.0", + api_root="custom/api/root", + ) + assert confluence.api_version == "2.0" + assert confluence.api_root == "custom/api/root" + + # Content Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_content(self, mock_get, confluence_server): + """Test get_content method.""" + mock_get.return_value = {"id": "123", "title": "Test Page", "type": "page"} + result = confluence_server.get_content("123") + mock_get.assert_called_once_with("content/123", **{}) + assert result == {"id": "123", "title": "Test Page", "type": "page"} + + @patch.object(ConfluenceServer, "get") + def test_get_content_by_type(self, mock_get, confluence_server): + """Test get_content_by_type method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_server.get_content_by_type("page") + mock_get.assert_called_once_with("content", params={"type": "page", **{}}) + assert result == {"results": [{"id": "123", "title": "Test Page"}]} + + @patch.object(ConfluenceServer, "post") + def test_create_content(self, mock_post, confluence_server): + """Test create_content method.""" + content_data = {"title": "New Page", "type": "page", "space": {"key": "TEST"}} + mock_post.return_value = {"id": "456", "title": "New Page", "type": "page"} + result = confluence_server.create_content(content_data) + mock_post.assert_called_once_with("content", data=content_data, **{}) + assert result == {"id": "456", "title": "New Page", "type": "page"} + + @patch.object(ConfluenceServer, "put") + def test_update_content(self, mock_put, confluence_server): + """Test update_content method.""" + content_data = {"title": "Updated Page"} + mock_put.return_value = {"id": "123", "title": "Updated Page"} + result = confluence_server.update_content("123", content_data) + mock_put.assert_called_once_with("content/123", data=content_data, **{}) + assert result == {"id": "123", "title": "Updated Page"} + + @patch.object(ConfluenceServer, "delete") + def test_delete_content(self, mock_delete, confluence_server): + """Test delete_content method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.delete_content("123") + mock_delete.assert_called_once_with("content/123", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceServer, "get") + def test_get_content_children(self, mock_get, confluence_server): + """Test get_content_children method.""" + mock_get.return_value = {"results": [{"id": "789", "title": "Child Page"}]} + result = confluence_server.get_content_children("123") + mock_get.assert_called_once_with("content/123/child", **{}) + assert result == {"results": [{"id": "789", "title": "Child Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_descendants(self, mock_get, confluence_server): + """Test get_content_descendants method.""" + mock_get.return_value = {"results": [{"id": "999", "title": "Descendant Page"}]} + result = confluence_server.get_content_descendants("123") + mock_get.assert_called_once_with("content/123/descendant", **{}) + assert result == {"results": [{"id": "999", "title": "Descendant Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_ancestors(self, mock_get, confluence_server): + """Test get_content_ancestors method.""" + mock_get.return_value = {"results": [{"id": "111", "title": "Ancestor Page"}]} + result = confluence_server.get_content_ancestors("123") + mock_get.assert_called_once_with("content/123/ancestor", **{}) + assert result == {"results": [{"id": "111", "title": "Ancestor Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_by_title(self, mock_get, confluence_server): + """Test get_content_by_title method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_server.get_content_by_title("TEST", "Test Page") + mock_get.assert_called_once_with("content", params={"spaceKey": "TEST", "title": "Test Page", **{}}) + assert result == {"results": [{"id": "123", "title": "Test Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_by_id(self, mock_get, confluence_server): + """Test get_content_by_id method.""" + mock_get.return_value = {"id": "123", "title": "Test Page"} + result = confluence_server.get_content_by_id("123") + mock_get.assert_called_once_with("content/123", **{}) + assert result == {"id": "123", "title": "Test Page"} + + @patch.object(ConfluenceServer, "get") + def test_get_all_pages_from_space(self, mock_get, confluence_server): + """Test get_all_pages_from_space method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Page in Space"}]} + result = confluence_server.get_all_pages_from_space("TEST") + mock_get.assert_called_once_with("content", params={"spaceKey": "TEST", "type": "page", **{}}) + assert result == {"results": [{"id": "123", "title": "Page in Space"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_all_blog_posts_from_space(self, mock_get, confluence_server): + """Test get_all_blog_posts_from_space method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Blog Post in Space"}]} + result = confluence_server.get_all_blog_posts_from_space("TEST") + mock_get.assert_called_once_with("content", params={"spaceKey": "TEST", "type": "blogpost", **{}}) + assert result == {"results": [{"id": "456", "title": "Blog Post in Space"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_page_by_title(self, mock_get, confluence_server): + """Test get_page_by_title method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_server.get_page_by_title("TEST", "Test Page") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "title": "Test Page", "type": "page", **{}} + ) + assert result == {"results": [{"id": "123", "title": "Test Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_blog_post_by_title(self, mock_get, confluence_server): + """Test get_blog_post_by_title method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Test Blog Post"}]} + result = confluence_server.get_blog_post_by_title("TEST", "Test Blog Post") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "title": "Test Blog Post", "type": "blogpost", **{}} + ) + assert result == {"results": [{"id": "456", "title": "Test Blog Post"}]} + + @patch.object(ConfluenceServer, "get") + def test_page_exists(self, mock_get, confluence_server): + """Test page_exists method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_server.page_exists("TEST", "Test Page") + assert result is True + + @patch.object(ConfluenceServer, "get") + def test_page_exists_false(self, mock_get, confluence_server): + """Test page_exists method when page doesn't exist.""" + mock_get.return_value = {"results": []} + result = confluence_server.page_exists("TEST", "Non-existent Page") + assert result is False + + @patch.object(ConfluenceServer, "get") + def test_blog_post_exists(self, mock_get, confluence_server): + """Test blog_post_exists method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Test Blog Post"}]} + result = confluence_server.blog_post_exists("TEST", "Test Blog Post") + assert result is True + + @patch.object(ConfluenceServer, "get") + def test_blog_post_exists_false(self, mock_get, confluence_server): + """Test blog_post_exists method when blog post doesn't exist.""" + mock_get.return_value = {"results": []} + result = confluence_server.blog_post_exists("TEST", "Non-existent Blog Post") + assert result is False + + @patch.object(ConfluenceServer, "get") + def test_get_content_id_page(self, mock_get, confluence_server): + """Test get_content_id method for page.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Test Page"}]} + result = confluence_server.get_content_id("TEST", "Test Page", "page") + assert result == "123" + + @patch.object(ConfluenceServer, "get") + def test_get_content_id_blogpost(self, mock_get, confluence_server): + """Test get_content_id method for blog post.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Test Blog Post"}]} + result = confluence_server.get_content_id("TEST", "Test Blog Post", "blogpost") + assert result == "456" + + def test_get_content_id_invalid_type(self, confluence_server): + """Test get_content_id method with invalid content type.""" + with pytest.raises(ValueError, match="content_type must be 'page' or 'blogpost'"): + confluence_server.get_content_id("TEST", "Test", "invalid") + + @patch.object(ConfluenceServer, "get") + def test_get_page_space(self, mock_get, confluence_server): + """Test get_page_space method.""" + mock_get.return_value = {"space": {"key": "TEST"}} + result = confluence_server.get_page_space("123") + mock_get.assert_called_once_with("content/123", expand="space") + assert result == "TEST" + + # Space Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_spaces(self, mock_get, confluence_server): + """Test get_spaces method.""" + mock_get.return_value = {"results": [{"key": "TEST", "name": "Test Space"}]} + result = confluence_server.get_spaces() + mock_get.assert_called_once_with("space", **{}) + assert result == {"results": [{"key": "TEST", "name": "Test Space"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_space(self, mock_get, confluence_server): + """Test get_space method.""" + mock_get.return_value = {"key": "TEST", "name": "Test Space"} + result = confluence_server.get_space("TEST") + mock_get.assert_called_once_with("space/TEST", **{}) + assert result == {"key": "TEST", "name": "Test Space"} + + @patch.object(ConfluenceServer, "post") + def test_create_space(self, mock_post, confluence_server): + """Test create_space method.""" + space_data = {"name": "New Space", "key": "NEW"} + mock_post.return_value = {"key": "NEW", "name": "New Space"} + result = confluence_server.create_space(space_data) + mock_post.assert_called_once_with("space", data=space_data, **{}) + assert result == {"key": "NEW", "name": "New Space"} + + @patch.object(ConfluenceServer, "put") + def test_update_space(self, mock_put, confluence_server): + """Test update_space method.""" + space_data = {"name": "Updated Space"} + mock_put.return_value = {"key": "TEST", "name": "Updated Space"} + result = confluence_server.update_space("TEST", space_data) + mock_put.assert_called_once_with("space/TEST", data=space_data, **{}) + assert result == {"key": "TEST", "name": "Updated Space"} + + @patch.object(ConfluenceServer, "delete") + def test_delete_space(self, mock_delete, confluence_server): + """Test delete_space method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.delete_space("TEST") + mock_delete.assert_called_once_with("space/TEST", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceServer, "get") + def test_get_space_content(self, mock_get, confluence_server): + """Test get_space_content method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Page in Space"}]} + result = confluence_server.get_space_content("TEST") + mock_get.assert_called_once_with("content", params={"spaceKey": "TEST", **{}}) + assert result == {"results": [{"id": "123", "title": "Page in Space"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_space_permissions(self, mock_get, confluence_server): + """Test get_space_permissions method.""" + mock_get.return_value = {"results": [{"userName": "test", "permission": "ADMIN"}]} + result = confluence_server.get_space_permissions("TEST") + mock_get.assert_called_once_with("space/TEST/permission", **{}) + assert result == {"results": [{"userName": "test", "permission": "ADMIN"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_space_settings(self, mock_get, confluence_server): + """Test get_space_settings method.""" + mock_get.return_value = {"settings": {"key": "value"}} + result = confluence_server.get_space_settings("TEST") + mock_get.assert_called_once_with("space/TEST/settings", **{}) + assert result == {"settings": {"key": "value"}} + + # User Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_users(self, mock_get, confluence_server): + """Test get_users method.""" + mock_get.return_value = {"results": [{"username": "user1", "displayName": "Test User"}]} + result = confluence_server.get_users() + mock_get.assert_called_once_with("user", **{}) + assert result == {"results": [{"username": "user1", "displayName": "Test User"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_user(self, mock_get, confluence_server): + """Test get_user method.""" + mock_get.return_value = {"username": "user1", "displayName": "Test User"} + result = confluence_server.get_user("user1") + mock_get.assert_called_once_with("user", params={"username": "user1", **{}}) + assert result == {"username": "user1", "displayName": "Test User"} + + @patch.object(ConfluenceServer, "get") + def test_get_current_user(self, mock_get, confluence_server): + """Test get_current_user method.""" + mock_get.return_value = {"username": "current", "displayName": "Current User"} + result = confluence_server.get_current_user() + mock_get.assert_called_once_with("user/current", **{}) + assert result == {"username": "current", "displayName": "Current User"} + + @patch.object(ConfluenceServer, "get") + def test_get_user_by_key(self, mock_get, confluence_server): + """Test get_user_by_key method.""" + mock_get.return_value = {"username": "user1", "displayName": "Test User"} + result = confluence_server.get_user_by_key("user1") + mock_get.assert_called_once_with("user", params={"key": "user1", **{}}) + assert result == {"username": "user1", "displayName": "Test User"} + + # Group Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_groups(self, mock_get, confluence_server): + """Test get_groups method.""" + mock_get.return_value = {"results": [{"name": "group1", "type": "group"}]} + result = confluence_server.get_groups() + mock_get.assert_called_once_with("group", **{}) + assert result == {"results": [{"name": "group1", "type": "group"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_group(self, mock_get, confluence_server): + """Test get_group method.""" + mock_get.return_value = {"name": "group1", "type": "group"} + result = confluence_server.get_group("group1") + mock_get.assert_called_once_with("group", params={"groupname": "group1", **{}}) + assert result == {"name": "group1", "type": "group"} + + @patch.object(ConfluenceServer, "get") + def test_get_group_members(self, mock_get, confluence_server): + """Test get_group_members method.""" + mock_get.return_value = {"results": [{"username": "user1", "displayName": "Test User"}]} + result = confluence_server.get_group_members("group1") + mock_get.assert_called_once_with("group/group1/member", **{}) + assert result == {"results": [{"username": "user1", "displayName": "Test User"}]} + + @patch.object(ConfluenceServer, "post") + def test_add_user_to_group(self, mock_post, confluence_server): + """Test add_user_to_group method.""" + mock_post.return_value = {"success": True} + result = confluence_server.add_user_to_group("group1", "user1") + mock_post.assert_called_once_with("group/group1/member", data={"name": "user1"}, **{}) + assert result == {"success": True} + + @patch.object(ConfluenceServer, "delete") + def test_remove_user_from_group(self, mock_delete, confluence_server): + """Test remove_user_from_group method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.remove_user_from_group("group1", "user1") + mock_delete.assert_called_once_with("group/group1/member/user1", **{}) + assert result == {"success": True} + + # Label Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_labels(self, mock_get, confluence_server): + """Test get_labels method.""" + mock_get.return_value = {"results": [{"name": "label1", "id": "1"}]} + result = confluence_server.get_labels() + mock_get.assert_called_once_with("label", **{}) + assert result == {"results": [{"name": "label1", "id": "1"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_labels(self, mock_get, confluence_server): + """Test get_content_labels method.""" + mock_get.return_value = {"results": [{"name": "label1", "id": "1"}]} + result = confluence_server.get_content_labels("123") + mock_get.assert_called_once_with("content/123/label", **{}) + assert result == {"results": [{"name": "label1", "id": "1"}]} + + @patch.object(ConfluenceServer, "post") + def test_add_content_labels(self, mock_post, confluence_server): + """Test add_content_labels method.""" + label_data = {"name": "New Label"} + mock_post.return_value = {"name": "New Label", "id": "2"} + result = confluence_server.add_content_labels("123", label_data) + mock_post.assert_called_once_with("content/123/label", data=label_data, **{}) + assert result == {"name": "New Label", "id": "2"} + + @patch.object(ConfluenceServer, "delete") + def test_remove_content_label(self, mock_delete, confluence_server): + """Test remove_content_label method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.remove_content_label("123", "label1") + mock_delete.assert_called_once_with("content/123/label/label1", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceServer, "get") + def test_get_all_pages_by_label(self, mock_get, confluence_server): + """Test get_all_pages_by_label method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Page with Label"}]} + result = confluence_server.get_all_pages_by_label("label1") + mock_get.assert_called_once_with("content", params={"label": "label1", "type": "page", **{}}) + assert result == {"results": [{"id": "123", "title": "Page with Label"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_all_blog_posts_by_label(self, mock_get, confluence_server): + """Test get_all_blog_posts_by_label method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Blog Post with Label"}]} + result = confluence_server.get_all_blog_posts_by_label("label1") + mock_get.assert_called_once_with("content", params={"label": "label1", "type": "blogpost", **{}}) + assert result == {"results": [{"id": "456", "title": "Blog Post with Label"}]} + + # Attachment Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_attachments(self, mock_get, confluence_server): + """Test get_attachments method.""" + mock_get.return_value = {"results": [{"id": "att1", "title": "Test Attachment"}]} + result = confluence_server.get_attachments("123") + mock_get.assert_called_once_with("content/123/child/attachment", **{}) + assert result == {"results": [{"id": "att1", "title": "Test Attachment"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_attachment(self, mock_get, confluence_server): + """Test get_attachment method.""" + mock_get.return_value = {"id": "att1", "title": "Test Attachment"} + result = confluence_server.get_attachment("att1") + mock_get.assert_called_once_with("content/att1", **{}) + assert result == {"id": "att1", "title": "Test Attachment"} + + @patch.object(ConfluenceServer, "post") + def test_create_attachment(self, mock_post, confluence_server): + """Test create_attachment method.""" + attachment_data = {"title": "New Attachment"} + mock_post.return_value = {"id": "att2", "title": "New Attachment"} + result = confluence_server.create_attachment("123", attachment_data) + mock_post.assert_called_once_with("content/123/child/attachment", data=attachment_data, **{}) + assert result == {"id": "att2", "title": "New Attachment"} + + @patch.object(ConfluenceServer, "put") + def test_update_attachment(self, mock_put, confluence_server): + """Test update_attachment method.""" + attachment_data = {"title": "Updated Attachment"} + mock_put.return_value = {"id": "att1", "title": "Updated Attachment"} + result = confluence_server.update_attachment("att1", attachment_data) + mock_put.assert_called_once_with("content/att1", data=attachment_data, **{}) + assert result == {"id": "att1", "title": "Updated Attachment"} + + @patch.object(ConfluenceServer, "delete") + def test_delete_attachment(self, mock_delete, confluence_server): + """Test delete_attachment method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.delete_attachment("att1") + mock_delete.assert_called_once_with("content/att1", **{}) + assert result == {"success": True} + + @patch.object(ConfluenceServer, "get") + def test_download_attachment(self, mock_get, confluence_server): + """Test download_attachment method.""" + mock_get.return_value = b"attachment_content" + result = confluence_server.download_attachment("att1") + mock_get.assert_called_once_with("content/att1/download", **{}) + assert result == b"attachment_content" + + # Comment Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_comments(self, mock_get, confluence_server): + """Test get_comments method.""" + mock_get.return_value = {"results": [{"id": "comment1", "text": "Test Comment"}]} + result = confluence_server.get_comments("123") + mock_get.assert_called_once_with("content/123/child/comment", **{}) + assert result == {"results": [{"id": "comment1", "text": "Test Comment"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_comment(self, mock_get, confluence_server): + """Test get_comment method.""" + mock_get.return_value = {"id": "comment1", "text": "Test Comment"} + result = confluence_server.get_comment("comment1") + mock_get.assert_called_once_with("content/comment1", **{}) + assert result == {"id": "comment1", "text": "Test Comment"} + + @patch.object(ConfluenceServer, "post") + def test_create_comment(self, mock_post, confluence_server): + """Test create_comment method.""" + comment_data = {"text": "New Comment"} + mock_post.return_value = {"id": "comment2", "text": "New Comment"} + result = confluence_server.create_comment("123", comment_data) + mock_post.assert_called_once_with("content/123/child/comment", data=comment_data, **{}) + assert result == {"id": "comment2", "text": "New Comment"} + + @patch.object(ConfluenceServer, "put") + def test_update_comment(self, mock_put, confluence_server): + """Test update_comment method.""" + comment_data = {"text": "Updated Comment"} + mock_put.return_value = {"id": "comment1", "text": "Updated Comment"} + result = confluence_server.update_comment("comment1", comment_data) + mock_put.assert_called_once_with("content/comment1", data=comment_data, **{}) + assert result == {"id": "comment1", "text": "Updated Comment"} + + @patch.object(ConfluenceServer, "delete") + def test_delete_comment(self, mock_delete, confluence_server): + """Test delete_comment method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.delete_comment("comment1") + mock_delete.assert_called_once_with("content/comment1", **{}) + assert result == {"success": True} + + # Search Tests + @patch.object(ConfluenceServer, "get") + def test_search_content(self, mock_get, confluence_server): + """Test search_content method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Search Result"}]} + result = confluence_server.search_content("type=page") + mock_get.assert_called_once_with("content/search", params={"cql": "type=page", **{}}) + assert result == {"results": [{"id": "123", "title": "Search Result"}]} + + @patch.object(ConfluenceServer, "get") + def test_search_spaces(self, mock_get, confluence_server): + """Test search_spaces method.""" + mock_get.return_value = {"results": [{"key": "TEST", "name": "Test Space"}]} + result = confluence_server.search_spaces("test") + mock_get.assert_called_once_with("space/search", params={"query": "test", **{}}) + assert result == {"results": [{"key": "TEST", "name": "Test Space"}]} + + # Page Properties Tests + @patch.object(ConfluenceServer, "get") + def test_get_content_properties(self, mock_get, confluence_server): + """Test get_content_properties method.""" + mock_get.return_value = {"results": [{"key": "prop1", "value": "value1"}]} + result = confluence_server.get_content_properties("123") + mock_get.assert_called_once_with("content/123/property", **{}) + assert result == {"results": [{"key": "prop1", "value": "value1"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_content_property(self, mock_get, confluence_server): + """Test get_content_property method.""" + mock_get.return_value = {"key": "prop1", "value": "value1"} + result = confluence_server.get_content_property("123", "prop1") + mock_get.assert_called_once_with("content/123/property/prop1", **{}) + assert result == {"key": "prop1", "value": "value1"} + + @patch.object(ConfluenceServer, "post") + def test_create_content_property(self, mock_post, confluence_server): + """Test create_content_property method.""" + property_data = {"key": "prop2", "value": "value2"} + mock_post.return_value = {"key": "prop2", "value": "value2"} + result = confluence_server.create_content_property("123", property_data) + mock_post.assert_called_once_with("content/123/property", data=property_data, **{}) + assert result == {"key": "prop2", "value": "value2"} + + @patch.object(ConfluenceServer, "put") + def test_update_content_property(self, mock_put, confluence_server): + """Test update_content_property method.""" + property_data = {"value": "updated_value"} + mock_put.return_value = {"key": "prop1", "value": "updated_value"} + result = confluence_server.update_content_property("123", "prop1", property_data) + mock_put.assert_called_once_with("content/123/property/prop1", data=property_data, **{}) + assert result == {"key": "prop1", "value": "updated_value"} + + @patch.object(ConfluenceServer, "delete") + def test_delete_content_property(self, mock_delete, confluence_server): + """Test delete_content_property method.""" + mock_delete.return_value = {"success": True} + result = confluence_server.delete_content_property("123", "prop1") + mock_delete.assert_called_once_with("content/123/property/prop1", **{}) + assert result == {"success": True} + + # Template Tests + @patch.object(ConfluenceServer, "get") + def test_get_templates(self, mock_get, confluence_server): + """Test get_templates method.""" + mock_get.return_value = {"results": [{"id": "template1", "name": "Test Template"}]} + result = confluence_server.get_templates() + mock_get.assert_called_once_with("template", **{}) + assert result == {"results": [{"id": "template1", "name": "Test Template"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_template(self, mock_get, confluence_server): + """Test get_template method.""" + mock_get.return_value = {"id": "template1", "name": "Test Template"} + result = confluence_server.get_template("template1") + mock_get.assert_called_once_with("template/template1", **{}) + assert result == {"id": "template1", "name": "Test Template"} + + # Draft Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_draft_content(self, mock_get, confluence_server): + """Test get_draft_content method.""" + mock_get.return_value = {"id": "123", "title": "Draft Page", "status": "draft"} + result = confluence_server.get_draft_content("123") + mock_get.assert_called_once_with("content/123", params={"status": "draft", **{}}) + assert result == {"id": "123", "title": "Draft Page", "status": "draft"} + + @patch.object(ConfluenceServer, "get") + def test_get_all_draft_pages_from_space(self, mock_get, confluence_server): + """Test get_all_draft_pages_from_space method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Draft Page"}]} + result = confluence_server.get_all_draft_pages_from_space("TEST") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "type": "page", "status": "draft", **{}} + ) + assert result == {"results": [{"id": "123", "title": "Draft Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_all_draft_blog_posts_from_space(self, mock_get, confluence_server): + """Test get_all_draft_blog_posts_from_space method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Draft Blog Post"}]} + result = confluence_server.get_all_draft_blog_posts_from_space("TEST") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "type": "blogpost", "status": "draft", **{}} + ) + assert result == {"results": [{"id": "456", "title": "Draft Blog Post"}]} + + # Trash Management Tests + @patch.object(ConfluenceServer, "get") + def test_get_trash_content(self, mock_get, confluence_server): + """Test get_trash_content method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Trashed Page"}]} + result = confluence_server.get_trash_content("TEST") + mock_get.assert_called_once_with("content", params={"spaceKey": "TEST", "status": "trashed", **{}}) + assert result == {"results": [{"id": "123", "title": "Trashed Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_all_pages_from_space_trash(self, mock_get, confluence_server): + """Test get_all_pages_from_space_trash method.""" + mock_get.return_value = {"results": [{"id": "123", "title": "Trashed Page"}]} + result = confluence_server.get_all_pages_from_space_trash("TEST") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "type": "page", "status": "trashed", **{}} + ) + assert result == {"results": [{"id": "123", "title": "Trashed Page"}]} + + @patch.object(ConfluenceServer, "get") + def test_get_all_blog_posts_from_space_trash(self, mock_get, confluence_server): + """Test get_all_blog_posts_from_space_trash method.""" + mock_get.return_value = {"results": [{"id": "456", "title": "Trashed Blog Post"}]} + result = confluence_server.get_all_blog_posts_from_space_trash("TEST") + mock_get.assert_called_once_with( + "content", params={"spaceKey": "TEST", "type": "blogpost", "status": "trashed", **{}} + ) + assert result == {"results": [{"id": "456", "title": "Trashed Blog Post"}]} + + # Export Tests + @patch.object(ConfluenceServer, "get") + def test_export_content(self, mock_get, confluence_server): + """Test export_content method.""" + mock_get.return_value = {"exportData": "base64_encoded_content"} + result = confluence_server.export_content("123") + mock_get.assert_called_once_with("content/123/export", **{}) + assert result == {"exportData": "base64_encoded_content"} + + @patch.object(ConfluenceServer, "get") + def test_export_space(self, mock_get, confluence_server): + """Test export_space method.""" + mock_get.return_value = {"exportData": "base64_encoded_space"} + result = confluence_server.export_space("TEST") + mock_get.assert_called_once_with("space/TEST/export", **{}) + assert result == {"exportData": "base64_encoded_space"} + + # Utility Methods Tests + @patch.object(ConfluenceServer, "get") + def test_get_metadata(self, mock_get, confluence_server): + """Test get_metadata method.""" + mock_get.return_value = {"version": "1.0", "buildNumber": "123"} + result = confluence_server.get_metadata() + mock_get.assert_called_once_with("metadata", **{}) + assert result == {"version": "1.0", "buildNumber": "123"} + + @patch.object(ConfluenceServer, "get") + def test_get_health(self, mock_get, confluence_server): + """Test get_health method.""" + mock_get.return_value = {"status": "healthy"} + result = confluence_server.get_health() + mock_get.assert_called_once_with("health", **{}) + assert result == {"status": "healthy"} + + @patch.object(ConfluenceServer, "post") + def test_reindex(self, mock_post, confluence_server): + """Test reindex method.""" + mock_post.return_value = {"taskId": "task123"} + result = confluence_server.reindex() + mock_post.assert_called_once_with("reindex", **{}) + assert result == {"taskId": "task123"} + + @patch.object(ConfluenceServer, "get") + def test_get_reindex_progress(self, mock_get, confluence_server): + """Test get_reindex_progress method.""" + mock_get.return_value = {"progress": 50, "status": "running"} + result = confluence_server.get_reindex_progress() + mock_get.assert_called_once_with("reindex", **{}) + assert result == {"progress": 50, "status": "running"} From 7e99ea58518ebaae082560fa298250743ad1077c Mon Sep 17 00:00:00 2001 From: Gonchik Tsymzhitov Date: Sun, 31 Aug 2025 23:33:48 +0300 Subject: [PATCH 2/3] Jira: set backward for <3.9 python releases #1578 --- atlassian/jira.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 6bff24ad8..a6abd2e98 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -3638,7 +3638,7 @@ def approximate_issue_count( url = self.resource_url("search/approximate-count") return self.post(url, data) - def match_jql(self, issue_ids: List[int], jqls: List[str]) -> Optional[dict[Any, Any]]: + def match_jql(self, issue_ids: List[int], jqls: List[str]) -> Optional[Dict[Any, Any]]: """ Checks which issues match a list of JQL queries. From 4502975978eab7e3ec9b0503aaf6ef19d3c7a05f Mon Sep 17 00:00:00 2001 From: Gonchik Tsymzhitov Date: Sun, 16 Aug 2026 10:25:06 +0300 Subject: [PATCH 3/3] Jira: Cloud method and compatibility with legacy --- atlassian/__init__.py | 7 +- atlassian/jira/__init__.py | 21 + atlassian/jira/core_methods.py | 5563 ++++++++++++++++++ atlassian/jira/jira_cloud.py | 104 + atlassian/{jira.py => jira/jira_server.py} | 7 +- atlassian/jira/service_management_methods.py | 610 ++ atlassian/jira/software_methods.py | 1027 ++++ atlassian/rest_client.py | 6 +- docs/index.rst | 2 + docs/jira_cloud.rst | 89 + docs/jira_cloud_api.rst | 822 +++ tests/test_jira_cloud_clients.py | 77 + tests/test_jira_cloud_method_coverage.py | 62 + tests/test_rest_client.py | 14 + 14 files changed, 8406 insertions(+), 5 deletions(-) create mode 100644 atlassian/jira/__init__.py create mode 100644 atlassian/jira/core_methods.py create mode 100644 atlassian/jira/jira_cloud.py rename atlassian/{jira.py => jira/jira_server.py} (99%) create mode 100644 atlassian/jira/service_management_methods.py create mode 100644 atlassian/jira/software_methods.py create mode 100644 docs/jira_cloud.rst create mode 100644 docs/jira_cloud_api.rst create mode 100644 tests/test_jira_cloud_clients.py create mode 100644 tests/test_jira_cloud_method_coverage.py diff --git a/atlassian/__init__.py b/atlassian/__init__.py index b7b8f7ca5..82508cf97 100644 --- a/atlassian/__init__.py +++ b/atlassian/__init__.py @@ -12,7 +12,7 @@ from .insight import Insight from .insight import Insight as Assets # used for Insight on-premise from .assets import AssetsCloud # used for Insight Cloud -from .jira import Jira +from .jira import Jira, JiraCloud, JiraServer, JiraServiceManagement, JiraSoftware, create_jira_cloud from .marketplace import MarketPlace from .portfolio import Portfolio from .service_desk import ServiceDesk @@ -36,6 +36,11 @@ def create_confluence(url, *args, api_version=1, **kwargs): "ConfluenceV2", "create_confluence", "Jira", + "JiraServer", + "JiraCloud", + "JiraSoftware", + "JiraServiceManagement", + "create_jira_cloud", "Bitbucket", "CloudAdminOrgs", "CloudAdminUsers", diff --git a/atlassian/jira/__init__.py b/atlassian/jira/__init__.py new file mode 100644 index 000000000..231d7a68e --- /dev/null +++ b/atlassian/jira/__init__.py @@ -0,0 +1,21 @@ +# coding=utf-8 +"""Jira API clients split by deployment target. + +``Jira`` remains the legacy-compatible Server/Data Center client. Use +``JiraCloud`` for Jira Cloud Core REST v2/v3. +""" + +from .jira_cloud import JiraCloud, JiraServiceManagement, JiraSoftware, create_jira_cloud +from .jira_server import Jira as JiraServer + +# Preserve ``from atlassian.jira import Jira`` and ``from atlassian import Jira``. +Jira = JiraServer + +__all__ = [ + "Jira", + "JiraServer", + "JiraCloud", + "JiraSoftware", + "JiraServiceManagement", + "create_jira_cloud", +] diff --git a/atlassian/jira/core_methods.py b/atlassian/jira/core_methods.py new file mode 100644 index 000000000..34cd0ade3 --- /dev/null +++ b/atlassian/jira/core_methods.py @@ -0,0 +1,5563 @@ +# coding=utf-8 +# Generated from the supplied Jira Cloud API descriptions; do not edit manually. + + +class JiraCloudCoreMethods: + """Concrete methods for every supplied core API operation.""" + + def get_banner(self, data=None, **request_kwargs): + """Get announcement banner configuration.""" + url = self.resource_url("announcementBanner", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_banner(self, data=None, **request_kwargs): + """Update announcement banner configuration.""" + url = self.resource_url("announcementBanner", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_custom_fields_configurations( + self, + id=None, + field_context_id=None, + issue_id=None, + project_key_or_id=None, + issue_type_id=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Bulk get custom field configurations.""" + url = self.resource_url( + "app/field/context/configuration/list", api_root="rest/api", api_version=self.api_version + ) + params = { + "id": id, + "fieldContextId": field_context_id, + "issueId": issue_id, + "projectKeyOrId": project_key_or_id, + "issueTypeId": issue_type_id, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_multiple_custom_field_values( + self, generate_changelog=None, generate_app_events=None, data=None, **request_kwargs + ): + """Update custom fields.""" + url = self.resource_url("app/field/value", api_root="rest/api", api_version=self.api_version) + params = {"generateChangelog": generate_changelog, "generateAppEvents": generate_app_events} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_custom_field_configuration( + self, + field_id_or_key, + id=None, + field_context_id=None, + issue_id=None, + project_key_or_id=None, + issue_type_id=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Get custom field configurations.""" + url = self.resource_url( + f"app/field/{field_id_or_key}/context/configuration", api_root="rest/api", api_version=self.api_version + ) + params = { + "id": id, + "fieldContextId": field_context_id, + "issueId": issue_id, + "projectKeyOrId": project_key_or_id, + "issueTypeId": issue_type_id, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_custom_field_configuration(self, field_id_or_key, data=None, **request_kwargs): + """Update custom field configurations.""" + url = self.resource_url( + f"app/field/{field_id_or_key}/context/configuration", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def update_custom_field_value( + self, field_id_or_key, generate_changelog=None, generate_app_events=None, data=None, **request_kwargs + ): + """Update custom field value.""" + url = self.resource_url(f"app/field/{field_id_or_key}/value", api_root="rest/api", api_version=self.api_version) + params = {"generateChangelog": generate_changelog, "generateAppEvents": generate_app_events} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_application_property(self, key=None, permission_level=None, key_filter=None, data=None, **request_kwargs): + """Get application property.""" + url = self.resource_url("application-properties", api_root="rest/api", api_version=self.api_version) + params = {"key": key, "permissionLevel": permission_level, "keyFilter": key_filter} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_advanced_settings(self, data=None, **request_kwargs): + """Get advanced settings.""" + url = self.resource_url( + "application-properties/advanced-settings", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_application_property(self, id, data=None, **request_kwargs): + """Set application property.""" + url = self.resource_url(f"application-properties/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_application_roles(self, data=None, **request_kwargs): + """Get all application roles.""" + url = self.resource_url("applicationrole", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_application_role(self, key, data=None, **request_kwargs): + """Get application role.""" + url = self.resource_url(f"applicationrole/{key}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_attachment_content(self, id, redirect=None, data=None, **request_kwargs): + """Get attachment content.""" + url = self.resource_url(f"attachment/content/{id}", api_root="rest/api", api_version=self.api_version) + params = {"redirect": redirect} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_attachment_meta(self, data=None, **request_kwargs): + """Get Jira attachment settings.""" + url = self.resource_url("attachment/meta", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_attachment_thumbnail( + self, id, redirect=None, fallback_to_default=None, width=None, height=None, data=None, **request_kwargs + ): + """Get attachment thumbnail.""" + url = self.resource_url(f"attachment/thumbnail/{id}", api_root="rest/api", api_version=self.api_version) + params = {"redirect": redirect, "fallbackToDefault": fallback_to_default, "width": width, "height": height} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_attachment(self, id, data=None, **request_kwargs): + """Delete attachment.""" + url = self.resource_url(f"attachment/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_attachment(self, id, data=None, **request_kwargs): + """Get attachment metadata.""" + url = self.resource_url(f"attachment/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def expand_attachment_for_humans(self, id, data=None, **request_kwargs): + """Get all metadata for an expanded attachment.""" + url = self.resource_url(f"attachment/{id}/expand/human", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def expand_attachment_for_machines(self, id, data=None, **request_kwargs): + """Get contents metadata for an expanded attachment.""" + url = self.resource_url(f"attachment/{id}/expand/raw", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_audit_records(self, offset=None, limit=None, filter=None, from_=None, to=None, data=None, **request_kwargs): + """Get audit records.""" + url = self.resource_url("auditing/record", api_root="rest/api", api_version=self.api_version) + params = {"offset": offset, "limit": limit, "filter": filter, "from": from_, "to": to} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_system_avatars(self, type, data=None, **request_kwargs): + """Get system avatars by type.""" + url = self.resource_url(f"avatar/{type}/system", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_bulk_delete(self, data=None, **request_kwargs): + """Bulk delete issues.""" + url = self.resource_url("bulk/issues/delete", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_bulk_editable_fields( + self, + issue_ids_or_keys=None, + search_text=None, + ending_before=None, + starting_after=None, + data=None, + **request_kwargs, + ): + """Get bulk editable fields.""" + url = self.resource_url("bulk/issues/fields", api_root="rest/api", api_version=self.api_version) + params = { + "issueIdsOrKeys": issue_ids_or_keys, + "searchText": search_text, + "endingBefore": ending_before, + "startingAfter": starting_after, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_bulk_edit(self, data=None, **request_kwargs): + """Bulk edit issues.""" + url = self.resource_url("bulk/issues/fields", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def submit_bulk_move(self, data=None, **request_kwargs): + """Bulk move issues.""" + url = self.resource_url("bulk/issues/move", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_available_transitions( + self, issue_ids_or_keys=None, ending_before=None, starting_after=None, data=None, **request_kwargs + ): + """Get available transitions.""" + url = self.resource_url("bulk/issues/transition", api_root="rest/api", api_version=self.api_version) + params = {"issueIdsOrKeys": issue_ids_or_keys, "endingBefore": ending_before, "startingAfter": starting_after} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_bulk_transition(self, data=None, **request_kwargs): + """Bulk transition issue statuses.""" + url = self.resource_url("bulk/issues/transition", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def submit_bulk_unwatch(self, data=None, **request_kwargs): + """Bulk unwatch issues.""" + url = self.resource_url("bulk/issues/unwatch", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def submit_bulk_watch(self, data=None, **request_kwargs): + """Bulk watch issues.""" + url = self.resource_url("bulk/issues/watch", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_bulk_operation_progress(self, task_id, data=None, **request_kwargs): + """Get bulk issue operation progress.""" + url = self.resource_url(f"bulk/queue/{task_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_bulk_changelogs(self, data=None, **request_kwargs): + """Bulk fetch changelogs.""" + url = self.resource_url("changelog/bulkfetch", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_user_data_classification_levels(self, status=None, order_by=None, data=None, **request_kwargs): + """Get all classification levels.""" + url = self.resource_url("classification-levels", api_root="rest/api", api_version=self.api_version) + params = {"status": status, "orderBy": order_by} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_comments_by_ids(self, expand=None, data=None, **request_kwargs): + """Get comments by IDs.""" + url = self.resource_url("comment/list", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_comment_property_keys(self, comment_id, data=None, **request_kwargs): + """Get comment property keys.""" + url = self.resource_url(f"comment/{comment_id}/properties", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_comment_property(self, comment_id, property_key, data=None, **request_kwargs): + """Delete comment property.""" + url = self.resource_url( + f"comment/{comment_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_comment_property(self, comment_id, property_key, data=None, **request_kwargs): + """Get comment property.""" + url = self.resource_url( + f"comment/{comment_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_comment_property(self, comment_id, property_key, data=None, **request_kwargs): + """Set comment property.""" + url = self.resource_url( + f"comment/{comment_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def find_components_for_projects( + self, + project_ids_or_keys=None, + start_at=None, + max_results=None, + order_by=None, + query=None, + data=None, + **request_kwargs, + ): + """Find components for projects.""" + url = self.resource_url("component", api_root="rest/api", api_version=self.api_version) + params = { + "projectIdsOrKeys": project_ids_or_keys, + "startAt": start_at, + "maxResults": max_results, + "orderBy": order_by, + "query": query, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_component(self, data=None, **request_kwargs): + """Create component.""" + url = self.resource_url("component", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_component(self, id, move_issues_to=None, data=None, **request_kwargs): + """Delete component.""" + url = self.resource_url(f"component/{id}", api_root="rest/api", api_version=self.api_version) + params = {"moveIssuesTo": move_issues_to} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_component(self, id, data=None, **request_kwargs): + """Get component.""" + url = self.resource_url(f"component/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_component(self, id, data=None, **request_kwargs): + """Update component.""" + url = self.resource_url(f"component/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_component_related_issues(self, id, data=None, **request_kwargs): + """Get component issues count.""" + url = self.resource_url(f"component/{id}/relatedIssueCounts", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_field_association_schemes( + self, project_id=None, query=None, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get field schemes.""" + url = self.resource_url("config/fieldschemes", api_root="rest/api", api_version=self.api_version) + params = {"projectId": project_id, "query": query, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_field_association_scheme(self, data=None, **request_kwargs): + """Create field scheme.""" + url = self.resource_url("config/fieldschemes", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_fields_associated_with_schemes(self, data=None, **request_kwargs): + """Remove fields associated with field schemes.""" + url = self.resource_url("config/fieldschemes/fields", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_fields_associated_with_schemes(self, data=None, **request_kwargs): + """Update fields associated with field schemes.""" + url = self.resource_url("config/fieldschemes/fields", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_field_association_scheme_item_parameters(self, data=None, **request_kwargs): + """Remove field parameters.""" + url = self.resource_url( + "config/fieldschemes/fields/parameters", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_field_association_scheme_item_parameters(self, data=None, **request_kwargs): + """Update field parameters.""" + url = self.resource_url( + "config/fieldschemes/fields/parameters", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_projects_with_field_schemes( + self, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get projects with field schemes.""" + url = self.resource_url("config/fieldschemes/projects", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def associate_projects_to_field_association_schemes(self, data=None, **request_kwargs): + """Associate projects to field schemes.""" + url = self.resource_url("config/fieldschemes/projects", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_field_association_scheme(self, id, data=None, **request_kwargs): + """Delete a field scheme.""" + url = self.resource_url(f"config/fieldschemes/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_field_association_scheme_by_id(self, id, data=None, **request_kwargs): + """Get field scheme.""" + url = self.resource_url(f"config/fieldschemes/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_field_association_scheme(self, id, data=None, **request_kwargs): + """Update field scheme.""" + url = self.resource_url(f"config/fieldschemes/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def clone_field_association_scheme(self, id, data=None, **request_kwargs): + """Clone field scheme.""" + url = self.resource_url(f"config/fieldschemes/{id}/clone", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def search_field_association_scheme_fields( + self, id, start_at=None, max_results=None, field_id=None, data=None, **request_kwargs + ): + """Search field scheme fields.""" + url = self.resource_url(f"config/fieldschemes/{id}/fields", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "fieldId": field_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_field_association_scheme_item_parameters(self, id, field_id, data=None, **request_kwargs): + """Get field parameters.""" + url = self.resource_url( + f"config/fieldschemes/{id}/fields/{field_id}/parameters", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def search_field_association_scheme_projects( + self, id, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Search field scheme projects.""" + url = self.resource_url(f"config/fieldschemes/{id}/projects", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_configuration(self, data=None, **request_kwargs): + """Get global settings.""" + url = self.resource_url("configuration", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_selected_time_tracking_implementation(self, data=None, **request_kwargs): + """Get selected time tracking provider.""" + url = self.resource_url("configuration/timetracking", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def select_time_tracking_implementation(self, data=None, **request_kwargs): + """Select time tracking provider.""" + url = self.resource_url("configuration/timetracking", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_available_time_tracking_implementations(self, data=None, **request_kwargs): + """Get all time tracking providers.""" + url = self.resource_url("configuration/timetracking/list", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_shared_time_tracking_configuration(self, data=None, **request_kwargs): + """Get time tracking settings.""" + url = self.resource_url("configuration/timetracking/options", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_shared_time_tracking_configuration(self, data=None, **request_kwargs): + """Set time tracking settings.""" + url = self.resource_url("configuration/timetracking/options", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_custom_field_option(self, id, data=None, **request_kwargs): + """Get custom field option.""" + url = self.resource_url(f"customFieldOption/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_dashboards(self, filter=None, start_at=None, max_results=None, data=None, **request_kwargs): + """Get all dashboards.""" + url = self.resource_url("dashboard", api_root="rest/api", api_version=self.api_version) + params = {"filter": filter, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_dashboard(self, extend_admin_permissions=None, data=None, **request_kwargs): + """Create dashboard.""" + url = self.resource_url("dashboard", api_root="rest/api", api_version=self.api_version) + params = {"extendAdminPermissions": extend_admin_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_edit_dashboards(self, data=None, **request_kwargs): + """Bulk edit dashboards.""" + url = self.resource_url("dashboard/bulk/edit", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_available_dashboard_gadgets(self, data=None, **request_kwargs): + """Get available gadgets.""" + url = self.resource_url("dashboard/gadgets", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_dashboards_paginated( + self, + dashboard_name=None, + account_id=None, + owner=None, + groupname=None, + group_id=None, + project_id=None, + order_by=None, + start_at=None, + max_results=None, + status=None, + expand=None, + data=None, + **request_kwargs, + ): + """Search for dashboards.""" + url = self.resource_url("dashboard/search", api_root="rest/api", api_version=self.api_version) + params = { + "dashboardName": dashboard_name, + "accountId": account_id, + "owner": owner, + "groupname": groupname, + "groupId": group_id, + "projectId": project_id, + "orderBy": order_by, + "startAt": start_at, + "maxResults": max_results, + "status": status, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_gadgets(self, dashboard_id, module_key=None, uri=None, gadget_id=None, data=None, **request_kwargs): + """Get gadgets.""" + url = self.resource_url(f"dashboard/{dashboard_id}/gadget", api_root="rest/api", api_version=self.api_version) + params = {"moduleKey": module_key, "uri": uri, "gadgetId": gadget_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_gadget(self, dashboard_id, data=None, **request_kwargs): + """Add gadget to dashboard.""" + url = self.resource_url(f"dashboard/{dashboard_id}/gadget", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_gadget(self, dashboard_id, gadget_id, data=None, **request_kwargs): + """Remove gadget from dashboard.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/gadget/{gadget_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_gadget(self, dashboard_id, gadget_id, data=None, **request_kwargs): + """Update gadget on dashboard.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/gadget/{gadget_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_dashboard_item_property_keys(self, dashboard_id, item_id, data=None, **request_kwargs): + """Get dashboard item property keys.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/items/{item_id}/properties", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_dashboard_item_property(self, dashboard_id, item_id, property_key, data=None, **request_kwargs): + """Delete dashboard item property.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/items/{item_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_dashboard_item_property(self, dashboard_id, item_id, property_key, data=None, **request_kwargs): + """Get dashboard item property.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/items/{item_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_dashboard_item_property(self, dashboard_id, item_id, property_key, data=None, **request_kwargs): + """Set dashboard item property.""" + url = self.resource_url( + f"dashboard/{dashboard_id}/items/{item_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_dashboard(self, id, data=None, **request_kwargs): + """Delete dashboard.""" + url = self.resource_url(f"dashboard/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_dashboard(self, id, data=None, **request_kwargs): + """Get dashboard.""" + url = self.resource_url(f"dashboard/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_dashboard(self, id, extend_admin_permissions=None, data=None, **request_kwargs): + """Update dashboard.""" + url = self.resource_url(f"dashboard/{id}", api_root="rest/api", api_version=self.api_version) + params = {"extendAdminPermissions": extend_admin_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def copy_dashboard(self, id, extend_admin_permissions=None, data=None, **request_kwargs): + """Copy dashboard.""" + url = self.resource_url(f"dashboard/{id}/copy", api_root="rest/api", api_version=self.api_version) + params = {"extendAdminPermissions": extend_admin_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_policy(self, data=None, **request_kwargs): + """Get data policy for the workspace.""" + url = self.resource_url("data-policy", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_policies(self, ids=None, data=None, **request_kwargs): + """Get data policy for projects.""" + url = self.resource_url("data-policy/project", api_root="rest/api", api_version=self.api_version) + params = {"ids": ids} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_events(self, data=None, **request_kwargs): + """Get events.""" + url = self.resource_url("events", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def analyse_expression(self, check=None, data=None, **request_kwargs): + """Analyse Jira expression.""" + url = self.resource_url("expression/analyse", api_root="rest/api", api_version=self.api_version) + params = {"check": check} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def evaluate_jira_expression(self, expand=None, data=None, **request_kwargs): + """Currently being removed. Evaluate Jira expression.""" + url = self.resource_url("expression/eval", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def evaluate_jsisjira_expression(self, expand=None, data=None, **request_kwargs): + """Evaluate Jira expression using enhanced search API.""" + url = self.resource_url("expression/evaluate", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_fields(self, data=None, **request_kwargs): + """Get fields.""" + url = self.resource_url("field", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_custom_field(self, data=None, **request_kwargs): + """Create custom field.""" + url = self.resource_url("field", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_associations(self, data=None, **request_kwargs): + """Remove associations.""" + url = self.resource_url("field/association", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def create_associations(self, data=None, **request_kwargs): + """Create associations.""" + url = self.resource_url("field/association", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_fields_paginated( + self, + start_at=None, + max_results=None, + type=None, + id=None, + query=None, + order_by=None, + expand=None, + project_ids=None, + data=None, + **request_kwargs, + ): + """Get fields paginated.""" + url = self.resource_url("field/search", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "type": type, + "id": id, + "query": query, + "orderBy": order_by, + "expand": expand, + "projectIds": project_ids, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_trashed_fields_paginated( + self, + start_at=None, + max_results=None, + id=None, + query=None, + expand=None, + order_by=None, + data=None, + **request_kwargs, + ): + """Get fields in trash paginated.""" + url = self.resource_url("field/search/trashed", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "query": query, + "expand": expand, + "orderBy": order_by, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_custom_field(self, field_id, data=None, **request_kwargs): + """Update custom field.""" + url = self.resource_url(f"field/{field_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_field_project_associations(self, field_id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get field project associations.""" + url = self.resource_url( + f"field/{field_id}/association/project", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_contexts_for_field( + self, + field_id, + is_any_issue_type=None, + is_global_context=None, + context_id=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Get custom field contexts.""" + url = self.resource_url(f"field/{field_id}/context", api_root="rest/api", api_version=self.api_version) + params = { + "isAnyIssueType": is_any_issue_type, + "isGlobalContext": is_global_context, + "contextId": context_id, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_custom_field_context(self, field_id, data=None, **request_kwargs): + """Create custom field context.""" + url = self.resource_url(f"field/{field_id}/context", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_default_values( + self, field_id, context_id=None, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get custom field contexts default values.""" + url = self.resource_url( + f"field/{field_id}/context/defaultValue", api_root="rest/api", api_version=self.api_version + ) + params = {"contextId": context_id, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_default_values(self, field_id, data=None, **request_kwargs): + """Set custom field contexts default values.""" + url = self.resource_url( + f"field/{field_id}/context/defaultValue", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_context_default_values( + self, + field_id, + context_id=None, + issue_type_id=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Get default values for a custom field grouped by context and issue type.""" + url = self.resource_url( + f"field/{field_id}/context/defaultValues", api_root="rest/api", api_version=self.api_version + ) + params = {"contextId": context_id, "issueTypeId": issue_type_id, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_type_mappings_for_contexts( + self, field_id, context_id=None, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get issue types for custom field context.""" + url = self.resource_url( + f"field/{field_id}/context/issuetypemapping", api_root="rest/api", api_version=self.api_version + ) + params = {"contextId": context_id, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_custom_field_contexts_for_projects_and_issue_types( + self, field_id, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get custom field contexts for projects and issue types.""" + url = self.resource_url(f"field/{field_id}/context/mapping", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_project_context_mapping( + self, field_id, context_id=None, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get project mappings for custom field context.""" + url = self.resource_url( + f"field/{field_id}/context/projectmapping", api_root="rest/api", api_version=self.api_version + ) + params = {"contextId": context_id, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_custom_field_context(self, field_id, context_id, data=None, **request_kwargs): + """Delete custom field context.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_custom_field_context(self, field_id, context_id, data=None, **request_kwargs): + """Update custom field context.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def add_issue_types_to_context(self, field_id, context_id, data=None, **request_kwargs): + """Add issue types to context.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/issuetype", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_issue_types_from_context(self, field_id, context_id, data=None, **request_kwargs): + """Remove issue types from context.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/issuetype/remove", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_options_for_context( + self, + field_id, + context_id, + option_id=None, + only_options=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Get custom field options (context).""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option", api_root="rest/api", api_version=self.api_version + ) + params = {"optionId": option_id, "onlyOptions": only_options, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_custom_field_option(self, field_id, context_id, data=None, **request_kwargs): + """Create custom field options (context).""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_custom_field_option(self, field_id, context_id, data=None, **request_kwargs): + """Update custom field options (context).""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def reorder_custom_field_options(self, field_id, context_id, data=None, **request_kwargs): + """Reorder custom field options (context).""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option/move", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_custom_field_option(self, field_id, context_id, option_id, data=None, **request_kwargs): + """Delete custom field options (context).""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option/{option_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def replace_custom_field_option( + self, field_id, option_id, context_id, replace_with=None, jql=None, data=None, **request_kwargs + ): + """Replace custom field options.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/option/{option_id}/issue", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"replaceWith": replace_with, "jql": jql} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def assign_projects_to_custom_field_context(self, field_id, context_id, data=None, **request_kwargs): + """Assign custom field context to projects.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/project", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_custom_field_context_from_projects(self, field_id, context_id, data=None, **request_kwargs): + """Remove custom field context from projects.""" + url = self.resource_url( + f"field/{field_id}/context/{context_id}/project/remove", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_contexts_for_field_deprecated(self, field_id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get contexts for a field.""" + url = self.resource_url(f"field/{field_id}/contexts", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_screens_for_field( + self, field_id, start_at=None, max_results=None, expand=None, data=None, **request_kwargs + ): + """Get screens for a field.""" + url = self.resource_url(f"field/{field_id}/screens", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_issue_field_options(self, field_key, start_at=None, max_results=None, data=None, **request_kwargs): + """Get all issue field options.""" + url = self.resource_url(f"field/{field_key}/option", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_field_option(self, field_key, data=None, **request_kwargs): + """Create issue field option.""" + url = self.resource_url(f"field/{field_key}/option", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_selectable_issue_field_options( + self, field_key, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get selectable issue field options.""" + url = self.resource_url( + f"field/{field_key}/option/suggestions/edit", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_visible_issue_field_options( + self, field_key, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get visible issue field options.""" + url = self.resource_url( + f"field/{field_key}/option/suggestions/search", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_issue_field_option(self, field_key, option_id, data=None, **request_kwargs): + """Delete issue field option.""" + url = self.resource_url( + f"field/{field_key}/option/{option_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_field_option(self, field_key, option_id, data=None, **request_kwargs): + """Get issue field option.""" + url = self.resource_url( + f"field/{field_key}/option/{option_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_issue_field_option(self, field_key, option_id, data=None, **request_kwargs): + """Update issue field option.""" + url = self.resource_url( + f"field/{field_key}/option/{option_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def replace_issue_field_option( + self, + field_key, + option_id, + replace_with=None, + jql=None, + override_screen_security=None, + override_editable_flag=None, + data=None, + **request_kwargs, + ): + """Replace issue field option.""" + url = self.resource_url( + f"field/{field_key}/option/{option_id}/issue", api_root="rest/api", api_version=self.api_version + ) + params = { + "replaceWith": replace_with, + "jql": jql, + "overrideScreenSecurity": override_screen_security, + "overrideEditableFlag": override_editable_flag, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def delete_custom_field(self, id, data=None, **request_kwargs): + """Delete custom field.""" + url = self.resource_url(f"field/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def restore_custom_field(self, id, data=None, **request_kwargs): + """Restore custom field from trash.""" + url = self.resource_url(f"field/{id}/restore", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def trash_custom_field(self, id, data=None, **request_kwargs): + """Move custom field to trash.""" + url = self.resource_url(f"field/{id}/trash", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_field_configurations( + self, start_at=None, max_results=None, id=None, is_default=None, query=None, data=None, **request_kwargs + ): + """Get all field configurations.""" + url = self.resource_url("fieldconfiguration", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "id": id, "isDefault": is_default, "query": query} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_field_configuration(self, data=None, **request_kwargs): + """Create field configuration.""" + url = self.resource_url("fieldconfiguration", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_field_configuration(self, id, data=None, **request_kwargs): + """Delete field configuration.""" + url = self.resource_url(f"fieldconfiguration/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_field_configuration(self, id, data=None, **request_kwargs): + """Update field configuration.""" + url = self.resource_url(f"fieldconfiguration/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_field_configuration_items(self, id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get field configuration items.""" + url = self.resource_url(f"fieldconfiguration/{id}/fields", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_field_configuration_items(self, id, data=None, **request_kwargs): + """Update field configuration items.""" + url = self.resource_url(f"fieldconfiguration/{id}/fields", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_field_configuration_schemes( + self, start_at=None, max_results=None, id=None, data=None, **request_kwargs + ): + """Get all field configuration schemes.""" + url = self.resource_url("fieldconfigurationscheme", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "id": id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_field_configuration_scheme(self, data=None, **request_kwargs): + """Create field configuration scheme.""" + url = self.resource_url("fieldconfigurationscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_field_configuration_scheme_mappings( + self, start_at=None, max_results=None, field_configuration_scheme_id=None, data=None, **request_kwargs + ): + """Get field configuration issue type items.""" + url = self.resource_url("fieldconfigurationscheme/mapping", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "fieldConfigurationSchemeId": field_configuration_scheme_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_field_configuration_scheme_project_mapping( + self, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get field configuration schemes for projects.""" + url = self.resource_url("fieldconfigurationscheme/project", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def assign_field_configuration_scheme_to_project(self, data=None, **request_kwargs): + """Assign field configuration scheme to project.""" + url = self.resource_url("fieldconfigurationscheme/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_field_configuration_scheme(self, id, data=None, **request_kwargs): + """Delete field configuration scheme.""" + url = self.resource_url(f"fieldconfigurationscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_field_configuration_scheme(self, id, data=None, **request_kwargs): + """Update field configuration scheme.""" + url = self.resource_url(f"fieldconfigurationscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def set_field_configuration_scheme_mapping(self, id, data=None, **request_kwargs): + """Assign issue types to field configurations.""" + url = self.resource_url( + f"fieldconfigurationscheme/{id}/mapping", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_issue_types_from_global_field_configuration_scheme(self, id, data=None, **request_kwargs): + """Remove issue types from field configuration scheme.""" + url = self.resource_url( + f"fieldconfigurationscheme/{id}/mapping/delete", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def create_filter(self, expand=None, override_share_permissions=None, data=None, **request_kwargs): + """Create filter.""" + url = self.resource_url("filter", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "overrideSharePermissions": override_share_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_default_share_scope(self, data=None, **request_kwargs): + """Get default share scope.""" + url = self.resource_url("filter/defaultShareScope", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_default_share_scope(self, data=None, **request_kwargs): + """Set default share scope.""" + url = self.resource_url("filter/defaultShareScope", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_favourite_filters(self, expand=None, data=None, **request_kwargs): + """Get favorite filters.""" + url = self.resource_url("filter/favourite", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_my_filters(self, expand=None, include_favourites=None, data=None, **request_kwargs): + """Get my filters.""" + url = self.resource_url("filter/my", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "includeFavourites": include_favourites} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_filters_paginated( + self, + filter_name=None, + account_id=None, + owner=None, + groupname=None, + group_id=None, + project_id=None, + id=None, + order_by=None, + start_at=None, + max_results=None, + expand=None, + override_share_permissions=None, + is_substring_match=None, + data=None, + **request_kwargs, + ): + """Search for filters.""" + url = self.resource_url("filter/search", api_root="rest/api", api_version=self.api_version) + params = { + "filterName": filter_name, + "accountId": account_id, + "owner": owner, + "groupname": groupname, + "groupId": group_id, + "projectId": project_id, + "id": id, + "orderBy": order_by, + "startAt": start_at, + "maxResults": max_results, + "expand": expand, + "overrideSharePermissions": override_share_permissions, + "isSubstringMatch": is_substring_match, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_filter(self, id, data=None, **request_kwargs): + """Delete filter.""" + url = self.resource_url(f"filter/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_filter(self, id, expand=None, override_share_permissions=None, data=None, **request_kwargs): + """Get filter.""" + url = self.resource_url(f"filter/{id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "overrideSharePermissions": override_share_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_filter(self, id, expand=None, override_share_permissions=None, data=None, **request_kwargs): + """Update filter.""" + url = self.resource_url(f"filter/{id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "overrideSharePermissions": override_share_permissions} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def reset_columns(self, id, data=None, **request_kwargs): + """Reset columns.""" + url = self.resource_url(f"filter/{id}/columns", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_columns(self, id, data=None, **request_kwargs): + """Get columns.""" + url = self.resource_url(f"filter/{id}/columns", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_columns(self, id, data=None, **request_kwargs): + """Set columns.""" + url = self.resource_url(f"filter/{id}/columns", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_favourite_for_filter(self, id, expand=None, data=None, **request_kwargs): + """Remove filter as favorite.""" + url = self.resource_url(f"filter/{id}/favourite", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def set_favourite_for_filter(self, id, expand=None, data=None, **request_kwargs): + """Add filter as favorite.""" + url = self.resource_url(f"filter/{id}/favourite", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def change_filter_owner(self, id, data=None, **request_kwargs): + """Change filter owner.""" + url = self.resource_url(f"filter/{id}/owner", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_share_permissions(self, id, data=None, **request_kwargs): + """Get share permissions.""" + url = self.resource_url(f"filter/{id}/permission", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_share_permission(self, id, data=None, **request_kwargs): + """Add share permission.""" + url = self.resource_url(f"filter/{id}/permission", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_share_permission(self, id, permission_id, data=None, **request_kwargs): + """Delete share permission.""" + url = self.resource_url( + f"filter/{id}/permission/{permission_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_share_permission(self, id, permission_id, data=None, **request_kwargs): + """Get share permission.""" + url = self.resource_url( + f"filter/{id}/permission/{permission_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def bulk_pin_unpin_projects_async(self, data=None, **request_kwargs): + """Bulk pin or unpin issue panel to projects.""" + url = self.resource_url("forge/panel/action/bulk/async", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_group( + self, groupname=None, group_id=None, swap_group=None, swap_group_id=None, data=None, **request_kwargs + ): + """Remove group.""" + url = self.resource_url("group", api_root="rest/api", api_version=self.api_version) + params = {"groupname": groupname, "groupId": group_id, "swapGroup": swap_group, "swapGroupId": swap_group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_group(self, groupname=None, group_id=None, expand=None, data=None, **request_kwargs): + """Get group.""" + url = self.resource_url("group", api_root="rest/api", api_version=self.api_version) + params = {"groupname": groupname, "groupId": group_id, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_group(self, data=None, **request_kwargs): + """Create group.""" + url = self.resource_url("group", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_get_groups( + self, + start_at=None, + max_results=None, + group_id=None, + group_name=None, + access_type=None, + application_key=None, + data=None, + **request_kwargs, + ): + """Bulk get groups.""" + url = self.resource_url("group/bulk", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "groupId": group_id, + "groupName": group_name, + "accessType": access_type, + "applicationKey": application_key, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_users_from_group( + self, + groupname=None, + group_id=None, + include_inactive_users=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Get users from group.""" + url = self.resource_url("group/member", api_root="rest/api", api_version=self.api_version) + params = { + "groupname": groupname, + "groupId": group_id, + "includeInactiveUsers": include_inactive_users, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_user_from_group( + self, groupname=None, group_id=None, username=None, account_id=None, data=None, **request_kwargs + ): + """Remove user from group.""" + url = self.resource_url("group/user", api_root="rest/api", api_version=self.api_version) + params = {"groupname": groupname, "groupId": group_id, "username": username, "accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def add_user_to_group(self, groupname=None, group_id=None, data=None, **request_kwargs): + """Add user to group.""" + url = self.resource_url("group/user", api_root="rest/api", api_version=self.api_version) + params = {"groupname": groupname, "groupId": group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def find_groups( + self, + account_id=None, + query=None, + exclude=None, + exclude_id=None, + max_results=None, + case_insensitive=None, + user_name=None, + data=None, + **request_kwargs, + ): + """Find groups.""" + url = self.resource_url("groups/picker", api_root="rest/api", api_version=self.api_version) + params = { + "accountId": account_id, + "query": query, + "exclude": exclude, + "excludeId": exclude_id, + "maxResults": max_results, + "caseInsensitive": case_insensitive, + "userName": user_name, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_users_and_groups( + self, + query=None, + max_results=None, + show_avatar=None, + field_id=None, + project_id=None, + issue_type_id=None, + avatar_size=None, + case_insensitive=None, + exclude_connect_addons=None, + include_ai_agents=None, + data=None, + **request_kwargs, + ): + """Find users and groups.""" + url = self.resource_url("groupuserpicker", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "maxResults": max_results, + "showAvatar": show_avatar, + "fieldId": field_id, + "projectId": project_id, + "issueTypeId": issue_type_id, + "avatarSize": avatar_size, + "caseInsensitive": case_insensitive, + "excludeConnectAddons": exclude_connect_addons, + "includeAiAgents": include_ai_agents, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_license(self, data=None, **request_kwargs): + """Get license.""" + url = self.resource_url("instance/license", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue(self, update_history=None, data=None, **request_kwargs): + """Create issue.""" + url = self.resource_url("issue", api_root="rest/api", api_version=self.api_version) + params = {"updateHistory": update_history} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def archive_issues_async(self, data=None, **request_kwargs): + """Archive issue(s) by JQL.""" + url = self.resource_url("issue/archive", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def archive_issues(self, data=None, **request_kwargs): + """Archive issue(s) by issue ID/key.""" + url = self.resource_url("issue/archive", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def create_issues(self, data=None, **request_kwargs): + """Bulk create issue.""" + url = self.resource_url("issue/bulk", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_fetch_issues(self, data=None, **request_kwargs): + """Bulk fetch issues.""" + url = self.resource_url("issue/bulkfetch", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_create_issue_meta( + self, + project_ids=None, + project_keys=None, + issuetype_ids=None, + issuetype_names=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get create issue metadata.""" + url = self.resource_url("issue/createmeta", api_root="rest/api", api_version=self.api_version) + params = { + "projectIds": project_ids, + "projectKeys": project_keys, + "issuetypeIds": issuetype_ids, + "issuetypeNames": issuetype_names, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_create_issue_meta_issue_types( + self, project_id_or_key, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get create metadata issue types for a project.""" + url = self.resource_url( + f"issue/createmeta/{project_id_or_key}/issuetypes", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_create_issue_meta_issue_type_id( + self, project_id_or_key, issue_type_id, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get create field metadata for a project and issue type id.""" + url = self.resource_url( + f"issue/createmeta/{project_id_or_key}/issuetypes/{issue_type_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_adf_limit_report(self, is_returning_keys=None, field_type=None, data=None, **request_kwargs): + """Get issue adf limit report.""" + url = self.resource_url("issue/limit/adf/report", api_root="rest/api", api_version=self.api_version) + params = {"isReturningKeys": is_returning_keys, "fieldType": field_type} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_limit_report(self, is_returning_keys=None, data=None, **request_kwargs): + """Get issue limit report.""" + url = self.resource_url("issue/limit/report", api_root="rest/api", api_version=self.api_version) + params = {"isReturningKeys": is_returning_keys} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_picker_resource( + self, + query=None, + current_jql=None, + current_issue_key=None, + current_project_id=None, + show_sub_tasks=None, + show_sub_task_parent=None, + data=None, + **request_kwargs, + ): + """Get issue picker suggestions.""" + url = self.resource_url("issue/picker", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "currentJQL": current_jql, + "currentIssueKey": current_issue_key, + "currentProjectId": current_project_id, + "showSubTasks": show_sub_tasks, + "showSubTaskParent": show_sub_task_parent, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def bulk_set_issues_properties_list(self, data=None, **request_kwargs): + """Bulk set issues properties by list.""" + url = self.resource_url("issue/properties", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_set_issue_properties_by_issue(self, data=None, **request_kwargs): + """Bulk set issue properties by issue.""" + url = self.resource_url("issue/properties/multi", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_delete_issue_property(self, property_key, data=None, **request_kwargs): + """Bulk delete issue property.""" + url = self.resource_url(f"issue/properties/{property_key}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def bulk_set_issue_property(self, property_key, data=None, **request_kwargs): + """Bulk set issue property.""" + url = self.resource_url(f"issue/properties/{property_key}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def unarchive_issues(self, data=None, **request_kwargs): + """Unarchive issue(s) by issue keys/ID.""" + url = self.resource_url("issue/unarchive", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_is_watching_issue_bulk(self, data=None, **request_kwargs): + """Get is watching issue bulk.""" + url = self.resource_url("issue/watching", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_issue(self, issue_id_or_key, delete_subtasks=None, data=None, **request_kwargs): + """Delete issue.""" + url = self.resource_url(f"issue/{issue_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = {"deleteSubtasks": delete_subtasks} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue( + self, + issue_id_or_key, + fields=None, + fields_by_keys=None, + expand=None, + properties=None, + update_history=None, + fail_fast=None, + data=None, + **request_kwargs, + ): + """Get issue.""" + url = self.resource_url(f"issue/{issue_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = { + "fields": fields, + "fieldsByKeys": fields_by_keys, + "expand": expand, + "properties": properties, + "updateHistory": update_history, + "failFast": fail_fast, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def edit_issue( + self, + issue_id_or_key, + notify_users=None, + override_screen_security=None, + override_editable_flag=None, + return_issue=None, + expand=None, + data=None, + **request_kwargs, + ): + """Edit issue.""" + url = self.resource_url(f"issue/{issue_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = { + "notifyUsers": notify_users, + "overrideScreenSecurity": override_screen_security, + "overrideEditableFlag": override_editable_flag, + "returnIssue": return_issue, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def assign_issue(self, issue_id_or_key, data=None, **request_kwargs): + """Assign issue.""" + url = self.resource_url(f"issue/{issue_id_or_key}/assignee", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def add_attachment(self, issue_id_or_key, data=None, **request_kwargs): + """Add attachment.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/attachments", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_change_logs(self, issue_id_or_key, start_at=None, max_results=None, data=None, **request_kwargs): + """Get changelogs.""" + url = self.resource_url(f"issue/{issue_id_or_key}/changelog", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_change_logs_by_ids(self, issue_id_or_key, data=None, **request_kwargs): + """Get changelogs by IDs.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/changelog/list", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_comments( + self, issue_id_or_key, start_at=None, max_results=None, order_by=None, expand=None, data=None, **request_kwargs + ): + """Get comments.""" + url = self.resource_url(f"issue/{issue_id_or_key}/comment", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "orderBy": order_by, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_comment(self, issue_id_or_key, expand=None, data=None, **request_kwargs): + """Add comment.""" + url = self.resource_url(f"issue/{issue_id_or_key}/comment", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_comment(self, issue_id_or_key, id, data=None, **request_kwargs): + """Delete comment.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/comment/{id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_comment(self, issue_id_or_key, id, expand=None, data=None, **request_kwargs): + """Get comment.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/comment/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_comment( + self, + issue_id_or_key, + id, + notify_users=None, + override_editable_flag=None, + expand=None, + data=None, + **request_kwargs, + ): + """Update comment.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/comment/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"notifyUsers": notify_users, "overrideEditableFlag": override_editable_flag, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_edit_issue_meta( + self, issue_id_or_key, override_screen_security=None, override_editable_flag=None, data=None, **request_kwargs + ): + """Get edit issue metadata.""" + url = self.resource_url(f"issue/{issue_id_or_key}/editmeta", api_root="rest/api", api_version=self.api_version) + params = {"overrideScreenSecurity": override_screen_security, "overrideEditableFlag": override_editable_flag} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def notify(self, issue_id_or_key, data=None, **request_kwargs): + """Send notification for issue.""" + url = self.resource_url(f"issue/{issue_id_or_key}/notify", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_property_keys(self, issue_id_or_key, data=None, **request_kwargs): + """Get issue property keys.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/properties", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_issue_property(self, issue_id_or_key, property_key, data=None, **request_kwargs): + """Delete issue property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_property(self, issue_id_or_key, property_key, data=None, **request_kwargs): + """Get issue property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_issue_property(self, issue_id_or_key, property_key, data=None, **request_kwargs): + """Set issue property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_remote_issue_link_by_global_id(self, issue_id_or_key, global_id=None, data=None, **request_kwargs): + """Delete remote issue link by global ID.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink", api_root="rest/api", api_version=self.api_version + ) + params = {"globalId": global_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_remote_issue_links(self, issue_id_or_key, global_id=None, data=None, **request_kwargs): + """Get remote issue links.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink", api_root="rest/api", api_version=self.api_version + ) + params = {"globalId": global_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_or_update_remote_issue_link(self, issue_id_or_key, data=None, **request_kwargs): + """Create or update remote issue link.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_remote_issue_link_by_id(self, issue_id_or_key, link_id, data=None, **request_kwargs): + """Delete remote issue link by ID.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink/{link_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_remote_issue_link_by_id(self, issue_id_or_key, link_id, data=None, **request_kwargs): + """Get remote issue link by ID.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink/{link_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_remote_issue_link(self, issue_id_or_key, link_id, data=None, **request_kwargs): + """Update remote issue link by ID.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/remotelink/{link_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_transitions( + self, + issue_id_or_key, + expand=None, + transition_id=None, + skip_remote_only_condition=None, + include_unavailable_transitions=None, + sort_by_ops_bar_and_status=None, + data=None, + **request_kwargs, + ): + """Get transitions.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/transitions", api_root="rest/api", api_version=self.api_version + ) + params = { + "expand": expand, + "transitionId": transition_id, + "skipRemoteOnlyCondition": skip_remote_only_condition, + "includeUnavailableTransitions": include_unavailable_transitions, + "sortByOpsBarAndStatus": sort_by_ops_bar_and_status, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def do_transition(self, issue_id_or_key, data=None, **request_kwargs): + """Transition issue.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/transitions", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_vote(self, issue_id_or_key, data=None, **request_kwargs): + """Delete vote.""" + url = self.resource_url(f"issue/{issue_id_or_key}/votes", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_votes(self, issue_id_or_key, data=None, **request_kwargs): + """Get votes.""" + url = self.resource_url(f"issue/{issue_id_or_key}/votes", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_vote(self, issue_id_or_key, data=None, **request_kwargs): + """Add vote.""" + url = self.resource_url(f"issue/{issue_id_or_key}/votes", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_watcher(self, issue_id_or_key, username=None, account_id=None, data=None, **request_kwargs): + """Delete watcher.""" + url = self.resource_url(f"issue/{issue_id_or_key}/watchers", api_root="rest/api", api_version=self.api_version) + params = {"username": username, "accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_watchers(self, issue_id_or_key, data=None, **request_kwargs): + """Get issue watchers.""" + url = self.resource_url(f"issue/{issue_id_or_key}/watchers", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_watcher(self, issue_id_or_key, data=None, **request_kwargs): + """Add watcher.""" + url = self.resource_url(f"issue/{issue_id_or_key}/watchers", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_delete_worklogs( + self, issue_id_or_key, adjust_estimate=None, override_editable_flag=None, data=None, **request_kwargs + ): + """Bulk delete worklogs.""" + url = self.resource_url(f"issue/{issue_id_or_key}/worklog", api_root="rest/api", api_version=self.api_version) + params = {"adjustEstimate": adjust_estimate, "overrideEditableFlag": override_editable_flag} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_worklog( + self, + issue_id_or_key, + start_at=None, + max_results=None, + started_after=None, + started_before=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issue worklogs.""" + url = self.resource_url(f"issue/{issue_id_or_key}/worklog", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "startedAfter": started_after, + "startedBefore": started_before, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_worklog( + self, + issue_id_or_key, + notify_users=None, + adjust_estimate=None, + new_estimate=None, + reduce_by=None, + expand=None, + override_editable_flag=None, + data=None, + **request_kwargs, + ): + """Add worklog.""" + url = self.resource_url(f"issue/{issue_id_or_key}/worklog", api_root="rest/api", api_version=self.api_version) + params = { + "notifyUsers": notify_users, + "adjustEstimate": adjust_estimate, + "newEstimate": new_estimate, + "reduceBy": reduce_by, + "expand": expand, + "overrideEditableFlag": override_editable_flag, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def bulk_move_worklogs( + self, issue_id_or_key, adjust_estimate=None, override_editable_flag=None, data=None, **request_kwargs + ): + """Bulk move worklogs.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/move", api_root="rest/api", api_version=self.api_version + ) + params = {"adjustEstimate": adjust_estimate, "overrideEditableFlag": override_editable_flag} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_worklog( + self, + issue_id_or_key, + id, + notify_users=None, + adjust_estimate=None, + new_estimate=None, + increase_by=None, + override_editable_flag=None, + data=None, + **request_kwargs, + ): + """Delete worklog.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{id}", api_root="rest/api", api_version=self.api_version + ) + params = { + "notifyUsers": notify_users, + "adjustEstimate": adjust_estimate, + "newEstimate": new_estimate, + "increaseBy": increase_by, + "overrideEditableFlag": override_editable_flag, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_worklog(self, issue_id_or_key, id, expand=None, data=None, **request_kwargs): + """Get worklog.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_worklog( + self, + issue_id_or_key, + id, + notify_users=None, + adjust_estimate=None, + new_estimate=None, + expand=None, + override_editable_flag=None, + data=None, + **request_kwargs, + ): + """Update worklog.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{id}", api_root="rest/api", api_version=self.api_version + ) + params = { + "notifyUsers": notify_users, + "adjustEstimate": adjust_estimate, + "newEstimate": new_estimate, + "expand": expand, + "overrideEditableFlag": override_editable_flag, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_worklog_property_keys(self, issue_id_or_key, worklog_id, data=None, **request_kwargs): + """Get worklog property keys.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{worklog_id}/properties", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_worklog_property(self, issue_id_or_key, worklog_id, property_key, data=None, **request_kwargs): + """Delete worklog property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{worklog_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_worklog_property(self, issue_id_or_key, worklog_id, property_key, data=None, **request_kwargs): + """Get worklog property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{worklog_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_worklog_property(self, issue_id_or_key, worklog_id, property_key, data=None, **request_kwargs): + """Set worklog property.""" + url = self.resource_url( + f"issue/{issue_id_or_key}/worklog/{worklog_id}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def link_issues(self, data=None, **request_kwargs): + """Create issue link.""" + url = self.resource_url("issueLink", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_issue_link(self, link_id, data=None, **request_kwargs): + """Delete issue link.""" + url = self.resource_url(f"issueLink/{link_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_link(self, link_id, data=None, **request_kwargs): + """Get issue link.""" + url = self.resource_url(f"issueLink/{link_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_link_types(self, data=None, **request_kwargs): + """Get issue link types.""" + url = self.resource_url("issueLinkType", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_link_type(self, data=None, **request_kwargs): + """Create issue link type.""" + url = self.resource_url("issueLinkType", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_issue_link_type(self, issue_link_type_id, data=None, **request_kwargs): + """Delete issue link type.""" + url = self.resource_url( + f"issueLinkType/{issue_link_type_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_link_type(self, issue_link_type_id, data=None, **request_kwargs): + """Get issue link type.""" + url = self.resource_url( + f"issueLinkType/{issue_link_type_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_issue_link_type(self, issue_link_type_id, data=None, **request_kwargs): + """Update issue link type.""" + url = self.resource_url( + f"issueLinkType/{issue_link_type_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def export_archived_issues(self, data=None, **request_kwargs): + """Export archived issue(s).""" + url = self.resource_url("issues/archive/export", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_issue_security_schemes(self, data=None, **request_kwargs): + """Get issue security schemes.""" + url = self.resource_url("issuesecurityschemes", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_security_scheme(self, data=None, **request_kwargs): + """Create issue security scheme.""" + url = self.resource_url("issuesecurityschemes", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_security_levels( + self, start_at=None, max_results=None, id=None, scheme_id=None, only_default=None, data=None, **request_kwargs + ): + """Get issue security levels.""" + url = self.resource_url("issuesecurityschemes/level", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "schemeId": scheme_id, + "onlyDefault": only_default, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_default_levels(self, data=None, **request_kwargs): + """Set default issue security levels.""" + url = self.resource_url("issuesecurityschemes/level/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_security_level_members( + self, + start_at=None, + max_results=None, + id=None, + scheme_id=None, + level_id=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issue security level members.""" + url = self.resource_url("issuesecurityschemes/level/member", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "schemeId": scheme_id, + "levelId": level_id, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def search_projects_using_security_schemes( + self, + start_at=None, + max_results=None, + issue_security_scheme_id=None, + project_id=None, + data=None, + **request_kwargs, + ): + """Get projects using issue security schemes.""" + url = self.resource_url("issuesecurityschemes/project", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "issueSecuritySchemeId": issue_security_scheme_id, + "projectId": project_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def associate_schemes_to_projects(self, data=None, **request_kwargs): + """Associate security scheme to project.""" + url = self.resource_url("issuesecurityschemes/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def search_security_schemes( + self, start_at=None, max_results=None, id=None, project_id=None, data=None, **request_kwargs + ): + """Search issue security schemes.""" + url = self.resource_url("issuesecurityschemes/search", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "id": id, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_security_scheme(self, id, data=None, **request_kwargs): + """Get issue security scheme.""" + url = self.resource_url(f"issuesecurityschemes/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_issue_security_scheme(self, id, data=None, **request_kwargs): + """Update issue security scheme.""" + url = self.resource_url(f"issuesecurityschemes/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_issue_security_level_members( + self, + issue_security_scheme_id, + start_at=None, + max_results=None, + issue_security_level_id=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issue security level members by issue security scheme.""" + url = self.resource_url( + f"issuesecurityschemes/{issue_security_scheme_id}/members", + api_root="rest/api", + api_version=self.api_version, + ) + params = { + "startAt": start_at, + "maxResults": max_results, + "issueSecurityLevelId": issue_security_level_id, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_security_scheme(self, scheme_id, data=None, **request_kwargs): + """Delete issue security scheme.""" + url = self.resource_url(f"issuesecurityschemes/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def add_security_level(self, scheme_id, data=None, **request_kwargs): + """Add issue security levels.""" + url = self.resource_url( + f"issuesecurityschemes/{scheme_id}/level", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_level(self, scheme_id, level_id, replace_with=None, data=None, **request_kwargs): + """Remove issue security level.""" + url = self.resource_url( + f"issuesecurityschemes/{scheme_id}/level/{level_id}", api_root="rest/api", api_version=self.api_version + ) + params = {"replaceWith": replace_with} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_security_level(self, scheme_id, level_id, data=None, **request_kwargs): + """Update issue security level.""" + url = self.resource_url( + f"issuesecurityschemes/{scheme_id}/level/{level_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def add_security_level_members(self, scheme_id, level_id, data=None, **request_kwargs): + """Add issue security level members.""" + url = self.resource_url( + f"issuesecurityschemes/{scheme_id}/level/{level_id}/member", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_member_from_security_level(self, scheme_id, level_id, member_id, data=None, **request_kwargs): + """Remove member from issue security level.""" + url = self.resource_url( + f"issuesecurityschemes/{scheme_id}/level/{level_id}/member/{member_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_all_types(self, data=None, **request_kwargs): + """Get all issue types for user.""" + url = self.resource_url("issuetype", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_type(self, data=None, **request_kwargs): + """Create issue type.""" + url = self.resource_url("issuetype", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_types_for_project(self, project_id=None, level=None, data=None, **request_kwargs): + """Get issue types for project.""" + url = self.resource_url("issuetype/project", api_root="rest/api", api_version=self.api_version) + params = {"projectId": project_id, "level": level} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_issue_type(self, id, alternative_issue_type_id=None, data=None, **request_kwargs): + """Delete issue type.""" + url = self.resource_url(f"issuetype/{id}", api_root="rest/api", api_version=self.api_version) + params = {"alternativeIssueTypeId": alternative_issue_type_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_type(self, id, data=None, **request_kwargs): + """Get issue type.""" + url = self.resource_url(f"issuetype/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_issue_type(self, id, data=None, **request_kwargs): + """Update issue type.""" + url = self.resource_url(f"issuetype/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_alternative_issue_types(self, id, data=None, **request_kwargs): + """Get alternative issue types.""" + url = self.resource_url(f"issuetype/{id}/alternatives", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_type_avatar(self, id, x=None, y=None, size=None, data=None, **request_kwargs): + """Load issue type avatar.""" + url = self.resource_url(f"issuetype/{id}/avatar2", api_root="rest/api", api_version=self.api_version) + params = {"x": x, "y": y, "size": size} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_type_property_keys(self, issue_type_id, data=None, **request_kwargs): + """Get issue type property keys.""" + url = self.resource_url( + f"issuetype/{issue_type_id}/properties", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_issue_type_property(self, issue_type_id, property_key, data=None, **request_kwargs): + """Delete issue type property.""" + url = self.resource_url( + f"issuetype/{issue_type_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_type_property(self, issue_type_id, property_key, data=None, **request_kwargs): + """Get issue type property.""" + url = self.resource_url( + f"issuetype/{issue_type_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_issue_type_property(self, issue_type_id, property_key, data=None, **request_kwargs): + """Set issue type property.""" + url = self.resource_url( + f"issuetype/{issue_type_id}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_issue_type_schemes( + self, + start_at=None, + max_results=None, + id=None, + order_by=None, + expand=None, + query_string=None, + data=None, + **request_kwargs, + ): + """Get all issue type schemes.""" + url = self.resource_url("issuetypescheme", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "orderBy": order_by, + "expand": expand, + "queryString": query_string, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_type_scheme(self, data=None, **request_kwargs): + """Create issue type scheme.""" + url = self.resource_url("issuetypescheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_type_schemes_mapping( + self, start_at=None, max_results=None, issue_type_scheme_id=None, data=None, **request_kwargs + ): + """Get issue type scheme items.""" + url = self.resource_url("issuetypescheme/mapping", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "issueTypeSchemeId": issue_type_scheme_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_type_scheme_for_projects( + self, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get issue type schemes for projects.""" + url = self.resource_url("issuetypescheme/project", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def assign_issue_type_scheme_to_project(self, data=None, **request_kwargs): + """Assign issue type scheme to project.""" + url = self.resource_url("issuetypescheme/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_issue_type_scheme(self, issue_type_scheme_id, data=None, **request_kwargs): + """Delete issue type scheme.""" + url = self.resource_url( + f"issuetypescheme/{issue_type_scheme_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_issue_type_scheme(self, issue_type_scheme_id, data=None, **request_kwargs): + """Update issue type scheme.""" + url = self.resource_url( + f"issuetypescheme/{issue_type_scheme_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def add_issue_types_to_issue_type_scheme(self, issue_type_scheme_id, data=None, **request_kwargs): + """Add issue types to issue type scheme.""" + url = self.resource_url( + f"issuetypescheme/{issue_type_scheme_id}/issuetype", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def reorder_issue_types_in_issue_type_scheme(self, issue_type_scheme_id, data=None, **request_kwargs): + """Change order of issue types.""" + url = self.resource_url( + f"issuetypescheme/{issue_type_scheme_id}/issuetype/move", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_issue_type_from_issue_type_scheme( + self, issue_type_scheme_id, issue_type_id, data=None, **request_kwargs + ): + """Remove issue type from issue type scheme.""" + url = self.resource_url( + f"issuetypescheme/{issue_type_scheme_id}/issuetype/{issue_type_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_issue_type_screen_schemes( + self, + start_at=None, + max_results=None, + id=None, + query_string=None, + order_by=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issue type screen schemes.""" + url = self.resource_url("issuetypescreenscheme", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "queryString": query_string, + "orderBy": order_by, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_issue_type_screen_scheme(self, data=None, **request_kwargs): + """Create issue type screen scheme.""" + url = self.resource_url("issuetypescreenscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_type_screen_scheme_mappings( + self, start_at=None, max_results=None, issue_type_screen_scheme_id=None, data=None, **request_kwargs + ): + """Get issue type screen scheme items.""" + url = self.resource_url("issuetypescreenscheme/mapping", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "issueTypeScreenSchemeId": issue_type_screen_scheme_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_type_screen_scheme_project_associations( + self, start_at=None, max_results=None, project_id=None, data=None, **request_kwargs + ): + """Get issue type screen schemes for projects.""" + url = self.resource_url("issuetypescreenscheme/project", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def assign_issue_type_screen_scheme_to_project(self, data=None, **request_kwargs): + """Assign issue type screen scheme to project.""" + url = self.resource_url("issuetypescreenscheme/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_issue_type_screen_scheme(self, issue_type_screen_scheme_id, data=None, **request_kwargs): + """Delete issue type screen scheme.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_issue_type_screen_scheme(self, issue_type_screen_scheme_id, data=None, **request_kwargs): + """Update issue type screen scheme.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def append_mappings_for_issue_type_screen_scheme(self, issue_type_screen_scheme_id, data=None, **request_kwargs): + """Append mappings to issue type screen scheme.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}/mapping", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def update_default_screen_scheme(self, issue_type_screen_scheme_id, data=None, **request_kwargs): + """Update issue type screen scheme default screen scheme.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}/mapping/default", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_mappings_from_issue_type_screen_scheme(self, issue_type_screen_scheme_id, data=None, **request_kwargs): + """Remove mappings from issue type screen scheme.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}/mapping/remove", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_projects_for_issue_type_screen_scheme( + self, issue_type_screen_scheme_id, start_at=None, max_results=None, query=None, data=None, **request_kwargs + ): + """Get issue type screen scheme projects.""" + url = self.resource_url( + f"issuetypescreenscheme/{issue_type_screen_scheme_id}/project", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"startAt": start_at, "maxResults": max_results, "query": query} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_auto_complete(self, data=None, **request_kwargs): + """Get field reference data (GET).""" + url = self.resource_url("jql/autocompletedata", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_auto_complete_post(self, data=None, **request_kwargs): + """Get field reference data (POST).""" + url = self.resource_url("jql/autocompletedata", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_field_auto_complete_for_query_string( + self, field_name=None, field_value=None, predicate_name=None, predicate_value=None, data=None, **request_kwargs + ): + """Get field auto complete suggestions.""" + url = self.resource_url("jql/autocompletedata/suggestions", api_root="rest/api", api_version=self.api_version) + params = { + "fieldName": field_name, + "fieldValue": field_value, + "predicateName": predicate_name, + "predicateValue": predicate_value, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_precomputations( + self, function_key=None, start_at=None, max_results=None, order_by=None, data=None, **request_kwargs + ): + """Get precomputations (apps).""" + url = self.resource_url("jql/function/computation", api_root="rest/api", api_version=self.api_version) + params = {"functionKey": function_key, "startAt": start_at, "maxResults": max_results, "orderBy": order_by} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_precomputations(self, skip_not_found_precomputations=None, data=None, **request_kwargs): + """Update precomputations (apps).""" + url = self.resource_url("jql/function/computation", api_root="rest/api", api_version=self.api_version) + params = {"skipNotFoundPrecomputations": skip_not_found_precomputations} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_precomputations_by_id(self, order_by=None, data=None, **request_kwargs): + """Get precomputations by ID (apps).""" + url = self.resource_url("jql/function/computation/search", api_root="rest/api", api_version=self.api_version) + params = {"orderBy": order_by} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def match_issues(self, data=None, **request_kwargs): + """Check issues against JQL.""" + url = self.resource_url("jql/match", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def parse_jql_queries(self, validation=None, data=None, **request_kwargs): + """Parse JQL query.""" + url = self.resource_url("jql/parse", api_root="rest/api", api_version=self.api_version) + params = {"validation": validation} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def migrate_queries(self, data=None, **request_kwargs): + """Convert user identifiers to account IDs in JQL queries.""" + url = self.resource_url("jql/pdcleaner", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def sanitise_jql_queries(self, data=None, **request_kwargs): + """Sanitize JQL queries.""" + url = self.resource_url("jql/sanitize", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_labels(self, start_at=None, max_results=None, data=None, **request_kwargs): + """Get all labels.""" + url = self.resource_url("label", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approximate_license_count(self, data=None, **request_kwargs): + """Get approximate license count.""" + url = self.resource_url("license/approximateLicenseCount", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approximate_application_license_count(self, application_key, data=None, **request_kwargs): + """Get approximate application license count.""" + url = self.resource_url( + f"license/approximateLicenseCount/product/{application_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_my_permissions( + self, + project_key=None, + project_id=None, + issue_key=None, + issue_id=None, + permissions=None, + project_uuid=None, + project_configuration_uuid=None, + comment_id=None, + data=None, + **request_kwargs, + ): + """Get my permissions.""" + url = self.resource_url("mypermissions", api_root="rest/api", api_version=self.api_version) + params = { + "projectKey": project_key, + "projectId": project_id, + "issueKey": issue_key, + "issueId": issue_id, + "permissions": permissions, + "projectUuid": project_uuid, + "projectConfigurationUuid": project_configuration_uuid, + "commentId": comment_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_preference(self, key=None, data=None, **request_kwargs): + """Delete preference.""" + url = self.resource_url("mypreferences", api_root="rest/api", api_version=self.api_version) + params = {"key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_preference(self, key=None, data=None, **request_kwargs): + """Get preference.""" + url = self.resource_url("mypreferences", api_root="rest/api", api_version=self.api_version) + params = {"key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_preference(self, key=None, data=None, **request_kwargs): + """Set preference.""" + url = self.resource_url("mypreferences", api_root="rest/api", api_version=self.api_version) + params = {"key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_locale(self, data=None, **request_kwargs): + """Get locale.""" + url = self.resource_url("mypreferences/locale", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_locale(self, data=None, **request_kwargs): + """Set locale.""" + url = self.resource_url("mypreferences/locale", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_current_user(self, expand=None, data=None, **request_kwargs): + """Get current user.""" + url = self.resource_url("myself", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_notification_schemes( + self, + start_at=None, + max_results=None, + id=None, + project_id=None, + only_default=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get notification schemes paginated.""" + url = self.resource_url("notificationscheme", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "projectId": project_id, + "onlyDefault": only_default, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_notification_scheme(self, data=None, **request_kwargs): + """Create notification scheme.""" + url = self.resource_url("notificationscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_notification_scheme_to_project_mappings( + self, start_at=None, max_results=None, notification_scheme_id=None, project_id=None, data=None, **request_kwargs + ): + """Get projects using notification schemes paginated.""" + url = self.resource_url("notificationscheme/project", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "notificationSchemeId": notification_scheme_id, + "projectId": project_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_notification_scheme(self, id, expand=None, data=None, **request_kwargs): + """Get notification scheme.""" + url = self.resource_url(f"notificationscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_notification_scheme(self, id, data=None, **request_kwargs): + """Update notification scheme.""" + url = self.resource_url(f"notificationscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def add_notifications(self, id, data=None, **request_kwargs): + """Add notifications to notification scheme.""" + url = self.resource_url( + f"notificationscheme/{id}/notification", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_notification_scheme(self, notification_scheme_id, data=None, **request_kwargs): + """Delete notification scheme.""" + url = self.resource_url( + f"notificationscheme/{notification_scheme_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def remove_notification_from_notification_scheme( + self, notification_scheme_id, notification_id, data=None, **request_kwargs + ): + """Remove notification from notification scheme.""" + url = self.resource_url( + f"notificationscheme/{notification_scheme_id}/notification/{notification_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_all_permissions(self, data=None, **request_kwargs): + """Get all permissions.""" + url = self.resource_url("permissions", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_bulk_permissions(self, data=None, **request_kwargs): + """Get bulk permissions.""" + url = self.resource_url("permissions/check", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_permitted_projects(self, data=None, **request_kwargs): + """Get permitted projects.""" + url = self.resource_url("permissions/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_permission_schemes(self, expand=None, data=None, **request_kwargs): + """Get all permission schemes.""" + url = self.resource_url("permissionscheme", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_permission_scheme(self, expand=None, data=None, **request_kwargs): + """Create permission scheme.""" + url = self.resource_url("permissionscheme", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_permission_scheme(self, scheme_id, data=None, **request_kwargs): + """Delete permission scheme.""" + url = self.resource_url(f"permissionscheme/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_permission_scheme(self, scheme_id, expand=None, data=None, **request_kwargs): + """Get permission scheme.""" + url = self.resource_url(f"permissionscheme/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_permission_scheme(self, scheme_id, expand=None, data=None, **request_kwargs): + """Update permission scheme.""" + url = self.resource_url(f"permissionscheme/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_permission_scheme_grants(self, scheme_id, expand=None, data=None, **request_kwargs): + """Get permission scheme grants.""" + url = self.resource_url( + f"permissionscheme/{scheme_id}/permission", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_permission_grant(self, scheme_id, expand=None, data=None, **request_kwargs): + """Create permission grant.""" + url = self.resource_url( + f"permissionscheme/{scheme_id}/permission", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_permission_scheme_entity(self, scheme_id, permission_id, data=None, **request_kwargs): + """Delete permission scheme grant.""" + url = self.resource_url( + f"permissionscheme/{scheme_id}/permission/{permission_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_permission_scheme_grant(self, scheme_id, permission_id, expand=None, data=None, **request_kwargs): + """Get permission scheme grant.""" + url = self.resource_url( + f"permissionscheme/{scheme_id}/permission/{permission_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_plans( + self, include_trashed=None, include_archived=None, cursor=None, max_results=None, data=None, **request_kwargs + ): + """Get plans paginated.""" + url = self.resource_url("plans/plan", api_root="rest/api", api_version=self.api_version) + params = { + "includeTrashed": include_trashed, + "includeArchived": include_archived, + "cursor": cursor, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_plan(self, use_group_id=None, data=None, **request_kwargs): + """Create plan.""" + url = self.resource_url("plans/plan", api_root="rest/api", api_version=self.api_version) + params = {"useGroupId": use_group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_plan(self, plan_id, use_group_id=None, data=None, **request_kwargs): + """Get plan.""" + url = self.resource_url(f"plans/plan/{plan_id}", api_root="rest/api", api_version=self.api_version) + params = {"useGroupId": use_group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_plan(self, plan_id, use_group_id=None, data=None, **request_kwargs): + """Update plan.""" + url = self.resource_url(f"plans/plan/{plan_id}", api_root="rest/api", api_version=self.api_version) + params = {"useGroupId": use_group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def archive_plan(self, plan_id, data=None, **request_kwargs): + """Archive plan.""" + url = self.resource_url(f"plans/plan/{plan_id}/archive", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def duplicate_plan(self, plan_id, data=None, **request_kwargs): + """Duplicate plan.""" + url = self.resource_url(f"plans/plan/{plan_id}/duplicate", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_teams(self, plan_id, cursor=None, max_results=None, data=None, **request_kwargs): + """Get teams in plan paginated.""" + url = self.resource_url(f"plans/plan/{plan_id}/team", api_root="rest/api", api_version=self.api_version) + params = {"cursor": cursor, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_atlassian_team(self, plan_id, data=None, **request_kwargs): + """Add Atlassian team to plan.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/atlassian", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_atlassian_team(self, plan_id, atlassian_team_id, data=None, **request_kwargs): + """Remove Atlassian team from plan.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/atlassian/{atlassian_team_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_atlassian_team(self, plan_id, atlassian_team_id, data=None, **request_kwargs): + """Get Atlassian team in plan.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/atlassian/{atlassian_team_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_atlassian_team(self, plan_id, atlassian_team_id, data=None, **request_kwargs): + """Update Atlassian team in plan.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/atlassian/{atlassian_team_id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def create_plan_only_team(self, plan_id, data=None, **request_kwargs): + """Create plan-only team.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/planonly", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_plan_only_team(self, plan_id, plan_only_team_id, data=None, **request_kwargs): + """Delete plan-only team.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/planonly/{plan_only_team_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_plan_only_team(self, plan_id, plan_only_team_id, data=None, **request_kwargs): + """Get plan-only team.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/planonly/{plan_only_team_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_plan_only_team(self, plan_id, plan_only_team_id, data=None, **request_kwargs): + """Update plan-only team.""" + url = self.resource_url( + f"plans/plan/{plan_id}/team/planonly/{plan_only_team_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def trash_plan(self, plan_id, data=None, **request_kwargs): + """Trash plan.""" + url = self.resource_url(f"plans/plan/{plan_id}/trash", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_priorities(self, data=None, **request_kwargs): + """Get priorities.""" + url = self.resource_url("priority", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_priority(self, data=None, **request_kwargs): + """Create priority.""" + url = self.resource_url("priority", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def set_default_priority(self, data=None, **request_kwargs): + """Set default priority.""" + url = self.resource_url("priority/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def move_priorities(self, data=None, **request_kwargs): + """Move priorities.""" + url = self.resource_url("priority/move", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def search_priorities( + self, + start_at=None, + max_results=None, + id=None, + project_id=None, + priority_name=None, + only_default=None, + expand=None, + data=None, + **request_kwargs, + ): + """Search priorities.""" + url = self.resource_url("priority/search", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "projectId": project_id, + "priorityName": priority_name, + "onlyDefault": only_default, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_priority(self, id, data=None, **request_kwargs): + """Delete priority.""" + url = self.resource_url(f"priority/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_priority(self, id, data=None, **request_kwargs): + """Get priority.""" + url = self.resource_url(f"priority/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_priority(self, id, data=None, **request_kwargs): + """Update priority.""" + url = self.resource_url(f"priority/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_priority_schemes( + self, + start_at=None, + max_results=None, + priority_id=None, + scheme_id=None, + scheme_name=None, + only_default=None, + order_by=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get priority schemes.""" + url = self.resource_url("priorityscheme", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "priorityId": priority_id, + "schemeId": scheme_id, + "schemeName": scheme_name, + "onlyDefault": only_default, + "orderBy": order_by, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_priority_scheme(self, data=None, **request_kwargs): + """Create priority scheme.""" + url = self.resource_url("priorityscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def suggested_priorities_for_mappings(self, data=None, **request_kwargs): + """Suggested priorities for mappings.""" + url = self.resource_url("priorityscheme/mappings", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_available_priorities_by_priority_scheme( + self, start_at=None, max_results=None, query=None, scheme_id=None, exclude=None, data=None, **request_kwargs + ): + """Get available priorities by priority scheme.""" + url = self.resource_url( + "priorityscheme/priorities/available", api_root="rest/api", api_version=self.api_version + ) + params = { + "startAt": start_at, + "maxResults": max_results, + "query": query, + "schemeId": scheme_id, + "exclude": exclude, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_priority_scheme(self, scheme_id, data=None, **request_kwargs): + """Delete priority scheme.""" + url = self.resource_url(f"priorityscheme/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_priority_scheme(self, scheme_id, data=None, **request_kwargs): + """Update priority scheme.""" + url = self.resource_url(f"priorityscheme/{scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_priorities_by_priority_scheme( + self, scheme_id, start_at=None, max_results=None, data=None, **request_kwargs + ): + """Get priorities by priority scheme.""" + url = self.resource_url( + f"priorityscheme/{scheme_id}/priorities", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_projects_by_priority_scheme( + self, scheme_id, start_at=None, max_results=None, project_id=None, query=None, data=None, **request_kwargs + ): + """Get projects by priority scheme.""" + url = self.resource_url( + f"priorityscheme/{scheme_id}/projects", api_root="rest/api", api_version=self.api_version + ) + params = {"startAt": start_at, "maxResults": max_results, "projectId": project_id, "query": query} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_projects(self, expand=None, recent=None, properties=None, data=None, **request_kwargs): + """Get all projects.""" + url = self.resource_url("project", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "recent": recent, "properties": properties} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_project(self, data=None, **request_kwargs): + """Create project.""" + url = self.resource_url("project", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def create_project_with_custom_template(self, data=None, **request_kwargs): + """Create custom project.""" + url = self.resource_url("project-template", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def edit_template(self, data=None, **request_kwargs): + """Edit a custom project template.""" + url = self.resource_url("project-template/edit-template", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def live_template(self, project_id=None, template_key=None, data=None, **request_kwargs): + """Gets a custom project template.""" + url = self.resource_url("project-template/live-template", api_root="rest/api", api_version=self.api_version) + params = {"projectId": project_id, "templateKey": template_key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_template(self, template_key=None, data=None, **request_kwargs): + """Deletes a custom project template.""" + url = self.resource_url("project-template/remove-template", api_root="rest/api", api_version=self.api_version) + params = {"templateKey": template_key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def save_template(self, data=None, **request_kwargs): + """Save a custom project template.""" + url = self.resource_url("project-template/save-template", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_recent(self, expand=None, properties=None, data=None, **request_kwargs): + """Get recent projects.""" + url = self.resource_url("project/recent", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "properties": properties} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def search_projects( + self, + start_at=None, + max_results=None, + order_by=None, + id=None, + keys=None, + query=None, + type_key=None, + category_id=None, + action=None, + expand=None, + status=None, + properties=None, + property_query=None, + data=None, + **request_kwargs, + ): + """Get projects paginated.""" + url = self.resource_url("project/search", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "orderBy": order_by, + "id": id, + "keys": keys, + "query": query, + "typeKey": type_key, + "categoryId": category_id, + "action": action, + "expand": expand, + "status": status, + "properties": properties, + "propertyQuery": property_query, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_project_types(self, data=None, **request_kwargs): + """Get all project types.""" + url = self.resource_url("project/type", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_accessible_project_types(self, data=None, **request_kwargs): + """Get licensed project types.""" + url = self.resource_url("project/type/accessible", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_type_by_key(self, project_type_key, data=None, **request_kwargs): + """Get project type by key.""" + url = self.resource_url(f"project/type/{project_type_key}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_accessible_project_type_by_key(self, project_type_key, data=None, **request_kwargs): + """Get accessible project type by key.""" + url = self.resource_url( + f"project/type/{project_type_key}/accessible", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_project(self, project_id_or_key, enable_undo=None, data=None, **request_kwargs): + """Delete project.""" + url = self.resource_url(f"project/{project_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = {"enableUndo": enable_undo} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project(self, project_id_or_key, expand=None, properties=None, data=None, **request_kwargs): + """Get project.""" + url = self.resource_url(f"project/{project_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand, "properties": properties} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_project(self, project_id_or_key, expand=None, data=None, **request_kwargs): + """Update project.""" + url = self.resource_url(f"project/{project_id_or_key}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def archive_project(self, project_id_or_key, data=None, **request_kwargs): + """Archive project.""" + url = self.resource_url( + f"project/{project_id_or_key}/archive", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_project_avatar(self, project_id_or_key, data=None, **request_kwargs): + """Set project avatar.""" + url = self.resource_url( + f"project/{project_id_or_key}/avatar", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_project_avatar(self, project_id_or_key, id, data=None, **request_kwargs): + """Delete project avatar.""" + url = self.resource_url( + f"project/{project_id_or_key}/avatar/{id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def create_project_avatar(self, project_id_or_key, x=None, y=None, size=None, data=None, **request_kwargs): + """Load project avatar.""" + url = self.resource_url( + f"project/{project_id_or_key}/avatar2", api_root="rest/api", api_version=self.api_version + ) + params = {"x": x, "y": y, "size": size} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_project_avatars(self, project_id_or_key, data=None, **request_kwargs): + """Get all project avatars.""" + url = self.resource_url( + f"project/{project_id_or_key}/avatars", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_classification_config(self, project_id_or_key, data=None, **request_kwargs): + """Get the classification configuration for a project.""" + url = self.resource_url( + f"project/{project_id_or_key}/classification-config", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_default_project_classification(self, project_id_or_key, data=None, **request_kwargs): + """Remove the default data classification level from a project.""" + url = self.resource_url( + f"project/{project_id_or_key}/classification-level/default", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_default_project_classification(self, project_id_or_key, data=None, **request_kwargs): + """Get the default data classification level of a project.""" + url = self.resource_url( + f"project/{project_id_or_key}/classification-level/default", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_default_project_classification(self, project_id_or_key, data=None, **request_kwargs): + """Update the default data classification level of a project.""" + url = self.resource_url( + f"project/{project_id_or_key}/classification-level/default", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_project_components_paginated( + self, + project_id_or_key, + start_at=None, + max_results=None, + order_by=None, + component_source=None, + query=None, + data=None, + **request_kwargs, + ): + """Get project components paginated.""" + url = self.resource_url( + f"project/{project_id_or_key}/component", api_root="rest/api", api_version=self.api_version + ) + params = { + "startAt": start_at, + "maxResults": max_results, + "orderBy": order_by, + "componentSource": component_source, + "query": query, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_components(self, project_id_or_key, component_source=None, data=None, **request_kwargs): + """Get project components.""" + url = self.resource_url( + f"project/{project_id_or_key}/components", api_root="rest/api", api_version=self.api_version + ) + params = {"componentSource": component_source} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_project_asynchronously(self, project_id_or_key, data=None, **request_kwargs): + """Delete project asynchronously.""" + url = self.resource_url( + f"project/{project_id_or_key}/delete", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_features_for_project(self, project_id_or_key, data=None, **request_kwargs): + """Get project features.""" + url = self.resource_url( + f"project/{project_id_or_key}/features", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def toggle_feature_for_project(self, project_id_or_key, feature_key, data=None, **request_kwargs): + """Set project feature state.""" + url = self.resource_url( + f"project/{project_id_or_key}/features/{feature_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_project_property_keys(self, project_id_or_key, data=None, **request_kwargs): + """Get project property keys.""" + url = self.resource_url( + f"project/{project_id_or_key}/properties", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_project_property(self, project_id_or_key, property_key, data=None, **request_kwargs): + """Delete project property.""" + url = self.resource_url( + f"project/{project_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project_property(self, project_id_or_key, property_key, data=None, **request_kwargs): + """Get project property.""" + url = self.resource_url( + f"project/{project_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_project_property(self, project_id_or_key, property_key, data=None, **request_kwargs): + """Set project property.""" + url = self.resource_url( + f"project/{project_id_or_key}/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def restore(self, project_id_or_key, data=None, **request_kwargs): + """Restore deleted or archived project.""" + url = self.resource_url( + f"project/{project_id_or_key}/restore", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_project_roles(self, project_id_or_key, data=None, **request_kwargs): + """Get project roles for project.""" + url = self.resource_url(f"project/{project_id_or_key}/role", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_actor(self, project_id_or_key, id, user=None, group=None, group_id=None, data=None, **request_kwargs): + """Delete actors from project role.""" + url = self.resource_url( + f"project/{project_id_or_key}/role/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"user": user, "group": group, "groupId": group_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project_role(self, project_id_or_key, id, exclude_inactive_users=None, data=None, **request_kwargs): + """Get project role for project.""" + url = self.resource_url( + f"project/{project_id_or_key}/role/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"excludeInactiveUsers": exclude_inactive_users} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_actor_users(self, project_id_or_key, id, data=None, **request_kwargs): + """Add actors to project role.""" + url = self.resource_url( + f"project/{project_id_or_key}/role/{id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def set_actors(self, project_id_or_key, id, data=None, **request_kwargs): + """Set actors for project role.""" + url = self.resource_url( + f"project/{project_id_or_key}/role/{id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_project_role_details( + self, + project_id_or_key, + current_member=None, + exclude_connect_addons=None, + exclude_other_service_roles=None, + data=None, + **request_kwargs, + ): + """Get project role details.""" + url = self.resource_url( + f"project/{project_id_or_key}/roledetails", api_root="rest/api", api_version=self.api_version + ) + params = { + "currentMember": current_member, + "excludeConnectAddons": exclude_connect_addons, + "excludeOtherServiceRoles": exclude_other_service_roles, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_statuses(self, project_id_or_key, data=None, **request_kwargs): + """Get all statuses for project.""" + url = self.resource_url( + f"project/{project_id_or_key}/statuses", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_versions_paginated( + self, + project_id_or_key, + start_at=None, + max_results=None, + order_by=None, + query=None, + status=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get project versions paginated.""" + url = self.resource_url( + f"project/{project_id_or_key}/version", api_root="rest/api", api_version=self.api_version + ) + params = { + "startAt": start_at, + "maxResults": max_results, + "orderBy": order_by, + "query": query, + "status": status, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_versions(self, project_id_or_key, expand=None, data=None, **request_kwargs): + """Get project versions.""" + url = self.resource_url( + f"project/{project_id_or_key}/versions", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_email(self, project_id, data=None, **request_kwargs): + """Get project's sender email.""" + url = self.resource_url(f"project/{project_id}/email", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_project_email(self, project_id, data=None, **request_kwargs): + """Set project's sender email.""" + url = self.resource_url(f"project/{project_id}/email", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_hierarchy(self, project_id, data=None, **request_kwargs): + """Get project issue type hierarchy.""" + url = self.resource_url(f"project/{project_id}/hierarchy", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_issue_security_scheme(self, project_key_or_id, data=None, **request_kwargs): + """Get project issue security scheme.""" + url = self.resource_url( + f"project/{project_key_or_id}/issuesecuritylevelscheme", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_notification_scheme_for_project(self, project_key_or_id, expand=None, data=None, **request_kwargs): + """Get project notification scheme.""" + url = self.resource_url( + f"project/{project_key_or_id}/notificationscheme", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_assigned_permission_scheme(self, project_key_or_id, expand=None, data=None, **request_kwargs): + """Get assigned permission scheme.""" + url = self.resource_url( + f"project/{project_key_or_id}/permissionscheme", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def assign_permission_scheme(self, project_key_or_id, expand=None, data=None, **request_kwargs): + """Assign permission scheme.""" + url = self.resource_url( + f"project/{project_key_or_id}/permissionscheme", api_root="rest/api", api_version=self.api_version + ) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_security_levels_for_project(self, project_key_or_id, data=None, **request_kwargs): + """Get project issue security levels.""" + url = self.resource_url( + f"project/{project_key_or_id}/securitylevel", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_project_categories(self, data=None, **request_kwargs): + """Get all project categories.""" + url = self.resource_url("projectCategory", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_project_category(self, data=None, **request_kwargs): + """Create project category.""" + url = self.resource_url("projectCategory", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_project_category(self, id, data=None, **request_kwargs): + """Delete project category.""" + url = self.resource_url(f"projectCategory/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project_category_by_id(self, id, data=None, **request_kwargs): + """Get project category by ID.""" + url = self.resource_url(f"projectCategory/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_project_category(self, id, data=None, **request_kwargs): + """Update project category.""" + url = self.resource_url(f"projectCategory/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_project_fields( + self, + start_at=None, + max_results=None, + project_id=None, + work_type_id=None, + field_id=None, + data=None, + **request_kwargs, + ): + """Get fields for projects.""" + url = self.resource_url("projects/fields", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "projectId": project_id, + "workTypeId": work_type_id, + "fieldId": field_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def validate_project_key(self, key=None, data=None, **request_kwargs): + """Validate project key.""" + url = self.resource_url("projectvalidate/key", api_root="rest/api", api_version=self.api_version) + params = {"key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_valid_project_key(self, key=None, data=None, **request_kwargs): + """Get valid project key.""" + url = self.resource_url("projectvalidate/validProjectKey", api_root="rest/api", api_version=self.api_version) + params = {"key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_valid_project_name(self, name=None, data=None, **request_kwargs): + """Get valid project name.""" + url = self.resource_url("projectvalidate/validProjectName", api_root="rest/api", api_version=self.api_version) + params = {"name": name} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def redact(self, data=None, **request_kwargs): + """Redact.""" + url = self.resource_url("redact", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_redaction_status(self, job_id, data=None, **request_kwargs): + """Get redaction status.""" + url = self.resource_url(f"redact/status/{job_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_resolutions(self, data=None, **request_kwargs): + """Get resolutions.""" + url = self.resource_url("resolution", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_resolution(self, data=None, **request_kwargs): + """Create resolution.""" + url = self.resource_url("resolution", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def set_default_resolution(self, data=None, **request_kwargs): + """Set default resolution.""" + url = self.resource_url("resolution/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def move_resolutions(self, data=None, **request_kwargs): + """Move resolutions.""" + url = self.resource_url("resolution/move", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def search_resolutions( + self, start_at=None, max_results=None, id=None, only_default=None, data=None, **request_kwargs + ): + """Search resolutions.""" + url = self.resource_url("resolution/search", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "id": id, "onlyDefault": only_default} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_resolution(self, id, replace_with=None, data=None, **request_kwargs): + """Delete resolution.""" + url = self.resource_url(f"resolution/{id}", api_root="rest/api", api_version=self.api_version) + params = {"replaceWith": replace_with} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_resolution(self, id, data=None, **request_kwargs): + """Get resolution.""" + url = self.resource_url(f"resolution/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_resolution(self, id, data=None, **request_kwargs): + """Update resolution.""" + url = self.resource_url(f"resolution/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_project_roles(self, data=None, **request_kwargs): + """Get all project roles.""" + url = self.resource_url("role", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_project_role(self, data=None, **request_kwargs): + """Create project role.""" + url = self.resource_url("role", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_project_role(self, id, swap=None, data=None, **request_kwargs): + """Delete project role.""" + url = self.resource_url(f"role/{id}", api_root="rest/api", api_version=self.api_version) + params = {"swap": swap} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project_role_by_id(self, id, data=None, **request_kwargs): + """Get project role by ID.""" + url = self.resource_url(f"role/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def partial_update_project_role(self, id, data=None, **request_kwargs): + """Partial update project role.""" + url = self.resource_url(f"role/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def fully_update_project_role(self, id, data=None, **request_kwargs): + """Fully update project role.""" + url = self.resource_url(f"role/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_project_role_actors_from_role( + self, id, user=None, group_id=None, group=None, data=None, **request_kwargs + ): + """Delete default actors from project role.""" + url = self.resource_url(f"role/{id}/actors", api_root="rest/api", api_version=self.api_version) + params = {"user": user, "groupId": group_id, "group": group} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_project_role_actors_for_role(self, id, data=None, **request_kwargs): + """Get default actors for project role.""" + url = self.resource_url(f"role/{id}/actors", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_project_role_actors_to_role(self, id, data=None, **request_kwargs): + """Add default actors to project role.""" + url = self.resource_url(f"role/{id}/actors", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_screens( + self, + start_at=None, + max_results=None, + id=None, + query_string=None, + scope=None, + order_by=None, + data=None, + **request_kwargs, + ): + """Get screens.""" + url = self.resource_url("screens", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "queryString": query_string, + "scope": scope, + "orderBy": order_by, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_screen(self, data=None, **request_kwargs): + """Create screen.""" + url = self.resource_url("screens", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def add_field_to_default_screen(self, field_id, data=None, **request_kwargs): + """Add field to default screen.""" + url = self.resource_url(f"screens/addToDefault/{field_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_bulk_screen_tabs( + self, screen_id=None, tab_id=None, start_at=None, max_result=None, data=None, **request_kwargs + ): + """Get bulk screen tabs.""" + url = self.resource_url("screens/tabs", api_root="rest/api", api_version=self.api_version) + params = {"screenId": screen_id, "tabId": tab_id, "startAt": start_at, "maxResult": max_result} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_screen(self, screen_id, data=None, **request_kwargs): + """Delete screen.""" + url = self.resource_url(f"screens/{screen_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_screen(self, screen_id, data=None, **request_kwargs): + """Update screen.""" + url = self.resource_url(f"screens/{screen_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_available_screen_fields(self, screen_id, data=None, **request_kwargs): + """Get available screen fields.""" + url = self.resource_url( + f"screens/{screen_id}/availableFields", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_screen_tabs(self, screen_id, project_key=None, data=None, **request_kwargs): + """Get all screen tabs.""" + url = self.resource_url(f"screens/{screen_id}/tabs", api_root="rest/api", api_version=self.api_version) + params = {"projectKey": project_key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_screen_tab(self, screen_id, data=None, **request_kwargs): + """Create screen tab.""" + url = self.resource_url(f"screens/{screen_id}/tabs", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_screen_tab(self, screen_id, tab_id, data=None, **request_kwargs): + """Delete screen tab.""" + url = self.resource_url(f"screens/{screen_id}/tabs/{tab_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def rename_screen_tab(self, screen_id, tab_id, data=None, **request_kwargs): + """Update screen tab.""" + url = self.resource_url(f"screens/{screen_id}/tabs/{tab_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_screen_tab_fields(self, screen_id, tab_id, project_key=None, data=None, **request_kwargs): + """Get all screen tab fields.""" + url = self.resource_url( + f"screens/{screen_id}/tabs/{tab_id}/fields", api_root="rest/api", api_version=self.api_version + ) + params = {"projectKey": project_key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_screen_tab_field(self, screen_id, tab_id, skip_field_association=None, data=None, **request_kwargs): + """Add screen tab field.""" + url = self.resource_url( + f"screens/{screen_id}/tabs/{tab_id}/fields", api_root="rest/api", api_version=self.api_version + ) + params = {"skipFieldAssociation": skip_field_association} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_screen_tab_field(self, screen_id, tab_id, id, data=None, **request_kwargs): + """Remove screen tab field.""" + url = self.resource_url( + f"screens/{screen_id}/tabs/{tab_id}/fields/{id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def move_screen_tab_field(self, screen_id, tab_id, id, data=None, **request_kwargs): + """Move screen tab field.""" + url = self.resource_url( + f"screens/{screen_id}/tabs/{tab_id}/fields/{id}/move", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def move_screen_tab(self, screen_id, tab_id, pos, data=None, **request_kwargs): + """Move screen tab.""" + url = self.resource_url( + f"screens/{screen_id}/tabs/{tab_id}/move/{pos}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_screen_schemes( + self, + start_at=None, + max_results=None, + id=None, + expand=None, + query_string=None, + order_by=None, + data=None, + **request_kwargs, + ): + """Get screen schemes.""" + url = self.resource_url("screenscheme", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "id": id, + "expand": expand, + "queryString": query_string, + "orderBy": order_by, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_screen_scheme(self, data=None, **request_kwargs): + """Create screen scheme.""" + url = self.resource_url("screenscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_screen_scheme(self, screen_scheme_id, data=None, **request_kwargs): + """Delete screen scheme.""" + url = self.resource_url(f"screenscheme/{screen_scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_screen_scheme(self, screen_scheme_id, data=None, **request_kwargs): + """Update screen scheme.""" + url = self.resource_url(f"screenscheme/{screen_scheme_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def search_for_issues_using_jql( + self, + jql=None, + start_at=None, + max_results=None, + validate_query=None, + fields=None, + expand=None, + properties=None, + fields_by_keys=None, + fail_fast=None, + data=None, + **request_kwargs, + ): + """Currently being removed. Search for issues using JQL (GET).""" + url = self.resource_url("search", api_root="rest/api", api_version=self.api_version) + params = { + "jql": jql, + "startAt": start_at, + "maxResults": max_results, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + "properties": properties, + "fieldsByKeys": fields_by_keys, + "failFast": fail_fast, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def search_for_issues_using_jql_post(self, data=None, **request_kwargs): + """Currently being removed. Search for issues using JQL (POST).""" + url = self.resource_url("search", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def count_issues(self, data=None, **request_kwargs): + """Count issues using JQL.""" + url = self.resource_url("search/approximate-count", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def search_and_reconsile_issues_using_jql( + self, + jql=None, + next_page_token=None, + max_results=None, + fields=None, + expand=None, + properties=None, + fields_by_keys=None, + fail_fast=None, + reconcile_issues=None, + data=None, + **request_kwargs, + ): + """Search for issues using JQL enhanced search (GET).""" + url = self.resource_url("search/jql", api_root="rest/api", api_version=self.api_version) + params = { + "jql": jql, + "nextPageToken": next_page_token, + "maxResults": max_results, + "fields": fields, + "expand": expand, + "properties": properties, + "fieldsByKeys": fields_by_keys, + "failFast": fail_fast, + "reconcileIssues": reconcile_issues, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def search_and_reconsile_issues_using_jql_post(self, data=None, **request_kwargs): + """Search for issues using JQL enhanced search (POST).""" + url = self.resource_url("search/jql", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issue_security_level(self, id, data=None, **request_kwargs): + """Get issue security level.""" + url = self.resource_url(f"securitylevel/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_server_info(self, data=None, **request_kwargs): + """Get Jira instance info.""" + url = self.resource_url("serverInfo", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_navigator_default_columns(self, data=None, **request_kwargs): + """Get issue navigator default columns.""" + url = self.resource_url("settings/columns", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_issue_navigator_default_columns(self, data=None, **request_kwargs): + """Set issue navigator default columns.""" + url = self.resource_url("settings/columns", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_statuses(self, data=None, **request_kwargs): + """Get all statuses.""" + url = self.resource_url("status", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_status(self, id_or_name, data=None, **request_kwargs): + """Get status.""" + url = self.resource_url(f"status/{id_or_name}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_status_categories(self, data=None, **request_kwargs): + """Get all status categories.""" + url = self.resource_url("statuscategory", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_status_category(self, id_or_key, data=None, **request_kwargs): + """Get status category.""" + url = self.resource_url(f"statuscategory/{id_or_key}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_statuses_by_id(self, id=None, data=None, **request_kwargs): + """Bulk delete Statuses.""" + url = self.resource_url("statuses", api_root="rest/api", api_version=self.api_version) + params = {"id": id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_statuses_by_id(self, id=None, data=None, **request_kwargs): + """Bulk get statuses.""" + url = self.resource_url("statuses", api_root="rest/api", api_version=self.api_version) + params = {"id": id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_statuses(self, data=None, **request_kwargs): + """Bulk create statuses.""" + url = self.resource_url("statuses", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_statuses(self, data=None, **request_kwargs): + """Bulk update statuses.""" + url = self.resource_url("statuses", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_statuses_by_name(self, name=None, project_id=None, data=None, **request_kwargs): + """Bulk get statuses by name.""" + url = self.resource_url("statuses/byNames", api_root="rest/api", api_version=self.api_version) + params = {"name": name, "projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def search( + self, + project_id=None, + start_at=None, + max_results=None, + search_string=None, + status_category=None, + include_global_statuses=None, + data=None, + **request_kwargs, + ): + """Search statuses paginated.""" + url = self.resource_url("statuses/search", api_root="rest/api", api_version=self.api_version) + params = { + "projectId": project_id, + "startAt": start_at, + "maxResults": max_results, + "searchString": search_string, + "statusCategory": status_category, + "includeGlobalStatuses": include_global_statuses, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_issue_type_usages_for_status( + self, status_id, project_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get issue type usages by status and project.""" + url = self.resource_url( + f"statuses/{status_id}/project/{project_id}/issueTypeUsages", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_usages_for_status( + self, status_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get project usages by status.""" + url = self.resource_url( + f"statuses/{status_id}/projectUsages", api_root="rest/api", api_version=self.api_version + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_workflow_usages_for_status( + self, status_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get workflow usages by status.""" + url = self.resource_url( + f"statuses/{status_id}/workflowUsages", api_root="rest/api", api_version=self.api_version + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_task(self, task_id, data=None, **request_kwargs): + """Get task.""" + url = self.resource_url(f"task/{task_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def cancel_task(self, task_id, data=None, **request_kwargs): + """Cancel task.""" + url = self.resource_url(f"task/{task_id}/cancel", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_ui_modifications(self, start_at=None, max_results=None, expand=None, data=None, **request_kwargs): + """Get UI modifications.""" + url = self.resource_url("uiModifications", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_ui_modification(self, data=None, **request_kwargs): + """Create UI modification.""" + url = self.resource_url("uiModifications", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_ui_modification(self, ui_modification_id, data=None, **request_kwargs): + """Delete UI modification.""" + url = self.resource_url( + f"uiModifications/{ui_modification_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def update_ui_modification(self, ui_modification_id, data=None, **request_kwargs): + """Update UI modification.""" + url = self.resource_url( + f"uiModifications/{ui_modification_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_avatars(self, type, entity_id, data=None, **request_kwargs): + """Get avatars.""" + url = self.resource_url( + f"universal_avatar/type/{type}/owner/{entity_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def store_avatar(self, type, entity_id, x=None, y=None, size=None, data=None, **request_kwargs): + """Load avatar.""" + url = self.resource_url( + f"universal_avatar/type/{type}/owner/{entity_id}", api_root="rest/api", api_version=self.api_version + ) + params = {"x": x, "y": y, "size": size} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_avatar(self, type, owning_object_id, id, data=None, **request_kwargs): + """Delete avatar.""" + url = self.resource_url( + f"universal_avatar/type/{type}/owner/{owning_object_id}/avatar/{id}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_avatar_image_by_type(self, type, size=None, format=None, data=None, **request_kwargs): + """Get avatar image by type.""" + url = self.resource_url(f"universal_avatar/view/type/{type}", api_root="rest/api", api_version=self.api_version) + params = {"size": size, "format": format} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_avatar_image_by_id(self, type, id, size=None, format=None, data=None, **request_kwargs): + """Get avatar image by ID.""" + url = self.resource_url( + f"universal_avatar/view/type/{type}/avatar/{id}", api_root="rest/api", api_version=self.api_version + ) + params = {"size": size, "format": format} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_avatar_image_by_owner(self, type, entity_id, size=None, format=None, data=None, **request_kwargs): + """Get avatar image by owner.""" + url = self.resource_url( + f"universal_avatar/view/type/{type}/owner/{entity_id}", api_root="rest/api", api_version=self.api_version + ) + params = {"size": size, "format": format} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_user(self, account_id=None, username=None, key=None, data=None, **request_kwargs): + """Delete user.""" + url = self.resource_url("user", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "username": username, "key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_user(self, account_id=None, username=None, key=None, expand=None, data=None, **request_kwargs): + """Get user.""" + url = self.resource_url("user", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "username": username, "key": key, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_user(self, data=None, **request_kwargs): + """Create user.""" + url = self.resource_url("user", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def find_bulk_assignable_users( + self, + query=None, + username=None, + account_id=None, + project_keys=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Find users assignable to projects.""" + url = self.resource_url("user/assignable/multiProjectSearch", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "username": username, + "accountId": account_id, + "projectKeys": project_keys, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_assignable_users( + self, + query=None, + session_id=None, + username=None, + account_id=None, + project=None, + issue_key=None, + issue_id=None, + start_at=None, + max_results=None, + action_descriptor_id=None, + recommend=None, + account_type=None, + app_type=None, + data=None, + **request_kwargs, + ): + """Find users assignable to issues.""" + url = self.resource_url("user/assignable/search", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "sessionId": session_id, + "username": username, + "accountId": account_id, + "project": project, + "issueKey": issue_key, + "issueId": issue_id, + "startAt": start_at, + "maxResults": max_results, + "actionDescriptorId": action_descriptor_id, + "recommend": recommend, + "accountType": account_type, + "appType": app_type, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def bulk_get_users( + self, start_at=None, max_results=None, username=None, key=None, account_id=None, data=None, **request_kwargs + ): + """Bulk get users.""" + url = self.resource_url("user/bulk", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "username": username, + "key": key, + "accountId": account_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def bulk_get_users_migration( + self, start_at=None, max_results=None, username=None, key=None, data=None, **request_kwargs + ): + """Get account IDs for users.""" + url = self.resource_url("user/bulk/migration", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "username": username, "key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def reset_user_columns(self, account_id=None, username=None, data=None, **request_kwargs): + """Reset user default columns.""" + url = self.resource_url("user/columns", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_user_default_columns(self, account_id=None, username=None, data=None, **request_kwargs): + """Get user default columns.""" + url = self.resource_url("user/columns", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_user_columns(self, account_id=None, data=None, **request_kwargs): + """Set user default columns.""" + url = self.resource_url("user/columns", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_user_email(self, account_id=None, data=None, **request_kwargs): + """Get user email.""" + url = self.resource_url("user/email", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_user_email_bulk(self, account_id=None, data=None, **request_kwargs): + """Get user email bulk.""" + url = self.resource_url("user/email/bulk", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_user_groups(self, account_id=None, username=None, key=None, data=None, **request_kwargs): + """Get user groups.""" + url = self.resource_url("user/groups", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "username": username, "key": key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_users_with_all_permissions( + self, + query=None, + username=None, + account_id=None, + permissions=None, + issue_key=None, + project_key=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Find users with permissions.""" + url = self.resource_url("user/permission/search", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "username": username, + "accountId": account_id, + "permissions": permissions, + "issueKey": issue_key, + "projectKey": project_key, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_users_for_picker( + self, + query=None, + max_results=None, + show_avatar=None, + exclude=None, + exclude_account_ids=None, + avatar_size=None, + exclude_connect_users=None, + data=None, + **request_kwargs, + ): + """Find users for picker.""" + url = self.resource_url("user/picker", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "maxResults": max_results, + "showAvatar": show_avatar, + "exclude": exclude, + "excludeAccountIds": exclude_account_ids, + "avatarSize": avatar_size, + "excludeConnectUsers": exclude_connect_users, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_user_property_keys(self, account_id=None, user_key=None, username=None, data=None, **request_kwargs): + """Get user property keys.""" + url = self.resource_url("user/properties", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "userKey": user_key, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_user_property( + self, property_key, account_id=None, user_key=None, username=None, data=None, **request_kwargs + ): + """Delete user property.""" + url = self.resource_url(f"user/properties/{property_key}", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "userKey": user_key, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_user_property( + self, property_key, account_id=None, user_key=None, username=None, data=None, **request_kwargs + ): + """Get user property.""" + url = self.resource_url(f"user/properties/{property_key}", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "userKey": user_key, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_user_property( + self, property_key, account_id=None, user_key=None, username=None, data=None, **request_kwargs + ): + """Set user property.""" + url = self.resource_url(f"user/properties/{property_key}", api_root="rest/api", api_version=self.api_version) + params = {"accountId": account_id, "userKey": user_key, "username": username} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def find_users( + self, + query=None, + username=None, + account_id=None, + start_at=None, + max_results=None, + property=None, + data=None, + **request_kwargs, + ): + """Find users.""" + url = self.resource_url("user/search", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "username": username, + "accountId": account_id, + "startAt": start_at, + "maxResults": max_results, + "property": property, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_users_by_query(self, query=None, start_at=None, max_results=None, data=None, **request_kwargs): + """Find users by query.""" + url = self.resource_url("user/search/query", api_root="rest/api", api_version=self.api_version) + params = {"query": query, "startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_user_keys_by_query(self, query=None, start_at=None, max_result=None, data=None, **request_kwargs): + """Find user keys by query.""" + url = self.resource_url("user/search/query/key", api_root="rest/api", api_version=self.api_version) + params = {"query": query, "startAt": start_at, "maxResult": max_result} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def find_users_with_browse_permission( + self, + query=None, + username=None, + account_id=None, + issue_key=None, + project_key=None, + start_at=None, + max_results=None, + data=None, + **request_kwargs, + ): + """Find users with browse permission.""" + url = self.resource_url("user/viewissue/search", api_root="rest/api", api_version=self.api_version) + params = { + "query": query, + "username": username, + "accountId": account_id, + "issueKey": issue_key, + "projectKey": project_key, + "startAt": start_at, + "maxResults": max_results, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_users_default(self, start_at=None, max_results=None, expand=None, data=None, **request_kwargs): + """Get all users default.""" + url = self.resource_url("users", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_users(self, start_at=None, max_results=None, expand=None, data=None, **request_kwargs): + """Get all users.""" + url = self.resource_url("users/search", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_version(self, data=None, **request_kwargs): + """Create version.""" + url = self.resource_url("version", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_version(self, id, move_fix_issues_to=None, move_affected_issues_to=None, data=None, **request_kwargs): + """Delete version.""" + url = self.resource_url(f"version/{id}", api_root="rest/api", api_version=self.api_version) + params = {"moveFixIssuesTo": move_fix_issues_to, "moveAffectedIssuesTo": move_affected_issues_to} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_version(self, id, expand=None, data=None, **request_kwargs): + """Get version.""" + url = self.resource_url(f"version/{id}", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_version(self, id, data=None, **request_kwargs): + """Update version.""" + url = self.resource_url(f"version/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def merge_versions(self, id, move_issues_to, data=None, **request_kwargs): + """Merge versions.""" + url = self.resource_url( + f"version/{id}/mergeto/{move_issues_to}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def move_version(self, id, data=None, **request_kwargs): + """Move version.""" + url = self.resource_url(f"version/{id}/move", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_version_related_issues(self, id, data=None, **request_kwargs): + """Get version's related issues count.""" + url = self.resource_url(f"version/{id}/relatedIssueCounts", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_related_work(self, id, data=None, **request_kwargs): + """Get related work.""" + url = self.resource_url(f"version/{id}/relatedwork", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_related_work(self, id, data=None, **request_kwargs): + """Create related work.""" + url = self.resource_url(f"version/{id}/relatedwork", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_related_work(self, id, data=None, **request_kwargs): + """Update related work.""" + url = self.resource_url(f"version/{id}/relatedwork", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_and_replace_version(self, id, data=None, **request_kwargs): + """Delete and replace version.""" + url = self.resource_url(f"version/{id}/removeAndSwap", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_version_unresolved_issues(self, id, data=None, **request_kwargs): + """Get version's unresolved issues count.""" + url = self.resource_url(f"version/{id}/unresolvedIssueCount", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_related_work(self, version_id, related_work_id, data=None, **request_kwargs): + """Delete related work.""" + url = self.resource_url( + f"version/{version_id}/relatedwork/{related_work_id}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def delete_webhook_by_id(self, data=None, **request_kwargs): + """Delete webhooks by ID.""" + url = self.resource_url("webhook", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_dynamic_webhooks_for_app(self, start_at=None, max_results=None, data=None, **request_kwargs): + """Get dynamic webhooks for app.""" + url = self.resource_url("webhook", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def register_dynamic_webhooks(self, data=None, **request_kwargs): + """Register dynamic webhooks.""" + url = self.resource_url("webhook", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_failed_webhooks(self, max_results=None, after=None, data=None, **request_kwargs): + """Get failed webhooks.""" + url = self.resource_url("webhook/failed", api_root="rest/api", api_version=self.api_version) + params = {"maxResults": max_results, "after": after} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def refresh_webhooks(self, data=None, **request_kwargs): + """Extend webhook life.""" + url = self.resource_url("webhook/refresh", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def read_workflow_from_history(self, data=None, **request_kwargs): + """Read workflow version from history.""" + url = self.resource_url("workflow/history", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def list_workflow_history(self, expand=None, data=None, **request_kwargs): + """List workflow history entries.""" + url = self.resource_url("workflow/history/list", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_workflow_transition_rule_configurations( + self, + start_at=None, + max_results=None, + types=None, + keys=None, + workflow_names=None, + with_tags=None, + draft=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get workflow transition rule configurations.""" + url = self.resource_url("workflow/rule/config", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "types": types, + "keys": keys, + "workflowNames": workflow_names, + "withTags": with_tags, + "draft": draft, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_workflow_transition_rule_configurations(self, data=None, **request_kwargs): + """Update workflow transition rule configurations.""" + url = self.resource_url("workflow/rule/config", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_workflow_transition_rule_configurations(self, data=None, **request_kwargs): + """Delete workflow transition rule configurations.""" + url = self.resource_url("workflow/rule/config/delete", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_workflows_paginated( + self, + start_at=None, + max_results=None, + workflow_name=None, + expand=None, + query_string=None, + order_by=None, + is_active=None, + data=None, + **request_kwargs, + ): + """Get workflows paginated.""" + url = self.resource_url("workflow/search", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "workflowName": workflow_name, + "expand": expand, + "queryString": query_string, + "orderBy": order_by, + "isActive": is_active, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_inactive_workflow(self, entity_id, data=None, **request_kwargs): + """Delete inactive workflow.""" + url = self.resource_url(f"workflow/{entity_id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow_project_issue_type_usages( + self, workflow_id, project_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get issue types in a project that are using a given workflow.""" + url = self.resource_url( + f"workflow/{workflow_id}/project/{project_id}/issueTypeUsages", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_project_usages_for_workflow( + self, workflow_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get projects using a given workflow.""" + url = self.resource_url( + f"workflow/{workflow_id}/projectUsages", api_root="rest/api", api_version=self.api_version + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme_usages_for_workflow( + self, workflow_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get workflow schemes which are using a given workflow.""" + url = self.resource_url( + f"workflow/{workflow_id}/workflowSchemes", api_root="rest/api", api_version=self.api_version + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def read_workflows(self, data=None, **request_kwargs): + """Bulk get workflows.""" + url = self.resource_url("workflows", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def workflow_capabilities(self, workflow_id=None, project_id=None, issue_type_id=None, data=None, **request_kwargs): + """Get available workflow capabilities.""" + url = self.resource_url("workflows/capabilities", api_root="rest/api", api_version=self.api_version) + params = {"workflowId": workflow_id, "projectId": project_id, "issueTypeId": issue_type_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_workflows(self, data=None, **request_kwargs): + """Bulk create workflows.""" + url = self.resource_url("workflows/create", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def validate_create_workflows(self, data=None, **request_kwargs): + """Validate create workflows.""" + url = self.resource_url("workflows/create/validation", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_default_editor(self, data=None, **request_kwargs): + """Get the user's default workflow editor.""" + url = self.resource_url("workflows/defaultEditor", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def read_workflow_previews(self, data=None, **request_kwargs): + """Preview workflow.""" + url = self.resource_url("workflows/preview", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def search_workflows( + self, + start_at=None, + max_results=None, + expand=None, + query_string=None, + order_by=None, + scope=None, + is_active=None, + project_id=None, + data=None, + **request_kwargs, + ): + """Search workflows.""" + url = self.resource_url("workflows/search", api_root="rest/api", api_version=self.api_version) + params = { + "startAt": start_at, + "maxResults": max_results, + "expand": expand, + "queryString": query_string, + "orderBy": order_by, + "scope": scope, + "isActive": is_active, + "projectId": project_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_workflows(self, data=None, **request_kwargs): + """Bulk update workflows.""" + url = self.resource_url("workflows/update", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def validate_update_workflows(self, data=None, **request_kwargs): + """Validate update workflows.""" + url = self.resource_url("workflows/update/validation", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_workflow_schemes(self, start_at=None, max_results=None, data=None, **request_kwargs): + """Get all workflow schemes.""" + url = self.resource_url("workflowscheme", api_root="rest/api", api_version=self.api_version) + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_workflow_scheme(self, data=None, **request_kwargs): + """Create workflow scheme.""" + url = self.resource_url("workflowscheme", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme_project_associations(self, project_id=None, data=None, **request_kwargs): + """Get workflow scheme project associations.""" + url = self.resource_url("workflowscheme/project", api_root="rest/api", api_version=self.api_version) + params = {"projectId": project_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def assign_scheme_to_project(self, data=None, **request_kwargs): + """Assign workflow scheme to project.""" + url = self.resource_url("workflowscheme/project", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def switch_workflow_scheme_for_project(self, data=None, **request_kwargs): + """Switch workflow scheme for project.""" + url = self.resource_url("workflowscheme/project/switch", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def read_workflow_schemes(self, data=None, **request_kwargs): + """Bulk get workflow schemes.""" + url = self.resource_url("workflowscheme/read", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_schemes(self, data=None, **request_kwargs): + """Update workflow scheme.""" + url = self.resource_url("workflowscheme/update", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_required_workflow_scheme_mappings(self, data=None, **request_kwargs): + """Get required status mappings for workflow scheme update.""" + url = self.resource_url("workflowscheme/update/mappings", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_workflow_scheme(self, id, data=None, **request_kwargs): + """Delete workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme(self, id, return_draft_if_exists=None, data=None, **request_kwargs): + """Get workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = {"returnDraftIfExists": return_draft_if_exists} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_workflow_scheme(self, id, data=None, **request_kwargs): + """Classic update workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def create_workflow_scheme_draft_from_parent(self, id, data=None, **request_kwargs): + """Create draft workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/createdraft", api_root="rest/api", api_version=self.api_version) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_default_workflow(self, id, update_draft_if_needed=None, data=None, **request_kwargs): + """Delete default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/default", api_root="rest/api", api_version=self.api_version) + params = {"updateDraftIfNeeded": update_draft_if_needed} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_default_workflow(self, id, return_draft_if_exists=None, data=None, **request_kwargs): + """Get default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/default", api_root="rest/api", api_version=self.api_version) + params = {"returnDraftIfExists": return_draft_if_exists} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_default_workflow(self, id, data=None, **request_kwargs): + """Update default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_workflow_scheme_draft(self, id, data=None, **request_kwargs): + """Delete draft workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/draft", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme_draft(self, id, data=None, **request_kwargs): + """Get draft workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/draft", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_workflow_scheme_draft(self, id, data=None, **request_kwargs): + """Update draft workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/draft", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_draft_default_workflow(self, id, data=None, **request_kwargs): + """Delete draft default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/draft/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_draft_default_workflow(self, id, data=None, **request_kwargs): + """Get draft default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/draft/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_draft_default_workflow(self, id, data=None, **request_kwargs): + """Update draft default workflow.""" + url = self.resource_url(f"workflowscheme/{id}/draft/default", api_root="rest/api", api_version=self.api_version) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_workflow_scheme_draft_issue_type(self, id, issue_type, data=None, **request_kwargs): + """Delete workflow for issue type in draft workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme_draft_issue_type(self, id, issue_type, data=None, **request_kwargs): + """Get workflow for issue type in draft workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_workflow_scheme_draft_issue_type(self, id, issue_type, data=None, **request_kwargs): + """Set workflow for issue type in draft workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def publish_draft_workflow_scheme(self, id, validate_only=None, data=None, **request_kwargs): + """Publish draft workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/draft/publish", api_root="rest/api", api_version=self.api_version) + params = {"validateOnly": validate_only} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_draft_workflow_mapping(self, id, workflow_name=None, data=None, **request_kwargs): + """Delete issue types for workflow in draft workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/workflow", api_root="rest/api", api_version=self.api_version + ) + params = {"workflowName": workflow_name} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_draft_workflow(self, id, workflow_name=None, data=None, **request_kwargs): + """Get issue types for workflows in draft workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/workflow", api_root="rest/api", api_version=self.api_version + ) + params = {"workflowName": workflow_name} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_draft_workflow_mapping(self, id, workflow_name=None, data=None, **request_kwargs): + """Set issue types for workflow in workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/draft/workflow", api_root="rest/api", api_version=self.api_version + ) + params = {"workflowName": workflow_name} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_workflow_scheme_issue_type( + self, id, issue_type, update_draft_if_needed=None, data=None, **request_kwargs + ): + """Delete workflow for issue type in workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = {"updateDraftIfNeeded": update_draft_if_needed} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow_scheme_issue_type(self, id, issue_type, return_draft_if_exists=None, data=None, **request_kwargs): + """Get workflow for issue type in workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = {"returnDraftIfExists": return_draft_if_exists} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_workflow_scheme_issue_type(self, id, issue_type, data=None, **request_kwargs): + """Set workflow for issue type in workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{id}/issuetype/{issue_type}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def delete_workflow_mapping(self, id, workflow_name=None, update_draft_if_needed=None, data=None, **request_kwargs): + """Delete issue types for workflow in workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/workflow", api_root="rest/api", api_version=self.api_version) + params = {"workflowName": workflow_name, "updateDraftIfNeeded": update_draft_if_needed} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workflow(self, id, workflow_name=None, return_draft_if_exists=None, data=None, **request_kwargs): + """Get issue types for workflows in workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/workflow", api_root="rest/api", api_version=self.api_version) + params = {"workflowName": workflow_name, "returnDraftIfExists": return_draft_if_exists} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def update_workflow_mapping(self, id, workflow_name=None, data=None, **request_kwargs): + """Set issue types for workflow in workflow scheme.""" + url = self.resource_url(f"workflowscheme/{id}/workflow", api_root="rest/api", api_version=self.api_version) + params = {"workflowName": workflow_name} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_project_usages_for_workflow_scheme( + self, workflow_scheme_id, next_page_token=None, max_results=None, data=None, **request_kwargs + ): + """Get projects which are using a given workflow scheme.""" + url = self.resource_url( + f"workflowscheme/{workflow_scheme_id}/projectUsages", api_root="rest/api", api_version=self.api_version + ) + params = {"nextPageToken": next_page_token, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_ids_of_worklogs_deleted_since(self, since=None, data=None, **request_kwargs): + """Get IDs of deleted worklogs.""" + url = self.resource_url("worklog/deleted", api_root="rest/api", api_version=self.api_version) + params = {"since": since} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_worklogs_for_ids(self, expand=None, data=None, **request_kwargs): + """Get worklogs.""" + url = self.resource_url("worklog/list", api_root="rest/api", api_version=self.api_version) + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_ids_of_worklogs_modified_since(self, since=None, expand=None, data=None, **request_kwargs): + """Get IDs of updated worklogs.""" + url = self.resource_url("worklog/updated", api_root="rest/api", api_version=self.api_version) + params = {"since": since, "expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def addon_properties_resource_get_addon_properties_get(self, addon_key, data=None, **request_kwargs): + """Get app properties.""" + url = self.resource_url( + f"rest/atlassian-connect/1/addons/{addon_key}/properties", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def addon_properties_resource_delete_addon_property_delete( + self, addon_key, property_key, data=None, **request_kwargs + ): + """Delete app property.""" + url = self.resource_url( + f"rest/atlassian-connect/1/addons/{addon_key}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def addon_properties_resource_get_addon_property_get(self, addon_key, property_key, data=None, **request_kwargs): + """Get app property.""" + url = self.resource_url( + f"rest/atlassian-connect/1/addons/{addon_key}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def addon_properties_resource_put_addon_property_put(self, addon_key, property_key, data=None, **request_kwargs): + """Set app property.""" + url = self.resource_url( + f"rest/atlassian-connect/1/addons/{addon_key}/properties/{property_key}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def dynamic_modules_resource_remove_modules_delete(self, module_key=None, data=None, **request_kwargs): + """Remove modules.""" + url = self.resource_url( + "rest/atlassian-connect/1/app/module/dynamic", api_root="rest/api", api_version=self.api_version + ) + params = {"moduleKey": module_key} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def dynamic_modules_resource_get_modules_get(self, data=None, **request_kwargs): + """Get modules.""" + url = self.resource_url( + "rest/atlassian-connect/1/app/module/dynamic", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def dynamic_modules_resource_register_modules_post(self, data=None, **request_kwargs): + """Register modules.""" + url = self.resource_url( + "rest/atlassian-connect/1/app/module/dynamic", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def app_issue_field_value_update_resource_update_issue_fields_put(self, data=None, **request_kwargs): + """Bulk update custom field value.""" + url = self.resource_url( + "rest/atlassian-connect/1/migration/field", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def migration_resource_update_entity_properties_value_put(self, entity_type, data=None, **request_kwargs): + """Bulk update entity properties.""" + url = self.resource_url( + f"rest/atlassian-connect/1/migration/properties/{entity_type}", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def migration_resource_workflow_rule_search_post(self, data=None, **request_kwargs): + """Get workflow transition rule configurations.""" + url = self.resource_url( + "rest/atlassian-connect/1/migration/workflow/rule/search", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def connect_to_forge_migration_fetch_task_resource_fetch_migration_task_get( + self, connect_key, jira_issue_fields_key, data=None, **request_kwargs + ): + """Get Connect issue field migration task.""" + url = self.resource_url( + f"rest/atlassian-connect/1/migration/{connect_key}/{jira_issue_fields_key}/task", + api_root="rest/api", + api_version=self.api_version, + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def connect_to_forge_migration_task_submission_resource_submit_task_post( + self, connect_key, jira_issue_fields_key, retrigger_completed_migration=None, data=None, **request_kwargs + ): + """Submit Connect issue field migration task.""" + url = self.resource_url( + f"rest/atlassian-connect/1/migration/{connect_key}/{jira_issue_fields_key}/task", + api_root="rest/api", + api_version=self.api_version, + ) + params = {"retriggerCompletedMigration": retrigger_completed_migration} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def service_registry_resource_services_get(self, service_ids=None, data=None, **request_kwargs): + """Retrieve the attributes of service registries.""" + url = self.resource_url( + "rest/atlassian-connect/1/service-registry", api_root="rest/api", api_version=self.api_version + ) + params = {"serviceIds": service_ids} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_forge_app_property_keys(self, data=None, **request_kwargs): + """Get app property keys (Forge).""" + url = self.resource_url("rest/forge/1/app/properties", api_root="rest/api", api_version=self.api_version) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_forge_app_property(self, property_key, data=None, **request_kwargs): + """Delete app property (Forge).""" + url = self.resource_url( + f"rest/forge/1/app/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_forge_app_property(self, property_key, data=None, **request_kwargs): + """Get app property (Forge).""" + url = self.resource_url( + f"rest/forge/1/app/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def put_forge_app_property(self, property_key, data=None, **request_kwargs): + """Set app property (Forge).""" + url = self.resource_url( + f"rest/forge/1/app/properties/{property_key}", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_worklogs_by_issue_id_and_worklog_id(self, data=None, **request_kwargs): + """Get worklogs by issue id and worklog id.""" + url = self.resource_url( + "rest/internal/api/latest/worklog/bulk", api_root="rest/api", api_version=self.api_version + ) + params = None + return self.post(url, params=params, data=data, **request_kwargs) diff --git a/atlassian/jira/jira_cloud.py b/atlassian/jira/jira_cloud.py new file mode 100644 index 000000000..bcb3611db --- /dev/null +++ b/atlassian/jira/jira_cloud.py @@ -0,0 +1,104 @@ +# coding=utf-8 +"""Versioned Jira Cloud clients. + +These clients are deliberately additive. ``Jira`` and ``ServiceDesk`` remain +the compatibility clients for Server, Data Center, and existing Cloud users. +""" + +from typing import Any, Optional, Union + +from ..rest_client import AtlassianRestAPI +from .core_methods import JiraCloudCoreMethods +from .service_management_methods import JiraServiceManagementMethods +from .software_methods import JiraSoftwareMethods + + +class JiraCloud(JiraCloudCoreMethods, AtlassianRestAPI): + """Jira Cloud platform (Core) REST API client for version 2 or 3. + + Use ``api_version=3`` for new integrations; version 2 is retained for + payload compatibility. The legacy ``Jira`` methods remain in + :class:`JiraServer` and are intentionally not mixed into this client. + """ + + SUPPORTED_API_VERSIONS = (2, 3) + + def __init__(self, url: str, *args: Any, api_version: Union[str, int] = 3, **kwargs: Any): + api_version = int(api_version) + if api_version not in self.SUPPORTED_API_VERSIONS: + raise ValueError("Jira Cloud Core api_version must be 2 or 3") + kwargs["api_version"] = api_version + kwargs["cloud"] = True + super(JiraCloud, self).__init__(url, *args, **kwargs) + + def endpoint(self, resource: str, api_version: Optional[Union[str, int]] = None) -> str: + """Return a Core REST endpoint path without issuing a request.""" + version = self.api_version if api_version is None else int(api_version) + if version not in self.SUPPORTED_API_VERSIONS: + raise ValueError("Jira Cloud Core api_version must be 2 or 3") + return self.resource_url(resource, api_root="rest/api", api_version=version) + + +class JiraSoftware(JiraSoftwareMethods, AtlassianRestAPI): + """Jira Software Cloud REST APIs. + + ``endpoint`` covers documented Agile, Software, DevInfo, deployments, + builds, feature-flags, remote-links, security, operations, and DevOps + component roots. Call ``get``, ``post``, ``put``, or ``delete`` with its + returned path. + """ + + API_VERSIONS = { + "agile": "1.0", + "software": "1.0", + "devinfo": "0.10", + "featureflags": "0.1", + "deployments": "0.1", + "builds": "0.1", + "remotelinks": "1.0", + "security": "1.0", + "operations": "1.0", + "devopscomponents": "1.0", + } + + def __init__(self, url: str, *args: Any, **kwargs: Any): + # These APIs do not share one root/version, so endpoint() supplies it. + kwargs["cloud"] = True + kwargs.setdefault("api_root", "rest") + kwargs.setdefault("api_version", None) + super(JiraSoftware, self).__init__(url, *args, **kwargs) + + def endpoint(self, api: str, resource: str = "", api_version: Optional[str] = None) -> str: + """Build a Jira Software Cloud endpoint path. + + ``api`` is one of :attr:`API_VERSIONS`; ``resource`` is the documented + portion following its version (for example ``"board/42/sprint"``). + """ + if api not in self.API_VERSIONS: + supported = ", ".join(sorted(self.API_VERSIONS)) + raise ValueError(f"Unsupported Jira Software API '{api}'. Use one of: {supported}") + version = api_version or self.API_VERSIONS[api] + return self.resource_url(resource, api_root=f"rest/{api}", api_version=version) + + +class JiraServiceManagement(JiraServiceManagementMethods, AtlassianRestAPI): + """Jira Service Management Cloud public REST API client. + + This client is independent from the legacy ``ServiceDesk`` surface, which + remains available unchanged for existing integrations. + """ + + API_VERSION = "1" + + def __init__(self, url: str, *args: Any, **kwargs: Any): + kwargs["cloud"] = True + super(JiraServiceManagement, self).__init__(url, *args, **kwargs) + + def endpoint(self, resource: str = "") -> str: + """Return a JSM public REST endpoint path without issuing a request.""" + return self.url_joiner("rest/servicedeskapi", resource) + + +def create_jira_cloud(url: str, *args: Any, api_version: Union[str, int] = 3, **kwargs: Any) -> JiraCloud: + """Create a versioned Jira Cloud Core client (v3 by default).""" + return JiraCloud(url, *args, api_version=api_version, **kwargs) diff --git a/atlassian/jira.py b/atlassian/jira/jira_server.py similarity index 99% rename from atlassian/jira.py rename to atlassian/jira/jira_server.py index 1a1cbfaf3..8971dbcd2 100644 --- a/atlassian/jira.py +++ b/atlassian/jira/jira_server.py @@ -1,4 +1,5 @@ # coding=utf-8 +# Jira Server/Data Center compatibility implementation. import logging import os import re @@ -14,9 +15,9 @@ from typing import Literal # Python 3.8+ else: from typing_extensions import Literal # Python <=3.7 -from .errors import ApiNotFoundError, ApiPermissionError -from .rest_client import AtlassianRestAPI -from .typehints import T_id, T_resp_json, copy_type +from ..errors import ApiNotFoundError, ApiPermissionError +from ..rest_client import AtlassianRestAPI +from ..typehints import T_id, T_resp_json, copy_type log = logging.getLogger(__name__) diff --git a/atlassian/jira/service_management_methods.py b/atlassian/jira/service_management_methods.py new file mode 100644 index 000000000..09ce762a2 --- /dev/null +++ b/atlassian/jira/service_management_methods.py @@ -0,0 +1,610 @@ +# coding=utf-8 +# Generated from the supplied Jira Cloud API descriptions; do not edit manually. + + +class JiraServiceManagementMethods: + """Concrete methods for every supplied service management API operation.""" + + def get_assets_workspaces(self, start=None, limit=None, data=None, **request_kwargs): + """Get assets workspaces.""" + url = "rest/servicedeskapi/assets/workspace" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_customer_customer_post(self, strict_conflict_status_code=None, data=None, **request_kwargs): + """Create customer.""" + url = "rest/servicedeskapi/customer" + params = {"strictConflictStatusCode": strict_conflict_status_code} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def create_customer_customer_skip_permission_check_post( + self, strict_conflict_status_code=None, data=None, **request_kwargs + ): + """Create customer.""" + url = "rest/servicedeskapi/customer/skip-permission-check" + params = {"strictConflictStatusCode": strict_conflict_status_code} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def revoke_portal_only_access_for_user(self, account_id, data=None, **request_kwargs): + """Revoke portal only access for user.""" + url = f"rest/servicedeskapi/customer/user/{account_id}/revoke-portal-only-access" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_info(self, data=None, **request_kwargs): + """Get info.""" + url = "rest/servicedeskapi/info" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_insight_workspaces(self, start=None, limit=None, data=None, **request_kwargs): + """Get insight workspaces.""" + url = "rest/servicedeskapi/insight/workspace" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_articles_knowledgebase_article_get( + self, query=None, highlight=None, start=None, limit=None, cursor=None, prev=None, data=None, **request_kwargs + ): + """Get articles.""" + url = "rest/servicedeskapi/knowledgebase/article" + params = { + "query": query, + "highlight": highlight, + "start": start, + "limit": limit, + "cursor": cursor, + "prev": prev, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def view_article(self, page_id, data=None, **request_kwargs): + """View knowledge base article.""" + url = f"rest/servicedeskapi/knowledgebase/article/view/{page_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_organizations_organization_get(self, start=None, limit=None, account_id=None, data=None, **request_kwargs): + """Get organizations.""" + url = "rest/servicedeskapi/organization" + params = {"start": start, "limit": limit, "accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_organization(self, data=None, **request_kwargs): + """Create organization.""" + url = "rest/servicedeskapi/organization" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_organization(self, organization_id, data=None, **request_kwargs): + """Delete organization.""" + url = f"rest/servicedeskapi/organization/{organization_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_organization(self, organization_id, data=None, **request_kwargs): + """Get organization.""" + url = f"rest/servicedeskapi/organization/{organization_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_properties_keys_organization_organization_id_property_get( + self, organization_id, data=None, **request_kwargs + ): + """Get properties keys.""" + url = f"rest/servicedeskapi/organization/{organization_id}/property" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_property_organization_organization_id_property_property_key_delete( + self, organization_id, property_key, data=None, **request_kwargs + ): + """Delete property.""" + url = f"rest/servicedeskapi/organization/{organization_id}/property/{property_key}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_property_organization_organization_id_property_property_key_get( + self, organization_id, property_key, data=None, **request_kwargs + ): + """Get property.""" + url = f"rest/servicedeskapi/organization/{organization_id}/property/{property_key}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_property_organization_organization_id_property_property_key_put( + self, organization_id, property_key, data=None, **request_kwargs + ): + """Set property.""" + url = f"rest/servicedeskapi/organization/{organization_id}/property/{property_key}" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_users_from_organization(self, organization_id, data=None, **request_kwargs): + """Remove users from organization.""" + url = f"rest/servicedeskapi/organization/{organization_id}/user" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_users_in_organization(self, organization_id, start=None, limit=None, data=None, **request_kwargs): + """Get users in organization.""" + url = f"rest/servicedeskapi/organization/{organization_id}/user" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_users_to_organization(self, organization_id, data=None, **request_kwargs): + """Add users to organization.""" + url = f"rest/servicedeskapi/organization/{organization_id}/user" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_customer_requests( + self, + search_term=None, + request_ownership=None, + request_status=None, + approval_status=None, + organization_id=None, + service_desk_id=None, + request_type_id=None, + expand=None, + start=None, + limit=None, + data=None, + **request_kwargs, + ): + """Get customer requests.""" + url = "rest/servicedeskapi/request" + params = { + "searchTerm": search_term, + "requestOwnership": request_ownership, + "requestStatus": request_status, + "approvalStatus": approval_status, + "organizationId": organization_id, + "serviceDeskId": service_desk_id, + "requestTypeId": request_type_id, + "expand": expand, + "start": start, + "limit": limit, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_customer_request(self, data=None, **request_kwargs): + """Create customer request.""" + url = "rest/servicedeskapi/request" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def validate_customer_request(self, data=None, **request_kwargs): + """Validate customer request.""" + url = "rest/servicedeskapi/request/validate" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_customer_request_by_id_or_key(self, issue_id_or_key, expand=None, data=None, **request_kwargs): + """Get customer request by id or key.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}" + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approvals(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get approvals.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/approval" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approval_by_id(self, issue_id_or_key, approval_id, data=None, **request_kwargs): + """Get approval by id.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/approval/{approval_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def answer_approval(self, issue_id_or_key, approval_id, data=None, **request_kwargs): + """Answer approval.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/approval/{approval_id}" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_attachments_for_request(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get attachments for request.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/attachment" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_comment_with_attachment(self, issue_id_or_key, data=None, **request_kwargs): + """Create comment with attachment.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/attachment" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_attachment_content(self, issue_id_or_key, attachment_id, data=None, **request_kwargs): + """Get attachment content.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/attachment/{attachment_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_attachment_thumbnail(self, issue_id_or_key, attachment_id, data=None, **request_kwargs): + """Get attachment thumbnail.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/attachment/{attachment_id}/thumbnail" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_request_comments( + self, + issue_id_or_key, + public=None, + internal=None, + expand=None, + start=None, + limit=None, + data=None, + **request_kwargs, + ): + """Get request comments.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/comment" + params = {"public": public, "internal": internal, "expand": expand, "start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_request_comment(self, issue_id_or_key, data=None, **request_kwargs): + """Create request comment.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/comment" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_request_comment_by_id(self, issue_id_or_key, comment_id, expand=None, data=None, **request_kwargs): + """Get request comment by id.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/comment/{comment_id}" + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_comment_attachments(self, issue_id_or_key, comment_id, start=None, limit=None, data=None, **request_kwargs): + """Get comment attachments.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/comment/{comment_id}/attachment" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def unsubscribe(self, issue_id_or_key, data=None, **request_kwargs): + """Unsubscribe.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/notification" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_subscription_status(self, issue_id_or_key, data=None, **request_kwargs): + """Get subscription status.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/notification" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def subscribe(self, issue_id_or_key, data=None, **request_kwargs): + """Subscribe.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/notification" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def remove_request_participants(self, issue_id_or_key, data=None, **request_kwargs): + """Remove request participants.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/participant" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_request_participants(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get request participants.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/participant" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_request_participants(self, issue_id_or_key, data=None, **request_kwargs): + """Add request participants.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/participant" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_sla_information(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get sla information.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/sla" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_sla_information_by_id(self, issue_id_or_key, sla_metric_id, data=None, **request_kwargs): + """Get sla information by id.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/sla/{sla_metric_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_customer_request_status(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get customer request status.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/status" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_customer_transitions(self, issue_id_or_key, start=None, limit=None, data=None, **request_kwargs): + """Get customer transitions.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/transition" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def perform_customer_transition(self, issue_id_or_key, data=None, **request_kwargs): + """Perform customer transition.""" + url = f"rest/servicedeskapi/request/{issue_id_or_key}/transition" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_feedback(self, request_id_or_key, data=None, **request_kwargs): + """Delete feedback.""" + url = f"rest/servicedeskapi/request/{request_id_or_key}/feedback" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_feedback(self, request_id_or_key, data=None, **request_kwargs): + """Get feedback.""" + url = f"rest/servicedeskapi/request/{request_id_or_key}/feedback" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def post_feedback(self, request_id_or_key, data=None, **request_kwargs): + """Post feedback.""" + url = f"rest/servicedeskapi/request/{request_id_or_key}/feedback" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_request_types( + self, + search_query=None, + service_desk_id=None, + start=None, + limit=None, + expand=None, + include_hidden_request_types_in_search=None, + restriction_status=None, + data=None, + **request_kwargs, + ): + """Get all request types.""" + url = "rest/servicedeskapi/requesttype" + params = { + "searchQuery": search_query, + "serviceDeskId": service_desk_id, + "start": start, + "limit": limit, + "expand": expand, + "includeHiddenRequestTypesInSearch": include_hidden_request_types_in_search, + "restrictionStatus": restriction_status, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_service_desks(self, start=None, limit=None, data=None, **request_kwargs): + """Get service desks.""" + url = "rest/servicedeskapi/servicedesk" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_service_desk_by_id(self, service_desk_id, data=None, **request_kwargs): + """Get service desk by id.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def attach_temporary_file(self, service_desk_id, data=None, **request_kwargs): + """Attach temporary file.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/attachTemporaryFile" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def remove_customers(self, service_desk_id, data=None, **request_kwargs): + """Remove customers.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/customer" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_customers(self, service_desk_id, query=None, start=None, limit=None, data=None, **request_kwargs): + """Get customers.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/customer" + params = {"query": query, "start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_customers_servicedesk_service_desk_id_customer_post(self, service_desk_id, data=None, **request_kwargs): + """Add customers.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/customer" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def invite_customer(self, service_desk_id, strict_conflict_status_code=None, data=None, **request_kwargs): + """Invite customer.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/customer/invite" + params = {"strictConflictStatusCode": strict_conflict_status_code} + params = {key: value for key, value in params.items() if value is not None} or None + return self.post(url, params=params, data=data, **request_kwargs) + + def add_customers_servicedesk_service_desk_id_customer_skip_permission_check_post( + self, service_desk_id, data=None, **request_kwargs + ): + """Add customers.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/customer/skip-permission-check" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_articles_servicedesk_service_desk_id_knowledgebase_article_get( + self, + service_desk_id, + query=None, + highlight=None, + start=None, + limit=None, + cursor=None, + prev=None, + data=None, + **request_kwargs, + ): + """Get articles.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/knowledgebase/article" + params = { + "query": query, + "highlight": highlight, + "start": start, + "limit": limit, + "cursor": cursor, + "prev": prev, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_organization(self, service_desk_id, data=None, **request_kwargs): + """Remove organization.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/organization" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_organizations_servicedesk_service_desk_id_organization_get( + self, service_desk_id, start=None, limit=None, account_id=None, data=None, **request_kwargs + ): + """Get organizations.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/organization" + params = {"start": start, "limit": limit, "accountId": account_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def add_organization(self, service_desk_id, data=None, **request_kwargs): + """Add organization.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/organization" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_queues(self, service_desk_id, include_count=None, start=None, limit=None, data=None, **request_kwargs): + """Get queues.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/queue" + params = {"includeCount": include_count, "start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_queue(self, service_desk_id, queue_id, include_count=None, data=None, **request_kwargs): + """Get queue.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/queue/{queue_id}" + params = {"includeCount": include_count} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_in_queue(self, service_desk_id, queue_id, start=None, limit=None, data=None, **request_kwargs): + """Get issues in queue.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/queue/{queue_id}/issue" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_request_types( + self, + service_desk_id, + group_id=None, + expand=None, + search_query=None, + start=None, + limit=None, + include_hidden_request_types_in_search=None, + restriction_status=None, + data=None, + **request_kwargs, + ): + """Get request types.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype" + params = { + "groupId": group_id, + "expand": expand, + "searchQuery": search_query, + "start": start, + "limit": limit, + "includeHiddenRequestTypesInSearch": include_hidden_request_types_in_search, + "restrictionStatus": restriction_status, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_request_type(self, service_desk_id, data=None, **request_kwargs): + """Create request type.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def check_request_type_permissions(self, service_desk_id, data=None, **request_kwargs): + """Check request type permissions.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/permissions/check" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_request_type(self, service_desk_id, request_type_id, data=None, **request_kwargs): + """Delete request type.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_request_type_by_id(self, service_desk_id, request_type_id, expand=None, data=None, **request_kwargs): + """Get request type by id.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}" + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_request_type_fields(self, service_desk_id, request_type_id, expand=None, data=None, **request_kwargs): + """Get request type fields.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}/field" + params = {"expand": expand} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_properties_keys_servicedesk_service_desk_id_requesttype_request_type_id_property_get( + self, request_type_id, service_desk_id, data=None, **request_kwargs + ): + """Get properties keys.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}/property" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_property_servicedesk_service_desk_id_requesttype_request_type_id_property_property_key_delete( + self, service_desk_id, request_type_id, property_key, data=None, **request_kwargs + ): + """Delete property.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}/property/{property_key}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_property_servicedesk_service_desk_id_requesttype_request_type_id_property_property_key_get( + self, service_desk_id, request_type_id, property_key, data=None, **request_kwargs + ): + """Get property.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}/property/{property_key}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_property_servicedesk_service_desk_id_requesttype_request_type_id_property_property_key_put( + self, service_desk_id, request_type_id, property_key, data=None, **request_kwargs + ): + """Set property.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttype/{request_type_id}/property/{property_key}" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_request_type_groups(self, service_desk_id, start=None, limit=None, data=None, **request_kwargs): + """Get request type groups.""" + url = f"rest/servicedeskapi/servicedesk/{service_desk_id}/requesttypegroup" + params = {"start": start, "limit": limit} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) diff --git a/atlassian/jira/software_methods.py b/atlassian/jira/software_methods.py new file mode 100644 index 000000000..2a17c5fa0 --- /dev/null +++ b/atlassian/jira/software_methods.py @@ -0,0 +1,1027 @@ +# coding=utf-8 +# Generated from the supplied Jira Cloud API descriptions; do not edit manually. + + +class JiraSoftwareMethods: + """Concrete methods for every supplied software API operation.""" + + def move_issues_to_backlog(self, data=None, **request_kwargs): + """Move issues to backlog.""" + url = "rest/agile/1.0/backlog/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def move_issues_to_backlog_for_board(self, board_id, data=None, **request_kwargs): + """Move issues to backlog for board.""" + url = f"rest/agile/1.0/backlog/{board_id}/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_all_boards( + self, + start_at=None, + max_results=None, + type=None, + name=None, + project_key_or_id=None, + account_id_location=None, + project_location=None, + include_private=None, + negate_location_filtering=None, + order_by=None, + expand=None, + project_type_location=None, + filter_id=None, + data=None, + **request_kwargs, + ): + """Get all boards.""" + url = "rest/agile/1.0/board" + params = { + "startAt": start_at, + "maxResults": max_results, + "type": type, + "name": name, + "projectKeyOrId": project_key_or_id, + "accountIdLocation": account_id_location, + "projectLocation": project_location, + "includePrivate": include_private, + "negateLocationFiltering": negate_location_filtering, + "orderBy": order_by, + "expand": expand, + "projectTypeLocation": project_type_location, + "filterId": filter_id, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def create_board(self, data=None, **request_kwargs): + """Create board.""" + url = "rest/agile/1.0/board" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_board_by_filter_id(self, filter_id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get board by filter id.""" + url = f"rest/agile/1.0/board/filter/{filter_id}" + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_board(self, board_id, data=None, **request_kwargs): + """Delete board.""" + url = f"rest/agile/1.0/board/{board_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_board(self, board_id, data=None, **request_kwargs): + """Get board.""" + url = f"rest/agile/1.0/board/{board_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_for_backlog( + self, + board_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for backlog.""" + url = f"rest/agile/1.0/board/{board_id}/backlog" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_for_backlog_jsis( + self, + board_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for backlog (enhanced).""" + url = f"rest/software/1.0/board/{board_id}/backlog" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approximate_issue_count_for_backlog(self, board_id, jql=None, data=None, **request_kwargs): + """Get approximate issue count for backlog.""" + url = f"rest/software/1.0/board/{board_id}/backlog/approximate-count" + params = {"jql": jql} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_configuration(self, board_id, data=None, **request_kwargs): + """Get configuration.""" + url = f"rest/agile/1.0/board/{board_id}/configuration" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_epics(self, board_id, start_at=None, max_results=None, done=None, data=None, **request_kwargs): + """Get epics.""" + url = f"rest/agile/1.0/board/{board_id}/epic" + params = {"startAt": start_at, "maxResults": max_results, "done": done} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_without_epic_for_board( + self, + board_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues without epic for board.""" + url = f"rest/agile/1.0/board/{board_id}/epic/none/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_without_epic_for_board_jsis( + self, + board_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues without epic for board (enhanced).""" + url = f"rest/software/1.0/board/{board_id}/epic/none/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_board_issues_for_epic( + self, + board_id, + epic_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get board issues for epic.""" + url = f"rest/agile/1.0/board/{board_id}/epic/{epic_id}/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_board_issues_for_epic_jsis( + self, + board_id, + epic_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get board issues for epic (enhanced).""" + url = f"rest/software/1.0/board/{board_id}/epic/{epic_id}/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_features_for_board(self, board_id, data=None, **request_kwargs): + """Get features for board.""" + url = f"rest/agile/1.0/board/{board_id}/features" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def toggle_features(self, board_id, data=None, **request_kwargs): + """Toggle features.""" + url = f"rest/agile/1.0/board/{board_id}/features" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_issues_for_board( + self, + board_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for board.""" + url = f"rest/agile/1.0/board/{board_id}/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def move_issues_to_board(self, board_id, data=None, **request_kwargs): + """Move issues to board.""" + url = f"rest/agile/1.0/board/{board_id}/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issues_for_board_jsis( + self, + board_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for board (enhanced).""" + url = f"rest/software/1.0/board/{board_id}/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_approximate_issue_count_for_board(self, board_id, jql=None, data=None, **request_kwargs): + """Get approximate issue count for board.""" + url = f"rest/software/1.0/board/{board_id}/issue/approximate-count" + params = {"jql": jql} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_projects(self, board_id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get projects.""" + url = f"rest/agile/1.0/board/{board_id}/project" + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_projects_full(self, board_id, data=None, **request_kwargs): + """Get projects full.""" + url = f"rest/agile/1.0/board/{board_id}/project/full" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_board_property_keys(self, board_id, data=None, **request_kwargs): + """Get board property keys.""" + url = f"rest/agile/1.0/board/{board_id}/properties" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_board_property(self, board_id, property_key, data=None, **request_kwargs): + """Delete board property.""" + url = f"rest/agile/1.0/board/{board_id}/properties/{property_key}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_board_property(self, board_id, property_key, data=None, **request_kwargs): + """Get board property.""" + url = f"rest/agile/1.0/board/{board_id}/properties/{property_key}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_board_property(self, board_id, property_key, data=None, **request_kwargs): + """Set board property.""" + url = f"rest/agile/1.0/board/{board_id}/properties/{property_key}" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_all_quick_filters(self, board_id, start_at=None, max_results=None, data=None, **request_kwargs): + """Get all quick filters.""" + url = f"rest/agile/1.0/board/{board_id}/quickfilter" + params = {"startAt": start_at, "maxResults": max_results} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_quick_filter(self, board_id, quick_filter_id, data=None, **request_kwargs): + """Get quick filter.""" + url = f"rest/agile/1.0/board/{board_id}/quickfilter/{quick_filter_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_reports_for_board(self, board_id, data=None, **request_kwargs): + """Get reports for board.""" + url = f"rest/agile/1.0/board/{board_id}/reports" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_sprints(self, board_id, start_at=None, max_results=None, state=None, data=None, **request_kwargs): + """Get all sprints.""" + url = f"rest/agile/1.0/board/{board_id}/sprint" + params = {"startAt": start_at, "maxResults": max_results, "state": state} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_board_issues_for_sprint( + self, + board_id, + sprint_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get board issues for sprint.""" + url = f"rest/agile/1.0/board/{board_id}/sprint/{sprint_id}/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_board_issues_for_sprint_jsis( + self, + board_id, + sprint_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get board issues for sprint (enhanced).""" + url = f"rest/software/1.0/board/{board_id}/sprint/{sprint_id}/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_all_versions(self, board_id, start_at=None, max_results=None, released=None, data=None, **request_kwargs): + """Get all versions.""" + url = f"rest/agile/1.0/board/{board_id}/version" + params = {"startAt": start_at, "maxResults": max_results, "released": released} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issues_without_epic( + self, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues without epic.""" + url = "rest/agile/1.0/epic/none/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def remove_issues_from_epic(self, data=None, **request_kwargs): + """Remove issues from epic.""" + url = "rest/agile/1.0/epic/none/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issues_without_epic_jsis( + self, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues without epic (enhanced).""" + url = "rest/software/1.0/epic/none/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_epic(self, epic_id_or_key, data=None, **request_kwargs): + """Get epic.""" + url = f"rest/agile/1.0/epic/{epic_id_or_key}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def partially_update_epic(self, epic_id_or_key, data=None, **request_kwargs): + """Partially update epic.""" + url = f"rest/agile/1.0/epic/{epic_id_or_key}" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issues_for_epic( + self, + epic_id_or_key, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for epic.""" + url = f"rest/agile/1.0/epic/{epic_id_or_key}/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def move_issues_to_epic(self, epic_id_or_key, data=None, **request_kwargs): + """Move issues to epic.""" + url = f"rest/agile/1.0/epic/{epic_id_or_key}/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issues_for_epic_jsis( + self, + epic_id_or_key, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for epic (enhanced).""" + url = f"rest/software/1.0/epic/{epic_id_or_key}/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def rank_epics(self, epic_id_or_key, data=None, **request_kwargs): + """Rank epics.""" + url = f"rest/agile/1.0/epic/{epic_id_or_key}/rank" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def rank_issues(self, data=None, **request_kwargs): + """Rank issues.""" + url = "rest/agile/1.0/issue/rank" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_issue(self, issue_id_or_key, fields=None, expand=None, update_history=None, data=None, **request_kwargs): + """Get issue.""" + url = f"rest/agile/1.0/issue/{issue_id_or_key}" + params = {"fields": fields, "expand": expand, "updateHistory": update_history} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_issue_estimation_for_board(self, issue_id_or_key, board_id=None, data=None, **request_kwargs): + """Get issue estimation for board.""" + url = f"rest/agile/1.0/issue/{issue_id_or_key}/estimation" + params = {"boardId": board_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def estimate_issue_for_board(self, issue_id_or_key, board_id=None, data=None, **request_kwargs): + """Estimate issue for board.""" + url = f"rest/agile/1.0/issue/{issue_id_or_key}/estimation" + params = {"boardId": board_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.put(url, params=params, data=data, **request_kwargs) + + def create_sprint(self, data=None, **request_kwargs): + """Create sprint.""" + url = "rest/agile/1.0/sprint" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_sprint(self, sprint_id, data=None, **request_kwargs): + """Delete sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_sprint(self, sprint_id, data=None, **request_kwargs): + """Get sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def partially_update_sprint(self, sprint_id, data=None, **request_kwargs): + """Partially update sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def update_sprint(self, sprint_id, data=None, **request_kwargs): + """Update sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def get_issues_for_sprint( + self, + sprint_id, + start_at=None, + max_results=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/issue" + params = { + "startAt": start_at, + "maxResults": max_results, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def move_issues_to_sprint_and_rank(self, sprint_id, data=None, **request_kwargs): + """Move issues to sprint and rank.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/issue" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_issues_for_sprint_jsis( + self, + sprint_id, + next_page_token=None, + max_results=None, + reconcile_issues=None, + jql=None, + validate_query=None, + fields=None, + expand=None, + data=None, + **request_kwargs, + ): + """Get issues for sprint (enhanced).""" + url = f"rest/software/1.0/sprint/{sprint_id}/issue" + params = { + "nextPageToken": next_page_token, + "maxResults": max_results, + "reconcileIssues": reconcile_issues, + "jql": jql, + "validateQuery": validate_query, + "fields": fields, + "expand": expand, + } + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_properties_keys(self, sprint_id, data=None, **request_kwargs): + """Get properties keys.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/properties" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_property(self, sprint_id, property_key, data=None, **request_kwargs): + """Delete property.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/properties/{property_key}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_property(self, sprint_id, property_key, data=None, **request_kwargs): + """Get property.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/properties/{property_key}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def set_property(self, sprint_id, property_key, data=None, **request_kwargs): + """Set property.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/properties/{property_key}" + params = None + return self.put(url, params=params, data=data, **request_kwargs) + + def swap_sprint(self, sprint_id, data=None, **request_kwargs): + """Swap sprint.""" + url = f"rest/agile/1.0/sprint/{sprint_id}/swap" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def store_development_information(self, data=None, **request_kwargs): + """Store development information.""" + url = "rest/devinfo/0.10/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def get_repository(self, repository_id, data=None, **request_kwargs): + """Get repository.""" + url = f"rest/devinfo/0.10/repository/{repository_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_repository(self, repository_id, update_sequence_id=None, data=None, **request_kwargs): + """Delete repository.""" + url = f"rest/devinfo/0.10/repository/{repository_id}" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def delete_by_properties(self, update_sequence_id=None, data=None, **request_kwargs): + """Delete development information by properties.""" + url = "rest/devinfo/0.10/bulkByProperties" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def exists_by_properties(self, update_sequence_id=None, data=None, **request_kwargs): + """Check if data exists for the supplied properties.""" + url = "rest/devinfo/0.10/existsByProperties" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_entity( + self, repository_id, entity_type, entity_id, update_sequence_id=None, data=None, **request_kwargs + ): + """Delete development information entity.""" + url = f"rest/devinfo/0.10/repository/{repository_id}/{entity_type}/{entity_id}" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_feature_flags(self, data=None, **request_kwargs): + """Submit Feature Flag data.""" + url = "rest/featureflags/0.1/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_feature_flags_by_property(self, update_sequence_id=None, data=None, **request_kwargs): + """Delete Feature Flags by Property.""" + url = "rest/featureflags/0.1/bulkByProperties" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_feature_flag_by_id(self, feature_flag_id, data=None, **request_kwargs): + """Get a Feature Flag by ID.""" + url = f"rest/featureflags/0.1/flag/{feature_flag_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_feature_flag_by_id(self, feature_flag_id, update_sequence_id=None, data=None, **request_kwargs): + """Delete a Feature Flag by ID.""" + url = f"rest/featureflags/0.1/flag/{feature_flag_id}" + params = {"_updateSequenceId": update_sequence_id} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_deployments(self, data=None, **request_kwargs): + """Submit deployment data.""" + url = "rest/deployments/0.1/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_deployments_by_property(self, update_sequence_number=None, data=None, **request_kwargs): + """Delete deployments by Property.""" + url = "rest/deployments/0.1/bulkByProperties" + params = {"_updateSequenceNumber": update_sequence_number} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_deployment_by_key( + self, pipeline_id, environment_id, deployment_sequence_number, data=None, **request_kwargs + ): + """Get a deployment by key.""" + url = f"rest/deployments/0.1/pipelines/{pipeline_id}/environments/{environment_id}/deployments/{deployment_sequence_number}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_deployment_by_key( + self, + pipeline_id, + environment_id, + deployment_sequence_number, + update_sequence_number=None, + data=None, + **request_kwargs, + ): + """Delete a deployment by key.""" + url = f"rest/deployments/0.1/pipelines/{pipeline_id}/environments/{environment_id}/deployments/{deployment_sequence_number}" + params = {"_updateSequenceNumber": update_sequence_number} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_deployment_gating_status_by_key( + self, pipeline_id, environment_id, deployment_sequence_number, data=None, **request_kwargs + ): + """Get deployment gating status by key.""" + url = f"rest/deployments/0.1/pipelines/{pipeline_id}/environments/{environment_id}/deployments/{deployment_sequence_number}/gating-status" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_builds(self, data=None, **request_kwargs): + """Submit build data.""" + url = "rest/builds/0.1/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_builds_by_property(self, update_sequence_number=None, data=None, **request_kwargs): + """Delete builds by Property.""" + url = "rest/builds/0.1/bulkByProperties" + params = {"_updateSequenceNumber": update_sequence_number} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_build_by_key(self, pipeline_id, build_number, data=None, **request_kwargs): + """Get a build by key.""" + url = f"rest/builds/0.1/pipelines/{pipeline_id}/builds/{build_number}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_build_by_key(self, pipeline_id, build_number, update_sequence_number=None, data=None, **request_kwargs): + """Delete a build by key.""" + url = f"rest/builds/0.1/pipelines/{pipeline_id}/builds/{build_number}" + params = {"_updateSequenceNumber": update_sequence_number} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_remote_links(self, data=None, **request_kwargs): + """Submit Remote Link data.""" + url = "rest/remotelinks/1.0/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_remote_links_by_property(self, update_sequence_number=None, params=None, data=None, **request_kwargs): + """Delete Remote Links by Property.""" + url = "rest/remotelinks/1.0/bulkByProperties" + params = {"_updateSequenceNumber": update_sequence_number, "params": params} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_remote_link_by_id(self, remote_link_id, data=None, **request_kwargs): + """Get a Remote Link by ID.""" + url = f"rest/remotelinks/1.0/remotelink/{remote_link_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_remote_link_by_id(self, remote_link_id, update_sequence_number=None, data=None, **request_kwargs): + """Delete a Remote Link by ID.""" + url = f"rest/remotelinks/1.0/remotelink/{remote_link_id}" + params = {"_updateSequenceNumber": update_sequence_number} + params = {key: value for key, value in params.items() if value is not None} or None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_workspaces(self, data=None, **request_kwargs): + """Submit Security Workspaces to link.""" + url = "rest/security/1.0/linkedWorkspaces/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_linked_workspaces(self, data=None, **request_kwargs): + """Delete linked Security Workspaces.""" + url = "rest/security/1.0/linkedWorkspaces/bulk" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_linked_workspaces(self, data=None, **request_kwargs): + """Get linked Security Workspaces.""" + url = "rest/security/1.0/linkedWorkspaces" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def get_linked_workspace_by_id(self, workspace_id, data=None, **request_kwargs): + """Get a linked Security Workspace by ID.""" + url = f"rest/security/1.0/linkedWorkspaces/{workspace_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_vulnerabilities(self, data=None, **request_kwargs): + """Submit Vulnerability data.""" + url = "rest/security/1.0/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_vulnerabilities_by_property(self, data=None, **request_kwargs): + """Delete Vulnerabilities by Property.""" + url = "rest/security/1.0/bulkByProperties" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_vulnerability_by_id(self, vulnerability_id, data=None, **request_kwargs): + """Get a Vulnerability by ID.""" + url = f"rest/security/1.0/vulnerability/{vulnerability_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_vulnerability_by_id(self, vulnerability_id, data=None, **request_kwargs): + """Delete a Vulnerability by ID.""" + url = f"rest/security/1.0/vulnerability/{vulnerability_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_operations_workspaces(self, data=None, **request_kwargs): + """Submit Operations Workspace Ids.""" + url = "rest/operations/1.0/linkedWorkspaces/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_workspaces(self, data=None, **request_kwargs): + """Delete Operations Workpaces by Id.""" + url = "rest/operations/1.0/linkedWorkspaces/bulk" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_workspaces(self, data=None, **request_kwargs): + """Get all Operations Workspace IDs or a specific Operations Workspace by ID.""" + url = "rest/operations/1.0/linkedWorkspaces" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def submit_entity(self, data=None, **request_kwargs): + """Submit Incident or Review data.""" + url = "rest/operations/1.0/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_entity_by_property(self, data=None, **request_kwargs): + """Delete Incidents or Review by Property.""" + url = "rest/operations/1.0/bulkByProperties" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_incident_by_id(self, incident_id, data=None, **request_kwargs): + """Get a Incident by ID.""" + url = f"rest/operations/1.0/incidents/{incident_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_incident_by_id(self, incident_id, data=None, **request_kwargs): + """Delete a Incident by ID.""" + url = f"rest/operations/1.0/incidents/{incident_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_review_by_id(self, review_id, data=None, **request_kwargs): + """Get a Review by ID.""" + url = f"rest/operations/1.0/post-incident-reviews/{review_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_review_by_id(self, review_id, data=None, **request_kwargs): + """Delete a Review by ID.""" + url = f"rest/operations/1.0/post-incident-reviews/{review_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def submit_components(self, data=None, **request_kwargs): + """Submit DevOps Components.""" + url = "rest/devopscomponents/1.0/bulk" + params = None + return self.post(url, params=params, data=data, **request_kwargs) + + def delete_components_by_property(self, data=None, **request_kwargs): + """Delete DevOps Components by Property.""" + url = "rest/devopscomponents/1.0/bulkByProperties" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) + + def get_component_by_id(self, component_id, data=None, **request_kwargs): + """Get a Component by ID.""" + url = f"rest/devopscomponents/1.0/devopscomponents/{component_id}" + params = None + return self.get(url, params=params, data=data, **request_kwargs) + + def delete_component_by_id(self, component_id, data=None, **request_kwargs): + """Delete a Component by ID.""" + url = f"rest/devopscomponents/1.0/devopscomponents/{component_id}" + params = None + return self.delete(url, params=params, data=data, **request_kwargs) diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 7b4e6fe0b..3b3c7fdc4 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -521,11 +521,15 @@ def request( headers=headers, data=data if data is not None else json_dump, ) + # ``requests`` does not accept booleans as request bodies. The + # public client has historically accepted them, so preserve that + # convenience while sending a valid textual representation. + request_data = str(data).lower() if isinstance(data, bool) else data response = self._session.request( method=method, url=url, headers=headers, - data=data, + data=request_data, json=json, timeout=self.timeout, verify=self.verify_ssl, diff --git a/docs/index.rst b/docs/index.rst index 7c735ec49..fe8453a1e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -270,6 +270,8 @@ Add a connection: :maxdepth: 2 jira + jira_cloud + jira_cloud_api confluence crowd bitbucket diff --git a/docs/jira_cloud.rst b/docs/jira_cloud.rst new file mode 100644 index 000000000..506b861ba --- /dev/null +++ b/docs/jira_cloud.rst @@ -0,0 +1,89 @@ +Jira Cloud API clients +====================== + +The clients are organized under ``atlassian.jira``. ``Jira`` is the retained +Server/Data Center-compatible client (also available explicitly as +``JiraServer``); ``ServiceDesk`` remains available for established +integrations. +New Cloud work should use the module-specific clients: + +.. code-block:: python + + from atlassian.jira import JiraCloud, JiraSoftware, JiraServiceManagement + + # Jira Cloud platform (Core), REST v3 by default; v2 is also supported. + core = JiraCloud("https://example.atlassian.net", token="...", api_version=3) + issue = core.get(core.endpoint("issue/ABC-1")) + + # Jira Software has several independently versioned roots. + software = JiraSoftware("https://example.atlassian.net", token="...") + sprints = software.get(software.endpoint("agile", "board/42/sprint")) + + # The public JSM API has the servicedeskapi root and keeps ServiceDesk APIs. + jsm = JiraServiceManagement("https://example.atlassian.net", token="...") + request = jsm.get(jsm.endpoint("request/ABC-1")) + +Core API versioning +------------------- + +``JiraCloud`` validates Core REST versions 2 and 3. It defaults to v3 for new +integrations. Use v2 only where its response or request payload is part of an +existing contract. ``Jira`` still defaults to its historical string version +``"2"`` and does not automatically enable Cloud mode; this is intentional +backward compatibility. + +Software API roots +------------------ + +``JiraSoftware.endpoint(api, resource)`` selects the supplied API's current +documented root/version. Supported API names are ``agile``, ``software``, +``devinfo``, ``featureflags``, ``deployments``, ``builds``, ``remotelinks``, +``security``, ``operations``, and ``devopscomponents``. The separate roots +avoid incorrectly treating Jira Software as Core v1. + +Generated OpenAPI operations +---------------------------- + +The supplied API descriptions provide 617 Core operations across 421 paths, +105 Software operations across 78 paths, and 75 Service Management operations +across 50 paths. Every operation is implemented as an ordinary snake_case +Python method, grouped in ``core_methods.py``, ``software_methods.py``, or +``service_management_methods.py``. For example: + +.. code-block:: python + + issue = core.get_issue("ABC-1") + boards = software.get_all_boards(project_key_or_id="ABC") + request = jsm.get_customer_request_by_id_or_key("ABC-1") + +Methods have explicit path and query arguments, plus ``data`` for the JSON +body and the same request options supported by the shared REST client. A few +JSM operation IDs are duplicated by Atlassian; their method names include a +deterministic endpoint suffix so no endpoint is lost. + +Compatibility and migration +--------------------------- + +``Jira`` and ``JiraServer`` are the same legacy-compatible implementation. +Their imports, constructor options, and existing method names remain unchanged: + +.. code-block:: python + + from atlassian import Jira + + jira = Jira("https://jira.example.org", username="admin", password="...") + jira.issue("ABC-1") + +Use the new Cloud classes only for new integrations or an intentional Cloud +migration. They force ``cloud=True`` and accept the ordinary connection +arguments such as ``token``, ``oauth2``, ``session``, ``verify_ssl``, +``timeout``, and retry settings. ``JiraCloud`` supports Core ``api_version=2`` +and ``api_version=3``; its concrete Core methods build routes for the selected +version. Jira Software and JSM select their documented fixed roots. + +For a complete method-to-endpoint reference, see :doc:`jira_cloud_api`. + +Core generated operations use the selected ``JiraCloud(api_version=2|3)`` +version in their route. Existing snake_case ``Jira`` and ``ServiceDesk`` +methods retain their endpoint selection and behavior; they are neither renamed +nor overwritten by the generated Cloud surface. diff --git a/docs/jira_cloud_api.rst b/docs/jira_cloud_api.rst new file mode 100644 index 000000000..436f51042 --- /dev/null +++ b/docs/jira_cloud_api.rst @@ -0,0 +1,822 @@ +Jira Cloud method reference +=========================== + +This index lists every concrete Jira Cloud client method. Each method has +explicit Python ``snake_case`` arguments and calls its documented REST +endpoint directly. The corresponding source files provide each method's +summary, request verb, endpoint path, query conversion, and JSON ``data`` +argument. + +Jira Core Cloud +--------------- + +* ``get_banner`` +* ``set_banner`` +* ``get_custom_fields_configurations`` +* ``update_multiple_custom_field_values`` +* ``get_custom_field_configuration`` +* ``update_custom_field_configuration`` +* ``update_custom_field_value`` +* ``get_application_property`` +* ``get_advanced_settings`` +* ``set_application_property`` +* ``get_all_application_roles`` +* ``get_application_role`` +* ``get_attachment_content`` +* ``get_attachment_meta`` +* ``get_attachment_thumbnail`` +* ``remove_attachment`` +* ``get_attachment`` +* ``expand_attachment_for_humans`` +* ``expand_attachment_for_machines`` +* ``get_audit_records`` +* ``get_all_system_avatars`` +* ``submit_bulk_delete`` +* ``get_bulk_editable_fields`` +* ``submit_bulk_edit`` +* ``submit_bulk_move`` +* ``get_available_transitions`` +* ``submit_bulk_transition`` +* ``submit_bulk_unwatch`` +* ``submit_bulk_watch`` +* ``get_bulk_operation_progress`` +* ``get_bulk_changelogs`` +* ``get_all_user_data_classification_levels`` +* ``get_comments_by_ids`` +* ``get_comment_property_keys`` +* ``delete_comment_property`` +* ``get_comment_property`` +* ``set_comment_property`` +* ``find_components_for_projects`` +* ``create_component`` +* ``delete_component`` +* ``get_component`` +* ``update_component`` +* ``get_component_related_issues`` +* ``get_field_association_schemes`` +* ``create_field_association_scheme`` +* ``remove_fields_associated_with_schemes`` +* ``update_fields_associated_with_schemes`` +* ``remove_field_association_scheme_item_parameters`` +* ``update_field_association_scheme_item_parameters`` +* ``get_projects_with_field_schemes`` +* ``associate_projects_to_field_association_schemes`` +* ``delete_field_association_scheme`` +* ``get_field_association_scheme_by_id`` +* ``update_field_association_scheme`` +* ``clone_field_association_scheme`` +* ``search_field_association_scheme_fields`` +* ``get_field_association_scheme_item_parameters`` +* ``search_field_association_scheme_projects`` +* ``get_configuration`` +* ``get_selected_time_tracking_implementation`` +* ``select_time_tracking_implementation`` +* ``get_available_time_tracking_implementations`` +* ``get_shared_time_tracking_configuration`` +* ``set_shared_time_tracking_configuration`` +* ``get_custom_field_option`` +* ``get_all_dashboards`` +* ``create_dashboard`` +* ``bulk_edit_dashboards`` +* ``get_all_available_dashboard_gadgets`` +* ``get_dashboards_paginated`` +* ``get_all_gadgets`` +* ``add_gadget`` +* ``remove_gadget`` +* ``update_gadget`` +* ``get_dashboard_item_property_keys`` +* ``delete_dashboard_item_property`` +* ``get_dashboard_item_property`` +* ``set_dashboard_item_property`` +* ``delete_dashboard`` +* ``get_dashboard`` +* ``update_dashboard`` +* ``copy_dashboard`` +* ``get_policy`` +* ``get_policies`` +* ``get_events`` +* ``analyse_expression`` +* ``evaluate_jira_expression`` +* ``evaluate_jsisjira_expression`` +* ``get_fields`` +* ``create_custom_field`` +* ``remove_associations`` +* ``create_associations`` +* ``get_fields_paginated`` +* ``get_trashed_fields_paginated`` +* ``update_custom_field`` +* ``get_field_project_associations`` +* ``get_contexts_for_field`` +* ``create_custom_field_context`` +* ``get_default_values`` +* ``set_default_values`` +* ``get_context_default_values`` +* ``get_issue_type_mappings_for_contexts`` +* ``get_custom_field_contexts_for_projects_and_issue_types`` +* ``get_project_context_mapping`` +* ``delete_custom_field_context`` +* ``update_custom_field_context`` +* ``add_issue_types_to_context`` +* ``remove_issue_types_from_context`` +* ``get_options_for_context`` +* ``create_custom_field_option`` +* ``update_custom_field_option`` +* ``reorder_custom_field_options`` +* ``delete_custom_field_option`` +* ``replace_custom_field_option`` +* ``assign_projects_to_custom_field_context`` +* ``remove_custom_field_context_from_projects`` +* ``get_contexts_for_field_deprecated`` +* ``get_screens_for_field`` +* ``get_all_issue_field_options`` +* ``create_issue_field_option`` +* ``get_selectable_issue_field_options`` +* ``get_visible_issue_field_options`` +* ``delete_issue_field_option`` +* ``get_issue_field_option`` +* ``update_issue_field_option`` +* ``replace_issue_field_option`` +* ``delete_custom_field`` +* ``restore_custom_field`` +* ``trash_custom_field`` +* ``get_all_field_configurations`` +* ``create_field_configuration`` +* ``delete_field_configuration`` +* ``update_field_configuration`` +* ``get_field_configuration_items`` +* ``update_field_configuration_items`` +* ``get_all_field_configuration_schemes`` +* ``create_field_configuration_scheme`` +* ``get_field_configuration_scheme_mappings`` +* ``get_field_configuration_scheme_project_mapping`` +* ``assign_field_configuration_scheme_to_project`` +* ``delete_field_configuration_scheme`` +* ``update_field_configuration_scheme`` +* ``set_field_configuration_scheme_mapping`` +* ``remove_issue_types_from_global_field_configuration_scheme`` +* ``create_filter`` +* ``get_default_share_scope`` +* ``set_default_share_scope`` +* ``get_favourite_filters`` +* ``get_my_filters`` +* ``get_filters_paginated`` +* ``delete_filter`` +* ``get_filter`` +* ``update_filter`` +* ``reset_columns`` +* ``get_columns`` +* ``set_columns`` +* ``delete_favourite_for_filter`` +* ``set_favourite_for_filter`` +* ``change_filter_owner`` +* ``get_share_permissions`` +* ``add_share_permission`` +* ``delete_share_permission`` +* ``get_share_permission`` +* ``bulk_pin_unpin_projects_async`` +* ``remove_group`` +* ``get_group`` +* ``create_group`` +* ``bulk_get_groups`` +* ``get_users_from_group`` +* ``remove_user_from_group`` +* ``add_user_to_group`` +* ``find_groups`` +* ``find_users_and_groups`` +* ``get_license`` +* ``create_issue`` +* ``archive_issues_async`` +* ``archive_issues`` +* ``create_issues`` +* ``bulk_fetch_issues`` +* ``get_create_issue_meta`` +* ``get_create_issue_meta_issue_types`` +* ``get_create_issue_meta_issue_type_id`` +* ``get_issue_adf_limit_report`` +* ``get_issue_limit_report`` +* ``get_issue_picker_resource`` +* ``bulk_set_issues_properties_list`` +* ``bulk_set_issue_properties_by_issue`` +* ``bulk_delete_issue_property`` +* ``bulk_set_issue_property`` +* ``unarchive_issues`` +* ``get_is_watching_issue_bulk`` +* ``delete_issue`` +* ``get_issue`` +* ``edit_issue`` +* ``assign_issue`` +* ``add_attachment`` +* ``get_change_logs`` +* ``get_change_logs_by_ids`` +* ``get_comments`` +* ``add_comment`` +* ``delete_comment`` +* ``get_comment`` +* ``update_comment`` +* ``get_edit_issue_meta`` +* ``notify`` +* ``get_issue_property_keys`` +* ``delete_issue_property`` +* ``get_issue_property`` +* ``set_issue_property`` +* ``delete_remote_issue_link_by_global_id`` +* ``get_remote_issue_links`` +* ``create_or_update_remote_issue_link`` +* ``delete_remote_issue_link_by_id`` +* ``get_remote_issue_link_by_id`` +* ``update_remote_issue_link`` +* ``get_transitions`` +* ``do_transition`` +* ``remove_vote`` +* ``get_votes`` +* ``add_vote`` +* ``remove_watcher`` +* ``get_issue_watchers`` +* ``add_watcher`` +* ``bulk_delete_worklogs`` +* ``get_issue_worklog`` +* ``add_worklog`` +* ``bulk_move_worklogs`` +* ``delete_worklog`` +* ``get_worklog`` +* ``update_worklog`` +* ``get_worklog_property_keys`` +* ``delete_worklog_property`` +* ``get_worklog_property`` +* ``set_worklog_property`` +* ``link_issues`` +* ``delete_issue_link`` +* ``get_issue_link`` +* ``get_issue_link_types`` +* ``create_issue_link_type`` +* ``delete_issue_link_type`` +* ``get_issue_link_type`` +* ``update_issue_link_type`` +* ``export_archived_issues`` +* ``get_issue_security_schemes`` +* ``create_issue_security_scheme`` +* ``get_security_levels`` +* ``set_default_levels`` +* ``get_security_level_members`` +* ``search_projects_using_security_schemes`` +* ``associate_schemes_to_projects`` +* ``search_security_schemes`` +* ``get_issue_security_scheme`` +* ``update_issue_security_scheme`` +* ``get_issue_security_level_members`` +* ``delete_security_scheme`` +* ``add_security_level`` +* ``remove_level`` +* ``update_security_level`` +* ``add_security_level_members`` +* ``remove_member_from_security_level`` +* ``get_issue_all_types`` +* ``create_issue_type`` +* ``get_issue_types_for_project`` +* ``delete_issue_type`` +* ``get_issue_type`` +* ``update_issue_type`` +* ``get_alternative_issue_types`` +* ``create_issue_type_avatar`` +* ``get_issue_type_property_keys`` +* ``delete_issue_type_property`` +* ``get_issue_type_property`` +* ``set_issue_type_property`` +* ``get_all_issue_type_schemes`` +* ``create_issue_type_scheme`` +* ``get_issue_type_schemes_mapping`` +* ``get_issue_type_scheme_for_projects`` +* ``assign_issue_type_scheme_to_project`` +* ``delete_issue_type_scheme`` +* ``update_issue_type_scheme`` +* ``add_issue_types_to_issue_type_scheme`` +* ``reorder_issue_types_in_issue_type_scheme`` +* ``remove_issue_type_from_issue_type_scheme`` +* ``get_issue_type_screen_schemes`` +* ``create_issue_type_screen_scheme`` +* ``get_issue_type_screen_scheme_mappings`` +* ``get_issue_type_screen_scheme_project_associations`` +* ``assign_issue_type_screen_scheme_to_project`` +* ``delete_issue_type_screen_scheme`` +* ``update_issue_type_screen_scheme`` +* ``append_mappings_for_issue_type_screen_scheme`` +* ``update_default_screen_scheme`` +* ``remove_mappings_from_issue_type_screen_scheme`` +* ``get_projects_for_issue_type_screen_scheme`` +* ``get_auto_complete`` +* ``get_auto_complete_post`` +* ``get_field_auto_complete_for_query_string`` +* ``get_precomputations`` +* ``update_precomputations`` +* ``get_precomputations_by_id`` +* ``match_issues`` +* ``parse_jql_queries`` +* ``migrate_queries`` +* ``sanitise_jql_queries`` +* ``get_all_labels`` +* ``get_approximate_license_count`` +* ``get_approximate_application_license_count`` +* ``get_my_permissions`` +* ``remove_preference`` +* ``get_preference`` +* ``set_preference`` +* ``get_locale`` +* ``set_locale`` +* ``get_current_user`` +* ``get_notification_schemes`` +* ``create_notification_scheme`` +* ``get_notification_scheme_to_project_mappings`` +* ``get_notification_scheme`` +* ``update_notification_scheme`` +* ``add_notifications`` +* ``delete_notification_scheme`` +* ``remove_notification_from_notification_scheme`` +* ``get_all_permissions`` +* ``get_bulk_permissions`` +* ``get_permitted_projects`` +* ``get_all_permission_schemes`` +* ``create_permission_scheme`` +* ``delete_permission_scheme`` +* ``get_permission_scheme`` +* ``update_permission_scheme`` +* ``get_permission_scheme_grants`` +* ``create_permission_grant`` +* ``delete_permission_scheme_entity`` +* ``get_permission_scheme_grant`` +* ``get_plans`` +* ``create_plan`` +* ``get_plan`` +* ``update_plan`` +* ``archive_plan`` +* ``duplicate_plan`` +* ``get_teams`` +* ``add_atlassian_team`` +* ``remove_atlassian_team`` +* ``get_atlassian_team`` +* ``update_atlassian_team`` +* ``create_plan_only_team`` +* ``delete_plan_only_team`` +* ``get_plan_only_team`` +* ``update_plan_only_team`` +* ``trash_plan`` +* ``get_priorities`` +* ``create_priority`` +* ``set_default_priority`` +* ``move_priorities`` +* ``search_priorities`` +* ``delete_priority`` +* ``get_priority`` +* ``update_priority`` +* ``get_priority_schemes`` +* ``create_priority_scheme`` +* ``suggested_priorities_for_mappings`` +* ``get_available_priorities_by_priority_scheme`` +* ``delete_priority_scheme`` +* ``update_priority_scheme`` +* ``get_priorities_by_priority_scheme`` +* ``get_projects_by_priority_scheme`` +* ``get_all_projects`` +* ``create_project`` +* ``create_project_with_custom_template`` +* ``edit_template`` +* ``live_template`` +* ``remove_template`` +* ``save_template`` +* ``get_recent`` +* ``search_projects`` +* ``get_all_project_types`` +* ``get_all_accessible_project_types`` +* ``get_project_type_by_key`` +* ``get_accessible_project_type_by_key`` +* ``delete_project`` +* ``get_project`` +* ``update_project`` +* ``archive_project`` +* ``update_project_avatar`` +* ``delete_project_avatar`` +* ``create_project_avatar`` +* ``get_all_project_avatars`` +* ``get_project_classification_config`` +* ``remove_default_project_classification`` +* ``get_default_project_classification`` +* ``update_default_project_classification`` +* ``get_project_components_paginated`` +* ``get_project_components`` +* ``delete_project_asynchronously`` +* ``get_features_for_project`` +* ``toggle_feature_for_project`` +* ``get_project_property_keys`` +* ``delete_project_property`` +* ``get_project_property`` +* ``set_project_property`` +* ``restore`` +* ``get_project_roles`` +* ``delete_actor`` +* ``get_project_role`` +* ``add_actor_users`` +* ``set_actors`` +* ``get_project_role_details`` +* ``get_all_statuses`` +* ``get_project_versions_paginated`` +* ``get_project_versions`` +* ``get_project_email`` +* ``update_project_email`` +* ``get_hierarchy`` +* ``get_project_issue_security_scheme`` +* ``get_notification_scheme_for_project`` +* ``get_assigned_permission_scheme`` +* ``assign_permission_scheme`` +* ``get_security_levels_for_project`` +* ``get_all_project_categories`` +* ``create_project_category`` +* ``remove_project_category`` +* ``get_project_category_by_id`` +* ``update_project_category`` +* ``get_project_fields`` +* ``validate_project_key`` +* ``get_valid_project_key`` +* ``get_valid_project_name`` +* ``redact`` +* ``get_redaction_status`` +* ``get_resolutions`` +* ``create_resolution`` +* ``set_default_resolution`` +* ``move_resolutions`` +* ``search_resolutions`` +* ``delete_resolution`` +* ``get_resolution`` +* ``update_resolution`` +* ``get_all_project_roles`` +* ``create_project_role`` +* ``delete_project_role`` +* ``get_project_role_by_id`` +* ``partial_update_project_role`` +* ``fully_update_project_role`` +* ``delete_project_role_actors_from_role`` +* ``get_project_role_actors_for_role`` +* ``add_project_role_actors_to_role`` +* ``get_screens`` +* ``create_screen`` +* ``add_field_to_default_screen`` +* ``get_bulk_screen_tabs`` +* ``delete_screen`` +* ``update_screen`` +* ``get_available_screen_fields`` +* ``get_all_screen_tabs`` +* ``add_screen_tab`` +* ``delete_screen_tab`` +* ``rename_screen_tab`` +* ``get_all_screen_tab_fields`` +* ``add_screen_tab_field`` +* ``remove_screen_tab_field`` +* ``move_screen_tab_field`` +* ``move_screen_tab`` +* ``get_screen_schemes`` +* ``create_screen_scheme`` +* ``delete_screen_scheme`` +* ``update_screen_scheme`` +* ``search_for_issues_using_jql`` +* ``search_for_issues_using_jql_post`` +* ``count_issues`` +* ``search_and_reconsile_issues_using_jql`` +* ``search_and_reconsile_issues_using_jql_post`` +* ``get_issue_security_level`` +* ``get_server_info`` +* ``get_issue_navigator_default_columns`` +* ``set_issue_navigator_default_columns`` +* ``get_statuses`` +* ``get_status`` +* ``get_status_categories`` +* ``get_status_category`` +* ``delete_statuses_by_id`` +* ``get_statuses_by_id`` +* ``create_statuses`` +* ``update_statuses`` +* ``get_statuses_by_name`` +* ``search`` +* ``get_project_issue_type_usages_for_status`` +* ``get_project_usages_for_status`` +* ``get_workflow_usages_for_status`` +* ``get_task`` +* ``cancel_task`` +* ``get_ui_modifications`` +* ``create_ui_modification`` +* ``delete_ui_modification`` +* ``update_ui_modification`` +* ``get_avatars`` +* ``store_avatar`` +* ``delete_avatar`` +* ``get_avatar_image_by_type`` +* ``get_avatar_image_by_id`` +* ``get_avatar_image_by_owner`` +* ``remove_user`` +* ``get_user`` +* ``create_user`` +* ``find_bulk_assignable_users`` +* ``find_assignable_users`` +* ``bulk_get_users`` +* ``bulk_get_users_migration`` +* ``reset_user_columns`` +* ``get_user_default_columns`` +* ``set_user_columns`` +* ``get_user_email`` +* ``get_user_email_bulk`` +* ``get_user_groups`` +* ``find_users_with_all_permissions`` +* ``find_users_for_picker`` +* ``get_user_property_keys`` +* ``delete_user_property`` +* ``get_user_property`` +* ``set_user_property`` +* ``find_users`` +* ``find_users_by_query`` +* ``find_user_keys_by_query`` +* ``find_users_with_browse_permission`` +* ``get_all_users_default`` +* ``get_all_users`` +* ``create_version`` +* ``delete_version`` +* ``get_version`` +* ``update_version`` +* ``merge_versions`` +* ``move_version`` +* ``get_version_related_issues`` +* ``get_related_work`` +* ``create_related_work`` +* ``update_related_work`` +* ``delete_and_replace_version`` +* ``get_version_unresolved_issues`` +* ``delete_related_work`` +* ``delete_webhook_by_id`` +* ``get_dynamic_webhooks_for_app`` +* ``register_dynamic_webhooks`` +* ``get_failed_webhooks`` +* ``refresh_webhooks`` +* ``read_workflow_from_history`` +* ``list_workflow_history`` +* ``get_workflow_transition_rule_configurations`` +* ``update_workflow_transition_rule_configurations`` +* ``delete_workflow_transition_rule_configurations`` +* ``get_workflows_paginated`` +* ``delete_inactive_workflow`` +* ``get_workflow_project_issue_type_usages`` +* ``get_project_usages_for_workflow`` +* ``get_workflow_scheme_usages_for_workflow`` +* ``read_workflows`` +* ``workflow_capabilities`` +* ``create_workflows`` +* ``validate_create_workflows`` +* ``get_default_editor`` +* ``read_workflow_previews`` +* ``search_workflows`` +* ``update_workflows`` +* ``validate_update_workflows`` +* ``get_all_workflow_schemes`` +* ``create_workflow_scheme`` +* ``get_workflow_scheme_project_associations`` +* ``assign_scheme_to_project`` +* ``switch_workflow_scheme_for_project`` +* ``read_workflow_schemes`` +* ``update_schemes`` +* ``get_required_workflow_scheme_mappings`` +* ``delete_workflow_scheme`` +* ``get_workflow_scheme`` +* ``update_workflow_scheme`` +* ``create_workflow_scheme_draft_from_parent`` +* ``delete_default_workflow`` +* ``get_default_workflow`` +* ``update_default_workflow`` +* ``delete_workflow_scheme_draft`` +* ``get_workflow_scheme_draft`` +* ``update_workflow_scheme_draft`` +* ``delete_draft_default_workflow`` +* ``get_draft_default_workflow`` +* ``update_draft_default_workflow`` +* ``delete_workflow_scheme_draft_issue_type`` +* ``get_workflow_scheme_draft_issue_type`` +* ``set_workflow_scheme_draft_issue_type`` +* ``publish_draft_workflow_scheme`` +* ``delete_draft_workflow_mapping`` +* ``get_draft_workflow`` +* ``update_draft_workflow_mapping`` +* ``delete_workflow_scheme_issue_type`` +* ``get_workflow_scheme_issue_type`` +* ``set_workflow_scheme_issue_type`` +* ``delete_workflow_mapping`` +* ``get_workflow`` +* ``update_workflow_mapping`` +* ``get_project_usages_for_workflow_scheme`` +* ``get_ids_of_worklogs_deleted_since`` +* ``get_worklogs_for_ids`` +* ``get_ids_of_worklogs_modified_since`` +* ``addon_properties_resource_get_addon_properties_get`` +* ``addon_properties_resource_delete_addon_property_delete`` +* ``addon_properties_resource_get_addon_property_get`` +* ``addon_properties_resource_put_addon_property_put`` +* ``dynamic_modules_resource_remove_modules_delete`` +* ``dynamic_modules_resource_get_modules_get`` +* ``dynamic_modules_resource_register_modules_post`` +* ``app_issue_field_value_update_resource_update_issue_fields_put`` +* ``migration_resource_update_entity_properties_value_put`` +* ``migration_resource_workflow_rule_search_post`` +* ``connect_to_forge_migration_fetch_task_resource_fetch_migration_task_get`` +* ``connect_to_forge_migration_task_submission_resource_submit_task_post`` +* ``service_registry_resource_services_get`` +* ``get_forge_app_property_keys`` +* ``delete_forge_app_property`` +* ``get_forge_app_property`` +* ``put_forge_app_property`` +* ``get_worklogs_by_issue_id_and_worklog_id`` + +Jira Software Cloud +------------------- + +* ``move_issues_to_backlog`` +* ``move_issues_to_backlog_for_board`` +* ``get_all_boards`` +* ``create_board`` +* ``get_board_by_filter_id`` +* ``delete_board`` +* ``get_board`` +* ``get_issues_for_backlog`` +* ``get_issues_for_backlog_jsis`` +* ``get_approximate_issue_count_for_backlog`` +* ``get_configuration`` +* ``get_epics`` +* ``get_issues_without_epic_for_board`` +* ``get_issues_without_epic_for_board_jsis`` +* ``get_board_issues_for_epic`` +* ``get_board_issues_for_epic_jsis`` +* ``get_features_for_board`` +* ``toggle_features`` +* ``get_issues_for_board`` +* ``move_issues_to_board`` +* ``get_issues_for_board_jsis`` +* ``get_approximate_issue_count_for_board`` +* ``get_projects`` +* ``get_projects_full`` +* ``get_board_property_keys`` +* ``delete_board_property`` +* ``get_board_property`` +* ``set_board_property`` +* ``get_all_quick_filters`` +* ``get_quick_filter`` +* ``get_reports_for_board`` +* ``get_all_sprints`` +* ``get_board_issues_for_sprint`` +* ``get_board_issues_for_sprint_jsis`` +* ``get_all_versions`` +* ``get_issues_without_epic`` +* ``remove_issues_from_epic`` +* ``get_issues_without_epic_jsis`` +* ``get_epic`` +* ``partially_update_epic`` +* ``get_issues_for_epic`` +* ``move_issues_to_epic`` +* ``get_issues_for_epic_jsis`` +* ``rank_epics`` +* ``rank_issues`` +* ``get_issue`` +* ``get_issue_estimation_for_board`` +* ``estimate_issue_for_board`` +* ``create_sprint`` +* ``delete_sprint`` +* ``get_sprint`` +* ``partially_update_sprint`` +* ``update_sprint`` +* ``get_issues_for_sprint`` +* ``move_issues_to_sprint_and_rank`` +* ``get_issues_for_sprint_jsis`` +* ``get_properties_keys`` +* ``delete_property`` +* ``get_property`` +* ``set_property`` +* ``swap_sprint`` +* ``store_development_information`` +* ``get_repository`` +* ``delete_repository`` +* ``delete_by_properties`` +* ``exists_by_properties`` +* ``delete_entity`` +* ``submit_feature_flags`` +* ``delete_feature_flags_by_property`` +* ``get_feature_flag_by_id`` +* ``delete_feature_flag_by_id`` +* ``submit_deployments`` +* ``delete_deployments_by_property`` +* ``get_deployment_by_key`` +* ``delete_deployment_by_key`` +* ``get_deployment_gating_status_by_key`` +* ``submit_builds`` +* ``delete_builds_by_property`` +* ``get_build_by_key`` +* ``delete_build_by_key`` +* ``submit_remote_links`` +* ``delete_remote_links_by_property`` +* ``get_remote_link_by_id`` +* ``delete_remote_link_by_id`` +* ``submit_workspaces`` +* ``delete_linked_workspaces`` +* ``get_linked_workspaces`` +* ``get_linked_workspace_by_id`` +* ``submit_vulnerabilities`` +* ``delete_vulnerabilities_by_property`` +* ``get_vulnerability_by_id`` +* ``delete_vulnerability_by_id`` +* ``submit_operations_workspaces`` +* ``delete_workspaces`` +* ``get_workspaces`` +* ``submit_entity`` +* ``delete_entity_by_property`` +* ``get_incident_by_id`` +* ``delete_incident_by_id`` +* ``get_review_by_id`` +* ``delete_review_by_id`` +* ``submit_components`` +* ``delete_components_by_property`` +* ``get_component_by_id`` +* ``delete_component_by_id`` + +Jira Service Management Cloud +----------------------------- + +* ``get_assets_workspaces`` +* ``create_customer_customer_post`` +* ``create_customer_customer_skip_permission_check_post`` +* ``revoke_portal_only_access_for_user`` +* ``get_info`` +* ``get_insight_workspaces`` +* ``get_articles_knowledgebase_article_get`` +* ``view_article`` +* ``get_organizations_organization_get`` +* ``create_organization`` +* ``delete_organization`` +* ``get_organization`` +* ``get_properties_keys_organization_organization_id_property_get`` +* ``delete_property_organization_organization_id_property_property_key_delete`` +* ``get_property_organization_organization_id_property_property_key_get`` +* ``set_property_organization_organization_id_property_property_key_put`` +* ``remove_users_from_organization`` +* ``get_users_in_organization`` +* ``add_users_to_organization`` +* ``get_customer_requests`` +* ``create_customer_request`` +* ``validate_customer_request`` +* ``get_customer_request_by_id_or_key`` +* ``get_approvals`` +* ``get_approval_by_id`` +* ``answer_approval`` +* ``get_attachments_for_request`` +* ``create_comment_with_attachment`` +* ``get_attachment_content`` +* ``get_attachment_thumbnail`` +* ``get_request_comments`` +* ``create_request_comment`` +* ``get_request_comment_by_id`` +* ``get_comment_attachments`` +* ``unsubscribe`` +* ``get_subscription_status`` +* ``subscribe`` +* ``remove_request_participants`` +* ``get_request_participants`` +* ``add_request_participants`` +* ``get_sla_information`` +* ``get_sla_information_by_id`` +* ``get_customer_request_status`` +* ``get_customer_transitions`` +* ``perform_customer_transition`` +* ``delete_feedback`` +* ``get_feedback`` +* ``post_feedback`` +* ``get_all_request_types`` +* ``get_service_desks`` +* ``get_service_desk_by_id`` +* ``attach_temporary_file`` +* ``remove_customers`` +* ``get_customers`` +* ``add_customers_servicedesk_service_desk_id_customer_post`` +* ``invite_customer`` +* ``add_customers_servicedesk_service_desk_id_customer_skip_`` + ``permission_check_post`` +* ``get_articles_servicedesk_service_desk_id_knowledgebase_article_get`` +* ``remove_organization`` +* ``get_organizations_servicedesk_service_desk_id_organization_get`` +* ``add_organization`` +* ``get_queues`` +* ``get_queue`` +* ``get_issues_in_queue`` +* ``get_request_types`` +* ``create_request_type`` +* ``check_request_type_permissions`` +* ``delete_request_type`` +* ``get_request_type_by_id`` +* ``get_request_type_fields`` +* ``get_properties_keys_servicedesk_service_desk_id_`` + ``requesttype_request_type_id_property_get`` +* ``delete_property_servicedesk_service_desk_id_requesttype_`` + ``request_type_id_property_property_key_delete`` +* ``get_property_servicedesk_service_desk_id_requesttype_`` + ``request_type_id_property_property_key_get`` +* ``set_property_servicedesk_service_desk_id_requesttype_`` + ``request_type_id_property_property_key_put`` +* ``get_request_type_groups`` diff --git a/tests/test_jira_cloud_clients.py b/tests/test_jira_cloud_clients.py new file mode 100644 index 000000000..7dde6d8a2 --- /dev/null +++ b/tests/test_jira_cloud_clients.py @@ -0,0 +1,77 @@ +from unittest import TestCase +from unittest.mock import patch + +from atlassian import Jira, JiraCloud, JiraServer, JiraServiceManagement, JiraSoftware, ServiceDesk, create_jira_cloud +from atlassian.jira import Jira as PackageJira + + +class TestJiraCloudClients(TestCase): + def test_legacy_clients_keep_their_defaults(self): + jira = Jira("https://example.atlassian.net") + service_desk = ServiceDesk("https://example.atlassian.net") + + self.assertEqual(jira.api_version, "2") + self.assertFalse(jira.cloud) + self.assertFalse(service_desk.cloud) + self.assertIs(Jira, JiraServer) + self.assertIs(PackageJira, JiraServer) + + def test_core_client_defaults_to_v3_and_forces_cloud_mode(self): + jira = JiraCloud("https://example.atlassian.net", cloud=False) + + self.assertEqual(jira.api_version, 3) + self.assertTrue(jira.cloud) + self.assertEqual(jira.endpoint("issue/ABC-1"), "rest/api/3/issue/ABC-1") + self.assertEqual(jira.endpoint("issue/ABC-1", api_version=2), "rest/api/2/issue/ABC-1") + + def test_core_client_rejects_unsupported_versions(self): + with self.assertRaisesRegex(ValueError, "must be 2 or 3"): + JiraCloud("https://example.atlassian.net", api_version=1) + + def test_core_factory_is_explicit_and_versioned(self): + jira = create_jira_cloud("https://example.atlassian.net", api_version="2") + + self.assertIsInstance(jira, JiraCloud) + self.assertEqual(jira.api_version, 2) + + def test_core_methods_cover_the_supplied_v3_document(self): + jira = JiraCloud("https://example.atlassian.net") + + with patch.object(jira, "get", return_value={}) as get: + jira.get_issue("ABC-1", fields="summary") + get.assert_called_once_with("rest/api/3/issue/ABC-1", params={"fields": "summary"}, data=None) + + def test_core_methods_honor_the_selected_v2_route(self): + jira = JiraCloud("https://example.atlassian.net", api_version=2) + + with patch.object(jira, "get", return_value={}) as get: + jira.get_issue("ABC-1") + get.assert_called_once_with("rest/api/2/issue/ABC-1", params=None, data=None) + + def test_software_client_builds_each_documented_api_root(self): + jira = JiraSoftware("https://example.atlassian.net", cloud=False) + + self.assertTrue(jira.cloud) + self.assertEqual(jira.endpoint("agile", "board/42/sprint"), "rest/agile/1.0/board/42/sprint") + self.assertEqual(jira.endpoint("software", "board/42/backlog"), "rest/software/1.0/board/42/backlog") + self.assertEqual(jira.endpoint("devinfo", "bulk"), "rest/devinfo/0.10/bulk") + self.assertEqual(jira.endpoint("builds", "bulk"), "rest/builds/0.1/bulk") + with patch.object(jira, "get", return_value={}) as get: + jira.get_all_boards(max_results=10) + get.assert_called_once_with("rest/agile/1.0/board", params={"maxResults": 10}, data=None) + + def test_software_client_rejects_unknown_api_root(self): + jira = JiraSoftware("https://example.atlassian.net") + + with self.assertRaisesRegex(ValueError, "Unsupported Jira Software API"): + jira.endpoint("api/3", "issue") + + def test_jsm_client_is_independent_from_the_legacy_service_desk_client(self): + service_management = JiraServiceManagement("https://example.atlassian.net", cloud=False) + + self.assertNotIsInstance(service_management, ServiceDesk) + self.assertTrue(service_management.cloud) + self.assertEqual(service_management.endpoint("request/ABC-1"), "rest/servicedeskapi/request/ABC-1") + with patch.object(service_management, "get", return_value={}) as get: + service_management.get_customer_request_by_id_or_key("ABC-1") + get.assert_called_once_with("rest/servicedeskapi/request/ABC-1", params=None, data=None) diff --git a/tests/test_jira_cloud_method_coverage.py b/tests/test_jira_cloud_method_coverage.py new file mode 100644 index 000000000..395dc9ed6 --- /dev/null +++ b/tests/test_jira_cloud_method_coverage.py @@ -0,0 +1,62 @@ +"""Structural tests for the complete, concrete Jira Cloud method surface.""" + +import inspect +from unittest import TestCase + +from atlassian.jira.core_methods import JiraCloudCoreMethods +from atlassian.jira.service_management_methods import JiraServiceManagementMethods +from atlassian.jira.software_methods import JiraSoftwareMethods + + +class RecordingClient: + """Minimal REST client that records requests made by generated methods.""" + + api_version = 3 + + def __init__(self): + self.calls = [] + + def resource_url(self, resource, api_root=None, api_version=None): + return "/".join(str(part).strip("/") for part in (api_root, api_version, resource) if part is not None) + + def __getattr__(self, method): + if method not in {"get", "post", "put", "delete", "patch"}: + raise AttributeError(method) + + def request(url, **kwargs): + self.calls.append((method, url, kwargs)) + return {"method": method, "url": url} + + return request + + +class TestJiraCloudMethodCoverage(TestCase): + METHOD_GROUPS = ( + (JiraCloudCoreMethods, 617, "rest/api/3/"), + (JiraSoftwareMethods, 105, "rest/"), + (JiraServiceManagementMethods, 75, "rest/servicedeskapi/"), + ) + + @staticmethod + def _required_arguments(method): + signature = inspect.signature(method) + return [ + "value" + for parameter in list(signature.parameters.values())[1:] + if parameter.default is inspect.Parameter.empty + and parameter.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD) + ] + + def test_every_documented_method_is_concrete_and_dispatches(self): + for method_group, expected_count, prefix in self.METHOD_GROUPS: + methods = [method for _, method in inspect.getmembers(method_group, inspect.isfunction)] + self.assertEqual(len(methods), expected_count) + + for method in methods: + client = RecordingClient() + result = method(client, *self._required_arguments(method)) + + self.assertEqual(result["method"], client.calls[0][0]) + self.assertTrue(client.calls[0][1].startswith(prefix)) + self.assertNotIn("{", client.calls[0][1]) + self.assertEqual(client.calls[0][2]["data"], None) diff --git a/tests/test_rest_client.py b/tests/test_rest_client.py index 61d2ca5ab..ad3f48656 100644 --- a/tests/test_rest_client.py +++ b/tests/test_rest_client.py @@ -144,6 +144,20 @@ def request(**kwargs): assert captured["url"].endswith("tasks?task-id=1&task-id=2") + def test_request_serializes_boolean_data_for_requests(self, monkeypatch): + captured = {} + + def request(**kwargs): + captured.update(kwargs) + return SimpleNamespace(status_code=200, reason="OK", text="", encoding=None) + + monkeypatch.setattr(self.api._session, "request", request) + monkeypatch.setattr(self.api, "raise_for_status", lambda _response: None) + + self.api.request("POST", "content", data=True, advanced_mode=True) + + assert captured["data"] == "true" + def test_init_with_cert(self): """Test initialization with certificate""" api = AtlassianRestAPI(url=f"{mockup_server()}/test", cert=("/path/to/cert.pem", "/path/to/key.pem"))