python-jira-plus 3.2.0__tar.gz → 4.1.0__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 (28) hide show
  1. {python_jira_plus-3.2.0/python_jira_plus.egg-info → python_jira_plus-4.1.0}/PKG-INFO +23 -15
  2. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/README.md +22 -9
  3. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/pyproject.toml +3 -2
  4. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus/__init__.py +10 -5
  5. python_jira_plus-4.1.0/python_jira_plus/consts.py +22 -0
  6. python_jira_plus-4.1.0/python_jira_plus/helpers.py +16 -0
  7. python_jira_plus-4.1.0/python_jira_plus/jira_base.py +448 -0
  8. python_jira_plus-4.1.0/python_jira_plus/jira_cloud.py +87 -0
  9. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus/jira_on_premise.py +27 -36
  10. python_jira_plus-4.1.0/python_jira_plus/types.py +9 -0
  11. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0/python_jira_plus.egg-info}/PKG-INFO +23 -15
  12. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus.egg-info/SOURCES.txt +6 -1
  13. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus.egg-info/requires.txt +0 -6
  14. python_jira_plus-4.1.0/tests/test_jira_cloud_write_integration.py +451 -0
  15. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/tests/test_jira_plus.py +0 -18
  16. python_jira_plus-4.1.0/tests/test_jira_plus_hardening.py +334 -0
  17. python_jira_plus-4.1.0/tests/test_jira_plus_v41.py +67 -0
  18. python_jira_plus-3.2.0/python_jira_plus/consts.py +0 -1
  19. python_jira_plus-3.2.0/python_jira_plus/jira_base.py +0 -320
  20. python_jira_plus-3.2.0/python_jira_plus/jira_cloud.py +0 -109
  21. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/LICENSE +0 -0
  22. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/MANIFEST.in +0 -0
  23. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus/describe_allowed_value.py +0 -0
  24. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus.egg-info/dependency_links.txt +0 -0
  25. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/python_jira_plus.egg-info/top_level.txt +0 -0
  26. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/setup.cfg +0 -0
  27. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/tests/test_describe_allowed_value.py +0 -0
  28. {python_jira_plus-3.2.0 → python_jira_plus-4.1.0}/tests/test_jira_cloud_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-jira-plus
3
- Version: 3.2.0
3
+ Version: 4.1.0
4
4
  Summary: Enhanced Python client for JIRA with better error handling, pagination, and metadata validation.
5
5
  Author: Avi Zaguri
6
6
  License: MIT
@@ -21,11 +21,6 @@ Requires-Dist: python-dotenv>=1.0.0
21
21
  Requires-Dist: jira>=3.10.5
22
22
  Requires-Dist: packaging>=26.2
23
23
  Requires-Dist: requests>=2.32.5
24
- Provides-Extra: dev
25
- Requires-Dist: pre-commit>=4.5.0; extra == "dev"
26
- Requires-Dist: pytest>=9.0.1; extra == "dev"
27
- Requires-Dist: setuptools>=80.9.0; extra == "dev"
28
- Requires-Dist: wheel>=0.45.1; extra == "dev"
29
24
  Dynamic: license-file
30
25
 
31
26
  ![PyPI version](https://img.shields.io/pypi/v/python-jira-plus)
@@ -44,7 +39,7 @@ An enhanced Python client for JIRA that extends the functionality of the officia
44
39
 
45
40
  ## Features
46
41
  - ✅ Simplified connection to JIRA Cloud and On-Premise instances
47
- - ✅ Robust error handling with automatic retries
42
+ - ✅ Clear error handling: failures raise (`raise_on_error=True`) or are logged and returned as `None`/`False`. Rate limits (429) and 503 are retried by `jira-python` itself
48
43
  - ✅ Built-in pagination for large result sets
49
44
  - ✅ Field validation against JIRA metadata
50
45
  - ✅ Enhanced issue creation, retrieval, and updating
@@ -62,7 +57,6 @@ pip install python-jira-plus
62
57
  ## Requirements
63
58
  - Python 3.12+
64
59
  - `jira` package
65
- - `retrying` package
66
60
  - `custom-python-logger` package
67
61
 
68
62
  ---
@@ -77,13 +71,26 @@ JIRA_TOKEN=your_jira_api_token
77
71
  JIRA_BASE_URL=your-instance.atlassian.net # Only used if base_url is not provided to constructor
78
72
  ```
79
73
 
74
+ ## Error handling
75
+ By default, write helpers (`delete_issue`, `create_link`, `transition_issue`, `upload_attachment`,
76
+ `update_issue`) and `get_issue_by_key` log the error and return `False`/`None`. Pass
77
+ `raise_on_error=True` to the constructor to get the original exception instead:
78
+
79
+ ```python
80
+ jira_client = JiraCloud(raise_on_error=True)
81
+ jira_client.get_issue_by_key("PROJ-404") # raises JIRAError instead of returning None
82
+ ```
83
+
84
+ Validation problems (`ValueError`) and failed searches (`JIRAError`) are always raised.
85
+ Rate limits (429) and 503 are retried by `jira-python`'s own session, not by this package.
86
+
80
87
  ## Examples
81
88
 
82
89
  ### Creating an Issue with Custom Fields
83
90
  ```python
84
- from python_jira_plus.jira_plus import JiraPlus
91
+ from python_jira_plus import JiraCloud # or JiraOnPremise
85
92
 
86
- jira_client = JiraPlus()
93
+ jira_client = JiraCloud()
87
94
  issue = jira_client.create_issue(
88
95
  project_key="PROJ",
89
96
  summary="Implement new feature",
@@ -98,12 +105,13 @@ issue = jira_client.create_issue(
98
105
 
99
106
  ### Searching for Issues
100
107
  ```python
101
- from python_jira_plus.jira_plus import JiraPlus
108
+ from python_jira_plus import JiraCloud
102
109
 
103
- jira_client = JiraPlus()
110
+ jira_client = JiraCloud()
104
111
  issues = jira_client.get_objects_by_query(
105
112
  query="project = PROJ AND status = 'In Progress' ORDER BY created DESC",
106
- max_results=50,
113
+ max_results=50, # page size
114
+ limit=200, # optional cap on the total number of issues returned
107
115
  specific_fields=["summary", "status", "assignee"],
108
116
  json_result=False
109
117
  )
@@ -114,9 +122,9 @@ for issue in issues:
114
122
 
115
123
  ### Updating an Issue
116
124
  ```python
117
- from python_jira_plus.jira_plus import JiraPlus
125
+ from python_jira_plus import JiraCloud
118
126
 
119
- jira_client = JiraPlus()
127
+ jira_client = JiraCloud()
120
128
  issue = jira_client.get_issue_by_key(key="PROJ-123", json_result=False)
121
129
 
122
130
  fields_to_update = {
@@ -14,7 +14,7 @@ An enhanced Python client for JIRA that extends the functionality of the officia
14
14
 
15
15
  ## Features
16
16
  - ✅ Simplified connection to JIRA Cloud and On-Premise instances
17
- - ✅ Robust error handling with automatic retries
17
+ - ✅ Clear error handling: failures raise (`raise_on_error=True`) or are logged and returned as `None`/`False`. Rate limits (429) and 503 are retried by `jira-python` itself
18
18
  - ✅ Built-in pagination for large result sets
19
19
  - ✅ Field validation against JIRA metadata
20
20
  - ✅ Enhanced issue creation, retrieval, and updating
@@ -32,7 +32,6 @@ pip install python-jira-plus
32
32
  ## Requirements
33
33
  - Python 3.12+
34
34
  - `jira` package
35
- - `retrying` package
36
35
  - `custom-python-logger` package
37
36
 
38
37
  ---
@@ -47,13 +46,26 @@ JIRA_TOKEN=your_jira_api_token
47
46
  JIRA_BASE_URL=your-instance.atlassian.net # Only used if base_url is not provided to constructor
48
47
  ```
49
48
 
49
+ ## Error handling
50
+ By default, write helpers (`delete_issue`, `create_link`, `transition_issue`, `upload_attachment`,
51
+ `update_issue`) and `get_issue_by_key` log the error and return `False`/`None`. Pass
52
+ `raise_on_error=True` to the constructor to get the original exception instead:
53
+
54
+ ```python
55
+ jira_client = JiraCloud(raise_on_error=True)
56
+ jira_client.get_issue_by_key("PROJ-404") # raises JIRAError instead of returning None
57
+ ```
58
+
59
+ Validation problems (`ValueError`) and failed searches (`JIRAError`) are always raised.
60
+ Rate limits (429) and 503 are retried by `jira-python`'s own session, not by this package.
61
+
50
62
  ## Examples
51
63
 
52
64
  ### Creating an Issue with Custom Fields
53
65
  ```python
54
- from python_jira_plus.jira_plus import JiraPlus
66
+ from python_jira_plus import JiraCloud # or JiraOnPremise
55
67
 
56
- jira_client = JiraPlus()
68
+ jira_client = JiraCloud()
57
69
  issue = jira_client.create_issue(
58
70
  project_key="PROJ",
59
71
  summary="Implement new feature",
@@ -68,12 +80,13 @@ issue = jira_client.create_issue(
68
80
 
69
81
  ### Searching for Issues
70
82
  ```python
71
- from python_jira_plus.jira_plus import JiraPlus
83
+ from python_jira_plus import JiraCloud
72
84
 
73
- jira_client = JiraPlus()
85
+ jira_client = JiraCloud()
74
86
  issues = jira_client.get_objects_by_query(
75
87
  query="project = PROJ AND status = 'In Progress' ORDER BY created DESC",
76
- max_results=50,
88
+ max_results=50, # page size
89
+ limit=200, # optional cap on the total number of issues returned
77
90
  specific_fields=["summary", "status", "assignee"],
78
91
  json_result=False
79
92
  )
@@ -84,9 +97,9 @@ for issue in issues:
84
97
 
85
98
  ### Updating an Issue
86
99
  ```python
87
- from python_jira_plus.jira_plus import JiraPlus
100
+ from python_jira_plus import JiraCloud
88
101
 
89
- jira_client = JiraPlus()
102
+ jira_client = JiraCloud()
90
103
  issue = jira_client.get_issue_by_key(key="PROJ-123", json_result=False)
91
104
 
92
105
  fields_to_update = {
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-jira-plus"
7
- version = "3.2.0"
7
+ version = "4.1.0"
8
8
  description = "Enhanced Python client for JIRA with better error handling, pagination, and metadata validation."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -28,9 +28,10 @@ dependencies = [
28
28
  "requests>=2.32.5",
29
29
  ]
30
30
 
31
- [project.optional-dependencies]
31
+ [dependency-groups]
32
32
  dev = [
33
33
  "pre-commit>=4.5.0",
34
+ "python-base-toolkit>=1.1.2",
34
35
  "pytest>=9.0.1",
35
36
  "setuptools>=80.9.0",
36
37
  "wheel>=0.45.1",
@@ -5,14 +5,11 @@ An enhanced Python client for JIRA with better error handling,
5
5
  pagination, metadata validation, and more.
6
6
  """
7
7
 
8
- from dotenv import load_dotenv
9
-
10
8
  from python_jira_plus.describe_allowed_value import describe_allowed_value
11
9
  from python_jira_plus.jira_base import JiraBase, UrlScheme
12
10
  from python_jira_plus.jira_cloud import JiraCloud
13
11
  from python_jira_plus.jira_on_premise import JiraOnPremise
14
-
15
- load_dotenv()
12
+ from python_jira_plus.types import CreateField
16
13
 
17
14
  BASIC_FIELDS = [
18
15
  "created",
@@ -29,4 +26,12 @@ BASIC_FIELDS = [
29
26
  "resolution",
30
27
  "parent",
31
28
  ]
32
- __all__ = ["JiraBase", "JiraCloud", "JiraOnPremise", "UrlScheme", "BASIC_FIELDS"]
29
+ __all__ = [
30
+ "JiraBase",
31
+ "JiraCloud",
32
+ "JiraOnPremise",
33
+ "UrlScheme",
34
+ "BASIC_FIELDS",
35
+ "describe_allowed_value",
36
+ "CreateField",
37
+ ]
@@ -0,0 +1,22 @@
1
+ import re
2
+ from typing import NamedTuple
3
+
4
+ LOGGER_NAME = "python_jira_plus"
5
+
6
+
7
+ class FieldTypeCheck(NamedTuple):
8
+ python_types: type | tuple[type, ...]
9
+ label: str
10
+
11
+
12
+ ALLOWED_VALUE_KEYS = ("name", "key", "value", "id")
13
+ SCHEME_PATTERN = re.compile(r"^(https?)://", re.IGNORECASE)
14
+ FIELD_TYPE_CHECKS = {
15
+ "string": FieldTypeCheck(str, "a string"),
16
+ "number": FieldTypeCheck((int, float), "a number"),
17
+ "boolean": FieldTypeCheck(bool, "a boolean"),
18
+ "array": FieldTypeCheck(list, "an array"),
19
+ "object": FieldTypeCheck(dict, "an object"),
20
+ "issuetype": FieldTypeCheck(dict, "an issue type"),
21
+ }
22
+ ISSUE_REF_PATTERN = re.compile(r"[A-Za-z0-9_]+(-[0-9]+)?")
@@ -0,0 +1,16 @@
1
+ from typing import Any
2
+
3
+ from python_jira_plus.consts import ISSUE_REF_PATTERN
4
+
5
+
6
+ def allowed_value_label(value: Any) -> str:
7
+ """A readable label for one entry of a field's `allowedValues`."""
8
+ if isinstance(value, dict):
9
+ return str(value.get("name") or value.get("value") or value.get("key") or value.get("id"))
10
+ return str(value)
11
+
12
+
13
+ def check_issue_ref(issue_ref: Any) -> None:
14
+ """Reject an issue key/id that could change the request path (jira-python does not escape it)."""
15
+ if isinstance(issue_ref, str) and not ISSUE_REF_PATTERN.fullmatch(issue_ref):
16
+ raise ValueError(f"Invalid issue key '{issue_ref}'. Expected a key like PROJ-123 or a numeric id")