devrev-Python-SDK 1.0.0__py3-none-any.whl

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 (45) hide show
  1. devrev/__init__.py +47 -0
  2. devrev/client.py +343 -0
  3. devrev/config.py +180 -0
  4. devrev/exceptions.py +205 -0
  5. devrev/models/__init__.py +499 -0
  6. devrev/models/accounts.py +187 -0
  7. devrev/models/articles.py +109 -0
  8. devrev/models/base.py +147 -0
  9. devrev/models/code_changes.py +103 -0
  10. devrev/models/conversations.py +115 -0
  11. devrev/models/dev_users.py +258 -0
  12. devrev/models/groups.py +140 -0
  13. devrev/models/links.py +107 -0
  14. devrev/models/parts.py +110 -0
  15. devrev/models/rev_users.py +177 -0
  16. devrev/models/slas.py +112 -0
  17. devrev/models/tags.py +90 -0
  18. devrev/models/timeline_entries.py +100 -0
  19. devrev/models/webhooks.py +109 -0
  20. devrev/models/works.py +280 -0
  21. devrev/py.typed +1 -0
  22. devrev/services/__init__.py +74 -0
  23. devrev/services/accounts.py +325 -0
  24. devrev/services/articles.py +80 -0
  25. devrev/services/base.py +234 -0
  26. devrev/services/code_changes.py +80 -0
  27. devrev/services/conversations.py +98 -0
  28. devrev/services/dev_users.py +401 -0
  29. devrev/services/groups.py +103 -0
  30. devrev/services/links.py +68 -0
  31. devrev/services/parts.py +100 -0
  32. devrev/services/rev_users.py +235 -0
  33. devrev/services/slas.py +82 -0
  34. devrev/services/tags.py +80 -0
  35. devrev/services/timeline_entries.py +80 -0
  36. devrev/services/webhooks.py +80 -0
  37. devrev/services/works.py +363 -0
  38. devrev/utils/__init__.py +14 -0
  39. devrev/utils/deprecation.py +49 -0
  40. devrev/utils/http.py +521 -0
  41. devrev/utils/logging.py +139 -0
  42. devrev/utils/pagination.py +155 -0
  43. devrev_python_sdk-1.0.0.dist-info/METADATA +774 -0
  44. devrev_python_sdk-1.0.0.dist-info/RECORD +45 -0
  45. devrev_python_sdk-1.0.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,363 @@
1
+ """Works service for DevRev SDK.
2
+
3
+ This module provides the WorksService for managing DevRev work items (issues and tickets).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from collections.abc import Sequence
9
+ from datetime import datetime
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from devrev.models.base import DateFilter, StageUpdate
13
+ from devrev.models.works import (
14
+ IssuePriority,
15
+ TicketSeverity,
16
+ Work,
17
+ WorksCountRequest,
18
+ WorksCountResponse,
19
+ WorksCreateRequest,
20
+ WorksCreateResponse,
21
+ WorksDeleteRequest,
22
+ WorksDeleteResponse,
23
+ WorksExportRequest,
24
+ WorksExportResponse,
25
+ WorksGetRequest,
26
+ WorksGetResponse,
27
+ WorksListRequest,
28
+ WorksListResponse,
29
+ WorksUpdateRequest,
30
+ WorksUpdateRequestOwnedBy,
31
+ WorksUpdateResponse,
32
+ WorkType,
33
+ )
34
+ from devrev.services.base import AsyncBaseService, BaseService
35
+
36
+ if TYPE_CHECKING:
37
+ from devrev.utils.http import AsyncHTTPClient, HTTPClient
38
+
39
+
40
+ class WorksService(BaseService):
41
+ """Synchronous service for managing DevRev work items.
42
+
43
+ Provides methods for creating, reading, updating, and deleting issues and tickets.
44
+ """
45
+
46
+ def __init__(self, http_client: HTTPClient) -> None:
47
+ """Initialize the WorksService."""
48
+ super().__init__(http_client)
49
+
50
+ def create(
51
+ self,
52
+ title: str,
53
+ applies_to_part: str,
54
+ type: WorkType,
55
+ owned_by: Sequence[str],
56
+ *,
57
+ body: str | None = None,
58
+ priority: IssuePriority | None = None,
59
+ severity: TicketSeverity | None = None,
60
+ target_close_date: datetime | None = None,
61
+ custom_fields: dict[str, Any] | None = None,
62
+ ) -> Work:
63
+ """Create a new work item (issue or ticket).
64
+
65
+ Args:
66
+ title: Work item title
67
+ applies_to_part: Part ID this work applies to
68
+ type: Work type (issue or ticket)
69
+ owned_by: Owner user IDs (required)
70
+ body: Work item body/description
71
+ priority: Issue priority (for issues)
72
+ severity: Ticket severity (for tickets)
73
+ target_close_date: Target close date
74
+ custom_fields: Custom fields
75
+
76
+ Returns:
77
+ The created Work item
78
+ """
79
+ request = WorksCreateRequest(
80
+ title=title,
81
+ applies_to_part=applies_to_part,
82
+ type=type,
83
+ owned_by=list(owned_by),
84
+ body=body,
85
+ priority=priority,
86
+ severity=severity,
87
+ target_close_date=target_close_date,
88
+ custom_fields=custom_fields,
89
+ )
90
+ response = self._post("/works.create", request, WorksCreateResponse)
91
+ return response.work
92
+
93
+ def get(self, id: str) -> Work:
94
+ """Get a work item by ID.
95
+
96
+ Args:
97
+ id: Work item ID
98
+
99
+ Returns:
100
+ The Work item
101
+ """
102
+ request = WorksGetRequest(id=id)
103
+ response = self._post("/works.get", request, WorksGetResponse)
104
+ return response.work
105
+
106
+ def list(
107
+ self,
108
+ *,
109
+ type: Sequence[WorkType] | None = None,
110
+ applies_to_part: Sequence[str] | None = None,
111
+ created_by: Sequence[str] | None = None,
112
+ created_date: DateFilter | None = None,
113
+ cursor: str | None = None,
114
+ limit: int | None = None,
115
+ owned_by: Sequence[str] | None = None,
116
+ stage_name: Sequence[str] | None = None,
117
+ ) -> WorksListResponse:
118
+ """List work items.
119
+
120
+ Args:
121
+ type: Filter by work types
122
+ applies_to_part: Filter by part IDs
123
+ created_by: Filter by creator user IDs
124
+ created_date: Filter by creation date
125
+ cursor: Pagination cursor
126
+ limit: Maximum number of results
127
+ owned_by: Filter by owner user IDs
128
+ stage_name: Filter by stage names
129
+
130
+ Returns:
131
+ Paginated list of work items
132
+ """
133
+ request = WorksListRequest(
134
+ type=type,
135
+ applies_to_part=applies_to_part,
136
+ created_by=created_by,
137
+ created_date=created_date,
138
+ cursor=cursor,
139
+ limit=limit,
140
+ owned_by=owned_by,
141
+ stage_name=stage_name,
142
+ )
143
+ return self._post("/works.list", request, WorksListResponse)
144
+
145
+ def update(
146
+ self,
147
+ id: str,
148
+ *,
149
+ title: str | None = None,
150
+ body: str | None = None,
151
+ owned_by: Sequence[str] | None = None,
152
+ stage: StageUpdate | None = None,
153
+ priority: IssuePriority | None = None,
154
+ severity: TicketSeverity | None = None,
155
+ target_close_date: datetime | None = None,
156
+ ) -> Work:
157
+ """Update a work item.
158
+
159
+ Args:
160
+ id: Work item ID
161
+ title: New title
162
+ body: New body/description
163
+ owned_by: New owner IDs
164
+ stage: New stage
165
+ priority: New priority (for issues)
166
+ severity: New severity (for tickets)
167
+ target_close_date: New target close date
168
+
169
+ Returns:
170
+ The updated Work item
171
+ """
172
+ owned_by_update = WorksUpdateRequestOwnedBy(set=owned_by) if owned_by else None
173
+ request = WorksUpdateRequest(
174
+ id=id,
175
+ title=title,
176
+ body=body,
177
+ owned_by=owned_by_update,
178
+ stage=stage,
179
+ priority=priority,
180
+ severity=severity,
181
+ target_close_date=target_close_date,
182
+ )
183
+ response = self._post("/works.update", request, WorksUpdateResponse)
184
+ return response.work
185
+
186
+ def delete(self, id: str) -> None:
187
+ """Delete a work item.
188
+
189
+ Args:
190
+ id: Work item ID to delete
191
+ """
192
+ request = WorksDeleteRequest(id=id)
193
+ self._post("/works.delete", request, WorksDeleteResponse)
194
+
195
+ def export(
196
+ self,
197
+ *,
198
+ type: Sequence[WorkType] | None = None,
199
+ applies_to_part: Sequence[str] | None = None,
200
+ created_by: Sequence[str] | None = None,
201
+ created_date: DateFilter | None = None,
202
+ first: int | None = None,
203
+ ) -> Sequence[Work]:
204
+ """Export work items.
205
+
206
+ Args:
207
+ type: Filter by work types
208
+ applies_to_part: Filter by part IDs
209
+ created_by: Filter by creator user IDs
210
+ created_date: Filter by creation date
211
+ first: Maximum number of results
212
+
213
+ Returns:
214
+ List of exported work items
215
+ """
216
+ request = WorksExportRequest(
217
+ type=type,
218
+ applies_to_part=applies_to_part,
219
+ created_by=created_by,
220
+ created_date=created_date,
221
+ first=first,
222
+ )
223
+ response = self._post("/works.export", request, WorksExportResponse)
224
+ return response.works
225
+
226
+ def count(
227
+ self,
228
+ *,
229
+ type: Sequence[WorkType] | None = None,
230
+ applies_to_part: Sequence[str] | None = None,
231
+ created_by: Sequence[str] | None = None,
232
+ owned_by: Sequence[str] | None = None,
233
+ ) -> int:
234
+ """Count work items.
235
+
236
+ Args:
237
+ type: Filter by work types
238
+ applies_to_part: Filter by part IDs
239
+ created_by: Filter by creator user IDs
240
+ owned_by: Filter by owner user IDs
241
+
242
+ Returns:
243
+ Number of matching work items
244
+ """
245
+ request = WorksCountRequest(
246
+ type=type,
247
+ applies_to_part=applies_to_part,
248
+ created_by=created_by,
249
+ owned_by=owned_by,
250
+ )
251
+ response = self._post("/works.count", request, WorksCountResponse)
252
+ return response.count
253
+
254
+
255
+ class AsyncWorksService(AsyncBaseService):
256
+ """Asynchronous service for managing DevRev work items."""
257
+
258
+ def __init__(self, http_client: AsyncHTTPClient) -> None:
259
+ """Initialize the AsyncWorksService."""
260
+ super().__init__(http_client)
261
+
262
+ async def create(
263
+ self,
264
+ title: str,
265
+ applies_to_part: str,
266
+ type: WorkType,
267
+ owned_by: Sequence[str],
268
+ *,
269
+ body: str | None = None,
270
+ priority: IssuePriority | None = None,
271
+ severity: TicketSeverity | None = None,
272
+ target_close_date: datetime | None = None,
273
+ custom_fields: dict[str, Any] | None = None,
274
+ ) -> Work:
275
+ """Create a new work item."""
276
+ request = WorksCreateRequest(
277
+ title=title,
278
+ applies_to_part=applies_to_part,
279
+ type=type,
280
+ owned_by=list(owned_by),
281
+ body=body,
282
+ priority=priority,
283
+ severity=severity,
284
+ target_close_date=target_close_date,
285
+ custom_fields=custom_fields,
286
+ )
287
+ response = await self._post("/works.create", request, WorksCreateResponse)
288
+ return response.work
289
+
290
+ async def get(self, id: str) -> Work:
291
+ """Get a work item by ID."""
292
+ request = WorksGetRequest(id=id)
293
+ response = await self._post("/works.get", request, WorksGetResponse)
294
+ return response.work
295
+
296
+ async def list(
297
+ self,
298
+ *,
299
+ type: Sequence[WorkType] | None = None,
300
+ applies_to_part: Sequence[str] | None = None,
301
+ cursor: str | None = None,
302
+ limit: int | None = None,
303
+ owned_by: Sequence[str] | None = None,
304
+ ) -> WorksListResponse:
305
+ """List work items."""
306
+ request = WorksListRequest(
307
+ type=type,
308
+ applies_to_part=applies_to_part,
309
+ cursor=cursor,
310
+ limit=limit,
311
+ owned_by=owned_by,
312
+ )
313
+ return await self._post("/works.list", request, WorksListResponse)
314
+
315
+ async def update(
316
+ self,
317
+ id: str,
318
+ *,
319
+ title: str | None = None,
320
+ body: str | None = None,
321
+ owned_by: Sequence[str] | None = None,
322
+ priority: IssuePriority | None = None,
323
+ severity: TicketSeverity | None = None,
324
+ ) -> Work:
325
+ """Update a work item."""
326
+ owned_by_update = WorksUpdateRequestOwnedBy(set=owned_by) if owned_by else None
327
+ request = WorksUpdateRequest(
328
+ id=id,
329
+ title=title,
330
+ body=body,
331
+ owned_by=owned_by_update,
332
+ priority=priority,
333
+ severity=severity,
334
+ )
335
+ response = await self._post("/works.update", request, WorksUpdateResponse)
336
+ return response.work
337
+
338
+ async def delete(self, id: str) -> None:
339
+ """Delete a work item."""
340
+ request = WorksDeleteRequest(id=id)
341
+ await self._post("/works.delete", request, WorksDeleteResponse)
342
+
343
+ async def export(
344
+ self,
345
+ *,
346
+ type: Sequence[WorkType] | None = None,
347
+ first: int | None = None,
348
+ ) -> Sequence[Work]:
349
+ """Export work items."""
350
+ request = WorksExportRequest(type=type, first=first)
351
+ response = await self._post("/works.export", request, WorksExportResponse)
352
+ return response.works
353
+
354
+ async def count(
355
+ self,
356
+ *,
357
+ type: Sequence[WorkType] | None = None,
358
+ owned_by: Sequence[str] | None = None,
359
+ ) -> int:
360
+ """Count work items."""
361
+ request = WorksCountRequest(type=type, owned_by=owned_by)
362
+ response = await self._post("/works.count", request, WorksCountResponse)
363
+ return response.count
@@ -0,0 +1,14 @@
1
+ """DevRev SDK Utilities.
2
+
3
+ This module contains utility functions and classes used throughout the SDK.
4
+ """
5
+
6
+ from devrev.utils.deprecation import deprecated
7
+ from devrev.utils.logging import ColoredFormatter, configure_logging, get_logger
8
+
9
+ __all__ = [
10
+ "ColoredFormatter",
11
+ "configure_logging",
12
+ "deprecated",
13
+ "get_logger",
14
+ ]
@@ -0,0 +1,49 @@
1
+ """Deprecation utilities.
2
+
3
+ This module provides a small, explicit deprecation mechanism for the public SDK.
4
+
5
+ See: DEPRECATIONS.md
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from collections.abc import Callable
11
+ from functools import wraps
12
+ from typing import ParamSpec, TypeVar
13
+ from warnings import warn
14
+
15
+ P = ParamSpec("P")
16
+ R = TypeVar("R")
17
+
18
+
19
+ def deprecated(
20
+ version: str,
21
+ reason: str,
22
+ replacement: str | None = None,
23
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
24
+ """Mark a callable as deprecated.
25
+
26
+ Args:
27
+ version: Version the symbol was deprecated in (e.g. "1.2.0").
28
+ reason: Short explanation of why it is deprecated.
29
+ replacement: Optional replacement symbol or guidance.
30
+
31
+ Returns:
32
+ A decorator that emits a DeprecationWarning on each call.
33
+ """
34
+
35
+ def decorator(func: Callable[P, R]) -> Callable[P, R]:
36
+ @wraps(func)
37
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
38
+ msg = f"{func.__name__} is deprecated since {version}: {reason}"
39
+ if replacement:
40
+ msg += f". Use {replacement} instead."
41
+ warn(msg, DeprecationWarning, stacklevel=2)
42
+ return func(*args, **kwargs)
43
+
44
+ return wrapper
45
+
46
+ return decorator
47
+
48
+
49
+ __all__ = ["deprecated"]