python-zendesk-sdk 0.10.0__tar.gz → 0.11.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 (59) hide show
  1. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/PKG-INFO +6 -2
  2. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/README.md +5 -1
  3. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/enriched_tickets.py +18 -0
  4. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/pyproject.toml +1 -1
  5. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/__init__.py +1 -1
  6. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/tickets.py +55 -0
  7. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_clients.py +93 -1
  8. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_package_import.py +1 -1
  9. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/.flake8 +0 -0
  10. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/.github/workflows/publish.yml +0 -0
  11. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/.gitignore +0 -0
  12. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/.python-version +0 -0
  13. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/LICENSE +0 -0
  14. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/context7.json +0 -0
  15. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/basic_usage.py +0 -0
  16. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/caching.py +0 -0
  17. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/error_handling.py +0 -0
  18. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/groups.py +0 -0
  19. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/help_center.py +0 -0
  20. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/organizations.py +0 -0
  21. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/pagination_example.py +0 -0
  22. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/search.py +0 -0
  23. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/examples/users.py +0 -0
  24. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/client.py +0 -0
  25. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/__init__.py +0 -0
  26. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/attachments.py +0 -0
  27. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/base.py +0 -0
  28. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/groups.py +0 -0
  29. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/help_center/__init__.py +0 -0
  30. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/help_center/articles.py +0 -0
  31. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/help_center/categories.py +0 -0
  32. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/help_center/sections.py +0 -0
  33. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/organizations.py +0 -0
  34. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/search.py +0 -0
  35. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/ticket_fields.py +0 -0
  36. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/clients/users.py +0 -0
  37. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/config.py +0 -0
  38. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/exceptions.py +0 -0
  39. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/http_client.py +0 -0
  40. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/__init__.py +0 -0
  41. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/base.py +0 -0
  42. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/comment.py +0 -0
  43. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/enriched_ticket.py +0 -0
  44. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/group.py +0 -0
  45. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/help_center.py +0 -0
  46. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/organization.py +0 -0
  47. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/search.py +0 -0
  48. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/ticket.py +0 -0
  49. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/models/user.py +0 -0
  50. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/src/zendesk_sdk/pagination.py +0 -0
  51. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/__init__.py +0 -0
  52. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_client.py +0 -0
  53. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_config.py +0 -0
  54. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_exceptions.py +0 -0
  55. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_help_center_client.py +0 -0
  56. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_http_client.py +0 -0
  57. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_models.py +0 -0
  58. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_pagination.py +0 -0
  59. {python_zendesk_sdk-0.10.0 → python_zendesk_sdk-0.11.0}/tests/test_search_query_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-zendesk-sdk
3
- Version: 0.10.0
3
+ Version: 0.11.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
@@ -289,6 +289,7 @@ await client.groups.delete(group_id)
289
289
  ```python
290
290
  # Read
291
291
  ticket = await client.tickets.get(ticket_id) # Get ticket by ID
292
+ tickets = await client.tickets.get_many([id1, id2]) # Get multiple tickets (batch)
292
293
  paginator = client.tickets.list() # List tickets (paginator)
293
294
  paginator = client.tickets.for_user(user_id) # User's tickets (paginator)
294
295
  paginator = client.tickets.for_organization(org_id) # Org's tickets (paginator)
@@ -358,9 +359,12 @@ Load tickets with all related data (comments, users, field definitions) in minim
358
359
  ```python
359
360
  from zendesk_sdk import SearchQueryConfig
360
361
 
361
- # Get ticket with all related data
362
+ # Get single ticket with all related data
362
363
  enriched = await client.tickets.get_enriched(12345)
363
364
 
365
+ # Batch: get multiple enriched tickets (much faster than calling get_enriched in a loop)
366
+ enriched_list = await client.tickets.get_many_enriched([12345, 12346, 12347])
367
+
364
368
  print(f"Ticket: {enriched.ticket.subject}")
365
369
  print(f"Requester: {enriched.requester.name}")
366
370
  print(f"Assignee: {enriched.assignee.name if enriched.assignee else 'Unassigned'}")
@@ -251,6 +251,7 @@ await client.groups.delete(group_id)
251
251
  ```python
252
252
  # Read
253
253
  ticket = await client.tickets.get(ticket_id) # Get ticket by ID
254
+ tickets = await client.tickets.get_many([id1, id2]) # Get multiple tickets (batch)
254
255
  paginator = client.tickets.list() # List tickets (paginator)
255
256
  paginator = client.tickets.for_user(user_id) # User's tickets (paginator)
256
257
  paginator = client.tickets.for_organization(org_id) # Org's tickets (paginator)
@@ -320,9 +321,12 @@ Load tickets with all related data (comments, users, field definitions) in minim
320
321
  ```python
321
322
  from zendesk_sdk import SearchQueryConfig
322
323
 
323
- # Get ticket with all related data
324
+ # Get single ticket with all related data
324
325
  enriched = await client.tickets.get_enriched(12345)
325
326
 
327
+ # Batch: get multiple enriched tickets (much faster than calling get_enriched in a loop)
328
+ enriched_list = await client.tickets.get_many_enriched([12345, 12346, 12347])
329
+
326
330
  print(f"Ticket: {enriched.ticket.subject}")
327
331
  print(f"Requester: {enriched.requester.name}")
328
332
  print(f"Assignee: {enriched.assignee.name if enriched.assignee else 'Unassigned'}")
@@ -2,6 +2,7 @@
2
2
 
3
3
  This example demonstrates:
4
4
  - Loading tickets with all related data (comments, users, field definitions)
5
+ - Batch loading multiple enriched tickets with get_many_enriched()
5
6
  - Using EnrichedTicket for efficient data access
6
7
  - Accessing custom field values with human-readable names
7
8
  - Minimizing API requests with batch loading
@@ -81,6 +82,23 @@ async def main() -> None:
81
82
  else:
82
83
  print(f" - Unknown: {body_preview}")
83
84
 
85
+ # ==================== Batch enriched tickets ====================
86
+
87
+ # Load multiple tickets with all related data at once
88
+ # Much more efficient than calling get_enriched() in a loop:
89
+ # - 1 API call for all tickets (show_many)
90
+ # - 1 API call for all users (show_many)
91
+ # - 1 API call for field definitions
92
+ # - N parallel API calls for comments (one per ticket)
93
+ ticket_ids = [12345, 12346, 12347]
94
+ enriched_list = await client.tickets.get_many_enriched(ticket_ids)
95
+
96
+ print(f"\n--- Batch loaded {len(enriched_list)} enriched tickets ---")
97
+ for item in enriched_list:
98
+ print(f" #{item.ticket.id}: {item.ticket.subject}")
99
+ print(f" Requester: {item.requester.name if item.requester else 'N/A'}")
100
+ print(f" Comments: {len(item.comments)}")
101
+
84
102
  # ==================== Search with enrichment ====================
85
103
 
86
104
  # Search for tickets and load all related data
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-zendesk-sdk"
3
- version = "0.10.0"
3
+ version = "0.11.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.10.0"
8
+ __version__ = "0.11.0"
9
9
 
10
10
  from .client import ZendeskClient
11
11
  from .clients import (
@@ -375,6 +375,32 @@ class TicketsClient(BaseClient):
375
375
  response = await self._get(f"tickets/{ticket_id}.json")
376
376
  return Ticket(**response["ticket"])
377
377
 
378
+ async def get_many(self, ticket_ids: List[int]) -> Dict[int, Ticket]:
379
+ """Fetch multiple tickets by IDs.
380
+
381
+ Uses show_many endpoint for efficiency (max 100 IDs per request).
382
+
383
+ Args:
384
+ ticket_ids: List of ticket IDs to fetch
385
+
386
+ Returns:
387
+ Dictionary mapping ticket_id to Ticket object
388
+ """
389
+ if not ticket_ids:
390
+ return {}
391
+
392
+ unique_ids = list(set(ticket_ids))[:100]
393
+ ids_param = ",".join(str(tid) for tid in unique_ids)
394
+
395
+ response = await self._get(f"tickets/show_many.json?ids={ids_param}")
396
+
397
+ tickets: Dict[int, Ticket] = {}
398
+ for ticket_data in response.get("tickets", []):
399
+ ticket = Ticket(**ticket_data)
400
+ if ticket.id is not None:
401
+ tickets[ticket.id] = ticket
402
+ return tickets
403
+
378
404
  def list(self, per_page: int = 100, limit: Optional[int] = None) -> "Paginator[Ticket]":
379
405
  """Get paginated list of all tickets in the account.
380
406
 
@@ -844,6 +870,35 @@ class TicketsClient(BaseClient):
844
870
  ticket_users = self._extract_users_from_response(response)
845
871
  return await self._build_enriched_ticket(ticket, ticket_users, fields)
846
872
 
873
+ async def get_many_enriched(self, ticket_ids: List[int]) -> List[EnrichedTicket]:
874
+ """Get multiple tickets with all related data: comments, users, and field definitions.
875
+
876
+ Batch-loads tickets, users, and fields in parallel, then fetches comments
877
+ for each ticket. Much more efficient than calling get_enriched() in a loop.
878
+
879
+ Args:
880
+ ticket_ids: List of ticket IDs to fetch (max 100)
881
+
882
+ Returns:
883
+ List of EnrichedTicket objects
884
+ """
885
+ if not ticket_ids:
886
+ return []
887
+
888
+ # Fetch tickets and fields in parallel (independent calls)
889
+ tickets_dict, fields = await asyncio.gather(
890
+ self.get_many(ticket_ids),
891
+ self._fetch_fields(),
892
+ )
893
+ if not tickets_dict:
894
+ return []
895
+
896
+ tickets = list(tickets_dict.values())
897
+ user_ids = self._collect_user_ids_from_tickets(tickets)
898
+ ticket_users = await self._fetch_users_batch(user_ids)
899
+
900
+ return await self._build_enriched_tickets(tickets, ticket_users, fields)
901
+
847
902
  async def search_enriched(
848
903
  self,
849
904
  query: Union[str, SearchQueryConfig],
@@ -13,7 +13,7 @@ from zendesk_sdk.clients import (
13
13
  TicketsClient,
14
14
  UsersClient,
15
15
  )
16
- from zendesk_sdk.models import Comment, Organization, Ticket, User
16
+ from zendesk_sdk.models import Comment, EnrichedTicket, Organization, Ticket, User
17
17
 
18
18
 
19
19
  class TestUsersClient:
@@ -960,6 +960,98 @@ class TestTicketsClient:
960
960
  assert result is True
961
961
  mock_delete.assert_called_once_with("tickets/12345.json")
962
962
 
963
+ @pytest.mark.asyncio
964
+ async def test_get_many(self):
965
+ """Test batch get tickets by IDs."""
966
+ client = self.get_client()
967
+ response_data = {
968
+ "tickets": [
969
+ {"id": 1, "subject": "Ticket 1", "status": "open", "created_at": "2023-01-01T00:00:00Z"},
970
+ {"id": 2, "subject": "Ticket 2", "status": "pending", "created_at": "2023-01-02T00:00:00Z"},
971
+ {"id": 3, "subject": "Ticket 3", "status": "solved", "created_at": "2023-01-03T00:00:00Z"},
972
+ ]
973
+ }
974
+
975
+ with patch.object(client, "_get", new_callable=AsyncMock) as mock_get:
976
+ mock_get.return_value = response_data
977
+
978
+ result = await client.get_many([1, 2, 3])
979
+
980
+ assert len(result) == 3
981
+ assert isinstance(result[1], Ticket)
982
+ assert result[1].subject == "Ticket 1"
983
+ assert result[2].subject == "Ticket 2"
984
+ assert result[3].subject == "Ticket 3"
985
+ mock_get.assert_called_once()
986
+ call_url = mock_get.call_args[0][0]
987
+ assert call_url.startswith("tickets/show_many.json?ids=")
988
+
989
+ @pytest.mark.asyncio
990
+ async def test_get_many_empty(self):
991
+ """Test batch get tickets with empty list."""
992
+ client = self.get_client()
993
+
994
+ result = await client.get_many([])
995
+
996
+ assert result == {}
997
+
998
+ @pytest.mark.asyncio
999
+ async def test_get_many_deduplicates(self):
1000
+ """Test batch get tickets deduplicates IDs."""
1001
+ client = self.get_client()
1002
+ response_data = {
1003
+ "tickets": [
1004
+ {"id": 1, "subject": "Ticket 1", "status": "open", "created_at": "2023-01-01T00:00:00Z"},
1005
+ ]
1006
+ }
1007
+
1008
+ with patch.object(client, "_get", new_callable=AsyncMock) as mock_get:
1009
+ mock_get.return_value = response_data
1010
+
1011
+ result = await client.get_many([1, 1, 1])
1012
+
1013
+ assert len(result) == 1
1014
+ call_url = mock_get.call_args[0][0]
1015
+ assert "ids=1" in call_url
1016
+
1017
+ @pytest.mark.asyncio
1018
+ async def test_get_many_enriched(self):
1019
+ """Test batch get enriched tickets."""
1020
+ client = self.get_client()
1021
+
1022
+ tickets_dict = {
1023
+ 1: Ticket(id=1, subject="T1", status="open", requester_id=100, created_at="2023-01-01T00:00:00Z"),
1024
+ 2: Ticket(id=2, subject="T2", status="open", requester_id=200, created_at="2023-01-02T00:00:00Z"),
1025
+ }
1026
+ mock_users = {100: User(id=100, name="User A"), 200: User(id=200, name="User B")}
1027
+ mock_enriched = [
1028
+ EnrichedTicket(ticket=tickets_dict[1], comments=[], users={100: mock_users[100]}, fields={}),
1029
+ EnrichedTicket(ticket=tickets_dict[2], comments=[], users={200: mock_users[200]}, fields={}),
1030
+ ]
1031
+
1032
+ with (
1033
+ patch.object(client, "get_many", new_callable=AsyncMock, return_value=tickets_dict) as mock_get_many,
1034
+ patch.object(client, "_fetch_users_batch", new_callable=AsyncMock, return_value=mock_users),
1035
+ patch.object(client, "_fetch_fields", new_callable=AsyncMock, return_value={}),
1036
+ patch.object(client, "_build_enriched_tickets", new_callable=AsyncMock, return_value=mock_enriched),
1037
+ ):
1038
+ result = await client.get_many_enriched([1, 2])
1039
+
1040
+ assert len(result) == 2
1041
+ assert isinstance(result[0], EnrichedTicket)
1042
+ assert result[0].ticket.id == 1
1043
+ assert result[1].ticket.id == 2
1044
+ mock_get_many.assert_called_once_with([1, 2])
1045
+
1046
+ @pytest.mark.asyncio
1047
+ async def test_get_many_enriched_empty(self):
1048
+ """Test batch get enriched tickets with empty list."""
1049
+ client = self.get_client()
1050
+
1051
+ result = await client.get_many_enriched([])
1052
+
1053
+ assert result == []
1054
+
963
1055
 
964
1056
  class TestCommentsClient:
965
1057
  """Test cases for CommentsClient."""
@@ -9,7 +9,7 @@ class TestPackageImport:
9
9
  import zendesk_sdk
10
10
 
11
11
  assert hasattr(zendesk_sdk, "__version__")
12
- assert zendesk_sdk.__version__ == "0.10.0"
12
+ assert zendesk_sdk.__version__ == "0.11.0"
13
13
 
14
14
  def test_client_import(self):
15
15
  """Test importing ZendeskClient."""