python-jira-plus 0.2.1__tar.gz → 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/MANIFEST.in +1 -0
  2. {python_jira_plus-0.2.1/python_jira_plus.egg-info → python_jira_plus-1.0.1}/PKG-INFO +11 -14
  3. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1/python_jira_plus.egg-info}/PKG-INFO +11 -14
  4. python_jira_plus-1.0.1/python_jira_plus.egg-info/SOURCES.txt +11 -0
  5. python_jira_plus-1.0.1/python_jira_plus.egg-info/requires.txt +8 -0
  6. python_jira_plus-1.0.1/python_jira_plus.egg-info/top_level.txt +1 -0
  7. python_jira_plus-1.0.1/requirements.txt +8 -0
  8. python_jira_plus-1.0.1/setup.py +36 -0
  9. python_jira_plus-0.2.1/python_jira_plus/__init__.py +0 -33
  10. python_jira_plus-0.2.1/python_jira_plus/describe_allowed_value.py +0 -38
  11. python_jira_plus-0.2.1/python_jira_plus/jira_plus.py +0 -396
  12. python_jira_plus-0.2.1/python_jira_plus.egg-info/SOURCES.txt +0 -18
  13. python_jira_plus-0.2.1/python_jira_plus.egg-info/requires.txt +0 -3
  14. python_jira_plus-0.2.1/python_jira_plus.egg-info/top_level.txt +0 -2
  15. python_jira_plus-0.2.1/setup.py +0 -33
  16. python_jira_plus-0.2.1/usage_example/__init__.py +0 -0
  17. python_jira_plus-0.2.1/usage_example/create_jira_issue.py +0 -26
  18. python_jira_plus-0.2.1/usage_example/featch_jira_issues.py +0 -28
  19. python_jira_plus-0.2.1/usage_example/get_jira_issue.py +0 -24
  20. python_jira_plus-0.2.1/usage_example/update_jira_issue.py +0 -28
  21. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/LICENSE +0 -0
  22. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/README.md +0 -0
  23. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/pyproject.toml +0 -0
  24. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/python_jira_plus.egg-info/dependency_links.txt +0 -0
  25. {python_jira_plus-0.2.1 → python_jira_plus-1.0.1}/setup.cfg +0 -0
@@ -1,2 +1,3 @@
1
+ include requirements.txt
1
2
  include README.md
2
3
  include LICENSE
@@ -1,29 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jira-plus
3
- Version: 0.2.1
4
- Summary: Enhanced Python client for JIRA with better error handling, pagination, and metadata validation
3
+ Version: 1.0.1
4
+ Summary: Enhanced Python client for JIRA with better error handling, pagination, and metadata validation.
5
5
  Home-page: https://github.com/aviz92/python-jira-plus
6
6
  Author: Avi Zaguri
7
7
  Author-email:
8
8
  Project-URL: Repository, https://github.com/aviz92/python-jira-plus
9
- Keywords: jira,atlassian,api,client
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3
15
- Requires-Python: >=3.9
9
+ Requires-Python: >=3.11
16
10
  Description-Content-Type: text/markdown
17
11
  License-File: LICENSE
18
- Requires-Dist: jira>=3.1.1
19
- Requires-Dist: retrying>=1.3.3
20
- Requires-Dist: custom-python-logger>=0.1.4
12
+ Requires-Dist: setuptools
13
+ Requires-Dist: wheel
14
+ Requires-Dist: dotenv
15
+ Requires-Dist: custom-python-logger
16
+ Requires-Dist: pytest
17
+ Requires-Dist: requests
18
+ Requires-Dist: retrying
19
+ Requires-Dist: jira~=3.8.0
21
20
  Dynamic: author
22
- Dynamic: classifier
23
21
  Dynamic: description
24
22
  Dynamic: description-content-type
25
23
  Dynamic: home-page
26
- Dynamic: keywords
27
24
  Dynamic: license-file
28
25
  Dynamic: project-url
29
26
  Dynamic: requires-dist
@@ -1,29 +1,26 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jira-plus
3
- Version: 0.2.1
4
- Summary: Enhanced Python client for JIRA with better error handling, pagination, and metadata validation
3
+ Version: 1.0.1
4
+ Summary: Enhanced Python client for JIRA with better error handling, pagination, and metadata validation.
5
5
  Home-page: https://github.com/aviz92/python-jira-plus
6
6
  Author: Avi Zaguri
7
7
  Author-email:
8
8
  Project-URL: Repository, https://github.com/aviz92/python-jira-plus
9
- Keywords: jira,atlassian,api,client
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3
15
- Requires-Python: >=3.9
9
+ Requires-Python: >=3.11
16
10
  Description-Content-Type: text/markdown
17
11
  License-File: LICENSE
18
- Requires-Dist: jira>=3.1.1
19
- Requires-Dist: retrying>=1.3.3
20
- Requires-Dist: custom-python-logger>=0.1.4
12
+ Requires-Dist: setuptools
13
+ Requires-Dist: wheel
14
+ Requires-Dist: dotenv
15
+ Requires-Dist: custom-python-logger
16
+ Requires-Dist: pytest
17
+ Requires-Dist: requests
18
+ Requires-Dist: retrying
19
+ Requires-Dist: jira~=3.8.0
21
20
  Dynamic: author
22
- Dynamic: classifier
23
21
  Dynamic: description
24
22
  Dynamic: description-content-type
25
23
  Dynamic: home-page
26
- Dynamic: keywords
27
24
  Dynamic: license-file
28
25
  Dynamic: project-url
29
26
  Dynamic: requires-dist
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ requirements.txt
6
+ setup.py
7
+ python_jira_plus.egg-info/PKG-INFO
8
+ python_jira_plus.egg-info/SOURCES.txt
9
+ python_jira_plus.egg-info/dependency_links.txt
10
+ python_jira_plus.egg-info/requires.txt
11
+ python_jira_plus.egg-info/top_level.txt
@@ -0,0 +1,8 @@
1
+ setuptools
2
+ wheel
3
+ dotenv
4
+ custom-python-logger
5
+ pytest
6
+ requests
7
+ retrying
8
+ jira~=3.8.0
@@ -0,0 +1,8 @@
1
+ setuptools
2
+ wheel
3
+ dotenv
4
+ custom-python-logger
5
+ pytest
6
+ requests
7
+ retrying
8
+ jira~=3.8.0
@@ -0,0 +1,36 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ package_version = '1.0.1'
4
+
5
+ package_name = 'python-jira-plus'
6
+ package_description = 'Enhanced Python client for JIRA with better error handling, pagination, and metadata validation.'
7
+
8
+ package_long_description_content_type = 'text/markdown'
9
+ package_url = f'https://github.com/aviz92/{package_name}'
10
+ package_python_requires = '>=3.11'
11
+ package_author = 'Avi Zaguri'
12
+
13
+ with open('requirements.txt', 'r') as file:
14
+ package_install_requires = [
15
+ line.strip() for line in file.readlines() if line.strip() and not line.startswith('#')
16
+ ]
17
+
18
+ with open('README.md', 'r') as file:
19
+ package_long_description = file.read()
20
+
21
+ setup(
22
+ name=package_name,
23
+ version=package_version,
24
+ packages=find_packages(include=[package_name, f'{package_name}.*']),
25
+ install_requires=package_install_requires,
26
+ author=package_author,
27
+ author_email='',
28
+ description=package_description,
29
+ long_description=package_long_description,
30
+ long_description_content_type=package_long_description_content_type,
31
+ url=package_url,
32
+ project_urls={
33
+ 'Repository': package_url,
34
+ },
35
+ python_requires=package_python_requires,
36
+ )
@@ -1,33 +0,0 @@
1
- # python-jira-plus
2
-
3
- """
4
- Python JIRA Plus
5
-
6
- An enhanced Python client for JIRA with better error handling,
7
- pagination, metadata validation, and more.
8
- """
9
-
10
- from dotenv import load_dotenv
11
- load_dotenv()
12
-
13
- from python_jira_plus.jira_plus import JiraPlus, ServerType
14
- from python_jira_plus.describe_allowed_value import describe_allowed_value
15
-
16
- load_dotenv()
17
-
18
- BASIC_FIELDS = [
19
- 'created',
20
- 'updated',
21
- 'issuetype',
22
- 'project',
23
- 'status',
24
- 'summary',
25
- 'description',
26
- 'reporter',
27
- 'assignee',
28
- 'priority',
29
- 'labels',
30
- 'resolution',
31
- 'parent'
32
- ]
33
- __all__ = ['JiraPlus', 'ServerType', 'BASIC_FIELDS']
@@ -1,38 +0,0 @@
1
- # describe_allowed_value.py
2
-
3
- from functools import singledispatch
4
-
5
-
6
- @singledispatch
7
- def describe_allowed_value(value, allowed_values: set = None):
8
- raise NotImplementedError(f"cannot describe this object of type {type(value)}")
9
-
10
-
11
- @describe_allowed_value.register
12
- def _(value: int, allowed_values: list = None):
13
- return value == allowed_values
14
-
15
-
16
- @describe_allowed_value.register
17
- def _(value: float, allowed_values: list = None):
18
- return value == allowed_values
19
-
20
-
21
- @describe_allowed_value.register
22
- def _(value: str, allowed_values: list = None):
23
- return value == allowed_values
24
-
25
-
26
- @describe_allowed_value.register
27
- def _(value: list, allowed_values: list = None):
28
- return any(v in allowed_values for v in value)
29
-
30
-
31
- @describe_allowed_value.register
32
- def _(value: set, allowed_values: list = None):
33
- return any(v in allowed_values for v in value)
34
-
35
-
36
- @describe_allowed_value.register
37
- def _(value: dict, allowed_values: list = None):
38
- return any(v in allowed_values for v in value.values())
@@ -1,396 +0,0 @@
1
- # jira_plus.py
2
-
3
- import json
4
- import logging
5
- import os
6
- import time
7
- from enum import Enum
8
- from typing import Any, Union, Optional
9
- import requests
10
- from jira import JIRA, JIRAError, Issue
11
- from jira.client import ResultList
12
- from retrying import retry
13
-
14
- from python_jira_plus.describe_allowed_value import describe_allowed_value
15
-
16
-
17
- class ServerType(Enum):
18
- CLOUD = 'cloud'
19
- ON_PREMISE = 'on_premise'
20
-
21
-
22
- class JiraPlus:
23
- def __init__(
24
- self, base_url: Optional[str] = None,
25
- server_type: ServerType = ServerType.CLOUD,
26
- urllib3_log_level: int = logging.WARNING
27
- ) -> None:
28
- self.logger = logging.getLogger()
29
- logging.getLogger("urllib3").setLevel(urllib3_log_level)
30
-
31
- self.jira_username = os.getenv('JIRA_USER_NAME')
32
- self.jira_token = os.getenv('JIRA_TOKEN')
33
-
34
- if not base_url:
35
- self.base_url = os.getenv('JIRA_BASE_URL')
36
-
37
- self.server_type = server_type
38
- self.server = f'https://{self.base_url}'
39
-
40
- self.jira_client = self.create_connection()
41
- self.check_client_connection()
42
-
43
- @retry(stop_max_attempt_number=3,
44
- wait_fixed=180000) # Retry 3 times with a 3-min (180000) delay between each attempt
45
- def create_connection(self) -> Optional[JIRA]:
46
- jira_client = JIRA(
47
- basic_auth=(self.jira_username, self.jira_token),
48
- options={'server': self.server},
49
- timeout=580
50
- )
51
-
52
- if jira_client.projects():
53
- self.logger.info(f"Jira Connection Successful")
54
- return jira_client
55
- else:
56
- self.logger.error(f"Jira Connection Failed")
57
- raise JIRAError("Jira Connection Failed")
58
-
59
- def close_connection(self) -> None:
60
- try:
61
- self.jira_client.close()
62
- except JIRAError as err:
63
- self.logger.exception(f"JIRAError \n{err}")
64
- except Exception as err:
65
- self.logger.exception(f"Exception \n{err}")
66
-
67
- self.check_client_connection()
68
-
69
- def check_client_connection(self) -> bool:
70
- try:
71
- if self.jira_client.projects():
72
- return True
73
- else:
74
- self.logger.info("Jira client is closed")
75
- return False
76
- except JIRAError as err:
77
- self.logger.exception(f"JIRAError \n{err}")
78
- return False
79
- except Exception as err:
80
- self.logger.exception(f"Exception \n{err}")
81
- return False
82
-
83
- def get_issue_by_key(
84
- self,
85
- key: str,
86
- specific_fields: str | list[str] = '*all',
87
- json_result: bool = True
88
- ) -> None | Issue | dict[str, Any]:
89
- try:
90
- jira_issue = self.jira_client.issue(id=key, fields=specific_fields)
91
- if json_result:
92
- return jira_issue.raw
93
- return jira_issue
94
- except JIRAError as err:
95
- self.logger.exception(f"JIRAError \n{err}")
96
- return None
97
- except Exception as err:
98
- self.logger.exception(f"Exception \n{err}")
99
- return None
100
-
101
- def get_objects_by_query(
102
- self,
103
- query: str,
104
- max_results: int = 5000,
105
- specific_fields: Union[str, list[str]] = "*all",
106
- jira_err_count: int = 3,
107
- json_result: bool = True,
108
- ) -> Optional[Union[ResultList[Issue], dict]]:
109
- return self._paginate_query(
110
- query=query,
111
- max_results=max_results,
112
- specific_fields=specific_fields,
113
- jira_err_limit=jira_err_count,
114
- json_result=json_result,
115
- )
116
-
117
- def _paginate_query(
118
- self,
119
- query: str,
120
- max_results: int,
121
- specific_fields: Union[str, list[str]],
122
- jira_err_limit: int,
123
- json_result: bool
124
- ) -> Union[ResultList[Issue] | list]:
125
- start_at = 0
126
- all_issues = []
127
- retries = 0
128
- while True:
129
- try:
130
- self.logger.debug(f"Fetching issues from startAt={start_at}")
131
- page = self.jira_client.search_issues(
132
- jql_str=query,
133
- startAt=start_at,
134
- maxResults=max_results,
135
- fields=specific_fields,
136
- json_result=json_result
137
- )
138
- all_issues += page['issues'] if json_result else page
139
- if json_result:
140
- start_at += page['maxResults']
141
- else:
142
- start_at += page.maxResults
143
- if start_at >= max_results:
144
- break
145
- retries = 0 # reset on success
146
- except JIRAError as err:
147
- retries += 1
148
- self.logger.warning(f"Retry {retries}/{jira_err_limit} after JIRAError: {err}")
149
- if retries >= jira_err_limit:
150
- self.logger.exception("Max retries exceeded.")
151
- return []
152
- time.sleep(300 if retries == jira_err_limit - 1 else 90)
153
- return all_issues
154
-
155
- def _fetch_metadata(self, project_key: str, issue_type: str) -> tuple[dict, dict]:
156
- meta = self.jira_client.createmeta(
157
- projectKeys=project_key,
158
- issuetypeNames=issue_type,
159
- expand="projects.issuetypes.fields"
160
- )
161
-
162
- project_meta = next((p for p in meta['projects'] if p['key'] == project_key), None)
163
- if not project_meta:
164
- raise ValueError(f"Project {project_key} not found in metadata")
165
-
166
- issue_meta = next(
167
- (it for it in project_meta['issuetypes'] if it['name'].lower() == issue_type.lower()),
168
- None
169
- )
170
- if not issue_meta:
171
- raise ValueError(f"Issue type '{issue_type}' not found in project '{project_key}'")
172
- return meta, issue_meta
173
-
174
- def get_allowed_values(
175
- self,
176
- project_key: str,
177
- issue_type: str,
178
- field_id_or_name: str,
179
- ) -> Optional[list]:
180
- """
181
- Returns the allowed values for a specific field in a project + issue type context.
182
- """
183
- _, issue_meta = self._fetch_metadata(project_key=project_key, issue_type=issue_type)
184
-
185
- # Try to find by ID or fallback to case-insensitive name
186
- field = issue_meta['fields'].get(field_id_or_name)
187
- if not field:
188
- # Try matching by name (case-insensitive)
189
- for fid, fmeta in issue_meta['fields'].items():
190
- if fmeta['name'].lower() == field_id_or_name.lower():
191
- field = fmeta
192
- break
193
-
194
- if not field:
195
- raise ValueError(
196
- f"Field '{field_id_or_name}' not found in project '{project_key}' with issue type '{issue_type}'")
197
-
198
- allowed = field.get('allowedValues')
199
- if not allowed:
200
- self.logger.info(f"No allowed values found for field '{field_id_or_name}'")
201
- return None
202
-
203
- return allowed
204
-
205
- def _is_value_allowed(self, value: Any, allowed_values: list[Any]) -> bool:
206
- allowed_names = {av.get('name') for av in allowed_values}
207
- allowed_keys = {av.get('key') for av in allowed_values}
208
- allowed_values = allowed_names | allowed_keys
209
- return describe_allowed_value(value, allowed_values=allowed_values)
210
-
211
- def validate_fields(
212
- self,
213
- project_key: str,
214
- issue_type: str,
215
- fields: Optional[dict] = None
216
- ) -> None:
217
- meta, issue_meta = self._fetch_metadata(project_key=project_key, issue_type=issue_type)
218
- field_types = meta['projects'][0]['issuetypes'][0]['fields']
219
- for custom_field, value in fields.items():
220
- if custom_field not in issue_meta['fields']:
221
- raise ValueError(
222
- f'Invalid field(s) for project "{project_key}", issue type "{issue_type}": "{custom_field}"'
223
- )
224
-
225
- if allowed_value := issue_meta['fields'][custom_field].get('allowedValues'):
226
- if not self._is_value_allowed(value=value, allowed_values=allowed_value):
227
- raise ValueError(
228
- f"Invalid value '{value}' for field '{custom_field}' in project '{project_key}' with issue type '{issue_type}'."
229
- f"The allowed values are: {json.dumps(allowed_value, indent=4, sort_keys=True)}"
230
- )
231
-
232
- field_type = field_types[custom_field]['schema']['type']
233
- if field_type == 'string' and not isinstance(value, str):
234
- raise ValueError(f"Field '{custom_field}' must be a string")
235
- elif field_type == 'number' and not isinstance(value, (int, float)):
236
- raise ValueError(f"Field '{custom_field}' must be a number")
237
- elif field_type == 'boolean' and not isinstance(value, bool):
238
- raise ValueError(f"Field '{custom_field}' must be a boolean")
239
- elif field_type == 'array' and not isinstance(value, list):
240
- raise ValueError(f"Field '{custom_field}' must be an array")
241
- elif field_type == 'object' and not isinstance(value, dict):
242
- raise ValueError(f"Field '{custom_field}' must be an object")
243
- elif field_type == 'issuetype' and not isinstance(value, dict):
244
- raise ValueError(f"Field '{custom_field}' must be an issue type")
245
- # Add more field type checks as needed
246
- return issue_meta['fields'].keys()
247
-
248
- def create_issue(
249
- self,
250
- project_key: str,
251
- summary: str,
252
- description: str,
253
- issue_type: str,
254
- assignee: Optional[str] = None,
255
- custom_fields: Optional[dict] = None,
256
- ) -> Optional[Issue]:
257
- issue_fields = {
258
- "project": {"key": project_key},
259
- "issuetype": {"name": issue_type},
260
- "summary": summary,
261
- "description": description,
262
- }
263
- if assignee:
264
- issue_fields["assignee"] = {"accountId": assignee} if self.server_type == ServerType.CLOUD else {"name": assignee}
265
-
266
- if custom_fields:
267
- issue_fields = issue_fields | custom_fields
268
- self.validate_fields(project_key=project_key, issue_type=issue_type, fields=issue_fields)
269
-
270
- try:
271
- issue = self.jira_client.create_issue(fields=issue_fields)
272
- self.logger.info(f"Issue {issue.key} created successfully.")
273
- return issue
274
- except JIRAError as err:
275
- self.logger.error(f"JIRAError while creating issue: {err.text}")
276
- return None
277
- except Exception as err:
278
- self.logger.exception(f"Unexpected error: {err}")
279
- return None
280
-
281
- def update_issue(
282
- self,
283
- issue_key: str,
284
- fields_to_update: dict
285
- ) -> Optional[Issue]:
286
- try:
287
- issue = self.jira_client.issue(issue_key)
288
- self.validate_fields(
289
- project_key=issue.fields.project.key,
290
- issue_type=issue.fields.issuetype.name,
291
- fields=fields_to_update
292
- )
293
-
294
- issue.update(fields=fields_to_update)
295
- self.logger.info(f"Issue {issue.key} updated successfully.")
296
- return issue
297
- except JIRAError as err:
298
- self.logger.error(f"JIRAError while updating issue: {err.text}")
299
- return None
300
- except Exception as err:
301
- self.logger.exception(f"Unexpected error: {err}")
302
- return None
303
-
304
- def delete_issue(self, issue_key: str) -> bool:
305
- try:
306
- issue = self.get_issue_by_key(key=issue_key)
307
- issue.delete()
308
- self.logger.info(f"Issue {issue_key} deleted successfully.")
309
- return True
310
- except JIRAError as err:
311
- self.logger.error(f"JIRAError while deleting issue: {err.text}")
312
- return False
313
- except Exception as err:
314
- self.logger.exception(f"Unexpected error: {err}")
315
- return False
316
-
317
- def _get_available_link_types(self) -> set[str]:
318
- try:
319
- return {link_type.name for link_type in self.jira_client.issue_link_types()}
320
- except Exception as err:
321
- self.logger.exception(f"Failed to retrieve link types from JIRA. \n{err}")
322
- return set()
323
-
324
- def create_link(self, issue_key: str, link_type: str, in_issue_key: str) -> bool:
325
- available_link_types = self._get_available_link_types()
326
- if link_type not in available_link_types:
327
- raise ValueError(f"Invalid link type '{link_type}'. Available link types: {available_link_types}")
328
-
329
- try:
330
- self.jira_client.create_issue_link(
331
- type=link_type,
332
- inwardIssue=issue_key,
333
- outwardIssue=in_issue_key,
334
- )
335
- self.logger.info(f"Link between {issue_key} and {in_issue_key} was created")
336
- return True
337
- except JIRAError as err:
338
- self.logger.exception(f"JIRAError - Link between {issue_key} and {in_issue_key} was not created\n{err}")
339
- return False
340
- except Exception as err:
341
- self.logger.exception(f"Exception - Link between {issue_key} and {in_issue_key} was not created\n{err}")
342
- return False
343
-
344
- def transition_issue(
345
- self,
346
- issue_key: str,
347
- transition_name: str,
348
- fields: Optional[dict] = None,
349
- comment: Optional[str] = None,
350
- ) -> bool:
351
- try:
352
- transitions = self.jira_client.transitions(issue_key)
353
- transition_names = {t['name'] for t in transitions}
354
- if transition_name not in transition_names:
355
- self.logger.error(
356
- f"Transition '{transition_name}' not found for issue {issue_key}. "
357
- f"\nAvailable transitions: {transition_names}"
358
- )
359
- return False
360
-
361
- transition_id = next(
362
- (t['id'] for t in transitions if t['name'].lower() == transition_name.lower()),
363
- None
364
- )
365
- if not transition_id:
366
- self.logger.error(f"Transition '{transition_name}' not found for issue {issue_key}")
367
- return False
368
-
369
- self.jira_client.transition_issue(
370
- issue=issue_key,
371
- transition=transition_id,
372
- fields=fields or {},
373
- comment=comment or None
374
- )
375
- self.logger.info(f"Issue {issue_key} transitioned to '{transition_name}'")
376
- return True
377
- except JIRAError as err:
378
- self.logger.exception(f"JIRAError during transition of {issue_key}: {err}")
379
- return False
380
- except Exception as err:
381
- self.logger.exception(f"Unexpected error during transition of {issue_key}: {err}")
382
- return False
383
-
384
- def get_account_data(self, user_mail: str) -> list[dict]:
385
- jira_rst_url = \
386
- f'https://{self.jira_username}:{self.jira_token}@{self.base_url}/rest/api/{3}/user/search?query={user_mail}'
387
- response = requests.get(jira_rst_url)
388
- return response.json()
389
-
390
- def get_account_id(self, user_mail: str) -> str:
391
- data = self.get_account_data(user_mail=user_mail)
392
- return data[0]['accountId']
393
-
394
- def get_account_display_name(self, user_display_name: str) -> str:
395
- data = self.get_account_data(user_mail=user_display_name)
396
- return data[0]['displayName']
@@ -1,18 +0,0 @@
1
- LICENSE
2
- MANIFEST.in
3
- README.md
4
- pyproject.toml
5
- setup.py
6
- python_jira_plus/__init__.py
7
- python_jira_plus/describe_allowed_value.py
8
- python_jira_plus/jira_plus.py
9
- python_jira_plus.egg-info/PKG-INFO
10
- python_jira_plus.egg-info/SOURCES.txt
11
- python_jira_plus.egg-info/dependency_links.txt
12
- python_jira_plus.egg-info/requires.txt
13
- python_jira_plus.egg-info/top_level.txt
14
- usage_example/__init__.py
15
- usage_example/create_jira_issue.py
16
- usage_example/featch_jira_issues.py
17
- usage_example/get_jira_issue.py
18
- usage_example/update_jira_issue.py
@@ -1,3 +0,0 @@
1
- jira>=3.1.1
2
- retrying>=1.3.3
3
- custom-python-logger>=0.1.4
@@ -1,2 +0,0 @@
1
- python_jira_plus
2
- usage_example
@@ -1,33 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- with open("README.md", "r", encoding="utf-8") as fh:
4
- long_description = fh.read()
5
-
6
- setup(
7
- name="python-jira-plus",
8
- version="0.2.1",
9
- author='Avi Zaguri',
10
- author_email="",
11
- description="Enhanced Python client for JIRA with better error handling, pagination, and metadata validation",
12
- long_description=long_description,
13
- long_description_content_type="text/markdown",
14
- url="https://github.com/aviz92/python-jira-plus",
15
- project_urls={
16
- 'Repository': 'https://github.com/aviz92/python-jira-plus',
17
- },
18
- packages=find_packages(),
19
- classifiers=[
20
- "Development Status :: 4 - Beta",
21
- "Intended Audience :: Developers",
22
- "Topic :: Software Development :: Libraries :: Python Modules",
23
- "License :: OSI Approved :: MIT License",
24
- "Programming Language :: Python :: 3",
25
- ],
26
- python_requires=">=3.9",
27
- install_requires=[
28
- "jira>=3.1.1",
29
- "retrying>=1.3.3",
30
- "custom-python-logger>=0.1.4",
31
- ],
32
- keywords="jira, atlassian, api, client",
33
- )
File without changes
@@ -1,26 +0,0 @@
1
- # create_jira_issue.py
2
-
3
- import logging
4
- from custom_python_logger.logger import get_logger
5
-
6
- from python_jira_plus import JiraPlus
7
-
8
-
9
- def main():
10
- _ = get_logger(
11
- project_name='Logger Project Test',
12
- log_level=logging.DEBUG,
13
- extra={'user': 'test_user'}
14
- )
15
-
16
- jira_plus = JiraPlus()
17
- _ = jira_plus.create_issue(
18
- project_key="JIRA TEST",
19
- issue_type='Story',
20
- summary='Test issue',
21
- description='This is a test issue.',
22
- )
23
-
24
-
25
- if __name__ == '__main__':
26
- main()
@@ -1,28 +0,0 @@
1
- # featch_jira_issue.py
2
-
3
- import logging
4
- from custom_python_logger.logger import get_logger
5
-
6
- from python_jira_plus import BASIC_FIELDS
7
- from python_jira_plus.jira_plus import JiraPlus
8
-
9
- QUERY = 'project = "JIRA TEST" AND issuetype = Story'
10
-
11
-
12
- def main():
13
- _ = get_logger(
14
- project_name='Logger Project Test',
15
- log_level=logging.DEBUG,
16
- extra={'user': 'test_user'}
17
- )
18
-
19
- jira_plus = JiraPlus()
20
- _ = jira_plus.get_objects_by_query(
21
- query=QUERY,
22
- specific_fields=BASIC_FIELDS,
23
- max_results=300,
24
- )
25
-
26
-
27
- if __name__ == '__main__':
28
- main()
@@ -1,24 +0,0 @@
1
- # get_jira_issue.py
2
-
3
- import logging
4
- from custom_python_logger.logger import get_logger
5
-
6
- from python_jira_plus.jira_plus import JiraPlus
7
-
8
- ISSUE_KEY = 'Test-123'
9
-
10
-
11
- def main():
12
- _ = get_logger(
13
- project_name='Logger Project Test',
14
- log_level=logging.DEBUG,
15
- extra={'user': 'test_user'}
16
- )
17
-
18
- jira_plus = JiraPlus()
19
- _ = jira_plus.get_issue_by_key(key=ISSUE_KEY)
20
- print()
21
-
22
-
23
- if __name__ == '__main__':
24
- main()
@@ -1,28 +0,0 @@
1
- # update_jira_issue.py
2
-
3
- import logging
4
- from custom_python_logger.logger import get_logger
5
-
6
- from python_jira_plus.jira_plus import JiraPlus
7
-
8
- ISSUE_KEY = 'Test-123'
9
-
10
-
11
- def main():
12
- _ = get_logger(
13
- project_name='Logger Project Test',
14
- log_level=logging.DEBUG,
15
- extra={'user': 'test_user'}
16
- )
17
-
18
- jira_plus = JiraPlus()
19
- _ = jira_plus.update_issue(
20
- issue_key=ISSUE_KEY,
21
- fields_to_update={
22
- 'priority': {'name': 'Critical'},
23
- }
24
- )
25
-
26
-
27
- if __name__ == '__main__':
28
- main()