zammad_py 3.2.0__py3-none-any.whl → 3.2.1__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.
zammad_py/__init__.py CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  __author__ = """Joe Paul"""
4
4
  __email__ = "joeirimpan@gmail.com"
5
- __version__ = "3.2.0"
5
+ __version__ = "3.2.1"
6
6
 
7
7
  from .api import ZammadAPI # noqa: F401
zammad_py/api.py CHANGED
@@ -8,9 +8,24 @@ from typing import Any, Generator, List, Optional, Tuple
8
8
  import requests
9
9
  from requests.exceptions import HTTPError
10
10
 
11
- from zammad_py.exceptions import ConfigException
12
-
13
- __all__ = ["ZammadAPI"]
11
+ from zammad_py.enums import KnowledgeBaseAnswerPublicity
12
+ from zammad_py.exceptions import (
13
+ ConfigException,
14
+ InvalidTypeError,
15
+ MissingParameterError,
16
+ UnusedResourceError,
17
+ )
18
+
19
+ __all__ = [
20
+ "ZammadAPI",
21
+ # Exceptions
22
+ "ConfigException",
23
+ "UnusedResourceError",
24
+ "MissingParameterError",
25
+ "InvalidTypeError",
26
+ # Enums
27
+ "KnowledgeBaseAnswerPublicity",
28
+ ]
14
29
 
15
30
 
16
31
  class ZammadAPI:
@@ -110,7 +125,7 @@ class ZammadAPI:
110
125
  return Ticket(connection=self)
111
126
 
112
127
  @property
113
- def link(self):
128
+ def link(self) -> "Link":
114
129
  """Return a `Link` instance"""
115
130
  return Link(connection=self)
116
131
 
@@ -150,10 +165,25 @@ class ZammadAPI:
150
165
  return TagList(connection=self)
151
166
 
152
167
  @property
153
- def ticket_tag(self):
168
+ def ticket_tag(self) -> "TicketTag":
154
169
  """Return a `TicketTag` instance"""
155
170
  return TicketTag(connection=self)
156
171
 
172
+ @property
173
+ def knowledge_bases(self) -> "KnowledgeBases":
174
+ """Return a `KnowledgeBases` instance"""
175
+ return KnowledgeBases(connection=self)
176
+
177
+ @property
178
+ def knowledge_bases_answers(self) -> "KnowledgeBasesAnswers":
179
+ """Return an `KnowledgeBasesAnswers` instance"""
180
+ return KnowledgeBasesAnswers(connection=self)
181
+
182
+ @property
183
+ def knowledge_bases_categories(self) -> "KnowledgeBasesCategories":
184
+ """Return an `KnowledgeBasesCategories` instance"""
185
+ return KnowledgeBasesCategories(connection=self)
186
+
157
187
 
158
188
  class Pagination:
159
189
  def __init__(
@@ -312,7 +342,7 @@ class Resource(ABC):
312
342
  page=params["page"],
313
343
  )
314
344
 
315
- def find(self, id):
345
+ def find(self, id: int) -> Any:
316
346
  """Return the resource associated with the id
317
347
 
318
348
  :param id: Resource id
@@ -320,7 +350,7 @@ class Resource(ABC):
320
350
  response = self._connection.session.get(self.url + "/%s" % id)
321
351
  return self._raise_or_return_json(response)
322
352
 
323
- def create(self, params):
353
+ def create(self, params: Any) -> Any:
324
354
  """Create the requested resource
325
355
 
326
356
  :param params: Resource data for creating
@@ -328,7 +358,7 @@ class Resource(ABC):
328
358
  response = self._connection.session.post(self.url, json=params)
329
359
  return self._raise_or_return_json(response)
330
360
 
331
- def update(self, id, params):
361
+ def update(self, id: int, params: Any) -> Any:
332
362
  """Update the requested resource
333
363
 
334
364
  :param id: Resource id
@@ -337,7 +367,7 @@ class Resource(ABC):
337
367
  response = self._connection.session.put(self.url + "/%s" % id, json=params)
338
368
  return self._raise_or_return_json(response)
339
369
 
340
- def destroy(self, id):
370
+ def destroy(self, id: int) -> Any:
341
371
  """Delete the resource associated with the id
342
372
 
343
373
  :param id: Resource id
@@ -361,7 +391,7 @@ class Organization(Resource):
361
391
  class Ticket(Resource):
362
392
  path_attribute = "tickets"
363
393
 
364
- def articles(self, id):
394
+ def articles(self, id: int) -> Any:
365
395
  """Returns all the articles associated with the ticket id
366
396
 
367
397
  :param id: Ticket id
@@ -371,7 +401,7 @@ class Ticket(Resource):
371
401
  )
372
402
  return self._raise_or_return_json(response)
373
403
 
374
- def tags(self, id):
404
+ def tags(self, id: int) -> Any:
375
405
  """Returns all the tags associated with the ticket id
376
406
 
377
407
  :param id: Ticket id
@@ -381,7 +411,7 @@ class Ticket(Resource):
381
411
  )
382
412
  return self._raise_or_return_json(response)
383
413
 
384
- def merge(self, id, number):
414
+ def merge(self, id: int, number: int) -> Any:
385
415
  """Merges two tickets, (undocumented in Zammad Docs)
386
416
  If the objects are already merged, it will return "Object already exists!"
387
417
  Attention: Must use password to authenticate to Zammad, otherwise this will not work!
@@ -400,12 +430,12 @@ class Link(Resource):
400
430
 
401
431
  def add(
402
432
  self,
403
- link_object_target_value,
404
- link_object_source_number,
405
- link_type="normal",
406
- link_object_target="Ticket",
407
- link_object_source="Ticket",
408
- ):
433
+ link_object_target_value: int,
434
+ link_object_source_number: int,
435
+ link_type: str = "normal",
436
+ link_object_target: str = "Ticket",
437
+ link_object_source: str = "Ticket",
438
+ ) -> Any:
409
439
  """Create the link
410
440
 
411
441
  :params link_type: Link type ('normal', 'parent', 'child')
@@ -429,12 +459,12 @@ class Link(Resource):
429
459
 
430
460
  def remove(
431
461
  self,
432
- link_object_target_value,
433
- link_object_source_number,
434
- link_type="normal",
435
- link_object_target="Ticket",
436
- link_object_source="Ticket",
437
- ):
462
+ link_object_target_value: int,
463
+ link_object_source_number: int,
464
+ link_type: str = "normal",
465
+ link_object_target: str = "Ticket",
466
+ link_object_source: str = "Ticket",
467
+ ) -> Any:
438
468
  """Remove the Link
439
469
 
440
470
  :params link_type: Link type ('normal', 'parent', 'child')
@@ -454,7 +484,7 @@ class Link(Resource):
454
484
  response = self._connection.session.delete(self.url + "/remove", json=params)
455
485
  return self._raise_or_return_json(response)
456
486
 
457
- def get(self, id):
487
+ def get(self, id: int) -> Any:
458
488
  """Returns all the links associated with the ticket id
459
489
 
460
490
  :param id: Ticket id
@@ -473,7 +503,7 @@ class TicketArticle(Resource):
473
503
  class TicketArticleAttachment(Resource):
474
504
  path_attribute = "ticket_attachment"
475
505
 
476
- def download(self, id, article_id, ticket_id):
506
+ def download(self, id: int, article_id: int, ticket_id: int) -> Any:
477
507
  """Download the ticket attachment associated with the ticket id
478
508
 
479
509
  :param id: Ticket attachment id
@@ -501,7 +531,7 @@ class TicketState(Resource):
501
531
  class User(Resource):
502
532
  path_attribute = "users"
503
533
 
504
- def me(self):
534
+ def me(self) -> Any:
505
535
  """Returns current user information"""
506
536
  response = self._connection.session.get(self.url + "/me")
507
537
  return self._raise_or_return_json(response)
@@ -510,7 +540,7 @@ class User(Resource):
510
540
  class OnlineNotification(Resource):
511
541
  path_attribute = "online_notifications"
512
542
 
513
- def mark_all_read(self):
543
+ def mark_all_read(self) -> Any:
514
544
  """Marks all online notification as read"""
515
545
  response = self._connection.session.post(self.url + "/mark_all_as_read")
516
546
  return self._raise_or_return_json(response)
@@ -519,7 +549,7 @@ class OnlineNotification(Resource):
519
549
  class Object(Resource):
520
550
  path_attribute = "object_manager_attributes"
521
551
 
522
- def execute_migrations(self):
552
+ def execute_migrations(self) -> Any:
523
553
  """Executes all database migrations"""
524
554
  response = self._connection.session.post(
525
555
  self._connection.url + "object_manager_attributes_execute_migrations"
@@ -538,7 +568,7 @@ class TicketTag(Resource):
538
568
 
539
569
  path_attribute = "tags"
540
570
 
541
- def add(self, id, tag, object="Ticket"):
571
+ def add(self, id: int, tag: str, object: str = "Ticket") -> Any:
542
572
  """Add a tag to a ticket
543
573
 
544
574
  :param id: Ticket id
@@ -555,7 +585,7 @@ class TicketTag(Resource):
555
585
  response = self._connection.session.post(self.url + "/add", json=params)
556
586
  return self._raise_or_return_json(response)
557
587
 
558
- def remove(self, id, tag, object="Ticket"):
588
+ def remove(self, id: int, tag: str, object: str = "Ticket") -> Any:
559
589
  """Remove a tag from a ticket.
560
590
 
561
591
  :param id: Ticket id
@@ -571,3 +601,399 @@ class TicketTag(Resource):
571
601
 
572
602
  response = self._connection.session.delete(self.url + "/remove", json=params)
573
603
  return self._raise_or_return_json(response)
604
+
605
+
606
+ class KnowledgeBases(Resource):
607
+ path_attribute = "knowledge_bases"
608
+
609
+ def init(self) -> Any:
610
+ """Returns a bootstrap object containing the entire structure (settings, categories, and answer IDs) of the knowledge base"""
611
+ response = self._connection.session.post(
612
+ self._connection.url + "knowledge_bases/init"
613
+ )
614
+ return self._raise_or_return_json(response)
615
+
616
+ def manage(self, id: int, settings: dict) -> Any:
617
+ """Updates specific knowledge base settings like custom URLs, colors, or visibility toggles
618
+
619
+ :param id: Knowledge Base ID
620
+ :param settings: Dictionary of setting to be applied to the knowledge base
621
+ """
622
+ response = self._connection.session.patch(
623
+ self._connection.url + "knowledge_bases/manage/%s" % id, json=settings
624
+ )
625
+ return self._raise_or_return_json(response)
626
+
627
+ def show_permissions(self, id: int) -> Any:
628
+ """Returns a list of roles and their associated access levels (reader/editor) for the knowledge base
629
+
630
+ :param id: Knowledge Base ID
631
+ """
632
+ response = self._connection.session.get(
633
+ self._connection.url + "knowledge_bases/%s/permissions" % id,
634
+ )
635
+ return self._raise_or_return_json(response)
636
+
637
+ def change_permissions(self, id: int, permissions: dict) -> Any:
638
+ """Replaces the current permission set with a new mapping of roles and access levels
639
+
640
+ :param id: Knowledge Base ID
641
+ :param permissions: Dictionary of new permissions to be applied to the knowledge base
642
+ """
643
+ response = self._connection.session.put(
644
+ self._connection.url + "knowledge_bases/%s/permissions" % id,
645
+ json=permissions,
646
+ )
647
+ return self._raise_or_return_json(response)
648
+
649
+ def reorder_sub_categories(self, id: int, category_id: int, params: dict) -> Any:
650
+ """Updates the display order of sub-categories within a specific parent category
651
+
652
+ :param id: Knowledge Base ID
653
+ :param category_id: Knowledge Base Category ID
654
+ :param params: A dictionary containing 'ordered_ids' (a list of sub-category IDs) in the desired sequence
655
+ """
656
+ response = self._connection.session.patch(
657
+ self._connection.url
658
+ + f"knowledge_bases/{id}/categories/{category_id}/reorder_categories",
659
+ json=params,
660
+ )
661
+ return self._raise_or_return_json(response)
662
+
663
+ def reorder_root_categories(self, id: int, params: dict) -> Any:
664
+ """Updates the display order of all top-level categories on the knowledge base homepage
665
+
666
+ :param id: Knowledge Base ID
667
+ :param params: A dictionary containing 'ordered_ids' (a list of category IDs) in the desired sequence
668
+ """
669
+ response = self._connection.session.patch(
670
+ self._connection.url
671
+ + "knowledge_bases/%s/categories/reorder_root_categories" % id,
672
+ json=params,
673
+ )
674
+ return self._raise_or_return_json(response)
675
+
676
+ def all(self, page: int = 1, filters=None) -> Pagination:
677
+ """Disabled: Zammad does not support a flat list of all knowledge bases"""
678
+ raise UnusedResourceError(self.__class__.__name__, "all")
679
+
680
+ def search(self, search_string: str, page: int = 1, filters=None) -> Pagination:
681
+ """Disabled: Knowledge base search is not supported"""
682
+ raise UnusedResourceError(self.__class__.__name__, "search")
683
+
684
+ def find(self, id: int) -> Any:
685
+ """Disabled: Knowledge base find is not supported"""
686
+ raise UnusedResourceError(self.__class__.__name__, "find")
687
+
688
+ def create(self, params: Any) -> Any:
689
+ """Disabled: Creation of new knowledge base instances is typically handled via the init method"""
690
+ raise UnusedResourceError(self.__class__.__name__, "create")
691
+
692
+ def update(self, id: int, params: Any) -> Any:
693
+ """Disabled: Update of a knowledge base instances is typically handled via the manage method"""
694
+ raise UnusedResourceError(self.__class__.__name__, "update")
695
+
696
+ def destroy(self, id: int) -> Any:
697
+ """Disabled: Knowledge base destroy is not supported"""
698
+ raise UnusedResourceError(self.__class__.__name__, "destroy")
699
+
700
+
701
+ class KnowledgeBasesAnswers(Resource):
702
+ path_attribute = "knowledge_bases"
703
+
704
+ def all(self, page: int = 1, filters=None) -> Pagination:
705
+ """Disabled: Zammad does not support a flat list of all answers"""
706
+ raise UnusedResourceError(self.__class__.__name__, "all")
707
+
708
+ def search(self, search_string: str, page: int = 1, filters=None) -> Pagination:
709
+ """Disabled: Answers search is not supported"""
710
+ raise UnusedResourceError(self.__class__.__name__, "search")
711
+
712
+ def find(self, id: int) -> Any:
713
+ """Disabled: Retrieving an answer requires both knowledge_base_id and answer_id"""
714
+ raise UnusedResourceError(self.__class__.__name__, "find")
715
+
716
+ def find_answer(
717
+ self,
718
+ knowledge_base_id: int,
719
+ answer_id: int,
720
+ include_content_id: Optional[int] = None,
721
+ ) -> Any:
722
+ """Retrieves a specific answer from a knowledge base, optionally including content details
723
+
724
+ :param knowledge_base_id: Knowledge Base ID
725
+ :param answer_id: Knowledge Base Answer ID
726
+ :param include_content_id: Optional ID to include specific translated content (localization) in the response
727
+ """
728
+ if include_content_id is None:
729
+ find_answer_url = (
730
+ self._connection.url
731
+ + "knowledge_bases/{}/answers/{}".format(
732
+ knowledge_base_id,
733
+ answer_id,
734
+ )
735
+ )
736
+ else:
737
+ find_answer_url = (
738
+ self._connection.url
739
+ + "knowledge_bases/%s/answers/%s?include_contents=%s"
740
+ % (knowledge_base_id, answer_id, include_content_id)
741
+ )
742
+
743
+ response = self._connection.session.get(find_answer_url)
744
+ return self._raise_or_return_json(response)
745
+
746
+ def create(self, params: dict) -> Any:
747
+ """Creates a new answer within a specified knowledge base
748
+
749
+ :param params: A dictionary containing all the required details for creating the new answer.
750
+ The dictionary must include "knowledge_base_id" field representing the Knowledge Base ID
751
+ """
752
+ if not isinstance(params, dict):
753
+ raise InvalidTypeError("params", dict, type(params))
754
+
755
+ if "knowledge_base_id" not in params:
756
+ raise MissingParameterError(
757
+ "knowledge_base_id", context="create knowledge base answer"
758
+ )
759
+
760
+ knowledge_base_id = params.pop("knowledge_base_id")
761
+
762
+ response = self._connection.session.post(
763
+ self._connection.url + "knowledge_bases/%s/answers" % knowledge_base_id,
764
+ json=params,
765
+ )
766
+ return self._raise_or_return_json(response)
767
+
768
+ def update(self, id: int, params: dict) -> Any:
769
+ """Updates an existing answer using the knowledge base ID and the answer ID
770
+
771
+ :param id: Knowledge Base Answer ID
772
+ :param params: A dictionary containing all the required details for updating the answer.
773
+ The dictionary must include "answer_id" field representing the Knowledge Base Answer ID
774
+ """
775
+ if not isinstance(params, dict):
776
+ raise InvalidTypeError("params", dict, type(params))
777
+
778
+ if "answer_id" not in params:
779
+ raise MissingParameterError(
780
+ "answer_id", context="update knowledge base answer"
781
+ )
782
+
783
+ answer_id = params.pop("answer_id")
784
+
785
+ response = self._connection.session.patch(
786
+ self._connection.url + f"knowledge_bases/{id}/answers/{answer_id}",
787
+ json=params,
788
+ )
789
+ return self._raise_or_return_json(response)
790
+
791
+ def destroy(self, id: int) -> Any:
792
+ """Disabled: Permanent deletion requires both knowledge_base_id and answer_id
793
+
794
+ :param id: Knowledge Base Answer ID
795
+ """
796
+ raise UnusedResourceError(self.__class__.__name__, "destroy")
797
+
798
+ def destroy_answer(self, knowledge_base_id: int, answer_id: int) -> Any:
799
+ """Permanently deletes an answer from the knowledge base
800
+
801
+ :param knowledge_base_id: Knowledge Base ID
802
+ :param answer_id: Knowledge Base Answer ID
803
+ """
804
+ response = self._connection.session.delete(
805
+ self._connection.url
806
+ + f"knowledge_bases/{knowledge_base_id}/answers/{answer_id}"
807
+ )
808
+ return self._raise_or_return_json(response)
809
+
810
+ def change_answer_visibility(
811
+ self,
812
+ knowledge_base_id: int,
813
+ answer_id: int,
814
+ answer_visibility: KnowledgeBaseAnswerPublicity,
815
+ ) -> Any:
816
+ """Updates the publication state (e.g., draft, public, internal) of a specific answer
817
+
818
+ :param knowledge_base_id: Knowledge Base ID
819
+ :param answer_id: Knowledge Base Answer ID
820
+ :param answer_visibility: The publication state of the answer (e.g., draft, internal, or public)
821
+ """
822
+ response = self._connection.session.post(
823
+ self._connection.url
824
+ + "knowledge_bases/%s/answers/%s/%s"
825
+ % (knowledge_base_id, answer_id, answer_visibility.value)
826
+ )
827
+ return self._raise_or_return_json(response)
828
+
829
+ def add_attachment(
830
+ self, knowledge_base_id: int, answer_id: int, attachment: Any
831
+ ) -> Any:
832
+ """Uploads a file as an attachment to an answer using multipart/form-data
833
+
834
+ :param knowledge_base_id: Knowledge Base ID
835
+ :param answer_id: Knowledge Base Answer ID
836
+ :param attachment: The file to be uploaded to the answer as an attachment
837
+ """
838
+ response = self._connection.session.post(
839
+ self._connection.url
840
+ + "knowledge_bases/%s/answers/%s/attachments"
841
+ % (knowledge_base_id, answer_id),
842
+ files={"file": attachment},
843
+ )
844
+ return self._raise_or_return_json(response)
845
+
846
+ def delete_attachment(
847
+ self, knowledge_base_id: int, answer_id: int, attachment_id: int
848
+ ) -> Any:
849
+ """Removes a specific attachment from an answer by its attachment ID
850
+
851
+ :param knowledge_base_id: Knowledge Base ID
852
+ :param answer_id: Knowledge Base Answer ID
853
+ :param attachment_id: Attachment ID
854
+ """
855
+ response = self._connection.session.delete(
856
+ self._connection.url
857
+ + "knowledge_bases/%s/answers/%s/attachments/%s"
858
+ % (knowledge_base_id, answer_id, attachment_id)
859
+ )
860
+ return self._raise_or_return_json(response)
861
+
862
+
863
+ class KnowledgeBasesCategories(Resource):
864
+ path_attribute = "knowledge_bases"
865
+
866
+ def all(self, page: int = 1, filters=None) -> Pagination:
867
+ """Disabled: Zammad does not support a flat list of all knowledge base categories"""
868
+ raise UnusedResourceError(self.__class__.__name__, "all")
869
+
870
+ def search(self, search_string: str, page: int = 1, filters=None) -> Pagination:
871
+ """Disabled: Knowledge base categories search is not supported"""
872
+ raise UnusedResourceError(self.__class__.__name__, "search")
873
+
874
+ def find(self, id: int) -> Any:
875
+ """Disabled: Retrieving an answer requires both knowledge_base_id and category_id"""
876
+ raise UnusedResourceError(self.__class__.__name__, "find")
877
+
878
+ def find_category(self, knowledge_base_id: int, category_id: int) -> Any:
879
+ """Retrieves a specific category from a knowledge base
880
+
881
+ :param knowledge_base_id: Knowledge Base ID
882
+ :param category_id: Knowledge Base Category ID
883
+ """
884
+ response = self._connection.session.get(
885
+ self._connection.url
886
+ + f"knowledge_bases/{knowledge_base_id}/categories/{category_id}"
887
+ )
888
+ return self._raise_or_return_json(response)
889
+
890
+ def create(self, params: dict) -> Any:
891
+ """Creates a new category within a specified knowledge base
892
+
893
+ :param params: A dictionary containing all the required details for creating the new knowledge base category.
894
+ The dictionary must include "knowledge_base_id" field representing the Knowledge Base ID
895
+ """
896
+ if not isinstance(params, dict):
897
+ raise InvalidTypeError("params", dict, type(params))
898
+
899
+ if "knowledge_base_id" not in params:
900
+ raise MissingParameterError(
901
+ "knowledge_base_id", context="create knowledge base category"
902
+ )
903
+
904
+ knowledge_base_id = params.pop("knowledge_base_id")
905
+
906
+ response = self._connection.session.post(
907
+ self._connection.url + "knowledge_bases/%s/categories" % knowledge_base_id,
908
+ json=params,
909
+ )
910
+ return self._raise_or_return_json(response)
911
+
912
+ def update(self, id: int, params: dict) -> Any:
913
+ """Updates an existing category using the knowledge base ID and the category ID
914
+
915
+ :param id: Knowledge Base ID
916
+ :param params: A dictionary containing all the required details for updating the knowledge base category.
917
+ The dictionary must include "category_id" field representing the Knowledge Base Category ID
918
+ """
919
+ if not isinstance(params, dict):
920
+ raise InvalidTypeError("params", dict, type(params))
921
+
922
+ if "category_id" not in params:
923
+ raise MissingParameterError(
924
+ "category_id", context="update knowledge base category"
925
+ )
926
+
927
+ category_id = params.pop("category_id")
928
+
929
+ response = self._connection.session.patch(
930
+ self._connection.url + f"knowledge_bases/{id}/categories/{category_id}",
931
+ json=params,
932
+ )
933
+ return self._raise_or_return_json(response)
934
+
935
+ def destroy(self, id: int) -> Any:
936
+ """Disabled: Permanent deletion requires both knowledge_base_id and category_id
937
+
938
+ :param id: Knowledge Base Category ID
939
+ """
940
+ raise UnusedResourceError(self.__class__.__name__, "destroy")
941
+
942
+ def destroy_category(self, knowledge_base_id: int, category_id: int) -> Any:
943
+ """Permanently deletes a category from the knowledge base
944
+
945
+ :param knowledge_base_id: Knowledge Base ID
946
+ :param category_id: Knowledge Base Category ID
947
+ """
948
+ response = self._connection.session.delete(
949
+ self._connection.url
950
+ + f"knowledge_bases/{knowledge_base_id}/categories/{category_id}"
951
+ )
952
+ return self._raise_or_return_json(response)
953
+
954
+ def show_permissions(self, knowledge_base_id: int, category_id: int) -> Any:
955
+ """Returns a list of roles and their associated access levels (reader/editor) for the knowledge base category
956
+
957
+ :param knowledge_base_id: Knowledge Base ID
958
+ :param category_id: Knowledge Base Category ID
959
+ """
960
+ response = self._connection.session.get(
961
+ self._connection.url
962
+ + "knowledge_bases/%s/categories/%s/permissions"
963
+ % (knowledge_base_id, category_id),
964
+ )
965
+ return self._raise_or_return_json(response)
966
+
967
+ def change_permissions(
968
+ self, knowledge_base_id: int, category_id: int, permissions: dict
969
+ ) -> Any:
970
+ """Replaces the current permission set with a new mapping of roles and access levels
971
+
972
+ :param knowledge_base_id: Knowledge Base ID
973
+ :param category_id: Knowledge Base Category ID
974
+ :param permissions: Dictionary of new permissions to be applied to the knowledge base category
975
+ """
976
+ response = self._connection.session.put(
977
+ self._connection.url
978
+ + "knowledge_bases/%s/categories/%s/permissions"
979
+ % (knowledge_base_id, category_id),
980
+ json=permissions,
981
+ )
982
+ return self._raise_or_return_json(response)
983
+
984
+ def reorder_answers(
985
+ self, knowledge_base_id: int, category_id: int, params: dict
986
+ ) -> Any:
987
+ """Updates the display order of answers within a specific category
988
+
989
+ :param knowledge_base_id: Knowledge Base ID
990
+ :param category_id: Knowledge Base Category ID
991
+ :param params: A dictionary containing 'ordered_ids' (a list of answer IDs) in the desired sequence
992
+ """
993
+ response = self._connection.session.patch(
994
+ self._connection.url
995
+ + "knowledge_bases/%s/categories/%s/reorder_answers"
996
+ % (knowledge_base_id, category_id),
997
+ json=params,
998
+ )
999
+ return self._raise_or_return_json(response)
zammad_py/enums.py ADDED
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class KnowledgeBaseAnswerPublicity(Enum):
5
+ INTERNALLY = "internal"
6
+ PUBLICLY = "publish"
7
+ ARCHIVE = "archive"
8
+ UNARCHIVE = "unarchive"
zammad_py/exceptions.py CHANGED
@@ -1,2 +1,36 @@
1
1
  class ConfigException(Exception):
2
2
  pass
3
+
4
+
5
+ class UnusedResourceError(NotImplementedError):
6
+ """Exception raised when a method is not supported by a specific Zammad resource."""
7
+
8
+ def __init__(self, resource_name, method_name):
9
+ self.message = f"The method '{method_name}' is not available for the {resource_name} resource."
10
+ super().__init__(self.message)
11
+
12
+
13
+ class MissingParameterError(ValueError):
14
+ """Exception raised when a required field is missing from the parameters."""
15
+
16
+ def __init__(self, field_name, context=None):
17
+ self.field_name = field_name
18
+ self.context = context
19
+ message = f"Missing required parameter: '{field_name}'"
20
+ if context:
21
+ message += f" in {context}"
22
+ super().__init__(message)
23
+
24
+
25
+ class InvalidTypeError(TypeError):
26
+ """Exception raised when a variable is not of the expected type."""
27
+
28
+ def __init__(self, variable_name, expected_type, actual_type):
29
+ self.variable_name = variable_name
30
+ self.expected_type = expected_type
31
+ self.actual_type = actual_type
32
+ message = (
33
+ f"Variable '{variable_name}' must be of type {expected_type.__name__}, "
34
+ f"but got {actual_type.__name__} instead."
35
+ )
36
+ super().__init__(message)
@@ -0,0 +1,9 @@
1
+ # Credits
2
+
3
+ ## Development Lead
4
+
5
+ * Joe Paul <joeirimpan@gmail.com>
6
+
7
+ ## Contributors
8
+
9
+ * Matthew Frost <matthew@uptic.nl>
@@ -0,0 +1,182 @@
1
+ Metadata-Version: 2.1
2
+ Name: zammad_py
3
+ Version: 3.2.1
4
+ Summary: Python API client for zammad
5
+ Home-page: https://github.com/joeirimpan/zammad_py
6
+ License: MIT
7
+ Author: Joe Paul
8
+ Author-email: joeirimpan@gmail.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Dist: requests (>=2.25.1,<3.0.0)
21
+ Project-URL: Documentation, https://zammad-py.readthedocs.io/
22
+ Project-URL: Repository, https://github.com/joeirimpan/zammad_py.git
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Zammad API Client
26
+
27
+ [![PyPI version](https://img.shields.io/pypi/v/zammad_py.svg)](https://pypi.python.org/pypi/zammad_py)
28
+ [![Documentation Status](https://readthedocs.org/projects/zammad-py/badge/?version=latest)](https://zammad-py.readthedocs.io/en/latest/?badge=latest)
29
+
30
+ Python API client for zammad
31
+
32
+ * Free software: MIT license
33
+ * Documentation: https://zammad-py.readthedocs.io.
34
+
35
+ ## Quickstart
36
+
37
+ ```python
38
+ from zammad_py import ZammadAPI
39
+
40
+ # Initialize the client with the URL, username, and password
41
+ # Note the Host URL should be in this format: 'https://zammad.example.org/api/v1/'
42
+ client = ZammadAPI(url='<HOST>', username='<USERNAME>', password='<PASSWORD>')
43
+
44
+ # Example: Access all users
45
+ this_page = client.user.all()
46
+ for user in this_page:
47
+ print(user)
48
+
49
+ # Example: Get information about the current user
50
+ print(client.user.me())
51
+
52
+ # Example: Create a ticket
53
+ params = {
54
+ "title": "Help me!",
55
+ "group": "2nd Level",
56
+ "customer": "david@example.com",
57
+ "article": {
58
+ "subject": "My subject",
59
+ "body": "I am a message!",
60
+ "type": "note",
61
+ "internal": False
62
+ }
63
+ }
64
+ new_ticket = client.ticket.create(params=params)
65
+ ```
66
+
67
+ ## General Methods
68
+
69
+ Most resources support these methods:
70
+
71
+ - `.all()`: Returns a paginated response with the current page number and a list of elements.
72
+ - `.next_page()`: Returns the next page of the current pagination object.
73
+ - `.prev_page()`: Returns the previous page of the current pagination object.
74
+ - `.search(params)`: Returns a paginated response based on the search parameters.
75
+ - `.find(id)`: Returns a single object with the specified ID.
76
+ - `.create(params)`: Creates a new object with the specified parameters.
77
+ - `.update(params)`: Updates an existing object with the specified parameters.
78
+ - `.destroy(id)`: Deletes an object with the specified ID.
79
+
80
+ ## Available Resources
81
+
82
+ | Resource | Property |
83
+ |----------|----------|
84
+ | User | `client.user` |
85
+ | Organization | `client.organization` |
86
+ | Group | `client.group` |
87
+ | Role | `client.role` |
88
+ | Ticket | `client.ticket` |
89
+ | Link | `client.link` |
90
+ | TicketArticle | `client.ticket_article` |
91
+ | TicketArticleAttachment | `client.ticket_article_attachment` |
92
+ | TicketArticlePlain | `client.ticket_article_plain` |
93
+ | TicketPriority | `client.ticket_priority` |
94
+ | TicketState | `client.ticket_state` |
95
+ | TagList | `client.taglist` |
96
+ | TicketTag | `client.ticket_tag` |
97
+ | KnowledgeBases | `client.knowledge_bases` |
98
+ | KnowledgeBasesAnswers | `client.knowledge_bases_answers` |
99
+ | KnowledgeBasesCategories | `client.knowledge_bases_categories` |
100
+
101
+ ## Resource-Specific Methods
102
+
103
+ ### User
104
+ - `.me()`: Returns information about the current user.
105
+
106
+ ### Ticket
107
+ - `.articles(id)`: Returns all articles associated with a ticket.
108
+ - `.tags(id)`: Returns all tags associated with a ticket.
109
+ - `.merge(id, number)`: Merges two tickets (requires password auth).
110
+
111
+ ### Link
112
+ - `.get(id)`: Returns all links associated with a ticket.
113
+ - `.add(...)`: Creates a link between two objects.
114
+ - `.remove(...)`: Removes a link between two objects.
115
+
116
+ ### TicketArticleAttachment
117
+ - `.download(id, article_id, ticket_id)`: Downloads a ticket attachment.
118
+
119
+ ### TagList
120
+ - `.all()`: Returns all tags (paginated).
121
+ - `.create(params)`: Creates a new tag.
122
+ - `.destroy(id)`: Deletes a tag.
123
+
124
+ ### TicketTag
125
+ - `.add(id, tag)`: Adds a tag to a ticket.
126
+ - `.remove(id, tag)`: Removes a tag from a ticket.
127
+
128
+ ### KnowledgeBases
129
+ - `.init()`: Returns the entire knowledge base structure.
130
+ - `.manage(id, settings)`: Updates knowledge base settings.
131
+ - `.show_permissions(id)`: Returns permissions for a knowledge base.
132
+ - `.change_permissions(id, permissions)`: Updates permissions.
133
+ - `.reorder_sub_categories(id, category_id, params)`: Reorders sub-categories.
134
+ - `.reorder_root_categories(id, params)`: Reorders root categories.
135
+
136
+ ### KnowledgeBasesAnswers
137
+ - `.find_answer(knowledge_base_id, answer_id)`: Retrieves a specific answer.
138
+ - `.create(params)`: Creates a new answer (requires `knowledge_base_id` in params).
139
+ - `.update(id, params)`: Updates an answer (requires `answer_id` in params).
140
+ - `.destroy_answer(knowledge_base_id, answer_id)`: Deletes an answer.
141
+ - `.change_answer_visibility(knowledge_base_id, answer_id, visibility)`: Updates answer visibility.
142
+ - `.add_attachment(knowledge_base_id, answer_id, attachment)`: Uploads an attachment.
143
+ - `.delete_attachment(knowledge_base_id, answer_id, attachment_id)`: Removes an attachment.
144
+
145
+ ### KnowledgeBasesCategories
146
+ - `.find_category(knowledge_base_id, category_id)`: Retrieves a specific category.
147
+ - `.create(params)`: Creates a new category (requires `knowledge_base_id` in params).
148
+ - `.update(id, params)`: Updates a category (requires `category_id` in params).
149
+ - `.destroy_category(knowledge_base_id, category_id)`: Deletes a category.
150
+ - `.show_permissions(knowledge_base_id, category_id)`: Returns category permissions.
151
+ - `.change_permissions(knowledge_base_id, category_id, permissions)`: Updates permissions.
152
+ - `.reorder_answers(knowledge_base_id, category_id, params)`: Reorders answers.
153
+
154
+ ## On Behalf Of
155
+
156
+ You can set the `on_behalf_of` attribute to perform actions on behalf of another user:
157
+
158
+ ```python
159
+ client.on_behalf_of = 'user@example.com'
160
+ ```
161
+
162
+ Or use the context manager:
163
+
164
+ ```python
165
+ with client.request_on_behalf_of('user@example.com'):
166
+ client.ticket.create(params=params)
167
+ ```
168
+
169
+ ## Contributing
170
+
171
+ The Zammad API Client (zammad_py) welcomes contributions.
172
+
173
+ You can contribute by reporting bugs, fixing bugs, implementing new features, writing documentation, and submitting feedback.
174
+
175
+ To get started, see the contributing section in the docs!
176
+
177
+ Please ensure that your changes include tests and updated documentation if necessary.
178
+
179
+ ## Credits
180
+
181
+ This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [audreyr/cookiecutter-pypackage](https://github.com/audreyr/cookiecutter-pypackage) project template.
182
+
@@ -0,0 +1,9 @@
1
+ zammad_py/__init__.py,sha256=Xu6tdXhuBbsKnzvlTOw11Nx7VvNakukOWC6ViW-3_Wg,175
2
+ zammad_py/api.py,sha256=JfaEMrLpo-F7DpT2hsK37QgK6pT_l8TNpzLHag50fMM,35323
3
+ zammad_py/enums.py,sha256=9mSsJ_GV7aqpzk3awB6ZvFT3xzzTBHgWENiBR4ezh3I,171
4
+ zammad_py/exceptions.py,sha256=-ABU-554T5MY2AI9KaVZm-iroiaXzakb3AIUa35Px9s,1285
5
+ zammad_py-3.2.1.dist-info/AUTHORS.md,sha256=NRpF3uzp7H8H_DqYsNRdopQNCniINZs0Ya3oGPSUOfk,119
6
+ zammad_py-3.2.1.dist-info/LICENSE,sha256=NNe9mRfWIQ3cigyQ1-cBQA5bRPuarc1iWRkfa72jSm0,1065
7
+ zammad_py-3.2.1.dist-info/METADATA,sha256=Me3XXn1fjAYL46mJEiuSDDl_n5EBY-jrYKOIqDECTu4,6786
8
+ zammad_py-3.2.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ zammad_py-3.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,138 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: zammad_py
3
- Version: 3.2.0
4
- Summary: Python API client for zammad
5
- Home-page: https://github.com/joeirimpan/zammad_py
6
- License: MIT
7
- Author: Joe Paul
8
- Author-email: joeirimpan@gmail.com
9
- Requires-Python: >=3.9,<4.0
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Natural Language :: English
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Requires-Dist: requests (>=2.25.1,<3.0.0)
20
- Project-URL: Documentation, https://zammad-py.readthedocs.io/
21
- Project-URL: Repository, https://github.com/joeirimpan/zammad_py.git
22
- Description-Content-Type: text/x-rst
23
-
24
- =================
25
- Zammad API Client
26
- =================
27
-
28
-
29
- .. image:: https://img.shields.io/pypi/v/zammad_py.svg
30
- :target: https://pypi.python.org/pypi/zammad_py
31
-
32
- .. image:: https://img.shields.io/travis/joeirimpan/zammad_py.svg
33
- :target: https://travis-ci.org/joeirimpan/zammad_py
34
-
35
- .. image:: https://readthedocs.org/projects/zammad-py/badge/?version=latest
36
- :target: https://zammad-py.readthedocs.io/en/latest/?badge=latest
37
- :alt: Documentation Status
38
-
39
- .. image:: https://pyup.io/repos/github/joeirimpan/zammad_py/shield.svg
40
- :target: https://pyup.io/repos/github/joeirimpan/zammad_py/
41
- :alt: Updates
42
-
43
-
44
- Python API client for zammad
45
-
46
- * Free software: MIT license
47
- * Documentation: https://zammad-py.readthedocs.io.
48
-
49
-
50
- Quickstart
51
- ----------
52
-
53
-
54
- .. code-block:: python
55
-
56
- from zammad_py import ZammadAPI
57
-
58
- # Initialize the client with the URL, username, and password
59
- # Note the Host URL should be in this format: 'https://zammad.example.org/api/v1/'
60
- client = ZammadAPI(url='<HOST>', username='<USERNAME>', password='<PASSWORD>')
61
-
62
- # Example: Access all users
63
- this_page = client.user.all()
64
- for user in this_page:
65
- print(user)
66
-
67
- # Example: Get information about the current user
68
- print(client.user.me())
69
-
70
- # Example: Create a ticket
71
- params = {
72
- "title": "Help me!",
73
- "group": "2nd Level",
74
- "customer": "david@example.com",
75
- "article": {
76
- "subject": "My subject",
77
- "body": "I am a message!",
78
- "type": "note",
79
- "internal": false
80
- }
81
- }
82
- new_ticket = client.ticket.create(params=params)
83
-
84
-
85
-
86
- General Methods
87
- ---------------
88
- Most resources support these methods:
89
-
90
- `.all()`: Returns a paginated response with the current page number and a list of elements.
91
-
92
- `.next_page()`: Returns the next page of the current pagination object.
93
-
94
- `.prev_page()`: Returns the previous page of the current pagination object.
95
-
96
- `.search(params)`: Returns a paginated response based on the search parameters.
97
-
98
- `.find(id)`: Returns a single object with the specified ID.
99
-
100
- `.create(params)`: Creates a new object with the specified parameters.
101
-
102
- `.update(params)`: Updates an existing object with the specified parameters.
103
-
104
- `.destroy(id)`: Deletes an object with the specified ID.
105
-
106
- Additional Resource Methods
107
- ---------------------------
108
- User resource also has the .me() method to get information about the current user.
109
-
110
- Ticket resource also has the .articles() method to get the articles associated with a ticket.
111
-
112
- Link resource has methods to list, add, and delete links between objects.
113
-
114
- TicketArticleAttachment resource has the .download() method to download a ticket attachment.
115
-
116
- Object resource has the .execute_migrations() method to run migrations on an object.
117
-
118
- You can set the `on_behalf_of` attribute of the ZammadAPI instance to do actions on behalf of another user.
119
-
120
- Contributing
121
- ------------
122
- The Zammad API Client (zammad_py) welcomes contributions.
123
-
124
- You can contribute by reporting bugs, fixing bugs, implementing new features, writing documentation, and submitting feedback.
125
-
126
- To get started, see the contributing section in the docs!
127
-
128
- Please ensure that your changes include tests and updated documentation if necessary.
129
-
130
- Credits
131
- -------
132
-
133
- This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
134
-
135
- .. _Cookiecutter: https://github.com/audreyr/cookiecutter
136
- .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
137
-
138
-
@@ -1,7 +0,0 @@
1
- zammad_py/__init__.py,sha256=DM_s56KwTdX5X4aLsh6nVqKRlMK2zOagjiCcXhx9IaY,175
2
- zammad_py/api.py,sha256=cNXvdFlQZRMIgx1agdvcVNak29S7fGEmbKwXFA9vYB0,17646
3
- zammad_py/exceptions.py,sha256=zKJqzIAKcY_2NvbNK-jijycfCkL4vr6qSCGK7bDROfw,43
4
- zammad_py-3.2.0.dist-info/LICENSE,sha256=NNe9mRfWIQ3cigyQ1-cBQA5bRPuarc1iWRkfa72jSm0,1065
5
- zammad_py-3.2.0.dist-info/METADATA,sha256=RWeGdLfsStnT9zeLMByJQvBAtXHDUeiGh6H-ItmvzE0,4395
6
- zammad_py-3.2.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
7
- zammad_py-3.2.0.dist-info/RECORD,,