From ddf9ab5480b81330dd85f459c4039da746022bb5 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 6 Jun 2024 18:24:59 -0500 Subject: [PATCH 01/15] adding type hints --- atlassian/confluence.py | 2 +- atlassian/jira.py | 1141 +++++++++++++----------- atlassian/py.typed | 0 atlassian/rest_client.py | 475 ++++++++-- atlassian/typehints.py | 9 + examples/jira/jira_review_groups.py | 12 +- tests/test_confluence_advanced_mode.py | 8 +- 7 files changed, 1040 insertions(+), 607 deletions(-) create mode 100644 atlassian/py.typed create mode 100644 atlassian/typehints.py diff --git a/atlassian/confluence.py b/atlassian/confluence.py index 7c5987392..22f5680e1 100644 --- a/atlassian/confluence.py +++ b/atlassian/confluence.py @@ -1489,7 +1489,7 @@ def set_page_label(self, page_id, label): return response - def remove_page_label(self, page_id, label): + def remove_page_label(self, page_id: str, label: str): """ Delete Confluence page label :param page_id: content_id format diff --git a/atlassian/jira.py b/atlassian/jira.py index f4056bda0..8c398fb35 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -1,14 +1,21 @@ # coding=utf-8 +from __future__ import annotations + import logging -import re import os +import re +from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast from warnings import warn + from deprecated import deprecated -from requests import HTTPError +from requests import HTTPError, Response from .errors import ApiNotFoundError, ApiPermissionError from .rest_client import AtlassianRestAPI +if TYPE_CHECKING: + from .typehints import T_id + log = logging.getLogger(__name__) @@ -18,7 +25,7 @@ class Jira(AtlassianRestAPI): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2 """ - def __init__(self, url, *args, **kwargs): + def __init__(self, url: str, *args: Any, **kwargs: Any): if "api_version" not in kwargs: kwargs["api_version"] = "2" @@ -26,12 +33,12 @@ def __init__(self, url, *args, **kwargs): def _get_paged( self, - url, - params=None, - data=None, - flags=None, - trailing=None, - absolute=False, + url: str, + params: dict | None = None, + data: dict | None = None, + flags: list | None = None, + trailing: bool | None = None, + absolute: bool = False, ): """ Used to get the paged data @@ -51,13 +58,16 @@ def _get_paged( params = {} while True: - response = super(Jira, self).get( - url, - trailing=trailing, - params=params, - data=data, - flags=flags, - absolute=absolute, + response = cast( + dict, + super(Jira, self).get( + url, + trailing=trailing, + params=params, + data=data, + flags=flags, + absolute=absolute, + ), ) values = response.get("values", []) for value in values: @@ -66,7 +76,7 @@ def _get_paged( if response.get("isLast", False) or len(values) == 0: break - url = response.get("nextPage") + url = cast(str, response.get("nextPage")) if url is None: break # From now on we have absolute URLs with parameters @@ -82,12 +92,12 @@ def _get_paged( def get_permissions( self, - permissions, - project_id=None, - project_key=None, - issue_id=None, - issue_key=None, - ): + permissions: str, + project_id: T_id | None = None, + project_key: T_id | None = None, + issue_id: T_id | None = None, + issue_key: T_id | None = None, + ) -> dict | None: """ Returns a list of permissions indicating which permissions the user has. Details of the user's permissions can be obtained in a global, project, issue or comment context. @@ -122,7 +132,7 @@ def get_permissions( """ url = self.resource_url("mypermissions") - params = {"permissions": permissions} + params: dict[str, str | int] = {"permissions": permissions} if project_id: params["projectId"] = project_id @@ -149,7 +159,7 @@ def get_all_permissions(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/application-properties """ - def get_property(self, key=None, permission_level=None, key_filter=None): + def get_property(self, key: T_id | None = None, permission_level: str | None = None, key_filter: str | None = None): """ Returns an application property :param key: str @@ -159,7 +169,7 @@ def get_property(self, key=None, permission_level=None, key_filter=None): """ url = self.resource_url("application-properties") - params = {} + params: dict = {} if key: params["key"] = key @@ -170,7 +180,7 @@ def get_property(self, key=None, permission_level=None, key_filter=None): return self.get(url, params=params) - def set_property(self, property_id, value): + def set_property(self, property_id: T_id, value: str): """ Modify an application property via PUT. The "value" field present in the PUT will override the existing value. :param property_id: @@ -183,7 +193,7 @@ def set_property(self, property_id, value): return self.put(url, data=data) - def get_advanced_settings(self): + def get_advanced_settings(self) -> dict | None: """ Returns the properties that are displayed on the "General Configuration > Advanced Settings" page. :return: @@ -205,7 +215,7 @@ def get_all_application_roles(self): url = self.resource_url("applicationrole") return self.get(url) or {} - def get_application_role(self, role_key): + def get_application_role(self, role_key: str): """ Returns the ApplicationRole with passed key if it exists :param role_key: str @@ -220,7 +230,7 @@ def get_application_role(self, role_key): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/attachment """ - def get_attachments_ids_from_issue(self, issue): + def get_attachments_ids_from_issue(self, issue: T_id) -> list[dict[str, str]]: """ Get attachments IDs from jira issue :param jira issue key: str @@ -232,7 +242,7 @@ def get_attachments_ids_from_issue(self, issue): list_attachments_id.append({"filename": attachment["filename"], "attachment_id": attachment["id"]}) return list_attachments_id - def get_attachment(self, attachment_id): + def get_attachment(self, attachment_id: T_id): """ Returns the meta-data for an attachment, including the URI of the actual attached file :param attachment_id: int @@ -242,7 +252,7 @@ def get_attachment(self, attachment_id): url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def download_attachments_from_issue(self, issue, path=None, cloud=True): + def download_attachments_from_issue(self, issue: T_id, path: str | None = None, cloud: bool = True) -> str | None: """ Downloads all attachments from a Jira issue. :param issue: The issue-key of the Jira issue @@ -280,7 +290,7 @@ def download_attachments_from_issue(self, issue, path=None, cloud=True): except Exception as e: raise e - def get_attachment_content(self, attachment_id): + def get_attachment_content(self, attachment_id: T_id): """ Returns the content for an attachment :param attachment_id: int @@ -290,7 +300,7 @@ def get_attachment_content(self, attachment_id): url = "{base_url}/content/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def remove_attachment(self, attachment_id): + def remove_attachment(self, attachment_id: T_id): """ Remove an attachment from an issue :param attachment_id: int @@ -309,7 +319,7 @@ def get_attachment_meta(self): url = self.resource_url("attachment/meta") return self.get(url) - def get_attachment_expand_human(self, attachment_id): + def get_attachment_expand_human(self, attachment_id: T_id): """ Returns the information for an expandable attachment in human-readable format :param attachment_id: int @@ -319,7 +329,7 @@ def get_attachment_expand_human(self, attachment_id): url = "{base_url}/{attachment_id}/expand/human".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def get_attachment_expand_raw(self, attachment_id): + def get_attachment_expand_raw(self, attachment_id: T_id): """ Returns the information for an expandable attachment in raw format :param attachment_id: int @@ -336,11 +346,11 @@ def get_attachment_expand_raw(self, attachment_id): def get_audit_records( self, - offset=None, - limit=None, - filter=None, - from_date=None, - to_date=None, + offset: int | None = None, + limit: int | None = None, + filter: str | None = None, + from_date: str | None = None, + to_date: str | None = None, ): """ Returns auditing records filtered using provided parameters @@ -357,7 +367,7 @@ def get_audit_records( the 'to' timestamp will be provided in response :return: """ - params = {} + params: dict = {} if offset: params["offset"] = offset if limit: @@ -371,7 +381,7 @@ def get_audit_records( url = self.resource_url("auditing/record") return self.get(url, params=params) or {} - def post_audit_record(self, audit_record): + def post_audit_record(self, audit_record: dict | str): """ Store a record in Audit Log :param audit_record: json with compat https://docs.atlassian.com/jira/REST/schema/audit-record# @@ -385,7 +395,7 @@ def post_audit_record(self, audit_record): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/avatar """ - def get_all_system_avatars(self, avatar_type="user"): + def get_all_system_avatars(self, avatar_type: str = "user"): """ Returns all system avatars of the given type. :param avatar_type: @@ -405,7 +415,7 @@ def get_cluster_all_nodes(self): url = self.resource_url("cluster/nodes") return self.get(url) - def delete_cluster_node(self, node_id): + def delete_cluster_node(self, node_id: T_id): """ Delete the node from the cluster if state of node is OFFLINE :param node_id: str @@ -415,7 +425,7 @@ def delete_cluster_node(self, node_id): url = "{base_url}/{node_id}".format(base_url=base_url, node_id=node_id) return self.delete(url) - def set_node_to_offline(self, node_id): + def set_node_to_offline(self, node_id: T_id): """ Change the node's state to offline if the node is reporting as active, but is not alive :param node_id: str @@ -432,7 +442,7 @@ def get_cluster_alive_nodes(self): """ return [_ for _ in self.get_cluster_all_nodes() if _["alive"]] - def request_current_index_from_node(self, node_id): + def request_current_index_from_node(self, node_id: T_id): """ Request current index from node (the request is processed asynchronously) :return: @@ -446,7 +456,7 @@ def request_current_index_from_node(self, node_id): Reference: https://confluence.atlassian.com/support/create-a-support-zip-using-the-rest-api-in-data-center-applications-952054641.html """ - def generate_support_zip_on_nodes(self, node_ids): + def generate_support_zip_on_nodes(self, node_ids: list): """ Generate a support zip on targeted nodes of a cluster :param node_ids: list @@ -456,7 +466,7 @@ def generate_support_zip_on_nodes(self, node_ids): url = "/rest/troubleshooting/latest/support-zip/cluster" return self.post(url, data=data) - def check_support_zip_status(self, cluster_task_id): + def check_support_zip_status(self, cluster_task_id: T_id): """ Check status of support zip creation task :param cluster_task_id: str @@ -465,7 +475,7 @@ def check_support_zip_status(self, cluster_task_id): url = "/rest/troubleshooting/latest/support-zip/status/cluster/{}".format(cluster_task_id) return self.get(url) - def download_support_zip(self, file_name): + def download_support_zip(self, file_name: str): """ Download created support zip file :param file_name: str @@ -484,7 +494,7 @@ def get_cluster_zdu_state(self): return self.get(url) # Issue Comments - def issue_get_comments(self, issue_id): + def issue_get_comments(self, issue_id: T_id): """ Get Comments on an Issue. :param issue_id: Issue ID @@ -509,7 +519,7 @@ def issues_get_comments_by_id(self, *args): url = "{base_url}/list".format(base_url=base_url) return self.post(url, data=data) - def issue_get_comment(self, issue_id, comment_id): + def issue_get_comment(self, issue_id: T_id, comment_id: T_id): """ Get a single comment :param issue_id: int or str @@ -528,7 +538,7 @@ def issue_get_comment(self, issue_id, comment_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/comment/{commentId}/properties """ - def get_comment_properties_keys(self, comment_id): + def get_comment_properties_keys(self, comment_id: T_id): """ Returns the keys of all properties for the comment identified by the key or by the id. :param comment_id: @@ -538,7 +548,7 @@ def get_comment_properties_keys(self, comment_id): url = "{base_url}/{commentId}/properties".format(base_url=base_url, commentId=comment_id) return self.get(url) - def get_comment_property(self, comment_id, property_key): + def get_comment_property(self, comment_id: T_id, property_key: str): """ Returns the value a property for a comment :param comment_id: int @@ -551,7 +561,7 @@ def get_comment_property(self, comment_id, property_key): ) return self.get(url) - def set_comment_property(self, comment_id, property_key, value_property): + def set_comment_property(self, comment_id: T_id, property_key: str, value_property: object): """ Returns the keys of all properties for the comment identified by the key or by the id. :param comment_id: int @@ -566,7 +576,7 @@ def set_comment_property(self, comment_id, property_key, value_property): data = {"value": value_property} return self.put(url, data=data) - def delete_comment_property(self, comment_id, property_key): + def delete_comment_property(self, comment_id: T_id, property_key: str): """ Deletes a property for a comment :param comment_id: int @@ -584,11 +594,11 @@ def delete_comment_property(self, comment_id, property_key): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/component """ - def component(self, component_id): + def component(self, component_id: T_id): base_url = self.resource_url("component") return self.get("{base_url}/{component_id}".format(base_url=base_url, component_id=component_id)) - def get_component_related_issues(self, component_id): + def get_component_related_issues(self, component_id: T_id): """ Returns counts of issues related to this component. :param component_id: @@ -598,23 +608,23 @@ def get_component_related_issues(self, component_id): url = "{base_url}/{component_id}/relatedIssueCounts".format(base_url=base_url, component_id=component_id) return self.get(url) - def create_component(self, component): + def create_component(self, component: dict): log.warning('Creating component "%s"', component["name"]) base_url = self.resource_url("component") url = "{base_url}/".format(base_url=base_url) return self.post(url, data=component) - def update_component(self, component, component_id): + def update_component(self, component: dict, component_id: T_id): base_url = self.resource_url("component") url = "{base_url}/{component_id}".format(base_url=base_url, component_id=component_id) return self.put(url, data=component) - def delete_component(self, component_id): + def delete_component(self, component_id: T_id): log.warning('Deleting component "%s"', component_id) base_url = self.resource_url("component") return self.delete("{base_url}/{component_id}".format(base_url=base_url, component_id=component_id)) - def update_component_lead(self, component_id, lead): + def update_component_lead(self, component_id: T_id, lead: str): data = {"id": component_id, "leadUserName": lead} base_url = self.resource_url("component") return self.put( @@ -643,7 +653,7 @@ def get_configurations_of_jira(self): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/field """ - def get_custom_field_option(self, option_id): + def get_custom_field_option(self, option_id: T_id): """ Returns a full representation of the Custom Field Option that has the given id. :param option_id: @@ -653,7 +663,7 @@ def get_custom_field_option(self, option_id): url = "{base_url}/{id}".format(base_url=base_url, id=option_id) return self.get(url) - def get_custom_fields(self, search=None, start=1, limit=50): + def get_custom_fields(self, search: str | None = None, start: int = 1, limit: int = 50): """ Get custom fields. Evaluated on 7.12 :param search: str @@ -662,7 +672,7 @@ def get_custom_fields(self, search=None, start=1, limit=50): :return: """ url = self.resource_url("customFields") - params = {} + params: dict = {} if search: params["search"] = search if start: @@ -679,7 +689,7 @@ def get_all_fields(self): url = self.resource_url("field") return self.get(url) - def create_custom_field(self, name, type, search_key=None, description=None): + def create_custom_field(self, name: str, type: str, search_key: str | None = None, description: str | None = None): """ Creates a custom field with the given name and type :param name: str - name of the custom field @@ -695,7 +705,7 @@ def create_custom_field(self, name, type, search_key=None, description=None): data["description"] = description return self.post(url, data=data) - def get_custom_field_option_context(self, field_id, context_id): + def get_custom_field_option_context(self, field_id: T_id, context_id: T_id): """ Gets the current values of a custom field :param field_id: @@ -710,7 +720,7 @@ def get_custom_field_option_context(self, field_id, context_id): ) return self.get(url) - def add_custom_field_option(self, field_id, context_id, options): + def add_custom_field_option(self, field_id: T_id, context_id: T_id, options: list): """ Adds the values given to the custom field Administrator permission required @@ -721,7 +731,7 @@ def add_custom_field_option(self, field_id, context_id, options): Reference: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issue-custom-field-options/#api-rest-api-2-field-fieldid-context-contextid-option-post """ - data = {"options": []} + data: dict = {"options": []} for i in options: data["options"].append({"disabled": "false", "value": i}) @@ -736,7 +746,7 @@ def add_custom_field_option(self, field_id, context_id, options): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/dashboard """ - def get_dashboards(self, filter="", start=0, limit=10): + def get_dashboards(self, filter: str = "", start: int = 0, limit: int = 10): """ Returns a list of all dashboards, optionally filtering them. :param filter: OPTIONAL: an optional filter that is applied to the list of dashboards. @@ -751,7 +761,7 @@ def get_dashboards(self, filter="", start=0, limit=10): the value that is effectively being used. :return: """ - params = {} + params: dict = {} if filter: params["filter"] = filter if start: @@ -761,7 +771,7 @@ def get_dashboards(self, filter="", start=0, limit=10): url = self.resource_url("dashboard") return self.get(url, params=params) - def get_dashboard(self, dashboard_id): + def get_dashboard(self, dashboard_id: T_id): """ Returns a single dashboard @@ -776,7 +786,7 @@ def get_dashboard(self, dashboard_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/filter """ - def create_filter(self, name, jql, description=None, favourite=False): + def create_filter(self, name: str, jql: str, description: str | None = None, favourite: bool = False): """ :param name: str :param jql: str @@ -792,7 +802,14 @@ def create_filter(self, name, jql, description=None, favourite=False): url = self.resource_url("filter") return self.post(url, data=data) - def edit_filter(self, filter_id, name, jql=None, description=None, favourite=None): + def edit_filter( + self, + filter_id: T_id, + name: str, + jql: str | None = None, + description: str | None = None, + favourite: bool | None = None, + ): """ Updates an existing filter. :param filter_id: Filter ID @@ -802,7 +819,7 @@ def edit_filter(self, filter_id, name, jql=None, description=None, favourite=Non :param favourite: Indicates if filter is selected as favorite :return: Returns updated filter information """ - data = {"name": name} + data: dict = {"name": name} if jql: data["jql"] = jql if description: @@ -813,7 +830,7 @@ def edit_filter(self, filter_id, name, jql=None, description=None, favourite=Non url = "{base_url}/{id}".format(base_url=base_url, id=filter_id) return self.put(url, data=data) - def get_filter(self, filter_id): + def get_filter(self, filter_id: T_id): """ Returns a full representation of a filter that has the given id. :param filter_id: @@ -823,7 +840,7 @@ def get_filter(self, filter_id): url = "{base_url}/{id}".format(base_url=base_url, id=filter_id) return self.get(url) - def update_filter(self, filter_id, jql, **kwargs): + def update_filter(self, filter_id: T_id, jql: str, **kwargs): """ :param filter_id: int :param jql: str @@ -839,7 +856,7 @@ def update_filter(self, filter_id, jql, **kwargs): url = "{base_url}/{id}".format(base_url=base_url, id=filter_id) return self.put(url, data=data) - def delete_filter(self, filter_id): + def delete_filter(self, filter_id: T_id): """ Deletes a filter that has the given id. :param filter_id: @@ -849,7 +866,7 @@ def delete_filter(self, filter_id): url = "{base_url}/{id}".format(base_url=base_url, id=filter_id) return self.delete(url) - def get_filter_share_permissions(self, filter_id): + def get_filter_share_permissions(self, filter_id: T_id): """ Gets share permissions of a filter. :param filter_id: Filter ID @@ -861,14 +878,14 @@ def get_filter_share_permissions(self, filter_id): def add_filter_share_permission( self, - filter_id, - type, - project_id=None, - project_role_id=None, - groupname=None, - user_key=None, - view=None, - edit=None, + filter_id: T_id, + type: str, + project_id: T_id | None = None, + project_role_id: T_id | None = None, + groupname: str | None = None, + user_key: str | None = None, + view: str | None = None, + edit: str | None = None, ): """ Adds share permission for a filter. See the documentation of the sharePermissions. @@ -884,7 +901,7 @@ def add_filter_share_permission( """ base_url = self.resource_url("filter") url = "{base_url}/{id}/permission".format(base_url=base_url, id=filter_id) - data = {"type": type} + data: dict = {"type": type} if project_id: data["projectId"] = project_id if project_role_id: @@ -899,7 +916,7 @@ def add_filter_share_permission( data["edit"] = edit return self.post(url, data=data) - def delete_filter_share_permission(self, filter_id, permission_id): + def delete_filter_share_permission(self, filter_id: T_id, permission_id: T_id): """ Removes share permission :param filter_id: Filter ID @@ -918,7 +935,7 @@ def delete_filter_share_permission(self, filter_id, permission_id): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/groups """ - def get_groups(self, query=None, exclude=None, limit=20): + def get_groups(self, query: str | None = None, exclude: str | None = None, limit: int = 20): """ REST endpoint for searching groups in a group picker Returns groups with substrings matching a given query. This is mainly for use with the group picker, @@ -933,7 +950,7 @@ def get_groups(self, query=None, exclude=None, limit=20): :return: Returned even if no groups match the given substring """ url = self.resource_url("groups/picker") - params = {} + params: dict = {} if query: params["query"] = query else: @@ -944,7 +961,7 @@ def get_groups(self, query=None, exclude=None, limit=20): params["maxResults"] = limit return self.get(url, params=params) - def create_group(self, name): + def create_group(self, name: str): """ Create a group by given group parameter @@ -955,7 +972,7 @@ def create_group(self, name): data = {"name": name} return self.post(url, data=data) - def remove_group(self, name, swap_group=None): + def remove_group(self, name: str, swap_group: str | None = None): """ 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 @@ -974,7 +991,9 @@ def remove_group(self, name, swap_group=None): return self.delete(url, params=params) - def get_all_users_from_group(self, group, include_inactive_users=False, start=0, limit=50): + def get_all_users_from_group( + self, group: str, include_inactive_users: bool = False, start: int = 0, limit: int = 50 + ): """ Just wrapping method user group members :param group: @@ -985,7 +1004,7 @@ def get_all_users_from_group(self, group, include_inactive_users=False, start=0, :return: """ url = self.resource_url("group/member") - params = {} + params: dict = {} if group: params["groupname"] = group params["includeInactiveUsers"] = include_inactive_users @@ -993,7 +1012,9 @@ def get_all_users_from_group(self, group, include_inactive_users=False, start=0, params["maxResults"] = limit return self.get(url, params=params) - def add_user_to_group(self, username=None, group_name=None, account_id=None): + def add_user_to_group( + self, username: str | None = None, group_name: str | None = None, account_id: str | None = None + ): """ Add given user to a group @@ -1008,7 +1029,7 @@ def add_user_to_group(self, username=None, group_name=None, account_id=None): :return: Current state of the group """ url = self.resource_url("group/user") - params = {"groupname": group_name} + params: dict = {"groupname": group_name} url_domain = self.url if "atlassian.net" in url_domain: data = {"accountId": account_id} @@ -1016,7 +1037,9 @@ def add_user_to_group(self, username=None, group_name=None, account_id=None): data = {"name": username} return self.post(url, params=params, data=data) - def remove_user_from_group(self, username=None, group_name=None, account_id=None): + def remove_user_from_group( + self, username: str | None = None, group_name: str | None = None, account_id: str | None = None + ) -> dict | None: """ Remove given user from a group @@ -1040,8 +1063,13 @@ def remove_user_from_group(self, username=None, group_name=None, account_id=None return self.delete(url, params=params) def get_users_with_browse_permission_to_a_project( - self, username, issue_key=None, project_key=None, start=0, limit=100 - ): + self, + username: str, + issue_key: str | None = None, + project_key: str | None = None, + start: int = 0, + limit: int = 100, + ) -> dict | None: """ Returns a list of active users that match the search string. This resource cannot be accessed anonymously and requires the Browse Users global permission. Given an issue key this resource will provide a list of users @@ -1055,7 +1083,7 @@ def get_users_with_browse_permission_to_a_project( :return: List of active users who has browser permission for the given project_key or issue_key """ url = self.resource_url("user/viewissue/search") - params = {} + params: dict = {} if username: params["username"] = username if issue_key: @@ -1074,15 +1102,22 @@ def get_users_with_browse_permission_to_a_project( Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/issue """ - def issue(self, key, fields="*all", expand=None): + def issue(self, key: T_id, fields: str | dict = "*all", expand: str | None = None): base_url = self.resource_url("issue") url = "{base_url}/{key}?fields={fields}".format(base_url=base_url, key=key, fields=fields) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def get_issue(self, issue_id_or_key, fields=None, properties=None, update_history=True, expand=None): + def get_issue( + self, + issue_id_or_key: T_id, + fields: str | list | tuple | set | None = None, + properties: str | None = None, + update_history: bool = True, + expand: str | None = None, + ): """ Returns a full representation of the issue for the given issue key By default, all fields are returned in this get-issue resource @@ -1096,7 +1131,7 @@ def get_issue(self, issue_id_or_key, fields=None, properties=None, update_histor """ base_url = self.resource_url("issue") url = "{base_url}/{issue_id_or_key}".format(base_url=base_url, issue_id_or_key=issue_id_or_key) - params = {} + params: dict = {} if fields is not None: if isinstance(fields, (list, tuple, set)): @@ -1109,7 +1144,7 @@ def get_issue(self, issue_id_or_key, fields=None, properties=None, update_histor params["updateHistory"] = str(update_history).lower() return self.get(url, params=params) - def epic_issues(self, epic, fields="*all", expand=None): + def epic_issues(self, epic: str, fields: list | str = "*all", expand: str | None = None): """ Given an epic return all child issues By default, all fields are returned in this get-issue resource @@ -1123,12 +1158,12 @@ def epic_issues(self, epic, fields="*all", expand=None): """ base_url = self.resource_url("epic", api_root="rest/agile", api_version="1.0") url = "{base_url}/{key}/issue?fields={fields}".format(base_url=base_url, key=epic, fields=fields) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def bulk_issue(self, issue_list, fields="*all"): + def bulk_issue(self, issue_list: list, fields: list | str = "*all"): """ :param fields: :param list issue_list: @@ -1151,7 +1186,7 @@ def bulk_issue(self, issue_list, fields="*all"): query_result, missing_issues = self.bulk_issue(issue_list, fields) return query_result, missing_issues - def issue_createmeta(self, project, expand="projects.issuetypes.fields"): + def issue_createmeta(self, project: str, expand: str = "projects.issuetypes.fields") -> dict | None: """ This function is deprecated. See https://confluence.atlassian.com/jiracore/createmeta-rest-endpoint-to-be-removed-975040986.html @@ -1163,13 +1198,13 @@ def issue_createmeta(self, project, expand="projects.issuetypes.fields"): DeprecationWarning, stacklevel=2, ) - params = {} + params: dict = {} if expand: params["expand"] = expand url = self.resource_url("issue/createmeta?projectKeys={}".format(project)) return self.get(url, params=params) - def issue_createmeta_issuetypes(self, project, start=None, limit=None): + def issue_createmeta_issuetypes(self, project: str, start: int | None = None, limit: int | None = None): """ Get create metadata issue types for a project Returns a page of issue type metadata for a specified project. @@ -1180,14 +1215,16 @@ def issue_createmeta_issuetypes(self, project, start=None, limit=None): :return: """ url = self.resource_url("issue/createmeta/{}/issuetypes".format(project)) - params = {} + params: dict = {} if start: params["startAt"] = start if limit: params["maxResults"] = limit return self.get(url, params=params) - def issue_createmeta_fieldtypes(self, project, issue_type_id, start=None, limit=None): + def issue_createmeta_fieldtypes( + self, project: str, issue_type_id: str, start: int | None = None, limit: int | None = None + ): """ Get create field metadata for a project and issue type id Returns a page of field metadata for a specified project and issuetype id. @@ -1200,19 +1237,19 @@ def issue_createmeta_fieldtypes(self, project, issue_type_id, start=None, limit= :return: """ url = self.resource_url("issue/createmeta/{}/issuetypes/{}".format(project, issue_type_id)) - params = {} + params: dict = {} if start: params["startAt"] = start if limit: params["maxResults"] = limit return self.get(url, params=params) - def issue_editmeta(self, key): + def issue_editmeta(self, key: str): base_url = self.resource_url("issue") url = "{}/{}/editmeta".format(base_url, key) return self.get(url) - def get_issue_changelog(self, issue_key, start=None, limit=None): + def get_issue_changelog(self, issue_key: str, start: int | None = None, limit: int | None = None): """ Get issue related change log :param issue_key: @@ -1221,7 +1258,7 @@ def get_issue_changelog(self, issue_key, start=None, limit=None): :return: """ base_url = self.resource_url("issue") - params = {} + params: dict = {} if start: params["startAt"] = start if limit: @@ -1234,7 +1271,7 @@ def get_issue_changelog(self, issue_key, start=None, limit=None): url = "{base_url}/{issue_key}?expand=changelog".format(base_url=base_url, issue_key=issue_key) return (self.get(url) or {}).get("changelog", params) - def issue_add_json_worklog(self, key, worklog): + def issue_add_json_worklog(self, key: str, worklog: dict | str): """ :param key: @@ -1245,7 +1282,7 @@ def issue_add_json_worklog(self, key, worklog): url = "{base_url}/{key}/worklog".format(base_url=base_url, key=key) return self.post(url, data=worklog) - def issue_worklog(self, key, started, time_sec, comment=None): + def issue_worklog(self, key: str, started: str, time_sec: int, comment: str | None = None): """ :param key: :param time_sec: int: second @@ -1258,7 +1295,7 @@ def issue_worklog(self, key, started, time_sec, comment=None): data["comment"] = comment return self.issue_add_json_worklog(key=key, worklog=data) - def issue_get_worklog(self, issue_id_or_key): + def issue_get_worklog(self, issue_id_or_key: str): """ Returns all work logs for an issue. Note: Work logs won't be returned if the Log work field is hidden for the project. @@ -1270,7 +1307,7 @@ def issue_get_worklog(self, issue_id_or_key): return self.get(url) - def issue_archive(self, issue_id_or_key, notify_users=None): + def issue_archive(self, issue_id_or_key: str, notify_users: bool | None = None): """ Archives an issue. :param issue_id_or_key: Issue id or issue key @@ -1278,14 +1315,14 @@ def issue_archive(self, issue_id_or_key, notify_users=None): The default value of None will apply the default behavior of Jira :return: """ - params = {} + params: dict = {} if notify_users is not None: params["notifyUsers"] = notify_users base_url = self.resource_url("issue") url = "{base_url}/{issueIdOrKey}/archive".format(base_url=base_url, issueIdOrKey=issue_id_or_key) return self.put(url, params=params) - def issue_restore(self, issue_id_or_key): + def issue_restore(self, issue_id_or_key: str): """ Restores an archived issue. :param issue_id_or_key: Issue id or issue key @@ -1295,17 +1332,19 @@ def issue_restore(self, issue_id_or_key): url = "{base_url}/{issueIdOrKey}/restore".format(base_url=base_url, issueIdOrKey=issue_id_or_key) return self.put(url) - def issue_field_value(self, key, field): + def issue_field_value(self, key: str, field: str): base_url = self.resource_url("issue") issue = self.get("{base_url}/{key}?fields={field}".format(base_url=base_url, key=key, field=field)) - return issue["fields"][field] + if issue: + return issue["fields"][field] - def issue_fields(self, key): + def issue_fields(self, key: str): base_url = self.resource_url("issue") issue = self.get("{base_url}/{key}".format(base_url=base_url, key=key)) - return issue["fields"] + if issue: + return issue["fields"] - def update_issue_field(self, key, fields="*all", notify_users=True): + def update_issue_field(self, key: T_id, fields: str | dict = "*all", notify_users: bool = True): """ Update an issue's fields. :param key: str Issue id or issye key @@ -1316,14 +1355,14 @@ def update_issue_field(self, key, fields="*all", notify_users=True): Reference: https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-issues/#api-rest-api-2-issue-issueidorkey-put """ base_url = self.resource_url("issue") - params = {"notifyUsers": "true" if notify_users else "false"} + params: dict = {"notifyUsers": "true" if notify_users else "false"} return self.put( "{base_url}/{key}".format(base_url=base_url, key=key), data={"fields": fields}, params=params, ) - def bulk_update_issue_field(self, key_list, fields="*all"): + def bulk_update_issue_field(self, key_list: list, fields: str | dict = "*all") -> bool: """ :param key_list: list of issues with common filed to be updated :param fields: common fields to be updated @@ -1341,7 +1380,7 @@ def bulk_update_issue_field(self, key_list, fields="*all"): return False return True - def issue_field_value_append(self, issue_id_or_key, field, value, notify_users=True): + def issue_field_value_append(self, issue_id_or_key: str, field: str, value: str, notify_users: bool = True): """ Add value to a multiple value field @@ -1352,7 +1391,7 @@ def issue_field_value_append(self, issue_id_or_key, field, value, notify_users=T if False, do not send any email notifications. (only works with admin privilege) """ base_url = self.resource_url("issue") - params = {"notifyUsers": True if notify_users else False} + params: dict = {"notifyUsers": True if notify_users else False} current_value = self.issue_field_value(key=issue_id_or_key, field=field) if current_value: @@ -1368,7 +1407,7 @@ def issue_field_value_append(self, issue_id_or_key, field, value, notify_users=T params=params, ) - def get_issue_labels(self, issue_key): + def get_issue_labels(self, issue_key: str): """ Get issue labels. :param issue_key: @@ -1380,7 +1419,7 @@ def get_issue_labels(self, issue_key): return self.get(url) return (self.get(url) or {}).get("fields").get("labels") - def update_issue(self, issue_key, update): + def update_issue(self, issue_key: T_id, update: dict | str) -> dict | None: """ :param issue: the issue to update :param update: the update to make @@ -1389,7 +1428,7 @@ def update_issue(self, issue_key, update): endpoint = "/rest/api/2/issue/{issue_key}".format(issue_key=issue_key) return self.put(endpoint, data=update) - def label_issue(self, issue_key, labels): + def label_issue(self, issue_key: T_id, labels: list): """ :param issue: the issue to update :param labels: the labels to add @@ -1398,16 +1437,16 @@ def label_issue(self, issue_key, labels): labels = [{"add": label} for label in labels] return self.update_issue(issue_key, {"update": {"labels": labels}}) - def unlabel_issue(self, issue_key, labels): + def unlabel_issue(self, issue_key: T_id, labels: list): """ - :param issue: the issue to update + :param issue_key: the issue to update :param labels: the labels to remove :return: True if successful, False if not """ labels = [{"remove": label} for label in labels] return self.update_issue(issue_key, {"update": {"labels": labels}}) - def add_attachment(self, issue_key, filename): + def add_attachment(self, issue_key: str, filename: str): """ Add attachment to Issue :param issue_key: str @@ -1416,7 +1455,7 @@ def add_attachment(self, issue_key, filename): with open(filename, "rb") as attachment: return self.add_attachment_object(issue_key, attachment) - def add_attachment_object(self, issue_key, attachment): + def add_attachment_object(self, issue_key: str, attachment: BinaryIO): """ Add attachment to Issue :param issue_key: str @@ -1432,7 +1471,7 @@ def add_attachment_object(self, issue_key, attachment): return None return self.post(url, headers=self.no_check_headers, files=files) - def issue_exists(self, issue_key): + def issue_exists(self, issue_key: str) -> bool | None: original_value = self.advanced_mode self.advanced_mode = True try: @@ -1446,7 +1485,7 @@ def issue_exists(self, issue_key): finally: self.advanced_mode = original_value - def issue_deleted(self, issue_key): + def issue_deleted(self, issue_key: str) -> bool: exists = self.issue_exists(issue_key) if exists: log.info('Issue "%s" is not deleted', issue_key) @@ -1454,7 +1493,7 @@ def issue_deleted(self, issue_key): log.info('Issue "%s" is deleted', issue_key) return not exists - def delete_issue(self, issue_id_or_key, delete_subtasks=True): + def delete_issue(self, issue_id_or_key: str, delete_subtasks: bool = True): """ Delete an issue If the issue has subtasks you must set the parameter delete_subtasks = True to delete the issue @@ -1465,7 +1504,7 @@ def delete_issue(self, issue_id_or_key, delete_subtasks=True): """ base_url = self.resource_url("issue") url = "{base_url}/{issue_id_or_key}".format(base_url=base_url, issue_id_or_key=issue_id_or_key) - params = {} + params: dict = {} if delete_subtasks is True: params["deleteSubtasks"] = "true" @@ -1477,13 +1516,13 @@ def delete_issue(self, issue_id_or_key, delete_subtasks=True): return self.delete(url, params=params) # @todo merge with edit_issue method - def issue_update(self, issue_key, fields): + def issue_update(self, issue_key: str, fields: str | dict): log.warning('Updating issue "%s" with "%s"', issue_key, fields) base_url = self.resource_url("issue") url = "{base_url}/{issue_key}".format(base_url=base_url, issue_key=issue_key) return self.put(url, data={"fields": fields}) - def edit_issue(self, issue_id_or_key, fields, notify_users=True): + def edit_issue(self, issue_id_or_key: str, fields: str | dict, notify_users: bool = True): """ Edits an issue from a JSON representation The issue can either be updated by setting explicit the field @@ -1496,7 +1535,7 @@ def edit_issue(self, issue_id_or_key, fields, notify_users=True): """ base_url = self.resource_url("issue") url = "{base_url}/{issue_id_or_key}".format(base_url=base_url, issue_id_or_key=issue_id_or_key) - params = {} + params: dict = {} data = {"update": fields} if notify_users is True: @@ -1505,7 +1544,7 @@ def edit_issue(self, issue_id_or_key, fields, notify_users=True): params["notifyUsers"] = "false" return self.put(url, data=data, params=params) - def issue_add_watcher(self, issue_key, user): + def issue_add_watcher(self, issue_key: str, user: str): """ Start watching issue :param issue_key: @@ -1520,7 +1559,7 @@ def issue_add_watcher(self, issue_key, user): data=data, ) - def issue_delete_watcher(self, issue_key, user): + def issue_delete_watcher(self, issue_key: str, user: str): """ Stop watching issue :param issue_key: @@ -1528,14 +1567,14 @@ def issue_delete_watcher(self, issue_key, user): :return: """ log.warning('Deleting user %s from "%s" watchers', user, issue_key) - params = {"username": user} + params: dict = {"username": user} base_url = self.resource_url("issue") return self.delete( "{base_url}/{issue_key}/watchers".format(base_url=base_url, issue_key=issue_key), params=params, ) - def issue_get_watchers(self, issue_key): + def issue_get_watchers(self, issue_key: str): """ Get watchers for an issue :param issue_key: Issue ID or Key @@ -1544,7 +1583,7 @@ def issue_get_watchers(self, issue_key): base_url = self.resource_url("issue") return self.get("{base_url}/{issue_key}/watchers".format(base_url=base_url, issue_key=issue_key)) - def assign_issue(self, issue, account_id=None): + def assign_issue(self, issue: T_id, account_id: str | None = None): """Assign an issue to a user. None will set it to unassigned. -1 will set it to Automatic. :param issue : the issue ID or key to assign :type issue: int or str @@ -1561,7 +1600,7 @@ def assign_issue(self, issue, account_id=None): data = {"name": account_id} return self.put(url, data=data) - def create_issue(self, fields, update_history=False, update=None): + def create_issue(self, fields: str | dict, update_history: bool = False, update: dict | None = None): """ Creates an issue or a sub-task from a JSON representation :param fields: JSON data @@ -1592,7 +1631,7 @@ def create_issue(self, fields, update_history=False, update=None): data = {"fields": fields} if update: data["update"] = update - params = {} + params: dict = {} if update_history is True: params["updateHistory"] = "true" @@ -1600,7 +1639,7 @@ def create_issue(self, fields, update_history=False, update=None): params["updateHistory"] = "false" return self.post(url, params=params, data=data) - def create_issues(self, list_of_issues_data): + def create_issues(self, list_of_issues_data: list): """ Creates issues or sub-tasks from a JSON representation Creates many issues in one bulk operation @@ -1612,12 +1651,12 @@ def create_issues(self, list_of_issues_data): return self.post(url, data=data) # @todo refactor and merge with create_issue method - def issue_create(self, fields): + def issue_create(self, fields: dict): log.warning('Creating issue "%s"', fields["summary"]) url = self.resource_url("issue") return self.post(url, data={"fields": fields}) - def issue_create_or_update(self, fields): + def issue_create_or_update(self, fields: dict): issue_key = fields.get("issuekey", None) if not issue_key or not self.issue_exists(issue_key): @@ -1633,7 +1672,7 @@ def issue_create_or_update(self, fields): fields.pop("issuekey", None) return self.issue_update(issue_key, fields) - def issue_add_comment(self, issue_key, comment, visibility=None): + def issue_add_comment(self, issue_key: str, comment: str, visibility: dict | None = None): """ Add comment into Jira issue :param issue_key: @@ -1643,12 +1682,14 @@ def issue_add_comment(self, issue_key, comment, visibility=None): """ base_url = self.resource_url("issue") url = "{base_url}/{issueIdOrKey}/comment".format(base_url=base_url, issueIdOrKey=issue_key) - data = {"body": comment} + data: dict = {"body": comment} if visibility: data["visibility"] = visibility return self.post(url, data=data) - def issue_edit_comment(self, issue_key, comment_id, comment, visibility=None, notify_users=True): + def issue_edit_comment( + self, issue_key: str, comment_id: T_id, comment: str, visibility: dict | None = None, notify_users: bool = True + ): """ Updates an existing comment :param issue_key: str @@ -1662,13 +1703,13 @@ def issue_edit_comment(self, issue_key, comment_id, comment, visibility=None, no url = "{base_url}/{issue_key}/comment/{comment_id}".format( base_url=base_url, issue_key=issue_key, comment_id=comment_id ) - data = {"body": comment} + data: dict = {"body": comment} if visibility: data["visibility"] = visibility - params = {"notifyUsers": "true" if notify_users else "false"} + params: dict = {"notifyUsers": "true" if notify_users else "false"} return self.put(url, data=data, params=params) - def scrap_regex_from_issue(self, issue, regex): + def scrap_regex_from_issue(self, issue: str, regex: str): """ This function scrapes the output of the given regex matches from the issue's description and comments. @@ -1707,13 +1748,13 @@ def scrap_regex_from_issue(self, issue, regex): reason=e, ) - def get_issue_remotelinks(self, issue_key, global_id=None, internal_id=None): + def get_issue_remotelinks(self, issue_key: str, global_id: T_id | None = None, internal_id: str | None = None): """ Compatibility naming method with get_issue_remote_links() """ return self.get_issue_remote_links(issue_key, global_id, internal_id) - def get_issue_remote_links(self, issue_key, global_id=None, internal_id=None): + def get_issue_remote_links(self, issue_key: str, global_id: T_id | None = None, internal_id: str | None = None): """ Finding all Remote Links on an issue, also with filtering by Global ID and internal ID :param issue_key: @@ -1723,14 +1764,14 @@ def get_issue_remote_links(self, issue_key, global_id=None, internal_id=None): """ base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/remotelink".format(base_url=base_url, issue_key=issue_key) - params = {} + params: dict = {} if global_id: params["globalId"] = global_id if internal_id: url += "/" + internal_id return self.get(url, params=params) - def get_issue_tree_recursive(self, issue_key, tree=[], depth=0): + def get_issue_tree_recursive(self, issue_key: str, tree: list = [], depth: int = 0): """ Returns list that contains the tree structure of the root issue, with all subtasks and inward linked issues. (!) Function only returns child issues from the same jira instance or from instance to which api key has access to. @@ -1768,14 +1809,14 @@ def get_issue_tree_recursive(self, issue_key, tree=[], depth=0): def create_or_update_issue_remote_links( self, - issue_key, - link_url, - title, - global_id=None, - relationship=None, - icon_url=None, - icon_title=None, - status_resolved=False, + issue_key: str, + link_url: str, + title: str, + global_id: T_id | None = None, + relationship: str | None = None, + icon_url: str | None = None, + icon_title: str | None = None, + status_resolved: bool = False, ): """ Add Remote Link to Issue, update url if global_id is passed @@ -1790,7 +1831,7 @@ def create_or_update_issue_remote_links( """ base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/remotelink".format(base_url=base_url, issue_key=issue_key) - data = {"object": {"url": link_url, "title": title, "status": {"resolved": status_resolved}}} + data: dict = {"object": {"url": link_url, "title": title, "status": {"resolved": status_resolved}}} if global_id: data["globalId"] = global_id if relationship: @@ -1804,14 +1845,22 @@ def create_or_update_issue_remote_links( data["object"]["icon"] = icon_data return self.post(url, data=data) - def get_issue_remote_link_by_id(self, issue_key, link_id): + def get_issue_remote_link_by_id(self, issue_key: str, link_id: T_id): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/remotelink/{link_id}".format( base_url=base_url, issue_key=issue_key, link_id=link_id ) return self.get(url) - def update_issue_remote_link_by_id(self, issue_key, link_id, url, title, global_id=None, relationship=None): + def update_issue_remote_link_by_id( + self, + issue_key: str, + link_id: T_id, + url: str, + title: str, + global_id: T_id | None = None, + relationship: str | None = None, + ): """ Update existing Remote Link on Issue :param issue_key: str @@ -1822,7 +1871,7 @@ def update_issue_remote_link_by_id(self, issue_key, link_id, url, title, global_ :param relationship: str, Optional. Default by built-in method: 'Web Link' """ - data = {"object": {"url": url, "title": title}} + data: dict = {"object": {"url": url, "title": title}} if global_id: data["globalId"] = global_id if relationship: @@ -1833,7 +1882,7 @@ def update_issue_remote_link_by_id(self, issue_key, link_id, url, title, global_ ) return self.put(url, data=data) - def delete_issue_remote_link_by_id(self, issue_key, link_id): + def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id): """ Deletes Remote Link on Issue :param issue_key: str @@ -1845,7 +1894,7 @@ def delete_issue_remote_link_by_id(self, issue_key, link_id): ) return self.delete(url) - def get_issue_transitions(self, issue_key): + def get_issue_transitions(self, issue_key: str): if self.advanced_mode: return [ { @@ -1865,10 +1914,12 @@ def get_issue_transitions(self, issue_key): for transition in (self.get_issue_transitions_full(issue_key) or {}).get("transitions") ] - def issue_transition(self, issue_key, status): + def issue_transition(self, issue_key: str, status: str): return self.set_issue_status(issue_key, status) - def set_issue_status(self, issue_key, status_name, fields=None, update=None): + def set_issue_status( + self, issue_key: str, status_name: str, fields: str | dict | None = None, update: dict | None = None + ): """ Setting status by status_name. Field defaults to None for transitions without mandatory fields. If there are mandatory fields for the transition, these can be set using a dict in 'fields'. @@ -1885,14 +1936,14 @@ def set_issue_status(self, issue_key, status_name, fields=None, update=None): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/transitions".format(base_url=base_url, issue_key=issue_key) transition_id = self.get_transition_id_to_status_name(issue_key, status_name) - data = {"transition": {"id": transition_id}} + data: dict = {"transition": {"id": transition_id}} if fields is not None: data["fields"] = fields if update is not None: data["update"] = update return self.post(url, data=data) - def get_issue_status_changelog(self, issue_id): + def get_issue_status_changelog(self, issue_id: T_id): # Get the issue details with changelog response_get_issue = self.get_issue(issue_id, expand="changelog") status_change_history = [] @@ -1906,7 +1957,7 @@ def get_issue_status_changelog(self, issue_id): return status_change_history - def set_issue_status_by_transition_id(self, issue_key, transition_id): + def set_issue_status_by_transition_id(self, issue_key: str, transition_id: T_id): """ Setting status by transition_id :param issue_key: str @@ -1916,17 +1967,19 @@ def set_issue_status_by_transition_id(self, issue_key, transition_id): url = "{base_url}/{issue_key}/transitions".format(base_url=base_url, issue_key=issue_key) return self.post(url, data={"transition": {"id": transition_id}}) - def get_issue_status(self, issue_key): + def get_issue_status(self, issue_key: str): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}?fields=status".format(base_url=base_url, issue_key=issue_key) return (((self.get(url) or {}).get("fields") or {}).get("status") or {}).get("name") or {} - def get_issue_status_id(self, issue_key): + def get_issue_status_id(self, issue_key: str): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}?fields=status".format(base_url=base_url, issue_key=issue_key) return (self.get(url) or {}).get("fields").get("status").get("id") - def get_issue_transitions_full(self, issue_key, transition_id=None, expand=None): + def get_issue_transitions_full( + self, issue_key: str, transition_id: T_id | None = None, expand: str | None = None + ) -> dict | None: """ Get a list of the transitions possible for this issue by the current user, along with fields that are required and their types. @@ -1940,14 +1993,14 @@ def get_issue_transitions_full(self, issue_key, transition_id=None, expand=None) """ base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/transitions".format(base_url=base_url, issue_key=issue_key) - params = {} + params: dict = {} if transition_id: params["transitionId"] = transition_id if expand: params["expand"] = expand return self.get(url, params=params) - def get_issue_property_keys(self, issue_key): + def get_issue_property_keys(self, issue_key: str): """ Get Property Keys on an Issue. :param issue_key: Issue KEY @@ -1958,28 +2011,28 @@ def get_issue_property_keys(self, issue_key): url = "{base_url}/{issue_key}/properties".format(base_url=base_url, issue_key=issue_key) return self.get(url) - def set_issue_property(self, issue_key, property_key, data): + def set_issue_property(self, issue_key: str, property_key: str, data: dict): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/properties/{propertyKey}".format( base_url=base_url, issue_key=issue_key, propertyKey=property_key ) return self.put(url, data=data) - def get_issue_property(self, issue_key, property_key): + def get_issue_property(self, issue_key: str, property_key: str): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/properties/{propertyKey}".format( base_url=base_url, issue_key=issue_key, propertyKey=property_key ) return self.get(url) - def delete_issue_property(self, issue_key, property_key): + def delete_issue_property(self, issue_key: str, property_key: str): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}/properties/{propertyKey}".format( base_url=base_url, issue_key=issue_key, propertyKey=property_key ) return self.delete(url) - def get_updated_worklogs(self, since, expand=None): + def get_updated_worklogs(self, since: str, expand: str | None = None): """ Returns a list of IDs and update timestamps for worklogs updated after a date and time. :param since: The date and time, as a UNIX timestamp in milliseconds, after which updated worklogs are returned. @@ -1987,7 +2040,7 @@ def get_updated_worklogs(self, since, expand=None): This parameter accepts properties that returns the properties of each worklog. """ url = self.resource_url("worklog/updated") - params = {} + params: dict = {} if since: params["since"] = str(int(since * 1000)) if expand: @@ -1995,19 +2048,19 @@ def get_updated_worklogs(self, since, expand=None): return self.get(url, params=params) - def get_deleted_worklogs(self, since): + def get_deleted_worklogs(self, since: str): """ Returns a list of IDs and timestamps for worklogs deleted after a date and time. :param since: The date and time, as a UNIX timestamp in milliseconds, after which deleted worklogs are returned. """ url = self.resource_url("worklog/deleted") - params = {} + params: dict = {} if since: params["since"] = str(int(since * 1000)) return self.get(url, params=params) - def get_worklogs(self, ids, expand=None): + def get_worklogs(self, ids: list[T_id], expand: str | None = None): """ Returns worklog details for a list of worklog IDs. :param expand: Use expand to include additional information about worklogs in the response. @@ -2016,7 +2069,7 @@ def get_worklogs(self, ids, expand=None): """ url = self.resource_url("worklog/list") - params = {} + params: dict = {} if expand: params["expand"] = expand data = {"ids": ids} @@ -2027,7 +2080,13 @@ def get_worklogs(self, ids, expand=None): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/user """ - def user(self, username=None, key=None, account_id=None, expand=None): + def user( + self, + username: str | None = None, + key: str | None = None, + account_id: str | None = None, + expand: str | None = None, + ): """ Returns a user. This resource cannot be accessed anonymously. You can use only one parameter: username or key @@ -2038,7 +2097,7 @@ def user(self, username=None, key=None, account_id=None, expand=None): :param expand: Can be 'groups,applicationRoles' :return: """ - params = {} + params: dict = {} major_parameter_enabled = False if account_id: params = {"accountId": account_id} @@ -2066,7 +2125,7 @@ def myself(self): url = self.resource_url("myself") return self.get(url) - def is_active_user(self, username): + def is_active_user(self, username: str): """ Check status of user :param username: @@ -2074,7 +2133,7 @@ def is_active_user(self, username): """ return self.user(username).get("active") - def user_remove(self, username=None, account_id=None, key=None): + def user_remove(self, username: str | None = None, account_id: str | None = None, key: str | None = None): """ Remove user from Jira if this user does not have any activity :param key: @@ -2082,7 +2141,7 @@ def user_remove(self, username=None, account_id=None, key=None): :param username: :return: """ - params = {} + params: dict = {} if username: params["username"] = username if account_id: @@ -2092,7 +2151,7 @@ def user_remove(self, username=None, account_id=None, key=None): url = self.resource_url("user") return self.delete(url, params=params) - def user_update(self, username, data): + def user_update(self, username: str, data: dict): """ Update user attributes based on json :param username: @@ -2103,7 +2162,7 @@ def user_update(self, username, data): url = "{base_url}?username={username}".format(base_url=base_url, username=username) return self.put(url, data=data) - def user_update_username(self, old_username, new_username): + def user_update_username(self, old_username: str, new_username: str): """ Update username :param old_username: @@ -2113,7 +2172,7 @@ def user_update_username(self, old_username, new_username): data = {"name": new_username} return self.user_update(old_username, data=data) - def user_update_email(self, username, email): + def user_update_email(self, username: str, email: str): """ Update user email for new domain changes :param username: @@ -2123,7 +2182,14 @@ def user_update_email(self, username, email): data = {"name": username, "emailAddress": email} return self.user_update(username, data=data) - def user_create(self, username, email, display_name, password=None, notification=None): + def user_create( + self, + username: str, + email: str, + display_name: str, + password: str | None = None, + notification: bool | None = None, + ): """ Create a user in Jira :param username: @@ -2135,7 +2201,7 @@ def user_create(self, username, email, display_name, password=None, notification :return: """ log.warning("Creating user %s", display_name) - data = { + data: dict = { "name": username, "emailAddress": email, "displayName": display_name, @@ -2151,7 +2217,7 @@ def user_create(self, username, email, display_name, password=None, notification url = self.resource_url("user") return self.post(url, data=data) - def user_properties(self, username=None, account_id=None): + def user_properties(self, username: str | None = None, account_id: str | None = None): """ Get user property :param username: @@ -2166,7 +2232,9 @@ def user_properties(self, username=None, account_id=None): url = "{base_url}?accountId={account_id}".format(base_url=base_url, account_id=account_id) return self.get(url) - def user_property(self, username=None, account_id=None, key_property=None): + def user_property( + self, username: str | None = None, account_id: str | None = None, key_property: str | None = None + ): """ Get user property :param username: @@ -2174,7 +2242,7 @@ def user_property(self, username=None, account_id=None, key_property=None): :param key_property: :return: """ - params = {} + params: dict = {} if username or not self.cloud: params = {"username": username} elif account_id or self.cloud: @@ -2187,10 +2255,10 @@ def user_property(self, username=None, account_id=None, key_property=None): def user_set_property( self, - username=None, - account_id=None, - key_property=None, - value_property=None, + username: str | None = None, + account_id: str | None = None, + key_property: str | None = None, + value_property: str | dict | None = None, ): """ Set property for user @@ -2215,7 +2283,9 @@ def user_set_property( return self.put(url, data=value_property) - def user_delete_property(self, username=None, account_id=None, key_property=None): + def user_delete_property( + self, username: str | None = None, account_id: str | None = None, key_property: str | None = None + ): """ Delete property for user :param username: @@ -2225,14 +2295,14 @@ def user_delete_property(self, username=None, account_id=None, key_property=None """ base_url = self.resource_url("user/properties") url = "{base_url}/{key_property}".format(base_url=base_url, key_property=key_property) - params = {} + params: dict = {} if username or not self.cloud: params = {"username": username} elif account_id or self.cloud: params = {"accountId": account_id} return self.delete(url, params=params) - def user_update_or_create_property_through_rest_point(self, username, key, value): + def user_update_or_create_property_through_rest_point(self, username: str, key: str, value: str): """ ATTENTION! This method used after configuration of rest endpoint on Jira side @@ -2242,10 +2312,10 @@ def user_update_or_create_property_through_rest_point(self, username, key, value :return: """ url = "rest/scriptrunner/latest/custom/updateUserProperty" - params = {"username": username, "property": key, "value": value} + params: dict = {"username": username, "property": key, "value": value} return self.get(url, params=params) - def user_deactivate(self, username): + def user_deactivate(self, username: str): """ Disable user. Works from 8.3.0 Release https://docs.atlassian.com/software/jira/docs/api/REST/8.3.0/#api/2/user-updateUser @@ -2255,15 +2325,15 @@ def user_deactivate(self, username): data = {"active": "false", "name": username} return self.user_update(username=username, data=data) - def user_disable(self, username): + def user_disable(self, username: str): """Override the disable method""" return self.user_deactivate(username) def user_disable_throw_rest_endpoint( self, - username, - url="rest/scriptrunner/latest/custom/disableUser", - param="userName", + username: str, + url: str = "rest/scriptrunner/latest/custom/disableUser", + param: str = "userName", ): """The disable method throw own rest endpoint""" url = "{}?{}={}".format(url, param, username) @@ -2298,8 +2368,8 @@ def invalidate_websudo(self): def users_get_all( self, - start=0, - limit=50, + start: int = 0, + limit: int = 50, ): """ :param start: @@ -2307,7 +2377,7 @@ def users_get_all( :return: """ url = self.resource_url("users/search") - params = { + params: dict = { "startAt": start, "maxResults": limit, } @@ -2315,14 +2385,14 @@ def users_get_all( def user_find_by_user_string( self, - username=None, - query=None, - account_id=None, - property_key=None, - start=0, - limit=50, - include_inactive_users=False, - include_active_users=True, + username: str | None = None, + query: str | None = None, + account_id: str | None = None, + property_key: str | None = None, + start: int = 0, + limit: int = 50, + include_inactive_users: bool = False, + include_active_users: bool = True, ): """ Fuzzy search using display name, emailAddress or property, or an exact search for accountId or username @@ -2345,7 +2415,7 @@ def user_find_by_user_string( :return: """ url = self.resource_url("user/search") - params = { + params: dict = { "includeActive": str(include_active_users).lower(), "includeInactive": str(include_inactive_users).lower(), "startAt": start, @@ -2375,7 +2445,7 @@ def user_find_by_user_string( return self.get(url, params=params) - def is_user_in_application(self, username, application_key): + def is_user_in_application(self, username: str, application_key: str) -> bool: """ Utility function to test whether a user has an application role :param username: The username of the user to test. @@ -2389,7 +2459,7 @@ def is_user_in_application(self, username, application_key): return True return False - def add_user_to_application(self, username, application_key): + def add_user_to_application(self, username: str, application_key: str): """ Add a user to an application :param username: The username of the user to add. @@ -2397,7 +2467,7 @@ def add_user_to_application(self, username, application_key): :return: True if the user was added to the application, else False :see: https://docs.atlassian.com/software/jira/docs/api/REST/7.5.3/#api/2/user-addUserToApplication """ - params = {"username": username, "applicationKey": application_key} + params: dict = {"username": username, "applicationKey": application_key} url = self.resource_url("user/application") return self.post(url, params=params) is None @@ -2406,7 +2476,7 @@ def add_user_to_application(self, username, application_key): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project """ - def get_user_groups(self, account_id=None): + def get_user_groups(self, account_id: str | None = None): """ Get groups of a user This API is only available for Jira Cloud platform @@ -2417,10 +2487,10 @@ def get_user_groups(self, account_id=None): url = self.resource_url("user/groups") return self.get(url, params=params) - def get_all_projects(self, included_archived=None, expand=None): + def get_all_projects(self, included_archived: bool | None = None, expand: str | None = None): return self.projects(included_archived, expand) - def projects(self, included_archived=None, expand=None): + def projects(self, included_archived: bool | None = None, expand: str | None = None): """ Returns all projects which are visible for the currently logged-in user. If no user is logged in, it returns the list of projects that are visible when using anonymous access. @@ -2429,7 +2499,7 @@ def projects(self, included_archived=None, expand=None): :return: """ - params = {} + params: dict = {} if included_archived: params["includeArchived"] = included_archived if expand: @@ -2445,7 +2515,7 @@ def projects(self, included_archived=None, expand=None): url = self.resource_url("project") return self.get(url, params=params) - def create_project_from_raw_json(self, json): + def create_project_from_raw_json(self, json: dict | str): """ Creates a new project. { @@ -2468,7 +2538,7 @@ def create_project_from_raw_json(self, json): """ return self.post("rest/api/2/project", json=json) - def create_project_from_shared_template(self, project_id, key, name, lead): + def create_project_from_shared_template(self, project_id: T_id, key: str, name: str, lead: str): """ Creates a new project based on an existing project. :param str project_id: The numeric ID of the project to clone @@ -2484,7 +2554,7 @@ def create_project_from_shared_template(self, project_id, key, name, lead): json=json, ) - def delete_project(self, key): + def delete_project(self, key: str): """ DELETE /rest/api/2/project/ :param key: str @@ -2494,7 +2564,7 @@ def delete_project(self, key): url = "{base_url}/{key}".format(base_url=base_url, key=key) return self.delete(url) - def archive_project(self, key): + def archive_project(self, key: str): """ Archives a project. :param key: @@ -2503,21 +2573,21 @@ def archive_project(self, key): url = "{base_url}/{key}/archive".format(base_url=base_url, key=key) return self.post(url) - def project(self, key, expand=None): + def project(self, key: str, expand: str | None = None): """ Get project with details :param key: :param expand: :return: """ - params = {} + params: dict = {} if expand: params["expand"] = expand base_url = self.resource_url("project") url = "{base_url}/{key}".format(base_url=base_url, key=key) return self.get(url, params=params) - def get_project(self, key, expand=None): + def get_project(self, key: str, expand: str | None = None): """ Contains a full representation of a project in JSON format. All project keys associated with the project will only be returned if expand=projectKeys. @@ -2527,7 +2597,7 @@ def get_project(self, key, expand=None): """ return self.project(key=key, expand=expand) - def get_project_components(self, key): + def get_project_components(self, key: str): """ Get project components using project key :param key: str @@ -2537,14 +2607,14 @@ def get_project_components(self, key): url = "{base_url}/{key}/components".format(base_url=base_url, key=key) return self.get(url) - def get_project_versions(self, key, expand=None): + def get_project_versions(self, key: str, expand: str | None = None): """ Contains a full representation of the specified project's versions. :param key: :param expand: the parameters to expand :return: """ - params = {} + params: dict = {} if expand is not None: params["expand"] = expand base_url = self.resource_url("project") @@ -2553,13 +2623,13 @@ def get_project_versions(self, key, expand=None): def get_project_versions_paginated( self, - key, - start=None, - limit=None, - order_by=None, - expand=None, - query=None, - status=None, + key: str, + start: int | None = None, + limit: int | None = None, + order_by: str | None = None, + expand: str | None = None, + query: str | None = None, + status: str | None = None, ): """ Returns all versions for the specified project. Results are paginated. @@ -2579,7 +2649,7 @@ def get_project_versions_paginated( This parameter accepts a comma-separated list. The status values are released, unreleased, and archived. :return: """ - params = {} + params: dict = {} if start is not None: params["startAt"] = int(start) if limit is not None: @@ -2596,7 +2666,7 @@ def get_project_versions_paginated( url = "{base_url}/{key}/version".format(base_url=base_url, key=key) return self.get(url, params=params) - def get_version(self, version): + def get_version(self, version: T_id): """ Returns a specific version with the given id. :param version: The id of the version to return @@ -2607,11 +2677,11 @@ def get_version(self, version): def add_version( self, - project_key, - project_id, - version, - is_archived=False, - is_released=False, + project_key: str, + project_id: T_id, + version: str, + is_archived: bool = False, + is_released: bool = False, ): """ Add missing version to project @@ -2632,7 +2702,7 @@ def add_version( url = self.resource_url("version") return self.post(url, data=payload) - def delete_version(self, version, moved_fixed=None, move_affected=None): + def delete_version(self, version: str, moved_fixed: str | None = None, move_affected: str | None = None): """ Delete version from the project :param int version: the version id to delete @@ -2650,13 +2720,13 @@ def delete_version(self, version, moved_fixed=None, move_affected=None): def update_version( self, - version, - name=None, - description=None, - is_archived=None, - is_released=None, - start_date=None, - release_date=None, + version: str, + name: str | None = None, + description: str | None = None, + is_archived: bool | None = None, + is_released: bool | None = None, + start_date: str | None = None, + release_date: str | None = None, ): """ Update a project version @@ -2680,7 +2750,7 @@ def update_version( url = "{base_url}/{version}".format(base_url=base_url, version=version) return self.put(url, data=payload) - def move_version(self, version, after=None, position=None): + def move_version(self, version: T_id, after: T_id | None = None, position: str | None = None): """ Reposition a project version :param version: The version id to move @@ -2702,7 +2772,7 @@ def move_version(self, version, after=None, position=None): ) return self.post(url, data={"position": position}) - def get_project_roles(self, project_key): + def get_project_roles(self, project_key: str): """ Provide associated project roles :param project_key: @@ -2712,7 +2782,7 @@ def get_project_roles(self, project_key): url = "{base_url}/{project_key}/role".format(base_url=base_url, project_key=project_key) return self.get(url) - def get_project_actors_for_role_project(self, project_key, role_id): + def get_project_actors_for_role_project(self, project_key: str, role_id: T_id): """ Returns the details for a given project role in a project. :param project_key: @@ -2723,7 +2793,9 @@ def get_project_actors_for_role_project(self, project_key, role_id): url = "{base_url}/{projectIdOrKey}/role/{id}".format(base_url=base_url, projectIdOrKey=project_key, id=role_id) return (self.get(url) or {}).get("actors") - def delete_project_actors(self, project_key, role_id, actor, actor_type=None): + def delete_project_actors( + self, project_key: str, role_id: T_id, actor: str, actor_type: Literal["user"] | Literal["group"] | None = None + ): """ Deletes actors (users or groups) from a project role. Delete a user from the role: /rest/api/2/project/{projectIdOrKey}/role/{roleId}?user={username} @@ -2738,12 +2810,12 @@ def delete_project_actors(self, project_key, role_id, actor, actor_type=None): url = "{base_url}/{projectIdOrKey}/role/{roleId}".format( base_url=base_url, projectIdOrKey=project_key, roleId=role_id ) - params = {} + params: dict = {} if actor_type is not None and actor_type in ["group", "user"]: params[actor_type] = actor return self.delete(url, params=params) - def add_user_into_project_role(self, project_key, role_id, user_name): + def add_user_into_project_role(self, project_key: str, role_id: T_id, user_name: str): """ :param project_key: @@ -2753,7 +2825,7 @@ def add_user_into_project_role(self, project_key, role_id, user_name): """ return self.add_project_actor_in_role(project_key, role_id, user_name, "atlassian-user-role-actor") - def add_project_actor_in_role(self, project_key, role_id, actor, actor_type): + def add_project_actor_in_role(self, project_key: str, role_id: T_id, actor: str, actor_type: str): """ :param project_key: @@ -2774,7 +2846,7 @@ def add_project_actor_in_role(self, project_key, role_id, actor, actor_type): return self.post(url, data=data) - def update_project(self, project_key, data, expand=None): + def update_project(self, project_key: str, data: dict, expand: str | None = None): """ Updates a project. Only non-null values sent in JSON will be updated in the project. @@ -2787,12 +2859,14 @@ def update_project(self, project_key, data, expand=None): """ base_url = self.resource_url("project") url = "{base_url}/{projectIdOrKey}".format(base_url=base_url, projectIdOrKey=project_key) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.put(url, data, params=params) - def update_project_category_for_project(self, project_key, new_project_category_id, expand=None): + def update_project_category_for_project( + self, project_key: str, new_project_category_id: T_id, expand: str | None = None + ): """ Updates a project. Update project: /rest/api/2/project/{projectIdOrKey} @@ -2810,7 +2884,7 @@ def update_project_category_for_project(self, project_key, new_project_category_ https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/notificationscheme """ - def get_notification_scheme_for_project(self, project_id_or_key): + def get_notification_scheme_for_project(self, project_id_or_key: str): """ Gets a notification scheme associated with the project. Follow the documentation of /notificationscheme/{id} resource for all details about returned value. @@ -2823,7 +2897,7 @@ def get_notification_scheme_for_project(self, project_id_or_key): ) return self.get(url) - def assign_project_notification_scheme(self, project_key, new_notification_scheme=""): + def assign_project_notification_scheme(self, project_key: str, new_notification_scheme: str = ""): """ Updates a project. Update project: /rest/api/2/project/{projectIdOrKey} @@ -2847,7 +2921,7 @@ def get_all_notification_schemes(self): """ return self.get_notification_schemes().get("values") or [] - def get_notification_scheme(self, notification_scheme_id, expand=None): + def get_notification_scheme(self, notification_scheme_id: T_id, expand: str | None = None): """ Returns a full representation of the notification scheme for the given id. Use 'expand' to get details @@ -2888,12 +2962,12 @@ def get_notification_scheme(self, notification_scheme_id, expand=None): url = "{base_url}/{notification_scheme_id}".format( base_url=base_url, notification_scheme_id=notification_scheme_id ) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def get_project_notification_scheme(self, project_id_or_key): + def get_project_notification_scheme(self, project_id_or_key: str): """ Gets a notification scheme assigned with a project @@ -2912,7 +2986,7 @@ def get_project_notification_scheme(self, project_id_or_key): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/permissionscheme """ - def assign_project_permission_scheme(self, project_id_or_key, permission_scheme_id): + def assign_project_permission_scheme(self, project_id_or_key: str, permission_scheme_id: T_id): """ Assigns a permission scheme with a project. :param project_id_or_key: @@ -2926,7 +3000,7 @@ def assign_project_permission_scheme(self, project_id_or_key, permission_scheme_ data = {"id": permission_scheme_id} return self.put(url, data=data) - def get_project_permission_scheme(self, project_id_or_key, expand=None): + def get_project_permission_scheme(self, project_id_or_key: str, expand: str | None = None): """ Gets a permission scheme assigned with a project Use 'expand' to get details @@ -2939,12 +3013,12 @@ def get_project_permission_scheme(self, project_id_or_key, expand=None): url = "{base_url}/{project_id_or_key}/permissionscheme".format( base_url=base_url, project_id_or_key=project_id_or_key ) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def create_permission_scheme(self, name, description, permissions): + def create_permission_scheme(self, name: str, description: str, permissions: dict): """ Create a new permission scheme @@ -2967,7 +3041,7 @@ def get_issue_types(self): url = self.resource_url("issuetype") return self.get(url) - def create_issue_type(self, name, description="", type="standard"): + def create_issue_type(self, name: str, description: str = "", type: str = "standard"): """ Create a new issue type :param name: @@ -3005,28 +3079,32 @@ def project_leaders(self): "lead_email": lead["emailAddress"], } - def get_project_issuekey_last(self, project): + def get_project_issuekey_last(self, project: str): jql = 'project = "{project}" ORDER BY issuekey DESC'.format(project=project) response = self.jql(jql) if self.advanced_mode: return response return (response.get("issues") or {"key": None})[0]["key"] - def get_project_issuekey_all(self, project, start=0, limit=None, expand=None): + def get_project_issuekey_all( + self, project: str, start: int = 0, limit: int | None = None, expand: str | None = None + ): jql = 'project = "{project}" ORDER BY issuekey ASC'.format(project=project) response = self.jql(jql, start=start, limit=limit, expand=expand) if self.advanced_mode: return response return [issue["key"] for issue in response["issues"]] - def get_project_issues_count(self, project): + def get_project_issues_count(self, project: str): jql = 'project = "{project}" '.format(project=project) response = self.jql(jql, fields="*none") if self.advanced_mode: return response return response["total"] - def get_all_project_issues(self, project, fields="*all", start=0, limit=None): + def get_all_project_issues( + self, project: str, fields: str | list[str] = "*all", start: int = 0, limit: int | None = None + ): """ Get the Issues for a Project :param project: Project Key name @@ -3041,7 +3119,7 @@ def get_all_project_issues(self, project, fields="*all", start=0, limit=None): return response return response["issues"] - def get_all_assignable_users_for_project(self, project_key, start=0, limit=50): + def get_all_assignable_users_for_project(self, project_key: str, start: int = 0, limit: int = 50): """ Provide assignable users for project :param project_key: @@ -3059,7 +3137,9 @@ def get_all_assignable_users_for_project(self, project_key, start=0, limit=50): ) return self.get(url) - def get_assignable_users_for_issue(self, issue_key, username=None, start=0, limit=50): + def get_assignable_users_for_issue( + self, issue_key: str, username: str | None = None, start: int = 0, limit: int = 50 + ): """ Provide assignable users for issue :param issue_key: @@ -3077,12 +3157,12 @@ def get_assignable_users_for_issue(self, issue_key, username=None, start=0, limi url += "&username={username}".format(username=username) return self.get(url) - def get_status_id_from_name(self, status_name): + def get_status_id_from_name(self, status_name: str): base_url = self.resource_url("status") url = "{base_url}/{name}".format(base_url=base_url, name=status_name) return int((self.get(url) or {}).get("id")) - def get_status_for_project(self, project_key): + def get_status_for_project(self, project_key: str): base_url = self.resource_url("project") url = "{base_url}/{name}/statuses".format(base_url=base_url, name=project_key) return self.get(url) @@ -3111,7 +3191,7 @@ def get_time_tracking_settings(self): url = self.resource_url("configuration/timetracking/options") return self.get(url) - def get_transition_id_to_status_name(self, issue_key, status_name): + def get_transition_id_to_status_name(self, issue_key: str, status_name: str): for transition in self.get_issue_transitions(issue_key): if status_name.lower() == transition["to"].lower(): return int(transition["id"]) @@ -3121,7 +3201,7 @@ def get_transition_id_to_status_name(self, issue_key, status_name): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/issueLink """ - def create_issue_link(self, data): + def create_issue_link(self, data: dict): """ Creates an issue link between two issues. The user requires the link issue permission for the issue which will be linked to another issue. @@ -3147,7 +3227,7 @@ def create_issue_link(self, data): url = self.resource_url("issueLink") return self.post(url, data=data) - def get_issue_link(self, link_id): + def get_issue_link(self, link_id: T_id): """ Returns an issue link with the specified id. :param link_id: the issue link id. @@ -3157,7 +3237,7 @@ def get_issue_link(self, link_id): url = "{base_url}/{link_id}".format(base_url=base_url, link_id=link_id) return self.get(url) - def remove_issue_link(self, link_id): + def remove_issue_link(self, link_id: T_id): """ Deletes an issue link with the specified id. To be able to delete an issue link you must be able to view both issues @@ -3190,7 +3270,7 @@ def get_issue_link_types_names(self): """ return [link_type["name"] for link_type in self.get_issue_link_types()] - def create_issue_link_type_by_json(self, data): + def create_issue_link_type_by_json(self, data: dict): """Create a new issue link type. :param data: { @@ -3203,7 +3283,7 @@ def create_issue_link_type_by_json(self, data): url = self.resource_url("issueLinkType") return self.post(url, data=data) - def create_issue_link_type(self, link_type_name, inward, outward): + def create_issue_link_type(self, link_type_name: str, inward: str, outward: str): """Create a new issue link type. :param outward: :param inward: @@ -3216,19 +3296,19 @@ def create_issue_link_type(self, link_type_name, inward, outward): data = {"name": link_type_name, "inward": inward, "outward": outward} return self.create_issue_link_type_by_json(data=data) - def get_issue_link_type(self, issue_link_type_id): + def get_issue_link_type(self, issue_link_type_id: T_id): """Returns for a given issue link type id all information about this issue link type.""" base_url = self.resource_url("issueLinkType") url = "{base_url}/{issueLinkTypeId}".format(base_url=base_url, issueLinkTypeId=issue_link_type_id) return self.get(url) - def delete_issue_link_type(self, issue_link_type_id): + def delete_issue_link_type(self, issue_link_type_id: T_id): """Delete the specified issue link type.""" base_url = self.resource_url("issueLinkType") url = "{base_url}/{issueLinkTypeId}".format(base_url=base_url, issueLinkTypeId=issue_link_type_id) return self.delete(url) - def update_issue_link_type(self, issue_link_type_id, data): + def update_issue_link_type(self, issue_link_type_id: T_id, data: dict): """ Update the specified issue link type. :param issue_link_type_id: @@ -3256,7 +3336,7 @@ def get_all_resolutions(self): url = self.resource_url("resolution") return self.get(url) - def get_resolution_by_id(self, resolution_id): + def get_resolution_by_id(self, resolution_id: T_id): """ Get Resolution info by id :param resolution_id: @@ -3292,7 +3372,7 @@ def get_all_screens(self): url = self.resource_url("screens") return self.get(url) - def get_all_available_screen_fields(self, screen_id): + def get_all_available_screen_fields(self, screen_id: T_id): """ Get all available fields by screen id :param screen_id: @@ -3302,7 +3382,7 @@ def get_all_available_screen_fields(self, screen_id): url = "{base_url}/{screen_id}/availableFields".format(base_url=base_url, screen_id=screen_id) return self.get(url) - def get_screen_tabs(self, screen_id): + def get_screen_tabs(self, screen_id: T_id): """ Get tabs for the screen id :param screen_id: @@ -3312,7 +3392,7 @@ def get_screen_tabs(self, screen_id): url = "{base_url}/{screen_id}/tabs".format(base_url=base_url, screen_id=screen_id) return self.get(url) - def get_screen_tab_fields(self, screen_id, tab_id): + def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id): """ Get fields by the tab id and the screen id :param tab_id: @@ -3325,14 +3405,14 @@ def get_screen_tab_fields(self, screen_id, tab_id): ) return self.get(url) - def get_all_screen_fields(self, screen_id): + def get_all_screen_fields(self, screen_id: T_id): """ Get all fields by screen id :param screen_id: :return: """ screen_tabs = self.get_screen_tabs(screen_id) - fields = [] + fields: list = [] for screen_tab in screen_tabs: tab_id = screen_tab["id"] if tab_id: @@ -3340,7 +3420,7 @@ def get_all_screen_fields(self, screen_id): fields = fields + tab_fields return fields - def add_field(self, field_id, screen_id, tab_id): + def add_field(self, field_id: T_id, screen_id: T_id, tab_id: T_id): """ Add field to a given tab in a screen :param field_id: field or custom field ID to be added @@ -3358,12 +3438,12 @@ def add_field(self, field_id, screen_id, tab_id): def jql( self, - jql, - fields="*all", - start=0, - limit=None, - expand=None, - validate_query=None, + jql: str, + fields: str | list[str] = "*all", + start: int = 0, + limit: int | None = None, + expand: str | None = None, + validate_query: str | None = None, ): """ Get issues from jql search result with all related fields @@ -3376,7 +3456,7 @@ def jql( :param validate_query: OPTIONAL: Whether to validate the JQL query :return: """ - params = {} + params: dict = {} if start is not None: params["startAt"] = int(start) if limit is not None: @@ -3396,13 +3476,13 @@ def jql( def jql_get_list_of_tickets( self, - jql, - fields="*all", - start=0, - limit=None, - expand=None, - validate_query=None, - ): + jql: str, + fields: str | dict = "*all", + start: int = 0, + limit: int | None = None, + expand: str | None = None, + validate_query: str | None = None, + ) -> list: """ Get issues from jql search result with all related fields :param jql: @@ -3414,7 +3494,7 @@ def jql_get_list_of_tickets( :param validate_query: Whether to validate the JQL query :return: """ - params = {} + params: dict = {} if limit is not None: params["maxResults"] = int(limit) if fields is not None: @@ -3429,7 +3509,7 @@ def jql_get_list_of_tickets( params["validateQuery"] = validate_query url = self.resource_url("search") - results = [] + results: list[object] = [] while True: params["startAt"] = int(start) response = self.get(url, params=params) @@ -3447,7 +3527,14 @@ def jql_get_list_of_tickets( return results - def csv(self, jql, limit=1000, all_fields=True, start=None, delimiter=None): + def csv( + self, + jql: str, + limit: int = 1000, + all_fields: bool = True, + start: int | None = None, + delimiter: str | None = None, + ): """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3459,7 +3546,7 @@ def csv(self, jql, limit=1000, all_fields=True, start=None, delimiter=None): :return: CSV file """ - params = {"jqlQuery": jql} + params: dict = {"jqlQuery": jql} if limit: params["tempMax"] = limit if start: @@ -3479,7 +3566,7 @@ def csv(self, jql, limit=1000, all_fields=True, start=None, delimiter=None): headers={"Accept": "application/csv"}, ) - def excel(self, jql, limit=1000, all_fields=True, start=None): + def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int | None = None): """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3490,7 +3577,7 @@ def excel(self, jql, limit=1000, all_fields=True, start=None): :return: CSV file """ - params = {"jqlQuery": jql} + params: dict = {"jqlQuery": jql} if limit: params["tempMax"] = limit if start: @@ -3508,7 +3595,7 @@ def excel(self, jql, limit=1000, all_fields=True, start=None): headers={"Accept": "application/vnd.ms-excel"}, ) - def export_html(self, jql, limit=None, all_fields=True, start=None): + def export_html(self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None): """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3519,7 +3606,7 @@ def export_html(self, jql, limit=None, all_fields=True, start=None): :return: HTML file """ - params = {"jqlQuery": jql} + params: dict = {"jqlQuery": jql} if limit: params["tempMax"] = limit if start: @@ -3545,7 +3632,7 @@ def get_all_priorities(self): url = self.resource_url("priority") return self.get(url) - def get_priority_by_id(self, priority_id): + def get_priority_by_id(self, priority_id: T_id): """ Get Priority info by id :param priority_id: @@ -3568,7 +3655,13 @@ def get_all_workflows(self): url = self.resource_url("workflow") return self.get(url) - def get_workflows_paginated(self, start_at=None, max_results=None, workflow_name=None, expand=None): + def get_workflows_paginated( + self, + start_at: int | None = None, + max_results: int | None = None, + workflow_name: str | None = None, + expand: str | None = None, + ): """ Provide all workflows paginated (see https://developer.atlassian.com/cloud/jira/platform/rest/v2/\ api-group-workflows/#api-rest-api-2-workflow-search-get) @@ -3582,7 +3675,7 @@ def get_workflows_paginated(self, start_at=None, max_results=None, workflow_name """ url = self.resource_url("workflow/search") - params = {} + params: dict = {} if start_at: params["startAt"] = start_at if max_results: @@ -3610,7 +3703,7 @@ def get_plugins_info(self): url = "rest/plugins/1.0/" return self.get(url, headers=self.no_check_headers, trailing=True) - def get_plugin_info(self, plugin_key): + def get_plugin_info(self, plugin_key: str): """ Provide plugin info :return a json of installed plugins @@ -3618,7 +3711,7 @@ def get_plugin_info(self, plugin_key): url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) return self.get(url, headers=self.no_check_headers, trailing=True) - def get_plugin_license_info(self, plugin_key): + def get_plugin_license_info(self, plugin_key: str): """ Provide plugin license info :return a json specific License query @@ -3626,7 +3719,7 @@ def get_plugin_license_info(self, plugin_key): url = "rest/plugins/1.0/{plugin_key}-key/license".format(plugin_key=plugin_key) return self.get(url, headers=self.no_check_headers, trailing=True) - def upload_plugin(self, plugin_path): + def upload_plugin(self, plugin_path: str): """ Provide plugin path for upload into Jira e.g. useful for auto deploy :param plugin_path: @@ -3642,7 +3735,7 @@ def upload_plugin(self, plugin_path): url = "rest/plugins/1.0/?token={upm_token}".format(upm_token=upm_token) return self.post(url, files=files, headers=self.no_check_headers) - def delete_plugin(self, plugin_key): + def delete_plugin(self, plugin_key: str): """ Delete plugin :param plugin_key: @@ -3651,11 +3744,11 @@ def delete_plugin(self, plugin_key): url = "rest/plugins/1.0/{}-key".format(plugin_key) return self.delete(url) - def check_plugin_manager_status(self): + def check_plugin_manager_status(self) -> Response: 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): + def update_plugin_license(self, plugin_key: str, raw_license: str): """ Update license for plugin :param plugin_key: @@ -3670,7 +3763,7 @@ def update_plugin_license(self, plugin_key, raw_license): data = {"rawLicense": raw_license} return self.put(url, data=data, headers=app_headers) - def disable_plugin(self, plugin_key): + def disable_plugin(self, plugin_key: str): """ Disable a plugin :param plugin_key: @@ -3684,7 +3777,7 @@ def disable_plugin(self, plugin_key): data = {"status": "disabled"} return self.put(url, data=data, headers=app_headers) - def enable_plugin(self, plugin_key): + def enable_plugin(self, plugin_key: str): """ Enable a plugin :param plugin_key: @@ -3698,7 +3791,7 @@ def enable_plugin(self, plugin_key): data = {"status": "enabled"} return self.put(url, data=data, headers=app_headers) - def get_all_permissionschemes(self, expand=None): + def get_all_permissionschemes(self, expand: str | None = None): """ Returns a list of all permission schemes. By default, only shortened beans are returned. @@ -3709,12 +3802,12 @@ def get_all_permissionschemes(self, expand=None): :return: """ url = self.resource_url("permissionscheme") - params = {} + params: dict = {} if expand: params["expand"] = expand return (self.get(url, params=params) or {}).get("permissionSchemes") - def get_permissionscheme(self, permission_id, expand=None): + def get_permissionscheme(self, permission_id: T_id, expand: str | None = None): """ Returns a list of all permission schemes. By default, only shortened beans are returned. @@ -3727,12 +3820,12 @@ def get_permissionscheme(self, permission_id, expand=None): """ base_url = self.resource_url("permissionscheme") url = "{base_url}/{schemeID}".format(base_url=base_url, schemeID=permission_id) - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def set_permissionscheme_grant(self, permission_id, new_permission): + def set_permissionscheme_grant(self, permission_id: T_id, new_permission: str): """ Creates a permission grant in a permission scheme. Example: @@ -3770,7 +3863,7 @@ def get_issue_security_schemes(self): url = self.resource_url("issuesecurityschemes") return self.get(url).get("issueSecuritySchemes") - def get_issue_security_scheme(self, scheme_id, only_levels=False): + def get_issue_security_scheme(self, scheme_id: T_id, only_levels: bool = False): """ Returns the issue security scheme along with that are defined @@ -3789,7 +3882,7 @@ def get_issue_security_scheme(self, scheme_id, only_levels=False): else: return self.get(url) - def get_project_issue_security_scheme(self, project_id_or_key, only_levels=False): + def get_project_issue_security_scheme(self, project_id_or_key: int, only_levels: bool = False) -> dict | None: """ Returns the issue security scheme for project @@ -3821,7 +3914,7 @@ def get_project_issue_security_scheme(self, project_id_or_key, only_levels=False return response.get("levels") or None return response - def get_all_priority_schemes(self, start=0, limit=100, expand=None): + def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: str | None = None): """ Returns all priority schemes. All project keys associated with the priority scheme will only be returned @@ -3832,7 +3925,7 @@ def get_all_priority_schemes(self, start=0, limit=100, expand=None): :return: """ url = self.resource_url("priorityschemes") - params = {} + params: dict = {} if start: params["startAt"] = int(start) if limit: @@ -3841,7 +3934,7 @@ def get_all_priority_schemes(self, start=0, limit=100, expand=None): params["expand"] = expand return self.get(url, params=params) - def create_priority_scheme(self, data): + def create_priority_scheme(self, data: dict): """ Creates new priority scheme. :param data: @@ -3866,7 +3959,7 @@ def create_priority_scheme(self, data): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/priorityscheme """ - def get_priority_scheme_of_project(self, project_key_or_id, expand=None): + def get_priority_scheme_of_project(self, project_key_or_id: str, expand: str | None = None): """ Gets a full representation of a priority scheme in JSON format used by specified project. Resource for associating priority scheme schemes and projects. @@ -3875,7 +3968,7 @@ def get_priority_scheme_of_project(self, project_key_or_id, expand=None): :param expand: notificationSchemeEvents,user,group,projectRole,field,all :return: """ - params = {} + params: dict = {} if expand: params["expand"] = expand base_url = self.resource_url("project") @@ -3884,7 +3977,7 @@ def get_priority_scheme_of_project(self, project_key_or_id, expand=None): ) return self.get(url, params=params) - def assign_priority_scheme_for_project(self, project_key_or_id, priority_scheme_id): + def assign_priority_scheme_for_project(self, project_key_or_id: str, priority_scheme_id: T_id): """ Assigns project with priority scheme. Priority scheme assign with migration is possible from the UI. Operation will fail if migration is needed as a result of operation @@ -3906,7 +3999,7 @@ def assign_priority_scheme_for_project(self, project_key_or_id, priority_scheme_ https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/securitylevel """ - def get_security_level_for_project(self, project_key_or_id): + def get_security_level_for_project(self, project_key_or_id: T_id): """ Returns all security levels for the project that the current logged-in user has access to. If the user does not have the Set Issue Security permission, the list will be empty. @@ -3949,13 +4042,13 @@ def get_all_project_categories(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/projectvalidate """ - def get_project_validated_key(self, key): + def get_project_validated_key(self, key: str): """ Validates a project key. :param key: the project key :return: """ - params = {"key": key} + params: dict = {"key": key} url = self.resource_url("projectvalidate/key") return self.get(url, params=params) @@ -3963,7 +4056,7 @@ def get_project_validated_key(self, key): REST resources for Issue Type Schemes """ - def add_issue_type_scheme(self, scheme_id, project_key): + def add_issue_type_scheme(self, scheme_id: T_id, project_key: str): """ Associate an issue type scheme with an additional project https://docs.atlassian.com/software/jira/docs/api/REST/8.5.8#api/2/issuetypescheme-addProjectAssociationsToScheme @@ -3975,7 +4068,7 @@ def add_issue_type_scheme(self, scheme_id, project_key): data = {"idsOrKeys": [project_key]} return self.post(url, data=data) - def create_issuetype_scheme(self, name, description, default_issue_type_id, issue_type_ids): + def create_issuetype_scheme(self, name: str, description: str, default_issue_type_id: T_id, issue_type_ids: list): """ Create an issue type scheme https://docs.atlassian.com/software/jira/docs/api/REST/8.13.6/#api/2/issuetypescheme-createIssueTypeScheme @@ -4000,10 +4093,10 @@ def create_issuetype_scheme(self, name, description, default_issue_type_id, issu def reindex( self, - comments=True, - change_history=True, - worklogs=True, - indexing_type="BACKGROUND_PREFERRED", + comments: bool = True, + change_history: bool = True, + worklogs: bool = True, + indexing_type: str = "BACKGROUND_PREFERRED", ): """ Reindex the Jira instance @@ -4023,7 +4116,7 @@ def reindex( :param indexing_type: OPTIONAL: The default value for the type is BACKGROUND_PREFERRED :return: """ - params = {} + params: dict = {} if not comments: params["indexComments"] = comments if not change_history: @@ -4035,7 +4128,7 @@ def reindex( url = self.resource_url("reindex") return self.post(url, params=params) - def reindex_with_type(self, indexing_type="BACKGROUND_PREFERRED"): + def reindex_with_type(self, indexing_type: str = "BACKGROUND_PREFERRED"): """ Reindex the Jira instance Type of re-indexing available: @@ -4060,27 +4153,27 @@ def reindex_status(self): url = self.resource_url("reindex") return self.get(url) - def reindex_project(self, project_key): + def reindex_project(self, project_key: str): return self.post( "secure/admin/IndexProject.jspa", data="confirmed=true&key={}".format(project_key), headers=self.form_token_headers, ) - def reindex_issue(self, list_of_): + def reindex_issue(self, list_of_: list) -> None: pass - def index_checker(self, max_results=100): + def index_checker(self, max_results: int = 100): """ Jira DC Index health checker :param max_results: :return: """ url = "rest/indexanalyzer/1/state" - params = {"maxResults": max_results} + params: dict = {"maxResults": max_results} return self.get(url, params=params) - def get_server_info(self, do_health_check=False): + def get_server_info(self, do_health_check: bool = False): """ Returns general information about the current Jira server. with health checks or not. @@ -4095,14 +4188,14 @@ def get_server_info(self, do_health_check=False): ####################################################################### # Tempo Account REST API implements ####################################################################### - def tempo_account_get_accounts(self, skip_archived=None, expand=None): + def tempo_account_get_accounts(self, skip_archived: bool | None = None, expand: str | None = None): """ Get all Accounts that the logged-in user has permission to browse. :param skip_archived: bool OPTIONAL: skip archived Accounts, either true or false, default value true. :param expand: bool OPTIONAL: With expanded data or not :return: """ - params = {} + params: dict = {} if skip_archived is not None: params["skipArchived"] = skip_archived if expand is not None: @@ -4110,7 +4203,7 @@ def tempo_account_get_accounts(self, skip_archived=None, expand=None): url = "rest/tempo-accounts/1/account" return self.get(url, params=params) - def tempo_account_get_accounts_by_jira_project(self, project_id): + def tempo_account_get_accounts_by_jira_project(self, project_id: T_id): """ Get Accounts by JIRA Project. The Caller must have the Browse Account permission for Account. This will return Accounts for which the Caller has Browse Account Permission for. @@ -4121,7 +4214,7 @@ def tempo_account_get_accounts_by_jira_project(self, project_id): return self.get(url) def tempo_account_associate_with_jira_project( - self, account_id, project_id, default_account=False, link_type="MANUAL" + self, account_id: T_id, project_id: T_id, default_account: bool = False, link_type: str = "MANUAL" ): """ The AccountLinkBean for associate Account with project @@ -4156,7 +4249,7 @@ def tempo_account_associate_with_jira_project( url = "rest/tempo-accounts/1/link/" return self.post(url, data=data) - def tempo_account_add_account(self, data=None): + def tempo_account_add_account(self, data: dict | None = None): """ Creates Account, adding new Account requires the Manage Accounts Permission. :param data: String then it will convert to json @@ -4173,7 +4266,7 @@ def tempo_account_add_account(self, data=None): """ return self.post(url, data=data) - def tempo_account_delete_account_by_id(self, account_id): + def tempo_account_delete_account_by_id(self, account_id: str): """ Delete an Account by id. Caller must have the Manage Account Permission for the Account. The Account can not be deleted if it has an AccountLinkBean. @@ -4183,17 +4276,17 @@ def tempo_account_delete_account_by_id(self, account_id): url = "rest/tempo-accounts/1/account/{id}/".format(id=account_id) return self.delete(url) - def tempo_account_get_rate_table_by_account_id(self, account_id): + def tempo_account_get_rate_table_by_account_id(self, account_id: str): """ Returns a rate table for the specified account. :param account_id: the account id. :return: """ - params = {"scopeType": "ACCOUNT", "scopeId": account_id} + params: dict = {"scopeType": "ACCOUNT", "scopeId": account_id} url = "rest/tempo-accounts/1/ratetable" return self.get(url, params=params) - def tempo_account_get_all_account_by_customer_id(self, customer_id): + def tempo_account_get_all_account_by_customer_id(self, customer_id: T_id): """ Get un-archived Accounts by customer. The Caller must have the Browse Account permission for the Account. :param customer_id: the Customer id. @@ -4202,7 +4295,7 @@ def tempo_account_get_all_account_by_customer_id(self, customer_id): url = "rest/tempo-accounts/1/account/customer/{customerId}/".format(customerId=customer_id) return self.get(url) - def tempo_account_get_customers(self, query=None, count_accounts=None): + def tempo_account_get_customers(self, query: str | None = None, count_accounts: bool | None = None): """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4210,7 +4303,7 @@ def tempo_account_get_customers(self, query=None, count_accounts=None): :param count_accounts: bool OPTIONAL: provide how many associated Accounts with Customer :return: list of customers """ - params = {} + params: dict = {} if query is not None: params["query"] = query if count_accounts is not None: @@ -4218,7 +4311,7 @@ def tempo_account_get_customers(self, query=None, count_accounts=None): url = "rest/tempo-accounts/1/customer" return self.get(url, params=params) - def tempo_account_add_new_customer(self, key, name): + def tempo_account_add_new_customer(self, key: str, name: str): """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4230,7 +4323,7 @@ def tempo_account_add_new_customer(self, key, name): url = "rest/tempo-accounts/1/customer" return self.post(url, data=data) - def tempo_account_add_customer(self, data=None): + def tempo_account_add_customer(self, data: dict | None = None): """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4245,7 +4338,7 @@ def tempo_account_add_customer(self, data=None): url = "rest/tempo-accounts/1/customer" return self.post(url, data=data) - def tempo_account_get_customer_by_id(self, customer_id=1): + def tempo_account_get_customer_by_id(self, customer_id: T_id = 1): """ Get Account Attribute whose key or name contain a specific substring. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4254,7 +4347,7 @@ def tempo_account_get_customer_by_id(self, customer_id=1): url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.get(url) - def tempo_account_update_customer_by_id(self, customer_id=1, data=None): + def tempo_account_update_customer_by_id(self, customer_id: T_id = 1, data: dict | None = None): """ Updates an Attribute. Caller must have Manage Account Permission. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4275,7 +4368,7 @@ def tempo_account_update_customer_by_id(self, customer_id=1, data=None): url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.put(url, data=data) - def tempo_account_delete_customer_by_id(self, customer_id=1): + def tempo_account_delete_customer_by_id(self, customer_id: T_id = 1): """ Delete an Attribute. Caller must have Manage Account Permission. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4301,7 +4394,7 @@ def tempo_holiday_get_schemes(self): url = "rest/tempo-core/2/holidayschemes/" return self.get(url) - def tempo_holiday_get_scheme_info(self, scheme_id): + def tempo_holiday_get_scheme_info(self, scheme_id: T_id): """ Provide a holiday scheme :return: @@ -4309,7 +4402,7 @@ def tempo_holiday_get_scheme_info(self, scheme_id): url = "rest/tempo-core/2/holidayschemes/{}".format(scheme_id) return self.get(url) - def tempo_holiday_get_scheme_members(self, scheme_id): + def tempo_holiday_get_scheme_members(self, scheme_id: T_id): """ Provide a holiday scheme members :return: @@ -4317,7 +4410,7 @@ def tempo_holiday_get_scheme_members(self, scheme_id): url = "rest/tempo-core/2/holidayschemes/{}/members".format(scheme_id) return self.get(url) - def tempo_holiday_put_into_scheme_member(self, scheme_id, username): + def tempo_holiday_put_into_scheme_member(self, scheme_id: T_id, username: str): """ Provide a holiday scheme :return: @@ -4326,7 +4419,7 @@ def tempo_holiday_put_into_scheme_member(self, scheme_id, username): data = {"id": scheme_id} return self.put(url, data=data) - def tempo_holiday_scheme_set_default(self, scheme_id): + def tempo_holiday_scheme_set_default(self, scheme_id: T_id): """ Set as default the holiday scheme :param scheme_id: @@ -4339,7 +4432,7 @@ def tempo_holiday_scheme_set_default(self, scheme_id): data = {"id": scheme_id} return self.post(url, data=data) - def tempo_workload_scheme_get_members(self, scheme_id): + def tempo_workload_scheme_get_members(self, scheme_id: T_id): """ Provide a workload scheme members :param scheme_id: @@ -4348,7 +4441,7 @@ def tempo_workload_scheme_get_members(self, scheme_id): url = "rest/tempo-core/1/workloadscheme/users/{}".format(scheme_id) return self.get(url) - def tempo_workload_scheme_set_member(self, scheme_id, member): + def tempo_workload_scheme_set_member(self, scheme_id: T_id, member: str): """ Provide a workload scheme members :param member: username of user @@ -4367,7 +4460,9 @@ def tempo_timesheets_get_configuration(self): url = "rest/tempo-timesheets/3/private/config/" return self.get(url) - def tempo_timesheets_get_team_utilization(self, team_id, date_from, date_to=None, group_by=None): + def tempo_timesheets_get_team_utilization( + self, team_id: T_id, date_from: str, date_to: str | None = None, group_by: str | None = None + ): """ Get team utilization. Response in json :param team_id: @@ -4377,7 +4472,7 @@ def tempo_timesheets_get_team_utilization(self, team_id, date_from, date_to=None :return: """ url = "rest/tempo-timesheets/3/report/team/{}/utilization".format(team_id) - params = {"dateFrom": date_from, "dateTo": date_to} + params: dict = {"dateFrom": date_from, "dateTo": date_to} if group_by: params["groupBy"] = group_by @@ -4385,12 +4480,12 @@ def tempo_timesheets_get_team_utilization(self, team_id, date_from, date_to=None def tempo_timesheets_get_worklogs( self, - date_from=None, - date_to=None, - username=None, - project_key=None, - account_key=None, - team_id=None, + date_from: str | None = None, + date_to: str | None = None, + username: str | None = None, + project_key: str | None = None, + account_key: str | None = None, + team_id: T_id | None = None, ): """ @@ -4402,7 +4497,7 @@ def tempo_timesheets_get_worklogs( :param team_id: id of the Team you wish to get the worklogs for :return: """ - params = {} + params: dict = {} if date_from: params["dateFrom"] = date_from if date_to: @@ -4419,7 +4514,7 @@ def tempo_timesheets_get_worklogs( return self.get(url, params=params) # noinspection PyIncorrectDocstring - def tempo_4_timesheets_find_worklogs(self, date_from=None, date_to=None, **params): + def tempo_4_timesheets_find_worklogs(self, date_from: str | None = None, date_to: str | None = None, **params): """ Find existing worklogs with searching parameters. NOTE: check if you are using correct types for the parameters! @@ -4454,7 +4549,7 @@ def tempo_4_timesheets_find_worklogs(self, date_from=None, date_to=None, **param url = "rest/tempo-timesheets/4/worklogs/search" return self.post(url, data=params) - def tempo_timesheets_get_worklogs_by_issue(self, issue): + def tempo_timesheets_get_worklogs_by_issue(self, issue: str): """ Get Tempo timesheet worklog by issue key or id. :param issue: Issue key or ID @@ -4463,7 +4558,9 @@ def tempo_timesheets_get_worklogs_by_issue(self, issue): url = "rest/tempo-timesheets/4/worklogs/jira/issue/{issue}".format(issue=issue) return self.get(url) - def tempo_timesheets_write_worklog(self, worker, started, time_spend_in_seconds, issue_id, comment=None): + def tempo_timesheets_write_worklog( + self, worker: str, started: str, time_spend_in_seconds: int, issue_id: T_id, comment: str | None = None + ): """ Log work for user :param worker: @@ -4484,7 +4581,7 @@ def tempo_timesheets_write_worklog(self, worker, started, time_spend_in_seconds, url = "rest/tempo-timesheets/4/worklogs/" return self.post(url, data=data) - def tempo_timesheets_approval_worklog_report(self, user_key, period_start_date): + def tempo_timesheets_approval_worklog_report(self, user_key: str, period_start_date: str): """ Return timesheets for approval :param user_key: @@ -4492,14 +4589,14 @@ def tempo_timesheets_approval_worklog_report(self, user_key, period_start_date): :return: """ url = "rest/tempo-timesheets/4/timesheet-approval/current" - params = {} + params: dict = {} if period_start_date: params["periodStartDate"] = period_start_date if user_key: params["userKey"] = user_key return self.get(url, params=params) - def tempo_timesheets_get_required_times(self, from_date, to_date, user_name): + def tempo_timesheets_get_required_times(self, from_date: str, to_date: str, user_name: str): """ Provide time how much should work :param from_date: @@ -4508,7 +4605,7 @@ def tempo_timesheets_get_required_times(self, from_date, to_date, user_name): :return: """ url = "rest/tempo-timesheets/3/private/days" - params = {} + params: dict = {} if from_date: params["from"] = from_date if to_date: @@ -4517,16 +4614,16 @@ def tempo_timesheets_get_required_times(self, from_date, to_date, user_name): params["user"] = user_name return self.get(url, params=params) - def tempo_timesheets_approval_status(self, period_start_date, user_name): + def tempo_timesheets_approval_status(self, period_start_date: str, user_name: str): url = "rest/tempo-timesheets/4/timesheet-approval/approval-statuses" - params = {} + params: dict = {} if user_name: params["userKey"] = user_name if period_start_date: params["periodStartDate"] = period_start_date return self.get(url, params=params) - def tempo_get_links_to_project(self, project_id): + def tempo_get_links_to_project(self, project_id: T_id): """ Gets all links to a specific project :param project_id: @@ -4535,7 +4632,7 @@ def tempo_get_links_to_project(self, project_id): url = "rest/tempo-accounts/1/link/project/{}/".format(project_id) return self.get(url) - def tempo_get_default_link_to_project(self, project_id): + def tempo_get_default_link_to_project(self, project_id: T_id): """ Gets the default link to a specific project :param project_id: @@ -4544,14 +4641,14 @@ def tempo_get_default_link_to_project(self, project_id): url = "rest/tempo-accounts/1/link/project/{}/default/".format(project_id) return self.get(url) - def tempo_teams_get_all_teams(self, expand=None): + def tempo_teams_get_all_teams(self, expand: str | None = None): url = "rest/tempo-teams/2/team" - params = {} + params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def tempo_teams_add_member(self, team_id, member_key): + def tempo_teams_add_member(self, team_id: T_id, member_key: str): """ Add team member :param team_id: @@ -4564,7 +4661,7 @@ def tempo_teams_add_member(self, team_id, member_key): } return self.tempo_teams_add_member_raw(team_id, member_data=data) - def tempo_teams_add_membership(self, team_id, member_id): + def tempo_teams_add_membership(self, team_id: T_id, member_id: T_id): """ Add team member :param team_id: @@ -4580,7 +4677,7 @@ def tempo_teams_add_membership(self, team_id, member_id): url = "rest/tempo-teams/2/team/{}/member/{}/membership".format(team_id, member_id) return self.post(url, data=data) - def tempo_teams_add_member_raw(self, team_id, member_data): + def tempo_teams_add_member_raw(self, team_id: T_id, member_data: dict): """ Add team member :param team_id: @@ -4591,7 +4688,7 @@ def tempo_teams_add_member_raw(self, team_id, member_data): data = member_data return self.post(url, data=data) - def tempo_teams_get_members(self, team_id): + def tempo_teams_get_members(self, team_id: T_id): """ Get members from team :param team_id: @@ -4600,7 +4697,7 @@ def tempo_teams_get_members(self, team_id): url = "rest/tempo-teams/2/team/{}/member/".format(team_id) return self.get(url) - def tempo_teams_remove_member(self, team_id, member_id, membership_id): + def tempo_teams_remove_member(self, team_id: T_id, member_id: T_id, membership_id: T_id): """ Remove team membership :param team_id: @@ -4611,7 +4708,7 @@ def tempo_teams_remove_member(self, team_id, member_id, membership_id): url = "rest/tempo-teams/2/team/{}/member/{}/membership/{}".format(team_id, member_id, membership_id) return self.delete(url) - def tempo_teams_update_member_information(self, team_id, member_id, membership_id, data): + def tempo_teams_update_member_information(self, team_id: T_id, member_id: T_id, membership_id: T_id, data: dict): """ Update team membership attribute info :param team_id: @@ -4623,13 +4720,13 @@ def tempo_teams_update_member_information(self, team_id, member_id, membership_i url = "rest/tempo-teams/2/team/{}/member/{}/membership/{}".format(team_id, member_id, membership_id) return self.put(url, data=data) - def tempo_timesheets_get_period_configuration(self): + def tempo_timesheets_get_period_configuration(self) -> dict | None: return self.get("rest/tempo-timesheets/3/period-configuration") - def tempo_timesheets_get_private_configuration(self): + def tempo_timesheets_get_private_configuration(self) -> dict | None: return self.get("rest/tempo-timesheets/3/private/config") - def tempo_teams_get_memberships_for_member(self, username): + def tempo_teams_get_memberships_for_member(self, username: str) -> dict | None: return self.get("rest/tempo-teams/2/user/{}/memberships".format(username)) ####################################################################### @@ -4637,7 +4734,7 @@ def tempo_teams_get_memberships_for_member(self, username): # Resource: https://docs.atlassian.com/jira-software/REST/7.3.1/ ####################################################################### # /rest/agile/1.0/backlog/issue - def move_issues_to_backlog(self, issue_keys): + def move_issues_to_backlog(self, issue_keys: list) -> dict | None: """ Move issues to backlog :param issue_keys: list of issues @@ -4645,7 +4742,7 @@ def move_issues_to_backlog(self, issue_keys): """ return self.add_issues_to_backlog(issues=issue_keys) - def add_issues_to_backlog(self, issues): + def add_issues_to_backlog(self, issues: list) -> dict | None: """ Adding Issue(s) to Backlog :param issues: list: List of Issue Keys @@ -4660,7 +4757,7 @@ def add_issues_to_backlog(self, issues): data = dict(issues=issues) return self.post(url, data=data) - def get_agile_board_by_filter_id(self, filter_id): + def get_agile_board_by_filter_id(self, filter_id: T_id) -> dict | None: """ Gets an agile board by the filter id :param filter_id: int, str @@ -4669,7 +4766,7 @@ def get_agile_board_by_filter_id(self, filter_id): return self.get(url) # /rest/agile/1.0/board - def create_agile_board(self, name, type, filter_id, location=None): + def create_agile_board(self, name: str, type: str, filter_id: T_id, location: dict | None = None) -> dict | None: """ Create an agile board :param name: str: Must be less than 255 characters. @@ -4677,7 +4774,7 @@ def create_agile_board(self, name, type, filter_id, location=None): :param filter_id: int :param location: dict, Optional. Only specify this for Jira Cloud! """ - data = {"name": name, "type": type, "filterId": filter_id} + data: dict = {"name": name, "type": type, "filterId": filter_id} if location: data["location"] = location url = "rest/agile/1.0/board" @@ -4685,12 +4782,12 @@ def create_agile_board(self, name, type, filter_id, location=None): def get_all_agile_boards( self, - board_name=None, - project_key=None, - board_type=None, - start=0, - limit=50, - ): + board_name: str | None = None, + project_key: str | None = None, + board_type: str | None = None, + start: int = 0, + limit: int = 50, + ) -> dict | None: """ Returns all boards. This only includes boards that the user has permission to view. :param board_name: @@ -4701,7 +4798,7 @@ def get_all_agile_boards( :return: """ url = "rest/agile/1.0/board" - params = {} + params: dict = {} if board_name: params["name"] = board_name if project_key: @@ -4715,7 +4812,7 @@ def get_all_agile_boards( return self.get(url, params=params) - def delete_agile_board(self, board_id): + def delete_agile_board(self, board_id: T_id) -> dict | None: """ Delete agile board by id :param board_id: @@ -4724,7 +4821,7 @@ def delete_agile_board(self, board_id): url = "rest/agile/1.0/board/{}".format(str(board_id)) return self.delete(url) - def get_agile_board(self, board_id): + def get_agile_board(self, board_id: T_id) -> dict | None: """ Get agile board info by id :param board_id: @@ -4733,7 +4830,7 @@ def get_agile_board(self, board_id): url = "rest/agile/1.0/board/{}".format(str(board_id)) return self.get(url) - def get_issues_for_backlog(self, board_id): + def get_issues_for_backlog(self, board_id: T_id) -> dict | None: """ Returns all issues from the board's backlog, for the given board ID. This only includes issues that the user has permission to view. @@ -4746,7 +4843,7 @@ def get_issues_for_backlog(self, board_id): url = "rest/agile/1.0/board/{board_id}/backlog".format(board_id=board_id) return self.get(url) - def get_agile_board_configuration(self, board_id): + def get_agile_board_configuration(self, board_id: T_id) -> dict | None: """ Get the board configuration. The response contains the following fields: id - ID of the board. @@ -4773,7 +4870,15 @@ def get_agile_board_configuration(self, board_id): url = "rest/agile/1.0/board/{}/configuration".format(str(board_id)) return self.get(url) - def get_issues_for_board(self, board_id, jql, fields="*all", start=0, limit=None, expand=None): + def get_issues_for_board( + self, + board_id: T_id, + jql: str, + fields: str = "*all", + start: int = 0, + limit: int | None = None, + expand: str | None = None, + ) -> dict | None: """ Returns all issues from a board, for a given board Id. This only includes issues that the user has permission to view. @@ -4789,7 +4894,7 @@ def get_issues_for_board(self, board_id, jql, fields="*all", start=0, limit=None :param expand: OPTIONAL: expand the search result :return: """ - params = {} + params: dict = {} if start is not None: params["startAt"] = int(start) if limit is not None: @@ -4809,11 +4914,11 @@ def get_issues_for_board(self, board_id, jql, fields="*all", start=0, limit=None # /rest/agile/1.0/board/{boardId}/epic def get_epics( self, - board_id, - done=False, - start=0, - limit=50, - ): + board_id: T_id, + done: bool = False, + start: int = 0, + limit: int = 50, + ) -> dict | None: """ Returns all epics from the board, for the given board Id. This only includes epics that the user has permission to view. @@ -4827,7 +4932,7 @@ def get_epics( :return: """ url = "rest/agile/1.0/board/{board_id}/epic".format(board_id=board_id) - params = {} + params: dict = {} if done: params["done"] = done if start: @@ -4837,8 +4942,16 @@ def get_epics( return self.get(url, params=params) def get_issues_for_epic( - self, board_id, epic_id, jql="", validate_query="", fields="*all", expand="", start=0, limit=50 - ): + self, + board_id: T_id, + epic_id: T_id, + jql: str = "", + validate_query: str = "", + fields: str = "*all", + expand: str = "", + start: int = 0, + limit: int = 50, + ) -> dict | None: """ Returns all issues that belong to an epic on the board, for the given epic Id and the board Id. This only includes issues that the user has permission to view. @@ -4865,7 +4978,7 @@ def get_issues_for_epic( :return: """ url = "/rest/agile/1.0/board/{boardId}/epic/{epicId}/issue".format(epicId=epic_id, boardId=board_id) - params = {} + params: dict = {} if jql: params["jql"] = jql if validate_query: @@ -4882,14 +4995,14 @@ def get_issues_for_epic( def get_issues_without_epic( self, - board_id, - jql="", - validate_query="", - fields="*all", - expand="", - start=0, - limit=50, - ): + board_id: T_id, + jql: str = "", + validate_query: str = "", + fields: str = "*all", + expand: str = "", + start: int = 0, + limit: int = 50, + ) -> dict | None: """ Returns all issues that do not belong to any epic on a board, for a given board Id. This only includes issues that the user has permission to view. @@ -4914,7 +5027,7 @@ def get_issues_without_epic( :return: """ url = "/rest/agile/1.0/board/{boardId}/epic/none/issue".format(boardId=board_id) - params = {} + params: dict = {} if jql: params["jql"] = jql if validate_query: @@ -4930,7 +5043,7 @@ def get_issues_without_epic( return self.get(url, params=params) # rest/agile/1.0/board/{boardId}/project - def get_all_projects_associated_with_board(self, board_id, start=0, limit=50): + def get_all_projects_associated_with_board(self, board_id: T_id, start: int = 0, limit: int = 50) -> dict | None: """ Returns all projects that are associated with the board, for the given board ID. A project is associated with a board only @@ -4950,7 +5063,7 @@ def get_all_projects_associated_with_board(self, board_id, start=0, limit=50): :return: """ url = "/rest/agile/1.0/board/{boardId}/project".format(boardId=board_id) - params = {} + params: dict = {} if start: params["startAt"] = start if limit: @@ -4958,7 +5071,7 @@ def get_all_projects_associated_with_board(self, board_id, start=0, limit=50): return self.get(url, params=params) # /rest/agile/1.0/board/{boardId}/properties - def get_agile_board_properties(self, board_id): + def get_agile_board_properties(self, board_id: T_id) -> dict | None: """ Returns the keys of all properties for the board identified by the id. The user who retrieves the property keys is required to have permissions to view the board. @@ -4967,7 +5080,7 @@ def get_agile_board_properties(self, board_id): url = "rest/agile/1.0/board/{boardId}/properties".format(boardId=board_id) return self.get(url) - def set_agile_board_property(self, board_id, property_key): + def set_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: """ Sets the value of the specified board's property. You can use this resource to store a custom data @@ -4982,7 +5095,7 @@ def set_agile_board_property(self, board_id, property_key): ) return self.put(url) - def get_agile_board_property(self, board_id, property_key): + def get_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: """ Returns the value of the property with a given key from the board identified by the provided id. The user who retrieves the property is required to have permissions to view the board. @@ -4995,7 +5108,7 @@ def get_agile_board_property(self, board_id, property_key): ) return self.get(url) - def delete_agile_board_property(self, board_id, property_key): + def delete_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: """ Removes the property from the board identified by the id. Ths user removing the property is required to have permissions to modify the board. @@ -5009,7 +5122,7 @@ def delete_agile_board_property(self, board_id, property_key): return self.delete(url) # /rest/agile/1.0/board/{boardId}/settings/refined-velocity - def get_agile_board_refined_velocity(self, board_id): + def get_agile_board_refined_velocity(self, board_id: T_id) -> dict | None: """ Returns the estimation statistic settings of the board. :param board_id: @@ -5018,7 +5131,7 @@ def get_agile_board_refined_velocity(self, board_id): url = "/rest/agile/1.0/board/{boardId}/settings/refined-velocity".format(boardId=board_id) return self.get(url) - def set_agile_board_refined_velocity(self, board_id, data): + def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> dict | None: """ Sets the estimation statistic settings of the board. :param board_id: @@ -5030,7 +5143,9 @@ def set_agile_board_refined_velocity(self, board_id, data): # /rest/agile/1.0/board/{boardId}/sprint - def get_all_sprints_from_board(self, board_id, state=None, start=0, limit=50): + def get_all_sprints_from_board( + self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50 + ) -> dict | None: """ Returns all sprints from a board, for a given board ID. This only includes sprints that the user has permission to view. @@ -5046,7 +5161,7 @@ def get_all_sprints_from_board(self, board_id, state=None, start=0, limit=50): See the 'Pagination' section at the top of this page for more details. :return: """ - params = {} + params: dict = {} if start: params["startAt"] = start if limit: @@ -5057,7 +5172,7 @@ def get_all_sprints_from_board(self, board_id, state=None, start=0, limit=50): return self.get(url, params=params) @deprecated(version="3.42.0", reason="Use get_all_sprints_from_board instead") - def get_all_sprint(self, board_id, state=None, start=0, limit=50): + def get_all_sprint(self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50) -> dict | None: """ Returns all sprints from a board, for a given board ID. :param board_id: @@ -5069,8 +5184,16 @@ def get_all_sprint(self, board_id, state=None, start=0, limit=50): return self.get_all_sprints_from_board(board_id, state, start, limit) def get_all_issues_for_sprint_in_board( - self, board_id, sprint_id, jql="", validateQuery=True, fields="", expand="", start=0, limit=50 - ): + self, + board_id: T_id, + sprint_id: T_id, + jql: str = "", + validateQuery: bool = True, + fields: str = "", + expand: str = "", + start: int = 0, + limit: int = 50, + ) -> dict | None: """ Get all issues you have access to that belong to the sprint from the board. Issue returned from this resource contains additional fields like: sprint, closedSprints, flagged and epic. @@ -5095,7 +5218,7 @@ def get_all_issues_for_sprint_in_board( If you exceed this limit, your results will be truncated. """ url = "/rest/agile/1.0/board/{boardId}/sprint/{sprintId}/issue".format(boardId=board_id, sprintId=sprint_id) - params = {} + params: dict = {} if jql: params["jql"] = jql if validateQuery: @@ -5111,7 +5234,9 @@ def get_all_issues_for_sprint_in_board( return self.get(url, params=params) # /rest/agile/1.0/board/{boardId}/version - def get_all_versions_from_board(self, board_id, released="true", start=0, limit=50): + def get_all_versions_from_board( + self, board_id: T_id, released: str = "true", start: int = 0, limit: int = 50 + ) -> dict | None: """ Returns all versions from a board, for a given board ID. This only includes versions that the user has permission to view. @@ -5130,7 +5255,7 @@ def get_all_versions_from_board(self, board_id, released="true", start=0, limit= See the 'Pagination' section at the top of this page for more details. :return: """ - params = {} + params: dict = {} if released: params["released"] = released if start: @@ -5140,7 +5265,14 @@ def get_all_versions_from_board(self, board_id, released="true", start=0, limit= url = "rest/agile/1.0/board/{boardId}/version".format(boardId=board_id) return self.get(url, params=params) - def create_sprint(self, name, board_id, start_date=None, end_date=None, goal=None): + def create_sprint( + self, + name: str, + board_id: T_id, + start_date: str | None = None, + end_date: str | None = None, + goal: str | None = None, + ) -> dict | None: """ Create a sprint within a board. ! User requires `Manage Sprints` permission for relevant boards. @@ -5167,7 +5299,7 @@ def create_sprint(self, name, board_id, start_date=None, end_date=None, goal=Non data["goal"] = goal return self.post(url, data=data) - def add_issues_to_sprint(self, sprint_id, issues): + def add_issues_to_sprint(self, sprint_id: T_id, issues: list[str]) -> dict | None: """ Adding Issue(s) to Sprint :param sprint_id: int/str: The ID for the Sprint. @@ -5185,7 +5317,7 @@ def add_issues_to_sprint(self, sprint_id, issues): data = dict(issues=issues) return self.post(url, data=data) - def get_sprint(self, sprint_id): + def get_sprint(self, sprint_id: T_id) -> dict | None: """ Returns the sprint for a given sprint ID. The sprint will only be returned if the user can view the board that the sprint was created on, @@ -5196,7 +5328,7 @@ def get_sprint(self, sprint_id): url = "rest/agile/1.0/sprint/{sprintId}".format(sprintId=sprint_id) return self.get(url) - def rename_sprint(self, sprint_id, name, start_date, end_date): + def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: str) -> dict | None: """ :param sprint_id: @@ -5210,7 +5342,7 @@ def rename_sprint(self, sprint_id, name, start_date, end_date): data={"name": name, "startDate": start_date, "endDate": end_date}, ) - def delete_sprint(self, sprint_id): + def delete_sprint(self, sprint_id: T_id) -> dict | None: """ Deletes a sprint. Once a sprint is deleted, all issues in the sprint will be moved to the backlog. @@ -5220,7 +5352,7 @@ def delete_sprint(self, sprint_id): """ return self.delete("rest/agile/1.0/sprint/{sprintId}".format(sprintId=sprint_id)) - def update_partially_sprint(self, sprint_id, data): + def update_partially_sprint(self, sprint_id: T_id, data: dict) -> dict | None: """ Performs a partial update of a sprint. A partial update means that fields not present in the request JSON will not be updated. @@ -5240,7 +5372,7 @@ def update_partially_sprint(self, sprint_id, data): """ return self.post("rest/agile/1.0/sprint/{}".format(sprint_id), data=data) - def get_sprint_issues(self, sprint_id, start, limit): + def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> dict | None: """ Returns all issues in a sprint, for a given sprint ID. This only includes issues that the user has permission to view. @@ -5257,7 +5389,7 @@ def get_sprint_issues(self, sprint_id, start, limit): If you exceed this limit, your results will be truncated. :return: """ - params = {} + params: dict = {} if start: params["startAt"] = start if limit: @@ -5265,7 +5397,7 @@ def get_sprint_issues(self, sprint_id, start, limit): url = "rest/agile/1.0/sprint/{sprintId}/issue".format(sprintId=sprint_id) return self.get(url, params=params) - def update_rank(self, issues_to_rank, rank_before, customfield_number): + def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number: T_id) -> dict | None: """ Updates the rank of issues (max 50), placing them before a given issue. :param issues_to_rank: List of issues to rank (max 50) @@ -5273,6 +5405,7 @@ def update_rank(self, issues_to_rank, rank_before, customfield_number): :param customfield_number: The number of the custom field Rank :return: """ + return self.put( "rest/agile/1.0/issue/rank", data={ @@ -5282,7 +5415,7 @@ def update_rank(self, issues_to_rank, rank_before, customfield_number): }, ) - def dvcs_get_linked_repos(self): + def dvcs_get_linked_repos(self) -> dict | None: """ Get DVCS linked repos :return: @@ -5290,7 +5423,7 @@ def dvcs_get_linked_repos(self): url = "rest/bitbucket/1.0/repositories" return self.get(url) - def dvcs_update_linked_repo_with_remote(self, repository_id): + def dvcs_update_linked_repo_with_remote(self, repository_id: T_id) -> dict | None: """ Resync delayed sync repo https://confluence.atlassian.com/jirakb/delays-for-commits-to-display-in-development-panel-in-jira-server-779160823.html @@ -5300,7 +5433,7 @@ def dvcs_update_linked_repo_with_remote(self, repository_id): url = "rest/bitbucket/1.0/repositories/{}/sync".format(repository_id) return self.post(url) - def flag_issue(self, issue_keys, flag=True): + def flag_issue(self, issue_keys: list[T_id], flag: bool = True) -> dict | None: """ Flags or un-flags one or multiple issues in Jira with a flag indicator. :param issue_keys: List of issue keys to flag or un-flag. @@ -5314,7 +5447,7 @@ def flag_issue(self, issue_keys, flag=True): data = {"issueKeys": issue_keys, "flag": flag} return self.post(url, data) - def health_check(self): + def health_check(self) -> dict | None: """ Get health status of Jira. https://confluence.atlassian.com/jirakb/how-to-retrieve-health-check-results-using-rest-api-867195158.html @@ -5327,7 +5460,7 @@ def health_check(self): response = self.get("rest/supportHealthCheck/1.0/check/") return response - def duplicated_account_checks_detail(self): + def duplicated_account_checks_detail(self) -> dict | None: """ Health check: Duplicate user accounts detail https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html @@ -5336,7 +5469,7 @@ def duplicated_account_checks_detail(self): response = self.get("rest/api/2/user/duplicated/list") return response - def duplicated_account_checks_flush(self): + def duplicated_account_checks_flush(self) -> dict | None: """ Health check: Duplicate user accounts by flush The responses returned by the count and list methods are stored in the duplicate users cache for 10 minutes. @@ -5345,11 +5478,11 @@ def duplicated_account_checks_flush(self): https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html :return: """ - params = {"flush": "true"} + params: dict = {"flush": "true"} response = self.get("rest/api/2/user/duplicated/list", params=params) return response - def duplicated_account_checks_count(self): + def duplicated_account_checks_count(self) -> dict | None: """ Health check: Duplicate user accounts count https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html diff --git a/atlassian/py.typed b/atlassian/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 677a99313..64c4fbe7b 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -1,6 +1,9 @@ # coding=utf-8 +from __future__ import annotations + import logging from json import dumps +from typing import TYPE_CHECKING, Literal, MutableMapping, overload import requests from requests.adapters import HTTPAdapter @@ -9,7 +12,8 @@ from oauthlib.oauth1.rfc5849 import SIGNATURE_RSA_SHA512 as SIGNATURE_RSA except ImportError: from oauthlib.oauth1 import SIGNATURE_RSA -from requests import HTTPError + +from requests import HTTPError, Response, Session from requests_oauthlib import OAuth1, OAuth2 from six.moves.urllib.parse import urlencode from urllib3.util import Retry @@ -17,6 +21,16 @@ from atlassian.request_utils import get_default_logger +if TYPE_CHECKING: + from http.cookiejar import CookieJar + + from typing_extensions import Self + + +T_resp = Response | dict | None +T_resp_get = Response | dict | str | bytes | None + + log = get_default_logger(__name__) @@ -47,27 +61,27 @@ class AtlassianRestAPI(object): def __init__( self, - url, - username=None, - password=None, - timeout=75, - api_root="rest/api", - api_version="latest", - verify_ssl=True, - session=None, - oauth=None, - oauth2=None, - cookies=None, - advanced_mode=None, - kerberos=None, - cloud=False, - proxies=None, - token=None, - cert=None, - backoff_and_retry=False, - retry_status_codes=[413, 429, 503], - max_backoff_seconds=1800, - max_backoff_retries=1000, + url: str, + username: str | None = None, + password: str | None = None, + timeout: int = 75, + api_root: str = "rest/api", + api_version: str | int = "latest", + verify_ssl: bool = True, + session: requests.Session | None = None, + oauth: dict | None = None, + oauth2: dict | None = None, + cookies: CookieJar | None = None, + advanced_mode: bool | None = None, + kerberos: object = None, + cloud: bool = False, + proxies: MutableMapping[str, str] | None = None, + token: str | None = None, + cert: str | tuple[str, str] | None = None, + backoff_and_retry: bool = False, + retry_status_codes: list[int] = [413, 429, 503], + max_backoff_seconds: int = 1800, + max_backoff_retries: int = 1000, ): """ init function for the AtlassianRestAPI object. @@ -144,24 +158,24 @@ def __init__( elif cookies is not None: self._session.cookies.update(cookies) - def __enter__(self): + def __enter__(self) -> Self: return self - def __exit__(self, *_): + def __exit__(self, *_: object): self.close() - def _create_basic_session(self, username, password): + def _create_basic_session(self, username: str, password: str) -> None: self._session.auth = (username, password) - def _create_token_session(self, token): + def _create_token_session(self, token: str) -> None: self._update_header("Authorization", "Bearer {token}".format(token=token.strip())) - def _create_kerberos_session(self, _): + def _create_kerberos_session(self, _: object) -> None: from requests_kerberos import OPTIONAL, HTTPKerberosAuth self._session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL) - def _create_oauth_session(self, oauth_dict): + def _create_oauth_session(self, oauth_dict: dict) -> None: oauth = OAuth1( oauth_dict["consumer_key"], rsa_key=oauth_dict["key_cert"], @@ -171,7 +185,7 @@ def _create_oauth_session(self, oauth_dict): ) self._session.auth = oauth - def _create_oauth2_session(self, oauth_dict): + def _create_oauth2_session(self, oauth_dict: dict) -> None: """ Use OAuth 2.0 Authentication :param oauth_dict: Dictionary containing access information. Must at @@ -184,7 +198,7 @@ def _create_oauth2_session(self, oauth_dict): oauth = OAuth2(oauth_dict["client_id"], oauth_dict["client"], oauth_dict["token"]) self._session.auth = oauth - def _update_header(self, key, value): + def _update_header(self, key: str, value: str): """ Update header for exist session :param key: @@ -194,7 +208,7 @@ def _update_header(self, key, value): self._session.headers.update({key: value}) @staticmethod - def _response_handler(response): + def _response_handler(response: Response) -> dict | None: try: return response.json() except ValueError: @@ -204,7 +218,14 @@ def _response_handler(response): log.error(e) return None - def log_curl_debug(self, method, url, data=None, headers=None, level=logging.DEBUG): + def log_curl_debug( + self, + method: str, + url: str, + data: dict | str | None = None, + headers: dict | None = None, + level: int = logging.DEBUG, + ) -> None: """ :param method: @@ -223,7 +244,7 @@ def log_curl_debug(self, method, url, data=None, headers=None, level=logging.DEB ) log.log(level=level, msg=message) - def resource_url(self, resource, api_root=None, api_version=None): + def resource_url(self, resource: str, api_root: str | None = None, api_version: str | int | None = None) -> str: if api_root is None: api_root = self.api_root if api_version is None: @@ -231,29 +252,29 @@ def resource_url(self, resource, api_root=None, api_version=None): return "/".join(str(s).strip("/") for s in [api_root, api_version, resource] if s is not None) @staticmethod - def url_joiner(url, path, trailing=None): + def url_joiner(url: str | None, path: str, trailing: bool | None = None) -> str: url_link = "/".join(str(s).strip("/") for s in [url, path] if s is not None) if trailing: url_link += "/" return url_link - def close(self): + def close(self) -> None: return self._session.close() def request( self, - method="GET", - path="/", - data=None, - json=None, - flags=None, - params=None, - headers=None, - files=None, - trailing=None, - absolute=False, - advanced_mode=False, - ): + method: str = "GET", + path: str = "/", + data: dict | str | None = None, + json: dict | str | None = None, + flags: list | None = None, + params: dict | None = None, + headers: dict | None = None, + files: dict | None = None, + trailing: bool | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> Response: """ :param method: @@ -313,18 +334,96 @@ def request( self.raise_for_status(response) return response + # both True + @overload def get( self, - path, - data=None, - flags=None, - params=None, - headers=None, - not_json_response=None, - trailing=None, - absolute=False, - advanced_mode=False, - ): + path: str, + data: dict | str | None = ..., + flags: list | None = ..., + params: dict | None = ..., + headers: dict | None = ..., + *, + not_json_response: Literal[True], + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: Literal[True], + ) -> bytes: ... + + # not_json_response True + @overload + def get( + self, + path: str, + data: dict | str | None = ..., + flags: list | None = ..., + params: dict | None = ..., + headers: dict | None = ..., + *, + not_json_response: Literal[True], + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: bool = ..., + ) -> bytes: ... + + # advanced mode True + @overload + def get( + self, + path: str, + data: dict | str | None = ..., + flags: list | None = ..., + params: dict | None = ..., + headers: dict | None = ..., + not_json_response: Literal[False] | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[True], + ) -> Response: ... + + # both False + @overload + def get( + self, + path: str, + data: dict | str | None = ..., + flags: list | None = ..., + params: dict | None = ..., + headers: dict | None = ..., + not_json_response: Literal[False] | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + # basic overall case + @overload + def get( + self, + path: str, + data: dict | str | None = ..., + flags: list | None = ..., + params: dict | None = ..., + headers: dict | None = ..., + not_json_response: bool | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: bool = ..., + ) -> T_resp_get: ... + + def get( + self, + path: str, + data: dict | str | None = None, + flags: list | None = None, + params: dict | None = None, + headers: dict | None = None, + not_json_response: bool | None = None, + trailing: bool | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> T_resp_get: """ Get request based on the python-requests module. You can override headers, and also, get not json response :param path: @@ -362,18 +461,94 @@ def get( log.error(e) return response.text + # advanced false + @overload def post( self, - path, - data=None, - json=None, - headers=None, - files=None, - params=None, - trailing=None, - absolute=False, - advanced_mode=False, - ): + path: str, + data: dict | str, + *, + json: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + @overload + def post( + self, + path: str, + data: dict | str | None = ..., + json: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + @overload + def post( + self, + path: str, + data: dict | str | None = ..., + json: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + # advanced True + @overload + def post( + self, + path: str, + data: dict | str | None = ..., + json: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[True], + ) -> Response: ... + + # basic overall case + @overload + def post( + self, + path: str, + data: dict | str | None = ..., + json: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: bool = ..., + ) -> Response | dict | None: ... + + def post( + self, + path: str, + data: dict | str | None = None, + json: dict | str | None = None, + headers: dict | None = None, + files: dict | None = None, + params: dict | None = None, + trailing: bool | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> Response | dict | None: """ :param path: :param data: @@ -402,17 +577,74 @@ def post( return response return self._response_handler(response) + # advanced False + @overload def put( self, - path, - data=None, - headers=None, - files=None, - trailing=None, - params=None, - absolute=False, - advanced_mode=False, - ): + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + trailing: bool | None = ..., + params: dict | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[False], + ) -> dict | None: ... + + @overload + def put( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + trailing: bool | None = ..., + params: dict | None = ..., + absolute: bool = ..., + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + # advanced True + @overload + def put( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + trailing: bool | None = ..., + params: dict | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[True], + ) -> Response: ... + + # basic overall case + @overload + def put( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + files: dict | None = ..., + trailing: bool | None = ..., + params: dict | None = ..., + absolute: bool = ..., + advanced_mode: bool = ..., + ) -> Response | dict | None: ... + + def put( + self, + path: str, + data: dict | str | None = None, + headers: dict | None = None, + files: dict | None = None, + trailing: bool | None = None, + params: dict | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> Response | dict | None: """ :param path: Path of request :param data: @@ -446,15 +678,15 @@ def put( def patch( self, - path, - data=None, - headers=None, - files=None, - trailing=None, - params=None, - absolute=False, - advanced_mode=False, - ): + path: str, + data: dict | str | None = None, + headers: dict | None = None, + files: dict | None = None, + trailing: bool | None = None, + params: dict | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> T_resp: """ :param path: Path of request :param data: @@ -481,16 +713,69 @@ def patch( return response return self._response_handler(response) + # advanced False + @overload def delete( self, - path, - data=None, - headers=None, - params=None, - trailing=None, - absolute=False, - advanced_mode=False, - ): + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[False], + ) -> dict | None: ... + + @overload + def delete( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: Literal[False] = ..., + ) -> dict | None: ... + + # advanced True + @overload + def delete( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + *, + advanced_mode: Literal[True], + ) -> Response: ... + + # basic overall case + @overload + def delete( + self, + path: str, + data: dict | str | None = ..., + headers: dict | None = ..., + params: dict | None = ..., + trailing: bool | None = ..., + absolute: bool = ..., + advanced_mode: bool = ..., + ) -> T_resp: ... + + def delete( + self, + path: str, + data: dict | str | None = None, + headers: dict | None = None, + params: dict | None = None, + trailing: bool | None = None, + absolute: bool = False, + advanced_mode: bool = False, + ) -> T_resp: """ Deletes resources at given paths. :param path: @@ -519,7 +804,7 @@ def delete( return response return self._response_handler(response) - def raise_for_status(self, response): + def raise_for_status(self, response: Response) -> None: """ Checks the response for errors and throws an exception if return code >= 400 Since different tools (Atlassian, Jira, ...) have different formats of returned json, @@ -554,6 +839,6 @@ def raise_for_status(self, response): response.raise_for_status() @property - def session(self): + def session(self) -> Session: """Providing access to the restricted field""" return self._session diff --git a/atlassian/typehints.py b/atlassian/typehints.py new file mode 100644 index 000000000..94b55db54 --- /dev/null +++ b/atlassian/typehints.py @@ -0,0 +1,9 @@ +from typing_extensions import TypeAlias + + +T_id: TypeAlias = str | int +_Data: TypeAlias = ( + dict | str +) + + diff --git a/examples/jira/jira_review_groups.py b/examples/jira/jira_review_groups.py index 75ddfaf65..d307c5131 100644 --- a/examples/jira/jira_review_groups.py +++ b/examples/jira/jira_review_groups.py @@ -4,7 +4,7 @@ jira = Jira(url="http://localhost:8080", username="admin", password="admin") -def get_all_users(group, include_inactive=True): +def get_all_users(group: str, include_inactive: bool = True): """ Get all users for group. If their more, than 50 users in group: go through the pages and append other users to the list @@ -28,7 +28,7 @@ def get_all_users(group, include_inactive=True): return processed_data -def sort_users_in_group(group): +def sort_users_in_group(group: dict): """ Take group, sort users by the name and return group with sorted users """ @@ -47,7 +47,7 @@ def get_groups_data(): return groups_and_users -def get_inactive_users(groups): +def get_inactive_users(groups: list[dict]): """ Take group list and return groups only with inactive users :param groups: @@ -66,7 +66,7 @@ def get_inactive_users(groups): return inactive_users_list -def exclude_inactive_users(groups): +def exclude_inactive_users(groups: list[dict]): """ Excluding inactive users from groups. :param groups: @@ -79,7 +79,7 @@ def exclude_inactive_users(groups): return True -def filter_groups_by_members(groups, quantity=1): +def filter_groups_by_members(groups: list[dict], quantity: int = 1): """ Take groups list and return empty groups :param groups: @@ -89,7 +89,7 @@ def filter_groups_by_members(groups, quantity=1): return [x for x in groups if int(x["total"]) < quantity] -def find_group(groups, group_name): +def find_group(groups: list[dict], group_name: str): """ Take groups list and find group by the group name :param groups: diff --git a/tests/test_confluence_advanced_mode.py b/tests/test_confluence_advanced_mode.py index 9a07f85f8..521ca70ce 100644 --- a/tests/test_confluence_advanced_mode.py +++ b/tests/test_confluence_advanced_mode.py @@ -1,7 +1,10 @@ # coding=utf-8 +from __future__ import annotations + import json import os import unittest + from requests import Response from atlassian import Confluence @@ -13,6 +16,9 @@ "credentials.secret missing, skipping test", ) class TestConfluenceAdvancedModeCalls(unittest.TestCase): + space: str + created_pages: set + confluence: Confluence secret_file = "../credentials.secret" """ @@ -42,7 +48,7 @@ def setUpClass(cls): cls.space = "SAN" cls.created_pages = set() - def test_confluence_advanced_mode_post(self): + def test_confluence_advanced_mode_post(self) -> None: """Tests the advanced_mode option of AtlassianRestAPI post method by manually creating a page""" page_title = "Test_confluence_advanced_mode_post" data = { From 4dc88856c7d03bda41cfcd6df18f8001535f34cf Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 11 Jun 2024 09:11:29 -0500 Subject: [PATCH 02/15] avoiding errors about override signature --- .../bitbucket/cloud/repositories/repositoryVariables.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atlassian/bitbucket/cloud/repositories/repositoryVariables.py b/atlassian/bitbucket/cloud/repositories/repositoryVariables.py index 22515b1fc..fbb21ebeb 100644 --- a/atlassian/bitbucket/cloud/repositories/repositoryVariables.py +++ b/atlassian/bitbucket/cloud/repositories/repositoryVariables.py @@ -1,5 +1,5 @@ # coding=utf-8 - +from __future__ import annotations from ..base import BitbucketCloudBase @@ -7,7 +7,7 @@ class RepositoryVariables(BitbucketCloudBase): def __init__(self, url, *args, **kwargs): super(RepositoryVariables, self).__init__(url, *args, **kwargs) - def __get_object(self, data): + def __get_object(self, data) -> RepositoryVariable: return RepositoryVariable( self.url_joiner(self.url, data["uuid"]), data, @@ -61,7 +61,7 @@ def each(self, q=None, sort=None): return - def get(self, uuid): + def get(self, uuid: str): # type: ignore[override] """ Returns the pipeline with the uuid in this repository. From e0aaf3826a1bdcfddae7ee1d8b4709290ba48024 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 11 Jun 2024 09:26:57 -0500 Subject: [PATCH 03/15] adding various types packages to requirements-dev.txt --- requirements-dev.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9a22795a1..4bdd7b908 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,9 +11,14 @@ codecov # used for example confluence attach file python-magic pylint -mypy +mypy>=0.812 bandit doc8 +types-Deprecated +types-requests +types-six +types-beautifulsoup4 + # On October 4, 2022 importlib-metadata released importlib-metadata 5.0.0 and in version 5.0.0 # They have Deprecated EntryPoints and that's why you are facing this error. From dba6e89a1bed2fc3d4ab496f46ffbe7167ab4cfd Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 11 Jun 2024 09:37:45 -0500 Subject: [PATCH 04/15] adding type hints --- atlassian/jira.py | 113 +++++++++++++++++++++-------------------- atlassian/typehints.py | 2 - 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 8c398fb35..9c112ff3f 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -4,7 +4,7 @@ import logging import os import re -from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast +from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast, Iterable from warnings import warn from deprecated import deprecated @@ -145,7 +145,7 @@ def get_permissions( return self.get(url, params=params) - def get_all_permissions(self): + def get_all_permissions(self) -> dict | None: """ Returns all permissions that are present in the Jira instance - Global, Project and the global ones added by plugins @@ -159,7 +159,9 @@ def get_all_permissions(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/application-properties """ - def get_property(self, key: T_id | None = None, permission_level: str | None = None, key_filter: str | None = None): + def get_property( + self, key: T_id | None = None, permission_level: str | None = None, key_filter: str | None = None + ) -> dict | None: """ Returns an application property :param key: str @@ -180,7 +182,7 @@ def get_property(self, key: T_id | None = None, permission_level: str | None = N return self.get(url, params=params) - def set_property(self, property_id: T_id, value: str): + def set_property(self, property_id: T_id, value: str) -> dict | None: """ Modify an application property via PUT. The "value" field present in the PUT will override the existing value. :param property_id: @@ -207,7 +209,7 @@ def get_advanced_settings(self) -> dict | None: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/applicationrole """ - def get_all_application_roles(self): + def get_all_application_roles(self) -> dict | None: """ Returns all ApplicationRoles in the system :return: @@ -215,7 +217,7 @@ def get_all_application_roles(self): url = self.resource_url("applicationrole") return self.get(url) or {} - def get_application_role(self, role_key: str): + def get_application_role(self, role_key: str) -> dict | None: """ Returns the ApplicationRole with passed key if it exists :param role_key: str @@ -242,7 +244,7 @@ def get_attachments_ids_from_issue(self, issue: T_id) -> list[dict[str, str]]: list_attachments_id.append({"filename": attachment["filename"], "attachment_id": attachment["id"]}) return list_attachments_id - def get_attachment(self, attachment_id: T_id): + def get_attachment(self, attachment_id: T_id) -> dict | None: """ Returns the meta-data for an attachment, including the URI of the actual attached file :param attachment_id: int @@ -290,7 +292,7 @@ def download_attachments_from_issue(self, issue: T_id, path: str | None = None, except Exception as e: raise e - def get_attachment_content(self, attachment_id: T_id): + def get_attachment_content(self, attachment_id: T_id) -> dict | None: """ Returns the content for an attachment :param attachment_id: int @@ -300,7 +302,7 @@ def get_attachment_content(self, attachment_id: T_id): url = "{base_url}/content/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def remove_attachment(self, attachment_id: T_id): + def remove_attachment(self, attachment_id: T_id) -> dict | None: """ Remove an attachment from an issue :param attachment_id: int @@ -310,7 +312,7 @@ def remove_attachment(self, attachment_id: T_id): url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.delete(url) - def get_attachment_meta(self): + def get_attachment_meta(self) -> dict | None: """ Returns the meta information for an attachments, specifically if they are enabled and the maximum upload size allowed @@ -319,7 +321,7 @@ def get_attachment_meta(self): url = self.resource_url("attachment/meta") return self.get(url) - def get_attachment_expand_human(self, attachment_id: T_id): + def get_attachment_expand_human(self, attachment_id: T_id) -> dict | None: """ Returns the information for an expandable attachment in human-readable format :param attachment_id: int @@ -329,7 +331,7 @@ def get_attachment_expand_human(self, attachment_id: T_id): url = "{base_url}/{attachment_id}/expand/human".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def get_attachment_expand_raw(self, attachment_id: T_id): + def get_attachment_expand_raw(self, attachment_id: T_id) -> dict | None: """ Returns the information for an expandable attachment in raw format :param attachment_id: int @@ -351,7 +353,7 @@ def get_audit_records( filter: str | None = None, from_date: str | None = None, to_date: str | None = None, - ): + ) -> dict | None: """ Returns auditing records filtered using provided parameters :param offset: the number of record from which search starts @@ -381,7 +383,7 @@ def get_audit_records( url = self.resource_url("auditing/record") return self.get(url, params=params) or {} - def post_audit_record(self, audit_record: dict | str): + def post_audit_record(self, audit_record: dict | str) -> dict | None: """ Store a record in Audit Log :param audit_record: json with compat https://docs.atlassian.com/jira/REST/schema/audit-record# @@ -395,7 +397,7 @@ def post_audit_record(self, audit_record: dict | str): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/avatar """ - def get_all_system_avatars(self, avatar_type: str = "user"): + def get_all_system_avatars(self, avatar_type: str = "user") -> dict | None: """ Returns all system avatars of the given type. :param avatar_type: @@ -411,11 +413,11 @@ def get_all_system_avatars(self, avatar_type: str = "user"): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/cluster """ - def get_cluster_all_nodes(self): + def get_cluster_all_nodes(self) -> dict | None: url = self.resource_url("cluster/nodes") return self.get(url) - def delete_cluster_node(self, node_id: T_id): + def delete_cluster_node(self, node_id: T_id) -> dict | None: """ Delete the node from the cluster if state of node is OFFLINE :param node_id: str @@ -425,7 +427,7 @@ def delete_cluster_node(self, node_id: T_id): url = "{base_url}/{node_id}".format(base_url=base_url, node_id=node_id) return self.delete(url) - def set_node_to_offline(self, node_id: T_id): + def set_node_to_offline(self, node_id: T_id) -> dict | None: """ Change the node's state to offline if the node is reporting as active, but is not alive :param node_id: str @@ -435,14 +437,15 @@ def set_node_to_offline(self, node_id: T_id): url = "{base_url}/{node_id}/offline".format(base_url=base_url, node_id=node_id) return self.put(url) - def get_cluster_alive_nodes(self): + def get_cluster_alive_nodes(self) -> list: """ Get cluster nodes where alive = True :return: list of node dicts """ - return [_ for _ in self.get_cluster_all_nodes() if _["alive"]] + nodes = self.get_cluster_all_nodes() + return [_ for _ in nodes.values() if _["alive"]] if nodes else [] - def request_current_index_from_node(self, node_id: T_id): + def request_current_index_from_node(self, node_id: T_id) -> dict | None: """ Request current index from node (the request is processed asynchronously) :return: @@ -456,7 +459,7 @@ def request_current_index_from_node(self, node_id: T_id): Reference: https://confluence.atlassian.com/support/create-a-support-zip-using-the-rest-api-in-data-center-applications-952054641.html """ - def generate_support_zip_on_nodes(self, node_ids: list): + def generate_support_zip_on_nodes(self, node_ids: list) -> dict | None: """ Generate a support zip on targeted nodes of a cluster :param node_ids: list @@ -466,7 +469,7 @@ def generate_support_zip_on_nodes(self, node_ids: list): url = "/rest/troubleshooting/latest/support-zip/cluster" return self.post(url, data=data) - def check_support_zip_status(self, cluster_task_id: T_id): + def check_support_zip_status(self, cluster_task_id: T_id) -> dict | None: """ Check status of support zip creation task :param cluster_task_id: str @@ -475,7 +478,7 @@ def check_support_zip_status(self, cluster_task_id: T_id): url = "/rest/troubleshooting/latest/support-zip/status/cluster/{}".format(cluster_task_id) return self.get(url) - def download_support_zip(self, file_name: str): + def download_support_zip(self, file_name: str) -> bytes: """ Download created support zip file :param file_name: str @@ -489,12 +492,12 @@ def download_support_zip(self, file_name: str): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/cluster/zdu """ - def get_cluster_zdu_state(self): + def get_cluster_zdu_state(self) -> dict | None: url = self.resource_url("cluster/zdu/state") return self.get(url) # Issue Comments - def issue_get_comments(self, issue_id: T_id): + def issue_get_comments(self, issue_id: T_id) -> dict | None: """ Get Comments on an Issue. :param issue_id: Issue ID @@ -505,7 +508,7 @@ def issue_get_comments(self, issue_id: T_id): url = "{base_url}/{issue_id}/comment".format(base_url=base_url, issue_id=issue_id) return self.get(url) - def issues_get_comments_by_id(self, *args): + def issues_get_comments_by_id(self, *args: int) -> dict | None: """ Get Comments on Multiple Issues :param *args: int Issue ID's @@ -840,7 +843,7 @@ def get_filter(self, filter_id: T_id): url = "{base_url}/{id}".format(base_url=base_url, id=filter_id) return self.get(url) - def update_filter(self, filter_id: T_id, jql: str, **kwargs): + def update_filter(self, filter_id: T_id, jql: str, **kwargs: Any): """ :param filter_id: int :param jql: str @@ -1407,7 +1410,7 @@ def issue_field_value_append(self, issue_id_or_key: str, field: str, value: str, params=params, ) - def get_issue_labels(self, issue_key: str): + def get_issue_labels(self, issue_key: str) -> dict | None: """ Get issue labels. :param issue_key: @@ -1417,7 +1420,11 @@ def get_issue_labels(self, issue_key: str): url = "{base_url}/{issue_key}?fields=labels".format(base_url=base_url, issue_key=issue_key) if self.advanced_mode: return self.get(url) - return (self.get(url) or {}).get("fields").get("labels") + d = self.get(url) or {} + f = d.get("fields") + if f: + return f.get("labels") + return None def update_issue(self, issue_key: T_id, update: dict | str) -> dict | None: """ @@ -1741,7 +1748,7 @@ def scrap_regex_from_issue(self, issue: str, regex: str): except HTTPError as e: if e.response.status_code == 404: # Raise ApiError as the documented reason is ambiguous - log.error("couldn't find issue: ", issue["key"]) + log.error("couldn't find issue: ", issue) raise ApiNotFoundError( "There is no content with the given issue ud," "or the calling user does not have permission to view the issue", @@ -1882,7 +1889,7 @@ def update_issue_remote_link_by_id( ) return self.put(url, data=data) - def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id): + def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id) -> dict | None: """ Deletes Remote Link on Issue :param issue_key: str @@ -1894,27 +1901,23 @@ def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id): ) return self.delete(url) - def get_issue_transitions(self, issue_key: str): + def get_issue_transitions(self, issue_key: str) -> list[dict]: if self.advanced_mode: - return [ - { - "name": transition["name"], - "id": int(transition["id"]), - "to": transition["to"]["name"], - } - for transition in (self.get_issue_transitions_full(issue_key).json() or {}).get("transitions") - ] + resp = cast(Response, self.get_issue_transitions_full(issue_key)) + d: dict[str, list] = resp.json() or {} else: - return [ - { - "name": transition["name"], - "id": int(transition["id"]), - "to": transition["to"]["name"], - } - for transition in (self.get_issue_transitions_full(issue_key) or {}).get("transitions") - ] + d = self.get_issue_transitions_full(issue_key) or {} + + return [ + { + "name": transition["name"], + "id": int(transition["id"]), + "to": transition["to"]["name"], + } + for transition in cast(list[dict], d.get("transitions")) + ] - def issue_transition(self, issue_key: str, status: str): + def issue_transition(self, issue_key: str, status: str) -> dict | None: return self.set_issue_status(issue_key, status) def set_issue_status( @@ -1970,12 +1973,12 @@ def set_issue_status_by_transition_id(self, issue_key: str, transition_id: T_id) def get_issue_status(self, issue_key: str): base_url = self.resource_url("issue") url = "{base_url}/{issue_key}?fields=status".format(base_url=base_url, issue_key=issue_key) - return (((self.get(url) or {}).get("fields") or {}).get("status") or {}).get("name") or {} + return (self.get(url) or {}).__getitem__("fields").__getitem__("status").__getitem__("name") - def get_issue_status_id(self, issue_key: str): + def get_issue_status_id(self, issue_key: str) -> str: base_url = self.resource_url("issue") url = "{base_url}/{issue_key}?fields=status".format(base_url=base_url, issue_key=issue_key) - return (self.get(url) or {}).get("fields").get("status").get("id") + return (self.get(url) or {}).__getitem__("fields").__getitem__("status").__getitem__("id") def get_issue_transitions_full( self, issue_key: str, transition_id: T_id | None = None, expand: str | None = None @@ -3160,7 +3163,7 @@ def get_assignable_users_for_issue( def get_status_id_from_name(self, status_name: str): base_url = self.resource_url("status") url = "{base_url}/{name}".format(base_url=base_url, name=status_name) - return int((self.get(url) or {}).get("id")) + return int((self.get(url) or {}).__getitem__("id")) def get_status_for_project(self, project_key: str): base_url = self.resource_url("project") @@ -3878,7 +3881,7 @@ def get_issue_security_scheme(self, scheme_id: T_id, only_levels: bool = False): url = "{base_url}/{scheme_id}".format(base_url=base_url, scheme_id=scheme_id) if only_levels is True: - return self.get(url).get("levels") + return (self.get(url) or {}).__getitem__("levels") else: return self.get(url) diff --git a/atlassian/typehints.py b/atlassian/typehints.py index 94b55db54..cf717c5be 100644 --- a/atlassian/typehints.py +++ b/atlassian/typehints.py @@ -5,5 +5,3 @@ _Data: TypeAlias = ( dict | str ) - - From 3310fa6be9d41308e4a666e4f3160b42e37b7b08 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 11 Jun 2024 11:21:52 -0500 Subject: [PATCH 05/15] using alias for json response type --- atlassian/jira.py | 418 ++++++++++++++++++++------------------- atlassian/rest_client.py | 25 +-- atlassian/typehints.py | 2 +- 3 files changed, 225 insertions(+), 220 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 9c112ff3f..26d197b58 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -4,7 +4,7 @@ import logging import os import re -from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast, Iterable +from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast from warnings import warn from deprecated import deprecated @@ -14,7 +14,7 @@ from .rest_client import AtlassianRestAPI if TYPE_CHECKING: - from .typehints import T_id + from .typehints import T_id, T_resp_json log = logging.getLogger(__name__) @@ -97,7 +97,7 @@ def get_permissions( project_key: T_id | None = None, issue_id: T_id | None = None, issue_key: T_id | None = None, - ) -> dict | None: + ) -> T_resp_json: """ Returns a list of permissions indicating which permissions the user has. Details of the user's permissions can be obtained in a global, project, issue or comment context. @@ -145,7 +145,7 @@ def get_permissions( return self.get(url, params=params) - def get_all_permissions(self) -> dict | None: + def get_all_permissions(self) -> T_resp_json: """ Returns all permissions that are present in the Jira instance - Global, Project and the global ones added by plugins @@ -161,7 +161,7 @@ def get_all_permissions(self) -> dict | None: def get_property( self, key: T_id | None = None, permission_level: str | None = None, key_filter: str | None = None - ) -> dict | None: + ) -> T_resp_json: """ Returns an application property :param key: str @@ -182,7 +182,7 @@ def get_property( return self.get(url, params=params) - def set_property(self, property_id: T_id, value: str) -> dict | None: + def set_property(self, property_id: T_id, value: str) -> T_resp_json: """ Modify an application property via PUT. The "value" field present in the PUT will override the existing value. :param property_id: @@ -195,7 +195,7 @@ def set_property(self, property_id: T_id, value: str) -> dict | None: return self.put(url, data=data) - def get_advanced_settings(self) -> dict | None: + def get_advanced_settings(self) -> T_resp_json: """ Returns the properties that are displayed on the "General Configuration > Advanced Settings" page. :return: @@ -209,7 +209,7 @@ def get_advanced_settings(self) -> dict | None: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/applicationrole """ - def get_all_application_roles(self) -> dict | None: + def get_all_application_roles(self) -> T_resp_json: """ Returns all ApplicationRoles in the system :return: @@ -217,7 +217,7 @@ def get_all_application_roles(self) -> dict | None: url = self.resource_url("applicationrole") return self.get(url) or {} - def get_application_role(self, role_key: str) -> dict | None: + def get_application_role(self, role_key: str) -> T_resp_json: """ Returns the ApplicationRole with passed key if it exists :param role_key: str @@ -244,7 +244,7 @@ def get_attachments_ids_from_issue(self, issue: T_id) -> list[dict[str, str]]: list_attachments_id.append({"filename": attachment["filename"], "attachment_id": attachment["id"]}) return list_attachments_id - def get_attachment(self, attachment_id: T_id) -> dict | None: + def get_attachment(self, attachment_id: T_id) -> T_resp_json: """ Returns the meta-data for an attachment, including the URI of the actual attached file :param attachment_id: int @@ -292,7 +292,7 @@ def download_attachments_from_issue(self, issue: T_id, path: str | None = None, except Exception as e: raise e - def get_attachment_content(self, attachment_id: T_id) -> dict | None: + def get_attachment_content(self, attachment_id: T_id) -> T_resp_json: """ Returns the content for an attachment :param attachment_id: int @@ -302,7 +302,7 @@ def get_attachment_content(self, attachment_id: T_id) -> dict | None: url = "{base_url}/content/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def remove_attachment(self, attachment_id: T_id) -> dict | None: + def remove_attachment(self, attachment_id: T_id) -> T_resp_json: """ Remove an attachment from an issue :param attachment_id: int @@ -312,7 +312,7 @@ def remove_attachment(self, attachment_id: T_id) -> dict | None: url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.delete(url) - def get_attachment_meta(self) -> dict | None: + def get_attachment_meta(self) -> T_resp_json: """ Returns the meta information for an attachments, specifically if they are enabled and the maximum upload size allowed @@ -321,7 +321,7 @@ def get_attachment_meta(self) -> dict | None: url = self.resource_url("attachment/meta") return self.get(url) - def get_attachment_expand_human(self, attachment_id: T_id) -> dict | None: + def get_attachment_expand_human(self, attachment_id: T_id) -> T_resp_json: """ Returns the information for an expandable attachment in human-readable format :param attachment_id: int @@ -331,7 +331,7 @@ def get_attachment_expand_human(self, attachment_id: T_id) -> dict | None: url = "{base_url}/{attachment_id}/expand/human".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def get_attachment_expand_raw(self, attachment_id: T_id) -> dict | None: + def get_attachment_expand_raw(self, attachment_id: T_id) -> T_resp_json: """ Returns the information for an expandable attachment in raw format :param attachment_id: int @@ -353,7 +353,7 @@ def get_audit_records( filter: str | None = None, from_date: str | None = None, to_date: str | None = None, - ) -> dict | None: + ) -> T_resp_json: """ Returns auditing records filtered using provided parameters :param offset: the number of record from which search starts @@ -383,7 +383,7 @@ def get_audit_records( url = self.resource_url("auditing/record") return self.get(url, params=params) or {} - def post_audit_record(self, audit_record: dict | str) -> dict | None: + def post_audit_record(self, audit_record: dict | str) -> T_resp_json: """ Store a record in Audit Log :param audit_record: json with compat https://docs.atlassian.com/jira/REST/schema/audit-record# @@ -397,7 +397,7 @@ def post_audit_record(self, audit_record: dict | str) -> dict | None: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/avatar """ - def get_all_system_avatars(self, avatar_type: str = "user") -> dict | None: + def get_all_system_avatars(self, avatar_type: str = "user") -> T_resp_json: """ Returns all system avatars of the given type. :param avatar_type: @@ -413,11 +413,11 @@ def get_all_system_avatars(self, avatar_type: str = "user") -> dict | None: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/cluster """ - def get_cluster_all_nodes(self) -> dict | None: + def get_cluster_all_nodes(self) -> T_resp_json: url = self.resource_url("cluster/nodes") return self.get(url) - def delete_cluster_node(self, node_id: T_id) -> dict | None: + def delete_cluster_node(self, node_id: T_id) -> T_resp_json: """ Delete the node from the cluster if state of node is OFFLINE :param node_id: str @@ -427,7 +427,7 @@ def delete_cluster_node(self, node_id: T_id) -> dict | None: url = "{base_url}/{node_id}".format(base_url=base_url, node_id=node_id) return self.delete(url) - def set_node_to_offline(self, node_id: T_id) -> dict | None: + def set_node_to_offline(self, node_id: T_id) -> T_resp_json: """ Change the node's state to offline if the node is reporting as active, but is not alive :param node_id: str @@ -445,7 +445,7 @@ def get_cluster_alive_nodes(self) -> list: nodes = self.get_cluster_all_nodes() return [_ for _ in nodes.values() if _["alive"]] if nodes else [] - def request_current_index_from_node(self, node_id: T_id) -> dict | None: + def request_current_index_from_node(self, node_id: T_id) -> T_resp_json: """ Request current index from node (the request is processed asynchronously) :return: @@ -459,7 +459,7 @@ def request_current_index_from_node(self, node_id: T_id) -> dict | None: Reference: https://confluence.atlassian.com/support/create-a-support-zip-using-the-rest-api-in-data-center-applications-952054641.html """ - def generate_support_zip_on_nodes(self, node_ids: list) -> dict | None: + def generate_support_zip_on_nodes(self, node_ids: list) -> T_resp_json: """ Generate a support zip on targeted nodes of a cluster :param node_ids: list @@ -469,7 +469,7 @@ def generate_support_zip_on_nodes(self, node_ids: list) -> dict | None: url = "/rest/troubleshooting/latest/support-zip/cluster" return self.post(url, data=data) - def check_support_zip_status(self, cluster_task_id: T_id) -> dict | None: + def check_support_zip_status(self, cluster_task_id: T_id) -> T_resp_json: """ Check status of support zip creation task :param cluster_task_id: str @@ -492,12 +492,12 @@ def download_support_zip(self, file_name: str) -> bytes: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/cluster/zdu """ - def get_cluster_zdu_state(self) -> dict | None: + def get_cluster_zdu_state(self) -> T_resp_json: url = self.resource_url("cluster/zdu/state") return self.get(url) # Issue Comments - def issue_get_comments(self, issue_id: T_id) -> dict | None: + def issue_get_comments(self, issue_id: T_id) -> T_resp_json: """ Get Comments on an Issue. :param issue_id: Issue ID @@ -508,7 +508,7 @@ def issue_get_comments(self, issue_id: T_id) -> dict | None: url = "{base_url}/{issue_id}/comment".format(base_url=base_url, issue_id=issue_id) return self.get(url) - def issues_get_comments_by_id(self, *args: int) -> dict | None: + def issues_get_comments_by_id(self, *args: int) -> T_resp_json: """ Get Comments on Multiple Issues :param *args: int Issue ID's @@ -522,7 +522,7 @@ def issues_get_comments_by_id(self, *args: int) -> dict | None: url = "{base_url}/list".format(base_url=base_url) return self.post(url, data=data) - def issue_get_comment(self, issue_id: T_id, comment_id: T_id): + def issue_get_comment(self, issue_id: T_id, comment_id: T_id) -> T_resp_json: """ Get a single comment :param issue_id: int or str @@ -541,7 +541,7 @@ def issue_get_comment(self, issue_id: T_id, comment_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/comment/{commentId}/properties """ - def get_comment_properties_keys(self, comment_id: T_id): + def get_comment_properties_keys(self, comment_id: T_id) -> T_resp_json: """ Returns the keys of all properties for the comment identified by the key or by the id. :param comment_id: @@ -551,7 +551,7 @@ def get_comment_properties_keys(self, comment_id: T_id): url = "{base_url}/{commentId}/properties".format(base_url=base_url, commentId=comment_id) return self.get(url) - def get_comment_property(self, comment_id: T_id, property_key: str): + def get_comment_property(self, comment_id: T_id, property_key: str) -> T_resp_json: """ Returns the value a property for a comment :param comment_id: int @@ -564,7 +564,7 @@ def get_comment_property(self, comment_id: T_id, property_key: str): ) return self.get(url) - def set_comment_property(self, comment_id: T_id, property_key: str, value_property: object): + def set_comment_property(self, comment_id: T_id, property_key: str, value_property: object) -> T_resp_json: """ Returns the keys of all properties for the comment identified by the key or by the id. :param comment_id: int @@ -579,7 +579,7 @@ def set_comment_property(self, comment_id: T_id, property_key: str, value_proper data = {"value": value_property} return self.put(url, data=data) - def delete_comment_property(self, comment_id: T_id, property_key: str): + def delete_comment_property(self, comment_id: T_id, property_key: str) -> T_resp_json: """ Deletes a property for a comment :param comment_id: int @@ -597,11 +597,11 @@ def delete_comment_property(self, comment_id: T_id, property_key: str): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/component """ - def component(self, component_id: T_id): + def component(self, component_id: T_id) -> T_resp_json: base_url = self.resource_url("component") return self.get("{base_url}/{component_id}".format(base_url=base_url, component_id=component_id)) - def get_component_related_issues(self, component_id: T_id): + def get_component_related_issues(self, component_id: T_id) -> T_resp_json: """ Returns counts of issues related to this component. :param component_id: @@ -611,23 +611,23 @@ def get_component_related_issues(self, component_id: T_id): url = "{base_url}/{component_id}/relatedIssueCounts".format(base_url=base_url, component_id=component_id) return self.get(url) - def create_component(self, component: dict): + def create_component(self, component: dict) -> T_resp_json: log.warning('Creating component "%s"', component["name"]) base_url = self.resource_url("component") url = "{base_url}/".format(base_url=base_url) return self.post(url, data=component) - def update_component(self, component: dict, component_id: T_id): + def update_component(self, component: dict, component_id: T_id) -> T_resp_json: base_url = self.resource_url("component") url = "{base_url}/{component_id}".format(base_url=base_url, component_id=component_id) return self.put(url, data=component) - def delete_component(self, component_id: T_id): + def delete_component(self, component_id: T_id) -> T_resp_json: log.warning('Deleting component "%s"', component_id) base_url = self.resource_url("component") return self.delete("{base_url}/{component_id}".format(base_url=base_url, component_id=component_id)) - def update_component_lead(self, component_id: T_id, lead: str): + def update_component_lead(self, component_id: T_id, lead: str) -> T_resp_json: data = {"id": component_id, "leadUserName": lead} base_url = self.resource_url("component") return self.put( @@ -640,7 +640,7 @@ def update_component_lead(self, component_id: T_id, lead: str): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/configuration """ - def get_configurations_of_jira(self): + def get_configurations_of_jira(self) -> T_resp_json: """ Returns the information if the optional features in JIRA are enabled or disabled. If the time tracking is enabled, it also returns the detailed information about time tracking configuration. @@ -656,7 +656,7 @@ def get_configurations_of_jira(self): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/field """ - def get_custom_field_option(self, option_id: T_id): + def get_custom_field_option(self, option_id: T_id) -> T_resp_json: """ Returns a full representation of the Custom Field Option that has the given id. :param option_id: @@ -666,7 +666,7 @@ def get_custom_field_option(self, option_id: T_id): url = "{base_url}/{id}".format(base_url=base_url, id=option_id) return self.get(url) - def get_custom_fields(self, search: str | None = None, start: int = 1, limit: int = 50): + def get_custom_fields(self, search: str | None = None, start: int = 1, limit: int = 50) -> T_resp_json: """ Get custom fields. Evaluated on 7.12 :param search: str @@ -684,7 +684,7 @@ def get_custom_fields(self, search: str | None = None, start: int = 1, limit: in params["maxResults"] = limit return self.get(url, params=params) - def get_all_fields(self): + def get_all_fields(self) -> T_resp_json: """ Returns a list of all fields, both System and Custom :return: application/jsonContains a full representation of all visible fields in JSON. @@ -692,7 +692,7 @@ def get_all_fields(self): url = self.resource_url("field") return self.get(url) - def create_custom_field(self, name: str, type: str, search_key: str | None = None, description: str | None = None): + def create_custom_field(self, name: str, type: str, search_key: str | None = None, description: str | None = None) -> T_resp_json: """ Creates a custom field with the given name and type :param name: str - name of the custom field @@ -708,7 +708,7 @@ def create_custom_field(self, name: str, type: str, search_key: str | None = Non data["description"] = description return self.post(url, data=data) - def get_custom_field_option_context(self, field_id: T_id, context_id: T_id): + def get_custom_field_option_context(self, field_id: T_id, context_id: T_id) -> T_resp_json: """ Gets the current values of a custom field :param field_id: @@ -723,7 +723,7 @@ def get_custom_field_option_context(self, field_id: T_id, context_id: T_id): ) return self.get(url) - def add_custom_field_option(self, field_id: T_id, context_id: T_id, options: list): + def add_custom_field_option(self, field_id: T_id, context_id: T_id, options: list) -> T_resp_json: """ Adds the values given to the custom field Administrator permission required @@ -1042,7 +1042,7 @@ def add_user_to_group( def remove_user_from_group( self, username: str | None = None, group_name: str | None = None, account_id: str | None = None - ) -> dict | None: + ) -> T_resp_json: """ Remove given user from a group @@ -1072,7 +1072,7 @@ def get_users_with_browse_permission_to_a_project( project_key: str | None = None, start: int = 0, limit: int = 100, - ) -> dict | None: + ) -> T_resp_json: """ Returns a list of active users that match the search string. This resource cannot be accessed anonymously and requires the Browse Users global permission. Given an issue key this resource will provide a list of users @@ -1180,7 +1180,7 @@ def bulk_issue(self, issue_list: list, fields: list | str = "*all"): matched_issue_keys.append(key) jql = "key in ({})".format(", ".join(set(matched_issue_keys))) query_result = self.jql(jql, fields=fields) - if "errorMessages" in query_result.keys(): + if query_result and "errorMessages" in query_result.keys(): for message in query_result["errorMessages"]: for key in issue_list: if key in message: @@ -1189,7 +1189,7 @@ def bulk_issue(self, issue_list: list, fields: list | str = "*all"): query_result, missing_issues = self.bulk_issue(issue_list, fields) return query_result, missing_issues - def issue_createmeta(self, project: str, expand: str = "projects.issuetypes.fields") -> dict | None: + def issue_createmeta(self, project: str, expand: str = "projects.issuetypes.fields") -> T_resp_json: """ This function is deprecated. See https://confluence.atlassian.com/jiracore/createmeta-rest-endpoint-to-be-removed-975040986.html @@ -1410,7 +1410,7 @@ def issue_field_value_append(self, issue_id_or_key: str, field: str, value: str, params=params, ) - def get_issue_labels(self, issue_key: str) -> dict | None: + def get_issue_labels(self, issue_key: str) -> T_resp_json: """ Get issue labels. :param issue_key: @@ -1426,7 +1426,7 @@ def get_issue_labels(self, issue_key: str) -> dict | None: return f.get("labels") return None - def update_issue(self, issue_key: T_id, update: dict | str) -> dict | None: + def update_issue(self, issue_key: T_id, update: dict | str) -> T_resp_json: """ :param issue: the issue to update :param update: the update to make @@ -1889,7 +1889,7 @@ def update_issue_remote_link_by_id( ) return self.put(url, data=data) - def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id) -> dict | None: + def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id) -> T_resp_json: """ Deletes Remote Link on Issue :param issue_key: str @@ -1917,7 +1917,7 @@ def get_issue_transitions(self, issue_key: str) -> list[dict]: for transition in cast(list[dict], d.get("transitions")) ] - def issue_transition(self, issue_key: str, status: str) -> dict | None: + def issue_transition(self, issue_key: str, status: str) -> T_resp_json: return self.set_issue_status(issue_key, status) def set_issue_status( @@ -1982,7 +1982,7 @@ def get_issue_status_id(self, issue_key: str) -> str: def get_issue_transitions_full( self, issue_key: str, transition_id: T_id | None = None, expand: str | None = None - ) -> dict | None: + ) -> T_resp_json: """ Get a list of the transitions possible for this issue by the current user, along with fields that are required and their types. @@ -3086,8 +3086,9 @@ def get_project_issuekey_last(self, project: str): jql = 'project = "{project}" ORDER BY issuekey DESC'.format(project=project) response = self.jql(jql) if self.advanced_mode: - return response - return (response.get("issues") or {"key": None})[0]["key"] + return cast(Response, response) + + return (cast(dict, response).__getitem__("issues") or {"key": None})[0]["key"] def get_project_issuekey_all( self, project: str, start: int = 0, limit: int | None = None, expand: str | None = None @@ -3095,15 +3096,15 @@ def get_project_issuekey_all( jql = 'project = "{project}" ORDER BY issuekey ASC'.format(project=project) response = self.jql(jql, start=start, limit=limit, expand=expand) if self.advanced_mode: - return response - return [issue["key"] for issue in response["issues"]] + return cast(Response, response) + return [issue["key"] for issue in cast(dict, response)["issues"]] def get_project_issues_count(self, project: str): jql = 'project = "{project}" '.format(project=project) response = self.jql(jql, fields="*none") if self.advanced_mode: - return response - return response["total"] + return cast(Response, response) + return cast(dict, response)["total"] def get_all_project_issues( self, project: str, fields: str | list[str] = "*all", start: int = 0, limit: int | None = None @@ -3119,8 +3120,8 @@ def get_all_project_issues( jql = 'project = "{project}" ORDER BY key'.format(project=project) response = self.jql(jql, fields=fields, start=start, limit=limit) if self.advanced_mode: - return response - return response["issues"] + return cast(Response, response) + return cast(dict, response)["issues"] def get_all_assignable_users_for_project(self, project_key: str, start: int = 0, limit: int = 50): """ @@ -3142,7 +3143,7 @@ def get_all_assignable_users_for_project(self, project_key: str, start: int = 0, def get_assignable_users_for_issue( self, issue_key: str, username: str | None = None, start: int = 0, limit: int = 50 - ): + ) -> T_resp_json: """ Provide assignable users for issue :param issue_key: @@ -3165,12 +3166,12 @@ def get_status_id_from_name(self, status_name: str): url = "{base_url}/{name}".format(base_url=base_url, name=status_name) return int((self.get(url) or {}).__getitem__("id")) - def get_status_for_project(self, project_key: str): + def get_status_for_project(self, project_key: str) -> T_resp_json: base_url = self.resource_url("project") url = "{base_url}/{name}/statuses".format(base_url=base_url, name=project_key) return self.get(url) - def get_all_time_tracking_providers(self): + def get_all_time_tracking_providers(self) -> T_resp_json: """ Returns all time tracking providers. By default, Jira only has one time tracking provider: JIRA provided time tracking. However, you can install other time tracking providers via apps from the Atlassian Marketplace. @@ -3178,7 +3179,7 @@ def get_all_time_tracking_providers(self): url = self.resource_url("configuration/timetracking/list") return self.get(url) - def get_selected_time_tracking_provider(self): + def get_selected_time_tracking_provider(self) -> T_resp_json: """ Returns the time tracking provider that is currently selected. Note that if time tracking is disabled, then a successful but empty response is returned. @@ -3186,7 +3187,7 @@ def get_selected_time_tracking_provider(self): url = self.resource_url("configuration/timetracking") return self.get(url) - def get_time_tracking_settings(self): + def get_time_tracking_settings(self) -> T_resp_json: """ Returns the time tracking settings. This includes settings such as the time format, default time unit, and others. @@ -3194,17 +3195,18 @@ def get_time_tracking_settings(self): url = self.resource_url("configuration/timetracking/options") return self.get(url) - def get_transition_id_to_status_name(self, issue_key: str, status_name: str): + def get_transition_id_to_status_name(self, issue_key: str, status_name: str) -> int | None: for transition in self.get_issue_transitions(issue_key): if status_name.lower() == transition["to"].lower(): return int(transition["id"]) + return None """ The Link Issue Resource provides functionality to manage issue links. Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/issueLink """ - def create_issue_link(self, data: dict): + def create_issue_link(self, data: dict) -> T_resp_json: """ Creates an issue link between two issues. The user requires the link issue permission for the issue which will be linked to another issue. @@ -3230,7 +3232,7 @@ def create_issue_link(self, data: dict): url = self.resource_url("issueLink") return self.post(url, data=data) - def get_issue_link(self, link_id: T_id): + def get_issue_link(self, link_id: T_id) -> T_resp_json: """ Returns an issue link with the specified id. :param link_id: the issue link id. @@ -3240,7 +3242,7 @@ def get_issue_link(self, link_id: T_id): url = "{base_url}/{link_id}".format(base_url=base_url, link_id=link_id) return self.get(url) - def remove_issue_link(self, link_id: T_id): + def remove_issue_link(self, link_id: T_id) -> T_resp_json: """ Deletes an issue link with the specified id. To be able to delete an issue link you must be able to view both issues @@ -3257,23 +3259,23 @@ def remove_issue_link(self, link_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/issueLinkType """ - def get_issue_link_types(self): + def get_issue_link_types(self) -> list: """Returns a list of available issue link types, if issue linking is enabled. Each issue link type has an id, a name and a label for the outward and inward link relationship. """ url = self.resource_url("issueLinkType") - return (self.get(url) or {}).get("issueLinkTypes") + return (self.get(url) or {}).__getitem__("issueLinkTypes") - def get_issue_link_types_names(self): + def get_issue_link_types_names(self) -> list: """ Provide issue link type names :return: """ return [link_type["name"] for link_type in self.get_issue_link_types()] - def create_issue_link_type_by_json(self, data: dict): + def create_issue_link_type_by_json(self, data: dict) -> T_resp_json: """Create a new issue link type. :param data: { @@ -3286,7 +3288,7 @@ def create_issue_link_type_by_json(self, data: dict): url = self.resource_url("issueLinkType") return self.post(url, data=data) - def create_issue_link_type(self, link_type_name: str, inward: str, outward: str): + def create_issue_link_type(self, link_type_name: str, inward: str, outward: str) -> T_resp_json | str: """Create a new issue link type. :param outward: :param inward: @@ -3299,19 +3301,19 @@ def create_issue_link_type(self, link_type_name: str, inward: str, outward: str) data = {"name": link_type_name, "inward": inward, "outward": outward} return self.create_issue_link_type_by_json(data=data) - def get_issue_link_type(self, issue_link_type_id: T_id): + def get_issue_link_type(self, issue_link_type_id: T_id) -> T_resp_json: """Returns for a given issue link type id all information about this issue link type.""" base_url = self.resource_url("issueLinkType") url = "{base_url}/{issueLinkTypeId}".format(base_url=base_url, issueLinkTypeId=issue_link_type_id) return self.get(url) - def delete_issue_link_type(self, issue_link_type_id: T_id): + def delete_issue_link_type(self, issue_link_type_id: T_id) -> T_resp_json: """Delete the specified issue link type.""" base_url = self.resource_url("issueLinkType") url = "{base_url}/{issueLinkTypeId}".format(base_url=base_url, issueLinkTypeId=issue_link_type_id) return self.delete(url) - def update_issue_link_type(self, issue_link_type_id: T_id, data: dict): + def update_issue_link_type(self, issue_link_type_id: T_id, data: dict) -> T_resp_json: """ Update the specified issue link type. :param issue_link_type_id: @@ -3331,7 +3333,7 @@ def update_issue_link_type(self, issue_link_type_id: T_id, data: dict): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/resolution """ - def get_all_resolutions(self): + def get_all_resolutions(self) -> T_resp_json: """ Returns a list of all resolutions. :return: @@ -3339,7 +3341,7 @@ def get_all_resolutions(self): url = self.resource_url("resolution") return self.get(url) - def get_resolution_by_id(self, resolution_id: T_id): + def get_resolution_by_id(self, resolution_id: T_id) -> T_resp_json: """ Get Resolution info by id :param resolution_id: @@ -3354,7 +3356,7 @@ def get_resolution_by_id(self, resolution_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/role """ - def get_all_global_project_roles(self): + def get_all_global_project_roles(self) -> T_resp_json: """ Get all the ProjectRoles available in Jira. Currently, this list is global. :return: @@ -3367,7 +3369,7 @@ def get_all_global_project_roles(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/screens """ - def get_all_screens(self): + def get_all_screens(self) -> T_resp_json: """ Get all available screens from Jira :return: list of json elements of screen with field id, name. description @@ -3375,7 +3377,7 @@ def get_all_screens(self): url = self.resource_url("screens") return self.get(url) - def get_all_available_screen_fields(self, screen_id: T_id): + def get_all_available_screen_fields(self, screen_id: T_id) -> T_resp_json: """ Get all available fields by screen id :param screen_id: @@ -3385,7 +3387,7 @@ def get_all_available_screen_fields(self, screen_id: T_id): url = "{base_url}/{screen_id}/availableFields".format(base_url=base_url, screen_id=screen_id) return self.get(url) - def get_screen_tabs(self, screen_id: T_id): + def get_screen_tabs(self, screen_id: T_id) -> list: """ Get tabs for the screen id :param screen_id: @@ -3395,7 +3397,7 @@ def get_screen_tabs(self, screen_id: T_id): url = "{base_url}/{screen_id}/tabs".format(base_url=base_url, screen_id=screen_id) return self.get(url) - def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id): + def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id) -> list: """ Get fields by the tab id and the screen id :param tab_id: @@ -3408,7 +3410,7 @@ def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id): ) return self.get(url) - def get_all_screen_fields(self, screen_id: T_id): + def get_all_screen_fields(self, screen_id: T_id) -> list: """ Get all fields by screen id :param screen_id: @@ -3423,7 +3425,7 @@ def get_all_screen_fields(self, screen_id: T_id): fields = fields + tab_fields return fields - def add_field(self, field_id: T_id, screen_id: T_id, tab_id: T_id): + def add_field(self, field_id: T_id, screen_id: T_id, tab_id: T_id) -> T_resp_json: """ Add field to a given tab in a screen :param field_id: field or custom field ID to be added @@ -3447,7 +3449,7 @@ def jql( limit: int | None = None, expand: str | None = None, validate_query: str | None = None, - ): + ) -> T_resp_json: """ Get issues from jql search result with all related fields :param jql: @@ -3537,7 +3539,7 @@ def csv( all_fields: bool = True, start: int | None = None, delimiter: str | None = None, - ): + ) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3569,7 +3571,7 @@ def csv( headers={"Accept": "application/csv"}, ) - def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int | None = None): + def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int | None = None) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3598,7 +3600,7 @@ def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int headers={"Accept": "application/vnd.ms-excel"}, ) - def export_html(self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None): + def export_html(self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3627,7 +3629,7 @@ def export_html(self, jql: str, limit: int | None = None, all_fields: bool = Tru headers={"Accept": "application/xhtml+xml"}, ) - def get_all_priorities(self): + def get_all_priorities(self) -> T_resp_json: """ Returns a list of all priorities. :return: @@ -3635,7 +3637,7 @@ def get_all_priorities(self): url = self.resource_url("priority") return self.get(url) - def get_priority_by_id(self, priority_id: T_id): + def get_priority_by_id(self, priority_id: T_id) -> T_resp_json: """ Get Priority info by id :param priority_id: @@ -3650,7 +3652,7 @@ def get_priority_by_id(self, priority_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/workflow """ - def get_all_workflows(self): + def get_all_workflows(self) -> T_resp_json: """ Provide all workflows for application admin :return: @@ -3664,7 +3666,7 @@ def get_workflows_paginated( max_results: int | None = None, workflow_name: str | None = None, expand: str | None = None, - ): + ) -> T_resp_json: """ Provide all workflows paginated (see https://developer.atlassian.com/cloud/jira/platform/rest/v2/\ api-group-workflows/#api-rest-api-2-workflow-search-get) @@ -3690,7 +3692,7 @@ def get_workflows_paginated( return self.get(url, params=params) - def get_all_statuses(self): + def get_all_statuses(self) -> T_resp_json: """ Returns a list of all statuses :return: @@ -3698,7 +3700,7 @@ def get_all_statuses(self): url = self.resource_url("status") return self.get(url) - def get_plugins_info(self): + def get_plugins_info(self) -> T_resp_json: """ Provide plugins info :return a json of installed plugins @@ -3706,7 +3708,7 @@ def get_plugins_info(self): url = "rest/plugins/1.0/" return self.get(url, headers=self.no_check_headers, trailing=True) - def get_plugin_info(self, plugin_key: str): + def get_plugin_info(self, plugin_key: str) -> T_resp_json: """ Provide plugin info :return a json of installed plugins @@ -3714,7 +3716,7 @@ def get_plugin_info(self, plugin_key: str): url = "rest/plugins/1.0/{plugin_key}-key".format(plugin_key=plugin_key) return self.get(url, headers=self.no_check_headers, trailing=True) - def get_plugin_license_info(self, plugin_key: str): + def get_plugin_license_info(self, plugin_key: str) -> T_resp_json: """ Provide plugin license info :return a json specific License query @@ -3722,7 +3724,7 @@ def get_plugin_license_info(self, plugin_key: str): url = "rest/plugins/1.0/{plugin_key}-key/license".format(plugin_key=plugin_key) return self.get(url, headers=self.no_check_headers, trailing=True) - def upload_plugin(self, plugin_path: str): + def upload_plugin(self, plugin_path: str) -> T_resp_json: """ Provide plugin path for upload into Jira e.g. useful for auto deploy :param plugin_path: @@ -3738,7 +3740,7 @@ def upload_plugin(self, plugin_path: str): url = "rest/plugins/1.0/?token={upm_token}".format(upm_token=upm_token) return self.post(url, files=files, headers=self.no_check_headers) - def delete_plugin(self, plugin_key: str): + def delete_plugin(self, plugin_key: str) -> T_resp_json: """ Delete plugin :param plugin_key: @@ -3751,7 +3753,7 @@ def check_plugin_manager_status(self) -> Response: 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: str, raw_license: str): + def update_plugin_license(self, plugin_key: str, raw_license: str) -> T_resp_json: """ Update license for plugin :param plugin_key: @@ -3766,7 +3768,7 @@ def update_plugin_license(self, plugin_key: str, raw_license: str): data = {"rawLicense": raw_license} return self.put(url, data=data, headers=app_headers) - def disable_plugin(self, plugin_key: str): + def disable_plugin(self, plugin_key: str) -> T_resp_json: """ Disable a plugin :param plugin_key: @@ -3780,7 +3782,7 @@ def disable_plugin(self, plugin_key: str): data = {"status": "disabled"} return self.put(url, data=data, headers=app_headers) - def enable_plugin(self, plugin_key: str): + def enable_plugin(self, plugin_key: str) -> T_resp_json: """ Enable a plugin :param plugin_key: @@ -3810,7 +3812,7 @@ def get_all_permissionschemes(self, expand: str | None = None): params["expand"] = expand return (self.get(url, params=params) or {}).get("permissionSchemes") - def get_permissionscheme(self, permission_id: T_id, expand: str | None = None): + def get_permissionscheme(self, permission_id: T_id, expand: str | None = None) -> T_resp_json: """ Returns a list of all permission schemes. By default, only shortened beans are returned. @@ -3828,7 +3830,7 @@ def get_permissionscheme(self, permission_id: T_id, expand: str | None = None): params["expand"] = expand return self.get(url, params=params) - def set_permissionscheme_grant(self, permission_id: T_id, new_permission: str): + def set_permissionscheme_grant(self, permission_id: T_id, new_permission: str) -> T_resp_json: """ Creates a permission grant in a permission scheme. Example: @@ -3856,7 +3858,7 @@ def set_permissionscheme_grant(self, permission_id: T_id, new_permission: str): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/priorityschemes """ - def get_issue_security_schemes(self): + def get_issue_security_schemes(self) -> T_resp_json: """ Returns all issue security schemes that are defined Administrator permission required @@ -3864,9 +3866,9 @@ def get_issue_security_schemes(self): :return: list """ url = self.resource_url("issuesecurityschemes") - return self.get(url).get("issueSecuritySchemes") + return (self.get(url) or {}).__getitem__("issueSecuritySchemes") - def get_issue_security_scheme(self, scheme_id: T_id, only_levels: bool = False): + def get_issue_security_scheme(self, scheme_id: T_id, only_levels: bool = False) -> T_resp_json: """ Returns the issue security scheme along with that are defined @@ -3885,7 +3887,7 @@ def get_issue_security_scheme(self, scheme_id: T_id, only_levels: bool = False): else: return self.get(url) - def get_project_issue_security_scheme(self, project_id_or_key: int, only_levels: bool = False) -> dict | None: + def get_project_issue_security_scheme(self, project_id_or_key: int, only_levels: bool = False) -> T_resp_json: """ Returns the issue security scheme for project @@ -3917,7 +3919,7 @@ def get_project_issue_security_scheme(self, project_id_or_key: int, only_levels: return response.get("levels") or None return response - def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: str | None = None): + def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: str | None = None) -> T_resp_json: """ Returns all priority schemes. All project keys associated with the priority scheme will only be returned @@ -3937,7 +3939,7 @@ def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: str params["expand"] = expand return self.get(url, params=params) - def create_priority_scheme(self, data: dict): + def create_priority_scheme(self, data: dict) -> T_resp_json: """ Creates new priority scheme. :param data: @@ -3962,7 +3964,7 @@ def create_priority_scheme(self, data: dict): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/priorityscheme """ - def get_priority_scheme_of_project(self, project_key_or_id: str, expand: str | None = None): + def get_priority_scheme_of_project(self, project_key_or_id: str, expand: str | None = None) -> T_resp_json: """ Gets a full representation of a priority scheme in JSON format used by specified project. Resource for associating priority scheme schemes and projects. @@ -3980,7 +3982,7 @@ def get_priority_scheme_of_project(self, project_key_or_id: str, expand: str | N ) return self.get(url, params=params) - def assign_priority_scheme_for_project(self, project_key_or_id: str, priority_scheme_id: T_id): + def assign_priority_scheme_for_project(self, project_key_or_id: str, priority_scheme_id: T_id) -> T_resp_json: """ Assigns project with priority scheme. Priority scheme assign with migration is possible from the UI. Operation will fail if migration is needed as a result of operation @@ -4002,7 +4004,7 @@ def assign_priority_scheme_for_project(self, project_key_or_id: str, priority_sc https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/securitylevel """ - def get_security_level_for_project(self, project_key_or_id: T_id): + def get_security_level_for_project(self, project_key_or_id: T_id) -> T_resp_json: """ Returns all security levels for the project that the current logged-in user has access to. If the user does not have the Set Issue Security permission, the list will be empty. @@ -4018,7 +4020,7 @@ def get_security_level_for_project(self, project_key_or_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/type """ - def get_all_project_types(self): + def get_all_project_types(self) -> T_resp_json: """ Returns all the project types defined on the Jira instance, not taking into account whether the license to use those project types is valid or not. @@ -4032,7 +4034,7 @@ def get_all_project_types(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/projectCategory """ - def get_all_project_categories(self): + def get_all_project_categories(self) -> T_resp_json: """ Returns all project categories :return: Returns a list of project categories. @@ -4045,7 +4047,7 @@ def get_all_project_categories(self): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/projectvalidate """ - def get_project_validated_key(self, key: str): + def get_project_validated_key(self, key: str) -> T_resp_json: """ Validates a project key. :param key: the project key @@ -4059,7 +4061,7 @@ def get_project_validated_key(self, key: str): REST resources for Issue Type Schemes """ - def add_issue_type_scheme(self, scheme_id: T_id, project_key: str): + def add_issue_type_scheme(self, scheme_id: T_id, project_key: str) -> T_resp_json: """ Associate an issue type scheme with an additional project https://docs.atlassian.com/software/jira/docs/api/REST/8.5.8#api/2/issuetypescheme-addProjectAssociationsToScheme @@ -4071,7 +4073,7 @@ def add_issue_type_scheme(self, scheme_id: T_id, project_key: str): data = {"idsOrKeys": [project_key]} return self.post(url, data=data) - def create_issuetype_scheme(self, name: str, description: str, default_issue_type_id: T_id, issue_type_ids: list): + def create_issuetype_scheme(self, name: str, description: str, default_issue_type_id: T_id, issue_type_ids: list) -> T_resp_json: """ Create an issue type scheme https://docs.atlassian.com/software/jira/docs/api/REST/8.13.6/#api/2/issuetypescheme-createIssueTypeScheme @@ -4100,7 +4102,7 @@ def reindex( change_history: bool = True, worklogs: bool = True, indexing_type: str = "BACKGROUND_PREFERRED", - ): + ) -> T_resp_json: """ Reindex the Jira instance Kicks off a reindex. Need Admin permissions to perform this reindex. @@ -4131,7 +4133,7 @@ def reindex( url = self.resource_url("reindex") return self.post(url, params=params) - def reindex_with_type(self, indexing_type: str = "BACKGROUND_PREFERRED"): + def reindex_with_type(self, indexing_type: str = "BACKGROUND_PREFERRED") -> T_resp_json: """ Reindex the Jira instance Type of re-indexing available: @@ -4145,7 +4147,7 @@ def reindex_with_type(self, indexing_type: str = "BACKGROUND_PREFERRED"): """ return self.reindex(indexing_type=indexing_type) - def reindex_status(self): + def reindex_status(self) -> T_resp_json: """ Returns information on the system reindexes. If a reindex is currently taking place then information about this reindex is returned. @@ -4156,7 +4158,7 @@ def reindex_status(self): url = self.resource_url("reindex") return self.get(url) - def reindex_project(self, project_key: str): + def reindex_project(self, project_key: str) -> T_resp_json: return self.post( "secure/admin/IndexProject.jspa", data="confirmed=true&key={}".format(project_key), @@ -4166,7 +4168,7 @@ def reindex_project(self, project_key: str): def reindex_issue(self, list_of_: list) -> None: pass - def index_checker(self, max_results: int = 100): + def index_checker(self, max_results: int = 100) -> T_resp_json: """ Jira DC Index health checker :param max_results: @@ -4176,7 +4178,7 @@ def index_checker(self, max_results: int = 100): params: dict = {"maxResults": max_results} return self.get(url, params=params) - def get_server_info(self, do_health_check: bool = False): + def get_server_info(self, do_health_check: bool = False) -> T_resp_json: """ Returns general information about the current Jira server. with health checks or not. @@ -4191,7 +4193,7 @@ def get_server_info(self, do_health_check: bool = False): ####################################################################### # Tempo Account REST API implements ####################################################################### - def tempo_account_get_accounts(self, skip_archived: bool | None = None, expand: str | None = None): + def tempo_account_get_accounts(self, skip_archived: bool | None = None, expand: str | None = None) -> T_resp_json: """ Get all Accounts that the logged-in user has permission to browse. :param skip_archived: bool OPTIONAL: skip archived Accounts, either true or false, default value true. @@ -4206,7 +4208,7 @@ def tempo_account_get_accounts(self, skip_archived: bool | None = None, expand: url = "rest/tempo-accounts/1/account" return self.get(url, params=params) - def tempo_account_get_accounts_by_jira_project(self, project_id: T_id): + def tempo_account_get_accounts_by_jira_project(self, project_id: T_id) -> T_resp_json: """ Get Accounts by JIRA Project. The Caller must have the Browse Account permission for Account. This will return Accounts for which the Caller has Browse Account Permission for. @@ -4218,7 +4220,7 @@ def tempo_account_get_accounts_by_jira_project(self, project_id: T_id): def tempo_account_associate_with_jira_project( self, account_id: T_id, project_id: T_id, default_account: bool = False, link_type: str = "MANUAL" - ): + ) -> T_resp_json: """ The AccountLinkBean for associate Account with project Adds a link to an Account. @@ -4252,7 +4254,7 @@ def tempo_account_associate_with_jira_project( url = "rest/tempo-accounts/1/link/" return self.post(url, data=data) - def tempo_account_add_account(self, data: dict | None = None): + def tempo_account_add_account(self, data: dict | None = None) -> T_resp_json | str: """ Creates Account, adding new Account requires the Manage Accounts Permission. :param data: String then it will convert to json @@ -4269,7 +4271,7 @@ def tempo_account_add_account(self, data: dict | None = None): """ return self.post(url, data=data) - def tempo_account_delete_account_by_id(self, account_id: str): + def tempo_account_delete_account_by_id(self, account_id: str) -> T_resp_json: """ Delete an Account by id. Caller must have the Manage Account Permission for the Account. The Account can not be deleted if it has an AccountLinkBean. @@ -4279,7 +4281,7 @@ def tempo_account_delete_account_by_id(self, account_id: str): url = "rest/tempo-accounts/1/account/{id}/".format(id=account_id) return self.delete(url) - def tempo_account_get_rate_table_by_account_id(self, account_id: str): + def tempo_account_get_rate_table_by_account_id(self, account_id: str) -> T_resp_json: """ Returns a rate table for the specified account. :param account_id: the account id. @@ -4289,7 +4291,7 @@ def tempo_account_get_rate_table_by_account_id(self, account_id: str): url = "rest/tempo-accounts/1/ratetable" return self.get(url, params=params) - def tempo_account_get_all_account_by_customer_id(self, customer_id: T_id): + def tempo_account_get_all_account_by_customer_id(self, customer_id: T_id) -> T_resp_json: """ Get un-archived Accounts by customer. The Caller must have the Browse Account permission for the Account. :param customer_id: the Customer id. @@ -4298,7 +4300,7 @@ def tempo_account_get_all_account_by_customer_id(self, customer_id: T_id): url = "rest/tempo-accounts/1/account/customer/{customerId}/".format(customerId=customer_id) return self.get(url) - def tempo_account_get_customers(self, query: str | None = None, count_accounts: bool | None = None): + def tempo_account_get_customers(self, query: str | None = None, count_accounts: bool | None = None) -> T_resp_json: """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4314,7 +4316,7 @@ def tempo_account_get_customers(self, query: str | None = None, count_accounts: url = "rest/tempo-accounts/1/customer" return self.get(url, params=params) - def tempo_account_add_new_customer(self, key: str, name: str): + def tempo_account_add_new_customer(self, key: str, name: str) -> T_resp_json: """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4326,7 +4328,7 @@ def tempo_account_add_new_customer(self, key: str, name: str): url = "rest/tempo-accounts/1/customer" return self.post(url, data=data) - def tempo_account_add_customer(self, data: dict | None = None): + def tempo_account_add_customer(self, data: dict | None = None) -> T_resp_json | str: """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4341,7 +4343,7 @@ def tempo_account_add_customer(self, data: dict | None = None): url = "rest/tempo-accounts/1/customer" return self.post(url, data=data) - def tempo_account_get_customer_by_id(self, customer_id: T_id = 1): + def tempo_account_get_customer_by_id(self, customer_id: T_id = 1) -> T_resp_json: """ Get Account Attribute whose key or name contain a specific substring. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4350,7 +4352,7 @@ def tempo_account_get_customer_by_id(self, customer_id: T_id = 1): url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.get(url) - def tempo_account_update_customer_by_id(self, customer_id: T_id = 1, data: dict | None = None): + def tempo_account_update_customer_by_id(self, customer_id: T_id = 1, data: dict | None = None) -> T_resp_json | str: """ Updates an Attribute. Caller must have Manage Account Permission. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4371,7 +4373,7 @@ def tempo_account_update_customer_by_id(self, customer_id: T_id = 1, data: dict url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.put(url, data=data) - def tempo_account_delete_customer_by_id(self, customer_id: T_id = 1): + def tempo_account_delete_customer_by_id(self, customer_id: T_id = 1) -> T_resp_json: """ Delete an Attribute. Caller must have Manage Account Permission. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4380,7 +4382,7 @@ def tempo_account_delete_customer_by_id(self, customer_id: T_id = 1): url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.delete(url) - def tempo_account_export_accounts(self): + def tempo_account_export_accounts(self) -> bytes: """ Get csv export file of Accounts from Tempo :return: csv file @@ -4389,7 +4391,7 @@ def tempo_account_export_accounts(self): url = "rest/tempo-accounts/1/export" return self.get(url, headers=headers, not_json_response=True) - def tempo_holiday_get_schemes(self): + def tempo_holiday_get_schemes(self) -> T_resp_json: """ Provide a holiday schemes :return: @@ -4397,7 +4399,7 @@ def tempo_holiday_get_schemes(self): url = "rest/tempo-core/2/holidayschemes/" return self.get(url) - def tempo_holiday_get_scheme_info(self, scheme_id: T_id): + def tempo_holiday_get_scheme_info(self, scheme_id: T_id) -> T_resp_json: """ Provide a holiday scheme :return: @@ -4405,7 +4407,7 @@ def tempo_holiday_get_scheme_info(self, scheme_id: T_id): url = "rest/tempo-core/2/holidayschemes/{}".format(scheme_id) return self.get(url) - def tempo_holiday_get_scheme_members(self, scheme_id: T_id): + def tempo_holiday_get_scheme_members(self, scheme_id: T_id) -> T_resp_json: """ Provide a holiday scheme members :return: @@ -4413,7 +4415,7 @@ def tempo_holiday_get_scheme_members(self, scheme_id: T_id): url = "rest/tempo-core/2/holidayschemes/{}/members".format(scheme_id) return self.get(url) - def tempo_holiday_put_into_scheme_member(self, scheme_id: T_id, username: str): + def tempo_holiday_put_into_scheme_member(self, scheme_id: T_id, username: str) -> T_resp_json: """ Provide a holiday scheme :return: @@ -4422,7 +4424,7 @@ def tempo_holiday_put_into_scheme_member(self, scheme_id: T_id, username: str): data = {"id": scheme_id} return self.put(url, data=data) - def tempo_holiday_scheme_set_default(self, scheme_id: T_id): + def tempo_holiday_scheme_set_default(self, scheme_id: T_id) -> T_resp_json: """ Set as default the holiday scheme :param scheme_id: @@ -4435,7 +4437,7 @@ def tempo_holiday_scheme_set_default(self, scheme_id: T_id): data = {"id": scheme_id} return self.post(url, data=data) - def tempo_workload_scheme_get_members(self, scheme_id: T_id): + def tempo_workload_scheme_get_members(self, scheme_id: T_id) -> T_resp_json: """ Provide a workload scheme members :param scheme_id: @@ -4444,7 +4446,7 @@ def tempo_workload_scheme_get_members(self, scheme_id: T_id): url = "rest/tempo-core/1/workloadscheme/users/{}".format(scheme_id) return self.get(url) - def tempo_workload_scheme_set_member(self, scheme_id: T_id, member: str): + def tempo_workload_scheme_set_member(self, scheme_id: T_id, member: str) -> T_resp_json: """ Provide a workload scheme members :param member: username of user @@ -4455,7 +4457,7 @@ def tempo_workload_scheme_set_member(self, scheme_id: T_id, member: str): data = {"id": scheme_id} return self.put(url, data=data) - def tempo_timesheets_get_configuration(self): + def tempo_timesheets_get_configuration(self) -> T_resp_json: """ Provide the configs of timesheets :return: @@ -4465,7 +4467,7 @@ def tempo_timesheets_get_configuration(self): def tempo_timesheets_get_team_utilization( self, team_id: T_id, date_from: str, date_to: str | None = None, group_by: str | None = None - ): + ) -> T_resp_json: """ Get team utilization. Response in json :param team_id: @@ -4489,7 +4491,7 @@ def tempo_timesheets_get_worklogs( project_key: str | None = None, account_key: str | None = None, team_id: T_id | None = None, - ): + ) -> T_resp_json: """ :param date_from: yyyy-MM-dd @@ -4517,7 +4519,7 @@ def tempo_timesheets_get_worklogs( return self.get(url, params=params) # noinspection PyIncorrectDocstring - def tempo_4_timesheets_find_worklogs(self, date_from: str | None = None, date_to: str | None = None, **params): + def tempo_4_timesheets_find_worklogs(self, date_from: str | None = None, date_to: str | None = None, **params: Any) -> T_resp_json: """ Find existing worklogs with searching parameters. NOTE: check if you are using correct types for the parameters! @@ -4552,7 +4554,7 @@ def tempo_4_timesheets_find_worklogs(self, date_from: str | None = None, date_to url = "rest/tempo-timesheets/4/worklogs/search" return self.post(url, data=params) - def tempo_timesheets_get_worklogs_by_issue(self, issue: str): + def tempo_timesheets_get_worklogs_by_issue(self, issue: str) -> T_resp_json: """ Get Tempo timesheet worklog by issue key or id. :param issue: Issue key or ID @@ -4563,7 +4565,7 @@ def tempo_timesheets_get_worklogs_by_issue(self, issue: str): def tempo_timesheets_write_worklog( self, worker: str, started: str, time_spend_in_seconds: int, issue_id: T_id, comment: str | None = None - ): + ) -> T_resp_json: """ Log work for user :param worker: @@ -4584,7 +4586,7 @@ def tempo_timesheets_write_worklog( url = "rest/tempo-timesheets/4/worklogs/" return self.post(url, data=data) - def tempo_timesheets_approval_worklog_report(self, user_key: str, period_start_date: str): + def tempo_timesheets_approval_worklog_report(self, user_key: str, period_start_date: str) -> T_resp_json: """ Return timesheets for approval :param user_key: @@ -4599,7 +4601,7 @@ def tempo_timesheets_approval_worklog_report(self, user_key: str, period_start_d params["userKey"] = user_key return self.get(url, params=params) - def tempo_timesheets_get_required_times(self, from_date: str, to_date: str, user_name: str): + def tempo_timesheets_get_required_times(self, from_date: str, to_date: str, user_name: str) -> T_resp_json: """ Provide time how much should work :param from_date: @@ -4617,7 +4619,7 @@ def tempo_timesheets_get_required_times(self, from_date: str, to_date: str, user params["user"] = user_name return self.get(url, params=params) - def tempo_timesheets_approval_status(self, period_start_date: str, user_name: str): + def tempo_timesheets_approval_status(self, period_start_date: str, user_name: str) -> T_resp_json: url = "rest/tempo-timesheets/4/timesheet-approval/approval-statuses" params: dict = {} if user_name: @@ -4626,7 +4628,7 @@ def tempo_timesheets_approval_status(self, period_start_date: str, user_name: st params["periodStartDate"] = period_start_date return self.get(url, params=params) - def tempo_get_links_to_project(self, project_id: T_id): + def tempo_get_links_to_project(self, project_id: T_id) -> T_resp_json: """ Gets all links to a specific project :param project_id: @@ -4635,7 +4637,7 @@ def tempo_get_links_to_project(self, project_id: T_id): url = "rest/tempo-accounts/1/link/project/{}/".format(project_id) return self.get(url) - def tempo_get_default_link_to_project(self, project_id: T_id): + def tempo_get_default_link_to_project(self, project_id: T_id) -> T_resp_json: """ Gets the default link to a specific project :param project_id: @@ -4644,14 +4646,14 @@ def tempo_get_default_link_to_project(self, project_id: T_id): url = "rest/tempo-accounts/1/link/project/{}/default/".format(project_id) return self.get(url) - def tempo_teams_get_all_teams(self, expand: str | None = None): + def tempo_teams_get_all_teams(self, expand: str | None = None) -> T_resp_json: url = "rest/tempo-teams/2/team" params: dict = {} if expand: params["expand"] = expand return self.get(url, params=params) - def tempo_teams_add_member(self, team_id: T_id, member_key: str): + def tempo_teams_add_member(self, team_id: T_id, member_key: str) -> T_resp_json: """ Add team member :param team_id: @@ -4664,7 +4666,7 @@ def tempo_teams_add_member(self, team_id: T_id, member_key: str): } return self.tempo_teams_add_member_raw(team_id, member_data=data) - def tempo_teams_add_membership(self, team_id: T_id, member_id: T_id): + def tempo_teams_add_membership(self, team_id: T_id, member_id: T_id) -> T_resp_json: """ Add team member :param team_id: @@ -4680,7 +4682,7 @@ def tempo_teams_add_membership(self, team_id: T_id, member_id: T_id): url = "rest/tempo-teams/2/team/{}/member/{}/membership".format(team_id, member_id) return self.post(url, data=data) - def tempo_teams_add_member_raw(self, team_id: T_id, member_data: dict): + def tempo_teams_add_member_raw(self, team_id: T_id, member_data: dict) -> T_resp_json: """ Add team member :param team_id: @@ -4691,7 +4693,7 @@ def tempo_teams_add_member_raw(self, team_id: T_id, member_data: dict): data = member_data return self.post(url, data=data) - def tempo_teams_get_members(self, team_id: T_id): + def tempo_teams_get_members(self, team_id: T_id) -> T_resp_json: """ Get members from team :param team_id: @@ -4700,7 +4702,7 @@ def tempo_teams_get_members(self, team_id: T_id): url = "rest/tempo-teams/2/team/{}/member/".format(team_id) return self.get(url) - def tempo_teams_remove_member(self, team_id: T_id, member_id: T_id, membership_id: T_id): + def tempo_teams_remove_member(self, team_id: T_id, member_id: T_id, membership_id: T_id) -> T_resp_json: """ Remove team membership :param team_id: @@ -4711,7 +4713,7 @@ def tempo_teams_remove_member(self, team_id: T_id, member_id: T_id, membership_i url = "rest/tempo-teams/2/team/{}/member/{}/membership/{}".format(team_id, member_id, membership_id) return self.delete(url) - def tempo_teams_update_member_information(self, team_id: T_id, member_id: T_id, membership_id: T_id, data: dict): + def tempo_teams_update_member_information(self, team_id: T_id, member_id: T_id, membership_id: T_id, data: dict) -> T_resp_json: """ Update team membership attribute info :param team_id: @@ -4723,13 +4725,13 @@ def tempo_teams_update_member_information(self, team_id: T_id, member_id: T_id, url = "rest/tempo-teams/2/team/{}/member/{}/membership/{}".format(team_id, member_id, membership_id) return self.put(url, data=data) - def tempo_timesheets_get_period_configuration(self) -> dict | None: + def tempo_timesheets_get_period_configuration(self) -> T_resp_json: return self.get("rest/tempo-timesheets/3/period-configuration") - def tempo_timesheets_get_private_configuration(self) -> dict | None: + def tempo_timesheets_get_private_configuration(self) -> T_resp_json: return self.get("rest/tempo-timesheets/3/private/config") - def tempo_teams_get_memberships_for_member(self, username: str) -> dict | None: + def tempo_teams_get_memberships_for_member(self, username: str) -> T_resp_json: return self.get("rest/tempo-teams/2/user/{}/memberships".format(username)) ####################################################################### @@ -4737,7 +4739,7 @@ def tempo_teams_get_memberships_for_member(self, username: str) -> dict | None: # Resource: https://docs.atlassian.com/jira-software/REST/7.3.1/ ####################################################################### # /rest/agile/1.0/backlog/issue - def move_issues_to_backlog(self, issue_keys: list) -> dict | None: + def move_issues_to_backlog(self, issue_keys: list) -> T_resp_json: """ Move issues to backlog :param issue_keys: list of issues @@ -4745,7 +4747,7 @@ def move_issues_to_backlog(self, issue_keys: list) -> dict | None: """ return self.add_issues_to_backlog(issues=issue_keys) - def add_issues_to_backlog(self, issues: list) -> dict | None: + def add_issues_to_backlog(self, issues: list) -> T_resp_json: """ Adding Issue(s) to Backlog :param issues: list: List of Issue Keys @@ -4760,7 +4762,7 @@ def add_issues_to_backlog(self, issues: list) -> dict | None: data = dict(issues=issues) return self.post(url, data=data) - def get_agile_board_by_filter_id(self, filter_id: T_id) -> dict | None: + def get_agile_board_by_filter_id(self, filter_id: T_id) -> T_resp_json: """ Gets an agile board by the filter id :param filter_id: int, str @@ -4769,7 +4771,7 @@ def get_agile_board_by_filter_id(self, filter_id: T_id) -> dict | None: return self.get(url) # /rest/agile/1.0/board - def create_agile_board(self, name: str, type: str, filter_id: T_id, location: dict | None = None) -> dict | None: + def create_agile_board(self, name: str, type: str, filter_id: T_id, location: dict | None = None) -> T_resp_json: """ Create an agile board :param name: str: Must be less than 255 characters. @@ -4790,7 +4792,7 @@ def get_all_agile_boards( board_type: str | None = None, start: int = 0, limit: int = 50, - ) -> dict | None: + ) -> T_resp_json: """ Returns all boards. This only includes boards that the user has permission to view. :param board_name: @@ -4815,7 +4817,7 @@ def get_all_agile_boards( return self.get(url, params=params) - def delete_agile_board(self, board_id: T_id) -> dict | None: + def delete_agile_board(self, board_id: T_id) -> T_resp_json: """ Delete agile board by id :param board_id: @@ -4824,7 +4826,7 @@ def delete_agile_board(self, board_id: T_id) -> dict | None: url = "rest/agile/1.0/board/{}".format(str(board_id)) return self.delete(url) - def get_agile_board(self, board_id: T_id) -> dict | None: + def get_agile_board(self, board_id: T_id) -> T_resp_json: """ Get agile board info by id :param board_id: @@ -4833,7 +4835,7 @@ def get_agile_board(self, board_id: T_id) -> dict | None: url = "rest/agile/1.0/board/{}".format(str(board_id)) return self.get(url) - def get_issues_for_backlog(self, board_id: T_id) -> dict | None: + def get_issues_for_backlog(self, board_id: T_id) -> T_resp_json: """ Returns all issues from the board's backlog, for the given board ID. This only includes issues that the user has permission to view. @@ -4846,7 +4848,7 @@ def get_issues_for_backlog(self, board_id: T_id) -> dict | None: url = "rest/agile/1.0/board/{board_id}/backlog".format(board_id=board_id) return self.get(url) - def get_agile_board_configuration(self, board_id: T_id) -> dict | None: + def get_agile_board_configuration(self, board_id: T_id) -> T_resp_json: """ Get the board configuration. The response contains the following fields: id - ID of the board. @@ -4881,7 +4883,7 @@ def get_issues_for_board( start: int = 0, limit: int | None = None, expand: str | None = None, - ) -> dict | None: + ) -> T_resp_json: """ Returns all issues from a board, for a given board Id. This only includes issues that the user has permission to view. @@ -4921,7 +4923,7 @@ def get_epics( done: bool = False, start: int = 0, limit: int = 50, - ) -> dict | None: + ) -> T_resp_json: """ Returns all epics from the board, for the given board Id. This only includes epics that the user has permission to view. @@ -4954,7 +4956,7 @@ def get_issues_for_epic( expand: str = "", start: int = 0, limit: int = 50, - ) -> dict | None: + ) -> T_resp_json: """ Returns all issues that belong to an epic on the board, for the given epic Id and the board Id. This only includes issues that the user has permission to view. @@ -5005,7 +5007,7 @@ def get_issues_without_epic( expand: str = "", start: int = 0, limit: int = 50, - ) -> dict | None: + ) -> T_resp_json: """ Returns all issues that do not belong to any epic on a board, for a given board Id. This only includes issues that the user has permission to view. @@ -5046,7 +5048,7 @@ def get_issues_without_epic( return self.get(url, params=params) # rest/agile/1.0/board/{boardId}/project - def get_all_projects_associated_with_board(self, board_id: T_id, start: int = 0, limit: int = 50) -> dict | None: + def get_all_projects_associated_with_board(self, board_id: T_id, start: int = 0, limit: int = 50) -> T_resp_json: """ Returns all projects that are associated with the board, for the given board ID. A project is associated with a board only @@ -5074,7 +5076,7 @@ def get_all_projects_associated_with_board(self, board_id: T_id, start: int = 0, return self.get(url, params=params) # /rest/agile/1.0/board/{boardId}/properties - def get_agile_board_properties(self, board_id: T_id) -> dict | None: + def get_agile_board_properties(self, board_id: T_id) -> T_resp_json: """ Returns the keys of all properties for the board identified by the id. The user who retrieves the property keys is required to have permissions to view the board. @@ -5083,7 +5085,7 @@ def get_agile_board_properties(self, board_id: T_id) -> dict | None: url = "rest/agile/1.0/board/{boardId}/properties".format(boardId=board_id) return self.get(url) - def set_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: + def set_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json: """ Sets the value of the specified board's property. You can use this resource to store a custom data @@ -5098,7 +5100,7 @@ def set_agile_board_property(self, board_id: T_id, property_key: str) -> dict | ) return self.put(url) - def get_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: + def get_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json: """ Returns the value of the property with a given key from the board identified by the provided id. The user who retrieves the property is required to have permissions to view the board. @@ -5111,7 +5113,7 @@ def get_agile_board_property(self, board_id: T_id, property_key: str) -> dict | ) return self.get(url) - def delete_agile_board_property(self, board_id: T_id, property_key: str) -> dict | None: + def delete_agile_board_property(self, board_id: T_id, property_key: str) -> T_resp_json: """ Removes the property from the board identified by the id. Ths user removing the property is required to have permissions to modify the board. @@ -5125,7 +5127,7 @@ def delete_agile_board_property(self, board_id: T_id, property_key: str) -> dict return self.delete(url) # /rest/agile/1.0/board/{boardId}/settings/refined-velocity - def get_agile_board_refined_velocity(self, board_id: T_id) -> dict | None: + def get_agile_board_refined_velocity(self, board_id: T_id) -> T_resp_json: """ Returns the estimation statistic settings of the board. :param board_id: @@ -5134,7 +5136,7 @@ def get_agile_board_refined_velocity(self, board_id: T_id) -> dict | None: url = "/rest/agile/1.0/board/{boardId}/settings/refined-velocity".format(boardId=board_id) return self.get(url) - def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> dict | None: + def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> T_resp_json: """ Sets the estimation statistic settings of the board. :param board_id: @@ -5148,7 +5150,7 @@ def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> dict | def get_all_sprints_from_board( self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50 - ) -> dict | None: + ) -> T_resp_json: """ Returns all sprints from a board, for a given board ID. This only includes sprints that the user has permission to view. @@ -5175,7 +5177,7 @@ def get_all_sprints_from_board( return self.get(url, params=params) @deprecated(version="3.42.0", reason="Use get_all_sprints_from_board instead") - def get_all_sprint(self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50) -> dict | None: + def get_all_sprint(self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50) -> T_resp_json: """ Returns all sprints from a board, for a given board ID. :param board_id: @@ -5196,7 +5198,7 @@ def get_all_issues_for_sprint_in_board( expand: str = "", start: int = 0, limit: int = 50, - ) -> dict | None: + ) -> T_resp_json: """ Get all issues you have access to that belong to the sprint from the board. Issue returned from this resource contains additional fields like: sprint, closedSprints, flagged and epic. @@ -5239,7 +5241,7 @@ def get_all_issues_for_sprint_in_board( # /rest/agile/1.0/board/{boardId}/version def get_all_versions_from_board( self, board_id: T_id, released: str = "true", start: int = 0, limit: int = 50 - ) -> dict | None: + ) -> T_resp_json: """ Returns all versions from a board, for a given board ID. This only includes versions that the user has permission to view. @@ -5275,7 +5277,7 @@ def create_sprint( start_date: str | None = None, end_date: str | None = None, goal: str | None = None, - ) -> dict | None: + ) -> T_resp_json: """ Create a sprint within a board. ! User requires `Manage Sprints` permission for relevant boards. @@ -5302,7 +5304,7 @@ def create_sprint( data["goal"] = goal return self.post(url, data=data) - def add_issues_to_sprint(self, sprint_id: T_id, issues: list[str]) -> dict | None: + def add_issues_to_sprint(self, sprint_id: T_id, issues: list[str]) -> T_resp_json: """ Adding Issue(s) to Sprint :param sprint_id: int/str: The ID for the Sprint. @@ -5320,7 +5322,7 @@ def add_issues_to_sprint(self, sprint_id: T_id, issues: list[str]) -> dict | Non data = dict(issues=issues) return self.post(url, data=data) - def get_sprint(self, sprint_id: T_id) -> dict | None: + def get_sprint(self, sprint_id: T_id) -> T_resp_json: """ Returns the sprint for a given sprint ID. The sprint will only be returned if the user can view the board that the sprint was created on, @@ -5331,7 +5333,7 @@ def get_sprint(self, sprint_id: T_id) -> dict | None: url = "rest/agile/1.0/sprint/{sprintId}".format(sprintId=sprint_id) return self.get(url) - def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: str) -> dict | None: + def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: str) -> T_resp_json: """ :param sprint_id: @@ -5345,7 +5347,7 @@ def rename_sprint(self, sprint_id: T_id, name: str, start_date: str, end_date: s data={"name": name, "startDate": start_date, "endDate": end_date}, ) - def delete_sprint(self, sprint_id: T_id) -> dict | None: + def delete_sprint(self, sprint_id: T_id) -> T_resp_json: """ Deletes a sprint. Once a sprint is deleted, all issues in the sprint will be moved to the backlog. @@ -5355,7 +5357,7 @@ def delete_sprint(self, sprint_id: T_id) -> dict | None: """ return self.delete("rest/agile/1.0/sprint/{sprintId}".format(sprintId=sprint_id)) - def update_partially_sprint(self, sprint_id: T_id, data: dict) -> dict | None: + def update_partially_sprint(self, sprint_id: T_id, data: dict) -> T_resp_json: """ Performs a partial update of a sprint. A partial update means that fields not present in the request JSON will not be updated. @@ -5375,7 +5377,7 @@ def update_partially_sprint(self, sprint_id: T_id, data: dict) -> dict | None: """ return self.post("rest/agile/1.0/sprint/{}".format(sprint_id), data=data) - def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> dict | None: + def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> T_resp_json: """ Returns all issues in a sprint, for a given sprint ID. This only includes issues that the user has permission to view. @@ -5400,7 +5402,7 @@ def get_sprint_issues(self, sprint_id: T_id, start: T_id, limit: T_id) -> dict | url = "rest/agile/1.0/sprint/{sprintId}/issue".format(sprintId=sprint_id) return self.get(url, params=params) - def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number: T_id) -> dict | None: + def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number: T_id) -> T_resp_json: """ Updates the rank of issues (max 50), placing them before a given issue. :param issues_to_rank: List of issues to rank (max 50) @@ -5418,7 +5420,7 @@ def update_rank(self, issues_to_rank: list, rank_before: str, customfield_number }, ) - def dvcs_get_linked_repos(self) -> dict | None: + def dvcs_get_linked_repos(self) -> T_resp_json: """ Get DVCS linked repos :return: @@ -5426,7 +5428,7 @@ def dvcs_get_linked_repos(self) -> dict | None: url = "rest/bitbucket/1.0/repositories" return self.get(url) - def dvcs_update_linked_repo_with_remote(self, repository_id: T_id) -> dict | None: + def dvcs_update_linked_repo_with_remote(self, repository_id: T_id) -> T_resp_json: """ Resync delayed sync repo https://confluence.atlassian.com/jirakb/delays-for-commits-to-display-in-development-panel-in-jira-server-779160823.html @@ -5436,7 +5438,7 @@ def dvcs_update_linked_repo_with_remote(self, repository_id: T_id) -> dict | Non url = "rest/bitbucket/1.0/repositories/{}/sync".format(repository_id) return self.post(url) - def flag_issue(self, issue_keys: list[T_id], flag: bool = True) -> dict | None: + def flag_issue(self, issue_keys: list[T_id], flag: bool = True) -> T_resp_json: """ Flags or un-flags one or multiple issues in Jira with a flag indicator. :param issue_keys: List of issue keys to flag or un-flag. @@ -5450,7 +5452,7 @@ def flag_issue(self, issue_keys: list[T_id], flag: bool = True) -> dict | None: data = {"issueKeys": issue_keys, "flag": flag} return self.post(url, data) - def health_check(self) -> dict | None: + def health_check(self) -> T_resp_json: """ Get health status of Jira. https://confluence.atlassian.com/jirakb/how-to-retrieve-health-check-results-using-rest-api-867195158.html @@ -5463,7 +5465,7 @@ def health_check(self) -> dict | None: response = self.get("rest/supportHealthCheck/1.0/check/") return response - def duplicated_account_checks_detail(self) -> dict | None: + def duplicated_account_checks_detail(self) -> T_resp_json: """ Health check: Duplicate user accounts detail https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html @@ -5472,7 +5474,7 @@ def duplicated_account_checks_detail(self) -> dict | None: response = self.get("rest/api/2/user/duplicated/list") return response - def duplicated_account_checks_flush(self) -> dict | None: + def duplicated_account_checks_flush(self) -> T_resp_json: """ Health check: Duplicate user accounts by flush The responses returned by the count and list methods are stored in the duplicate users cache for 10 minutes. @@ -5485,7 +5487,7 @@ def duplicated_account_checks_flush(self) -> dict | None: response = self.get("rest/api/2/user/duplicated/list", params=params) return response - def duplicated_account_checks_count(self) -> dict | None: + def duplicated_account_checks_count(self) -> T_resp_json: """ Health check: Duplicate user accounts count https://confluence.atlassian.com/jirakb/health-check-duplicate-user-accounts-1063554355.html diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 64c4fbe7b..704cf24cd 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -8,6 +8,9 @@ import requests from requests.adapters import HTTPAdapter +from atlassian.typehints import T_resp_json + + try: from oauthlib.oauth1.rfc5849 import SIGNATURE_RSA_SHA512 as SIGNATURE_RSA except ImportError: @@ -27,8 +30,8 @@ from typing_extensions import Self -T_resp = Response | dict | None -T_resp_get = Response | dict | str | bytes | None +T_resp = Response | T_resp_json +T_resp_get = Response | T_resp_json | str | bytes log = get_default_logger(__name__) @@ -208,7 +211,7 @@ def _update_header(self, key: str, value: str): self._session.headers.update({key: value}) @staticmethod - def _response_handler(response: Response) -> dict | None: + def _response_handler(response: Response) -> T_resp_json: try: return response.json() except ValueError: @@ -395,7 +398,7 @@ def get( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... # basic overall case @overload @@ -475,7 +478,7 @@ def post( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... @overload def post( @@ -490,7 +493,7 @@ def post( absolute: bool = ..., *, advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... @overload def post( @@ -504,7 +507,7 @@ def post( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... # advanced True @overload @@ -590,7 +593,7 @@ def put( absolute: bool = ..., *, advanced_mode: Literal[False], - ) -> dict | None: ... + ) -> T_resp_json: ... @overload def put( @@ -603,7 +606,7 @@ def put( params: dict | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... # advanced True @overload @@ -725,7 +728,7 @@ def delete( absolute: bool = ..., *, advanced_mode: Literal[False], - ) -> dict | None: ... + ) -> T_resp_json: ... @overload def delete( @@ -737,7 +740,7 @@ def delete( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> dict | None: ... + ) -> T_resp_json: ... # advanced True @overload diff --git a/atlassian/typehints.py b/atlassian/typehints.py index cf717c5be..80e3fde75 100644 --- a/atlassian/typehints.py +++ b/atlassian/typehints.py @@ -1,7 +1,7 @@ from typing_extensions import TypeAlias - T_id: TypeAlias = str | int _Data: TypeAlias = ( dict | str ) +T_resp_json: TypeAlias = dict | None From 124a1529c5cf0a36fb8917451f0faaae136aa52f Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 10:32:43 -0500 Subject: [PATCH 06/15] allowing test action to be run manually to help troubleshoot failures --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c95f0e7c8..dc8dfced6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,7 @@ on: branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: jobs: From 71453194419508e4a2b3bf3434d0b42e7b3ce74f Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 11:02:01 -0500 Subject: [PATCH 07/15] cannot utilize the workflow_dispatch on branches until it's been merged into main/master. --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc8dfced6..c95f0e7c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,6 @@ on: branches: [ master ] pull_request: branches: [ master ] - workflow_dispatch: jobs: From 85a9d016a631b7d1e6b3151fd29e2a15343af0f7 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 11:26:00 -0500 Subject: [PATCH 08/15] black formatting is different in 3.7 --- atlassian/jira.py | 22 +++++++++++----- atlassian/rest_client.py | 54 ++++++++++++++++++++++++++-------------- atlassian/typehints.py | 4 +-- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 26d197b58..1b7d77512 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -692,7 +692,9 @@ def get_all_fields(self) -> T_resp_json: url = self.resource_url("field") return self.get(url) - def create_custom_field(self, name: str, type: str, search_key: str | None = None, description: str | None = None) -> T_resp_json: + def create_custom_field( + self, name: str, type: str, search_key: str | None = None, description: str | None = None + ) -> T_resp_json: """ Creates a custom field with the given name and type :param name: str - name of the custom field @@ -3087,7 +3089,7 @@ def get_project_issuekey_last(self, project: str): response = self.jql(jql) if self.advanced_mode: return cast(Response, response) - + return (cast(dict, response).__getitem__("issues") or {"key": None})[0]["key"] def get_project_issuekey_all( @@ -3600,7 +3602,9 @@ def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int headers={"Accept": "application/vnd.ms-excel"}, ) - def export_html(self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None) -> bytes: + def export_html( + self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None + ) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -4073,7 +4077,9 @@ def add_issue_type_scheme(self, scheme_id: T_id, project_key: str) -> T_resp_jso data = {"idsOrKeys": [project_key]} return self.post(url, data=data) - def create_issuetype_scheme(self, name: str, description: str, default_issue_type_id: T_id, issue_type_ids: list) -> T_resp_json: + def create_issuetype_scheme( + self, name: str, description: str, default_issue_type_id: T_id, issue_type_ids: list + ) -> T_resp_json: """ Create an issue type scheme https://docs.atlassian.com/software/jira/docs/api/REST/8.13.6/#api/2/issuetypescheme-createIssueTypeScheme @@ -4519,7 +4525,9 @@ def tempo_timesheets_get_worklogs( return self.get(url, params=params) # noinspection PyIncorrectDocstring - def tempo_4_timesheets_find_worklogs(self, date_from: str | None = None, date_to: str | None = None, **params: Any) -> T_resp_json: + def tempo_4_timesheets_find_worklogs( + self, date_from: str | None = None, date_to: str | None = None, **params: Any + ) -> T_resp_json: """ Find existing worklogs with searching parameters. NOTE: check if you are using correct types for the parameters! @@ -4713,7 +4721,9 @@ def tempo_teams_remove_member(self, team_id: T_id, member_id: T_id, membership_i url = "rest/tempo-teams/2/team/{}/member/{}/membership/{}".format(team_id, member_id, membership_id) return self.delete(url) - def tempo_teams_update_member_information(self, team_id: T_id, member_id: T_id, membership_id: T_id, data: dict) -> T_resp_json: + def tempo_teams_update_member_information( + self, team_id: T_id, member_id: T_id, membership_id: T_id, data: dict + ) -> T_resp_json: """ Update team membership attribute info :param team_id: diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 704cf24cd..1c0945d3a 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -351,7 +351,8 @@ def get( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[True], - ) -> bytes: ... + ) -> bytes: + ... # not_json_response True @overload @@ -367,7 +368,8 @@ def get( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> bytes: ... + ) -> bytes: + ... # advanced mode True @overload @@ -383,7 +385,8 @@ def get( absolute: bool = ..., *, advanced_mode: Literal[True], - ) -> Response: ... + ) -> Response: + ... # both False @overload @@ -398,7 +401,8 @@ def get( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... # basic overall case @overload @@ -413,7 +417,8 @@ def get( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> T_resp_get: ... + ) -> T_resp_get: + ... def get( self, @@ -478,7 +483,8 @@ def post( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... @overload def post( @@ -493,7 +499,8 @@ def post( absolute: bool = ..., *, advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... @overload def post( @@ -507,7 +514,8 @@ def post( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... # advanced True @overload @@ -523,7 +531,8 @@ def post( absolute: bool = ..., *, advanced_mode: Literal[True], - ) -> Response: ... + ) -> Response: + ... # basic overall case @overload @@ -538,7 +547,8 @@ def post( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> Response | dict | None: ... + ) -> Response | dict | None: + ... def post( self, @@ -593,7 +603,8 @@ def put( absolute: bool = ..., *, advanced_mode: Literal[False], - ) -> T_resp_json: ... + ) -> T_resp_json: + ... @overload def put( @@ -606,7 +617,8 @@ def put( params: dict | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... # advanced True @overload @@ -621,7 +633,8 @@ def put( absolute: bool = ..., *, advanced_mode: Literal[True], - ) -> Response: ... + ) -> Response: + ... # basic overall case @overload @@ -635,7 +648,8 @@ def put( params: dict | None = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> Response | dict | None: ... + ) -> Response | dict | None: + ... def put( self, @@ -728,7 +742,8 @@ def delete( absolute: bool = ..., *, advanced_mode: Literal[False], - ) -> T_resp_json: ... + ) -> T_resp_json: + ... @overload def delete( @@ -740,7 +755,8 @@ def delete( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., - ) -> T_resp_json: ... + ) -> T_resp_json: + ... # advanced True @overload @@ -754,7 +770,8 @@ def delete( absolute: bool = ..., *, advanced_mode: Literal[True], - ) -> Response: ... + ) -> Response: + ... # basic overall case @overload @@ -767,7 +784,8 @@ def delete( trailing: bool | None = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> T_resp: ... + ) -> T_resp: + ... def delete( self, diff --git a/atlassian/typehints.py b/atlassian/typehints.py index 80e3fde75..4ec474fd9 100644 --- a/atlassian/typehints.py +++ b/atlassian/typehints.py @@ -1,7 +1,5 @@ from typing_extensions import TypeAlias T_id: TypeAlias = str | int -_Data: TypeAlias = ( - dict | str -) +_Data: TypeAlias = dict | str T_resp_json: TypeAlias = dict | None From f3274b37439bb25d184c8f53728424bb7fed3252 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 12:23:19 -0500 Subject: [PATCH 09/15] urllib3 is imported directly, thus should be in the reqs --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 0092d3d55..ce5610a7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ requests-kerberos==0.14.0 # Add this package to search string in json jmespath beautifulsoup4 +urllib3 From 2969f6d6196c3555c69defc75af3c36e1c578c3a Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 12:41:48 -0500 Subject: [PATCH 10/15] cannot use new style annotations with older versions of python --- atlassian/jira.py | 370 +++++++++++++++++++++------------------ atlassian/rest_client.py | 324 +++++++++++++++++----------------- atlassian/typehints.py | 7 +- 3 files changed, 364 insertions(+), 337 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 1b7d77512..e298cc2d1 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -1,10 +1,9 @@ # coding=utf-8 -from __future__ import annotations - +from typing_extensions import Literal import logging import os import re -from typing import TYPE_CHECKING, Any, BinaryIO, Literal, cast +from typing import TYPE_CHECKING, Any, BinaryIO, cast, Optional, Union, Dict, List from warnings import warn from deprecated import deprecated @@ -34,10 +33,10 @@ def __init__(self, url: str, *args: Any, **kwargs: Any): def _get_paged( self, url: str, - params: dict | None = None, - data: dict | None = None, - flags: list | None = None, - trailing: bool | None = None, + params: Optional[dict] = None, + data: Optional[dict] = None, + flags: Optional[list] = None, + trailing: Optional[bool] = None, absolute: bool = False, ): """ @@ -93,10 +92,10 @@ def _get_paged( def get_permissions( self, permissions: str, - project_id: T_id | None = None, - project_key: T_id | None = None, - issue_id: T_id | None = None, - issue_key: T_id | None = None, + project_id: Optional[T_id] = None, + project_key: Optional[T_id] = None, + issue_id: Optional[T_id] = None, + issue_key: Optional[T_id] = None, ) -> T_resp_json: """ Returns a list of permissions indicating which permissions the user has. Details of the user's permissions can @@ -132,7 +131,7 @@ def get_permissions( """ url = self.resource_url("mypermissions") - params: dict[str, str | int] = {"permissions": permissions} + params: Dict[str, Union[str, int]] = {"permissions": permissions} if project_id: params["projectId"] = project_id @@ -160,7 +159,7 @@ def get_all_permissions(self) -> T_resp_json: """ def get_property( - self, key: T_id | None = None, permission_level: str | None = None, key_filter: str | None = None + self, key: Optional[T_id] = None, permission_level: Optional[str] = None, key_filter: Optional[str] = None ) -> T_resp_json: """ Returns an application property @@ -232,7 +231,7 @@ def get_application_role(self, role_key: str) -> T_resp_json: Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/attachment """ - def get_attachments_ids_from_issue(self, issue: T_id) -> list[dict[str, str]]: + def get_attachments_ids_from_issue(self, issue: T_id) -> List[Dict[str, str]]: """ Get attachments IDs from jira issue :param jira issue key: str @@ -254,7 +253,9 @@ def get_attachment(self, attachment_id: T_id) -> T_resp_json: url = "{base_url}/{attachment_id}".format(base_url=base_url, attachment_id=attachment_id) return self.get(url) - def download_attachments_from_issue(self, issue: T_id, path: str | None = None, cloud: bool = True) -> str | None: + def download_attachments_from_issue( + self, issue: T_id, path: Optional[str] = None, cloud: bool = True + ) -> Optional[str]: """ Downloads all attachments from a Jira issue. :param issue: The issue-key of the Jira issue @@ -348,11 +349,11 @@ def get_attachment_expand_raw(self, attachment_id: T_id) -> T_resp_json: def get_audit_records( self, - offset: int | None = None, - limit: int | None = None, - filter: str | None = None, - from_date: str | None = None, - to_date: str | None = None, + offset: Optional[int] = None, + limit: Optional[int] = None, + filter: Optional[str] = None, + from_date: Optional[str] = None, + to_date: Optional[str] = None, ) -> T_resp_json: """ Returns auditing records filtered using provided parameters @@ -383,7 +384,7 @@ def get_audit_records( url = self.resource_url("auditing/record") return self.get(url, params=params) or {} - def post_audit_record(self, audit_record: dict | str) -> T_resp_json: + def post_audit_record(self, audit_record: Union[dict, str]) -> T_resp_json: """ Store a record in Audit Log :param audit_record: json with compat https://docs.atlassian.com/jira/REST/schema/audit-record# @@ -666,7 +667,7 @@ def get_custom_field_option(self, option_id: T_id) -> T_resp_json: url = "{base_url}/{id}".format(base_url=base_url, id=option_id) return self.get(url) - def get_custom_fields(self, search: str | None = None, start: int = 1, limit: int = 50) -> T_resp_json: + def get_custom_fields(self, search: Optional[str] = None, start: int = 1, limit: int = 50) -> T_resp_json: """ Get custom fields. Evaluated on 7.12 :param search: str @@ -693,7 +694,7 @@ def get_all_fields(self) -> T_resp_json: return self.get(url) def create_custom_field( - self, name: str, type: str, search_key: str | None = None, description: str | None = None + self, name: str, type: str, search_key: Optional[str] = None, description: Optional[str] = None ) -> T_resp_json: """ Creates a custom field with the given name and type @@ -791,7 +792,7 @@ def get_dashboard(self, dashboard_id: T_id): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/filter """ - def create_filter(self, name: str, jql: str, description: str | None = None, favourite: bool = False): + def create_filter(self, name: str, jql: str, description: Optional[str] = None, favourite: bool = False): """ :param name: str :param jql: str @@ -811,9 +812,9 @@ def edit_filter( self, filter_id: T_id, name: str, - jql: str | None = None, - description: str | None = None, - favourite: bool | None = None, + jql: Optional[str] = None, + description: Optional[str] = None, + favourite: Optional[bool] = None, ): """ Updates an existing filter. @@ -885,12 +886,12 @@ def add_filter_share_permission( self, filter_id: T_id, type: str, - project_id: T_id | None = None, - project_role_id: T_id | None = None, - groupname: str | None = None, - user_key: str | None = None, - view: str | None = None, - edit: str | None = None, + project_id: Optional[T_id] = None, + project_role_id: Optional[T_id] = None, + groupname: Optional[str] = None, + user_key: Optional[str] = None, + view: Optional[str] = None, + edit: Optional[str] = None, ): """ Adds share permission for a filter. See the documentation of the sharePermissions. @@ -940,7 +941,7 @@ def delete_filter_share_permission(self, filter_id: T_id, permission_id: T_id): https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/groups """ - def get_groups(self, query: str | None = None, exclude: str | None = None, limit: int = 20): + def get_groups(self, query: Optional[str] = None, exclude: Optional[str] = None, limit: int = 20): """ REST endpoint for searching groups in a group picker Returns groups with substrings matching a given query. This is mainly for use with the group picker, @@ -977,7 +978,7 @@ def create_group(self, name: str): data = {"name": name} return self.post(url, data=data) - def remove_group(self, name: str, swap_group: str | None = None): + def remove_group(self, name: str, swap_group: Optional[str] = None): """ 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 @@ -1018,7 +1019,7 @@ def get_all_users_from_group( return self.get(url, params=params) def add_user_to_group( - self, username: str | None = None, group_name: str | None = None, account_id: str | None = None + self, username: Optional[str] = None, group_name: Optional[str] = None, account_id: Optional[str] = None ): """ Add given user to a group @@ -1043,7 +1044,7 @@ def add_user_to_group( return self.post(url, params=params, data=data) def remove_user_from_group( - self, username: str | None = None, group_name: str | None = None, account_id: str | None = None + self, username: Optional[str] = None, group_name: Optional[str] = None, account_id: Optional[str] = None ) -> T_resp_json: """ Remove given user from a group @@ -1070,8 +1071,8 @@ def remove_user_from_group( def get_users_with_browse_permission_to_a_project( self, username: str, - issue_key: str | None = None, - project_key: str | None = None, + issue_key: Optional[str] = None, + project_key: Optional[str] = None, start: int = 0, limit: int = 100, ) -> T_resp_json: @@ -1107,7 +1108,7 @@ def get_users_with_browse_permission_to_a_project( Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/issue """ - def issue(self, key: T_id, fields: str | dict = "*all", expand: str | None = None): + def issue(self, key: T_id, fields: Union[str, dict] = "*all", expand: Optional[str] = None): base_url = self.resource_url("issue") url = "{base_url}/{key}?fields={fields}".format(base_url=base_url, key=key, fields=fields) params: dict = {} @@ -1118,10 +1119,10 @@ def issue(self, key: T_id, fields: str | dict = "*all", expand: str | None = Non def get_issue( self, issue_id_or_key: T_id, - fields: str | list | tuple | set | None = None, - properties: str | None = None, + fields: Union[str, list, tuple, set, None] = None, + properties: Optional[str] = None, update_history: bool = True, - expand: str | None = None, + expand: Optional[str] = None, ): """ Returns a full representation of the issue for the given issue key @@ -1149,7 +1150,7 @@ def get_issue( params["updateHistory"] = str(update_history).lower() return self.get(url, params=params) - def epic_issues(self, epic: str, fields: list | str = "*all", expand: str | None = None): + def epic_issues(self, epic: str, fields: Union[str, list] = "*all", expand: Optional[str] = None): """ Given an epic return all child issues By default, all fields are returned in this get-issue resource @@ -1168,7 +1169,7 @@ def epic_issues(self, epic: str, fields: list | str = "*all", expand: str | None params["expand"] = expand return self.get(url, params=params) - def bulk_issue(self, issue_list: list, fields: list | str = "*all"): + def bulk_issue(self, issue_list: list, fields: Union[str, list] = "*all"): """ :param fields: :param list issue_list: @@ -1209,7 +1210,7 @@ def issue_createmeta(self, project: str, expand: str = "projects.issuetypes.fiel url = self.resource_url("issue/createmeta?projectKeys={}".format(project)) return self.get(url, params=params) - def issue_createmeta_issuetypes(self, project: str, start: int | None = None, limit: int | None = None): + def issue_createmeta_issuetypes(self, project: str, start: Optional[int] = None, limit: Optional[int] = None): """ Get create metadata issue types for a project Returns a page of issue type metadata for a specified project. @@ -1228,7 +1229,7 @@ def issue_createmeta_issuetypes(self, project: str, start: int | None = None, li return self.get(url, params=params) def issue_createmeta_fieldtypes( - self, project: str, issue_type_id: str, start: int | None = None, limit: int | None = None + self, project: str, issue_type_id: str, start: Optional[int] = None, limit: Optional[int] = None ): """ Get create field metadata for a project and issue type id @@ -1254,7 +1255,7 @@ def issue_editmeta(self, key: str): url = "{}/{}/editmeta".format(base_url, key) return self.get(url) - def get_issue_changelog(self, issue_key: str, start: int | None = None, limit: int | None = None): + def get_issue_changelog(self, issue_key: str, start: Optional[int] = None, limit: Optional[int] = None): """ Get issue related change log :param issue_key: @@ -1276,7 +1277,7 @@ def get_issue_changelog(self, issue_key: str, start: int | None = None, limit: i url = "{base_url}/{issue_key}?expand=changelog".format(base_url=base_url, issue_key=issue_key) return (self.get(url) or {}).get("changelog", params) - def issue_add_json_worklog(self, key: str, worklog: dict | str): + def issue_add_json_worklog(self, key: str, worklog: Union[dict, str]): """ :param key: @@ -1287,7 +1288,7 @@ def issue_add_json_worklog(self, key: str, worklog: dict | str): url = "{base_url}/{key}/worklog".format(base_url=base_url, key=key) return self.post(url, data=worklog) - def issue_worklog(self, key: str, started: str, time_sec: int, comment: str | None = None): + def issue_worklog(self, key: str, started: str, time_sec: int, comment: Optional[str] = None): """ :param key: :param time_sec: int: second @@ -1312,7 +1313,7 @@ def issue_get_worklog(self, issue_id_or_key: str): return self.get(url) - def issue_archive(self, issue_id_or_key: str, notify_users: bool | None = None): + def issue_archive(self, issue_id_or_key: str, notify_users: Optional[bool] = None): """ Archives an issue. :param issue_id_or_key: Issue id or issue key @@ -1349,7 +1350,7 @@ def issue_fields(self, key: str): if issue: return issue["fields"] - def update_issue_field(self, key: T_id, fields: str | dict = "*all", notify_users: bool = True): + def update_issue_field(self, key: T_id, fields: Union[str, dict] = "*all", notify_users: bool = True): """ Update an issue's fields. :param key: str Issue id or issye key @@ -1367,7 +1368,7 @@ def update_issue_field(self, key: T_id, fields: str | dict = "*all", notify_user params=params, ) - def bulk_update_issue_field(self, key_list: list, fields: str | dict = "*all") -> bool: + def bulk_update_issue_field(self, key_list: list, fields: Union[str, dict] = "*all") -> bool: """ :param key_list: list of issues with common filed to be updated :param fields: common fields to be updated @@ -1428,7 +1429,7 @@ def get_issue_labels(self, issue_key: str) -> T_resp_json: return f.get("labels") return None - def update_issue(self, issue_key: T_id, update: dict | str) -> T_resp_json: + def update_issue(self, issue_key: T_id, update: Union[str, dict]) -> T_resp_json: """ :param issue: the issue to update :param update: the update to make @@ -1480,7 +1481,7 @@ def add_attachment_object(self, issue_key: str, attachment: BinaryIO): return None return self.post(url, headers=self.no_check_headers, files=files) - def issue_exists(self, issue_key: str) -> bool | None: + def issue_exists(self, issue_key: str) -> Optional[bool]: original_value = self.advanced_mode self.advanced_mode = True try: @@ -1525,13 +1526,13 @@ def delete_issue(self, issue_id_or_key: str, delete_subtasks: bool = True): return self.delete(url, params=params) # @todo merge with edit_issue method - def issue_update(self, issue_key: str, fields: str | dict): + def issue_update(self, issue_key: str, fields: Union[str, dict]): log.warning('Updating issue "%s" with "%s"', issue_key, fields) base_url = self.resource_url("issue") url = "{base_url}/{issue_key}".format(base_url=base_url, issue_key=issue_key) return self.put(url, data={"fields": fields}) - def edit_issue(self, issue_id_or_key: str, fields: str | dict, notify_users: bool = True): + def edit_issue(self, issue_id_or_key: str, fields: Union[str, dict], notify_users: bool = True): """ Edits an issue from a JSON representation The issue can either be updated by setting explicit the field @@ -1592,7 +1593,7 @@ def issue_get_watchers(self, issue_key: str): base_url = self.resource_url("issue") return self.get("{base_url}/{issue_key}/watchers".format(base_url=base_url, issue_key=issue_key)) - def assign_issue(self, issue: T_id, account_id: str | None = None): + def assign_issue(self, issue: T_id, account_id: Optional[str] = None): """Assign an issue to a user. None will set it to unassigned. -1 will set it to Automatic. :param issue : the issue ID or key to assign :type issue: int or str @@ -1609,7 +1610,7 @@ def assign_issue(self, issue: T_id, account_id: str | None = None): data = {"name": account_id} return self.put(url, data=data) - def create_issue(self, fields: str | dict, update_history: bool = False, update: dict | None = None): + def create_issue(self, fields: Union[str, dict], update_history: bool = False, update: Optional[dict] = None): """ Creates an issue or a sub-task from a JSON representation :param fields: JSON data @@ -1681,7 +1682,7 @@ def issue_create_or_update(self, fields: dict): fields.pop("issuekey", None) return self.issue_update(issue_key, fields) - def issue_add_comment(self, issue_key: str, comment: str, visibility: dict | None = None): + def issue_add_comment(self, issue_key: str, comment: str, visibility: Optional[dict] = None): """ Add comment into Jira issue :param issue_key: @@ -1697,7 +1698,12 @@ def issue_add_comment(self, issue_key: str, comment: str, visibility: dict | Non return self.post(url, data=data) def issue_edit_comment( - self, issue_key: str, comment_id: T_id, comment: str, visibility: dict | None = None, notify_users: bool = True + self, + issue_key: str, + comment_id: T_id, + comment: str, + visibility: Optional[dict] = None, + notify_users: bool = True, ): """ Updates an existing comment @@ -1757,13 +1763,17 @@ def scrap_regex_from_issue(self, issue: str, regex: str): reason=e, ) - def get_issue_remotelinks(self, issue_key: str, global_id: T_id | None = None, internal_id: str | None = None): + def get_issue_remotelinks( + self, issue_key: str, global_id: Optional[T_id] = None, internal_id: Optional[str] = None + ): """ Compatibility naming method with get_issue_remote_links() """ return self.get_issue_remote_links(issue_key, global_id, internal_id) - def get_issue_remote_links(self, issue_key: str, global_id: T_id | None = None, internal_id: str | None = None): + def get_issue_remote_links( + self, issue_key: str, global_id: Optional[T_id] = None, internal_id: Optional[str] = None + ): """ Finding all Remote Links on an issue, also with filtering by Global ID and internal ID :param issue_key: @@ -1821,10 +1831,10 @@ def create_or_update_issue_remote_links( issue_key: str, link_url: str, title: str, - global_id: T_id | None = None, - relationship: str | None = None, - icon_url: str | None = None, - icon_title: str | None = None, + global_id: Optional[T_id] = None, + relationship: Optional[str] = None, + icon_url: Optional[str] = None, + icon_title: Optional[str] = None, status_resolved: bool = False, ): """ @@ -1867,8 +1877,8 @@ def update_issue_remote_link_by_id( link_id: T_id, url: str, title: str, - global_id: T_id | None = None, - relationship: str | None = None, + global_id: Optional[T_id] = None, + relationship: Optional[str] = None, ): """ Update existing Remote Link on Issue @@ -1903,10 +1913,10 @@ def delete_issue_remote_link_by_id(self, issue_key: str, link_id: T_id) -> T_res ) return self.delete(url) - def get_issue_transitions(self, issue_key: str) -> list[dict]: + def get_issue_transitions(self, issue_key: str) -> List[dict]: if self.advanced_mode: resp = cast(Response, self.get_issue_transitions_full(issue_key)) - d: dict[str, list] = resp.json() or {} + d: Dict[str, list] = resp.json() or {} else: d = self.get_issue_transitions_full(issue_key) or {} @@ -1916,14 +1926,14 @@ def get_issue_transitions(self, issue_key: str) -> list[dict]: "id": int(transition["id"]), "to": transition["to"]["name"], } - for transition in cast(list[dict], d.get("transitions")) + for transition in cast(List[dict], d.get("transitions")) ] def issue_transition(self, issue_key: str, status: str) -> T_resp_json: return self.set_issue_status(issue_key, status) def set_issue_status( - self, issue_key: str, status_name: str, fields: str | dict | None = None, update: dict | None = None + self, issue_key: str, status_name: str, fields: Union[str, dict, None] = None, update: Optional[dict] = None ): """ Setting status by status_name. Field defaults to None for transitions without mandatory fields. @@ -1983,7 +1993,7 @@ def get_issue_status_id(self, issue_key: str) -> str: return (self.get(url) or {}).__getitem__("fields").__getitem__("status").__getitem__("id") def get_issue_transitions_full( - self, issue_key: str, transition_id: T_id | None = None, expand: str | None = None + self, issue_key: str, transition_id: Optional[T_id] = None, expand: Optional[str] = None ) -> T_resp_json: """ Get a list of the transitions possible for this issue by the current user, @@ -2037,7 +2047,7 @@ def delete_issue_property(self, issue_key: str, property_key: str): ) return self.delete(url) - def get_updated_worklogs(self, since: str, expand: str | None = None): + def get_updated_worklogs(self, since: str, expand: Optional[str] = None): """ Returns a list of IDs and update timestamps for worklogs updated after a date and time. :param since: The date and time, as a UNIX timestamp in milliseconds, after which updated worklogs are returned. @@ -2065,7 +2075,7 @@ def get_deleted_worklogs(self, since: str): return self.get(url, params=params) - def get_worklogs(self, ids: list[T_id], expand: str | None = None): + def get_worklogs(self, ids: List[T_id], expand: Optional[str] = None): """ Returns worklog details for a list of worklog IDs. :param expand: Use expand to include additional information about worklogs in the response. @@ -2087,10 +2097,10 @@ def get_worklogs(self, ids: list[T_id], expand: str | None = None): def user( self, - username: str | None = None, - key: str | None = None, - account_id: str | None = None, - expand: str | None = None, + username: Optional[str] = None, + key: Optional[str] = None, + account_id: Optional[str] = None, + expand: Optional[str] = None, ): """ Returns a user. This resource cannot be accessed anonymously. @@ -2138,7 +2148,7 @@ def is_active_user(self, username: str): """ return self.user(username).get("active") - def user_remove(self, username: str | None = None, account_id: str | None = None, key: str | None = None): + def user_remove(self, username: Optional[str] = None, account_id: Optional[str] = None, key: Optional[str] = None): """ Remove user from Jira if this user does not have any activity :param key: @@ -2192,8 +2202,8 @@ def user_create( username: str, email: str, display_name: str, - password: str | None = None, - notification: bool | None = None, + password: Optional[str] = None, + notification: Optional[bool] = None, ): """ Create a user in Jira @@ -2222,7 +2232,7 @@ def user_create( url = self.resource_url("user") return self.post(url, data=data) - def user_properties(self, username: str | None = None, account_id: str | None = None): + def user_properties(self, username: Optional[str] = None, account_id: Optional[str] = None): """ Get user property :param username: @@ -2238,7 +2248,7 @@ def user_properties(self, username: str | None = None, account_id: str | None = return self.get(url) def user_property( - self, username: str | None = None, account_id: str | None = None, key_property: str | None = None + self, username: Optional[str] = None, account_id: Optional[str] = None, key_property: Optional[str] = None ): """ Get user property @@ -2260,10 +2270,10 @@ def user_property( def user_set_property( self, - username: str | None = None, - account_id: str | None = None, - key_property: str | None = None, - value_property: str | dict | None = None, + username: Optional[str] = None, + account_id: Optional[str] = None, + key_property: Optional[str] = None, + value_property: Union[str, dict, None] = None, ): """ Set property for user @@ -2289,7 +2299,7 @@ def user_set_property( return self.put(url, data=value_property) def user_delete_property( - self, username: str | None = None, account_id: str | None = None, key_property: str | None = None + self, username: Optional[str] = None, account_id: Optional[str] = None, key_property: Optional[str] = None ): """ Delete property for user @@ -2390,10 +2400,10 @@ def users_get_all( def user_find_by_user_string( self, - username: str | None = None, - query: str | None = None, - account_id: str | None = None, - property_key: str | None = None, + username: Optional[str] = None, + query: Optional[str] = None, + account_id: Optional[str] = None, + property_key: Optional[str] = None, start: int = 0, limit: int = 50, include_inactive_users: bool = False, @@ -2481,7 +2491,7 @@ def add_user_to_application(self, username: str, application_key: str): Reference: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project """ - def get_user_groups(self, account_id: str | None = None): + def get_user_groups(self, account_id: Optional[str] = None): """ Get groups of a user This API is only available for Jira Cloud platform @@ -2492,10 +2502,10 @@ def get_user_groups(self, account_id: str | None = None): url = self.resource_url("user/groups") return self.get(url, params=params) - def get_all_projects(self, included_archived: bool | None = None, expand: str | None = None): + def get_all_projects(self, included_archived: Optional[bool] = None, expand: Optional[str] = None): return self.projects(included_archived, expand) - def projects(self, included_archived: bool | None = None, expand: str | None = None): + def projects(self, included_archived: Optional[bool] = None, expand: Optional[str] = None): """ Returns all projects which are visible for the currently logged-in user. If no user is logged in, it returns the list of projects that are visible when using anonymous access. @@ -2520,7 +2530,7 @@ def projects(self, included_archived: bool | None = None, expand: str | None = N url = self.resource_url("project") return self.get(url, params=params) - def create_project_from_raw_json(self, json: dict | str): + def create_project_from_raw_json(self, json: Union[str, dict]): """ Creates a new project. { @@ -2578,7 +2588,7 @@ def archive_project(self, key: str): url = "{base_url}/{key}/archive".format(base_url=base_url, key=key) return self.post(url) - def project(self, key: str, expand: str | None = None): + def project(self, key: str, expand: Optional[str] = None): """ Get project with details :param key: @@ -2592,7 +2602,7 @@ def project(self, key: str, expand: str | None = None): url = "{base_url}/{key}".format(base_url=base_url, key=key) return self.get(url, params=params) - def get_project(self, key: str, expand: str | None = None): + def get_project(self, key: str, expand: Optional[str] = None): """ Contains a full representation of a project in JSON format. All project keys associated with the project will only be returned if expand=projectKeys. @@ -2612,7 +2622,7 @@ def get_project_components(self, key: str): url = "{base_url}/{key}/components".format(base_url=base_url, key=key) return self.get(url) - def get_project_versions(self, key: str, expand: str | None = None): + def get_project_versions(self, key: str, expand: Optional[str] = None): """ Contains a full representation of the specified project's versions. :param key: @@ -2629,12 +2639,12 @@ def get_project_versions(self, key: str, expand: str | None = None): def get_project_versions_paginated( self, key: str, - start: int | None = None, - limit: int | None = None, - order_by: str | None = None, - expand: str | None = None, - query: str | None = None, - status: str | None = None, + start: Optional[int] = None, + limit: Optional[int] = None, + order_by: Optional[str] = None, + expand: Optional[str] = None, + query: Optional[str] = None, + status: Optional[str] = None, ): """ Returns all versions for the specified project. Results are paginated. @@ -2707,7 +2717,7 @@ def add_version( url = self.resource_url("version") return self.post(url, data=payload) - def delete_version(self, version: str, moved_fixed: str | None = None, move_affected: str | None = None): + def delete_version(self, version: str, moved_fixed: Optional[str] = None, move_affected: Optional[str] = None): """ Delete version from the project :param int version: the version id to delete @@ -2726,12 +2736,12 @@ def delete_version(self, version: str, moved_fixed: str | None = None, move_affe def update_version( self, version: str, - name: str | None = None, - description: str | None = None, - is_archived: bool | None = None, - is_released: bool | None = None, - start_date: str | None = None, - release_date: str | None = None, + name: Optional[str] = None, + description: Optional[str] = None, + is_archived: Optional[bool] = None, + is_released: Optional[bool] = None, + start_date: Optional[str] = None, + release_date: Optional[str] = None, ): """ Update a project version @@ -2755,7 +2765,7 @@ def update_version( url = "{base_url}/{version}".format(base_url=base_url, version=version) return self.put(url, data=payload) - def move_version(self, version: T_id, after: T_id | None = None, position: str | None = None): + def move_version(self, version: T_id, after: Optional[T_id] = None, position: Optional[str] = None): """ Reposition a project version :param version: The version id to move @@ -2799,7 +2809,11 @@ def get_project_actors_for_role_project(self, project_key: str, role_id: T_id): return (self.get(url) or {}).get("actors") def delete_project_actors( - self, project_key: str, role_id: T_id, actor: str, actor_type: Literal["user"] | Literal["group"] | None = None + self, + project_key: str, + role_id: T_id, + actor: str, + actor_type: Union[Literal["user"], Literal["group"], None] = None, ): """ Deletes actors (users or groups) from a project role. @@ -2851,7 +2865,7 @@ def add_project_actor_in_role(self, project_key: str, role_id: T_id, actor: str, return self.post(url, data=data) - def update_project(self, project_key: str, data: dict, expand: str | None = None): + def update_project(self, project_key: str, data: dict, expand: Optional[str] = None): """ Updates a project. Only non-null values sent in JSON will be updated in the project. @@ -2870,7 +2884,7 @@ def update_project(self, project_key: str, data: dict, expand: str | None = None return self.put(url, data, params=params) def update_project_category_for_project( - self, project_key: str, new_project_category_id: T_id, expand: str | None = None + self, project_key: str, new_project_category_id: T_id, expand: Optional[str] = None ): """ Updates a project. @@ -2926,7 +2940,7 @@ def get_all_notification_schemes(self): """ return self.get_notification_schemes().get("values") or [] - def get_notification_scheme(self, notification_scheme_id: T_id, expand: str | None = None): + def get_notification_scheme(self, notification_scheme_id: T_id, expand: Optional[str] = None): """ Returns a full representation of the notification scheme for the given id. Use 'expand' to get details @@ -3005,7 +3019,7 @@ def assign_project_permission_scheme(self, project_id_or_key: str, permission_sc data = {"id": permission_scheme_id} return self.put(url, data=data) - def get_project_permission_scheme(self, project_id_or_key: str, expand: str | None = None): + def get_project_permission_scheme(self, project_id_or_key: str, expand: Optional[str] = None): """ Gets a permission scheme assigned with a project Use 'expand' to get details @@ -3093,7 +3107,7 @@ def get_project_issuekey_last(self, project: str): return (cast(dict, response).__getitem__("issues") or {"key": None})[0]["key"] def get_project_issuekey_all( - self, project: str, start: int = 0, limit: int | None = None, expand: str | None = None + self, project: str, start: int = 0, limit: Optional[int] = None, expand: Optional[str] = None ): jql = 'project = "{project}" ORDER BY issuekey ASC'.format(project=project) response = self.jql(jql, start=start, limit=limit, expand=expand) @@ -3109,7 +3123,7 @@ def get_project_issues_count(self, project: str): return cast(dict, response)["total"] def get_all_project_issues( - self, project: str, fields: str | list[str] = "*all", start: int = 0, limit: int | None = None + self, project: str, fields: Union[str, List[str]] = "*all", start: int = 0, limit: Optional[int] = None ): """ Get the Issues for a Project @@ -3144,7 +3158,7 @@ def get_all_assignable_users_for_project(self, project_key: str, start: int = 0, return self.get(url) def get_assignable_users_for_issue( - self, issue_key: str, username: str | None = None, start: int = 0, limit: int = 50 + self, issue_key: str, username: Optional[str] = None, start: int = 0, limit: int = 50 ) -> T_resp_json: """ Provide assignable users for issue @@ -3197,7 +3211,7 @@ def get_time_tracking_settings(self) -> T_resp_json: url = self.resource_url("configuration/timetracking/options") return self.get(url) - def get_transition_id_to_status_name(self, issue_key: str, status_name: str) -> int | None: + def get_transition_id_to_status_name(self, issue_key: str, status_name: str) -> Optional[int]: for transition in self.get_issue_transitions(issue_key): if status_name.lower() == transition["to"].lower(): return int(transition["id"]) @@ -3290,7 +3304,7 @@ def create_issue_link_type_by_json(self, data: dict) -> T_resp_json: url = self.resource_url("issueLinkType") return self.post(url, data=data) - def create_issue_link_type(self, link_type_name: str, inward: str, outward: str) -> T_resp_json | str: + def create_issue_link_type(self, link_type_name: str, inward: str, outward: str) -> Union[T_resp_json, str]: """Create a new issue link type. :param outward: :param inward: @@ -3446,11 +3460,11 @@ def add_field(self, field_id: T_id, screen_id: T_id, tab_id: T_id) -> T_resp_jso def jql( self, jql: str, - fields: str | list[str] = "*all", + fields: Union[str, List[str]] = "*all", start: int = 0, - limit: int | None = None, - expand: str | None = None, - validate_query: str | None = None, + limit: Optional[int] = None, + expand: Optional[str] = None, + validate_query: Optional[str] = None, ) -> T_resp_json: """ Get issues from jql search result with all related fields @@ -3484,11 +3498,11 @@ def jql( def jql_get_list_of_tickets( self, jql: str, - fields: str | dict = "*all", + fields: Union[str, dict] = "*all", start: int = 0, - limit: int | None = None, - expand: str | None = None, - validate_query: str | None = None, + limit: Optional[int] = None, + expand: Optional[str] = None, + validate_query: Optional[str] = None, ) -> list: """ Get issues from jql search result with all related fields @@ -3516,7 +3530,7 @@ def jql_get_list_of_tickets( params["validateQuery"] = validate_query url = self.resource_url("search") - results: list[object] = [] + results: List[object] = [] while True: params["startAt"] = int(start) response = self.get(url, params=params) @@ -3539,8 +3553,8 @@ def csv( jql: str, limit: int = 1000, all_fields: bool = True, - start: int | None = None, - delimiter: str | None = None, + start: Optional[int] = None, + delimiter: Optional[str] = None, ) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields @@ -3573,7 +3587,7 @@ def csv( headers={"Accept": "application/csv"}, ) - def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int | None = None) -> bytes: + def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: Optional[int] = None) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields default will be to return all fields @@ -3603,7 +3617,7 @@ def excel(self, jql: str, limit: int = 1000, all_fields: bool = True, start: int ) def export_html( - self, jql: str, limit: int | None = None, all_fields: bool = True, start: int | None = None + self, jql: str, limit: Optional[int] = None, all_fields: bool = True, start: Optional[int] = None ) -> bytes: """ Get issues from jql search result with ALL or CURRENT fields @@ -3666,10 +3680,10 @@ def get_all_workflows(self) -> T_resp_json: def get_workflows_paginated( self, - start_at: int | None = None, - max_results: int | None = None, - workflow_name: str | None = None, - expand: str | None = None, + start_at: Optional[int] = None, + max_results: Optional[int] = None, + workflow_name: Optional[str] = None, + expand: Optional[str] = None, ) -> T_resp_json: """ Provide all workflows paginated (see https://developer.atlassian.com/cloud/jira/platform/rest/v2/\ @@ -3800,7 +3814,7 @@ def enable_plugin(self, plugin_key: str) -> T_resp_json: data = {"status": "enabled"} return self.put(url, data=data, headers=app_headers) - def get_all_permissionschemes(self, expand: str | None = None): + def get_all_permissionschemes(self, expand: Optional[str] = None): """ Returns a list of all permission schemes. By default, only shortened beans are returned. @@ -3816,7 +3830,7 @@ def get_all_permissionschemes(self, expand: str | None = None): params["expand"] = expand return (self.get(url, params=params) or {}).get("permissionSchemes") - def get_permissionscheme(self, permission_id: T_id, expand: str | None = None) -> T_resp_json: + def get_permissionscheme(self, permission_id: T_id, expand: Optional[str] = None) -> T_resp_json: """ Returns a list of all permission schemes. By default, only shortened beans are returned. @@ -3923,7 +3937,7 @@ def get_project_issue_security_scheme(self, project_id_or_key: int, only_levels: return response.get("levels") or None return response - def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: str | None = None) -> T_resp_json: + def get_all_priority_schemes(self, start: int = 0, limit: int = 100, expand: Optional[str] = None) -> T_resp_json: """ Returns all priority schemes. All project keys associated with the priority scheme will only be returned @@ -3968,7 +3982,7 @@ def create_priority_scheme(self, data: dict) -> T_resp_json: https://docs.atlassian.com/software/jira/docs/api/REST/8.5.0/#api/2/project/{projectKeyOrId}/priorityscheme """ - def get_priority_scheme_of_project(self, project_key_or_id: str, expand: str | None = None) -> T_resp_json: + def get_priority_scheme_of_project(self, project_key_or_id: str, expand: Optional[str] = None) -> T_resp_json: """ Gets a full representation of a priority scheme in JSON format used by specified project. Resource for associating priority scheme schemes and projects. @@ -4199,7 +4213,9 @@ def get_server_info(self, do_health_check: bool = False) -> T_resp_json: ####################################################################### # Tempo Account REST API implements ####################################################################### - def tempo_account_get_accounts(self, skip_archived: bool | None = None, expand: str | None = None) -> T_resp_json: + def tempo_account_get_accounts( + self, skip_archived: Optional[bool] = None, expand: Optional[str] = None + ) -> T_resp_json: """ Get all Accounts that the logged-in user has permission to browse. :param skip_archived: bool OPTIONAL: skip archived Accounts, either true or false, default value true. @@ -4260,7 +4276,7 @@ def tempo_account_associate_with_jira_project( url = "rest/tempo-accounts/1/link/" return self.post(url, data=data) - def tempo_account_add_account(self, data: dict | None = None) -> T_resp_json | str: + def tempo_account_add_account(self, data: Optional[dict] = None) -> Union[T_resp_json, str]: """ Creates Account, adding new Account requires the Manage Accounts Permission. :param data: String then it will convert to json @@ -4306,7 +4322,9 @@ def tempo_account_get_all_account_by_customer_id(self, customer_id: T_id) -> T_r url = "rest/tempo-accounts/1/account/customer/{customerId}/".format(customerId=customer_id) return self.get(url) - def tempo_account_get_customers(self, query: str | None = None, count_accounts: bool | None = None) -> T_resp_json: + def tempo_account_get_customers( + self, query: Optional[str] = None, count_accounts: Optional[bool] = None + ) -> T_resp_json: """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4334,7 +4352,7 @@ def tempo_account_add_new_customer(self, key: str, name: str) -> T_resp_json: url = "rest/tempo-accounts/1/customer" return self.post(url, data=data) - def tempo_account_add_customer(self, data: dict | None = None) -> T_resp_json | str: + def tempo_account_add_customer(self, data: Optional[dict] = None) -> Union[T_resp_json, str]: """ Gets all or some Attribute whose key or name contain a specific substring. Attributes can be a Category or Customer. @@ -4358,7 +4376,9 @@ def tempo_account_get_customer_by_id(self, customer_id: T_id = 1) -> T_resp_json url = "rest/tempo-accounts/1/customer/{id}".format(id=customer_id) return self.get(url) - def tempo_account_update_customer_by_id(self, customer_id: T_id = 1, data: dict | None = None) -> T_resp_json | str: + def tempo_account_update_customer_by_id( + self, customer_id: T_id = 1, data: Optional[dict] = None + ) -> Union[T_resp_json, str]: """ Updates an Attribute. Caller must have Manage Account Permission. Attribute can be a Category or Customer. :param customer_id: id of Customer record @@ -4472,7 +4492,7 @@ def tempo_timesheets_get_configuration(self) -> T_resp_json: return self.get(url) def tempo_timesheets_get_team_utilization( - self, team_id: T_id, date_from: str, date_to: str | None = None, group_by: str | None = None + self, team_id: T_id, date_from: str, date_to: Optional[str] = None, group_by: Optional[str] = None ) -> T_resp_json: """ Get team utilization. Response in json @@ -4491,12 +4511,12 @@ def tempo_timesheets_get_team_utilization( def tempo_timesheets_get_worklogs( self, - date_from: str | None = None, - date_to: str | None = None, - username: str | None = None, - project_key: str | None = None, - account_key: str | None = None, - team_id: T_id | None = None, + date_from: Optional[str] = None, + date_to: Optional[str] = None, + username: Optional[str] = None, + project_key: Optional[str] = None, + account_key: Optional[str] = None, + team_id: Optional[T_id] = None, ) -> T_resp_json: """ @@ -4526,7 +4546,7 @@ def tempo_timesheets_get_worklogs( # noinspection PyIncorrectDocstring def tempo_4_timesheets_find_worklogs( - self, date_from: str | None = None, date_to: str | None = None, **params: Any + self, date_from: Optional[str] = None, date_to: Optional[str] = None, **params: Any ) -> T_resp_json: """ Find existing worklogs with searching parameters. @@ -4572,7 +4592,7 @@ def tempo_timesheets_get_worklogs_by_issue(self, issue: str) -> T_resp_json: return self.get(url) def tempo_timesheets_write_worklog( - self, worker: str, started: str, time_spend_in_seconds: int, issue_id: T_id, comment: str | None = None + self, worker: str, started: str, time_spend_in_seconds: int, issue_id: T_id, comment: Optional[str] = None ) -> T_resp_json: """ Log work for user @@ -4654,7 +4674,7 @@ def tempo_get_default_link_to_project(self, project_id: T_id) -> T_resp_json: url = "rest/tempo-accounts/1/link/project/{}/default/".format(project_id) return self.get(url) - def tempo_teams_get_all_teams(self, expand: str | None = None) -> T_resp_json: + def tempo_teams_get_all_teams(self, expand: Optional[str] = None) -> T_resp_json: url = "rest/tempo-teams/2/team" params: dict = {} if expand: @@ -4781,7 +4801,7 @@ def get_agile_board_by_filter_id(self, filter_id: T_id) -> T_resp_json: return self.get(url) # /rest/agile/1.0/board - def create_agile_board(self, name: str, type: str, filter_id: T_id, location: dict | None = None) -> T_resp_json: + def create_agile_board(self, name: str, type: str, filter_id: T_id, location: Optional[dict] = None) -> T_resp_json: """ Create an agile board :param name: str: Must be less than 255 characters. @@ -4797,9 +4817,9 @@ def create_agile_board(self, name: str, type: str, filter_id: T_id, location: di def get_all_agile_boards( self, - board_name: str | None = None, - project_key: str | None = None, - board_type: str | None = None, + board_name: Optional[str] = None, + project_key: Optional[str] = None, + board_type: Optional[str] = None, start: int = 0, limit: int = 50, ) -> T_resp_json: @@ -4891,8 +4911,8 @@ def get_issues_for_board( jql: str, fields: str = "*all", start: int = 0, - limit: int | None = None, - expand: str | None = None, + limit: Optional[int] = None, + expand: Optional[str] = None, ) -> T_resp_json: """ Returns all issues from a board, for a given board Id. @@ -5159,7 +5179,7 @@ def set_agile_board_refined_velocity(self, board_id: T_id, data: dict) -> T_resp # /rest/agile/1.0/board/{boardId}/sprint def get_all_sprints_from_board( - self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50 + self, board_id: T_id, state: Optional[str] = None, start: int = 0, limit: int = 50 ) -> T_resp_json: """ Returns all sprints from a board, for a given board ID. @@ -5187,7 +5207,9 @@ def get_all_sprints_from_board( return self.get(url, params=params) @deprecated(version="3.42.0", reason="Use get_all_sprints_from_board instead") - def get_all_sprint(self, board_id: T_id, state: str | None = None, start: int = 0, limit: int = 50) -> T_resp_json: + def get_all_sprint( + self, board_id: T_id, state: Optional[str] = None, start: int = 0, limit: int = 50 + ) -> T_resp_json: """ Returns all sprints from a board, for a given board ID. :param board_id: @@ -5284,9 +5306,9 @@ def create_sprint( self, name: str, board_id: T_id, - start_date: str | None = None, - end_date: str | None = None, - goal: str | None = None, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + goal: Optional[str] = None, ) -> T_resp_json: """ Create a sprint within a board. @@ -5314,7 +5336,7 @@ def create_sprint( data["goal"] = goal return self.post(url, data=data) - def add_issues_to_sprint(self, sprint_id: T_id, issues: list[str]) -> T_resp_json: + def add_issues_to_sprint(self, sprint_id: T_id, issues: List[str]) -> T_resp_json: """ Adding Issue(s) to Sprint :param sprint_id: int/str: The ID for the Sprint. @@ -5448,7 +5470,7 @@ def dvcs_update_linked_repo_with_remote(self, repository_id: T_id) -> T_resp_jso url = "rest/bitbucket/1.0/repositories/{}/sync".format(repository_id) return self.post(url) - def flag_issue(self, issue_keys: list[T_id], flag: bool = True) -> T_resp_json: + def flag_issue(self, issue_keys: List[T_id], flag: bool = True) -> T_resp_json: """ Flags or un-flags one or multiple issues in Jira with a flag indicator. :param issue_keys: List of issue keys to flag or un-flag. diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 1c0945d3a..37ad428b5 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -1,37 +1,39 @@ # coding=utf-8 -from __future__ import annotations - import logging from json import dumps -from typing import TYPE_CHECKING, Literal, MutableMapping, overload +from typing import ( + List, + MutableMapping, + Optional, + Tuple, + Union, + overload, +) import requests from requests.adapters import HTTPAdapter +from typing_extensions import Literal from atlassian.typehints import T_resp_json - try: from oauthlib.oauth1.rfc5849 import SIGNATURE_RSA_SHA512 as SIGNATURE_RSA except ImportError: from oauthlib.oauth1 import SIGNATURE_RSA +from http.cookiejar import CookieJar + +import urllib3 from requests import HTTPError, Response, Session from requests_oauthlib import OAuth1, OAuth2 from six.moves.urllib.parse import urlencode +from typing_extensions import Self from urllib3.util import Retry -import urllib3 from atlassian.request_utils import get_default_logger -if TYPE_CHECKING: - from http.cookiejar import CookieJar - - from typing_extensions import Self - - -T_resp = Response | T_resp_json -T_resp_get = Response | T_resp_json | str | bytes +T_resp = Union[Response, T_resp_json] +T_resp_get = Union[Response, T_resp_json, str, bytes] log = get_default_logger(__name__) @@ -65,24 +67,24 @@ class AtlassianRestAPI(object): def __init__( self, url: str, - username: str | None = None, - password: str | None = None, + username: Optional[str] = None, + password: Optional[str] = None, timeout: int = 75, api_root: str = "rest/api", - api_version: str | int = "latest", + api_version: Union[str, int] = "latest", verify_ssl: bool = True, - session: requests.Session | None = None, - oauth: dict | None = None, - oauth2: dict | None = None, - cookies: CookieJar | None = None, - advanced_mode: bool | None = None, + session: Optional[requests.Session] = None, + oauth: Optional[dict] = None, + oauth2: Optional[dict] = None, + cookies: Optional[CookieJar] = None, + advanced_mode: Optional[bool] = None, kerberos: object = None, cloud: bool = False, - proxies: MutableMapping[str, str] | None = None, - token: str | None = None, - cert: str | tuple[str, str] | None = None, + proxies: Optional[MutableMapping[str, str]] = None, + token: Optional[str] = None, + cert: Union[str, Tuple[str, str], None] = None, backoff_and_retry: bool = False, - retry_status_codes: list[int] = [413, 429, 503], + retry_status_codes: List[int] = [413, 429, 503], max_backoff_seconds: int = 1800, max_backoff_retries: int = 1000, ): @@ -225,8 +227,8 @@ def log_curl_debug( self, method: str, url: str, - data: dict | str | None = None, - headers: dict | None = None, + data: Union[dict, str, None] = None, + headers: Optional[dict] = None, level: int = logging.DEBUG, ) -> None: """ @@ -247,7 +249,9 @@ def log_curl_debug( ) log.log(level=level, msg=message) - def resource_url(self, resource: str, api_root: str | None = None, api_version: str | int | None = None) -> str: + def resource_url( + self, resource: str, api_root: Optional[str] = None, api_version: Union[str, int, None] = None + ) -> str: if api_root is None: api_root = self.api_root if api_version is None: @@ -255,7 +259,7 @@ def resource_url(self, resource: str, api_root: str | None = None, api_version: return "/".join(str(s).strip("/") for s in [api_root, api_version, resource] if s is not None) @staticmethod - def url_joiner(url: str | None, path: str, trailing: bool | None = None) -> str: + def url_joiner(url: Optional[str], path: str, trailing: Optional[bool] = None) -> str: url_link = "/".join(str(s).strip("/") for s in [url, path] if s is not None) if trailing: url_link += "/" @@ -268,13 +272,13 @@ def request( self, method: str = "GET", path: str = "/", - data: dict | str | None = None, - json: dict | str | None = None, - flags: list | None = None, - params: dict | None = None, - headers: dict | None = None, - files: dict | None = None, - trailing: bool | None = None, + data: Union[dict, str, None] = None, + json: Union[dict, str, None] = None, + flags: Optional[list] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + files: Optional[dict] = None, + trailing: Optional[bool] = None, absolute: bool = False, advanced_mode: bool = False, ) -> Response: @@ -342,13 +346,13 @@ def request( def get( self, path: str, - data: dict | str | None = ..., - flags: list | None = ..., - params: dict | None = ..., - headers: dict | None = ..., + data: Union[dict, str, None] = ..., + flags: Optional[list] = ..., + params: Optional[dict] = ..., + headers: Optional[dict] = ..., *, not_json_response: Literal[True], - trailing: bool | None = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: Literal[True], ) -> bytes: @@ -359,13 +363,13 @@ def get( def get( self, path: str, - data: dict | str | None = ..., - flags: list | None = ..., - params: dict | None = ..., - headers: dict | None = ..., + data: Union[dict, str, None] = ..., + flags: Optional[list] = ..., + params: Optional[dict] = ..., + headers: Optional[dict] = ..., *, not_json_response: Literal[True], - trailing: bool | None = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: bool = ..., ) -> bytes: @@ -376,12 +380,12 @@ def get( def get( self, path: str, - data: dict | str | None = ..., - flags: list | None = ..., - params: dict | None = ..., - headers: dict | None = ..., - not_json_response: Literal[False] | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + flags: Optional[list] = ..., + params: Optional[dict] = ..., + headers: Optional[dict] = ..., + not_json_response: Optional[Literal[False]] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., *, advanced_mode: Literal[True], @@ -393,12 +397,12 @@ def get( def get( self, path: str, - data: dict | str | None = ..., - flags: list | None = ..., - params: dict | None = ..., - headers: dict | None = ..., - not_json_response: Literal[False] | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + flags: Optional[list] = ..., + params: Optional[dict] = ..., + headers: Optional[dict] = ..., + not_json_response: Optional[Literal[False]] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: @@ -409,12 +413,12 @@ def get( def get( self, path: str, - data: dict | str | None = ..., - flags: list | None = ..., - params: dict | None = ..., - headers: dict | None = ..., - not_json_response: bool | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + flags: Optional[list] = ..., + params: Optional[dict] = ..., + headers: Optional[dict] = ..., + not_json_response: Optional[bool] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: bool = ..., ) -> T_resp_get: @@ -423,12 +427,12 @@ def get( def get( self, path: str, - data: dict | str | None = None, - flags: list | None = None, - params: dict | None = None, - headers: dict | None = None, - not_json_response: bool | None = None, - trailing: bool | None = None, + data: Union[dict, str, None] = None, + flags: Optional[list] = None, + params: Optional[dict] = None, + headers: Optional[dict] = None, + not_json_response: Optional[bool] = None, + trailing: Optional[bool] = None, absolute: bool = False, advanced_mode: bool = False, ) -> T_resp_get: @@ -474,13 +478,13 @@ def get( def post( self, path: str, - data: dict | str, + data: Union[dict, str], *, - json: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + json: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: @@ -490,12 +494,12 @@ def post( def post( self, path: str, - data: dict | str | None = ..., - json: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + json: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., *, advanced_mode: Literal[False] = ..., @@ -506,12 +510,12 @@ def post( def post( self, path: str, - data: dict | str | None = ..., - json: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + json: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: @@ -522,12 +526,12 @@ def post( def post( self, path: str, - data: dict | str | None = ..., - json: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + json: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., *, advanced_mode: Literal[True], @@ -539,29 +543,29 @@ def post( def post( self, path: str, - data: dict | str | None = ..., - json: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + json: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> Response | dict | None: + ) -> Union[Response, dict, None]: ... def post( self, path: str, - data: dict | str | None = None, - json: dict | str | None = None, - headers: dict | None = None, - files: dict | None = None, - params: dict | None = None, - trailing: bool | None = None, + data: Union[dict, str, None] = None, + json: Union[dict, str, None] = None, + headers: Optional[dict] = None, + files: Optional[dict] = None, + params: Optional[dict] = None, + trailing: Optional[bool] = None, absolute: bool = False, advanced_mode: bool = False, - ) -> Response | dict | None: + ) -> Union[Response, dict, None]: """ :param path: :param data: @@ -595,11 +599,11 @@ def post( def put( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - trailing: bool | None = ..., - params: dict | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + trailing: Optional[bool] = ..., + params: Optional[dict] = ..., absolute: bool = ..., *, advanced_mode: Literal[False], @@ -610,11 +614,11 @@ def put( def put( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - trailing: bool | None = ..., - params: dict | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + trailing: Optional[bool] = ..., + params: Optional[dict] = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: @@ -625,11 +629,11 @@ def put( def put( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - trailing: bool | None = ..., - params: dict | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + trailing: Optional[bool] = ..., + params: Optional[dict] = ..., absolute: bool = ..., *, advanced_mode: Literal[True], @@ -641,27 +645,27 @@ def put( def put( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - files: dict | None = ..., - trailing: bool | None = ..., - params: dict | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + files: Optional[dict] = ..., + trailing: Optional[bool] = ..., + params: Optional[dict] = ..., absolute: bool = ..., advanced_mode: bool = ..., - ) -> Response | dict | None: + ) -> Union[Response, dict, None]: ... def put( self, path: str, - data: dict | str | None = None, - headers: dict | None = None, - files: dict | None = None, - trailing: bool | None = None, - params: dict | None = None, + data: Union[dict, str, None] = None, + headers: Optional[dict] = None, + files: Optional[dict] = None, + trailing: Optional[bool] = None, + params: Optional[dict] = None, absolute: bool = False, advanced_mode: bool = False, - ) -> Response | dict | None: + ) -> Union[Response, dict, None]: """ :param path: Path of request :param data: @@ -696,11 +700,11 @@ def put( def patch( self, path: str, - data: dict | str | None = None, - headers: dict | None = None, - files: dict | None = None, - trailing: bool | None = None, - params: dict | None = None, + data: Union[dict, str, None] = None, + headers: Optional[dict] = None, + files: Optional[dict] = None, + trailing: Optional[bool] = None, + params: Optional[dict] = None, absolute: bool = False, advanced_mode: bool = False, ) -> T_resp: @@ -735,10 +739,10 @@ def patch( def delete( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., *, advanced_mode: Literal[False], @@ -749,10 +753,10 @@ def delete( def delete( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: @@ -763,10 +767,10 @@ def delete( def delete( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., *, advanced_mode: Literal[True], @@ -778,10 +782,10 @@ def delete( def delete( self, path: str, - data: dict | str | None = ..., - headers: dict | None = ..., - params: dict | None = ..., - trailing: bool | None = ..., + data: Union[dict, str, None] = ..., + headers: Optional[dict] = ..., + params: Optional[dict] = ..., + trailing: Optional[bool] = ..., absolute: bool = ..., advanced_mode: bool = ..., ) -> T_resp: @@ -790,10 +794,10 @@ def delete( def delete( self, path: str, - data: dict | str | None = None, - headers: dict | None = None, - params: dict | None = None, - trailing: bool | None = None, + data: Union[dict, str, None] = None, + headers: Optional[dict] = None, + params: Optional[dict] = None, + trailing: Optional[bool] = None, absolute: bool = False, advanced_mode: bool = False, ) -> T_resp: diff --git a/atlassian/typehints.py b/atlassian/typehints.py index 4ec474fd9..6fb69ec03 100644 --- a/atlassian/typehints.py +++ b/atlassian/typehints.py @@ -1,5 +1,6 @@ +from typing import Union from typing_extensions import TypeAlias -T_id: TypeAlias = str | int -_Data: TypeAlias = dict | str -T_resp_json: TypeAlias = dict | None +T_id: TypeAlias = Union[str, int] +_Data: TypeAlias = Union[dict, str] +T_resp_json: TypeAlias = Union[dict, None] From 08a4c6479abd6836ef7d070992796267f70973b3 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 12:48:10 -0500 Subject: [PATCH 11/15] future annotations aren't available in 3.6 --- .../cloud/repositories/repositoryVariables.py | 3 +-- atlassian/jira.py | 18 ++++++++---------- tests/test_confluence_advanced_mode.py | 2 -- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/atlassian/bitbucket/cloud/repositories/repositoryVariables.py b/atlassian/bitbucket/cloud/repositories/repositoryVariables.py index fbb21ebeb..faeaacb04 100644 --- a/atlassian/bitbucket/cloud/repositories/repositoryVariables.py +++ b/atlassian/bitbucket/cloud/repositories/repositoryVariables.py @@ -1,5 +1,4 @@ # coding=utf-8 -from __future__ import annotations from ..base import BitbucketCloudBase @@ -7,7 +6,7 @@ class RepositoryVariables(BitbucketCloudBase): def __init__(self, url, *args, **kwargs): super(RepositoryVariables, self).__init__(url, *args, **kwargs) - def __get_object(self, data) -> RepositoryVariable: + def __get_object(self, data) -> "RepositoryVariable": return RepositoryVariable( self.url_joiner(self.url, data["uuid"]), data, diff --git a/atlassian/jira.py b/atlassian/jira.py index e298cc2d1..4b9abf652 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -1,19 +1,17 @@ # coding=utf-8 -from typing_extensions import Literal import logging import os import re -from typing import TYPE_CHECKING, Any, BinaryIO, cast, Optional, Union, Dict, List +from typing import Any, BinaryIO, Dict, List, Optional, Union, cast from warnings import warn from deprecated import deprecated from requests import HTTPError, Response +from typing_extensions import Literal from .errors import ApiNotFoundError, ApiPermissionError from .rest_client import AtlassianRestAPI - -if TYPE_CHECKING: - from .typehints import T_id, T_resp_json +from .typehints import T_id, T_resp_json log = logging.getLogger(__name__) @@ -234,7 +232,7 @@ def get_application_role(self, role_key: str) -> T_resp_json: def get_attachments_ids_from_issue(self, issue: T_id) -> List[Dict[str, str]]: """ Get attachments IDs from jira issue - :param jira issue key: str + :param issue: str jira issue key :return: list of integers attachment IDs """ issue_id = self.get_issue(issue)["fields"]["attachment"] @@ -512,7 +510,7 @@ def issue_get_comments(self, issue_id: T_id) -> T_resp_json: def issues_get_comments_by_id(self, *args: int) -> T_resp_json: """ Get Comments on Multiple Issues - :param *args: int Issue ID's + :param args: int Issue ID's :raises: requests.exceptions.HTTPError :return: """ @@ -1431,7 +1429,7 @@ def get_issue_labels(self, issue_key: str) -> T_resp_json: def update_issue(self, issue_key: T_id, update: Union[str, dict]) -> T_resp_json: """ - :param issue: the issue to update + :param issue_key: the issue to update :param update: the update to make :return: True if successful, False if not """ @@ -1440,7 +1438,7 @@ def update_issue(self, issue_key: T_id, update: Union[str, dict]) -> T_resp_json def label_issue(self, issue_key: T_id, labels: list): """ - :param issue: the issue to update + :param issue_key: the issue to update :param labels: the labels to add :return: True if successful, False if not """ @@ -1485,7 +1483,7 @@ def issue_exists(self, issue_key: str) -> Optional[bool]: original_value = self.advanced_mode self.advanced_mode = True try: - resp = self.issue(issue_key, fields="*none") + resp = cast(Response, self.issue(issue_key, fields="*none")) if resp.status_code == 404: log.info('Issue "%s" does not exists', issue_key) return False diff --git a/tests/test_confluence_advanced_mode.py b/tests/test_confluence_advanced_mode.py index 521ca70ce..424062398 100644 --- a/tests/test_confluence_advanced_mode.py +++ b/tests/test_confluence_advanced_mode.py @@ -1,6 +1,4 @@ # coding=utf-8 -from __future__ import annotations - import json import os import unittest From bf4b4b920bd23c90885890725b7a6fe23b69871f Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 13:15:25 -0500 Subject: [PATCH 12/15] looks like bandit is not able to be installed in python3.6 --- requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4bdd7b908..18338caac 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,7 +12,6 @@ codecov python-magic pylint mypy>=0.812 -bandit doc8 types-Deprecated types-requests From 3639db7c517e3321a2a0ffd3a14a021313a88bca Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 13:40:32 -0500 Subject: [PATCH 13/15] lets see if the progress bar is causing problems --- Dockerfile.qa | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.qa b/Dockerfile.qa index 310c015dd..bd814dbe6 100644 --- a/Dockerfile.qa +++ b/Dockerfile.qa @@ -57,6 +57,7 @@ RUN apt-get clean WORKDIR /atlassian-python-api COPY requirements.txt . COPY requirements-dev.txt . +RUN python3 -m pip config --user set global.progress_bar off RUN python3 -m pip install --no-cache-dir --upgrade setuptools RUN python3 -m pip install --no-cache-dir --upgrade pip RUN python3 -m pip install --no-cache-dir --upgrade wheel From 13161bb009d764b0d0d465449f0927587427f6b3 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 17:02:09 -0500 Subject: [PATCH 14/15] ignoring the list return vs the typical dictionary --- atlassian/jira.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/atlassian/jira.py b/atlassian/jira.py index 4b9abf652..20bfbea61 100644 --- a/atlassian/jira.py +++ b/atlassian/jira.py @@ -3401,7 +3401,7 @@ def get_all_available_screen_fields(self, screen_id: T_id) -> T_resp_json: url = "{base_url}/{screen_id}/availableFields".format(base_url=base_url, screen_id=screen_id) return self.get(url) - def get_screen_tabs(self, screen_id: T_id) -> list: + def get_screen_tabs(self, screen_id: T_id) -> Optional[list]: """ Get tabs for the screen id :param screen_id: @@ -3409,9 +3409,9 @@ def get_screen_tabs(self, screen_id: T_id) -> list: """ base_url = self.resource_url("screens") url = "{base_url}/{screen_id}/tabs".format(base_url=base_url, screen_id=screen_id) - return self.get(url) + return self.get(url) # type: ignore[return-value] - def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id) -> list: + def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id) -> Optional[list]: """ Get fields by the tab id and the screen id :param tab_id: @@ -3422,7 +3422,7 @@ def get_screen_tab_fields(self, screen_id: T_id, tab_id: T_id) -> list: url = "{base_url}/{screen_id}/tabs/{tab_id}/fields".format( base_url=base_url, screen_id=screen_id, tab_id=tab_id ) - return self.get(url) + return self.get(url) # type: ignore[return-value] def get_all_screen_fields(self, screen_id: T_id) -> list: """ @@ -3430,12 +3430,12 @@ def get_all_screen_fields(self, screen_id: T_id) -> list: :param screen_id: :return: """ - screen_tabs = self.get_screen_tabs(screen_id) + screen_tabs = self.get_screen_tabs(screen_id) or [] fields: list = [] for screen_tab in screen_tabs: tab_id = screen_tab["id"] if tab_id: - tab_fields = self.get_screen_tab_fields(screen_id=screen_id, tab_id=tab_id) + tab_fields = self.get_screen_tab_fields(screen_id=screen_id, tab_id=tab_id) or [] fields = fields + tab_fields return fields From 50dd19f063c732cd7fcb0069a3a742ad82f2d878 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 12 Jun 2024 17:21:47 -0500 Subject: [PATCH 15/15] avoiding black formatter inconsistencies between 23.3 and 24.x --- atlassian/rest_client.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/atlassian/rest_client.py b/atlassian/rest_client.py index 37ad428b5..6ea6bcc57 100644 --- a/atlassian/rest_client.py +++ b/atlassian/rest_client.py @@ -356,7 +356,7 @@ def get( absolute: bool = ..., advanced_mode: Literal[True], ) -> bytes: - ... + ... # fmt: skip # not_json_response True @overload @@ -373,7 +373,7 @@ def get( absolute: bool = ..., advanced_mode: bool = ..., ) -> bytes: - ... + ... # fmt: skip # advanced mode True @overload @@ -390,7 +390,7 @@ def get( *, advanced_mode: Literal[True], ) -> Response: - ... + ... # fmt: skip # both False @overload @@ -406,7 +406,7 @@ def get( absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip # basic overall case @overload @@ -422,7 +422,7 @@ def get( absolute: bool = ..., advanced_mode: bool = ..., ) -> T_resp_get: - ... + ... # fmt: skip def get( self, @@ -488,7 +488,7 @@ def post( absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip @overload def post( @@ -504,7 +504,7 @@ def post( *, advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip @overload def post( @@ -519,7 +519,7 @@ def post( absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip # advanced True @overload @@ -536,7 +536,7 @@ def post( *, advanced_mode: Literal[True], ) -> Response: - ... + ... # fmt: skip # basic overall case @overload @@ -552,7 +552,7 @@ def post( absolute: bool = ..., advanced_mode: bool = ..., ) -> Union[Response, dict, None]: - ... + ... # fmt: skip def post( self, @@ -608,7 +608,7 @@ def put( *, advanced_mode: Literal[False], ) -> T_resp_json: - ... + ... # fmt: skip @overload def put( @@ -622,7 +622,7 @@ def put( absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip # advanced True @overload @@ -638,7 +638,7 @@ def put( *, advanced_mode: Literal[True], ) -> Response: - ... + ... # fmt: skip # basic overall case @overload @@ -653,7 +653,7 @@ def put( absolute: bool = ..., advanced_mode: bool = ..., ) -> Union[Response, dict, None]: - ... + ... # fmt: skip def put( self, @@ -747,7 +747,7 @@ def delete( *, advanced_mode: Literal[False], ) -> T_resp_json: - ... + ... # fmt: skip @overload def delete( @@ -760,7 +760,7 @@ def delete( absolute: bool = ..., advanced_mode: Literal[False] = ..., ) -> T_resp_json: - ... + ... # fmt: skip # advanced True @overload @@ -775,7 +775,7 @@ def delete( *, advanced_mode: Literal[True], ) -> Response: - ... + ... # fmt: skip # basic overall case @overload @@ -789,7 +789,7 @@ def delete( absolute: bool = ..., advanced_mode: bool = ..., ) -> T_resp: - ... + ... # fmt: skip def delete( self,