python-zendesk-sdk 0.1.0__tar.gz → 0.1.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_zendesk_sdk-0.1.2/PKG-INFO +283 -0
- python_zendesk_sdk-0.1.2/README.md +246 -0
- python_zendesk_sdk-0.1.2/examples/basic_usage.py +62 -0
- python_zendesk_sdk-0.1.2/examples/enriched_tickets.py +86 -0
- python_zendesk_sdk-0.1.2/examples/error_handling.py +108 -0
- python_zendesk_sdk-0.1.2/examples/pagination_example.py +66 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/pyproject.toml +1 -1
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/__init__.py +3 -1
- python_zendesk_sdk-0.1.2/src/zendesk_sdk/client.py +862 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/config.py +7 -28
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/http_client.py +12 -2
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/__init__.py +3 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/comment.py +1 -1
- python_zendesk_sdk-0.1.2/src/zendesk_sdk/models/enriched_ticket.py +57 -0
- python_zendesk_sdk-0.1.2/tests/test_client.py +1015 -0
- python_zendesk_sdk-0.1.2/tests/test_config.py +92 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/test_http_client.py +0 -6
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/test_models.py +158 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/test_package_import.py +1 -2
- python_zendesk_sdk-0.1.0/PKG-INFO +0 -218
- python_zendesk_sdk-0.1.0/README.md +0 -181
- python_zendesk_sdk-0.1.0/src/zendesk_sdk/client.py +0 -321
- python_zendesk_sdk-0.1.0/tests/test_client.py +0 -439
- python_zendesk_sdk-0.1.0/tests/test_config.py +0 -52
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/.flake8 +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/.github/workflows/publish.yml +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/.gitignore +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/.python-version +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/LICENSE +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/exceptions.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/base.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/organization.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/ticket.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/models/user.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/src/zendesk_sdk/pagination.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/__init__.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/test_exceptions.py +0 -0
- {python_zendesk_sdk-0.1.0 → python_zendesk_sdk-0.1.2}/tests/test_pagination.py +0 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-zendesk-sdk
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Modern Python SDK for Zendesk API
|
|
5
|
+
Project-URL: Homepage, https://github.com/bormog/python-zendesk-sdk
|
|
6
|
+
Project-URL: Repository, https://github.com/bormog/python-zendesk-sdk
|
|
7
|
+
Project-URL: Issues, https://github.com/bormog/python-zendesk-sdk/issues
|
|
8
|
+
Author: bormog
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,client,sdk,zendesk
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Requires-Python: >=3.8.1
|
|
25
|
+
Requires-Dist: httpx>=0.25.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: flake8>=6.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: isort>=5.12.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: mypy>=1.5.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Python Zendesk SDK
|
|
39
|
+
|
|
40
|
+
Modern Python SDK for Zendesk API with async support, full type safety, and comprehensive error handling.
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
- **Async HTTP Client**: Built on httpx with retry logic, rate limiting, and exponential backoff
|
|
45
|
+
- **Type Safety**: Full Pydantic v2 models for Users, Organizations, Tickets, and Comments
|
|
46
|
+
- **Pagination**: Both offset-based and cursor-based pagination support
|
|
47
|
+
- **Search**: Zendesk search API support
|
|
48
|
+
- **Configuration**: Flexible configuration with environment variable support
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install python-zendesk-sdk
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
from zendesk_sdk import ZendeskClient, ZendeskConfig
|
|
61
|
+
|
|
62
|
+
async def main():
|
|
63
|
+
config = ZendeskConfig(
|
|
64
|
+
subdomain="your-subdomain",
|
|
65
|
+
email="your-email@example.com",
|
|
66
|
+
token="your-api-token",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
async with ZendeskClient(config) as client:
|
|
70
|
+
# Get users with pagination
|
|
71
|
+
users_paginator = await client.get_users(per_page=10)
|
|
72
|
+
users = await users_paginator.get_page()
|
|
73
|
+
|
|
74
|
+
for user in users:
|
|
75
|
+
print(f"User: {user.name} ({user.email})")
|
|
76
|
+
|
|
77
|
+
# Get specific ticket
|
|
78
|
+
ticket = await client.get_ticket(ticket_id=12345)
|
|
79
|
+
print(f"Ticket: {ticket.subject}")
|
|
80
|
+
|
|
81
|
+
# Search tickets
|
|
82
|
+
results = await client.search_tickets("status:open priority:high")
|
|
83
|
+
for ticket in results:
|
|
84
|
+
print(f"High priority: {ticket.subject}")
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
### Direct instantiation
|
|
92
|
+
```python
|
|
93
|
+
config = ZendeskConfig(
|
|
94
|
+
subdomain="mycompany",
|
|
95
|
+
email="user@example.com",
|
|
96
|
+
token="api_token_here"
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Environment variables
|
|
101
|
+
```bash
|
|
102
|
+
export ZENDESK_SUBDOMAIN=mycompany
|
|
103
|
+
export ZENDESK_EMAIL=user@example.com
|
|
104
|
+
export ZENDESK_TOKEN=api_token_here
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
config = ZendeskConfig() # Will load from environment
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## API Methods
|
|
112
|
+
|
|
113
|
+
### Users
|
|
114
|
+
- `get_users()` - List users with pagination
|
|
115
|
+
- `get_user(user_id)` - Get user by ID
|
|
116
|
+
- `get_user_by_email(email)` - Get user by email
|
|
117
|
+
|
|
118
|
+
### Organizations
|
|
119
|
+
- `get_organizations()` - List organizations with pagination
|
|
120
|
+
- `get_organization(organization_id)` - Get organization by ID
|
|
121
|
+
|
|
122
|
+
### Tickets
|
|
123
|
+
- `get_tickets()` - List tickets with pagination
|
|
124
|
+
- `get_ticket(ticket_id)` - Get ticket by ID
|
|
125
|
+
- `get_user_tickets(user_id)` - Get tickets for a user
|
|
126
|
+
- `get_organization_tickets(organization_id)` - Get tickets for an organization
|
|
127
|
+
|
|
128
|
+
### Enriched Tickets
|
|
129
|
+
|
|
130
|
+
Load tickets with all related data (comments + users) in minimum API requests:
|
|
131
|
+
|
|
132
|
+
- `get_enriched_ticket(ticket_id)` - Get ticket with comments and all users
|
|
133
|
+
- `search_enriched_tickets(query)` - Search tickets with all related data
|
|
134
|
+
- `get_organization_enriched_tickets(org_id)` - Get organization tickets with all data
|
|
135
|
+
- `get_user_enriched_tickets(user_id)` - Get user tickets with all data
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
# Get ticket with all related data
|
|
139
|
+
enriched = await client.get_enriched_ticket(12345)
|
|
140
|
+
|
|
141
|
+
print(f"Ticket: {enriched.ticket.subject}")
|
|
142
|
+
print(f"Requester: {enriched.requester.name}")
|
|
143
|
+
print(f"Assignee: {enriched.assignee.name if enriched.assignee else 'Unassigned'}")
|
|
144
|
+
|
|
145
|
+
for comment in enriched.comments:
|
|
146
|
+
author = enriched.get_comment_author(comment)
|
|
147
|
+
print(f"Comment by {author.name}: {comment.body[:50]}...")
|
|
148
|
+
|
|
149
|
+
# Search with all data loaded
|
|
150
|
+
results = await client.search_enriched_tickets("status:open")
|
|
151
|
+
for item in results:
|
|
152
|
+
print(f"{item.ticket.subject} - {len(item.comments)} comments")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Comments
|
|
156
|
+
- `get_ticket_comments(ticket_id)` - Get comments for a ticket
|
|
157
|
+
- `add_ticket_comment(ticket_id, body, public=False)` - Add a comment (private by default)
|
|
158
|
+
- `make_comment_private(ticket_id, comment_id)` - Convert public comment to internal note
|
|
159
|
+
- `redact_comment_string(ticket_id, comment_id, text)` - Permanently redact text from comment
|
|
160
|
+
|
|
161
|
+
### Tags
|
|
162
|
+
- `get_ticket_tags(ticket_id)` - Get all tags for a ticket
|
|
163
|
+
- `add_ticket_tags(ticket_id, tags)` - Add tags without removing existing ones
|
|
164
|
+
- `set_ticket_tags(ticket_id, tags)` - Replace all tags with a new set
|
|
165
|
+
- `remove_ticket_tags(ticket_id, tags)` - Remove specific tags
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Get current tags
|
|
169
|
+
tags = await client.get_ticket_tags(12345)
|
|
170
|
+
# ["billing", "urgent"]
|
|
171
|
+
|
|
172
|
+
# Add new tags (keeps existing)
|
|
173
|
+
tags = await client.add_ticket_tags(12345, ["vip"])
|
|
174
|
+
# ["billing", "urgent", "vip"]
|
|
175
|
+
|
|
176
|
+
# Replace all tags
|
|
177
|
+
tags = await client.set_ticket_tags(12345, ["support", "priority"])
|
|
178
|
+
# ["support", "priority"]
|
|
179
|
+
|
|
180
|
+
# Remove specific tags
|
|
181
|
+
tags = await client.remove_ticket_tags(12345, ["priority"])
|
|
182
|
+
# ["support"]
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Attachments
|
|
186
|
+
- `download_attachment(content_url)` - Download attachment content as bytes
|
|
187
|
+
- `upload_attachment(data, filename, content_type)` - Upload file and get token
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# Download an attachment from a comment
|
|
191
|
+
comments = await client.get_ticket_comments(12345)
|
|
192
|
+
for comment in comments:
|
|
193
|
+
for attachment in comment.attachments or []:
|
|
194
|
+
content = await client.download_attachment(attachment.content_url)
|
|
195
|
+
with open(attachment.file_name, "wb") as f:
|
|
196
|
+
f.write(content)
|
|
197
|
+
|
|
198
|
+
# Upload a file and attach to a comment
|
|
199
|
+
with open("screenshot.png", "rb") as f:
|
|
200
|
+
token = await client.upload_attachment(
|
|
201
|
+
f.read(),
|
|
202
|
+
"screenshot.png",
|
|
203
|
+
"image/png"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
await client.add_ticket_comment(
|
|
207
|
+
ticket_id=12345,
|
|
208
|
+
body="See attached screenshot",
|
|
209
|
+
uploads=[token]
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Search
|
|
214
|
+
- `search(query)` - General search
|
|
215
|
+
- `search_users(query)` - Search users
|
|
216
|
+
- `search_tickets(query)` - Search tickets
|
|
217
|
+
- `search_organizations(query)` - Search organizations
|
|
218
|
+
|
|
219
|
+
## Error Handling
|
|
220
|
+
|
|
221
|
+
The SDK provides specific exception classes for different error types:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from zendesk_sdk.exceptions import (
|
|
225
|
+
ZendeskAuthException,
|
|
226
|
+
ZendeskHTTPException,
|
|
227
|
+
ZendeskRateLimitException,
|
|
228
|
+
ZendeskTimeoutException,
|
|
229
|
+
ZendeskValidationException,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
async with ZendeskClient(config) as client:
|
|
233
|
+
try:
|
|
234
|
+
user = await client.get_user(user_id=12345)
|
|
235
|
+
except ZendeskAuthException as e:
|
|
236
|
+
# 401/403 - Authentication failed
|
|
237
|
+
print(f"Auth error: {e.message}")
|
|
238
|
+
except ZendeskRateLimitException as e:
|
|
239
|
+
# 429 - Rate limit exceeded
|
|
240
|
+
print(f"Rate limited, retry after: {e.retry_after}s")
|
|
241
|
+
except ZendeskHTTPException as e:
|
|
242
|
+
# Other HTTP errors (404, 500, etc.)
|
|
243
|
+
print(f"HTTP {e.status_code}: {e.message}")
|
|
244
|
+
except ZendeskTimeoutException as e:
|
|
245
|
+
# Request timeout
|
|
246
|
+
print(f"Timeout: {e.message}")
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Automatic Retry
|
|
250
|
+
|
|
251
|
+
The SDK automatically retries on:
|
|
252
|
+
- Rate limiting (429) - with respect to `Retry-After` header
|
|
253
|
+
- Server errors (5xx) - with exponential backoff
|
|
254
|
+
- Network errors and timeouts
|
|
255
|
+
|
|
256
|
+
Configure retry behavior:
|
|
257
|
+
```python
|
|
258
|
+
config = ZendeskConfig(
|
|
259
|
+
subdomain="mycompany",
|
|
260
|
+
email="user@example.com",
|
|
261
|
+
token="api_token",
|
|
262
|
+
timeout=30.0, # Request timeout in seconds
|
|
263
|
+
max_retries=3, # Number of retry attempts
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Examples
|
|
268
|
+
|
|
269
|
+
See the `examples/` directory for complete usage examples:
|
|
270
|
+
- `basic_usage.py` - Basic configuration and API operations
|
|
271
|
+
- `pagination_example.py` - Working with paginated results
|
|
272
|
+
- `error_handling.py` - Error handling patterns
|
|
273
|
+
- `enriched_tickets.py` - Loading tickets with related data
|
|
274
|
+
|
|
275
|
+
## Requirements
|
|
276
|
+
|
|
277
|
+
- Python 3.8+
|
|
278
|
+
- httpx
|
|
279
|
+
- pydantic >=2.0
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT License
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Python Zendesk SDK
|
|
2
|
+
|
|
3
|
+
Modern Python SDK for Zendesk API with async support, full type safety, and comprehensive error handling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Async HTTP Client**: Built on httpx with retry logic, rate limiting, and exponential backoff
|
|
8
|
+
- **Type Safety**: Full Pydantic v2 models for Users, Organizations, Tickets, and Comments
|
|
9
|
+
- **Pagination**: Both offset-based and cursor-based pagination support
|
|
10
|
+
- **Search**: Zendesk search API support
|
|
11
|
+
- **Configuration**: Flexible configuration with environment variable support
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install python-zendesk-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
from zendesk_sdk import ZendeskClient, ZendeskConfig
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
config = ZendeskConfig(
|
|
27
|
+
subdomain="your-subdomain",
|
|
28
|
+
email="your-email@example.com",
|
|
29
|
+
token="your-api-token",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
async with ZendeskClient(config) as client:
|
|
33
|
+
# Get users with pagination
|
|
34
|
+
users_paginator = await client.get_users(per_page=10)
|
|
35
|
+
users = await users_paginator.get_page()
|
|
36
|
+
|
|
37
|
+
for user in users:
|
|
38
|
+
print(f"User: {user.name} ({user.email})")
|
|
39
|
+
|
|
40
|
+
# Get specific ticket
|
|
41
|
+
ticket = await client.get_ticket(ticket_id=12345)
|
|
42
|
+
print(f"Ticket: {ticket.subject}")
|
|
43
|
+
|
|
44
|
+
# Search tickets
|
|
45
|
+
results = await client.search_tickets("status:open priority:high")
|
|
46
|
+
for ticket in results:
|
|
47
|
+
print(f"High priority: {ticket.subject}")
|
|
48
|
+
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
### Direct instantiation
|
|
55
|
+
```python
|
|
56
|
+
config = ZendeskConfig(
|
|
57
|
+
subdomain="mycompany",
|
|
58
|
+
email="user@example.com",
|
|
59
|
+
token="api_token_here"
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Environment variables
|
|
64
|
+
```bash
|
|
65
|
+
export ZENDESK_SUBDOMAIN=mycompany
|
|
66
|
+
export ZENDESK_EMAIL=user@example.com
|
|
67
|
+
export ZENDESK_TOKEN=api_token_here
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
config = ZendeskConfig() # Will load from environment
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## API Methods
|
|
75
|
+
|
|
76
|
+
### Users
|
|
77
|
+
- `get_users()` - List users with pagination
|
|
78
|
+
- `get_user(user_id)` - Get user by ID
|
|
79
|
+
- `get_user_by_email(email)` - Get user by email
|
|
80
|
+
|
|
81
|
+
### Organizations
|
|
82
|
+
- `get_organizations()` - List organizations with pagination
|
|
83
|
+
- `get_organization(organization_id)` - Get organization by ID
|
|
84
|
+
|
|
85
|
+
### Tickets
|
|
86
|
+
- `get_tickets()` - List tickets with pagination
|
|
87
|
+
- `get_ticket(ticket_id)` - Get ticket by ID
|
|
88
|
+
- `get_user_tickets(user_id)` - Get tickets for a user
|
|
89
|
+
- `get_organization_tickets(organization_id)` - Get tickets for an organization
|
|
90
|
+
|
|
91
|
+
### Enriched Tickets
|
|
92
|
+
|
|
93
|
+
Load tickets with all related data (comments + users) in minimum API requests:
|
|
94
|
+
|
|
95
|
+
- `get_enriched_ticket(ticket_id)` - Get ticket with comments and all users
|
|
96
|
+
- `search_enriched_tickets(query)` - Search tickets with all related data
|
|
97
|
+
- `get_organization_enriched_tickets(org_id)` - Get organization tickets with all data
|
|
98
|
+
- `get_user_enriched_tickets(user_id)` - Get user tickets with all data
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# Get ticket with all related data
|
|
102
|
+
enriched = await client.get_enriched_ticket(12345)
|
|
103
|
+
|
|
104
|
+
print(f"Ticket: {enriched.ticket.subject}")
|
|
105
|
+
print(f"Requester: {enriched.requester.name}")
|
|
106
|
+
print(f"Assignee: {enriched.assignee.name if enriched.assignee else 'Unassigned'}")
|
|
107
|
+
|
|
108
|
+
for comment in enriched.comments:
|
|
109
|
+
author = enriched.get_comment_author(comment)
|
|
110
|
+
print(f"Comment by {author.name}: {comment.body[:50]}...")
|
|
111
|
+
|
|
112
|
+
# Search with all data loaded
|
|
113
|
+
results = await client.search_enriched_tickets("status:open")
|
|
114
|
+
for item in results:
|
|
115
|
+
print(f"{item.ticket.subject} - {len(item.comments)} comments")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Comments
|
|
119
|
+
- `get_ticket_comments(ticket_id)` - Get comments for a ticket
|
|
120
|
+
- `add_ticket_comment(ticket_id, body, public=False)` - Add a comment (private by default)
|
|
121
|
+
- `make_comment_private(ticket_id, comment_id)` - Convert public comment to internal note
|
|
122
|
+
- `redact_comment_string(ticket_id, comment_id, text)` - Permanently redact text from comment
|
|
123
|
+
|
|
124
|
+
### Tags
|
|
125
|
+
- `get_ticket_tags(ticket_id)` - Get all tags for a ticket
|
|
126
|
+
- `add_ticket_tags(ticket_id, tags)` - Add tags without removing existing ones
|
|
127
|
+
- `set_ticket_tags(ticket_id, tags)` - Replace all tags with a new set
|
|
128
|
+
- `remove_ticket_tags(ticket_id, tags)` - Remove specific tags
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# Get current tags
|
|
132
|
+
tags = await client.get_ticket_tags(12345)
|
|
133
|
+
# ["billing", "urgent"]
|
|
134
|
+
|
|
135
|
+
# Add new tags (keeps existing)
|
|
136
|
+
tags = await client.add_ticket_tags(12345, ["vip"])
|
|
137
|
+
# ["billing", "urgent", "vip"]
|
|
138
|
+
|
|
139
|
+
# Replace all tags
|
|
140
|
+
tags = await client.set_ticket_tags(12345, ["support", "priority"])
|
|
141
|
+
# ["support", "priority"]
|
|
142
|
+
|
|
143
|
+
# Remove specific tags
|
|
144
|
+
tags = await client.remove_ticket_tags(12345, ["priority"])
|
|
145
|
+
# ["support"]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Attachments
|
|
149
|
+
- `download_attachment(content_url)` - Download attachment content as bytes
|
|
150
|
+
- `upload_attachment(data, filename, content_type)` - Upload file and get token
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Download an attachment from a comment
|
|
154
|
+
comments = await client.get_ticket_comments(12345)
|
|
155
|
+
for comment in comments:
|
|
156
|
+
for attachment in comment.attachments or []:
|
|
157
|
+
content = await client.download_attachment(attachment.content_url)
|
|
158
|
+
with open(attachment.file_name, "wb") as f:
|
|
159
|
+
f.write(content)
|
|
160
|
+
|
|
161
|
+
# Upload a file and attach to a comment
|
|
162
|
+
with open("screenshot.png", "rb") as f:
|
|
163
|
+
token = await client.upload_attachment(
|
|
164
|
+
f.read(),
|
|
165
|
+
"screenshot.png",
|
|
166
|
+
"image/png"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
await client.add_ticket_comment(
|
|
170
|
+
ticket_id=12345,
|
|
171
|
+
body="See attached screenshot",
|
|
172
|
+
uploads=[token]
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Search
|
|
177
|
+
- `search(query)` - General search
|
|
178
|
+
- `search_users(query)` - Search users
|
|
179
|
+
- `search_tickets(query)` - Search tickets
|
|
180
|
+
- `search_organizations(query)` - Search organizations
|
|
181
|
+
|
|
182
|
+
## Error Handling
|
|
183
|
+
|
|
184
|
+
The SDK provides specific exception classes for different error types:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from zendesk_sdk.exceptions import (
|
|
188
|
+
ZendeskAuthException,
|
|
189
|
+
ZendeskHTTPException,
|
|
190
|
+
ZendeskRateLimitException,
|
|
191
|
+
ZendeskTimeoutException,
|
|
192
|
+
ZendeskValidationException,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async with ZendeskClient(config) as client:
|
|
196
|
+
try:
|
|
197
|
+
user = await client.get_user(user_id=12345)
|
|
198
|
+
except ZendeskAuthException as e:
|
|
199
|
+
# 401/403 - Authentication failed
|
|
200
|
+
print(f"Auth error: {e.message}")
|
|
201
|
+
except ZendeskRateLimitException as e:
|
|
202
|
+
# 429 - Rate limit exceeded
|
|
203
|
+
print(f"Rate limited, retry after: {e.retry_after}s")
|
|
204
|
+
except ZendeskHTTPException as e:
|
|
205
|
+
# Other HTTP errors (404, 500, etc.)
|
|
206
|
+
print(f"HTTP {e.status_code}: {e.message}")
|
|
207
|
+
except ZendeskTimeoutException as e:
|
|
208
|
+
# Request timeout
|
|
209
|
+
print(f"Timeout: {e.message}")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Automatic Retry
|
|
213
|
+
|
|
214
|
+
The SDK automatically retries on:
|
|
215
|
+
- Rate limiting (429) - with respect to `Retry-After` header
|
|
216
|
+
- Server errors (5xx) - with exponential backoff
|
|
217
|
+
- Network errors and timeouts
|
|
218
|
+
|
|
219
|
+
Configure retry behavior:
|
|
220
|
+
```python
|
|
221
|
+
config = ZendeskConfig(
|
|
222
|
+
subdomain="mycompany",
|
|
223
|
+
email="user@example.com",
|
|
224
|
+
token="api_token",
|
|
225
|
+
timeout=30.0, # Request timeout in seconds
|
|
226
|
+
max_retries=3, # Number of retry attempts
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Examples
|
|
231
|
+
|
|
232
|
+
See the `examples/` directory for complete usage examples:
|
|
233
|
+
- `basic_usage.py` - Basic configuration and API operations
|
|
234
|
+
- `pagination_example.py` - Working with paginated results
|
|
235
|
+
- `error_handling.py` - Error handling patterns
|
|
236
|
+
- `enriched_tickets.py` - Loading tickets with related data
|
|
237
|
+
|
|
238
|
+
## Requirements
|
|
239
|
+
|
|
240
|
+
- Python 3.8+
|
|
241
|
+
- httpx
|
|
242
|
+
- pydantic >=2.0
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT License
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Basic usage example for Zendesk SDK.
|
|
2
|
+
|
|
3
|
+
This example demonstrates:
|
|
4
|
+
- Configuration setup
|
|
5
|
+
- Basic API operations (get users, tickets, organizations)
|
|
6
|
+
- Search functionality
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
from zendesk_sdk import ZendeskClient, ZendeskConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def main() -> None:
|
|
15
|
+
# Option 1: Direct configuration
|
|
16
|
+
config = ZendeskConfig(
|
|
17
|
+
subdomain="your-subdomain",
|
|
18
|
+
email="your-email@example.com",
|
|
19
|
+
token="your-api-token",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Option 2: Configuration from environment variables
|
|
23
|
+
# Set these environment variables:
|
|
24
|
+
# ZENDESK_SUBDOMAIN=your-subdomain
|
|
25
|
+
# ZENDESK_EMAIL=your-email@example.com
|
|
26
|
+
# ZENDESK_TOKEN=your-api-token
|
|
27
|
+
# config = ZendeskConfig()
|
|
28
|
+
|
|
29
|
+
# Use async context manager for proper resource cleanup
|
|
30
|
+
async with ZendeskClient(config) as client:
|
|
31
|
+
# Get a single user
|
|
32
|
+
user = await client.get_user(user_id=12345)
|
|
33
|
+
print(f"User: {user.name} ({user.email})")
|
|
34
|
+
|
|
35
|
+
# Find user by email
|
|
36
|
+
user_by_email = await client.get_user_by_email("user@example.com")
|
|
37
|
+
if user_by_email:
|
|
38
|
+
print(f"Found user: {user_by_email.name}")
|
|
39
|
+
|
|
40
|
+
# Get a single ticket
|
|
41
|
+
ticket = await client.get_ticket(ticket_id=12345)
|
|
42
|
+
print(f"Ticket: {ticket.subject} (status: {ticket.status})")
|
|
43
|
+
|
|
44
|
+
# Get ticket comments
|
|
45
|
+
comments = await client.get_ticket_comments(ticket_id=12345)
|
|
46
|
+
print(f"Ticket has {len(comments)} comments")
|
|
47
|
+
|
|
48
|
+
# Get organization
|
|
49
|
+
org = await client.get_organization(org_id=123)
|
|
50
|
+
print(f"Organization: {org.name}")
|
|
51
|
+
|
|
52
|
+
# Search for tickets
|
|
53
|
+
open_tickets = await client.search_tickets("status:open")
|
|
54
|
+
print(f"Found {len(open_tickets)} open tickets")
|
|
55
|
+
|
|
56
|
+
# Search for users
|
|
57
|
+
users = await client.search_users("role:admin")
|
|
58
|
+
print(f"Found {len(users)} admin users")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Enriched tickets example for Zendesk SDK.
|
|
2
|
+
|
|
3
|
+
This example demonstrates:
|
|
4
|
+
- Loading tickets with all related data (comments + users)
|
|
5
|
+
- Using EnrichedTicket for efficient data access
|
|
6
|
+
- Minimizing API requests with batch loading
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
from zendesk_sdk import ZendeskClient, ZendeskConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def main() -> None:
|
|
15
|
+
config = ZendeskConfig(
|
|
16
|
+
subdomain="your-subdomain",
|
|
17
|
+
email="your-email@example.com",
|
|
18
|
+
token="your-api-token",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
async with ZendeskClient(config) as client:
|
|
22
|
+
# Get a single ticket with all related data
|
|
23
|
+
# This makes 2 API calls: ticket + comments (with sideloaded users)
|
|
24
|
+
enriched = await client.get_enriched_ticket(ticket_id=12345)
|
|
25
|
+
|
|
26
|
+
print(f"Ticket: {enriched.ticket.subject}")
|
|
27
|
+
print(f"Status: {enriched.ticket.status}")
|
|
28
|
+
|
|
29
|
+
# Access requester directly
|
|
30
|
+
requester = enriched.requester
|
|
31
|
+
if requester:
|
|
32
|
+
print(f"Requester: {requester.name} ({requester.email})")
|
|
33
|
+
|
|
34
|
+
# Access assignee directly
|
|
35
|
+
assignee = enriched.assignee
|
|
36
|
+
if assignee:
|
|
37
|
+
print(f"Assignee: {assignee.name}")
|
|
38
|
+
else:
|
|
39
|
+
print("Ticket is unassigned")
|
|
40
|
+
|
|
41
|
+
# Process comments with author information
|
|
42
|
+
print(f"\nComments ({len(enriched.comments)}):")
|
|
43
|
+
for comment in enriched.comments:
|
|
44
|
+
author = enriched.get_comment_author(comment)
|
|
45
|
+
if author:
|
|
46
|
+
print(f" - {author.name}: {comment.body[:50]}...")
|
|
47
|
+
else:
|
|
48
|
+
print(f" - Unknown: {comment.body[:50]}...")
|
|
49
|
+
|
|
50
|
+
# Search for tickets and load all related data
|
|
51
|
+
# This efficiently batch-loads users using show_many endpoint
|
|
52
|
+
print("\n--- Searching tickets with enriched data ---")
|
|
53
|
+
results = await client.search_enriched_tickets(
|
|
54
|
+
query="status:open priority:high",
|
|
55
|
+
per_page=10,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for item in results:
|
|
59
|
+
print(f"\nTicket #{item.ticket.id}: {item.ticket.subject}")
|
|
60
|
+
print(f" Requester: {item.requester.name if item.requester else 'N/A'}")
|
|
61
|
+
print(f" Assignee: {item.assignee.name if item.assignee else 'Unassigned'}")
|
|
62
|
+
print(f" Comments: {len(item.comments)}")
|
|
63
|
+
|
|
64
|
+
# Get organization tickets with all related data
|
|
65
|
+
print("\n--- Organization tickets ---")
|
|
66
|
+
org_tickets = await client.get_organization_enriched_tickets(
|
|
67
|
+
org_id=123,
|
|
68
|
+
per_page=10,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
for item in org_tickets:
|
|
72
|
+
print(f"Ticket #{item.ticket.id}: {item.ticket.subject}")
|
|
73
|
+
|
|
74
|
+
# Get user's tickets with all related data
|
|
75
|
+
print("\n--- User tickets ---")
|
|
76
|
+
user_tickets = await client.get_user_enriched_tickets(
|
|
77
|
+
user_id=456,
|
|
78
|
+
per_page=10,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for item in user_tickets:
|
|
82
|
+
print(f"Ticket #{item.ticket.id}: {item.ticket.subject}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
asyncio.run(main())
|