python-zendesk-sdk 0.13.0__tar.gz → 0.14.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/.gitignore +4 -2
  2. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/PKG-INFO +76 -1
  3. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/README.md +75 -0
  4. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/pagination_example.py +19 -1
  5. python_zendesk_sdk-0.14.0/examples/ticket_metrics_example.py +74 -0
  6. python_zendesk_sdk-0.14.0/examples/views.py +84 -0
  7. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/pyproject.toml +1 -1
  8. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/__init__.py +11 -1
  9. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/client.py +54 -0
  10. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/__init__.py +4 -0
  11. python_zendesk_sdk-0.14.0/src/zendesk_sdk/clients/ticket_metrics.py +84 -0
  12. python_zendesk_sdk-0.14.0/src/zendesk_sdk/clients/views.py +198 -0
  13. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/config.py +4 -0
  14. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/__init__.py +4 -0
  15. python_zendesk_sdk-0.14.0/src/zendesk_sdk/models/view.py +69 -0
  16. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/pagination.py +102 -1
  17. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_package_import.py +1 -1
  18. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_pagination.py +112 -0
  19. python_zendesk_sdk-0.14.0/tests/test_ticket_metrics_client.py +114 -0
  20. python_zendesk_sdk-0.14.0/tests/test_views_client.py +248 -0
  21. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/.flake8 +0 -0
  22. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/.github/workflows/publish.yml +0 -0
  23. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/.python-version +0 -0
  24. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/LICENSE +0 -0
  25. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/context7.json +0 -0
  26. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/basic_usage.py +0 -0
  27. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/caching.py +0 -0
  28. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/enriched_tickets.py +0 -0
  29. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/error_handling.py +0 -0
  30. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/groups.py +0 -0
  31. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/help_center.py +0 -0
  32. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/organizations.py +0 -0
  33. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/search.py +0 -0
  34. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/examples/users.py +0 -0
  35. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/attachments.py +0 -0
  36. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/base.py +0 -0
  37. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/groups.py +0 -0
  38. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/help_center/__init__.py +0 -0
  39. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/help_center/articles.py +0 -0
  40. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/help_center/categories.py +0 -0
  41. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/help_center/sections.py +0 -0
  42. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/organizations.py +0 -0
  43. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/search.py +0 -0
  44. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/ticket_fields.py +0 -0
  45. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/tickets.py +0 -0
  46. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/clients/users.py +0 -0
  47. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/exceptions.py +0 -0
  48. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/http_client.py +0 -0
  49. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/base.py +0 -0
  50. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/comment.py +0 -0
  51. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/enriched_ticket.py +0 -0
  52. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/group.py +0 -0
  53. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/group_membership.py +0 -0
  54. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/help_center.py +0 -0
  55. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/organization.py +0 -0
  56. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/search.py +0 -0
  57. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/ticket.py +0 -0
  58. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/src/zendesk_sdk/models/user.py +0 -0
  59. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/__init__.py +0 -0
  60. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_client.py +0 -0
  61. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_clients.py +0 -0
  62. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_config.py +0 -0
  63. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_exceptions.py +0 -0
  64. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_help_center_client.py +0 -0
  65. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_http_client.py +0 -0
  66. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_models.py +0 -0
  67. {python_zendesk_sdk-0.13.0 → python_zendesk_sdk-0.14.0}/tests/test_search_query_config.py +0 -0
@@ -190,5 +190,7 @@ debug_*.py
190
190
 
191
191
  # Local directories
192
192
  .claude/
193
- llm/
194
- CLAUDE.md
193
+ CLAUDE.md
194
+ # Note: `llm/` is excluded via .git/info/exclude (local git-ignore not committed)
195
+ # so Claude Code can still see it via @-autocomplete, which honors .gitignore
196
+ # but not .git/info/exclude.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-zendesk-sdk
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Modern Python SDK for Zendesk API
5
5
  Project-URL: Homepage, https://github.com/bormog/python-zendesk-sdk
6
6
  Project-URL: Repository, https://github.com/bormog/python-zendesk-sdk
@@ -63,9 +63,11 @@ Modern Python SDK for Zendesk API, designed for automation and AI agents.
63
63
  - [Comments](#comments-nested-under-tickets)
64
64
  - [Tags](#tags-nested-under-tickets)
65
65
  - [Ticket Fields](#ticket-fields)
66
+ - [Ticket Metrics](#ticket-metrics)
66
67
  - [Enriched Tickets](#enriched-tickets)
67
68
  - [Attachments](#attachments)
68
69
  - [Search](#search)
70
+ - [Views](#views)
69
71
  - [Help Center](#help-center)
70
72
  - [Error Handling](#error-handling)
71
73
  - [Proactive Rate Limiting](#proactive-rate-limiting)
@@ -194,6 +196,11 @@ async for user in client.users.list():
194
196
 
195
197
  # 3. Collect to list
196
198
  users = await client.users.list(limit=50).collect()
199
+
200
+ # Get total count without iterating (uses Zendesk's count from response)
201
+ paginator = client.users.list()
202
+ total = await paginator.count() # async; issues a probe request if needed
203
+ cached = paginator.total_count # sync; None until at least one page fetched
197
204
  ```
198
205
 
199
206
  ### Users
@@ -379,6 +386,32 @@ field = await client.ticket_fields.get(field_id)
379
386
  field = await client.ticket_fields.get_by_title("Subscription")
380
387
  ```
381
388
 
389
+ ### Ticket Metrics
390
+
391
+ Read-only access to per-ticket metrics: first reply time, full resolution time,
392
+ agent/requester wait time, on-hold time. Every time field exposes both calendar
393
+ (wall-clock) and business (business-hours) values.
394
+
395
+ ```python
396
+ # Metrics for a specific ticket — primary use case
397
+ metrics = await client.ticket_metrics.for_ticket(ticket_id)
398
+ print(metrics.reply_time_in_minutes)
399
+ # {"calendar": 42, "business": 15}
400
+
401
+ # Metric by its own id
402
+ metrics = await client.ticket_metrics.get(metric_id)
403
+
404
+ # Iterate over all metrics (offset pagination)
405
+ async for m in client.ticket_metrics.list(per_page=100):
406
+ if m.full_resolution_time_in_minutes:
407
+ ...
408
+
409
+ # Total count (single extra request, see Pagination)
410
+ total = await client.ticket_metrics.list().count()
411
+ ```
412
+
413
+ Metrics are live data — the client intentionally does not cache responses.
414
+
382
415
  ### Enriched Tickets
383
416
 
384
417
  Load tickets with all related data (comments, users, field definitions) in minimum API requests:
@@ -543,6 +576,48 @@ async for ticket in client.search.export_tickets("priority:high", limit=500):
543
576
  | `search.tickets()` | 1000 max | Offset | Possible |
544
577
  | `search.export_tickets()` | None | Cursor | None |
545
578
 
579
+ ### Views
580
+
581
+ Views are saved searches over tickets — the day-to-day workspace of an
582
+ agent. Read-only access through `client.views`: list views, fetch a
583
+ view's tickets, count tickets without loading them. Useful for
584
+ LLM-agents (skip query construction — use views the support team has
585
+ already curated) and dashboards (counts in one request).
586
+
587
+ ```python
588
+ # List views
589
+ async for view in client.views.list():
590
+ print(f"{view.title} (active={view.active})")
591
+
592
+ # Active views only
593
+ active = await client.views.list(active_only=True).collect()
594
+
595
+ # Get a single view (cached)
596
+ view = await client.views.get(12345)
597
+ print(view.conditions) # raw {'all': [...], 'any': [...]}
598
+
599
+ # Get many views in one request (max 100 IDs)
600
+ views = await client.views.get_many([1, 2, 3])
601
+
602
+ # Iterate tickets in a view
603
+ async for ticket in client.views.tickets(12345):
604
+ print(f"#{ticket.id}: {ticket.subject}")
605
+
606
+ # Count tickets without loading them
607
+ count = await client.views.count(12345)
608
+ print(f"{count.value} tickets (fresh={count.fresh})")
609
+
610
+ # Counts for several views in one request (great for dashboards, max 20 IDs)
611
+ counts = await client.views.count_many([1, 2, 3])
612
+ for c in counts:
613
+ print(f"view {c.view_id}: {c.value}")
614
+ ```
615
+
616
+ > **Read-only** by design. Views are owned by admins and rarely edited
617
+ > via API. For very large views the count is served from a server-side
618
+ > cache — check `ViewCount.fresh` to know if it's authoritative.
619
+
620
+
546
621
  ### Help Center
547
622
 
548
623
  Access Help Center (Guide) via `client.help_center` namespace:
@@ -25,9 +25,11 @@ Modern Python SDK for Zendesk API, designed for automation and AI agents.
25
25
  - [Comments](#comments-nested-under-tickets)
26
26
  - [Tags](#tags-nested-under-tickets)
27
27
  - [Ticket Fields](#ticket-fields)
28
+ - [Ticket Metrics](#ticket-metrics)
28
29
  - [Enriched Tickets](#enriched-tickets)
29
30
  - [Attachments](#attachments)
30
31
  - [Search](#search)
32
+ - [Views](#views)
31
33
  - [Help Center](#help-center)
32
34
  - [Error Handling](#error-handling)
33
35
  - [Proactive Rate Limiting](#proactive-rate-limiting)
@@ -156,6 +158,11 @@ async for user in client.users.list():
156
158
 
157
159
  # 3. Collect to list
158
160
  users = await client.users.list(limit=50).collect()
161
+
162
+ # Get total count without iterating (uses Zendesk's count from response)
163
+ paginator = client.users.list()
164
+ total = await paginator.count() # async; issues a probe request if needed
165
+ cached = paginator.total_count # sync; None until at least one page fetched
159
166
  ```
160
167
 
161
168
  ### Users
@@ -341,6 +348,32 @@ field = await client.ticket_fields.get(field_id)
341
348
  field = await client.ticket_fields.get_by_title("Subscription")
342
349
  ```
343
350
 
351
+ ### Ticket Metrics
352
+
353
+ Read-only access to per-ticket metrics: first reply time, full resolution time,
354
+ agent/requester wait time, on-hold time. Every time field exposes both calendar
355
+ (wall-clock) and business (business-hours) values.
356
+
357
+ ```python
358
+ # Metrics for a specific ticket — primary use case
359
+ metrics = await client.ticket_metrics.for_ticket(ticket_id)
360
+ print(metrics.reply_time_in_minutes)
361
+ # {"calendar": 42, "business": 15}
362
+
363
+ # Metric by its own id
364
+ metrics = await client.ticket_metrics.get(metric_id)
365
+
366
+ # Iterate over all metrics (offset pagination)
367
+ async for m in client.ticket_metrics.list(per_page=100):
368
+ if m.full_resolution_time_in_minutes:
369
+ ...
370
+
371
+ # Total count (single extra request, see Pagination)
372
+ total = await client.ticket_metrics.list().count()
373
+ ```
374
+
375
+ Metrics are live data — the client intentionally does not cache responses.
376
+
344
377
  ### Enriched Tickets
345
378
 
346
379
  Load tickets with all related data (comments, users, field definitions) in minimum API requests:
@@ -505,6 +538,48 @@ async for ticket in client.search.export_tickets("priority:high", limit=500):
505
538
  | `search.tickets()` | 1000 max | Offset | Possible |
506
539
  | `search.export_tickets()` | None | Cursor | None |
507
540
 
541
+ ### Views
542
+
543
+ Views are saved searches over tickets — the day-to-day workspace of an
544
+ agent. Read-only access through `client.views`: list views, fetch a
545
+ view's tickets, count tickets without loading them. Useful for
546
+ LLM-agents (skip query construction — use views the support team has
547
+ already curated) and dashboards (counts in one request).
548
+
549
+ ```python
550
+ # List views
551
+ async for view in client.views.list():
552
+ print(f"{view.title} (active={view.active})")
553
+
554
+ # Active views only
555
+ active = await client.views.list(active_only=True).collect()
556
+
557
+ # Get a single view (cached)
558
+ view = await client.views.get(12345)
559
+ print(view.conditions) # raw {'all': [...], 'any': [...]}
560
+
561
+ # Get many views in one request (max 100 IDs)
562
+ views = await client.views.get_many([1, 2, 3])
563
+
564
+ # Iterate tickets in a view
565
+ async for ticket in client.views.tickets(12345):
566
+ print(f"#{ticket.id}: {ticket.subject}")
567
+
568
+ # Count tickets without loading them
569
+ count = await client.views.count(12345)
570
+ print(f"{count.value} tickets (fresh={count.fresh})")
571
+
572
+ # Counts for several views in one request (great for dashboards, max 20 IDs)
573
+ counts = await client.views.count_many([1, 2, 3])
574
+ for c in counts:
575
+ print(f"view {c.view_id}: {c.value}")
576
+ ```
577
+
578
+ > **Read-only** by design. Views are owned by admins and rarely edited
579
+ > via API. For very large views the count is served from a server-side
580
+ > cache — check `ViewCount.fresh` to know if it's authoritative.
581
+
582
+
508
583
  ### Help Center
509
584
 
510
585
  Access Help Center (Guide) via `client.help_center` namespace:
@@ -1,9 +1,10 @@
1
1
  """Pagination example for Zendesk SDK.
2
2
 
3
- This example demonstrates three ways to work with paginators:
3
+ This example demonstrates four ways to work with paginators:
4
4
  1. Get specific page
5
5
  2. Iterate through all items
6
6
  3. Collect to list
7
+ 4. Ask for total count without collecting
7
8
 
8
9
  All list() and search() methods return Paginator objects.
9
10
  """
@@ -68,6 +69,23 @@ async def main() -> None:
68
69
  # Collect all (be careful with large datasets!)
69
70
  # all_users = await client.users.list().collect()
70
71
 
72
+ # ==================== Method 4: Total count only ====================
73
+
74
+ print("\n=== Method 4: Total count without collecting ===")
75
+
76
+ # count() issues a lightweight probe (per_page=1) when no page has been
77
+ # fetched yet; returns the cached count otherwise. Does not mutate state.
78
+ total_open = await client.search.tickets("status:open").count()
79
+ print(f"Open tickets in total: {total_open}")
80
+
81
+ # After iteration / get_page(), total_count is a free sync property.
82
+ paginator = client.users.list(per_page=100)
83
+ await paginator.get_page()
84
+ print(f"Users total_count (cached): {paginator.total_count}")
85
+
86
+ # Cursor-based paginators (incremental, search export) return None:
87
+ # the Zendesk API does not expose a total for them.
88
+
71
89
  # ==================== Pagination with search ====================
72
90
 
73
91
  print("\n=== Pagination with search ===")
@@ -0,0 +1,74 @@
1
+ """Ticket Metrics example for Zendesk SDK.
2
+
3
+ Three ways to use ticket_metrics:
4
+ 1. Get metrics for a specific ticket (primary use case)
5
+ 2. Iterate over all metrics
6
+ 3. Find tickets that took long to resolve
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
+ # ==================== Method 1: Metrics for a ticket ====================
23
+
24
+ print("=== Method 1: Metrics for a specific ticket ===")
25
+
26
+ ticket_id = 12345
27
+ metrics = await client.ticket_metrics.for_ticket(ticket_id)
28
+
29
+ # Time fields are dicts with "calendar" (wall-clock) and "business"
30
+ # (business hours) sub-values, either of which may be None.
31
+ reply = metrics.reply_time_in_minutes or {}
32
+ resolution = metrics.full_resolution_time_in_minutes or {}
33
+
34
+ print(f"Ticket #{metrics.ticket_id}")
35
+ print(f" Replies: {metrics.replies}")
36
+ print(f" Reply time (calendar): {reply.get('calendar')} min")
37
+ print(f" Reply time (business): {reply.get('business')} min")
38
+ print(f" Full resolution (calendar): {resolution.get('calendar')} min")
39
+ print(f" Reopens: {metrics.reopens}")
40
+
41
+ # ==================== Method 2: Iterate all ====================
42
+
43
+ print("\n=== Method 2: Iterate over all metrics ===")
44
+
45
+ total = await client.ticket_metrics.list().count()
46
+ print(f"Total ticket_metrics: {total}")
47
+
48
+ count = 0
49
+ async for m in client.ticket_metrics.list(per_page=100, limit=10):
50
+ count += 1
51
+ print(f" #{m.ticket_id}: replies={m.replies}, reopens={m.reopens}")
52
+
53
+ print(f"Looked at {count} metric records")
54
+
55
+ # ==================== Method 3: Long-to-resolve tickets =================
56
+
57
+ print("\n=== Method 3: Tickets that took > 1 day of business hours ===")
58
+
59
+ threshold_minutes = 24 * 60
60
+ slow = []
61
+ async for m in client.ticket_metrics.list(limit=500):
62
+ resolution = m.full_resolution_time_in_minutes or {}
63
+ business = resolution.get("business")
64
+ if business is not None and business > threshold_minutes:
65
+ slow.append((m.ticket_id, business))
66
+
67
+ slow.sort(key=lambda pair: pair[1], reverse=True)
68
+ for ticket_id, business_minutes in slow[:10]:
69
+ hours = business_minutes / 60
70
+ print(f" #{ticket_id}: {hours:.1f} business hours to resolve")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ asyncio.run(main())
@@ -0,0 +1,84 @@
1
+ """Views API example for Zendesk SDK.
2
+
3
+ This example demonstrates read-only operations on Views:
4
+ - Listing views (all + active only)
5
+ - Getting a single view (cached)
6
+ - Iterating tickets in a view
7
+ - Counting tickets in views (single + bulk)
8
+ """
9
+
10
+ import asyncio
11
+
12
+ from zendesk_sdk import ZendeskClient, ZendeskConfig
13
+
14
+
15
+ async def main() -> None:
16
+ config = ZendeskConfig(
17
+ subdomain="your-subdomain",
18
+ email="your-email@example.com",
19
+ token="your-api-token",
20
+ )
21
+
22
+ async with ZendeskClient(config) as client:
23
+ # ==================== List views ====================
24
+
25
+ print("=== All views ===")
26
+ async for view in client.views.list(limit=10):
27
+ status = "active" if view.active else "inactive"
28
+ default = " (default)" if view.default else ""
29
+ print(f" {view.id}: {view.title} [{status}]{default}")
30
+
31
+ print("\n=== Active views only ===")
32
+ active_views = await client.views.list(active_only=True, limit=10).collect()
33
+ for view in active_views:
34
+ print(f" {view.id}: {view.title}")
35
+
36
+ if not active_views:
37
+ print(" (no active views found)")
38
+ return
39
+
40
+ sample_view = active_views[0]
41
+
42
+ # ==================== Get a single view ====================
43
+
44
+ print(f"\n=== Get view {sample_view.id} ===")
45
+ view = await client.views.get(sample_view.id) # type: ignore[arg-type]
46
+ print(f" Title: {view.title}")
47
+ print(f" Description: {view.description or '(none)'}")
48
+ print(f" Conditions: {view.conditions}")
49
+
50
+ # Second call hits the cache
51
+ view_cached = await client.views.get(sample_view.id) # type: ignore[arg-type]
52
+ print(f" (cached) Title: {view_cached.title}")
53
+
54
+ # ==================== get_many ====================
55
+
56
+ print("\n=== get_many ===")
57
+ ids = [v.id for v in active_views[:3] if v.id is not None]
58
+ many = await client.views.get_many(ids)
59
+ for vid, v in many.items():
60
+ print(f" {vid}: {v.title}")
61
+
62
+ # ==================== Tickets in a view ====================
63
+
64
+ print(f"\n=== First 5 tickets in view {sample_view.id} ===")
65
+ async for ticket in client.views.tickets(sample_view.id, limit=5): # type: ignore[arg-type]
66
+ print(f" #{ticket.id}: {ticket.subject} [{ticket.status}]")
67
+
68
+ # ==================== Count tickets in a view ====================
69
+
70
+ print(f"\n=== Count for view {sample_view.id} ===")
71
+ count = await client.views.count(sample_view.id) # type: ignore[arg-type]
72
+ staleness = "fresh" if count.fresh else "stale (cached server-side)"
73
+ print(f" {count.value} tickets ({staleness})")
74
+
75
+ # ==================== count_many for a dashboard ====================
76
+
77
+ print("\n=== count_many for several views ===")
78
+ counts = await client.views.count_many(ids)
79
+ for c in counts:
80
+ print(f" view {c.view_id}: {c.value} tickets")
81
+
82
+
83
+ if __name__ == "__main__":
84
+ asyncio.run(main())
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-zendesk-sdk"
3
- version = "0.13.0"
3
+ version = "0.14.0"
4
4
  description = "Modern Python SDK for Zendesk API"
5
5
  authors = [
6
6
  {name = "bormog"}
@@ -5,7 +5,7 @@ This package provides a clean, async-first interface to the Zendesk API
5
5
  with full type safety and comprehensive error handling.
6
6
  """
7
7
 
8
- __version__ = "0.13.0"
8
+ __version__ = "0.14.0"
9
9
 
10
10
  from .client import ZendeskClient
11
11
  from .clients import (
@@ -20,8 +20,10 @@ from .clients import (
20
20
  SectionsClient,
21
21
  TagsClient,
22
22
  TicketFieldsClient,
23
+ TicketMetricsClient,
23
24
  TicketsClient,
24
25
  UsersClient,
26
+ ViewsClient,
25
27
  )
26
28
  from .config import CacheConfig, ZendeskConfig
27
29
  from .exceptions import (
@@ -46,6 +48,7 @@ from .models import (
46
48
  SortOrder,
47
49
  TicketChannel,
48
50
  TicketField,
51
+ TicketMetrics,
49
52
  TicketPriority,
50
53
  TicketPriorityInput,
51
54
  TicketStatus,
@@ -53,6 +56,8 @@ from .models import (
53
56
  TicketType,
54
57
  TicketTypeInput,
55
58
  UserRole,
59
+ View,
60
+ ViewCount,
56
61
  )
57
62
 
58
63
  __all__ = [
@@ -66,10 +71,12 @@ __all__ = [
66
71
  "OrganizationsClient",
67
72
  "TicketsClient",
68
73
  "TicketFieldsClient",
74
+ "TicketMetricsClient",
69
75
  "CommentsClient",
70
76
  "TagsClient",
71
77
  "AttachmentsClient",
72
78
  "SearchClient",
79
+ "ViewsClient",
73
80
  # Help Center
74
81
  "HelpCenterClient",
75
82
  "CategoriesClient",
@@ -80,9 +87,12 @@ __all__ = [
80
87
  "GroupMembership",
81
88
  "EnrichedTicket",
82
89
  "TicketField",
90
+ "TicketMetrics",
83
91
  "Category",
84
92
  "Section",
85
93
  "Article",
94
+ "View",
95
+ "ViewCount",
86
96
  # Search
87
97
  "SearchQueryConfig",
88
98
  "SearchType",
@@ -14,8 +14,10 @@ if TYPE_CHECKING:
14
14
  OrganizationsClient,
15
15
  SearchClient,
16
16
  TicketFieldsClient,
17
+ TicketMetricsClient,
17
18
  TicketsClient,
18
19
  UsersClient,
20
+ ViewsClient,
19
21
  )
20
22
 
21
23
 
@@ -204,6 +206,30 @@ class ZendeskClient:
204
206
 
205
207
  return TicketFieldsClient(self.http_client, self.config.cache)
206
208
 
209
+ @cached_property
210
+ def ticket_metrics(self) -> "TicketMetricsClient":
211
+ """Access Ticket Metrics API.
212
+
213
+ Read-only access to per-ticket metrics: first reply time, full
214
+ resolution time, agent/requester wait time, on-hold time, etc.
215
+ Time fields expose both calendar and business hours.
216
+
217
+ Example:
218
+ # Metrics for a specific ticket (primary use case)
219
+ metrics = await client.ticket_metrics.for_ticket(12345)
220
+ print(metrics.reply_time_in_minutes) # {"calendar": 42, "business": 15}
221
+
222
+ # Metric by its own id
223
+ metrics = await client.ticket_metrics.get(999)
224
+
225
+ # Iterate over all metrics
226
+ async for m in client.ticket_metrics.list():
227
+ ...
228
+ """
229
+ from .clients import TicketMetricsClient
230
+
231
+ return TicketMetricsClient(self.http_client)
232
+
207
233
  @cached_property
208
234
  def attachments(self) -> "AttachmentsClient":
209
235
  """Access Attachments API.
@@ -234,6 +260,34 @@ class ZendeskClient:
234
260
 
235
261
  return SearchClient(self.http_client)
236
262
 
263
+ @cached_property
264
+ def views(self) -> "ViewsClient":
265
+ """Access Views API (read-only).
266
+
267
+ Views are saved searches over tickets — the way agents work
268
+ with tickets day-to-day. Read-only: list, get, tickets, count,
269
+ count_many.
270
+
271
+ Example:
272
+ # List all views
273
+ async for view in client.views.list():
274
+ print(f"{view.title} (active={view.active})")
275
+
276
+ # Get a specific view (cached)
277
+ view = await client.views.get(12345)
278
+
279
+ # Tickets in a view
280
+ async for ticket in client.views.tickets(12345):
281
+ print(ticket.subject)
282
+
283
+ # Counts without loading tickets
284
+ count = await client.views.count(12345)
285
+ counts = await client.views.count_many([1, 2, 3])
286
+ """
287
+ from .clients import ViewsClient
288
+
289
+ return ViewsClient(self.http_client, self.config.cache)
290
+
237
291
  @cached_property
238
292
  def help_center(self) -> "HelpCenterClient":
239
293
  """Access Help Center API with nested categories, sections, articles.
@@ -6,8 +6,10 @@ from .help_center import ArticlesClient, CategoriesClient, HelpCenterClient, Sec
6
6
  from .organizations import OrganizationsClient
7
7
  from .search import SearchClient
8
8
  from .ticket_fields import TicketFieldsClient
9
+ from .ticket_metrics import TicketMetricsClient
9
10
  from .tickets import CommentsClient, TagsClient, TicketsClient
10
11
  from .users import UsersClient
12
+ from .views import ViewsClient
11
13
 
12
14
  __all__ = [
13
15
  # Main clients
@@ -16,8 +18,10 @@ __all__ = [
16
18
  "OrganizationsClient",
17
19
  "TicketsClient",
18
20
  "TicketFieldsClient",
21
+ "TicketMetricsClient",
19
22
  "AttachmentsClient",
20
23
  "SearchClient",
24
+ "ViewsClient",
21
25
  # Ticket sub-clients
22
26
  "CommentsClient",
23
27
  "TagsClient",
@@ -0,0 +1,84 @@
1
+ """Ticket Metrics API client."""
2
+
3
+ from typing import TYPE_CHECKING, Optional
4
+
5
+ from ..models.ticket import TicketMetrics
6
+ from ..pagination import ZendeskPaginator
7
+ from .base import BaseClient
8
+
9
+ if TYPE_CHECKING:
10
+ from ..http_client import HTTPClient
11
+ from ..pagination import Paginator
12
+
13
+
14
+ class TicketMetricsClient(BaseClient):
15
+ """Client for Zendesk Ticket Metrics API.
16
+
17
+ Provides read-only access to per-ticket metrics: first reply time,
18
+ full resolution time, requester/agent wait time, on-hold time, etc.
19
+ All time-based fields expose both calendar and business hours.
20
+
21
+ Metrics are live data — they change with every ticket update, so
22
+ responses are not cached.
23
+
24
+ Example:
25
+ async with ZendeskClient(config) as client:
26
+ # Metrics for a specific ticket (most common)
27
+ metrics = await client.ticket_metrics.for_ticket(12345)
28
+ print(metrics.reply_time_in_minutes)
29
+ # {"calendar": 42, "business": 15}
30
+
31
+ # Metric by its own id
32
+ metrics = await client.ticket_metrics.get(999)
33
+
34
+ # Iterate over all metrics
35
+ async for m in client.ticket_metrics.list(per_page=100):
36
+ if m.full_resolution_time_in_minutes:
37
+ ...
38
+ """
39
+
40
+ def __init__(self, http_client: "HTTPClient") -> None:
41
+ """Initialize TicketMetricsClient.
42
+
43
+ No cache: metrics are live data and change with each ticket update.
44
+ """
45
+ super().__init__(http_client)
46
+
47
+ async def get(self, metric_id: int) -> TicketMetrics:
48
+ """Get a ticket metric by its own id.
49
+
50
+ Args:
51
+ metric_id: The ticket metric id (not the ticket id)
52
+
53
+ Returns:
54
+ TicketMetrics object
55
+ """
56
+ response = await self._get(f"ticket_metrics/{metric_id}.json")
57
+ return TicketMetrics(**response["ticket_metric"])
58
+
59
+ async def for_ticket(self, ticket_id: int) -> TicketMetrics:
60
+ """Get metrics for a given ticket.
61
+
62
+ This is the primary use case — look up metrics for a ticket
63
+ you already have an id for.
64
+
65
+ Args:
66
+ ticket_id: The ticket id
67
+
68
+ Returns:
69
+ TicketMetrics object
70
+ """
71
+ response = await self._get(f"tickets/{ticket_id}/metrics.json")
72
+ return TicketMetrics(**response["ticket_metric"])
73
+
74
+ def list(self, per_page: int = 100, limit: Optional[int] = None) -> "Paginator[TicketMetrics]":
75
+ """Get paginated list of all ticket metrics.
76
+
77
+ Args:
78
+ per_page: Number of metrics per page (max 100)
79
+ limit: Maximum total items when iterating (None = no limit)
80
+
81
+ Returns:
82
+ Paginator yielding TicketMetrics objects
83
+ """
84
+ return ZendeskPaginator.create_ticket_metrics_paginator(self._http, per_page=per_page, limit=limit)