unique_toolkit 1.12.1__py3-none-any.whl → 1.14.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ from unique_toolkit.content.schemas import (
16
16
  ContentInfo,
17
17
  ContentRerankerConfig,
18
18
  ContentSearchType,
19
+ DeleteContentResponse,
19
20
  FolderInfo,
20
21
  PaginatedContentInfos,
21
22
  )
@@ -585,6 +586,7 @@ def download_content(
585
586
  def get_content_info(
586
587
  user_id: str,
587
588
  company_id: str,
589
+ *,
588
590
  metadata_filter: dict[str, Any] | None = None,
589
591
  skip: int | None = None,
590
592
  take: int | None = None,
@@ -610,7 +612,7 @@ def get_content_info(
610
612
  )
611
613
 
612
614
 
613
- def get_folder_info(user_id: str, company_id: str, scope_id: str) -> FolderInfo:
615
+ def get_folder_info(user_id: str, company_id: str, *, scope_id: str) -> FolderInfo:
614
616
  info = unique_sdk.Folder.get_info(
615
617
  user_id=user_id, company_id=company_id, scopeId=scope_id
616
618
  )
@@ -648,3 +650,45 @@ def update_content(
648
650
  user_id=user_id, company_id=company_id, **update_params
649
651
  )
650
652
  return ContentInfo.model_validate(content_info, by_alias=True, by_name=True)
653
+
654
+
655
+ def delete_content(
656
+ user_id: str,
657
+ company_id: str,
658
+ *,
659
+ content_id: str | None = None,
660
+ file_path: str | None = None,
661
+ ) -> DeleteContentResponse:
662
+ if content_id:
663
+ resp = unique_sdk.Content.delete(
664
+ user_id=user_id, company_id=company_id, contentId=content_id
665
+ )
666
+ elif file_path:
667
+ resp = unique_sdk.Content.delete(
668
+ user_id=user_id, company_id=company_id, filePath=file_path
669
+ )
670
+ else:
671
+ raise ValueError("content_id or file_path must be provided")
672
+
673
+ return DeleteContentResponse.model_validate(resp, by_alias=True, by_name=True)
674
+
675
+
676
+ async def delete_content_async(
677
+ user_id: str,
678
+ company_id: str,
679
+ *,
680
+ content_id: str | None = None,
681
+ file_path: str | None = None,
682
+ ) -> DeleteContentResponse:
683
+ if content_id:
684
+ resp = await unique_sdk.Content.delete_async(
685
+ user_id=user_id, company_id=company_id, contentId=content_id
686
+ )
687
+ elif file_path:
688
+ resp = await unique_sdk.Content.delete_async(
689
+ user_id=user_id, company_id=company_id, filePath=file_path
690
+ )
691
+ else:
692
+ raise ValueError("content_id or file_path must be provided")
693
+
694
+ return DeleteContentResponse.model_validate(resp, by_alias=True, by_name=True)
@@ -185,3 +185,8 @@ class FolderInfo(BaseModel):
185
185
  updatedAt: str | None
186
186
  parentId: str | None
187
187
  externalId: str | None
188
+
189
+
190
+ class DeleteContentResponse(BaseModel):
191
+ model_config = model_config
192
+ id: str
@@ -1,7 +1,9 @@
1
+ import asyncio
1
2
  import logging
2
3
  from pathlib import Path
3
4
  from typing import Any, overload
4
5
 
6
+ import humps
5
7
  import unique_sdk
6
8
 
7
9
  from unique_toolkit._common.validate_required_values import validate_required_values
@@ -11,6 +13,8 @@ from unique_toolkit.content.constants import (
11
13
  DEFAULT_SEARCH_LANGUAGE,
12
14
  )
13
15
  from unique_toolkit.content.functions import (
16
+ delete_content,
17
+ delete_content_async,
14
18
  download_content_to_bytes,
15
19
  download_content_to_file_by_id,
16
20
  get_content_info,
@@ -29,6 +33,7 @@ from unique_toolkit.content.schemas import (
29
33
  ContentInfo,
30
34
  ContentRerankerConfig,
31
35
  ContentSearchType,
36
+ DeleteContentResponse,
32
37
  FolderInfo,
33
38
  PaginatedContentInfos,
34
39
  )
@@ -585,6 +590,261 @@ class KnowledgeBaseService:
585
590
 
586
591
  return self._resolve_visible_file_tree(content_infos=info.content_infos)
587
592
 
593
+ def _pop_forbidden_metadata_keys(self, metadata: dict[str, Any]) -> dict[str, Any]:
594
+ forbidden_keys = [
595
+ "key",
596
+ "url",
597
+ "title",
598
+ "folderId",
599
+ "mimeType",
600
+ "companyId",
601
+ "contentId",
602
+ "folderIdPath",
603
+ "externalFileOwner",
604
+ ]
605
+ for key in forbidden_keys:
606
+ metadata.pop(key, None)
607
+ return metadata
608
+
609
+ def update_content_metadata(
610
+ self,
611
+ *,
612
+ content_info: ContentInfo,
613
+ additional_metadata: dict[str, Any],
614
+ ) -> ContentInfo:
615
+ camelized_additional_metadata = humps.camelize(additional_metadata)
616
+ camelized_additional_metadata = self._pop_forbidden_metadata_keys(
617
+ camelized_additional_metadata
618
+ )
619
+
620
+ if content_info.metadata is not None:
621
+ content_info.metadata.update(camelized_additional_metadata)
622
+ else:
623
+ content_info.metadata = camelized_additional_metadata
624
+
625
+ return update_content(
626
+ user_id=self._user_id,
627
+ company_id=self._company_id,
628
+ content_id=content_info.id,
629
+ metadata=content_info.metadata,
630
+ )
631
+
632
+ def remove_content_metadata(
633
+ self,
634
+ *,
635
+ content_info: ContentInfo,
636
+ keys_to_remove: list[str],
637
+ ) -> ContentInfo:
638
+ """
639
+ Removes the specified keys irreversibly from the content metadata.
640
+ """
641
+
642
+ if content_info.metadata is None:
643
+ _LOGGER.warning(f"Content metadata is None for content {content_info.id}")
644
+ return content_info
645
+
646
+ for key in keys_to_remove:
647
+ content_info.metadata.pop(key, None)
648
+
649
+ return update_content(
650
+ user_id=self._user_id,
651
+ company_id=self._company_id,
652
+ content_id=content_info.id,
653
+ metadata=content_info.metadata or {},
654
+ )
655
+
656
+ @overload
657
+ def update_contents_metadata(
658
+ self,
659
+ *,
660
+ additional_metadata: dict[str, Any],
661
+ content_infos: list[ContentInfo],
662
+ ) -> list[ContentInfo]: ...
663
+
664
+ @overload
665
+ def update_contents_metadata(
666
+ self, *, additional_metadata: dict[str, Any], metadata_filter: dict[str, Any]
667
+ ) -> list[ContentInfo]: ...
668
+
669
+ def update_contents_metadata(
670
+ self,
671
+ *,
672
+ additional_metadata: dict[str, Any],
673
+ metadata_filter: dict[str, Any] | None = None,
674
+ content_infos: list[ContentInfo] | None = None,
675
+ ) -> list[ContentInfo]:
676
+ additional_metadata_camelized = humps.camelize(additional_metadata)
677
+ additional_metadata_camelized = self._pop_forbidden_metadata_keys(
678
+ additional_metadata_camelized
679
+ )
680
+
681
+ if content_infos is None:
682
+ content_infos = self.get_paginated_content_infos(
683
+ metadata_filter=metadata_filter,
684
+ ).content_infos
685
+
686
+ for info in content_infos:
687
+ self.update_content_metadata(
688
+ content_info=info, additional_metadata=additional_metadata_camelized
689
+ )
690
+
691
+ return content_infos
692
+
693
+ @overload
694
+ def remove_contents_metadata(
695
+ self,
696
+ *,
697
+ keys_to_remove: list[str],
698
+ content_infos: list[ContentInfo],
699
+ ) -> list[ContentInfo]: ...
700
+
701
+ @overload
702
+ def remove_contents_metadata(
703
+ self, *, keys_to_remove: list[str], metadata_filter: dict[str, Any]
704
+ ) -> list[ContentInfo]: ...
705
+
706
+ def remove_contents_metadata(
707
+ self,
708
+ *,
709
+ keys_to_remove: list[str],
710
+ metadata_filter: dict[str, Any] | None = None,
711
+ content_infos: list[ContentInfo] | None = None,
712
+ ) -> list[ContentInfo]:
713
+ if content_infos is None:
714
+ content_infos = self.get_paginated_content_infos(
715
+ metadata_filter=metadata_filter,
716
+ ).content_infos
717
+
718
+ for info in content_infos:
719
+ self.remove_content_metadata(
720
+ content_info=info, keys_to_remove=keys_to_remove
721
+ )
722
+
723
+ return content_infos
724
+
725
+ @overload
726
+ def delete_content(
727
+ self,
728
+ *,
729
+ content_id: str,
730
+ ) -> DeleteContentResponse: ...
731
+
732
+ """Delete content by id"""
733
+
734
+ @overload
735
+ def delete_content(
736
+ self,
737
+ *,
738
+ file_path: str,
739
+ ) -> DeleteContentResponse: ...
740
+
741
+ """Delete all content matching the file path"""
742
+
743
+ def delete_content(
744
+ self,
745
+ *,
746
+ content_id: str | None = None,
747
+ file_path: str | None = None,
748
+ metadata_filter: dict[str, Any] | None = None,
749
+ ) -> DeleteContentResponse:
750
+ """Delete content by id, file path or metadata filter"""
751
+ if metadata_filter:
752
+ infos = self.get_paginated_content_infos(
753
+ metadata_filter=metadata_filter,
754
+ )
755
+ for info in infos.content_infos:
756
+ delete_content(
757
+ user_id=self._user_id,
758
+ company_id=self._company_id,
759
+ content_id=info.id,
760
+ )
761
+
762
+ return delete_content(
763
+ user_id=self._user_id,
764
+ company_id=self._company_id,
765
+ content_id=content_id,
766
+ file_path=file_path,
767
+ )
768
+
769
+ def delete_contents(
770
+ self,
771
+ *,
772
+ metadata_filter: dict[str, Any],
773
+ ) -> list[DeleteContentResponse]:
774
+ """Delete all content matching the metadata filter"""
775
+ resp: list[DeleteContentResponse] = []
776
+
777
+ if metadata_filter:
778
+ infos = self.get_paginated_content_infos(
779
+ metadata_filter=metadata_filter,
780
+ )
781
+
782
+ for info in infos.content_infos:
783
+ resp.append(
784
+ delete_content(
785
+ user_id=self._user_id,
786
+ company_id=self._company_id,
787
+ content_id=info.id,
788
+ )
789
+ )
790
+
791
+ return resp
792
+
793
+ @overload
794
+ async def delete_content_async(
795
+ self,
796
+ *,
797
+ content_id: str,
798
+ ) -> DeleteContentResponse: ...
799
+
800
+ @overload
801
+ async def delete_content_async(
802
+ self,
803
+ *,
804
+ file_path: str,
805
+ ) -> DeleteContentResponse: ...
806
+
807
+ async def delete_content_async(
808
+ self,
809
+ *,
810
+ content_id: str | None = None,
811
+ file_path: str | None = None,
812
+ ) -> DeleteContentResponse:
813
+ return await delete_content_async(
814
+ user_id=self._user_id,
815
+ company_id=self._company_id,
816
+ content_id=content_id,
817
+ file_path=file_path,
818
+ )
819
+
820
+ async def delete_contents_async(
821
+ self,
822
+ *,
823
+ metadata_filter: dict[str, Any],
824
+ ) -> list[DeleteContentResponse]:
825
+ """Delete all content matching the metadata filter"""
826
+ if not metadata_filter:
827
+ return []
828
+
829
+ infos = self.get_paginated_content_infos(
830
+ metadata_filter=metadata_filter,
831
+ )
832
+
833
+ # Create all delete tasks without awaiting them
834
+ delete_tasks = [
835
+ delete_content_async(
836
+ user_id=self._user_id,
837
+ company_id=self._company_id,
838
+ content_id=info.id,
839
+ )
840
+ for info in infos.content_infos
841
+ ]
842
+
843
+ # Await all delete operations concurrently
844
+ resp = await asyncio.gather(*delete_tasks)
845
+
846
+ return list(resp)
847
+
588
848
 
589
849
  if __name__ == "__main__":
590
850
  kb_service = KnowledgeBaseService.from_settings()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.12.1
3
+ Version: 1.14.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -118,6 +118,12 @@ All notable changes to this project will be documented in this file.
118
118
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
119
119
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
120
120
 
121
+ ## [1.14.0] - 2025-10-07
122
+ - Manipulate Metadata with knowledge base service
123
+
124
+ ## [1.13.0] - 2025-10-07
125
+ - Delete contents with knowledge base service
126
+
121
127
  ## [1.12.1] - 2025-10-07
122
128
  - Fix bug where failed evaluations did not show an error to the user.
123
129
 
@@ -112,8 +112,8 @@ unique_toolkit/chat/state.py,sha256=Cjgwv_2vhDFbV69xxsn7SefhaoIAEqLx3ferdVFCnOg,
112
112
  unique_toolkit/chat/utils.py,sha256=ihm-wQykBWhB4liR3LnwPVPt_qGW6ETq21Mw4HY0THE,854
113
113
  unique_toolkit/content/__init__.py,sha256=EdJg_A_7loEtCQf4cah3QARQreJx6pdz89Rm96YbMVg,940
114
114
  unique_toolkit/content/constants.py,sha256=1iy4Y67xobl5VTnJB6SxSyuoBWbdLl9244xfVMUZi5o,60
115
- unique_toolkit/content/functions.py,sha256=6Z9fOEsngJaT1R5DgoeRl5z06RavYgCxiAAPjkw-NKI,21100
116
- unique_toolkit/content/schemas.py,sha256=YmZa6ddjf6_RvDFGPovamLd0YX99am9nzWYrKHo8-Hg,5579
115
+ unique_toolkit/content/functions.py,sha256=TWwXGHZ8AeBC7UbRVjKXjHC7PmlghPtPNQvw7fOj1tY,22403
116
+ unique_toolkit/content/schemas.py,sha256=pBm1XHSgATdVe9PWUcEetRux9W6oRQ91oFl-kHofDh0,5665
117
117
  unique_toolkit/content/service.py,sha256=i7wN_wYjF8NBZHBcpcA5XRlMRGntuw3mlVoudAoGQRk,23293
118
118
  unique_toolkit/content/smart_rules.py,sha256=z2gHToPrdyj3HqO8Uu-JE5G2ClvJPuhR2XERmmkgoug,9668
119
119
  unique_toolkit/content/utils.py,sha256=qNVmHTuETaPNGqheg7TbgPr1_1jbNHDc09N5RrmUIyo,7901
@@ -130,7 +130,7 @@ unique_toolkit/framework_utilities/openai/__init__.py,sha256=CrHYoC7lv2pBscitLer
130
130
  unique_toolkit/framework_utilities/openai/client.py,sha256=ct1cqPcIK1wPl11G9sJV39ZnLJwKr2kDUDSra0FjvtM,2007
131
131
  unique_toolkit/framework_utilities/openai/message_builder.py,sha256=RT1pZjxH42TFZlAxQ5zlqdKPvHKVTjc5t3JDUy58I7Q,6887
132
132
  unique_toolkit/framework_utilities/utils.py,sha256=JK7g2yMfEx3eMprug26769xqNpS5WJcizf8n2zWMBng,789
133
- unique_toolkit/knowledge_base.py,sha256=SHAFs68zDQuHJZdrFcdU7wnkUdfNQlNzpSLNckx3Scg,20167
133
+ unique_toolkit/knowledge_base.py,sha256=UU8S858sjrAJhOKtDb0b0RvjwzjZOvErNk4ShyLrTZY,27610
134
134
  unique_toolkit/language_model/__init__.py,sha256=lRQyLlbwHbNFf4-0foBU13UGb09lwEeodbVsfsSgaCk,1971
135
135
  unique_toolkit/language_model/builder.py,sha256=4OKfwJfj3TrgO1ezc_ewIue6W7BCQ2ZYQXUckWVPPTA,3369
136
136
  unique_toolkit/language_model/constants.py,sha256=B-topqW0r83dkC_25DeQfnPk3n53qzIHUCBS7YJ0-1U,119
@@ -149,7 +149,7 @@ unique_toolkit/short_term_memory/schemas.py,sha256=OhfcXyF6ACdwIXW45sKzjtZX_gkcJ
149
149
  unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBuE9sI2o9Aajqjxg,8884
150
150
  unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
151
  unique_toolkit/smart_rules/compile.py,sha256=Ozhh70qCn2yOzRWr9d8WmJeTo7AQurwd3tStgBMPFLA,1246
152
- unique_toolkit-1.12.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
153
- unique_toolkit-1.12.1.dist-info/METADATA,sha256=mBeCg71Dak88SQUNJpaiQwTuh9MuKmA9vDPaakm3AQg,36092
154
- unique_toolkit-1.12.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
155
- unique_toolkit-1.12.1.dist-info/RECORD,,
152
+ unique_toolkit-1.14.0.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
153
+ unique_toolkit-1.14.0.dist-info/METADATA,sha256=OWdWXkAGiHMEPujnxslwCXHkheERRJy_wC9Ad4uX7v0,36240
154
+ unique_toolkit-1.14.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
155
+ unique_toolkit-1.14.0.dist-info/RECORD,,