From 6beaf0c7fe33f81171e92140275cc1af5f3a394d Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 21:54:10 +0300 Subject: [PATCH 01/10] pydantic packages added --- requirements-dev.txt => requirements-dev.txt | 3 +++ requirements.txt | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) rename requirements-dev.txt => requirements-dev.txt (63%) diff --git a/requirements-dev.txt b/requirements-dev.txt similarity index 63% rename from requirements-dev.txt rename to requirements-dev.txt index 47aaa92..1d6ea9b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,9 @@ requests python-dotenv +pydantic +pydantic[email] + setuptools wheel twine \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a830e70..8549f3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ -python-dotenv -requests +email_validator==2.2.0 +jira2py==0.2.0 +pydantic==2.10.4 +pydantic_core==2.27.2 +python-dotenv==1.0.1 +requests==2.32.3 From 2ed4f0615511348376f2af108da5cecc46498f2a Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 21:54:58 +0300 Subject: [PATCH 02/10] pydanctic added and version increased --- setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 6a51cbe..2780fcb 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,22 @@ setup( name="jira2py", - version="0.1.0", + version="0.2.0", author="nEver1", author_email="7fhhwpuuo@mozmail.com", + license="MIT", description="The Python library to interact with Atlassian Jira REST API", long_description=open("README.md").read(), long_description_content_type="text/markdown", url="https://github.com/en-ver/jira2py", packages=find_packages(), - install_requires=["python-dotenv>=1.0.0", "requests>=2.0.0"], + install_requires=[ + "email_validator==2.2.0", + "pydantic==2.10.4", + "pydantic_core==2.27.2", + "python-dotenv==1.0.1", + "requests==2.32.3", + ], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", From 4733cbaa4620c6755c77d16e0d23f3f881a72b00 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 21:55:24 +0300 Subject: [PATCH 03/10] directory clean up --- .vscode/settings.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 35fe170..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CodeGPT.apiKey": "Ollama" -} \ No newline at end of file From f058c433a28fc5e5e0f764eb1d1c31a8cf1ac31c Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:02:10 +0300 Subject: [PATCH 04/10] updated according to the new functionality --- examples/fields_examples.py | 23 +++++++++++------ examples/issue_examples.py | 49 +++++++++++++++++++++---------------- examples/jql_examples.py | 26 ++++++++++++++++++++ 3 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 examples/jql_examples.py diff --git a/examples/fields_examples.py b/examples/fields_examples.py index 5576e7b..f0f2ac6 100644 --- a/examples/fields_examples.py +++ b/examples/fields_examples.py @@ -1,16 +1,25 @@ from jira2py import Jira -import os from dotenv import load_dotenv +import os -"""Load .env variables""" load_dotenv() -"""Create a Jira instance""" jira = Jira( - url=os.getenv("JIRA_URL", ""), - user=os.getenv("JIRA_USER", ""), - api_token=os.getenv("JIRA_API_TOKEN", ""), + jira_url=os.environ.get("JIRA_URL"), + jira_user=os.environ.get("JIRA_USER"), + jira_api_token=os.environ.get("JIRA_API_TOKEN"), ) +"""Create fields instance""" +fields = jira.fields() + """Get the list of Jira fields with its metadata""" -fields = jira.fields().get() +jira_fields = fields.get() + +"""Get the field id by its name""" +field_ids = fields.get_field_id(["Summary", "Reporter", "Parent"]) + +"""Get the field id by its id""" +field_names = fields.get_field_name(["summary", "reporter", "parent"]) + +"""Follow https://en-ver.github.io/jira2py/ for more details""" diff --git a/examples/issue_examples.py b/examples/issue_examples.py index 65a6551..185dd48 100644 --- a/examples/issue_examples.py +++ b/examples/issue_examples.py @@ -1,34 +1,41 @@ from jira2py import Jira -import os from dotenv import load_dotenv +import os -"""Load .env variables""" load_dotenv() -"""Create a Jira instance""" jira = Jira( - url=os.getenv("JIRA_URL", ""), - user=os.getenv("JIRA_USER", ""), - api_token=os.getenv("JIRA_API_TOKEN", ""), + jira_url=os.environ.get("JIRA_URL"), + jira_user=os.environ.get("JIRA_USER"), + jira_api_token=os.environ.get("JIRA_API_TOKEN"), ) +issue_key = os.environ.get("ISSUE_KEY") """Create an Issue instance""" -issue = jira.issue() +issue = jira.issue(issue_key) """Get Issue details""" -response = issue.get("PRJ-1111") # issue object including the issue details -names = response["names"] # field ID-name mapping -fields = response["fields"] # field ID-name mapping -status = fields["status"]["name"] # status field value -summary = fields["summary"] # summary field value +issue_json = issue.get(fields=["status", "summary"]) # Raw JSON reposnse form jira API +names = issue_json["names"] # Field ID-name mapping +fields = issue_json["fields"] # Fields of the issues +status = issue_json["fields"]["status"]["name"] # Status field value +summary = issue_json.get("fields", {}).get("summary", None) # Summary field value """Update "summary" field""" -response = issue.edit(key="PRJ-1111", fields={"summary": f"Test summary"}) - -"""Get changelog of the issue""" -search_first_page = issue.get_changelogs( - "PRJ-1111" -) # Long changelogs returned paginated -search_second_page = issue.get_changelogs( - "PRJ-1111", start_at=50, max_results=50 -) # set the # of item to start from load and items to load to get the next page of results +enable_edit = False # put here True to let teh script proceed with edit +if enable_edit: + edit_response_json = issue.edit(fields={"summary": f"Test summary"}) + +"""Get the the first page of the changelog""" +changelog_page_json = issue.changelog_page() + +"""Get the changelog first page filtered by field ids""" +changelog_page_json_filtered = issue.changelog_page(fields=["issuetype", "labels"]) + +"""Get full changelog of the issue""" +full_changelog_list = issue.changelog_all_pages() + +"""Get full changelog of the issue filtered by field ids""" +full_changelog_list_filtered = issue.changelog_all_pages(fields=["issuetype", "labels"]) + +"""Follow https://en-ver.github.io/jira2py/ for more details""" diff --git a/examples/jql_examples.py b/examples/jql_examples.py new file mode 100644 index 0000000..f508f39 --- /dev/null +++ b/examples/jql_examples.py @@ -0,0 +1,26 @@ +from jira2py import Jira +from dotenv import load_dotenv +import os + +load_dotenv() + +jira = Jira( + jira_url=os.environ.get("JIRA_URL"), + jira_user=os.environ.get("JIRA_USER"), + jira_api_token=os.environ.get("JIRA_API_TOKEN"), +) +jql = os.getenv("JQL", None) + +"""Create search instance""" +search = jira.jql(jql=jql) + +"""Search and return all fields except priority, status category changedate, and status""" +search_results = search.get_page( + fields=["*all", "-priority", "-statuscategorychangedate", "-status"], + expand="names,changelog", +) + +"""Get all pages of search results using paginated method""" +all_issues = search.get_all_pages(fields=["*all"], expand="names,changelog") + +"""Follow https://en-ver.github.io/jira2py/ for more details""" From 482d681359df4f81099a85851f55a1b36857bdd4 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:02:23 +0300 Subject: [PATCH 05/10] renamed to jql --- examples/search_examples.py | 44 ------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 examples/search_examples.py diff --git a/examples/search_examples.py b/examples/search_examples.py deleted file mode 100644 index 006fb26..0000000 --- a/examples/search_examples.py +++ /dev/null @@ -1,44 +0,0 @@ -from jira2py import Jira -import json, os -from dotenv import load_dotenv - -# Load .env variables -load_dotenv() - -# Create a Jira instance -jira = Jira( - url=os.getenv("JIRA_URL", ""), - user=os.getenv("JIRA_USER", ""), - api_token=os.getenv("JIRA_API_TOKEN", ""), -) - -jql = 'statuscategory IN ("In Progress")' - -# Search and return all fields except priority, status category changedate, and status -# search = jira.search().jql( -# jql=jql, -# fields=['*all','-priority', '-statuscategorychangedate', '-status'] -# ) - -# print(json.dumps(search['issues'][0], indent=4)) - -# Get all pages of search results - -all_issues = [] -search = {} - -while True: - - next_page_token = search.get("nextPageToken", None) - if not next_page_token and search or len(all_issues) >= 50: - break - - # Fetch the next page of search results - search = jira.search().jql( - jql=jql, next_page_token=next_page_token, fields=["*all"], expand="names" - ) - - all_issues.extend(search["issues"]) - -print(len(all_issues)) -print(json.dumps(all_issues[0], indent=4)) From 44dcaf19bff7053ec218b3ae2c2f99e3653b3336 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:03:15 +0300 Subject: [PATCH 06/10] refactoring --- jira2py/fields.py | 89 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/jira2py/fields.py b/jira2py/fields.py index 22759ab..9c146e6 100644 --- a/jira2py/fields.py +++ b/jira2py/fields.py @@ -1,10 +1,93 @@ -from .jirabase import JiraBase +from .jira_base import JiraBase +from pydantic import validate_call + +""" +This module provides the `Fields` class, which allows interaction with Jira field metadata. + +The `Fields` class enables users to retrieve and manage information about fields available in a Jira instance. +It includes methods for listing all fields, retrieving field IDs based on field names, and retrieving field +names based on field IDs. + +This functionality simplifies working with Jira's field data for custom integrations and automation. +""" class Fields(JiraBase): - def get(self) -> list[dict]: + def __init__(self, auth_kwargs: tuple): - kwargs = {"method": "GET", "context_path": "field"} + self._set_jira_auth(auth_kwargs) + + def _load_fields(self): + kwargs = {"method": "GET", "context_path": "field"} return self._request_jira(**kwargs) + + def _get_field_attr( + self, + in_attr_name: str, + in_attr_values: str | list[str], + out_attr_name: str | list[str], + ): + + fields = self._load_fields() + + return [ + field.get(out_attr_name, None) + for attr_value in in_attr_values + for field in fields + if field.get(in_attr_name, None) == attr_value + ] + + def get(self) -> list[dict]: + """ + Retrieve all available fields in the Jira instance. + + This method fetches a list of all fields currently available in the Jira instance, + including standard and custom fields. It provides comprehensive metadata for each + field, which can be used for various integrations and automations. + + Returns: + list[dict]: A list of dictionaries where each dictionary contains metadata + about a field (e.g., field ID, name, and other attributes). + """ + + return self._load_fields() + + @validate_call + def get_field_id(self, field_names: list[str]) -> list[str]: + """ + Retrieve the IDs of fields based on their names. + + This method takes a list of field names and returns a list of corresponding field IDs + available in the Jira instance. + + Args: + field_names (list[str]): A list of field names to search for. + + Returns: + list[str]: A list of field IDs that correspond to the provided field names. + """ + + return self._get_field_attr( + in_attr_name="name", in_attr_values=field_names, out_attr_name="id" + ) + + @validate_call + def get_field_name(self, field_ids: list[str]) -> list[str]: + """ + Retrieve the names of fields based on their IDs. + + This method takes a list of field IDs and returns a list of corresponding field names + available in the Jira instance. + + Args: + field_ids (list[str]): A list of field IDs to search for. + + Returns: + list[str]: A list of field names that correspond to the provided field IDs. + """ + + return self._get_field_attr( + in_attr_name="id", in_attr_values=field_ids, out_attr_name="name" + ) From 09100b1cafc24f46d438e9b27f2693b472a4777a Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:05:06 +0300 Subject: [PATCH 07/10] renamed to jql --- jira2py/search.py | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 jira2py/search.py diff --git a/jira2py/search.py b/jira2py/search.py deleted file mode 100644 index 2429f4d..0000000 --- a/jira2py/search.py +++ /dev/null @@ -1,27 +0,0 @@ -from .jirabase import JiraBase - - -class Search(JiraBase): - - def jql( - self, - jql: str, - fields: list[str] | None = None, - max_results: int | None = 50, - next_page_token: str | None = None, - expand: str | None = None, - ) -> dict: - - kwargs = { - "method": "POST", - "context_path": "search/jql", - "data": { - "jql": jql, - "nextPageToken": next_page_token, - "fields": fields, - "expand": expand, - "maxResults": max_results, - }, - } - - return self._request_jira(**kwargs) From b7ad03680f290add9e97c2c84ef248765fdcbf92 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:06:12 +0300 Subject: [PATCH 08/10] pydantic validation implementation and other improvements --- jira2py/issue.py | 152 ++++++++++++++++++++++++++++++++++++++++---- jira2py/jira.py | 101 +++++++++++++++++++++++------ jira2py/jirabase.py | 31 ++++----- jira2py/jql.py | 123 +++++++++++++++++++++++++++++++++++ 4 files changed, 357 insertions(+), 50 deletions(-) create mode 100644 jira2py/jql.py diff --git a/jira2py/issue.py b/jira2py/issue.py index 82464ec..93e6d83 100644 --- a/jira2py/issue.py +++ b/jira2py/issue.py @@ -1,47 +1,171 @@ from .jirabase import JiraBase +from pydantic import validate_call class Issue(JiraBase): - def get( + @validate_call + def __init__( self, - key: str, - fields: str = "*all", - expand: str = "names", + auth_kwargs: tuple, + key_id: str | int | None = None, ): + self._set_jira_auth(auth_kwargs) + self.key_id = key_id + + @validate_call + def get( + self, fields: list[str] | None = ["*all"], expand: str | None = "names" + ) -> dict: + """ + Retrieve details of the associated Jira issue. + + This method fetches the details of the Jira issue specified by `key_id` with + options to customize the fields and expand properties in the response. + + Args: + fields (list[str] | None): Optional. A list of field names to include in + the response. Defaults to all fields (`"*all"`). + expand (str | None): Optional. Specifies additional information to include + in the response (e.g., "names"). Defaults to "names". + + Returns: + dict: A dictionary containing the issue details. + """ + kwargs = { "method": "GET", - "context_path": f"issue/{key}", - "params": {"expand": expand, "fields": fields}, + "context_path": f"issue/{self.key_id}", + "params": {"expand": expand, "fields": ",".join(fields)}, } issue = self._request_jira(**kwargs) return issue - def get_changelogs(self, key: str, start_at: int = 0, max_results: int = 100): + def _changelog_page_loader( + self, + start_at: int | None = None, + max_results: int | None = None, + fields: list[str] | None = None, + ) -> dict: kwargs = { "method": "GET", - "context_path": f"issue/{key}/changelog", + "context_path": f"issue/{self.key_id}/changelog", "params": {"startAt": start_at, "maxResults": max_results}, } changelogs = self._request_jira(**kwargs) + + if fields: + + fields_set = set(fields) + + def filter_items(items: list | None = []): + """Filter items based on fieldId matching field IDs.""" + return [item for item in items if item.get("fieldId", "") in fields_set] + + filtered_values = [ + { + **value, + "items": filter_items(value.get("items", [])), + } + for value in changelogs.get("values", []) + if filter_items(value.get("items", [])) + ] + + changelogs.update({"values": filtered_values}) + return changelogs + @validate_call + def changelog_page( + self, + start_at: int | None = None, + max_results: int | None = None, + fields: list[str] | None = None, + ) -> dict: + """ + Retrieve a single page of changelog entries for the issue. + + This method fetches one page of changelog data for the associated Jira issue, + optionally filtered by specific field IDs. + + Args: + start_at (int | None): Optional. The starting index for pagination. Defaults to None. + max_results (int | None): Optional. The maximum number of results to return. Defaults to None. + fields (list[str] | None): Optional. A list of field IDs to filter changelog entries. + Defaults to None. + + Returns: + dict: A dictionary containing the changelog data for the requested page. + """ + + return self._changelog_page_loader( + start_at=start_at, max_results=max_results, fields=fields + ) + + @validate_call + def changelog_all_pages(self, fields: list[str] | None = None) -> list[dict]: + """ + Retrieve all changelog entries for the issue. + + This method fetches all pages of changelog data for the associated Jira issue + by iterating through paginated results. Optionally, changelog entries can be + filtered by specific field IDs. + + Args: + fields (list[str] | None): Optional. A list of field IDs to filter changelog entries. + Defaults to None. + + Returns: + list[dict]: A list of dictionaries containing all changelog entries for the issue. + """ + + start_at = 0 + changelog = [] + + while True: + + response = self.changelog_page(start_at=start_at, fields=fields) + start_at += response.get("maxResults", 0) + changelog.extend(response.get("values", [])) + if response.get("isLast", True): + break + + return changelog + + @validate_call def edit( self, - key: str, fields: dict, - return_issue: bool = True, - notify_users: bool = False, - expand: str = "names", - ): + return_issue: bool | None = True, + notify_users: bool | None = False, + expand: str | None = "names", + ) -> dict: + """ + Edit the details of the associated Jira issue. + + This method updates the specified fields of the Jira issue and provides options + to notify users, return the updated issue, and include expanded properties. + + Args: + fields (dict): A dictionary of field names and their new values to update the issue. + return_issue (bool | None): Optional. Whether to return the updated issue details. + Defaults to True. + notify_users (bool | None): Optional. Whether to notify users about the update. + Defaults to False. + expand (str | None): Optional. Specifies additional information to include + in the response. Defaults to "names". + + Returns: + dict: A dictionary containing the server's response to the update request. + """ kwargs = { "method": "PUT", - "context_path": f"issue/{key}", + "context_path": f"issue/{self.key_id}", "params": { "notifyUsers": notify_users, "returnIssue": return_issue, diff --git a/jira2py/jira.py b/jira2py/jira.py index 0ccd4b6..25813c2 100644 --- a/jira2py/jira.py +++ b/jira2py/jira.py @@ -1,34 +1,97 @@ -from requests.auth import HTTPBasicAuth -import json, os -from decimal import Decimal +from pydantic import validate_call, EmailStr, HttpUrl + +""" +This module provides the main `Jira` class for interacting with a Jira instance. + +The `Jira` class serves as an entry point to various functionalities such as working with issues, +executing JQL queries, and accessing Jira fields. Initialize the `Jira` class with the +necessary credentials to begin using its features. +""" class Jira: + """ + A client for interacting with a Jira instance. + + This class provides methods for managing Jira issues, executing JQL queries, + and retrieving metadata about fields. It simplifies authentication and + interaction with Jira's APIs by centralizing the required credentials. + + Initialize this class with your Jira instance's URL, user email, and API token. + """ + + @validate_call + def __init__( + self, + jira_url: HttpUrl, + jira_user: EmailStr, + jira_api_token: str, + ): + """ + Set up the Jira client with authentication details. + + Use this method to configure the Jira client by providing the base URL + of your Jira instance, your user email, and your API token. + + Args: + jira_url (str): The base URL of the Jira instance (e.g., https://yourcompany.atlassian.net). + jira_user (str): The email address associated with your Jira account. + jira_api_token (str): The API token used for authenticating with Jira. + """ - def __init__(self, url: str, user: str, api_token: str): + self._auth_kwargs = (jira_url, jira_user, jira_api_token) - os.environ["_JIRA_URL"] = url - os.environ["_JIRA_USER"] = user - os.environ["_JIRA_API_TOKEN"] = api_token + @validate_call + def issue(self, id_key: str | None = None): + """ + Retrieve or initialize a Jira issue. - def search(self): - from .search import Search + This method provides access to Jira issues. By providing an issue key + or ID, you can interact with a specific Jira issue. If no key is + provided, you can initialize the issue object for further configuration. - return Search() + Args: + id_key (str | None): The unique key or ID of the Jira issue. If None, + the method initializes the issue object without + targeting a specific issue. - def issue(self): + Returns: + Issue: An object for managing the specified Jira issue or initializing a new one. + """ from .issue import Issue - return Issue() + return Issue(auth_kwargs=self._auth_kwargs, key_id=id_key) + @validate_call + def jql(self, jql: str): + """ + Execute a JQL query to retrieve issues. + + Use this method to search for Jira issues that match a given JQL (Jira Query Language) + expression. It returns a JQL object for handling the results. + + Args: + jql (str): A valid JQL query string. + + Returns: + JQL: An object for managing the query and its results. + """ + from .jql import JQL + + return JQL(auth_kwargs=self._auth_kwargs, jql=jql) + + @validate_call def fields(self): - from .fields import Fields + """ + Retrieve metadata about Jira fields. - return Fields() + This method provides access to Jira fields, including information about + custom fields. Use it to retrieve a comprehensive list of fields available + in your Jira instance. + Returns: + Fields: An object for managing field-related data. + """ + from .fields import Fields -class DecimalEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, Decimal): - return float(obj) - return super().default(obj) + return Fields(auth_kwargs=self._auth_kwargs) diff --git a/jira2py/jirabase.py b/jira2py/jirabase.py index 7aba05a..cecdea3 100644 --- a/jira2py/jirabase.py +++ b/jira2py/jirabase.py @@ -1,17 +1,13 @@ +import requests, json from requests.auth import HTTPBasicAuth -import json, os, requests -from decimal import Decimal from abc import ABC -class DecimalEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, Decimal): - return float(obj) - return super().default(obj) +class JiraBase(ABC): + def _set_jira_auth(self, auth_kwargs: tuple): -class JiraBase(ABC): + self._jira_url, self._jira_user, self._jira_api_token = auth_kwargs def _request_jira( self, @@ -19,24 +15,25 @@ def _request_jira( context_path: str, params: dict | None = None, data: dict | None = None, - ) -> any: - - jira_url = os.getenv("_JIRA_URL", None) - jira_user = os.getenv("_JIRA_USER", None) - jira_api_token = os.getenv("_JIRA_API_TOKEN", None) + ): try: response = requests.request( method=method, - url=f'{jira_url}/rest/api/3/{context_path.strip("/")}', + url=f'{self._jira_url}/rest/api/3/{context_path.strip("/")}', params=params, - data=json.dumps(data, cls=DecimalEncoder) if data else None, + data=json.dumps(data) if data else None, headers={ "Accept": "application/json", "Content-Type": "application/json", }, - auth=HTTPBasicAuth(jira_user, jira_api_token), + auth=HTTPBasicAuth(self._jira_user, self._jira_api_token), ) + response.raise_for_status() return response.json() + except requests.exceptions.HTTPError as http_err: + raise ValueError(f"HTTP error occurred: {http_err}") from http_err + except requests.exceptions.RequestException as req_err: + raise ValueError(f"Request error: {req_err}") from req_err except Exception as e: - raise e + raise ValueError(f"Unexpected error: {e}") from e diff --git a/jira2py/jql.py b/jira2py/jql.py new file mode 100644 index 0000000..f52ade5 --- /dev/null +++ b/jira2py/jql.py @@ -0,0 +1,123 @@ +from .jirabase import JiraBase +from pydantic import validate_call + + +class JQL(JiraBase): + + @validate_call + def __init__( + self, + auth_kwargs: tuple, + jql: str, + ): + + self._set_jira_auth(auth_kwargs) + self.jql = jql + + def _jql_page_loader( + self, + jql: str, + fields: list[str] | None = None, + max_results: int | None = 100, + next_page_token: str | None = None, + expand: str | None = None, + ) -> dict: + + kwargs = { + "method": "POST", + "context_path": "search/jql", + "data": { + "jql": jql, + "nextPageToken": next_page_token, + "fields": fields, + "expand": expand, + "maxResults": max_results, + }, + } + + return self._request_jira(**kwargs) + + @validate_call + def get_page( + self, + fields: list[str] | None = ["*all"], + max_results: int | None = 100, + next_page_token: str | None = None, + expand: str | None = "names", + ) -> dict: + """ + Retrieve a single page of issues matching the JQL query. + + This method fetches a single page of issues that match the JQL query associated with + this object. You can customize the response by specifying fields to include, + pagination options, and additional properties to expand. + + Args: + fields (list[str] | None): Optional. A list of field names to include in the + response. Defaults to all fields (`"*all"`). + max_results (int | None): Optional. The maximum number of issues to return in + the response. Defaults to 100. + next_page_token (str | None): Optional. A token indicating where to start the + next page of results. Defaults to None. + expand (str | None): Optional. Additional properties to include in the + response (e.g., "names"). Defaults to "names". + + Returns: + dict: A dictionary containing the issues in the current page and other metadata. + """ + + return self._jql_page_loader( + jql=self.jql, + fields=fields, + max_results=max_results, + next_page_token=next_page_token, + expand=expand, + ) + + @validate_call + def get_all_pages( + self, + fields: list[str] | None = ["*all"], + expand: str | None = "names", + ) -> list[dict]: + """ + Retrieve all issues matching the JQL query across all pages. + + This method iteratively fetches all issues that match the JQL query associated + with this object, handling pagination automatically. You can customize the response + by specifying fields to include and additional properties to expand. + + Args: + fields (list[str] | None): Optional. A list of field names to include in the + response. Defaults to all fields (`"*all"`). + expand (str | None): Optional. Additional properties to include in the + response (e.g., "names"). Defaults to "names". + + Returns: + list: A list of dictionaries containing all issues that match the JQL query. + """ + + all_issues = {} + is_last = False + next_page_token = None + + while not is_last: + + search_result_json = self._jql_page_loader( + jql=self.jql, + fields=fields, + next_page_token=next_page_token, + expand=expand, + ) + + all_issues.setdefault("issues", []).extend( + search_result_json.get("issues", []) + ) + + if "names" in expand and not "names" in all_issues: + all_issues.update({"names": search_result_json.get("names", {})}) + + next_page_token = search_result_json.get("nextPageToken", None) + is_last = False if next_page_token else True + + return all_issues From 39f29ff21f4b74aaf37f6221f5abb58843a161a6 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:07:43 +0300 Subject: [PATCH 09/10] refactoring --- jira2py/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jira2py/fields.py b/jira2py/fields.py index 9c146e6..4f7008b 100644 --- a/jira2py/fields.py +++ b/jira2py/fields.py @@ -1,4 +1,4 @@ -from .jira_base import JiraBase +from .jirabase import JiraBase from pydantic import validate_call """ From 67238a62db515f08d5882a0101e6ca4d9c482702 Mon Sep 17 00:00:00 2001 From: en-ver Date: Sun, 5 Jan 2025 22:10:09 +0300 Subject: [PATCH 10/10] init commit --- docs/index.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/index.md diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..ce442b7 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,3 @@ +# jira2py + +The documentation WIP \ No newline at end of file