karakeep-python-api 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {karakeep_python_api-0.2.0/karakeep_python_api.egg-info → karakeep_python_api-0.2.2}/PKG-INFO +1 -1
  2. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api/datatypes.py +33 -91
  3. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api/karakeep_api.py +81 -55
  4. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2/karakeep_python_api.egg-info}/PKG-INFO +1 -1
  5. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/setup.py +1 -1
  6. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/tests/test_karakeep_api.py +2 -4
  7. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/LICENSE +0 -0
  8. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/MANIFEST.in +0 -0
  9. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/README.md +0 -0
  10. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api/__init__.py +0 -0
  11. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api/__main__.py +0 -0
  12. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api/openapi_reference.json +0 -0
  13. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
  14. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
  15. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api.egg-info/entry_points.txt +0 -0
  16. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api.egg-info/requires.txt +0 -0
  17. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/karakeep_python_api.egg-info/top_level.txt +0 -0
  18. {karakeep_python_api-0.2.0 → karakeep_python_api-0.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Community python client for the Karakeep API.
5
5
  Home-page: https://github.com/thiswillbeyourgithub/karakeep_python_api/
6
6
  License: GPLv3
@@ -1,50 +1,30 @@
1
- # generated by datamodel-codegen:
2
- # filename: openapi_reference.json
3
- # timestamp: 2025-05-04T21:59:34+00:00
1
+ """
2
+ The datatype file was originally generated by datamodel-codegen, then refactored and manually (or via LLMs) kept up to date with upstream
3
+ """
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
7
  from enum import Enum
8
- from typing import List, Optional, Union
8
+ from typing import List, Optional, Union, Literal
9
9
 
10
10
  from pydantic import BaseModel, Field, RootModel
11
11
 
12
12
 
13
- class AssetId(RootModel[str]):
14
- root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
15
-
16
-
17
- class BookmarkId(RootModel[str]):
18
- root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
19
-
20
-
21
- class ListId(RootModel[str]):
22
- root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
23
-
24
-
25
- class HighlightId(RootModel[str]):
26
- root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
27
-
28
-
29
- class TagId(RootModel[str]):
30
- root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
31
-
32
-
33
- class TaggingStatus(str, Enum):
13
+ class StatusTypes(str, Enum):
34
14
  success = "success"
35
15
  failure = "failure"
36
16
  pending = "pending"
37
17
 
38
18
 
39
- class AttachedBy(str, Enum):
40
- ai = "ai"
41
- human = "human"
19
+ class NumBookmarksByAttachedType(BaseModel):
20
+ ai: Optional[float] = None
21
+ human: Optional[float] = None
42
22
 
43
23
 
44
24
  class TagShort(BaseModel):
45
25
  id: str
46
26
  name: str
47
- attachedBy: AttachedBy
27
+ attachedBy: Literal["ai", "human"]
48
28
 
49
29
 
50
30
  class Tag(BaseModel):
@@ -58,8 +38,8 @@ class Type(str, Enum):
58
38
  link = "link"
59
39
 
60
40
 
61
- class Content(BaseModel):
62
- type: Type
41
+ class ContentTypeLink(BaseModel):
42
+ type: Literal["link"] = "link"
63
43
  url: str
64
44
  title: Optional[str] = None
65
45
  description: Optional[str] = None
@@ -78,36 +58,19 @@ class Content(BaseModel):
78
58
  dateModified: Optional[str] = None
79
59
 
80
60
 
81
- class TypeText(str, Enum):
82
- text = "text"
83
-
84
-
85
- class TypeAsset(str, Enum):
86
- asset = "asset"
87
-
88
-
89
- class TypeUnknown(str, Enum):
90
- unknown = "unknown"
91
-
92
-
93
- class ContentUnknown(BaseModel):
94
- type: TypeUnknown
95
-
96
-
97
- class AssetTypeAssetType(str, Enum):
98
- image = "image"
99
- pdf = "pdf"
61
+ class ContentTypeUnknown(BaseModel):
62
+ type: Literal["unknown"] = "unknown"
100
63
 
101
64
 
102
65
  class ContentTypeText(BaseModel):
103
- type: TypeText
66
+ type: Literal["text"] = "text"
104
67
  text: str
105
68
  sourceUrl: Optional[str] = None
106
69
 
107
70
 
108
71
  class ContentTypeAsset(BaseModel):
109
- type: TypeAsset
110
- assetType: AssetTypeAssetType
72
+ type: Literal["asset"] = "asset"
73
+ assetType: Literal["image", "pdf"]
111
74
  assetId: str
112
75
  fileName: Optional[str] = None
113
76
  sourceUrl: Optional[str] = None
@@ -115,20 +78,18 @@ class ContentTypeAsset(BaseModel):
115
78
  content: Optional[str] = None
116
79
 
117
80
 
118
- class AssetType(str, Enum):
119
- screenshot = "screenshot"
120
- assetScreenshot = "assetScreenshot"
121
- bannerImage = "bannerImage"
122
- fullPageArchive = "fullPageArchive"
123
- video = "video"
124
- bookmarkAsset = "bookmarkAsset"
125
- precrawledArchive = "precrawledArchive"
126
- unknown = "unknown"
127
-
128
-
129
81
  class Asset(BaseModel):
130
82
  id: str
131
- assetType: AssetType
83
+ assetType: Literal[
84
+ "screenshot",
85
+ "assetScreenshot",
86
+ "bannerImage",
87
+ "fullPageArchive",
88
+ "video",
89
+ "bookmarkAsset",
90
+ "precrawledArchive",
91
+ "unknown",
92
+ ]
132
93
 
133
94
 
134
95
  class Bookmark(BaseModel):
@@ -138,12 +99,14 @@ class Bookmark(BaseModel):
138
99
  title: Optional[str] = None
139
100
  archived: bool
140
101
  favourited: bool
141
- taggingStatus: TaggingStatus
142
- summarizationStatus: Optional[TaggingStatus] = None
102
+ taggingStatus: Literal["success", "failure", "pending"]
103
+ summarizationStatus: Optional[Literal["success", "failure", "pending"]] = None
143
104
  note: Optional[str] = None
144
105
  summary: Optional[str] = None
145
106
  tags: List[TagShort]
146
- content: Union[Content, ContentTypeText, ContentTypeAsset, ContentUnknown]
107
+ content: Union[
108
+ ContentTypeLink, ContentTypeText, ContentTypeAsset, ContentTypeUnknown
109
+ ]
147
110
  assets: List[Asset]
148
111
 
149
112
 
@@ -152,22 +115,11 @@ class PaginatedBookmarks(BaseModel):
152
115
  nextCursor: Optional[str]
153
116
 
154
117
 
155
- class Cursor(RootModel[str]):
156
- root: str
157
-
158
-
159
- class Color(str, Enum):
160
- yellow = "yellow"
161
- red = "red"
162
- green = "green"
163
- blue = "blue"
164
-
165
-
166
118
  class Highlight(BaseModel):
167
119
  bookmarkId: str
168
120
  startOffset: float
169
121
  endOffset: float
170
- color: Optional[Color] = "yellow"
122
+ color: Optional[Literal["yellow", "red", "green", "blue"]] = "yellow"
171
123
  text: Optional[str]
172
124
  note: Optional[str]
173
125
  id: str
@@ -175,26 +127,16 @@ class Highlight(BaseModel):
175
127
  createdAt: str
176
128
 
177
129
 
178
- class ListType(str, Enum):
179
- manual = "manual"
180
- smart = "smart"
181
-
182
-
183
130
  class ListModel(BaseModel):
184
131
  id: str
185
132
  name: str
186
133
  description: Optional[str] = None
187
134
  icon: str
188
135
  parentId: Optional[str]
189
- type: Optional[ListType] = "manual"
136
+ type: Optional[Literal["manual", "smart"]] = "manual"
190
137
  query: Optional[str] = None
191
138
 
192
139
 
193
- class NumBookmarksByAttachedType(BaseModel):
194
- ai: Optional[float] = None
195
- human: Optional[float] = None
196
-
197
-
198
140
  class PaginatedHighlights(BaseModel):
199
141
  highlights: List[Highlight]
200
142
  nextCursor: Optional[str]
@@ -85,7 +85,7 @@ class KarakeepAPI:
85
85
  """
86
86
 
87
87
  # Version reflects the client library version, updated by bumpver
88
- VERSION: str = "0.2.0"
88
+ VERSION: str = "0.2.2"
89
89
 
90
90
  def __init__(
91
91
  self,
@@ -955,16 +955,18 @@ class KarakeepAPI:
955
955
 
956
956
  @optional_typecheck
957
957
  def attach_tags_to_a_bookmark(
958
- self, bookmark_id: str, tags_data: dict
958
+ self,
959
+ bookmark_id: str,
960
+ tag_ids: Optional[List[str]] = None,
961
+ tag_names: Optional[List[str]] = None,
959
962
  ) -> Dict[str, Any]:
960
963
  """
961
964
  Attach one or more tags to a bookmark. Corresponds to POST /bookmarks/{bookmarkId}/tags.
962
965
 
963
966
  Args:
964
967
  bookmark_id: The ID (string) of the bookmark.
965
- tags_data: Dictionary specifying the tags to attach. Must contain a "tags" key
966
- which is a list of objects, each having *either* "tagId" (string) *or* "tagName" (string).
967
- Example: `{"tags": [{"tagId": "existing_tag_id"}, {"tagName": "new_or_existing_tag_name"}]}`
968
+ tag_ids: List of existing tag IDs to attach (optional).
969
+ tag_names: List of tag names to attach (will create tags if they don't exist) (optional).
968
970
 
969
971
  Returns:
970
972
  dict: A dictionary containing the list of attached tag IDs under the key "attached".
@@ -972,38 +974,49 @@ class KarakeepAPI:
972
974
  Validation is not performed on this response type by default.
973
975
 
974
976
  Raises:
975
- ValueError: If tags_data structure is invalid.
977
+ ValueError: If no tags are provided or if arguments are invalid.
976
978
  APIError: If the API request fails (e.g., 404 bookmark not found).
977
979
  """
978
- # Validate the tags_data structure
979
- if not isinstance(tags_data, dict):
980
- raise ValueError("tags_data must be a dictionary")
980
+ # Validate that at least one tag source is provided
981
+ if not tag_ids and not tag_names:
982
+ raise ValueError("At least one of 'tag_ids' or 'tag_names' must be provided")
981
983
 
982
- if "tags" not in tags_data:
983
- raise ValueError("tags_data must contain a 'tags' key")
984
+ # Validate input types
985
+ if tag_ids is not None and not isinstance(tag_ids, list):
986
+ raise ValueError("'tag_ids' must be a list of strings")
984
987
 
985
- tags_list = tags_data["tags"]
986
- if not isinstance(tags_list, list):
987
- raise ValueError("The 'tags' value must be a list")
988
+ if tag_names is not None and not isinstance(tag_names, list):
989
+ raise ValueError("'tag_names' must be a list of strings")
988
990
 
989
- # Validate each tag object in the list
990
- for i, tag in enumerate(tags_list):
991
- if not isinstance(tag, dict):
992
- raise ValueError(f"Tag at index {i} must be a dictionary")
991
+ # Validate individual elements
992
+ if tag_ids:
993
+ for i, tag_id in enumerate(tag_ids):
994
+ if not isinstance(tag_id, str) or not tag_id.strip():
995
+ raise ValueError(f"Tag ID at index {i} must be a non-empty string")
993
996
 
994
- has_tag_id = "tagId" in tag
995
- has_tag_name = "tagName" in tag
997
+ if tag_names:
998
+ for i, tag_name in enumerate(tag_names):
999
+ if not isinstance(tag_name, str) or not tag_name.strip():
1000
+ raise ValueError(f"Tag name at index {i} must be a non-empty string")
996
1001
 
997
- if not has_tag_id and not has_tag_name:
998
- raise ValueError(
999
- f"Tag at index {i} must contain either 'tagId' or 'tagName'"
1000
- )
1002
+ # Construct the tags_data dict in the format expected by the API
1003
+ tags_list = []
1004
+
1005
+ if tag_ids:
1006
+ for tag_id in tag_ids:
1007
+ tags_list.append({"tagId": tag_id.strip()})
1008
+
1009
+ if tag_names:
1010
+ for tag_name in tag_names:
1011
+ tags_list.append({"tagName": tag_name.strip()})
1001
1012
 
1002
- if has_tag_id and not isinstance(tag["tagId"], str):
1003
- raise ValueError(f"Tag at index {i}: 'tagId' must be a string")
1013
+ tags_data = {"tags": tags_list}
1004
1014
 
1005
- if has_tag_name and not isinstance(tag["tagName"], str):
1006
- raise ValueError(f"Tag at index {i}: 'tagName' must be a string")
1015
+ # Optional validation using Tag datatype if validation is enabled
1016
+ if not self.disable_response_validation:
1017
+ # Validate the constructed structure matches expected format
1018
+ # This is primarily for development/debugging purposes
1019
+ logger.debug("Validating constructed tags_data structure")
1007
1020
 
1008
1021
  endpoint = f"bookmarks/{bookmark_id}/tags"
1009
1022
  response_data = self._call("POST", endpoint, data=tags_data)
@@ -1013,16 +1026,18 @@ class KarakeepAPI:
1013
1026
 
1014
1027
  @optional_typecheck
1015
1028
  def detach_tags_from_a_bookmark(
1016
- self, bookmark_id: str, tags_data: dict
1029
+ self,
1030
+ bookmark_id: str,
1031
+ tag_ids: Optional[List[str]] = None,
1032
+ tag_names: Optional[List[str]] = None,
1017
1033
  ) -> Dict[str, Any]:
1018
1034
  """
1019
1035
  Detach one or more tags from a bookmark. Corresponds to DELETE /bookmarks/{bookmarkId}/tags.
1020
1036
 
1021
1037
  Args:
1022
1038
  bookmark_id: The ID (string) of the bookmark.
1023
- tags_data: Dictionary specifying the tags to detach. Must contain a "tags" key
1024
- which is a list of objects, each having *either* "tagId" (string) *or* "tagName" (string).
1025
- Example: `{"tags": [{"tagId": "tag_id_to_remove"}, {"tagName": "tag_name_to_remove"}]}`
1039
+ tag_ids: List of existing tag IDs to detach (optional).
1040
+ tag_names: List of tag names to detach (optional).
1026
1041
 
1027
1042
  Returns:
1028
1043
  dict: A dictionary containing the list of detached tag IDs under the key "detached".
@@ -1030,38 +1045,49 @@ class KarakeepAPI:
1030
1045
  Validation is not performed on this response type by default.
1031
1046
 
1032
1047
  Raises:
1033
- ValueError: If tags_data structure is invalid.
1048
+ ValueError: If no tags are provided or if arguments are invalid.
1034
1049
  APIError: If the API request fails (e.g., 404 bookmark not found).
1035
1050
  """
1036
- # Validate the tags_data structure
1037
- if not isinstance(tags_data, dict):
1038
- raise ValueError("tags_data must be a dictionary")
1051
+ # Validate that at least one tag source is provided
1052
+ if not tag_ids and not tag_names:
1053
+ raise ValueError("At least one of 'tag_ids' or 'tag_names' must be provided")
1039
1054
 
1040
- if "tags" not in tags_data:
1041
- raise ValueError("tags_data must contain a 'tags' key")
1055
+ # Validate input types
1056
+ if tag_ids is not None and not isinstance(tag_ids, list):
1057
+ raise ValueError("'tag_ids' must be a list of strings")
1042
1058
 
1043
- tags_list = tags_data["tags"]
1044
- if not isinstance(tags_list, list):
1045
- raise ValueError("The 'tags' value must be a list")
1059
+ if tag_names is not None and not isinstance(tag_names, list):
1060
+ raise ValueError("'tag_names' must be a list of strings")
1046
1061
 
1047
- # Validate each tag object in the list
1048
- for i, tag in enumerate(tags_list):
1049
- if not isinstance(tag, dict):
1050
- raise ValueError(f"Tag at index {i} must be a dictionary")
1062
+ # Validate individual elements
1063
+ if tag_ids:
1064
+ for i, tag_id in enumerate(tag_ids):
1065
+ if not isinstance(tag_id, str) or not tag_id.strip():
1066
+ raise ValueError(f"Tag ID at index {i} must be a non-empty string")
1051
1067
 
1052
- has_tag_id = "tagId" in tag
1053
- has_tag_name = "tagName" in tag
1068
+ if tag_names:
1069
+ for i, tag_name in enumerate(tag_names):
1070
+ if not isinstance(tag_name, str) or not tag_name.strip():
1071
+ raise ValueError(f"Tag name at index {i} must be a non-empty string")
1054
1072
 
1055
- if not has_tag_id and not has_tag_name:
1056
- raise ValueError(
1057
- f"Tag at index {i} must contain either 'tagId' or 'tagName'"
1058
- )
1073
+ # Construct the tags_data dict in the format expected by the API
1074
+ tags_list = []
1075
+
1076
+ if tag_ids:
1077
+ for tag_id in tag_ids:
1078
+ tags_list.append({"tagId": tag_id.strip()})
1079
+
1080
+ if tag_names:
1081
+ for tag_name in tag_names:
1082
+ tags_list.append({"tagName": tag_name.strip()})
1059
1083
 
1060
- if has_tag_id and not isinstance(tag["tagId"], str):
1061
- raise ValueError(f"Tag at index {i}: 'tagId' must be a string")
1084
+ tags_data = {"tags": tags_list}
1062
1085
 
1063
- if has_tag_name and not isinstance(tag["tagName"], str):
1064
- raise ValueError(f"Tag at index {i}: 'tagName' must be a string")
1086
+ # Optional validation using Tag datatype if validation is enabled
1087
+ if not self.disable_response_validation:
1088
+ # Validate the constructed structure matches expected format
1089
+ # This is primarily for development/debugging purposes
1090
+ logger.debug("Validating constructed tags_data structure")
1065
1091
 
1066
1092
  endpoint = f"bookmarks/{bookmark_id}/tags"
1067
1093
  response_data = self._call("DELETE", endpoint, data=tags_data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Community python client for the Karakeep API.
5
5
  Home-page: https://github.com/thiswillbeyourgithub/karakeep_python_api/
6
6
  License: GPLv3
@@ -7,7 +7,7 @@ with open("README.md", "r") as readme:
7
7
 
8
8
  setup(
9
9
  name="karakeep_python_api",
10
- version="0.2.0",
10
+ version="0.2.2",
11
11
  description="Community python client for the Karakeep API.", # Simplified description
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",
@@ -530,9 +530,8 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
530
530
  try:
531
531
  # 1. Attach a new tag by name to the bookmark
532
532
  logger.info(f"\nAttempting to attach tag '{initial_tag_name}' to bookmark {bookmark_id}")
533
- attach_payload = {"tags": [{"tagName": initial_tag_name}]}
534
533
  attach_response = karakeep_client.attach_tags_to_a_bookmark(
535
- bookmark_id=bookmark_id, tags_data=attach_payload
534
+ bookmark_id=bookmark_id, tag_names=[initial_tag_name]
536
535
  )
537
536
  assert "attached" in attach_response and len(attach_response["attached"]) == 1, \
538
537
  "Failed to attach tag or response format incorrect"
@@ -563,9 +562,8 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
563
562
 
564
563
  # 4. Detach the tag from the bookmark
565
564
  logger.info(f"\nAttempting to detach tag {tag_id_to_manage} from bookmark {bookmark_id}")
566
- detach_payload = {"tags": [{"tagId": tag_id_to_manage}]}
567
565
  detach_response = karakeep_client.detach_tags_from_a_bookmark(
568
- bookmark_id=bookmark_id, tags_data=detach_payload
566
+ bookmark_id=bookmark_id, tag_ids=[tag_id_to_manage]
569
567
  )
570
568
  assert "detached" in detach_response and tag_id_to_manage in detach_response["detached"], \
571
569
  "Failed to detach tag or response format incorrect"