python-jira-plus 1.0.13__tar.gz → 1.0.15__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jira-plus
3
- Version: 1.0.13
3
+ Version: 1.0.15
4
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
@@ -12,6 +12,7 @@ License-File: LICENSE
12
12
  Requires-Dist: setuptools
13
13
  Requires-Dist: wheel
14
14
  Requires-Dist: dotenv
15
+ Requires-Dist: pre-commit
15
16
  Requires-Dist: custom-python-logger
16
17
  Requires-Dist: pytest
17
18
  Requires-Dist: requests
@@ -1,5 +1,3 @@
1
- # python-jira-plus
2
-
3
1
  """
4
2
  Python JIRA Plus
5
3
 
@@ -8,26 +6,25 @@ pagination, metadata validation, and more.
8
6
  """
9
7
 
10
8
  from dotenv import load_dotenv
11
- load_dotenv()
12
9
 
13
- from python_jira_plus.jira_plus import JiraPlus, ServerType
14
10
  from python_jira_plus.describe_allowed_value import describe_allowed_value
11
+ from python_jira_plus.jira_plus import JiraPlus, ServerType
15
12
 
16
13
  load_dotenv()
17
14
 
18
15
  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'
16
+ "created",
17
+ "updated",
18
+ "issuetype",
19
+ "project",
20
+ "status",
21
+ "summary",
22
+ "description",
23
+ "reporter",
24
+ "assignee",
25
+ "priority",
26
+ "labels",
27
+ "resolution",
28
+ "parent",
32
29
  ]
33
- __all__ = ['JiraPlus', 'ServerType', 'BASIC_FIELDS']
30
+ __all__ = ["JiraPlus", "ServerType", "BASIC_FIELDS"]
@@ -1,42 +1,43 @@
1
1
  # describe_allowed_value.py
2
2
 
3
3
  from functools import singledispatch
4
+ from typing import Any
4
5
 
5
6
 
6
7
  @singledispatch
7
- def describe_allowed_value(value, allowed_values: set = None):
8
+ def describe_allowed_value(value: Any, allowed_values: set = None) -> bool:
8
9
  raise NotImplementedError(f"cannot describe this object of type {type(value)}")
9
10
 
10
11
 
11
12
  @describe_allowed_value.register
12
- def _(value: int, allowed_values: list = None):
13
+ def _(value: int, allowed_values: list = None) -> bool:
13
14
  return value == allowed_values
14
15
 
15
16
 
16
17
  @describe_allowed_value.register
17
- def _(value: float, allowed_values: list = None):
18
+ def _(value: float, allowed_values: list = None) -> bool:
18
19
  return value == allowed_values
19
20
 
20
21
 
21
22
  @describe_allowed_value.register
22
- def _(value: str, allowed_values: list = None):
23
+ def _(value: str, allowed_values: list = None) -> bool:
23
24
  return value == allowed_values
24
25
 
25
26
 
26
27
  @describe_allowed_value.register
27
- def _(value: list, allowed_values: list = None):
28
- if all(isinstance(v, (str, int, float)) for v in value):
28
+ def _(value: list, allowed_values: list = None) -> bool:
29
+ if all(isinstance(v, str | int | float) for v in value):
29
30
  return any(v in allowed_values for v in value)
30
31
  if all(isinstance(v, dict) for v in value):
31
- return any([list(v.values())[0] in allowed_values for v in value])
32
+ return any(list(v.values())[0] in allowed_values for v in value)
32
33
  return False
33
34
 
34
35
 
35
36
  @describe_allowed_value.register
36
- def _(value: set, allowed_values: list = None):
37
+ def _(value: set, allowed_values: list = None) -> bool:
37
38
  return any(v in allowed_values for v in value)
38
39
 
39
40
 
40
41
  @describe_allowed_value.register
41
- def _(value: dict, allowed_values: list = None):
42
+ def _(value: dict, allowed_values: list = None) -> bool:
42
43
  return any(v in allowed_values for v in value.values())
@@ -5,9 +5,11 @@ import logging
5
5
  import os
6
6
  import time
7
7
  from enum import Enum
8
- from typing import Any, Union, Optional
8
+ from typing import Any
9
+
9
10
  import requests
10
- from jira import JIRA, JIRAError, Issue
11
+ from custom_python_logger import get_logger
12
+ from jira import JIRA, Issue, JIRAError
11
13
  from jira.client import ResultList
12
14
  from retrying import retry
13
15
 
@@ -15,36 +17,37 @@ from python_jira_plus.describe_allowed_value import describe_allowed_value
15
17
 
16
18
 
17
19
  class UrlScheme(Enum):
18
- HTTP = 'http'
19
- HTTPS = 'https'
20
+ HTTP = "http"
21
+ HTTPS = "https"
20
22
 
21
23
 
22
24
  class ServerType(Enum):
23
- CLOUD = 'cloud'
24
- ON_PREMISE = 'on_premise'
25
+ CLOUD = "cloud"
26
+ ON_PREMISE = "on_premise"
25
27
 
26
28
 
27
29
  class JiraPlus:
28
30
  def __init__(
29
- self, base_url: Optional[str] = None,
30
- server_type: ServerType = ServerType.CLOUD,
31
- urllib3_log_level: int = logging.WARNING,
32
- jira_username: Optional[str] = None,
33
- jira_token: Optional[str] = None, # Use API token (not PAT)
34
- verify_ssl: bool = True,
35
- sso: bool = False,
36
- url_scheme: UrlScheme = UrlScheme.HTTPS
31
+ self,
32
+ base_url: str | None = None,
33
+ server_type: ServerType = ServerType.CLOUD,
34
+ urllib3_log_level: int = logging.WARNING,
35
+ jira_username: str | None = None,
36
+ jira_token: str | None = None, # Use API token (not PAT)
37
+ verify_ssl: bool = True,
38
+ sso: bool = False,
39
+ url_scheme: UrlScheme = UrlScheme.HTTPS,
37
40
  ) -> None:
38
- self.logger = logging.getLogger()
39
- logging.getLogger("urllib3").setLevel(urllib3_log_level)
41
+ self.logger = get_logger(self.__class__.__name__)
42
+ get_logger("urllib3").setLevel(urllib3_log_level)
40
43
 
41
- self.jira_username = jira_username or os.getenv('JIRA_USER_NAME')
42
- self.jira_token = jira_token or os.getenv('JIRA_TOKEN')
43
- self.base_url = base_url or os.getenv('JIRA_BASE_URL')
44
+ self.jira_username = jira_username or os.getenv("JIRA_USER_NAME")
45
+ self.jira_token = jira_token or os.getenv("JIRA_TOKEN")
46
+ self.base_url = base_url or os.getenv("JIRA_BASE_URL")
44
47
  self.url_scheme = url_scheme.value
45
48
 
46
49
  self.server_type = server_type
47
- self.server = f'{self.url_scheme}://{self.base_url}'
50
+ self.server = f"{self.url_scheme}://{self.base_url}"
48
51
  self.verify_ssl = verify_ssl
49
52
  self.sso = sso
50
53
 
@@ -52,26 +55,23 @@ class JiraPlus:
52
55
  self.check_client_connection()
53
56
 
54
57
  @retry(stop_max_attempt_number=3, wait_fixed=180000)
55
- def create_connection(self, timeout=580) -> Optional[JIRA]:
56
- if self.server_type == ServerType.ON_PREMISE and not self.sso:
58
+ def create_connection(self, timeout: int = 580) -> JIRA | None:
59
+ if self.server_type == ServerType.ON_PREMISE and not self.sso: # pylint: disable=W0160:
57
60
  jira_client = JIRA(
58
- token_auth=self.jira_token,
59
- options={'server': self.server, 'verify': self.verify_ssl},
60
- timeout=timeout
61
+ token_auth=self.jira_token, options={"server": self.server, "verify": self.verify_ssl}, timeout=timeout
61
62
  )
62
63
  else:
63
64
  jira_client = JIRA(
64
65
  basic_auth=(self.jira_username, self.jira_token),
65
- options={'server': self.server, 'verify': self.verify_ssl},
66
- timeout=timeout
66
+ options={"server": self.server, "verify": self.verify_ssl},
67
+ timeout=timeout,
67
68
  )
68
69
 
69
70
  if jira_client.projects():
70
- self.logger.info(f"Jira Connection Successful")
71
+ self.logger.info("Jira Connection Successful")
71
72
  return jira_client
72
- else:
73
- self.logger.error(f"Jira Connection Failed")
74
- raise JIRAError("Jira Connection Failed")
73
+ self.logger.error("Jira Connection Failed")
74
+ raise JIRAError("Jira Connection Failed")
75
75
 
76
76
  def close_connection(self) -> None:
77
77
  try:
@@ -87,9 +87,8 @@ class JiraPlus:
87
87
  try:
88
88
  if self.jira_client.projects():
89
89
  return True
90
- else:
91
- self.logger.info("Jira client is closed")
92
- return False
90
+ self.logger.info("Jira client is closed")
91
+ return False
93
92
  except JIRAError as err:
94
93
  self.logger.exception(f"JIRAError \n{err}")
95
94
  return False
@@ -98,10 +97,7 @@ class JiraPlus:
98
97
  return False
99
98
 
100
99
  def get_issue_by_key(
101
- self,
102
- key: str,
103
- specific_fields: str | list[str] = '*all',
104
- json_result: bool = True
100
+ self, key: str, specific_fields: str | list[str] = "*all", json_result: bool = True
105
101
  ) -> None | Issue | dict[str, Any]:
106
102
  try:
107
103
  jira_issue = self.jira_client.issue(id=key, fields=specific_fields)
@@ -116,13 +112,13 @@ class JiraPlus:
116
112
  return None
117
113
 
118
114
  def get_objects_by_query(
119
- self,
120
- query: str,
121
- max_results: int = 5000,
122
- specific_fields: Union[str, list[str]] = "*all",
123
- jira_err_count: int = 3,
124
- json_result: bool = True,
125
- ) -> Optional[Union[ResultList[Issue], dict]]:
115
+ self,
116
+ query: str,
117
+ max_results: int = 5000,
118
+ specific_fields: str | list[str] = "*all",
119
+ jira_err_count: int = 3,
120
+ json_result: bool = True,
121
+ ) -> ResultList[Issue] | dict | None:
126
122
  return self._paginate_query(
127
123
  query=query,
128
124
  max_results=max_results,
@@ -132,17 +128,17 @@ class JiraPlus:
132
128
  )
133
129
 
134
130
  def _paginate_query(
135
- self,
136
- query: str,
137
- max_results: int,
138
- specific_fields: Union[str, list[str]],
139
- jira_err_limit: int,
140
- json_result: bool
141
- ) -> Union[ResultList[Issue] | list]:
131
+ self,
132
+ query: str,
133
+ max_results: int,
134
+ specific_fields: str | list[str],
135
+ jira_err_limit: int,
136
+ json_result: bool,
137
+ ) -> ResultList[Issue] | list:
142
138
  start_at = 0
143
139
  all_issues = []
144
140
  retries = 0
145
- while True:
141
+ while True: # pylint: disable=W0149
146
142
  try:
147
143
  self.logger.debug(f"Fetching issues from startAt={start_at}")
148
144
  page = self.jira_client.search_issues(
@@ -150,11 +146,11 @@ class JiraPlus:
150
146
  startAt=start_at,
151
147
  maxResults=max_results,
152
148
  fields=specific_fields,
153
- json_result=json_result
149
+ json_result=json_result,
154
150
  )
155
- all_issues += page['issues'] if json_result else page
151
+ all_issues += page["issues"] if json_result else page
156
152
  if json_result:
157
- start_at += page['maxResults']
153
+ start_at += page["maxResults"]
158
154
  else:
159
155
  start_at += page.maxResults
160
156
  if start_at >= max_results:
@@ -163,7 +159,7 @@ class JiraPlus:
163
159
  except JIRAError as err:
164
160
  if err.status_code == 400:
165
161
  self.logger.error(f"Bad Request: {err.text}")
166
- raise JIRAError(f"Bad Request: {err.text}")
162
+ raise JIRAError(f"Bad Request: {err.text}") from err
167
163
  retries += 1
168
164
  self.logger.warning(f"Retry {retries}/{jira_err_limit} after JIRAError: {err}")
169
165
  if retries >= jira_err_limit:
@@ -174,69 +170,60 @@ class JiraPlus:
174
170
 
175
171
  def _fetch_metadata(self, project_key: str, issue_type: str) -> tuple[dict, dict]:
176
172
  meta = self.jira_client.createmeta(
177
- projectKeys=project_key,
178
- issuetypeNames=issue_type,
179
- expand="projects.issuetypes.fields"
173
+ projectKeys=project_key, issuetypeNames=issue_type, expand="projects.issuetypes.fields"
180
174
  )
181
175
 
182
- project_meta = next((p for p in meta['projects'] if p['key'] == project_key), None)
183
- if not project_meta:
176
+ if not (project_meta := next((p for p in meta["projects"] if p["key"] == project_key), None)):
184
177
  raise ValueError(f"Project {project_key} not found in metadata")
185
178
 
186
- issue_meta = next(
187
- (it for it in project_meta['issuetypes'] if it['name'].lower() == issue_type.lower()),
188
- None
189
- )
179
+ issue_meta = next((it for it in project_meta["issuetypes"] if it["name"].lower() == issue_type.lower()), None)
190
180
  if not issue_meta:
191
181
  raise ValueError(f"Issue type '{issue_type}' not found in project '{project_key}'")
192
182
  return meta, issue_meta
193
183
 
194
184
  def get_allowed_values(
195
- self,
196
- project_key: str,
197
- issue_type: str,
198
- field_id_or_name: str,
199
- ) -> Optional[list]:
185
+ self,
186
+ project_key: str,
187
+ issue_type: str,
188
+ field_id_or_name: str,
189
+ ) -> list | None:
200
190
  """
201
191
  Returns the allowed values for a specific field in a project + issue type context.
202
192
  """
203
- fields_metadata = self.get_project_fields_metadata(
204
- project_key=project_key,
205
- issue_type=issue_type
206
- )
193
+ fields_metadata = self.get_project_fields_metadata(project_key=project_key, issue_type=issue_type)
207
194
 
208
195
  # Try to find by ID or fallback to case-insensitive name
209
- field = fields_metadata.get(field_id_or_name)
210
- if not field:
196
+ if not (field := fields_metadata.get(field_id_or_name)):
211
197
  # Try matching by name (case-insensitive)
212
- for fid, fmeta in fields_metadata.items():
213
- if fmeta['name'].lower() == field_id_or_name.lower():
198
+ for _, fmeta in fields_metadata.items():
199
+ if fmeta["name"].lower() == field_id_or_name.lower():
214
200
  field = fmeta
215
201
  break
216
202
 
217
203
  if not field:
218
204
  raise ValueError(
219
- f"Field '{field_id_or_name}' not found in project '{project_key}' with issue type '{issue_type}'")
205
+ f"Field '{field_id_or_name}' not found in project '{project_key}' with issue type '{issue_type}'"
206
+ )
220
207
 
221
- allowed = field.get('allowedValues')
222
- if not allowed:
208
+ if not (allowed := field.get("allowedValues")):
223
209
  self.logger.info(f"No allowed values found for field '{field_id_or_name}'")
224
210
  return None
225
211
 
226
212
  return allowed
227
213
 
228
- def _is_value_allowed(self, value: Any, allowed_values: list[Any]) -> bool:
229
- allowed_names = {av.get('name') for av in allowed_values}
230
- allowed_keys = {av.get('key') for av in allowed_values}
214
+ @staticmethod
215
+ def _is_value_allowed(value: Any, allowed_values: list[Any]) -> bool:
216
+ allowed_names = {av.get("name") for av in allowed_values}
217
+ allowed_keys = {av.get("key") for av in allowed_values}
231
218
  allowed_values = allowed_names | allowed_keys
232
219
  return describe_allowed_value(value, allowed_values=allowed_values)
233
220
 
234
- def get_server_version(self):
235
- return tuple(self.jira_client.server_info()['versionNumbers'])
221
+ def get_server_version(self) -> tuple[int, ...]:
222
+ return tuple(self.jira_client.server_info()["versionNumbers"])
236
223
 
237
224
  def check_server_createmeta_compatibility(self) -> bool:
238
225
  server_version = self.get_server_version()
239
- if self.server_type == ServerType.ON_PREMISE and ''.join(str(x) for x in server_version) > '9.0.0':
226
+ if self.server_type == ServerType.ON_PREMISE and "".join(str(x) for x in server_version) > "9.0.0":
240
227
  self.logger.warning(
241
228
  f"JIRA server version {server_version} is below the minimum required version (9.0.0). "
242
229
  "Some features may not work as expected."
@@ -246,61 +233,55 @@ class JiraPlus:
246
233
 
247
234
  def get_project_fields_metadata(self, project_key: str, issue_type: str) -> dict:
248
235
  if self.check_server_createmeta_compatibility():
249
- meta, issue_meta = self._fetch_metadata(project_key=project_key, issue_type=issue_type)
250
- return issue_meta['fields']
251
- else:
252
- _project_issue_types = self.jira_client.project_issue_types(project_key)
253
- _issue_types = {t.name: t for t in _project_issue_types}
254
- field_list = self.jira_client.project_issue_fields(project_key, _issue_types[issue_type].id)
255
- return {f.fieldId: f.raw for f in field_list}
256
-
257
- def validate_fields(
258
- self,
259
- project_key: str,
260
- issue_type: str,
261
- fields: Optional[dict] = None
236
+ _, issue_meta = self._fetch_metadata(project_key=project_key, issue_type=issue_type)
237
+ return issue_meta["fields"]
238
+ _project_issue_types = self.jira_client.project_issue_types(project_key)
239
+ _issue_types = {t.name: t for t in _project_issue_types}
240
+ field_list = self.jira_client.project_issue_fields(project_key, _issue_types[issue_type].id)
241
+ return {f.fieldId: f.raw for f in field_list}
242
+
243
+ def validate_fields( # pylint: disable=R1260
244
+ self, project_key: str, issue_type: str, fields: dict | None = None
262
245
  ) -> None:
263
- fields_metadata = self.get_project_fields_metadata(
264
- project_key=project_key,
265
- issue_type=issue_type
266
- )
246
+ fields_metadata = self.get_project_fields_metadata(project_key=project_key, issue_type=issue_type)
267
247
  for custom_field, value in fields.items():
268
248
  if custom_field not in fields_metadata:
269
249
  raise ValueError(
270
250
  f'Invalid field(s) for project "{project_key}", issue type "{issue_type}": "{custom_field}"'
271
251
  )
272
252
 
273
- if allowed_value := fields_metadata[custom_field].get('allowedValues'):
253
+ if allowed_value := fields_metadata[custom_field].get("allowedValues"):
274
254
  if not self._is_value_allowed(value=value, allowed_values=allowed_value):
275
255
  raise ValueError(
276
- f"Invalid value '{value}' for field '{custom_field}' in project '{project_key}' with issue type '{issue_type}'."
256
+ f"Invalid value '{value}' for field '{custom_field}' in project '{project_key}' "
257
+ f"with issue type '{issue_type}'."
277
258
  f"The allowed values are: {json.dumps(allowed_value, indent=4, sort_keys=True)}"
278
259
  )
279
260
 
280
- field_type = fields_metadata[custom_field]['schema']['type']
281
- if field_type == 'string' and not isinstance(value, str):
261
+ field_type = fields_metadata[custom_field]["schema"]["type"]
262
+ if field_type == "string" and not isinstance(value, str):
282
263
  raise ValueError(f"Field '{custom_field}' must be a string")
283
- elif field_type == 'number' and not isinstance(value, (int, float)):
264
+ if field_type == "number" and not isinstance(value, int | float):
284
265
  raise ValueError(f"Field '{custom_field}' must be a number")
285
- elif field_type == 'boolean' and not isinstance(value, bool):
266
+ if field_type == "boolean" and not isinstance(value, bool):
286
267
  raise ValueError(f"Field '{custom_field}' must be a boolean")
287
- elif field_type == 'array' and not isinstance(value, list):
268
+ if field_type == "array" and not isinstance(value, list):
288
269
  raise ValueError(f"Field '{custom_field}' must be an array")
289
- elif field_type == 'object' and not isinstance(value, dict):
270
+ if field_type == "object" and not isinstance(value, dict):
290
271
  raise ValueError(f"Field '{custom_field}' must be an object")
291
- elif field_type == 'issuetype' and not isinstance(value, dict):
272
+ if field_type == "issuetype" and not isinstance(value, dict):
292
273
  raise ValueError(f"Field '{custom_field}' must be an issue type")
293
274
  # Add more field type checks as needed
294
275
 
295
276
  def create_issue(
296
- self,
297
- project_key: str,
298
- summary: str,
299
- description: str,
300
- issue_type: str,
301
- assignee: Optional[str] = None,
302
- custom_fields: Optional[dict] = None,
303
- ) -> Optional[Issue]:
277
+ self,
278
+ project_key: str,
279
+ summary: str,
280
+ description: str,
281
+ issue_type: str,
282
+ assignee: str | None = None,
283
+ custom_fields: dict | None = None,
284
+ ) -> Issue | None:
304
285
  issue_fields = {
305
286
  "project": {"key": project_key},
306
287
  "issuetype": {"name": issue_type},
@@ -308,8 +289,9 @@ class JiraPlus:
308
289
  "description": description,
309
290
  }
310
291
  if assignee:
311
- issue_fields["assignee"] = {"accountId": assignee} if self.server_type == ServerType.CLOUD else {
312
- "name": assignee}
292
+ issue_fields["assignee"] = (
293
+ {"accountId": assignee} if self.server_type == ServerType.CLOUD else {"name": assignee}
294
+ )
313
295
 
314
296
  if custom_fields:
315
297
  issue_fields = issue_fields | custom_fields
@@ -319,17 +301,11 @@ class JiraPlus:
319
301
  self.logger.info(f"Issue {issue.key} created successfully.")
320
302
  return issue
321
303
 
322
- def update_issue(
323
- self,
324
- issue_key: str,
325
- fields_to_update: dict
326
- ) -> Optional[Issue]:
304
+ def update_issue(self, issue_key: str, fields_to_update: dict) -> Issue | None:
327
305
  try:
328
306
  issue = self.jira_client.issue(issue_key)
329
307
  self.validate_fields(
330
- project_key=issue.fields.project.key,
331
- issue_type=issue.fields.issuetype.name,
332
- fields=fields_to_update
308
+ project_key=issue.fields.project.key, issue_type=issue.fields.issuetype.name, fields=fields_to_update
333
309
  )
334
310
 
335
311
  issue.update(fields=fields_to_update)
@@ -395,15 +371,15 @@ class JiraPlus:
395
371
  self.logger.exception(f"Exception - Attachment {file_path} was not uploaded to issue {issue_key}\n{err}")
396
372
 
397
373
  def transition_issue(
398
- self,
399
- issue_key: str,
400
- transition_name: str,
401
- fields: Optional[dict] = None,
402
- comment: Optional[str] = None,
374
+ self,
375
+ issue_key: str,
376
+ transition_name: str,
377
+ fields: dict | None = None,
378
+ comment: str | None = None,
403
379
  ) -> bool:
404
380
  try:
405
381
  transitions = self.jira_client.transitions(issue_key)
406
- transition_names = {t['name'] for t in transitions}
382
+ transition_names = {t["name"] for t in transitions}
407
383
  if transition_name not in transition_names:
408
384
  self.logger.error(
409
385
  f"Transition '{transition_name}' not found for issue {issue_key}. "
@@ -411,19 +387,13 @@ class JiraPlus:
411
387
  )
412
388
  return False
413
389
 
414
- transition_id = next(
415
- (t['id'] for t in transitions if t['name'].lower() == transition_name.lower()),
416
- None
417
- )
390
+ transition_id = next((t["id"] for t in transitions if t["name"].lower() == transition_name.lower()), None)
418
391
  if not transition_id:
419
392
  self.logger.error(f"Transition '{transition_name}' not found for issue {issue_key}")
420
393
  return False
421
394
 
422
395
  self.jira_client.transition_issue(
423
- issue=issue_key,
424
- transition=transition_id,
425
- fields=fields or {},
426
- comment=comment or None
396
+ issue=issue_key, transition=transition_id, fields=fields or {}, comment=comment or None
427
397
  )
428
398
  self.logger.info(f"Issue {issue_key} transitioned to '{transition_name}'")
429
399
  return True
@@ -435,15 +405,16 @@ class JiraPlus:
435
405
  return False
436
406
 
437
407
  def get_account_data(self, user_mail: str) -> list[dict]:
438
- jira_rst_url = \
439
- f'https://{self.jira_username}:{self.jira_token}@{self.base_url}/rest/api/{3}/user/search?query={user_mail}'
408
+ jira_rst_url = (
409
+ f"https://{self.jira_username}:{self.jira_token}@{self.base_url}/rest/api/{3}/user/search?query={user_mail}"
410
+ )
440
411
  response = requests.get(jira_rst_url)
441
412
  return response.json()
442
413
 
443
414
  def get_account_id(self, user_mail: str) -> str:
444
415
  data = self.get_account_data(user_mail=user_mail)
445
- return data[0]['accountId']
416
+ return data[0]["accountId"]
446
417
 
447
418
  def get_account_display_name(self, user_display_name: str) -> str:
448
419
  data = self.get_account_data(user_mail=user_display_name)
449
- return data[0]['displayName']
420
+ return data[0]["displayName"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jira-plus
3
- Version: 1.0.13
3
+ Version: 1.0.15
4
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
@@ -12,6 +12,7 @@ License-File: LICENSE
12
12
  Requires-Dist: setuptools
13
13
  Requires-Dist: wheel
14
14
  Requires-Dist: dotenv
15
+ Requires-Dist: pre-commit
15
16
  Requires-Dist: custom-python-logger
16
17
  Requires-Dist: pytest
17
18
  Requires-Dist: requests
@@ -1,6 +1,7 @@
1
1
  setuptools
2
2
  wheel
3
3
  dotenv
4
+ pre-commit
4
5
  custom-python-logger
5
6
  pytest
6
7
  requests
@@ -1,6 +1,7 @@
1
1
  setuptools
2
2
  wheel
3
3
  dotenv
4
+ pre-commit
4
5
  custom-python-logger
5
6
  pytest
6
7
  requests
@@ -0,0 +1,35 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ package_version = "1.0.15"
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_name_ = package_name.replace("-", "_")
9
+ package_long_description_content_type = "text/markdown"
10
+ package_url = f"https://github.com/aviz92/{package_name}"
11
+ package_python_requires = ">=3.11"
12
+ package_author = "Avi Zaguri"
13
+
14
+ with open("requirements.txt") as file:
15
+ package_install_requires = [line.strip() for line in file.readlines() if line.strip() and not line.startswith("#")]
16
+
17
+ with open("README.md") as file:
18
+ package_long_description = file.read()
19
+
20
+ setup(
21
+ name=package_name,
22
+ version=package_version,
23
+ packages=find_packages(include=[package_name_, f"{package_name_}.*"]),
24
+ install_requires=package_install_requires,
25
+ author=package_author,
26
+ author_email="",
27
+ description=package_description,
28
+ long_description=package_long_description,
29
+ long_description_content_type=package_long_description_content_type,
30
+ url=package_url,
31
+ project_urls={
32
+ "Repository": package_url,
33
+ },
34
+ python_requires=package_python_requires,
35
+ )
@@ -1,37 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- package_version = '1.0.13'
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_name_ = package_name.replace('-', '_')
9
- package_long_description_content_type = 'text/markdown'
10
- package_url = f'https://github.com/aviz92/{package_name}'
11
- package_python_requires = '>=3.11'
12
- package_author = 'Avi Zaguri'
13
-
14
- with open('requirements.txt', 'r') as file:
15
- package_install_requires = [
16
- line.strip() for line in file.readlines() if line.strip() and not line.startswith('#')
17
- ]
18
-
19
- with open('README.md', 'r') as file:
20
- package_long_description = file.read()
21
-
22
- setup(
23
- name=package_name,
24
- version=package_version,
25
- packages=find_packages(include=[package_name_, f'{package_name_}.*']),
26
- install_requires=package_install_requires,
27
- author=package_author,
28
- author_email='',
29
- description=package_description,
30
- long_description=package_long_description,
31
- long_description_content_type=package_long_description_content_type,
32
- url=package_url,
33
- project_urls={
34
- 'Repository': package_url,
35
- },
36
- python_requires=package_python_requires,
37
- )