karakeep-python-api 1.2.4__tar.gz → 1.3.1__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.
- {karakeep_python_api-1.2.4/karakeep_python_api.egg-info → karakeep_python_api-1.3.1}/PKG-INFO +1 -1
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/datatypes.py +26 -16
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/karakeep_api.py +100 -28
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/openapi_reference.json +237 -46
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1/karakeep_python_api.egg-info}/PKG-INFO +1 -1
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/setup.py +1 -1
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/tests/test_karakeep_api.py +95 -91
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/LICENSE +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/MANIFEST.in +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/README.md +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/__init__.py +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/__main__.py +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/entry_points.txt +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/requires.txt +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/top_level.txt +0 -0
- {karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/setup.cfg +0 -0
|
@@ -17,8 +17,8 @@ class StatusTypes(str, Enum):
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class NumBookmarksByAttachedType(BaseModel):
|
|
20
|
-
ai: Optional[
|
|
21
|
-
human: Optional[
|
|
20
|
+
ai: Optional[int] = None
|
|
21
|
+
human: Optional[int] = None
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class TagShort(BaseModel):
|
|
@@ -30,7 +30,7 @@ class TagShort(BaseModel):
|
|
|
30
30
|
class Tag(BaseModel):
|
|
31
31
|
id: str
|
|
32
32
|
name: str
|
|
33
|
-
numBookmarks:
|
|
33
|
+
numBookmarks: int
|
|
34
34
|
numBookmarksByAttachedType: NumBookmarksByAttachedType
|
|
35
35
|
|
|
36
36
|
|
|
@@ -112,6 +112,11 @@ class Bookmark(BaseModel):
|
|
|
112
112
|
summarizationStatus: Optional[Literal["success", "failure", "pending"]] = None
|
|
113
113
|
note: Optional[str] = None
|
|
114
114
|
summary: Optional[str] = None
|
|
115
|
+
source: Optional[
|
|
116
|
+
Literal[
|
|
117
|
+
"api", "web", "cli", "mobile", "extension", "singlefile", "rss", "import"
|
|
118
|
+
]
|
|
119
|
+
] = None
|
|
115
120
|
tags: List[TagShort]
|
|
116
121
|
content: Union[
|
|
117
122
|
ContentTypeLink, ContentTypeText, ContentTypeAsset, ContentTypeUnknown
|
|
@@ -121,7 +126,18 @@ class Bookmark(BaseModel):
|
|
|
121
126
|
|
|
122
127
|
class PaginatedBookmarks(BaseModel):
|
|
123
128
|
bookmarks: List[Bookmark]
|
|
124
|
-
nextCursor: Optional[str]
|
|
129
|
+
nextCursor: Optional[str] = ""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ListModel(BaseModel):
|
|
133
|
+
id: str
|
|
134
|
+
name: str
|
|
135
|
+
description: Optional[str] = None
|
|
136
|
+
icon: str
|
|
137
|
+
parentId: Optional[str]
|
|
138
|
+
type: Optional[Literal["manual", "smart"]] = "manual"
|
|
139
|
+
query: Optional[str] = None
|
|
140
|
+
public: bool
|
|
125
141
|
|
|
126
142
|
|
|
127
143
|
class Highlight(BaseModel):
|
|
@@ -136,17 +152,11 @@ class Highlight(BaseModel):
|
|
|
136
152
|
createdAt: str
|
|
137
153
|
|
|
138
154
|
|
|
139
|
-
class ListModel(BaseModel):
|
|
140
|
-
id: str
|
|
141
|
-
name: str
|
|
142
|
-
description: Optional[str] = None
|
|
143
|
-
icon: str
|
|
144
|
-
parentId: Optional[str]
|
|
145
|
-
type: Optional[Literal["manual", "smart"]] = "manual"
|
|
146
|
-
query: Optional[str] = None
|
|
147
|
-
public: bool
|
|
148
|
-
|
|
149
|
-
|
|
150
155
|
class PaginatedHighlights(BaseModel):
|
|
151
156
|
highlights: List[Highlight]
|
|
152
|
-
nextCursor: Optional[str]
|
|
157
|
+
nextCursor: Optional[str] = ""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class PaginatedTags(BaseModel):
|
|
161
|
+
tags: List[Tag]
|
|
162
|
+
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 = "1.
|
|
88
|
+
VERSION: str = "1.3.1"
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
@@ -758,6 +758,19 @@ class KarakeepAPI:
|
|
|
758
758
|
summary: Optional[str] = None,
|
|
759
759
|
createdAt: Optional[str] = None, # ISO 8601 format string
|
|
760
760
|
crawlPriority: Optional[Literal["low", "normal"]] = None,
|
|
761
|
+
importSessionId: Optional[str] = None,
|
|
762
|
+
source: Optional[
|
|
763
|
+
Literal[
|
|
764
|
+
"api",
|
|
765
|
+
"web",
|
|
766
|
+
"cli",
|
|
767
|
+
"mobile",
|
|
768
|
+
"extension",
|
|
769
|
+
"singlefile",
|
|
770
|
+
"rss",
|
|
771
|
+
"import",
|
|
772
|
+
]
|
|
773
|
+
] = None,
|
|
761
774
|
# Link specific
|
|
762
775
|
url: Optional[str] = None,
|
|
763
776
|
precrawledArchiveId: Optional[str] = None,
|
|
@@ -783,6 +796,8 @@ class KarakeepAPI:
|
|
|
783
796
|
summary: Optional summary content for the bookmark.
|
|
784
797
|
createdAt: Optional creation timestamp override (ISO 8601 format string).
|
|
785
798
|
crawlPriority: Optional either 'low' or 'normal'.
|
|
799
|
+
importSessionId: Optional import session ID for tracking imported bookmarks.
|
|
800
|
+
source: Optional source of the bookmark ("api", "web", "cli", "mobile", "extension", "singlefile", "rss", "import").
|
|
786
801
|
|
|
787
802
|
--- Link Type Specific ---
|
|
788
803
|
url: The URL for the link bookmark. Required if type='link'.
|
|
@@ -799,7 +814,9 @@ class KarakeepAPI:
|
|
|
799
814
|
sourceUrl: Optional source URL where the asset originated.
|
|
800
815
|
|
|
801
816
|
Returns:
|
|
802
|
-
datatypes.Bookmark: The
|
|
817
|
+
datatypes.Bookmark: The bookmark object.
|
|
818
|
+
Returns with status 200 if the bookmark already exists,
|
|
819
|
+
or status 201 if the bookmark was newly created.
|
|
803
820
|
If response validation is disabled, returns the raw API response (dict/list).
|
|
804
821
|
|
|
805
822
|
Raises:
|
|
@@ -825,6 +842,10 @@ class KarakeepAPI:
|
|
|
825
842
|
request_body["createdAt"] = createdAt
|
|
826
843
|
if crawlPriority is not None:
|
|
827
844
|
request_body["crawlPriority"] = crawlPriority
|
|
845
|
+
if importSessionId is not None:
|
|
846
|
+
request_body["importSessionId"] = importSessionId
|
|
847
|
+
if source is not None:
|
|
848
|
+
request_body["source"] = source
|
|
828
849
|
|
|
829
850
|
# Add type-specific fields and perform validation
|
|
830
851
|
if type == "link":
|
|
@@ -911,7 +932,9 @@ class KarakeepAPI:
|
|
|
911
932
|
|
|
912
933
|
@optional_typecheck
|
|
913
934
|
def get_a_single_bookmark(
|
|
914
|
-
self,
|
|
935
|
+
self,
|
|
936
|
+
bookmark_id: str,
|
|
937
|
+
include_content: bool = True, # Default from spec
|
|
915
938
|
) -> Union[datatypes.Bookmark, Dict[str, Any], List[Any]]:
|
|
916
939
|
"""
|
|
917
940
|
Get a single bookmark by its ID. Corresponds to GET /bookmarks/{bookmarkId}.
|
|
@@ -1169,6 +1192,54 @@ class KarakeepAPI:
|
|
|
1169
1192
|
# No Pydantic validation applied here as the spec defines a simple dict response
|
|
1170
1193
|
return response_data
|
|
1171
1194
|
|
|
1195
|
+
@optional_typecheck
|
|
1196
|
+
def get_lists_of_a_bookmark(
|
|
1197
|
+
self, bookmark_id: str
|
|
1198
|
+
) -> Union[List[datatypes.ListModel], Dict[str, Any], List[Any]]:
|
|
1199
|
+
"""
|
|
1200
|
+
Get all lists associated with a specific bookmark. Corresponds to GET /bookmarks/{bookmarkId}/lists.
|
|
1201
|
+
|
|
1202
|
+
Args:
|
|
1203
|
+
bookmark_id: The ID (string) of the bookmark.
|
|
1204
|
+
|
|
1205
|
+
Returns:
|
|
1206
|
+
List[datatypes.ListModel]: A list of list objects associated with the bookmark.
|
|
1207
|
+
If response validation is disabled, returns the raw API response (dict/list).
|
|
1208
|
+
|
|
1209
|
+
Raises:
|
|
1210
|
+
APIError: If the API request fails (e.g., 404 bookmark not found).
|
|
1211
|
+
pydantic.ValidationError: If response validation fails (and is not disabled).
|
|
1212
|
+
"""
|
|
1213
|
+
endpoint = f"bookmarks/{bookmark_id}/lists"
|
|
1214
|
+
response_data = self._call("GET", endpoint)
|
|
1215
|
+
|
|
1216
|
+
if self.disable_response_validation:
|
|
1217
|
+
logger.debug("Skipping response validation as requested.")
|
|
1218
|
+
# Return raw data, which might be {"lists": [...]} or something else
|
|
1219
|
+
return response_data
|
|
1220
|
+
else:
|
|
1221
|
+
# Response schema is {"lists": [ListModel]}, extract the list and validate
|
|
1222
|
+
if (
|
|
1223
|
+
isinstance(response_data, dict)
|
|
1224
|
+
and "lists" in response_data
|
|
1225
|
+
and isinstance(response_data["lists"], list)
|
|
1226
|
+
):
|
|
1227
|
+
try:
|
|
1228
|
+
return [
|
|
1229
|
+
datatypes.ListModel.model_validate(lst)
|
|
1230
|
+
for lst in response_data["lists"]
|
|
1231
|
+
]
|
|
1232
|
+
except (
|
|
1233
|
+
Exception
|
|
1234
|
+
) as e: # Catch validation errors during list comprehension
|
|
1235
|
+
logger.error(f"Validation failed for one or more lists: {e}")
|
|
1236
|
+
raise # Re-raise the validation error
|
|
1237
|
+
else:
|
|
1238
|
+
# Raise error if format is unexpected and validation is enabled
|
|
1239
|
+
raise APIError(
|
|
1240
|
+
f"Unexpected response format for get_lists_of_a_bookmark when validation is enabled: {response_data}"
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1172
1243
|
@optional_typecheck
|
|
1173
1244
|
def get_highlights_of_a_bookmark(
|
|
1174
1245
|
self, bookmark_id: str
|
|
@@ -1601,46 +1672,47 @@ class KarakeepAPI:
|
|
|
1601
1672
|
return None # Explicitly return None for 204
|
|
1602
1673
|
|
|
1603
1674
|
@optional_typecheck
|
|
1604
|
-
def get_all_tags(
|
|
1675
|
+
def get_all_tags(
|
|
1676
|
+
self,
|
|
1677
|
+
name_contains: Optional[str] = None,
|
|
1678
|
+
sort: Optional[Literal["name", "usage", "relevance"]] = None,
|
|
1679
|
+
attached_by: Optional[Literal["ai", "human", "none"]] = None,
|
|
1680
|
+
cursor: Optional[str] = None,
|
|
1681
|
+
limit: Optional[int] = None,
|
|
1682
|
+
) -> Union[datatypes.PaginatedTags, Dict[str, Any], List[Any]]:
|
|
1605
1683
|
"""
|
|
1606
1684
|
Get all tags for the current user. Corresponds to GET /tags.
|
|
1607
1685
|
|
|
1686
|
+
Args:
|
|
1687
|
+
name_contains: Filter tags by name substring (optional).
|
|
1688
|
+
sort: Sort order for tags ("name", "usage", "relevance"). Default from API is "usage" (optional).
|
|
1689
|
+
attached_by: Filter tags by how they were attached ("ai", "human", "none") (optional).
|
|
1690
|
+
cursor: Pagination cursor for the next page (optional).
|
|
1691
|
+
limit: Maximum number of tags to return (optional).
|
|
1692
|
+
|
|
1608
1693
|
Returns:
|
|
1609
|
-
|
|
1694
|
+
datatypes.PaginatedTags: Paginated list of tags with nextCursor for pagination.
|
|
1610
1695
|
If response validation is disabled, returns the raw API response (dict/list).
|
|
1611
1696
|
|
|
1612
1697
|
Raises:
|
|
1613
1698
|
APIError: If the API request fails.
|
|
1614
1699
|
pydantic.ValidationError: If response validation fails (and is not disabled).
|
|
1615
1700
|
"""
|
|
1616
|
-
|
|
1701
|
+
params = {
|
|
1702
|
+
"nameContains": name_contains,
|
|
1703
|
+
"sort": sort,
|
|
1704
|
+
"attachedBy": attached_by,
|
|
1705
|
+
"cursor": cursor,
|
|
1706
|
+
"limit": limit,
|
|
1707
|
+
}
|
|
1708
|
+
response_data = self._call("GET", "tags", params=params)
|
|
1617
1709
|
|
|
1618
1710
|
if self.disable_response_validation:
|
|
1619
1711
|
logger.debug("Skipping response validation as requested.")
|
|
1620
|
-
# Return raw data, which might be {"tags": [...]} or something else
|
|
1621
1712
|
return response_data
|
|
1622
1713
|
else:
|
|
1623
|
-
# Response
|
|
1624
|
-
|
|
1625
|
-
isinstance(response_data, dict)
|
|
1626
|
-
and "tags" in response_data
|
|
1627
|
-
and isinstance(response_data["tags"], list)
|
|
1628
|
-
):
|
|
1629
|
-
try:
|
|
1630
|
-
return [
|
|
1631
|
-
datatypes.Tag.model_validate(tag)
|
|
1632
|
-
for tag in response_data["tags"]
|
|
1633
|
-
]
|
|
1634
|
-
except (
|
|
1635
|
-
Exception
|
|
1636
|
-
) as e: # Catch validation errors during list comprehension
|
|
1637
|
-
logger.error(f"Validation failed for one or more tags: {e}")
|
|
1638
|
-
raise # Re-raise the validation error
|
|
1639
|
-
else:
|
|
1640
|
-
# Raise error if format is unexpected and validation is enabled
|
|
1641
|
-
raise APIError(
|
|
1642
|
-
f"Unexpected response format for get_all_tags when validation is enabled: {response_data}"
|
|
1643
|
-
)
|
|
1714
|
+
# Response should match PaginatedTags schema
|
|
1715
|
+
return datatypes.PaginatedTags.model_validate(response_data)
|
|
1644
1716
|
|
|
1645
1717
|
@optional_typecheck
|
|
1646
1718
|
def create_a_new_tag(self, name: str) -> Dict[str, Any]:
|
{karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api/openapi_reference.json
RENAMED
|
@@ -94,6 +94,20 @@
|
|
|
94
94
|
"type": "string",
|
|
95
95
|
"nullable": true
|
|
96
96
|
},
|
|
97
|
+
"source": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"nullable": true,
|
|
100
|
+
"enum": [
|
|
101
|
+
"api",
|
|
102
|
+
"web",
|
|
103
|
+
"cli",
|
|
104
|
+
"mobile",
|
|
105
|
+
"extension",
|
|
106
|
+
"singlefile",
|
|
107
|
+
"rss",
|
|
108
|
+
"import"
|
|
109
|
+
]
|
|
110
|
+
},
|
|
97
111
|
"tags": {
|
|
98
112
|
"type": "array",
|
|
99
113
|
"items": {
|
|
@@ -349,6 +363,50 @@
|
|
|
349
363
|
"Cursor": {
|
|
350
364
|
"type": "string"
|
|
351
365
|
},
|
|
366
|
+
"List": {
|
|
367
|
+
"type": "object",
|
|
368
|
+
"properties": {
|
|
369
|
+
"id": {
|
|
370
|
+
"type": "string"
|
|
371
|
+
},
|
|
372
|
+
"name": {
|
|
373
|
+
"type": "string"
|
|
374
|
+
},
|
|
375
|
+
"description": {
|
|
376
|
+
"type": "string",
|
|
377
|
+
"nullable": true
|
|
378
|
+
},
|
|
379
|
+
"icon": {
|
|
380
|
+
"type": "string"
|
|
381
|
+
},
|
|
382
|
+
"parentId": {
|
|
383
|
+
"type": "string",
|
|
384
|
+
"nullable": true
|
|
385
|
+
},
|
|
386
|
+
"type": {
|
|
387
|
+
"type": "string",
|
|
388
|
+
"enum": [
|
|
389
|
+
"manual",
|
|
390
|
+
"smart"
|
|
391
|
+
],
|
|
392
|
+
"default": "manual"
|
|
393
|
+
},
|
|
394
|
+
"query": {
|
|
395
|
+
"type": "string",
|
|
396
|
+
"nullable": true
|
|
397
|
+
},
|
|
398
|
+
"public": {
|
|
399
|
+
"type": "boolean"
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"required": [
|
|
403
|
+
"id",
|
|
404
|
+
"name",
|
|
405
|
+
"icon",
|
|
406
|
+
"parentId",
|
|
407
|
+
"public"
|
|
408
|
+
]
|
|
409
|
+
},
|
|
352
410
|
"Highlight": {
|
|
353
411
|
"type": "object",
|
|
354
412
|
"properties": {
|
|
@@ -400,50 +458,6 @@
|
|
|
400
458
|
"createdAt"
|
|
401
459
|
]
|
|
402
460
|
},
|
|
403
|
-
"List": {
|
|
404
|
-
"type": "object",
|
|
405
|
-
"properties": {
|
|
406
|
-
"id": {
|
|
407
|
-
"type": "string"
|
|
408
|
-
},
|
|
409
|
-
"name": {
|
|
410
|
-
"type": "string"
|
|
411
|
-
},
|
|
412
|
-
"description": {
|
|
413
|
-
"type": "string",
|
|
414
|
-
"nullable": true
|
|
415
|
-
},
|
|
416
|
-
"icon": {
|
|
417
|
-
"type": "string"
|
|
418
|
-
},
|
|
419
|
-
"parentId": {
|
|
420
|
-
"type": "string",
|
|
421
|
-
"nullable": true
|
|
422
|
-
},
|
|
423
|
-
"type": {
|
|
424
|
-
"type": "string",
|
|
425
|
-
"enum": [
|
|
426
|
-
"manual",
|
|
427
|
-
"smart"
|
|
428
|
-
],
|
|
429
|
-
"default": "manual"
|
|
430
|
-
},
|
|
431
|
-
"query": {
|
|
432
|
-
"type": "string",
|
|
433
|
-
"nullable": true
|
|
434
|
-
},
|
|
435
|
-
"public": {
|
|
436
|
-
"type": "boolean"
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
"required": [
|
|
440
|
-
"id",
|
|
441
|
-
"name",
|
|
442
|
-
"icon",
|
|
443
|
-
"parentId",
|
|
444
|
-
"public"
|
|
445
|
-
]
|
|
446
|
-
},
|
|
447
461
|
"Tag": {
|
|
448
462
|
"type": "object",
|
|
449
463
|
"properties": {
|
|
@@ -693,6 +707,22 @@
|
|
|
693
707
|
"low",
|
|
694
708
|
"normal"
|
|
695
709
|
]
|
|
710
|
+
},
|
|
711
|
+
"importSessionId": {
|
|
712
|
+
"type": "string"
|
|
713
|
+
},
|
|
714
|
+
"source": {
|
|
715
|
+
"type": "string",
|
|
716
|
+
"enum": [
|
|
717
|
+
"api",
|
|
718
|
+
"web",
|
|
719
|
+
"cli",
|
|
720
|
+
"mobile",
|
|
721
|
+
"extension",
|
|
722
|
+
"singlefile",
|
|
723
|
+
"rss",
|
|
724
|
+
"import"
|
|
725
|
+
]
|
|
696
726
|
}
|
|
697
727
|
}
|
|
698
728
|
},
|
|
@@ -781,8 +811,18 @@
|
|
|
781
811
|
}
|
|
782
812
|
},
|
|
783
813
|
"responses": {
|
|
814
|
+
"200": {
|
|
815
|
+
"description": "The bookmark already exists",
|
|
816
|
+
"content": {
|
|
817
|
+
"application/json": {
|
|
818
|
+
"schema": {
|
|
819
|
+
"$ref": "#/components/schemas/Bookmark"
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
},
|
|
784
824
|
"201": {
|
|
785
|
-
"description": "The created
|
|
825
|
+
"description": "The bookmark got created",
|
|
786
826
|
"content": {
|
|
787
827
|
"application/json": {
|
|
788
828
|
"schema": {
|
|
@@ -1135,6 +1175,20 @@
|
|
|
1135
1175
|
"summary": {
|
|
1136
1176
|
"type": "string",
|
|
1137
1177
|
"nullable": true
|
|
1178
|
+
},
|
|
1179
|
+
"source": {
|
|
1180
|
+
"type": "string",
|
|
1181
|
+
"nullable": true,
|
|
1182
|
+
"enum": [
|
|
1183
|
+
"api",
|
|
1184
|
+
"web",
|
|
1185
|
+
"cli",
|
|
1186
|
+
"mobile",
|
|
1187
|
+
"extension",
|
|
1188
|
+
"singlefile",
|
|
1189
|
+
"rss",
|
|
1190
|
+
"import"
|
|
1191
|
+
]
|
|
1138
1192
|
}
|
|
1139
1193
|
},
|
|
1140
1194
|
"required": [
|
|
@@ -1245,6 +1299,20 @@
|
|
|
1245
1299
|
"summary": {
|
|
1246
1300
|
"type": "string",
|
|
1247
1301
|
"nullable": true
|
|
1302
|
+
},
|
|
1303
|
+
"source": {
|
|
1304
|
+
"type": "string",
|
|
1305
|
+
"nullable": true,
|
|
1306
|
+
"enum": [
|
|
1307
|
+
"api",
|
|
1308
|
+
"web",
|
|
1309
|
+
"cli",
|
|
1310
|
+
"mobile",
|
|
1311
|
+
"extension",
|
|
1312
|
+
"singlefile",
|
|
1313
|
+
"rss",
|
|
1314
|
+
"import"
|
|
1315
|
+
]
|
|
1248
1316
|
}
|
|
1249
1317
|
},
|
|
1250
1318
|
"required": [
|
|
@@ -1469,6 +1537,70 @@
|
|
|
1469
1537
|
}
|
|
1470
1538
|
}
|
|
1471
1539
|
},
|
|
1540
|
+
"/bookmarks/{bookmarkId}/lists": {
|
|
1541
|
+
"get": {
|
|
1542
|
+
"description": "Get lists of a bookmark",
|
|
1543
|
+
"summary": "Get lists of a bookmark",
|
|
1544
|
+
"tags": [
|
|
1545
|
+
"Bookmarks"
|
|
1546
|
+
],
|
|
1547
|
+
"security": [
|
|
1548
|
+
{
|
|
1549
|
+
"bearerAuth": []
|
|
1550
|
+
}
|
|
1551
|
+
],
|
|
1552
|
+
"parameters": [
|
|
1553
|
+
{
|
|
1554
|
+
"$ref": "#/components/parameters/BookmarkId"
|
|
1555
|
+
}
|
|
1556
|
+
],
|
|
1557
|
+
"responses": {
|
|
1558
|
+
"200": {
|
|
1559
|
+
"description": "The list of highlights",
|
|
1560
|
+
"content": {
|
|
1561
|
+
"application/json": {
|
|
1562
|
+
"schema": {
|
|
1563
|
+
"type": "object",
|
|
1564
|
+
"properties": {
|
|
1565
|
+
"lists": {
|
|
1566
|
+
"type": "array",
|
|
1567
|
+
"items": {
|
|
1568
|
+
"$ref": "#/components/schemas/List"
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
},
|
|
1572
|
+
"required": [
|
|
1573
|
+
"lists"
|
|
1574
|
+
]
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
},
|
|
1579
|
+
"404": {
|
|
1580
|
+
"description": "Bookmark not found",
|
|
1581
|
+
"content": {
|
|
1582
|
+
"application/json": {
|
|
1583
|
+
"schema": {
|
|
1584
|
+
"type": "object",
|
|
1585
|
+
"properties": {
|
|
1586
|
+
"code": {
|
|
1587
|
+
"type": "string"
|
|
1588
|
+
},
|
|
1589
|
+
"message": {
|
|
1590
|
+
"type": "string"
|
|
1591
|
+
}
|
|
1592
|
+
},
|
|
1593
|
+
"required": [
|
|
1594
|
+
"code",
|
|
1595
|
+
"message"
|
|
1596
|
+
]
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1472
1604
|
"/bookmarks/{bookmarkId}/highlights": {
|
|
1473
1605
|
"get": {
|
|
1474
1606
|
"description": "Get highlights of a bookmark",
|
|
@@ -2292,6 +2424,60 @@
|
|
|
2292
2424
|
"bearerAuth": []
|
|
2293
2425
|
}
|
|
2294
2426
|
],
|
|
2427
|
+
"parameters": [
|
|
2428
|
+
{
|
|
2429
|
+
"schema": {
|
|
2430
|
+
"type": "string"
|
|
2431
|
+
},
|
|
2432
|
+
"required": false,
|
|
2433
|
+
"name": "nameContains",
|
|
2434
|
+
"in": "query"
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
"schema": {
|
|
2438
|
+
"type": "string",
|
|
2439
|
+
"enum": [
|
|
2440
|
+
"name",
|
|
2441
|
+
"usage",
|
|
2442
|
+
"relevance"
|
|
2443
|
+
],
|
|
2444
|
+
"default": "usage"
|
|
2445
|
+
},
|
|
2446
|
+
"required": false,
|
|
2447
|
+
"name": "sort",
|
|
2448
|
+
"in": "query"
|
|
2449
|
+
},
|
|
2450
|
+
{
|
|
2451
|
+
"schema": {
|
|
2452
|
+
"type": "string",
|
|
2453
|
+
"enum": [
|
|
2454
|
+
"ai",
|
|
2455
|
+
"human",
|
|
2456
|
+
"none"
|
|
2457
|
+
]
|
|
2458
|
+
},
|
|
2459
|
+
"required": false,
|
|
2460
|
+
"name": "attachedBy",
|
|
2461
|
+
"in": "query"
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
"schema": {
|
|
2465
|
+
"type": "string"
|
|
2466
|
+
},
|
|
2467
|
+
"required": false,
|
|
2468
|
+
"name": "cursor",
|
|
2469
|
+
"in": "query"
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
"schema": {
|
|
2473
|
+
"type": "number",
|
|
2474
|
+
"nullable": true
|
|
2475
|
+
},
|
|
2476
|
+
"required": false,
|
|
2477
|
+
"name": "limit",
|
|
2478
|
+
"in": "query"
|
|
2479
|
+
}
|
|
2480
|
+
],
|
|
2295
2481
|
"responses": {
|
|
2296
2482
|
"200": {
|
|
2297
2483
|
"description": "Object with all tags data.",
|
|
@@ -2305,10 +2491,15 @@
|
|
|
2305
2491
|
"items": {
|
|
2306
2492
|
"$ref": "#/components/schemas/Tag"
|
|
2307
2493
|
}
|
|
2494
|
+
},
|
|
2495
|
+
"nextCursor": {
|
|
2496
|
+
"type": "string",
|
|
2497
|
+
"nullable": true
|
|
2308
2498
|
}
|
|
2309
2499
|
},
|
|
2310
2500
|
"required": [
|
|
2311
|
-
"tags"
|
|
2501
|
+
"tags",
|
|
2502
|
+
"nextCursor"
|
|
2312
2503
|
]
|
|
2313
2504
|
}
|
|
2314
2505
|
}
|
|
@@ -7,7 +7,7 @@ with open("README.md", "r") as readme:
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name="karakeep_python_api",
|
|
10
|
-
version="1.
|
|
10
|
+
version="1.3.1",
|
|
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",
|
|
@@ -21,9 +21,9 @@ def test_get_all_bookmarks_paginated(karakeep_client: KarakeepAPI):
|
|
|
21
21
|
try:
|
|
22
22
|
# Get the first page
|
|
23
23
|
page1 = karakeep_client.get_all_bookmarks(limit=2)
|
|
24
|
-
assert isinstance(
|
|
25
|
-
|
|
26
|
-
)
|
|
24
|
+
assert isinstance(page1, datatypes.PaginatedBookmarks), (
|
|
25
|
+
"Response should be PaginatedBookmarks model"
|
|
26
|
+
)
|
|
27
27
|
assert isinstance(page1.bookmarks, list), "Bookmarks attribute should be a list"
|
|
28
28
|
assert len(page1.bookmarks) <= 2, "Should return at most 'limit' bookmarks"
|
|
29
29
|
logger.info(f"✓ Retrieved first page with {len(page1.bookmarks)} bookmarks.")
|
|
@@ -42,9 +42,9 @@ def test_get_all_bookmarks_paginated(karakeep_client: KarakeepAPI):
|
|
|
42
42
|
)
|
|
43
43
|
# Ensure bookmarks are different from page 1 (simple check)
|
|
44
44
|
if page1.bookmarks and page2.bookmarks:
|
|
45
|
-
assert (
|
|
46
|
-
|
|
47
|
-
)
|
|
45
|
+
assert page1.bookmarks[0].id != page2.bookmarks[0].id, (
|
|
46
|
+
"Bookmarks on page 1 and 2 should differ"
|
|
47
|
+
)
|
|
48
48
|
else:
|
|
49
49
|
logger.info(" No next cursor found, pagination test ends.")
|
|
50
50
|
|
|
@@ -83,9 +83,9 @@ def test_get_all_lists(karakeep_client: KarakeepAPI):
|
|
|
83
83
|
lists = karakeep_client.get_all_lists()
|
|
84
84
|
assert isinstance(lists, list), "Response should be a list"
|
|
85
85
|
if lists: # Only check elements if the list is not empty
|
|
86
|
-
assert all(
|
|
87
|
-
|
|
88
|
-
)
|
|
86
|
+
assert all(isinstance(item, datatypes.ListModel) for item in lists), (
|
|
87
|
+
"All items should be ListModel instances"
|
|
88
|
+
)
|
|
89
89
|
logger.info(f"✓ Successfully retrieved {len(lists)} lists.")
|
|
90
90
|
except (APIError, AuthenticationError) as e:
|
|
91
91
|
pytest.fail(f"API error during list retrieval: {e}")
|
|
@@ -118,11 +118,15 @@ def test_get_all_tags(karakeep_client: KarakeepAPI):
|
|
|
118
118
|
"""Test retrieving all tags."""
|
|
119
119
|
try:
|
|
120
120
|
tags = karakeep_client.get_all_tags()
|
|
121
|
-
assert isinstance(tags,
|
|
121
|
+
assert isinstance(tags, datatypes.PaginatedTags), (
|
|
122
|
+
"Response should be of type PaginatedTags"
|
|
123
|
+
)
|
|
124
|
+
tags = tags.tags
|
|
125
|
+
assert isinstance(tags, list), "tags var should be of type list at this point"
|
|
122
126
|
if tags: # Only check elements if the list is not empty
|
|
123
|
-
assert all(
|
|
124
|
-
|
|
125
|
-
)
|
|
127
|
+
assert all(isinstance(item, datatypes.Tag) for item in tags), (
|
|
128
|
+
"All items should be Tag instances"
|
|
129
|
+
)
|
|
126
130
|
logger.info(f"✓ Successfully retrieved {len(tags)} tags.")
|
|
127
131
|
except (APIError, AuthenticationError) as e:
|
|
128
132
|
pytest.fail(f"API error during tag retrieval: {e}")
|
|
@@ -156,12 +160,12 @@ def test_get_all_highlights_paginated(karakeep_client: KarakeepAPI):
|
|
|
156
160
|
try:
|
|
157
161
|
# Get the first page
|
|
158
162
|
page1 = karakeep_client.get_all_highlights(limit=3)
|
|
159
|
-
assert isinstance(
|
|
160
|
-
|
|
161
|
-
)
|
|
162
|
-
assert isinstance(
|
|
163
|
-
|
|
164
|
-
)
|
|
163
|
+
assert isinstance(page1, datatypes.PaginatedHighlights), (
|
|
164
|
+
"Response should be PaginatedHighlights model"
|
|
165
|
+
)
|
|
166
|
+
assert isinstance(page1.highlights, list), (
|
|
167
|
+
"Highlights attribute should be a list"
|
|
168
|
+
)
|
|
165
169
|
assert len(page1.highlights) <= 3, "Should return at most 'limit' highlights"
|
|
166
170
|
logger.info(f"✓ Retrieved first page with {len(page1.highlights)} highlights.")
|
|
167
171
|
|
|
@@ -179,9 +183,9 @@ def test_get_all_highlights_paginated(karakeep_client: KarakeepAPI):
|
|
|
179
183
|
)
|
|
180
184
|
# Ensure highlights are different from page 1 (simple check)
|
|
181
185
|
if page1.highlights and page2.highlights:
|
|
182
|
-
assert (
|
|
183
|
-
|
|
184
|
-
)
|
|
186
|
+
assert page1.highlights[0].id != page2.highlights[0].id, (
|
|
187
|
+
"Highlights on page 1 and 2 should differ"
|
|
188
|
+
)
|
|
185
189
|
else:
|
|
186
190
|
logger.info(" No next cursor found, pagination test ends.")
|
|
187
191
|
|
|
@@ -224,9 +228,9 @@ def test_openapi_spec_accessible(karakeep_client: KarakeepAPI):
|
|
|
224
228
|
assert spec is not None, "openapi_spec attribute should not be None"
|
|
225
229
|
assert isinstance(spec, dict), "openapi_spec should be a dictionary"
|
|
226
230
|
# Check for a top-level key expected in an OpenAPI spec
|
|
227
|
-
assert (
|
|
228
|
-
"openapi
|
|
229
|
-
)
|
|
231
|
+
assert "openapi" in spec, (
|
|
232
|
+
"openapi_spec should contain the 'openapi' version key"
|
|
233
|
+
)
|
|
230
234
|
logger.info(
|
|
231
235
|
f"✓ Successfully accessed openapi_spec attribute. Version: {spec.get('openapi', 'N/A')}"
|
|
232
236
|
)
|
|
@@ -284,9 +288,9 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
284
288
|
created_list = karakeep_client.create_a_new_list(
|
|
285
289
|
name=list_name, icon=list_icon, list_type="manual"
|
|
286
290
|
)
|
|
287
|
-
assert isinstance(
|
|
288
|
-
|
|
289
|
-
)
|
|
291
|
+
assert isinstance(created_list, datatypes.ListModel), (
|
|
292
|
+
"Response should be a ListModel"
|
|
293
|
+
)
|
|
290
294
|
assert created_list.name == list_name, "Created list name should match"
|
|
291
295
|
assert created_list.icon == list_icon, "Created list icon should match"
|
|
292
296
|
assert created_list.id, "Created list must have an ID"
|
|
@@ -295,12 +299,12 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
295
299
|
|
|
296
300
|
# 4. Verify the list appears in get_all_lists
|
|
297
301
|
current_lists_after_create = karakeep_client.get_all_lists()
|
|
298
|
-
assert (
|
|
299
|
-
|
|
300
|
-
)
|
|
301
|
-
assert any(
|
|
302
|
-
|
|
303
|
-
)
|
|
302
|
+
assert len(current_lists_after_create) == initial_list_count + 1, (
|
|
303
|
+
"List count should increase by one after creation"
|
|
304
|
+
)
|
|
305
|
+
assert any(lst.id == created_list_id for lst in current_lists_after_create), (
|
|
306
|
+
"Created list should be present in the list of all lists"
|
|
307
|
+
)
|
|
304
308
|
logger.info(f" List count after creation: {len(current_lists_after_create)}")
|
|
305
309
|
logger.info(f"✓ Verified list {created_list_id} is present in get_all_lists.")
|
|
306
310
|
|
|
@@ -331,21 +335,21 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
331
335
|
f"List with ID {created_list_id} should not exist after deletion, but get_a_single_list succeeded."
|
|
332
336
|
)
|
|
333
337
|
except APIError as e:
|
|
334
|
-
assert (
|
|
335
|
-
e.status_code
|
|
336
|
-
)
|
|
338
|
+
assert e.status_code == 404, (
|
|
339
|
+
f"Expected 404 Not Found when getting deleted list, but got status {e.status_code}"
|
|
340
|
+
)
|
|
337
341
|
logger.info(
|
|
338
342
|
f"✓ Confirmed list {created_list_id} is deleted (received 404)."
|
|
339
343
|
)
|
|
340
344
|
|
|
341
345
|
# 8. Verify list count decreased (optional check)
|
|
342
346
|
final_lists = karakeep_client.get_all_lists()
|
|
343
|
-
assert (
|
|
344
|
-
|
|
345
|
-
)
|
|
346
|
-
assert not any(
|
|
347
|
-
|
|
348
|
-
)
|
|
347
|
+
assert len(final_lists) == initial_list_count, (
|
|
348
|
+
"List count should return to initial count after deletion"
|
|
349
|
+
)
|
|
350
|
+
assert not any(lst.id == created_list_id for lst in final_lists), (
|
|
351
|
+
"Deleted list should not be present in the final list of all lists"
|
|
352
|
+
)
|
|
349
353
|
logger.info(f" Final list count: {len(final_lists)}")
|
|
350
354
|
|
|
351
355
|
except (APIError, AuthenticationError) as e:
|
|
@@ -410,12 +414,12 @@ def test_create_and_delete_bookmark(
|
|
|
410
414
|
search_results = karakeep_client.search_bookmarks(
|
|
411
415
|
q=search_query_component, limit=100, include_content=False
|
|
412
416
|
)
|
|
413
|
-
assert isinstance(
|
|
414
|
-
|
|
415
|
-
)
|
|
416
|
-
assert isinstance(
|
|
417
|
-
|
|
418
|
-
)
|
|
417
|
+
assert isinstance(search_results, datatypes.PaginatedBookmarks), (
|
|
418
|
+
"Search response should be PaginatedBookmarks model"
|
|
419
|
+
)
|
|
420
|
+
assert isinstance(search_results.bookmarks, list), (
|
|
421
|
+
"Search results bookmarks attribute should be a list"
|
|
422
|
+
)
|
|
419
423
|
|
|
420
424
|
titles_in_search = [b.title for b in search_results.bookmarks]
|
|
421
425
|
found_in_search = any(
|
|
@@ -425,9 +429,9 @@ def test_create_and_delete_bookmark(
|
|
|
425
429
|
break
|
|
426
430
|
else:
|
|
427
431
|
time.sleep(3)
|
|
428
|
-
assert (
|
|
429
|
-
|
|
430
|
-
)
|
|
432
|
+
assert found_in_search, (
|
|
433
|
+
f"Managed bookmark {created_bookmark_id} (Title: '{original_title}') not found in {trial + 1} different search results for '{search_query_component}'. Titles were: '{titles_in_search}'."
|
|
434
|
+
)
|
|
431
435
|
logger.info(
|
|
432
436
|
f"✓ Found managed bookmark in search results for '{search_query_component}'."
|
|
433
437
|
)
|
|
@@ -445,9 +449,9 @@ def test_create_and_delete_bookmark(
|
|
|
445
449
|
capture_output=True,
|
|
446
450
|
text=True,
|
|
447
451
|
)
|
|
448
|
-
assert (
|
|
449
|
-
created_bookmark_id in
|
|
450
|
-
)
|
|
452
|
+
assert created_bookmark_id in search_cli_output.stdout, (
|
|
453
|
+
f"Managed bookmark ID {created_bookmark_id} not found in CLI search output for '{search_query_component}'"
|
|
454
|
+
)
|
|
451
455
|
logger.info(
|
|
452
456
|
"✓ CLI search command executed successfully and contained the bookmark ID."
|
|
453
457
|
)
|
|
@@ -498,12 +502,12 @@ def test_update_bookmark_title(
|
|
|
498
502
|
updated_bookmark_partial = karakeep_client.update_a_bookmark(
|
|
499
503
|
bookmark_id=created_bookmark_id, update_data=update_payload_api
|
|
500
504
|
)
|
|
501
|
-
assert isinstance(
|
|
502
|
-
|
|
503
|
-
)
|
|
504
|
-
assert (
|
|
505
|
-
updated_bookmark_partial.get(
|
|
506
|
-
)
|
|
505
|
+
assert isinstance(updated_bookmark_partial, dict), (
|
|
506
|
+
"Update response should be a dict"
|
|
507
|
+
)
|
|
508
|
+
assert updated_bookmark_partial.get("title") == target_api_title, (
|
|
509
|
+
f"Partial response title '{updated_bookmark_partial.get('title')}' does not match target API title '{target_api_title}'"
|
|
510
|
+
)
|
|
507
511
|
logger.info(
|
|
508
512
|
f"✓ API call to update_a_bookmark successful. Partial response title: '{updated_bookmark_partial.get('title')}'"
|
|
509
513
|
)
|
|
@@ -516,9 +520,9 @@ def test_update_bookmark_title(
|
|
|
516
520
|
bookmark_id=created_bookmark_id
|
|
517
521
|
)
|
|
518
522
|
assert isinstance(retrieved_bookmark_after_api_update, datatypes.Bookmark)
|
|
519
|
-
assert (
|
|
520
|
-
retrieved_bookmark_after_api_update.title
|
|
521
|
-
)
|
|
523
|
+
assert retrieved_bookmark_after_api_update.title == target_api_title, (
|
|
524
|
+
f"Retrieved bookmark title '{retrieved_bookmark_after_api_update.title}' does not match expected API-updated title '{target_api_title}'"
|
|
525
|
+
)
|
|
522
526
|
logger.info(
|
|
523
527
|
f"✓ Successfully verified bookmark title updated by API to: '{retrieved_bookmark_after_api_update.title}'"
|
|
524
528
|
)
|
|
@@ -549,9 +553,9 @@ def test_update_bookmark_title(
|
|
|
549
553
|
bookmark_id=created_bookmark_id
|
|
550
554
|
)
|
|
551
555
|
assert isinstance(retrieved_bookmark_after_cli_update, datatypes.Bookmark)
|
|
552
|
-
assert (
|
|
553
|
-
retrieved_bookmark_after_cli_update.title
|
|
554
|
-
)
|
|
556
|
+
assert retrieved_bookmark_after_cli_update.title == target_cli_title, (
|
|
557
|
+
f"Retrieved bookmark title '{retrieved_bookmark_after_cli_update.title}' after CLI update does not match expected '{target_cli_title}'"
|
|
558
|
+
)
|
|
555
559
|
logger.info(
|
|
556
560
|
f"✓ Successfully verified bookmark title updated by CLI to: '{retrieved_bookmark_after_cli_update.title}'"
|
|
557
561
|
)
|
|
@@ -617,9 +621,9 @@ def test_tag_lifecycle_on_bookmark(
|
|
|
617
621
|
# assert isinstance(updated_tag, datatypes.Tag), "Update tag response should be Tag model"
|
|
618
622
|
# assert updated_tag.name == updated_tag_name, "Tag name was not updated as expected"
|
|
619
623
|
# logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag.name}'")
|
|
620
|
-
assert (
|
|
621
|
-
|
|
622
|
-
)
|
|
624
|
+
assert updated_tag["name"] == updated_tag_name, (
|
|
625
|
+
"Tag name was not updated as expected"
|
|
626
|
+
)
|
|
623
627
|
logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag['name']}'")
|
|
624
628
|
|
|
625
629
|
# 3. Verify tag update by getting it directly
|
|
@@ -627,12 +631,12 @@ def test_tag_lifecycle_on_bookmark(
|
|
|
627
631
|
f"\nFetching tag {tag_id_to_manage} to verify its name is '{updated_tag_name}'"
|
|
628
632
|
)
|
|
629
633
|
retrieved_tag = karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
|
|
630
|
-
assert isinstance(
|
|
631
|
-
|
|
632
|
-
)
|
|
633
|
-
assert (
|
|
634
|
-
|
|
635
|
-
)
|
|
634
|
+
assert isinstance(retrieved_tag, datatypes.Tag), (
|
|
635
|
+
"Get single tag response should be Tag model"
|
|
636
|
+
)
|
|
637
|
+
assert retrieved_tag.name == updated_tag_name, (
|
|
638
|
+
"Retrieved tag name does not match updated name"
|
|
639
|
+
)
|
|
636
640
|
assert retrieved_tag.id == tag_id_to_manage, "Retrieved tag ID does not match"
|
|
637
641
|
logger.info(
|
|
638
642
|
f"✓ Verified tag {tag_id_to_manage} has name '{retrieved_tag.name}'"
|
|
@@ -670,9 +674,9 @@ def test_tag_lifecycle_on_bookmark(
|
|
|
670
674
|
f"Tag {tag_id_to_manage} should not exist after deletion, but get_a_single_tag succeeded."
|
|
671
675
|
)
|
|
672
676
|
except APIError as e:
|
|
673
|
-
assert (
|
|
674
|
-
e.status_code
|
|
675
|
-
)
|
|
677
|
+
assert e.status_code == 404, (
|
|
678
|
+
f"Expected 404 Not Found when getting deleted tag, but got status {e.status_code}"
|
|
679
|
+
)
|
|
676
680
|
logger.info(
|
|
677
681
|
f"✓ Confirmed tag {tag_id_to_manage} is deleted (received 404)."
|
|
678
682
|
)
|
|
@@ -725,9 +729,9 @@ def test_cli_get_bookmarks_count_with_jq(karakeep_client: KarakeepAPI):
|
|
|
725
729
|
logger.info(f"✓ Command returned {actual_count} bookmarks")
|
|
726
730
|
|
|
727
731
|
# Check if we got exactly the requested number or fewer (if there aren't enough bookmarks)
|
|
728
|
-
assert (
|
|
729
|
-
|
|
730
|
-
)
|
|
732
|
+
assert actual_count <= test_limit, (
|
|
733
|
+
f"Expected at most {test_limit} bookmarks, got {actual_count}"
|
|
734
|
+
)
|
|
731
735
|
|
|
732
736
|
# Check if we got any bookmarks at all (to ensure the test is meaningful)
|
|
733
737
|
# This could fail if the account has no bookmarks
|
|
@@ -821,9 +825,9 @@ def test_asset_lifecycle_with_pdf(karakeep_client: KarakeepAPI):
|
|
|
821
825
|
assert isinstance(uploaded_asset, datatypes.Asset)
|
|
822
826
|
assert uploaded_asset.assetId, "Uploaded asset must have an ID"
|
|
823
827
|
assert "pdf" in uploaded_asset.contentType.lower(), "Asset should be PDF type"
|
|
824
|
-
assert (
|
|
825
|
-
|
|
826
|
-
)
|
|
828
|
+
assert uploaded_asset.fileName == "PDF Bookmark Sample.pdf", (
|
|
829
|
+
"Asset filename should match the original file"
|
|
830
|
+
)
|
|
827
831
|
uploaded_asset_id = uploaded_asset.assetId
|
|
828
832
|
logger.info(f"✓ PDF uploaded with asset ID: {uploaded_asset_id}")
|
|
829
833
|
|
|
@@ -848,15 +852,15 @@ def test_asset_lifecycle_with_pdf(karakeep_client: KarakeepAPI):
|
|
|
848
852
|
bookmark_id=created_bookmark_id
|
|
849
853
|
)
|
|
850
854
|
assert isinstance(retrieved_bookmark, datatypes.Bookmark)
|
|
851
|
-
assert (
|
|
852
|
-
|
|
853
|
-
)
|
|
855
|
+
assert len(retrieved_bookmark.assets) > 0, (
|
|
856
|
+
"Bookmark should have at least one asset"
|
|
857
|
+
)
|
|
854
858
|
|
|
855
859
|
# Check that our uploaded asset is among the bookmark's assets
|
|
856
860
|
asset_ids = [asset.id for asset in retrieved_bookmark.assets]
|
|
857
|
-
assert (
|
|
858
|
-
uploaded_asset_id
|
|
859
|
-
)
|
|
861
|
+
assert uploaded_asset_id in asset_ids, (
|
|
862
|
+
f"Uploaded asset {uploaded_asset_id} should be attached to bookmark"
|
|
863
|
+
)
|
|
860
864
|
logger.info(f"✓ Verified bookmark contains the PDF asset {uploaded_asset_id}")
|
|
861
865
|
|
|
862
866
|
# 4. Retrieve and verify the asset content
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{karakeep_python_api-1.2.4 → karakeep_python_api-1.3.1}/karakeep_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|