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.
Files changed (36) hide show
  1. python_backlog-0.7.2/MANIFEST.in +3 -0
  2. python_backlog-0.7.2/PKG-INFO +89 -0
  3. python_backlog-0.7.2/VERSION +1 -0
  4. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/base.py +2 -0
  5. python_backlog-0.7.2/backlog/custom_field.py +378 -0
  6. python_backlog-0.7.2/python_backlog.egg-info/PKG-INFO +89 -0
  7. {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/SOURCES.txt +15 -1
  8. python_backlog-0.7.2/python_backlog.egg-info/requires.txt +1 -0
  9. python_backlog-0.7.2/requirements-dev.txt +4 -0
  10. python_backlog-0.7.2/requirements.txt +1 -0
  11. {python-backlog-0.6.0 → python_backlog-0.7.2}/setup.py +9 -10
  12. python_backlog-0.7.2/tests/test_api.py +120 -0
  13. python_backlog-0.7.2/tests/test_attachment.py +55 -0
  14. python_backlog-0.7.2/tests/test_category.py +97 -0
  15. python_backlog-0.7.2/tests/test_custom_filed.py +328 -0
  16. python_backlog-0.7.2/tests/test_git.py +426 -0
  17. python_backlog-0.7.2/tests/test_issue.py +685 -0
  18. python_backlog-0.7.2/tests/test_project.py +438 -0
  19. python_backlog-0.7.2/tests/test_user.py +169 -0
  20. python_backlog-0.7.2/tests/test_wiki.py +520 -0
  21. python-backlog-0.6.0/PKG-INFO +0 -88
  22. python-backlog-0.6.0/python_backlog.egg-info/PKG-INFO +0 -88
  23. python-backlog-0.6.0/python_backlog.egg-info/requires.txt +0 -1
  24. {python-backlog-0.6.0 → python_backlog-0.7.2}/LICENSE +0 -0
  25. {python-backlog-0.6.0 → python_backlog-0.7.2}/README.md +0 -0
  26. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/__init__.py +0 -0
  27. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/attachment.py +0 -0
  28. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/category.py +0 -0
  29. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/git.py +0 -0
  30. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/issue.py +0 -0
  31. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/project.py +0 -0
  32. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/user.py +0 -0
  33. {python-backlog-0.6.0 → python_backlog-0.7.2}/backlog/wiki.py +0 -0
  34. {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/dependency_links.txt +0 -0
  35. {python-backlog-0.6.0 → python_backlog-0.7.2}/python_backlog.egg-info/top_level.txt +0 -0
  36. {python-backlog-0.6.0 → python_backlog-0.7.2}/setup.cfg +0 -0
@@ -0,0 +1,3 @@
1
+ include VERSION
2
+ include requirements.txt
3
+ include requirements-dev.txt
@@ -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,4 @@
1
+ flake8~=6.1.0
2
+ tox~=4.11.0
3
+ httpretty~=1.1.0
4
+ coverage~=7.3.0
@@ -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
  }