python-backlog 0.6.0__tar.gz → 0.7.2__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.
- python_backlog-0.7.2/MANIFEST.in +3 -0
- python_backlog-0.7.2/PKG-INFO +89 -0
- python_backlog-0.7.2/VERSION +1 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/base.py +2 -0
- python_backlog-0.7.2/backlog/custom_field.py +378 -0
- python_backlog-0.7.2/python_backlog.egg-info/PKG-INFO +89 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/SOURCES.txt +15 -1
- python_backlog-0.7.2/python_backlog.egg-info/requires.txt +1 -0
- python_backlog-0.7.2/requirements-dev.txt +4 -0
- python_backlog-0.7.2/requirements.txt +1 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/setup.py +9 -10
- python_backlog-0.7.2/tests/test_api.py +120 -0
- python_backlog-0.7.2/tests/test_attachment.py +55 -0
- python_backlog-0.7.2/tests/test_category.py +97 -0
- python_backlog-0.7.2/tests/test_custom_filed.py +328 -0
- python_backlog-0.7.2/tests/test_git.py +426 -0
- python_backlog-0.7.2/tests/test_issue.py +685 -0
- python_backlog-0.7.2/tests/test_project.py +438 -0
- python_backlog-0.7.2/tests/test_user.py +169 -0
- python_backlog-0.7.2/tests/test_wiki.py +520 -0
- python-backlog-0.6.0/PKG-INFO +0 -88
- python-backlog-0.6.0/python_backlog.egg-info/PKG-INFO +0 -88
- python-backlog-0.6.0/python_backlog.egg-info/requires.txt +0 -1
- {python-backlog-0.6.0 → python_backlog-0.7.2}/LICENSE +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/README.md +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/__init__.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/attachment.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/category.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/git.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/issue.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/project.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/user.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/wiki.py +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/dependency_links.txt +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/top_level.txt +0 -0
- {python-backlog-0.6.0 → python_backlog-0.7.2}/setup.cfg +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-backlog
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Backlog API v2 wrapper
|
|
5
|
+
Home-page: https://github.com/hassaku63/pbl
|
|
6
|
+
Author: hassaku63
|
|
7
|
+
Author-email: hassaku63@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/hassaku63/pbl
|
|
10
|
+
Keywords: nulab backlog
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Natural Language :: Japanese
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests~=2.32.2
|
|
22
|
+
|
|
23
|
+
# pbl
|
|
24
|
+
|
|
25
|
+
A Python package wraps Backlog API V2
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install python-backlog
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Code snippet
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import base64
|
|
39
|
+
import json
|
|
40
|
+
from backlog.base import BacklogAPI
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
"""
|
|
45
|
+
Initialize API Object
|
|
46
|
+
"""
|
|
47
|
+
api = BacklogAPI("your-space", "your-api-key")
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
Project API
|
|
51
|
+
"""
|
|
52
|
+
# list project users
|
|
53
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-project-list/
|
|
54
|
+
print("# list project users")
|
|
55
|
+
users = api.project.list_users("SampleProject")
|
|
56
|
+
print(json.dumps(users, indent=2))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
Wiki API
|
|
61
|
+
"""
|
|
62
|
+
# list wikis
|
|
63
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-wiki-page-list/
|
|
64
|
+
print("# list wikis")
|
|
65
|
+
wikis = api.wiki.list("SampleProject")
|
|
66
|
+
print(json.dumps(wikis[0], indent=2))
|
|
67
|
+
|
|
68
|
+
# get attachment
|
|
69
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-issue-attachment/
|
|
70
|
+
print("# get attachment")
|
|
71
|
+
wiki = [w for w in api.wiki.list("SampleProject") if len(w["attachments"]) > 0][0]
|
|
72
|
+
attachment = api.wiki.get_attachment(
|
|
73
|
+
wikiId=wiki["id"],
|
|
74
|
+
attachmentId=wiki["attachments"][0]["id"])
|
|
75
|
+
attachment["data"] = base64.b64encode(attachment["data"]).decode()
|
|
76
|
+
print(json.dumps(attachment, indent=2))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## See also
|
|
84
|
+
|
|
85
|
+
Qiita https://qiita.com/hassaku_63/items/b9eb2a1c7ecd3c19507d
|
|
86
|
+
|
|
87
|
+
## Contact
|
|
88
|
+
|
|
89
|
+
Twitter: [hassaku_63](https://twitter.com/hassaku_63)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.7.2
|
|
@@ -9,6 +9,7 @@ from backlog.issue import Issue
|
|
|
9
9
|
from backlog.git import Git
|
|
10
10
|
from backlog.user import User
|
|
11
11
|
from backlog.category import Category
|
|
12
|
+
from backlog.custom_field import CustomField
|
|
12
13
|
|
|
13
14
|
BACKLOG_BASE_URL = "https://{space}.backlog.jp"
|
|
14
15
|
BACKLOG_URI_PREFIX = "/api/v2/"
|
|
@@ -97,6 +98,7 @@ class BacklogAPI(BaseAPI):
|
|
|
97
98
|
self.git = Git(self)
|
|
98
99
|
self.user = User(self)
|
|
99
100
|
self.category = Category(self)
|
|
101
|
+
self.custom_field = CustomField(self)
|
|
100
102
|
|
|
101
103
|
def get(self, method, uri, query_param, request_param, headers, **kwargs):
|
|
102
104
|
_url = self.base_url + uri
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, TypedDict, NewType, Optional, Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class CustomFieldResponse(TypedDict):
|
|
6
|
+
id: int
|
|
7
|
+
projectId: int
|
|
8
|
+
typeId: int
|
|
9
|
+
version: str
|
|
10
|
+
name: str
|
|
11
|
+
description: str
|
|
12
|
+
required: bool
|
|
13
|
+
useIssueType: bool
|
|
14
|
+
applicableIssueTypes: List[int]
|
|
15
|
+
displayOrder: int
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _InitialDate(TypedDict):
|
|
19
|
+
id: int
|
|
20
|
+
shift: Optional[int]
|
|
21
|
+
date: Optional[str]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NumberCustomFieldResponse(CustomFieldResponse):
|
|
25
|
+
min: int
|
|
26
|
+
max: int
|
|
27
|
+
initialValue: int
|
|
28
|
+
unit: str
|
|
29
|
+
initialDate: _InitialDate
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DateCustomFieldResponse(CustomFieldResponse):
|
|
33
|
+
min: str
|
|
34
|
+
max: str
|
|
35
|
+
initialValueType: int
|
|
36
|
+
initialDate: str
|
|
37
|
+
initialShift: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _ListCustomFieldResponseItem(TypedDict):
|
|
41
|
+
id: int
|
|
42
|
+
name: str
|
|
43
|
+
displayOrder: int
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ListCustomFieldResponse(CustomFieldResponse):
|
|
47
|
+
items: List[_ListCustomFieldResponseItem]
|
|
48
|
+
allowInput: bool
|
|
49
|
+
allowAddItem: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
AddTextCustomFieldResponse = NewType("AddTextCustomFieldResponse", CustomFieldResponse)
|
|
53
|
+
AddSentenceCustomFieldResponse = NewType("AddSentenceCustomFieldResponse", CustomFieldResponse)
|
|
54
|
+
AddNumberCustomFieldResponse = NewType("AddNumberCustomFieldResponse", NumberCustomFieldResponse)
|
|
55
|
+
AddDateCustomFieldResponse = NewType("AddDateCustomFieldResponse", DateCustomFieldResponse)
|
|
56
|
+
AddSingleListCustomFieldResponse = NewType("AddSingleListCustomFieldResponse", ListCustomFieldResponse)
|
|
57
|
+
AddMultipleListCustomFieldResponse = NewType("AddMultipleListCustomFieldResponse", ListCustomFieldResponse)
|
|
58
|
+
AddCheckboxCustomFieldResponse = NewType("AddCheckboxCustomFieldResponse", ListCustomFieldResponse)
|
|
59
|
+
AddRadioCustomFieldResponse = NewType("AddRadioCustomFieldResponse", ListCustomFieldResponse)
|
|
60
|
+
AddCustomFiledResponse = Union[
|
|
61
|
+
AddTextCustomFieldResponse, AddSentenceCustomFieldResponse,
|
|
62
|
+
AddNumberCustomFieldResponse, AddDateCustomFieldResponse,
|
|
63
|
+
AddSingleListCustomFieldResponse, AddMultipleListCustomFieldResponse,
|
|
64
|
+
AddCheckboxCustomFieldResponse, AddRadioCustomFieldResponse,
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
DeleteTextCustomFieldResponse = NewType("DeleteTextCustomFieldResponse", CustomFieldResponse)
|
|
68
|
+
DeleteSentenceCustomFieldResponse = NewType("DeleteSentenceCustomFieldResponse", CustomFieldResponse)
|
|
69
|
+
DeleteNumberCustomFieldResponse = NewType("DeleteNumberCustomFieldResponse", NumberCustomFieldResponse)
|
|
70
|
+
DeleteDateCustomFieldResponse = NewType("DeleteDateCustomFieldResponse", DateCustomFieldResponse)
|
|
71
|
+
DeleteSingleListCustomFieldResponse = NewType("DeleteSingleListCustomFieldResponse", ListCustomFieldResponse)
|
|
72
|
+
DeleteMultipleListCustomFieldResponse = NewType("DeleteMultipleListCustomFieldResponse", ListCustomFieldResponse)
|
|
73
|
+
DeleteCheckboxCustomFieldResponse = NewType("DeleteCheckboxCustomFieldResponse", ListCustomFieldResponse)
|
|
74
|
+
DeleteRadioCustomFieldResponse = NewType("DeleteRadioCustomFieldResponse", ListCustomFieldResponse)
|
|
75
|
+
DeleteCustomFiledResponse = Union[
|
|
76
|
+
DeleteTextCustomFieldResponse, DeleteSentenceCustomFieldResponse,
|
|
77
|
+
DeleteNumberCustomFieldResponse, DeleteDateCustomFieldResponse,
|
|
78
|
+
DeleteSingleListCustomFieldResponse, DeleteMultipleListCustomFieldResponse,
|
|
79
|
+
DeleteCheckboxCustomFieldResponse, DeleteRadioCustomFieldResponse,
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CustomField(object):
|
|
84
|
+
class TypeId(Enum):
|
|
85
|
+
TEXT = 1
|
|
86
|
+
SENTENCE = 2
|
|
87
|
+
NUMBER = 3
|
|
88
|
+
DATE = 4
|
|
89
|
+
SINGLE_LIST = 5
|
|
90
|
+
MULTIPLE_LIST = 6
|
|
91
|
+
CHECKBOX = 7
|
|
92
|
+
RADIO = 8
|
|
93
|
+
|
|
94
|
+
class DateInitialValueType(Enum):
|
|
95
|
+
TODAY = 1
|
|
96
|
+
TODAY_PLUS_INITIAL_SHIFT = 2
|
|
97
|
+
SPECIFIED_DAY = 3
|
|
98
|
+
|
|
99
|
+
def __init__(self, api) -> None:
|
|
100
|
+
self.api = api
|
|
101
|
+
|
|
102
|
+
def list(self, projectIdOrKey: str):
|
|
103
|
+
"""List custom fields
|
|
104
|
+
https://developer.nulab.com/docs/backlog/api/2/get-custom-field-list/
|
|
105
|
+
|
|
106
|
+
:param projectIdOrKey: project id or key
|
|
107
|
+
:type projectIdOrKey: str
|
|
108
|
+
"""
|
|
109
|
+
_url = f"projects/{projectIdOrKey}/customFields"
|
|
110
|
+
_method = "GET"
|
|
111
|
+
|
|
112
|
+
resp = self.api.invoke_method(_method, _url)
|
|
113
|
+
|
|
114
|
+
return resp.json()
|
|
115
|
+
|
|
116
|
+
def add(
|
|
117
|
+
self,
|
|
118
|
+
projectIdOrKey: str,
|
|
119
|
+
typeId: Union[TypeId, int],
|
|
120
|
+
name: str,
|
|
121
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
122
|
+
description: Optional[str] = None,
|
|
123
|
+
required: Optional[bool] = None,
|
|
124
|
+
**kwargs,
|
|
125
|
+
) -> AddCustomFiledResponse:
|
|
126
|
+
"""Add custom field
|
|
127
|
+
https://developer.nulab.com/docs/backlog/api/2/add-custom-field/#
|
|
128
|
+
"""
|
|
129
|
+
_url = f"projects/{projectIdOrKey}/customFields"
|
|
130
|
+
_method = "POST"
|
|
131
|
+
tid = typeId
|
|
132
|
+
if isinstance(typeId, Enum):
|
|
133
|
+
tid = typeId.value
|
|
134
|
+
elif isinstance(typeId, int):
|
|
135
|
+
tid = typeId
|
|
136
|
+
else:
|
|
137
|
+
raise ValueError(f"invalid typeId: {typeId}")
|
|
138
|
+
request_param = {
|
|
139
|
+
"typeId": tid,
|
|
140
|
+
"name": name,
|
|
141
|
+
"applicableIssueTypes": applicableIssueTypes,
|
|
142
|
+
"description": description,
|
|
143
|
+
"required": required,
|
|
144
|
+
**kwargs,
|
|
145
|
+
}
|
|
146
|
+
for k, v in request_param.copy().items():
|
|
147
|
+
if v is None:
|
|
148
|
+
del request_param[k]
|
|
149
|
+
if type(v) is bool:
|
|
150
|
+
request_param[k] = "true" if v else "false"
|
|
151
|
+
|
|
152
|
+
# for backlog parameter key spec
|
|
153
|
+
if "applicableIssueTypes" in request_param:
|
|
154
|
+
request_param["applicableIssueTypes[]"] = request_param["applicableIssueTypes"]
|
|
155
|
+
del request_param["applicableIssueTypes"]
|
|
156
|
+
if "items" in request_param:
|
|
157
|
+
request_param["items[]"] = request_param["items"]
|
|
158
|
+
del request_param["items"]
|
|
159
|
+
|
|
160
|
+
resp = self.api.invoke_method(_method, _url, request_param=request_param)
|
|
161
|
+
|
|
162
|
+
return resp.json()
|
|
163
|
+
|
|
164
|
+
def add_text_custom_field(
|
|
165
|
+
self,
|
|
166
|
+
projectIdOrKey: str,
|
|
167
|
+
name: str,
|
|
168
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
169
|
+
description: Optional[str] = None,
|
|
170
|
+
required: Optional[bool] = None,
|
|
171
|
+
) -> AddTextCustomFieldResponse:
|
|
172
|
+
return self.add(
|
|
173
|
+
projectIdOrKey,
|
|
174
|
+
1,
|
|
175
|
+
name,
|
|
176
|
+
applicableIssueTypes,
|
|
177
|
+
description,
|
|
178
|
+
required,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def add_sentence_custom_field(
|
|
182
|
+
self,
|
|
183
|
+
projectIdOrKey: str,
|
|
184
|
+
name: str,
|
|
185
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
186
|
+
description: Optional[str] = None,
|
|
187
|
+
required: Optional[bool] = None,
|
|
188
|
+
) -> AddSentenceCustomFieldResponse:
|
|
189
|
+
return self.add(
|
|
190
|
+
projectIdOrKey,
|
|
191
|
+
2,
|
|
192
|
+
name,
|
|
193
|
+
applicableIssueTypes,
|
|
194
|
+
description,
|
|
195
|
+
required,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def add_number_custom_field(
|
|
199
|
+
self,
|
|
200
|
+
projectIdOrKey: str,
|
|
201
|
+
name: str,
|
|
202
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
203
|
+
description: Optional[str] = None,
|
|
204
|
+
required: Optional[bool] = None,
|
|
205
|
+
min: Optional[int] = None,
|
|
206
|
+
max: Optional[int] = None,
|
|
207
|
+
initialValue: Optional[int] = None,
|
|
208
|
+
unit: Optional[str] = None,
|
|
209
|
+
) -> AddNumberCustomFieldResponse:
|
|
210
|
+
return self.add(
|
|
211
|
+
projectIdOrKey,
|
|
212
|
+
3,
|
|
213
|
+
name,
|
|
214
|
+
applicableIssueTypes,
|
|
215
|
+
description,
|
|
216
|
+
required,
|
|
217
|
+
min=min,
|
|
218
|
+
max=max,
|
|
219
|
+
initialValue=initialValue,
|
|
220
|
+
unit=unit,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def add_date_custom_field(
|
|
224
|
+
self,
|
|
225
|
+
projectIdOrKey: str,
|
|
226
|
+
name: str,
|
|
227
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
228
|
+
description: Optional[str] = None,
|
|
229
|
+
required: Optional[bool] = None,
|
|
230
|
+
min: Optional[str] = None, # yyyy-MM-dd
|
|
231
|
+
max: Optional[str] = None, # yyyy-MM-dd
|
|
232
|
+
initialValueType: Optional[DateInitialValueType] = None,
|
|
233
|
+
initialDate: Optional[str] = None, # yyyy-MM-dd
|
|
234
|
+
initialShift: Optional[int] = None,
|
|
235
|
+
) -> AddDateCustomFieldResponse:
|
|
236
|
+
if isinstance(initialValueType, Enum):
|
|
237
|
+
initialValueType = initialValueType.value
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
(initialValueType == self.DateInitialValueType.SPECIFIED_DAY.value)
|
|
241
|
+
and (initialDate is None)
|
|
242
|
+
):
|
|
243
|
+
raise ValueError("initialDate is required when initialValueType is SPECIFIED_DAY")
|
|
244
|
+
|
|
245
|
+
return self.add(
|
|
246
|
+
projectIdOrKey,
|
|
247
|
+
4,
|
|
248
|
+
name,
|
|
249
|
+
applicableIssueTypes,
|
|
250
|
+
description,
|
|
251
|
+
required,
|
|
252
|
+
min=min,
|
|
253
|
+
max=max,
|
|
254
|
+
initialValueType=initialValueType,
|
|
255
|
+
initialDate=initialDate,
|
|
256
|
+
initialShift=initialShift,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def add_single_list_custom_field(
|
|
260
|
+
self,
|
|
261
|
+
projectIdOrKey: str,
|
|
262
|
+
name: str,
|
|
263
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
264
|
+
description: Optional[str] = None,
|
|
265
|
+
required: Optional[bool] = None,
|
|
266
|
+
items: Optional[List[str]] = None,
|
|
267
|
+
allowInput: Optional[bool] = None,
|
|
268
|
+
allowAddItem: Optional[bool] = None,
|
|
269
|
+
) -> AddSingleListCustomFieldResponse:
|
|
270
|
+
return self.add(
|
|
271
|
+
projectIdOrKey,
|
|
272
|
+
5,
|
|
273
|
+
name,
|
|
274
|
+
applicableIssueTypes,
|
|
275
|
+
description,
|
|
276
|
+
required,
|
|
277
|
+
items=items,
|
|
278
|
+
allowInput=allowInput,
|
|
279
|
+
allowAddItem=allowAddItem,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def add_multiple_list_custom_field(
|
|
283
|
+
self,
|
|
284
|
+
projectIdOrKey: str,
|
|
285
|
+
name: str,
|
|
286
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
287
|
+
description: Optional[str] = None,
|
|
288
|
+
required: Optional[bool] = None,
|
|
289
|
+
items: Optional[List[str]] = None,
|
|
290
|
+
allowInput: Optional[bool] = None,
|
|
291
|
+
allowAddItem: Optional[bool] = None,
|
|
292
|
+
) -> AddMultipleListCustomFieldResponse:
|
|
293
|
+
return self.add(
|
|
294
|
+
projectIdOrKey,
|
|
295
|
+
6,
|
|
296
|
+
name,
|
|
297
|
+
applicableIssueTypes,
|
|
298
|
+
description,
|
|
299
|
+
required,
|
|
300
|
+
items=items,
|
|
301
|
+
allowInput=allowInput,
|
|
302
|
+
allowAddItem=allowAddItem,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def add_checkbox_custom_field(
|
|
306
|
+
self,
|
|
307
|
+
projectIdOrKey: str,
|
|
308
|
+
name: str,
|
|
309
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
310
|
+
description: Optional[str] = None,
|
|
311
|
+
required: Optional[bool] = None,
|
|
312
|
+
items: Optional[List[str]] = None,
|
|
313
|
+
allowInput: Optional[bool] = None,
|
|
314
|
+
allowAddItem: Optional[bool] = None,
|
|
315
|
+
) -> AddCheckboxCustomFieldResponse:
|
|
316
|
+
return self.add(
|
|
317
|
+
projectIdOrKey,
|
|
318
|
+
7,
|
|
319
|
+
name,
|
|
320
|
+
applicableIssueTypes,
|
|
321
|
+
description,
|
|
322
|
+
required,
|
|
323
|
+
items=items,
|
|
324
|
+
allowInput=allowInput,
|
|
325
|
+
allowAddItem=allowAddItem,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def add_radio_custom_field(
|
|
329
|
+
self,
|
|
330
|
+
projectIdOrKey: str,
|
|
331
|
+
name: str,
|
|
332
|
+
applicableIssueTypes: Optional[List[int]] = None,
|
|
333
|
+
description: Optional[str] = None,
|
|
334
|
+
required: Optional[bool] = None,
|
|
335
|
+
items: Optional[List[str]] = None,
|
|
336
|
+
allowInput: Optional[bool] = None,
|
|
337
|
+
allowAddItem: Optional[bool] = None,
|
|
338
|
+
) -> AddRadioCustomFieldResponse:
|
|
339
|
+
return self.add(
|
|
340
|
+
projectIdOrKey,
|
|
341
|
+
8,
|
|
342
|
+
name,
|
|
343
|
+
applicableIssueTypes,
|
|
344
|
+
description,
|
|
345
|
+
required,
|
|
346
|
+
items=items,
|
|
347
|
+
allowInput=allowInput,
|
|
348
|
+
allowAddItem=allowAddItem,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# def update(self, projectIdOrKey: str, id: int, name: str):
|
|
352
|
+
# """Update category
|
|
353
|
+
# https://developer.nulab.com/docs/backlog/api/2/update-category/#
|
|
354
|
+
|
|
355
|
+
# :param projectIdOrKey: project id or key
|
|
356
|
+
# :type projectIdOrKey: str
|
|
357
|
+
# :param id: category id
|
|
358
|
+
# :type id: int
|
|
359
|
+
# :param name: category name
|
|
360
|
+
# :type name: str
|
|
361
|
+
# """
|
|
362
|
+
# _url = f"projects/{projectIdOrKey}/categories/{id}"
|
|
363
|
+
# _method = "PATCH"
|
|
364
|
+
|
|
365
|
+
# resp = self.api.invoke_method(_method, _url, request_param={"name": name})
|
|
366
|
+
|
|
367
|
+
# return resp.json()
|
|
368
|
+
|
|
369
|
+
def delete(self, projectIdOrKey: str, id: int) -> DeleteCustomFiledResponse:
|
|
370
|
+
"""Delete custom field
|
|
371
|
+
https://developer.nulab.com/docs/backlog/api/2/delete-custom-field/#delete-custom-field
|
|
372
|
+
"""
|
|
373
|
+
_url = f"projects/{projectIdOrKey}/customFields/{id}"
|
|
374
|
+
_method = "DELETE"
|
|
375
|
+
|
|
376
|
+
resp = self.api.invoke_method(_method, _url)
|
|
377
|
+
|
|
378
|
+
return resp.json()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-backlog
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Backlog API v2 wrapper
|
|
5
|
+
Home-page: https://github.com/hassaku63/pbl
|
|
6
|
+
Author: hassaku63
|
|
7
|
+
Author-email: hassaku63@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Source, https://github.com/hassaku63/pbl
|
|
10
|
+
Keywords: nulab backlog
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Natural Language :: Japanese
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: requests~=2.32.2
|
|
22
|
+
|
|
23
|
+
# pbl
|
|
24
|
+
|
|
25
|
+
A Python package wraps Backlog API V2
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install python-backlog
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Code snippet
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import base64
|
|
39
|
+
import json
|
|
40
|
+
from backlog.base import BacklogAPI
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def main():
|
|
44
|
+
"""
|
|
45
|
+
Initialize API Object
|
|
46
|
+
"""
|
|
47
|
+
api = BacklogAPI("your-space", "your-api-key")
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
Project API
|
|
51
|
+
"""
|
|
52
|
+
# list project users
|
|
53
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-project-list/
|
|
54
|
+
print("# list project users")
|
|
55
|
+
users = api.project.list_users("SampleProject")
|
|
56
|
+
print(json.dumps(users, indent=2))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
Wiki API
|
|
61
|
+
"""
|
|
62
|
+
# list wikis
|
|
63
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-wiki-page-list/
|
|
64
|
+
print("# list wikis")
|
|
65
|
+
wikis = api.wiki.list("SampleProject")
|
|
66
|
+
print(json.dumps(wikis[0], indent=2))
|
|
67
|
+
|
|
68
|
+
# get attachment
|
|
69
|
+
# https://developer.nulab-inc.com/ja/docs/backlog/api/2/get-issue-attachment/
|
|
70
|
+
print("# get attachment")
|
|
71
|
+
wiki = [w for w in api.wiki.list("SampleProject") if len(w["attachments"]) > 0][0]
|
|
72
|
+
attachment = api.wiki.get_attachment(
|
|
73
|
+
wikiId=wiki["id"],
|
|
74
|
+
attachmentId=wiki["attachments"][0]["id"])
|
|
75
|
+
attachment["data"] = base64.b64encode(attachment["data"]).decode()
|
|
76
|
+
print(json.dumps(attachment, indent=2))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == "__main__":
|
|
80
|
+
main()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## See also
|
|
84
|
+
|
|
85
|
+
Qiita https://qiita.com/hassaku_63/items/b9eb2a1c7ecd3c19507d
|
|
86
|
+
|
|
87
|
+
## Contact
|
|
88
|
+
|
|
89
|
+
Twitter: [hassaku_63](https://twitter.com/hassaku_63)
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
2
3
|
README.md
|
|
4
|
+
VERSION
|
|
5
|
+
requirements-dev.txt
|
|
6
|
+
requirements.txt
|
|
3
7
|
setup.py
|
|
4
8
|
backlog/__init__.py
|
|
5
9
|
backlog/attachment.py
|
|
6
10
|
backlog/base.py
|
|
7
11
|
backlog/category.py
|
|
12
|
+
backlog/custom_field.py
|
|
8
13
|
backlog/git.py
|
|
9
14
|
backlog/issue.py
|
|
10
15
|
backlog/project.py
|
|
@@ -14,4 +19,13 @@ python_backlog.egg-info/PKG-INFO
|
|
|
14
19
|
python_backlog.egg-info/SOURCES.txt
|
|
15
20
|
python_backlog.egg-info/dependency_links.txt
|
|
16
21
|
python_backlog.egg-info/requires.txt
|
|
17
|
-
python_backlog.egg-info/top_level.txt
|
|
22
|
+
python_backlog.egg-info/top_level.txt
|
|
23
|
+
tests/test_api.py
|
|
24
|
+
tests/test_attachment.py
|
|
25
|
+
tests/test_category.py
|
|
26
|
+
tests/test_custom_filed.py
|
|
27
|
+
tests/test_git.py
|
|
28
|
+
tests/test_issue.py
|
|
29
|
+
tests/test_project.py
|
|
30
|
+
tests/test_user.py
|
|
31
|
+
tests/test_wiki.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests~=2.32.2
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests~=2.32.2
|
|
@@ -10,14 +10,18 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f:
|
|
|
10
10
|
long_description = f.read()
|
|
11
11
|
|
|
12
12
|
with open(path.join(here, 'VERSION'), encoding='utf-8') as f:
|
|
13
|
-
version = f.read()
|
|
13
|
+
version = f.read().strip()
|
|
14
|
+
|
|
15
|
+
with open(path.join(here, 'requirements.txt'), encoding='utf-8') as f:
|
|
16
|
+
install_requires = f.read().splitlines()
|
|
17
|
+
|
|
18
|
+
with open(path.join(here, 'requirements-dev.txt'), encoding='utf-8') as f:
|
|
19
|
+
tests_require = f.read().splitlines()
|
|
14
20
|
|
|
15
21
|
setup(
|
|
16
22
|
name='python-backlog',
|
|
17
23
|
version=version,
|
|
18
|
-
install_requires=
|
|
19
|
-
'requests~=2.31.0',
|
|
20
|
-
],
|
|
24
|
+
install_requires=install_requires,
|
|
21
25
|
description='Backlog API v2 wrapper',
|
|
22
26
|
long_description=long_description,
|
|
23
27
|
long_description_content_type='text/markdown',
|
|
@@ -38,12 +42,7 @@ setup(
|
|
|
38
42
|
scripts=[],
|
|
39
43
|
packages=find_packages(exclude=['tests*']),
|
|
40
44
|
python_requires=">=3.8",
|
|
41
|
-
tests_require=
|
|
42
|
-
"flake8~=6.1.0",
|
|
43
|
-
"tox~=4.11.0",
|
|
44
|
-
"httpretty~=1.1.0",
|
|
45
|
-
"coverage~=7.3.0",
|
|
46
|
-
],
|
|
45
|
+
tests_require=install_requires + tests_require,
|
|
47
46
|
project_urls={
|
|
48
47
|
'Source': 'https://github.com/hassaku63/pbl',
|
|
49
48
|
}
|