karakeep-python-api 1.4.0__tar.gz → 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {karakeep_python_api-1.4.0/karakeep_python_api.egg-info → karakeep_python_api-1.5.0}/PKG-INFO +8 -2
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/README.md +7 -1
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/datatypes.py +16 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/karakeep_api.py +194 -4
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/openapi_reference.json +422 -4
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0/karakeep_python_api.egg-info}/PKG-INFO +8 -2
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/setup.py +1 -1
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/tests/test_karakeep_api.py +130 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/LICENSE +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/MANIFEST.in +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/__init__.py +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/__main__.py +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/entry_points.txt +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/requires.txt +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/top_level.txt +0 -0
- {karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/setup.cfg +0 -0
{karakeep_python_api-1.4.0/karakeep_python_api.egg-info → karakeep_python_api-1.5.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: karakeep_python_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
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
|
|
@@ -70,6 +70,7 @@ The development process involved:
|
|
|
70
70
|
|
|
71
71
|
* **Experimental Methods:** The included Pytest suite currently only covers a subset of the available API methods (primarily 'get all' endpoints and client initialization). Methods *not* explicitly tested should be considered **experimental**.
|
|
72
72
|
* **Ongoing Development:** The author intends to improve and validate methods as they are needed for personal use cases. Contributions and bug reports are welcome!
|
|
73
|
+
* **Updating process**: I have local scripts to keep track of the changes in the OpenAPI specs on the server side. From time to time I go and use aider to make the code reflect the latest openapi and push that to the `dev` branch. When there is a new karakeep release, I will merge the `dev` branch to the `main` branch and create my own release. This way, `dev` hopefully is up to date with the latest code of karakeep, while `main` is up to date with the latest release of karakeep.
|
|
73
74
|
|
|
74
75
|
## API Method Coverage
|
|
75
76
|
|
|
@@ -116,7 +117,12 @@ Methods or CLI commands marked with ❌ should be used with caution as their beh
|
|
|
116
117
|
| `get_a_single_asset` | ✅ | ❌ | Tested in PDF asset lifecycle test. |
|
|
117
118
|
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
118
119
|
| `get_current_user_stats` | ✅ | ✅ | |
|
|
119
|
-
| `update_user`
|
|
120
|
+
| `update_user` | ❌ | ❌ | |
|
|
121
|
+
| `get_all_backups` | ✅ | ✅ | Tested in backup lifecycle test. |
|
|
122
|
+
| `trigger_a_new_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
123
|
+
| `get_a_single_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
124
|
+
| `delete_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
125
|
+
| `download_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
120
126
|
|
|
121
127
|
## Installation
|
|
122
128
|
|
|
@@ -35,6 +35,7 @@ The development process involved:
|
|
|
35
35
|
|
|
36
36
|
* **Experimental Methods:** The included Pytest suite currently only covers a subset of the available API methods (primarily 'get all' endpoints and client initialization). Methods *not* explicitly tested should be considered **experimental**.
|
|
37
37
|
* **Ongoing Development:** The author intends to improve and validate methods as they are needed for personal use cases. Contributions and bug reports are welcome!
|
|
38
|
+
* **Updating process**: I have local scripts to keep track of the changes in the OpenAPI specs on the server side. From time to time I go and use aider to make the code reflect the latest openapi and push that to the `dev` branch. When there is a new karakeep release, I will merge the `dev` branch to the `main` branch and create my own release. This way, `dev` hopefully is up to date with the latest code of karakeep, while `main` is up to date with the latest release of karakeep.
|
|
38
39
|
|
|
39
40
|
## API Method Coverage
|
|
40
41
|
|
|
@@ -81,7 +82,12 @@ Methods or CLI commands marked with ❌ should be used with caution as their beh
|
|
|
81
82
|
| `get_a_single_asset` | ✅ | ❌ | Tested in PDF asset lifecycle test. |
|
|
82
83
|
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
83
84
|
| `get_current_user_stats` | ✅ | ✅ | |
|
|
84
|
-
| `update_user`
|
|
85
|
+
| `update_user` | ❌ | ❌ | |
|
|
86
|
+
| `get_all_backups` | ✅ | ✅ | Tested in backup lifecycle test. |
|
|
87
|
+
| `trigger_a_new_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
88
|
+
| `get_a_single_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
89
|
+
| `delete_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
90
|
+
| `download_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
85
91
|
|
|
86
92
|
## Installation
|
|
87
93
|
|
|
@@ -90,8 +90,10 @@ class BookmarkAsset(BaseModel):
|
|
|
90
90
|
"video",
|
|
91
91
|
"bookmarkAsset",
|
|
92
92
|
"precrawledArchive",
|
|
93
|
+
"userUploaded",
|
|
93
94
|
"unknown",
|
|
94
95
|
]
|
|
96
|
+
fileName: Optional[str] = None
|
|
95
97
|
|
|
96
98
|
|
|
97
99
|
class Asset(BaseModel):
|
|
@@ -117,6 +119,7 @@ class Bookmark(BaseModel):
|
|
|
117
119
|
"api", "web", "cli", "mobile", "extension", "singlefile", "rss", "import"
|
|
118
120
|
]
|
|
119
121
|
] = None
|
|
122
|
+
userId: str
|
|
120
123
|
tags: List[TagShort]
|
|
121
124
|
content: Union[
|
|
122
125
|
ContentTypeLink, ContentTypeText, ContentTypeAsset, ContentTypeUnknown
|
|
@@ -138,6 +141,8 @@ class ListModel(BaseModel):
|
|
|
138
141
|
type: Optional[Literal["manual", "smart"]] = "manual"
|
|
139
142
|
query: Optional[str] = None
|
|
140
143
|
public: bool
|
|
144
|
+
hasCollaborators: bool
|
|
145
|
+
userRole: Literal["owner", "editor", "viewer", "public"]
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
class Highlight(BaseModel):
|
|
@@ -160,3 +165,14 @@ class PaginatedHighlights(BaseModel):
|
|
|
160
165
|
class PaginatedTags(BaseModel):
|
|
161
166
|
tags: List[Tag]
|
|
162
167
|
nextCursor: Optional[str] = ""
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class Backup(BaseModel):
|
|
171
|
+
id: str
|
|
172
|
+
userId: str
|
|
173
|
+
assetId: Optional[str]
|
|
174
|
+
createdAt: str
|
|
175
|
+
size: float
|
|
176
|
+
bookmarkCount: int
|
|
177
|
+
status: Literal["pending", "success", "failure"]
|
|
178
|
+
errorMessage: Optional[str] = None
|
|
@@ -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.5.0"
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
@@ -134,7 +134,7 @@ class KarakeepAPI:
|
|
|
134
134
|
|
|
135
135
|
# Configure logger based on verbose setting
|
|
136
136
|
log_level = "DEBUG" if self.verbose else "INFO"
|
|
137
|
-
logger.remove() # Remove default handler
|
|
137
|
+
# logger.remove() # Remove default handler
|
|
138
138
|
if self.verbose:
|
|
139
139
|
logger.add(
|
|
140
140
|
sys.stderr,
|
|
@@ -1301,6 +1301,7 @@ class KarakeepAPI:
|
|
|
1301
1301
|
"video",
|
|
1302
1302
|
"bookmarkAsset",
|
|
1303
1303
|
"precrawledArchive",
|
|
1304
|
+
"userUploaded",
|
|
1304
1305
|
"unknown",
|
|
1305
1306
|
],
|
|
1306
1307
|
) -> Union[datatypes.BookmarkAsset, Dict[str, Any], List[Any]]:
|
|
@@ -1311,7 +1312,8 @@ class KarakeepAPI:
|
|
|
1311
1312
|
bookmark_id: The ID (string) of the bookmark.
|
|
1312
1313
|
asset_id: The ID (string) of the asset to attach.
|
|
1313
1314
|
asset_type: The type of asset being attached. Must be one of: "screenshot", "assetScreenshot",
|
|
1314
|
-
"bannerImage", "fullPageArchive", "video", "bookmarkAsset", "precrawledArchive",
|
|
1315
|
+
"bannerImage", "fullPageArchive", "video", "bookmarkAsset", "precrawledArchive",
|
|
1316
|
+
"userUploaded", "unknown".
|
|
1315
1317
|
|
|
1316
1318
|
Returns:
|
|
1317
1319
|
datatypes.BookmarkAsset: The attached asset object.
|
|
@@ -2058,7 +2060,15 @@ class KarakeepAPI:
|
|
|
2058
2060
|
Get statistics about the current authenticated user's data. Corresponds to GET /users/me/stats.
|
|
2059
2061
|
|
|
2060
2062
|
Returns:
|
|
2061
|
-
dict: A dictionary containing user statistics
|
|
2063
|
+
dict: A dictionary containing user statistics including:
|
|
2064
|
+
- numBookmarks, numFavorites, numArchived, numTags, numLists, numHighlights
|
|
2065
|
+
- bookmarksByType: breakdown by content type (link, text, asset)
|
|
2066
|
+
- topDomains: most bookmarked domains
|
|
2067
|
+
- totalAssetSize: total size of stored assets
|
|
2068
|
+
- assetsByType: breakdown of assets by type
|
|
2069
|
+
- bookmarkingActivity: activity metrics (thisWeek, thisMonth, thisYear, byHour, byDayOfWeek)
|
|
2070
|
+
- tagUsage: most used tags
|
|
2071
|
+
- bookmarksBySource: breakdown by source (api, web, cli, mobile, extension, singlefile, rss, import)
|
|
2062
2072
|
Validation is not performed on this response type by default.
|
|
2063
2073
|
|
|
2064
2074
|
Raises:
|
|
@@ -2175,6 +2185,186 @@ class KarakeepAPI:
|
|
|
2175
2185
|
# Response should match Asset schema
|
|
2176
2186
|
return datatypes.Asset.model_validate(response_data)
|
|
2177
2187
|
|
|
2188
|
+
@optional_typecheck
|
|
2189
|
+
def get_all_backups(
|
|
2190
|
+
self,
|
|
2191
|
+
) -> Union[List[datatypes.Backup], Dict[str, Any], List[Any]]:
|
|
2192
|
+
"""
|
|
2193
|
+
Get all backups for the current user. Corresponds to GET /backups.
|
|
2194
|
+
|
|
2195
|
+
Returns:
|
|
2196
|
+
List[datatypes.Backup]: A list of backup objects.
|
|
2197
|
+
If response validation is disabled, returns the raw API response (dict/list).
|
|
2198
|
+
|
|
2199
|
+
Raises:
|
|
2200
|
+
APIError: If the API request fails.
|
|
2201
|
+
pydantic.ValidationError: If response validation fails (and is not disabled).
|
|
2202
|
+
"""
|
|
2203
|
+
response_data = self._call("GET", "backups")
|
|
2204
|
+
|
|
2205
|
+
if self.disable_response_validation:
|
|
2206
|
+
logger.debug("Skipping response validation as requested.")
|
|
2207
|
+
# Return raw data, which might be {"backups": [...]} or something else
|
|
2208
|
+
return response_data
|
|
2209
|
+
else:
|
|
2210
|
+
# Response schema is {"backups": [Backup]}, extract the list and validate
|
|
2211
|
+
if (
|
|
2212
|
+
isinstance(response_data, dict)
|
|
2213
|
+
and "backups" in response_data
|
|
2214
|
+
and isinstance(response_data["backups"], list)
|
|
2215
|
+
):
|
|
2216
|
+
try:
|
|
2217
|
+
return [
|
|
2218
|
+
datatypes.Backup.model_validate(backup)
|
|
2219
|
+
for backup in response_data["backups"]
|
|
2220
|
+
]
|
|
2221
|
+
except (
|
|
2222
|
+
Exception
|
|
2223
|
+
) as e: # Catch validation errors during list comprehension
|
|
2224
|
+
logger.error(f"Validation failed for one or more backups: {e}")
|
|
2225
|
+
raise # Re-raise the validation error
|
|
2226
|
+
else:
|
|
2227
|
+
# Raise error if format is unexpected and validation is enabled
|
|
2228
|
+
raise APIError(
|
|
2229
|
+
f"Unexpected response format for get_all_backups when validation is enabled: {response_data}"
|
|
2230
|
+
)
|
|
2231
|
+
|
|
2232
|
+
@optional_typecheck
|
|
2233
|
+
def trigger_a_new_backup(
|
|
2234
|
+
self,
|
|
2235
|
+
) -> Union[datatypes.Backup, Dict[str, Any], List[Any]]:
|
|
2236
|
+
"""
|
|
2237
|
+
Trigger a new backup. Corresponds to POST /backups.
|
|
2238
|
+
|
|
2239
|
+
Returns:
|
|
2240
|
+
datatypes.Backup: The created backup object.
|
|
2241
|
+
If response validation is disabled, returns the raw API response (dict/list).
|
|
2242
|
+
|
|
2243
|
+
Raises:
|
|
2244
|
+
APIError: If the API request fails.
|
|
2245
|
+
pydantic.ValidationError: If response validation fails (and is not disabled).
|
|
2246
|
+
"""
|
|
2247
|
+
response_data = self._call("POST", "backups")
|
|
2248
|
+
|
|
2249
|
+
if self.disable_response_validation:
|
|
2250
|
+
logger.debug("Skipping response validation as requested.")
|
|
2251
|
+
return response_data
|
|
2252
|
+
else:
|
|
2253
|
+
# Response should match Backup schema
|
|
2254
|
+
return datatypes.Backup.model_validate(response_data)
|
|
2255
|
+
|
|
2256
|
+
@optional_typecheck
|
|
2257
|
+
def get_a_single_backup(
|
|
2258
|
+
self, backup_id: str
|
|
2259
|
+
) -> Union[datatypes.Backup, Dict[str, Any], List[Any]]:
|
|
2260
|
+
"""
|
|
2261
|
+
Get a single backup by its ID. Corresponds to GET /backups/{backupId}.
|
|
2262
|
+
|
|
2263
|
+
Args:
|
|
2264
|
+
backup_id: The ID (string) of the backup to retrieve.
|
|
2265
|
+
|
|
2266
|
+
Returns:
|
|
2267
|
+
datatypes.Backup: The requested backup object.
|
|
2268
|
+
If response validation is disabled, returns the raw API response (dict/list).
|
|
2269
|
+
|
|
2270
|
+
Raises:
|
|
2271
|
+
APIError: If the API request fails (e.g., 404 backup not found).
|
|
2272
|
+
pydantic.ValidationError: If response validation fails (and is not disabled).
|
|
2273
|
+
"""
|
|
2274
|
+
endpoint = f"backups/{backup_id}"
|
|
2275
|
+
response_data = self._call("GET", endpoint)
|
|
2276
|
+
|
|
2277
|
+
if self.disable_response_validation:
|
|
2278
|
+
logger.debug("Skipping response validation as requested.")
|
|
2279
|
+
return response_data
|
|
2280
|
+
else:
|
|
2281
|
+
# Response should match Backup schema
|
|
2282
|
+
return datatypes.Backup.model_validate(response_data)
|
|
2283
|
+
|
|
2284
|
+
@optional_typecheck
|
|
2285
|
+
def delete_a_backup(self, backup_id: str) -> None:
|
|
2286
|
+
"""
|
|
2287
|
+
Delete a backup by its ID. Corresponds to DELETE /backups/{backupId}.
|
|
2288
|
+
|
|
2289
|
+
Args:
|
|
2290
|
+
backup_id: The ID (string) of the backup to delete.
|
|
2291
|
+
|
|
2292
|
+
Returns:
|
|
2293
|
+
None: Returns None upon successful deletion (204 No Content).
|
|
2294
|
+
|
|
2295
|
+
Raises:
|
|
2296
|
+
APIError: If the API request fails (e.g., 404 backup not found).
|
|
2297
|
+
"""
|
|
2298
|
+
endpoint = f"backups/{backup_id}"
|
|
2299
|
+
self._call("DELETE", endpoint) # Expects 204 No Content
|
|
2300
|
+
return None # Explicitly return None for 204
|
|
2301
|
+
|
|
2302
|
+
@optional_typecheck
|
|
2303
|
+
def download_a_backup(self, backup_id: str) -> bytes:
|
|
2304
|
+
"""
|
|
2305
|
+
Download the backup file (zip archive) by its ID. Corresponds to GET /backups/{backupId}/download.
|
|
2306
|
+
|
|
2307
|
+
Args:
|
|
2308
|
+
backup_id: The ID (string) of the backup to download.
|
|
2309
|
+
|
|
2310
|
+
Returns:
|
|
2311
|
+
bytes: The raw backup file content (zip archive).
|
|
2312
|
+
|
|
2313
|
+
Raises:
|
|
2314
|
+
APIError: If the API request fails (e.g., 404 backup not found).
|
|
2315
|
+
ValueError: If backup_id is empty or invalid.
|
|
2316
|
+
|
|
2317
|
+
Note:
|
|
2318
|
+
This method always returns raw bytes regardless of the disable_response_validation setting,
|
|
2319
|
+
as the response is binary content (zip file) rather than JSON.
|
|
2320
|
+
"""
|
|
2321
|
+
# Validate backup_id
|
|
2322
|
+
if not backup_id or not backup_id.strip():
|
|
2323
|
+
raise ValueError("backup_id cannot be empty")
|
|
2324
|
+
|
|
2325
|
+
backup_id = backup_id.strip()
|
|
2326
|
+
|
|
2327
|
+
# Validate backup_id format (basic check for reasonable ID format)
|
|
2328
|
+
if len(backup_id) < 5: # Assuming backup IDs are at least 5 characters
|
|
2329
|
+
raise ValueError(f"backup_id appears to be invalid: {backup_id}")
|
|
2330
|
+
|
|
2331
|
+
endpoint = f"backups/{backup_id}/download"
|
|
2332
|
+
|
|
2333
|
+
# Override the Accept header to get the zip file instead of JSON
|
|
2334
|
+
extra_headers = {"Accept": "application/zip"}
|
|
2335
|
+
|
|
2336
|
+
if self.verbose:
|
|
2337
|
+
logger.debug(f"Downloading backup: {backup_id}")
|
|
2338
|
+
|
|
2339
|
+
response_data = self._call("GET", endpoint, extra_headers=extra_headers)
|
|
2340
|
+
|
|
2341
|
+
# The _call method should return bytes for non-JSON responses
|
|
2342
|
+
if isinstance(response_data, bytes):
|
|
2343
|
+
if self.verbose:
|
|
2344
|
+
logger.debug(
|
|
2345
|
+
f"Downloaded backup {backup_id}: {len(response_data)} bytes"
|
|
2346
|
+
)
|
|
2347
|
+
return response_data
|
|
2348
|
+
elif response_data is None:
|
|
2349
|
+
# Handle empty response
|
|
2350
|
+
if self.verbose:
|
|
2351
|
+
logger.debug(f"Downloaded empty backup {backup_id}")
|
|
2352
|
+
return b""
|
|
2353
|
+
else:
|
|
2354
|
+
# This shouldn't happen with the updated _call method, but handle gracefully
|
|
2355
|
+
error_msg = f"Expected bytes from backup download endpoint for backup {backup_id}, got {type(response_data).__name__}"
|
|
2356
|
+
if isinstance(response_data, (dict, list)):
|
|
2357
|
+
# If we got JSON, it might be an error response that wasn't caught
|
|
2358
|
+
error_detail = (
|
|
2359
|
+
str(response_data)[:200] + "..."
|
|
2360
|
+
if len(str(response_data)) > 200
|
|
2361
|
+
else str(response_data)
|
|
2362
|
+
)
|
|
2363
|
+
error_msg += f". Response content: {error_detail}"
|
|
2364
|
+
|
|
2365
|
+
logger.error(error_msg)
|
|
2366
|
+
raise APIError(error_msg)
|
|
2367
|
+
|
|
2178
2368
|
@optional_typecheck
|
|
2179
2369
|
def get_a_single_asset(self, asset_id: str) -> bytes:
|
|
2180
2370
|
"""
|
{karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api/openapi_reference.json
RENAMED
|
@@ -45,6 +45,10 @@
|
|
|
45
45
|
"type": "string",
|
|
46
46
|
"example": "ieidlxygmwj87oxz5hxttoc8"
|
|
47
47
|
},
|
|
48
|
+
"BackupId": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"example": "ieidlxygmwj87oxz5hxttoc8"
|
|
51
|
+
},
|
|
48
52
|
"Bookmark": {
|
|
49
53
|
"type": "object",
|
|
50
54
|
"properties": {
|
|
@@ -108,6 +112,9 @@
|
|
|
108
112
|
"import"
|
|
109
113
|
]
|
|
110
114
|
},
|
|
115
|
+
"userId": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
111
118
|
"tags": {
|
|
112
119
|
"type": "array",
|
|
113
120
|
"items": {
|
|
@@ -317,8 +324,13 @@
|
|
|
317
324
|
"video",
|
|
318
325
|
"bookmarkAsset",
|
|
319
326
|
"precrawledArchive",
|
|
327
|
+
"userUploaded",
|
|
320
328
|
"unknown"
|
|
321
329
|
]
|
|
330
|
+
},
|
|
331
|
+
"fileName": {
|
|
332
|
+
"type": "string",
|
|
333
|
+
"nullable": true
|
|
322
334
|
}
|
|
323
335
|
},
|
|
324
336
|
"required": [
|
|
@@ -336,6 +348,7 @@
|
|
|
336
348
|
"favourited",
|
|
337
349
|
"taggingStatus",
|
|
338
350
|
"summarizationStatus",
|
|
351
|
+
"userId",
|
|
339
352
|
"tags",
|
|
340
353
|
"content",
|
|
341
354
|
"assets"
|
|
@@ -397,6 +410,18 @@
|
|
|
397
410
|
},
|
|
398
411
|
"public": {
|
|
399
412
|
"type": "boolean"
|
|
413
|
+
},
|
|
414
|
+
"hasCollaborators": {
|
|
415
|
+
"type": "boolean"
|
|
416
|
+
},
|
|
417
|
+
"userRole": {
|
|
418
|
+
"type": "string",
|
|
419
|
+
"enum": [
|
|
420
|
+
"owner",
|
|
421
|
+
"editor",
|
|
422
|
+
"viewer",
|
|
423
|
+
"public"
|
|
424
|
+
]
|
|
400
425
|
}
|
|
401
426
|
},
|
|
402
427
|
"required": [
|
|
@@ -404,7 +429,9 @@
|
|
|
404
429
|
"name",
|
|
405
430
|
"icon",
|
|
406
431
|
"parentId",
|
|
407
|
-
"public"
|
|
432
|
+
"public",
|
|
433
|
+
"hasCollaborators",
|
|
434
|
+
"userRole"
|
|
408
435
|
]
|
|
409
436
|
},
|
|
410
437
|
"Highlight": {
|
|
@@ -573,6 +600,14 @@
|
|
|
573
600
|
"required": true,
|
|
574
601
|
"name": "assetId",
|
|
575
602
|
"in": "path"
|
|
603
|
+
},
|
|
604
|
+
"BackupId": {
|
|
605
|
+
"schema": {
|
|
606
|
+
"$ref": "#/components/schemas/BackupId"
|
|
607
|
+
},
|
|
608
|
+
"required": true,
|
|
609
|
+
"name": "backupId",
|
|
610
|
+
"in": "path"
|
|
576
611
|
}
|
|
577
612
|
}
|
|
578
613
|
},
|
|
@@ -1189,6 +1224,9 @@
|
|
|
1189
1224
|
"rss",
|
|
1190
1225
|
"import"
|
|
1191
1226
|
]
|
|
1227
|
+
},
|
|
1228
|
+
"userId": {
|
|
1229
|
+
"type": "string"
|
|
1192
1230
|
}
|
|
1193
1231
|
},
|
|
1194
1232
|
"required": [
|
|
@@ -1198,7 +1236,8 @@
|
|
|
1198
1236
|
"archived",
|
|
1199
1237
|
"favourited",
|
|
1200
1238
|
"taggingStatus",
|
|
1201
|
-
"summarizationStatus"
|
|
1239
|
+
"summarizationStatus",
|
|
1240
|
+
"userId"
|
|
1202
1241
|
]
|
|
1203
1242
|
}
|
|
1204
1243
|
}
|
|
@@ -1313,6 +1352,9 @@
|
|
|
1313
1352
|
"rss",
|
|
1314
1353
|
"import"
|
|
1315
1354
|
]
|
|
1355
|
+
},
|
|
1356
|
+
"userId": {
|
|
1357
|
+
"type": "string"
|
|
1316
1358
|
}
|
|
1317
1359
|
},
|
|
1318
1360
|
"required": [
|
|
@@ -1322,7 +1364,8 @@
|
|
|
1322
1364
|
"archived",
|
|
1323
1365
|
"favourited",
|
|
1324
1366
|
"taggingStatus",
|
|
1325
|
-
"summarizationStatus"
|
|
1367
|
+
"summarizationStatus",
|
|
1368
|
+
"userId"
|
|
1326
1369
|
]
|
|
1327
1370
|
}
|
|
1328
1371
|
}
|
|
@@ -1703,6 +1746,7 @@
|
|
|
1703
1746
|
"video",
|
|
1704
1747
|
"bookmarkAsset",
|
|
1705
1748
|
"precrawledArchive",
|
|
1749
|
+
"userUploaded",
|
|
1706
1750
|
"unknown"
|
|
1707
1751
|
]
|
|
1708
1752
|
}
|
|
@@ -1737,8 +1781,13 @@
|
|
|
1737
1781
|
"video",
|
|
1738
1782
|
"bookmarkAsset",
|
|
1739
1783
|
"precrawledArchive",
|
|
1784
|
+
"userUploaded",
|
|
1740
1785
|
"unknown"
|
|
1741
1786
|
]
|
|
1787
|
+
},
|
|
1788
|
+
"fileName": {
|
|
1789
|
+
"type": "string",
|
|
1790
|
+
"nullable": true
|
|
1742
1791
|
}
|
|
1743
1792
|
},
|
|
1744
1793
|
"required": [
|
|
@@ -3122,6 +3171,10 @@
|
|
|
3122
3171
|
"green",
|
|
3123
3172
|
"blue"
|
|
3124
3173
|
]
|
|
3174
|
+
},
|
|
3175
|
+
"note": {
|
|
3176
|
+
"type": "string",
|
|
3177
|
+
"nullable": true
|
|
3125
3178
|
}
|
|
3126
3179
|
}
|
|
3127
3180
|
}
|
|
@@ -3386,6 +3439,35 @@
|
|
|
3386
3439
|
]
|
|
3387
3440
|
},
|
|
3388
3441
|
"maxItems": 10
|
|
3442
|
+
},
|
|
3443
|
+
"bookmarksBySource": {
|
|
3444
|
+
"type": "array",
|
|
3445
|
+
"items": {
|
|
3446
|
+
"type": "object",
|
|
3447
|
+
"properties": {
|
|
3448
|
+
"source": {
|
|
3449
|
+
"type": "string",
|
|
3450
|
+
"nullable": true,
|
|
3451
|
+
"enum": [
|
|
3452
|
+
"api",
|
|
3453
|
+
"web",
|
|
3454
|
+
"cli",
|
|
3455
|
+
"mobile",
|
|
3456
|
+
"extension",
|
|
3457
|
+
"singlefile",
|
|
3458
|
+
"rss",
|
|
3459
|
+
"import"
|
|
3460
|
+
]
|
|
3461
|
+
},
|
|
3462
|
+
"count": {
|
|
3463
|
+
"type": "number"
|
|
3464
|
+
}
|
|
3465
|
+
},
|
|
3466
|
+
"required": [
|
|
3467
|
+
"source",
|
|
3468
|
+
"count"
|
|
3469
|
+
]
|
|
3470
|
+
}
|
|
3389
3471
|
}
|
|
3390
3472
|
},
|
|
3391
3473
|
"required": [
|
|
@@ -3400,7 +3482,8 @@
|
|
|
3400
3482
|
"totalAssetSize",
|
|
3401
3483
|
"assetsByType",
|
|
3402
3484
|
"bookmarkingActivity",
|
|
3403
|
-
"tagUsage"
|
|
3485
|
+
"tagUsage",
|
|
3486
|
+
"bookmarksBySource"
|
|
3404
3487
|
]
|
|
3405
3488
|
}
|
|
3406
3489
|
}
|
|
@@ -3632,6 +3715,341 @@
|
|
|
3632
3715
|
}
|
|
3633
3716
|
}
|
|
3634
3717
|
}
|
|
3718
|
+
},
|
|
3719
|
+
"/backups": {
|
|
3720
|
+
"get": {
|
|
3721
|
+
"description": "Get all backups",
|
|
3722
|
+
"summary": "Get all backups",
|
|
3723
|
+
"tags": [
|
|
3724
|
+
"Backups"
|
|
3725
|
+
],
|
|
3726
|
+
"security": [
|
|
3727
|
+
{
|
|
3728
|
+
"bearerAuth": []
|
|
3729
|
+
}
|
|
3730
|
+
],
|
|
3731
|
+
"responses": {
|
|
3732
|
+
"200": {
|
|
3733
|
+
"description": "Object with all backups data.",
|
|
3734
|
+
"content": {
|
|
3735
|
+
"application/json": {
|
|
3736
|
+
"schema": {
|
|
3737
|
+
"type": "object",
|
|
3738
|
+
"properties": {
|
|
3739
|
+
"backups": {
|
|
3740
|
+
"type": "array",
|
|
3741
|
+
"items": {
|
|
3742
|
+
"type": "object",
|
|
3743
|
+
"properties": {
|
|
3744
|
+
"id": {
|
|
3745
|
+
"type": "string"
|
|
3746
|
+
},
|
|
3747
|
+
"userId": {
|
|
3748
|
+
"type": "string"
|
|
3749
|
+
},
|
|
3750
|
+
"assetId": {
|
|
3751
|
+
"type": "string",
|
|
3752
|
+
"nullable": true
|
|
3753
|
+
},
|
|
3754
|
+
"createdAt": {
|
|
3755
|
+
"type": "string"
|
|
3756
|
+
},
|
|
3757
|
+
"size": {
|
|
3758
|
+
"type": "number"
|
|
3759
|
+
},
|
|
3760
|
+
"bookmarkCount": {
|
|
3761
|
+
"type": "number"
|
|
3762
|
+
},
|
|
3763
|
+
"status": {
|
|
3764
|
+
"type": "string",
|
|
3765
|
+
"enum": [
|
|
3766
|
+
"pending",
|
|
3767
|
+
"success",
|
|
3768
|
+
"failure"
|
|
3769
|
+
]
|
|
3770
|
+
},
|
|
3771
|
+
"errorMessage": {
|
|
3772
|
+
"type": "string",
|
|
3773
|
+
"nullable": true
|
|
3774
|
+
}
|
|
3775
|
+
},
|
|
3776
|
+
"required": [
|
|
3777
|
+
"id",
|
|
3778
|
+
"userId",
|
|
3779
|
+
"assetId",
|
|
3780
|
+
"createdAt",
|
|
3781
|
+
"size",
|
|
3782
|
+
"bookmarkCount",
|
|
3783
|
+
"status"
|
|
3784
|
+
]
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
},
|
|
3788
|
+
"required": [
|
|
3789
|
+
"backups"
|
|
3790
|
+
]
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
},
|
|
3797
|
+
"post": {
|
|
3798
|
+
"description": "Trigger a new backup",
|
|
3799
|
+
"summary": "Trigger a new backup",
|
|
3800
|
+
"tags": [
|
|
3801
|
+
"Backups"
|
|
3802
|
+
],
|
|
3803
|
+
"security": [
|
|
3804
|
+
{
|
|
3805
|
+
"bearerAuth": []
|
|
3806
|
+
}
|
|
3807
|
+
],
|
|
3808
|
+
"responses": {
|
|
3809
|
+
"201": {
|
|
3810
|
+
"description": "Backup created successfully",
|
|
3811
|
+
"content": {
|
|
3812
|
+
"application/json": {
|
|
3813
|
+
"schema": {
|
|
3814
|
+
"type": "object",
|
|
3815
|
+
"properties": {
|
|
3816
|
+
"id": {
|
|
3817
|
+
"type": "string"
|
|
3818
|
+
},
|
|
3819
|
+
"userId": {
|
|
3820
|
+
"type": "string"
|
|
3821
|
+
},
|
|
3822
|
+
"assetId": {
|
|
3823
|
+
"type": "string",
|
|
3824
|
+
"nullable": true
|
|
3825
|
+
},
|
|
3826
|
+
"createdAt": {
|
|
3827
|
+
"type": "string"
|
|
3828
|
+
},
|
|
3829
|
+
"size": {
|
|
3830
|
+
"type": "number"
|
|
3831
|
+
},
|
|
3832
|
+
"bookmarkCount": {
|
|
3833
|
+
"type": "number"
|
|
3834
|
+
},
|
|
3835
|
+
"status": {
|
|
3836
|
+
"type": "string",
|
|
3837
|
+
"enum": [
|
|
3838
|
+
"pending",
|
|
3839
|
+
"success",
|
|
3840
|
+
"failure"
|
|
3841
|
+
]
|
|
3842
|
+
},
|
|
3843
|
+
"errorMessage": {
|
|
3844
|
+
"type": "string",
|
|
3845
|
+
"nullable": true
|
|
3846
|
+
}
|
|
3847
|
+
},
|
|
3848
|
+
"required": [
|
|
3849
|
+
"id",
|
|
3850
|
+
"userId",
|
|
3851
|
+
"assetId",
|
|
3852
|
+
"createdAt",
|
|
3853
|
+
"size",
|
|
3854
|
+
"bookmarkCount",
|
|
3855
|
+
"status"
|
|
3856
|
+
]
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
},
|
|
3864
|
+
"/backups/{backupId}": {
|
|
3865
|
+
"get": {
|
|
3866
|
+
"description": "Get backup by its id",
|
|
3867
|
+
"summary": "Get a single backup",
|
|
3868
|
+
"tags": [
|
|
3869
|
+
"Backups"
|
|
3870
|
+
],
|
|
3871
|
+
"security": [
|
|
3872
|
+
{
|
|
3873
|
+
"bearerAuth": []
|
|
3874
|
+
}
|
|
3875
|
+
],
|
|
3876
|
+
"parameters": [
|
|
3877
|
+
{
|
|
3878
|
+
"$ref": "#/components/parameters/BackupId"
|
|
3879
|
+
}
|
|
3880
|
+
],
|
|
3881
|
+
"responses": {
|
|
3882
|
+
"200": {
|
|
3883
|
+
"description": "Object with backup data.",
|
|
3884
|
+
"content": {
|
|
3885
|
+
"application/json": {
|
|
3886
|
+
"schema": {
|
|
3887
|
+
"type": "object",
|
|
3888
|
+
"properties": {
|
|
3889
|
+
"id": {
|
|
3890
|
+
"type": "string"
|
|
3891
|
+
},
|
|
3892
|
+
"userId": {
|
|
3893
|
+
"type": "string"
|
|
3894
|
+
},
|
|
3895
|
+
"assetId": {
|
|
3896
|
+
"type": "string",
|
|
3897
|
+
"nullable": true
|
|
3898
|
+
},
|
|
3899
|
+
"createdAt": {
|
|
3900
|
+
"type": "string"
|
|
3901
|
+
},
|
|
3902
|
+
"size": {
|
|
3903
|
+
"type": "number"
|
|
3904
|
+
},
|
|
3905
|
+
"bookmarkCount": {
|
|
3906
|
+
"type": "number"
|
|
3907
|
+
},
|
|
3908
|
+
"status": {
|
|
3909
|
+
"type": "string",
|
|
3910
|
+
"enum": [
|
|
3911
|
+
"pending",
|
|
3912
|
+
"success",
|
|
3913
|
+
"failure"
|
|
3914
|
+
]
|
|
3915
|
+
},
|
|
3916
|
+
"errorMessage": {
|
|
3917
|
+
"type": "string",
|
|
3918
|
+
"nullable": true
|
|
3919
|
+
}
|
|
3920
|
+
},
|
|
3921
|
+
"required": [
|
|
3922
|
+
"id",
|
|
3923
|
+
"userId",
|
|
3924
|
+
"assetId",
|
|
3925
|
+
"createdAt",
|
|
3926
|
+
"size",
|
|
3927
|
+
"bookmarkCount",
|
|
3928
|
+
"status"
|
|
3929
|
+
]
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
},
|
|
3934
|
+
"404": {
|
|
3935
|
+
"description": "Backup not found",
|
|
3936
|
+
"content": {
|
|
3937
|
+
"application/json": {
|
|
3938
|
+
"schema": {
|
|
3939
|
+
"type": "object",
|
|
3940
|
+
"properties": {
|
|
3941
|
+
"code": {
|
|
3942
|
+
"type": "string"
|
|
3943
|
+
},
|
|
3944
|
+
"message": {
|
|
3945
|
+
"type": "string"
|
|
3946
|
+
}
|
|
3947
|
+
},
|
|
3948
|
+
"required": [
|
|
3949
|
+
"code",
|
|
3950
|
+
"message"
|
|
3951
|
+
]
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
},
|
|
3958
|
+
"delete": {
|
|
3959
|
+
"description": "Delete backup by its id",
|
|
3960
|
+
"summary": "Delete a backup",
|
|
3961
|
+
"tags": [
|
|
3962
|
+
"Backups"
|
|
3963
|
+
],
|
|
3964
|
+
"security": [
|
|
3965
|
+
{
|
|
3966
|
+
"bearerAuth": []
|
|
3967
|
+
}
|
|
3968
|
+
],
|
|
3969
|
+
"parameters": [
|
|
3970
|
+
{
|
|
3971
|
+
"$ref": "#/components/parameters/BackupId"
|
|
3972
|
+
}
|
|
3973
|
+
],
|
|
3974
|
+
"responses": {
|
|
3975
|
+
"204": {
|
|
3976
|
+
"description": "No content - the backup was deleted"
|
|
3977
|
+
},
|
|
3978
|
+
"404": {
|
|
3979
|
+
"description": "Backup not found",
|
|
3980
|
+
"content": {
|
|
3981
|
+
"application/json": {
|
|
3982
|
+
"schema": {
|
|
3983
|
+
"type": "object",
|
|
3984
|
+
"properties": {
|
|
3985
|
+
"code": {
|
|
3986
|
+
"type": "string"
|
|
3987
|
+
},
|
|
3988
|
+
"message": {
|
|
3989
|
+
"type": "string"
|
|
3990
|
+
}
|
|
3991
|
+
},
|
|
3992
|
+
"required": [
|
|
3993
|
+
"code",
|
|
3994
|
+
"message"
|
|
3995
|
+
]
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
},
|
|
4003
|
+
"/backups/{backupId}/download": {
|
|
4004
|
+
"get": {
|
|
4005
|
+
"description": "Download backup file",
|
|
4006
|
+
"summary": "Download a backup",
|
|
4007
|
+
"tags": [
|
|
4008
|
+
"Backups"
|
|
4009
|
+
],
|
|
4010
|
+
"security": [
|
|
4011
|
+
{
|
|
4012
|
+
"bearerAuth": []
|
|
4013
|
+
}
|
|
4014
|
+
],
|
|
4015
|
+
"parameters": [
|
|
4016
|
+
{
|
|
4017
|
+
"$ref": "#/components/parameters/BackupId"
|
|
4018
|
+
}
|
|
4019
|
+
],
|
|
4020
|
+
"responses": {
|
|
4021
|
+
"200": {
|
|
4022
|
+
"description": "Backup file (zip archive)",
|
|
4023
|
+
"content": {
|
|
4024
|
+
"application/zip": {
|
|
4025
|
+
"schema": {}
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
},
|
|
4029
|
+
"404": {
|
|
4030
|
+
"description": "Backup not found",
|
|
4031
|
+
"content": {
|
|
4032
|
+
"application/json": {
|
|
4033
|
+
"schema": {
|
|
4034
|
+
"type": "object",
|
|
4035
|
+
"properties": {
|
|
4036
|
+
"code": {
|
|
4037
|
+
"type": "string"
|
|
4038
|
+
},
|
|
4039
|
+
"message": {
|
|
4040
|
+
"type": "string"
|
|
4041
|
+
}
|
|
4042
|
+
},
|
|
4043
|
+
"required": [
|
|
4044
|
+
"code",
|
|
4045
|
+
"message"
|
|
4046
|
+
]
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
3635
4053
|
}
|
|
3636
4054
|
}
|
|
3637
4055
|
}
|
{karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0/karakeep_python_api.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: karakeep_python_api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
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
|
|
@@ -70,6 +70,7 @@ The development process involved:
|
|
|
70
70
|
|
|
71
71
|
* **Experimental Methods:** The included Pytest suite currently only covers a subset of the available API methods (primarily 'get all' endpoints and client initialization). Methods *not* explicitly tested should be considered **experimental**.
|
|
72
72
|
* **Ongoing Development:** The author intends to improve and validate methods as they are needed for personal use cases. Contributions and bug reports are welcome!
|
|
73
|
+
* **Updating process**: I have local scripts to keep track of the changes in the OpenAPI specs on the server side. From time to time I go and use aider to make the code reflect the latest openapi and push that to the `dev` branch. When there is a new karakeep release, I will merge the `dev` branch to the `main` branch and create my own release. This way, `dev` hopefully is up to date with the latest code of karakeep, while `main` is up to date with the latest release of karakeep.
|
|
73
74
|
|
|
74
75
|
## API Method Coverage
|
|
75
76
|
|
|
@@ -116,7 +117,12 @@ Methods or CLI commands marked with ❌ should be used with caution as their beh
|
|
|
116
117
|
| `get_a_single_asset` | ✅ | ❌ | Tested in PDF asset lifecycle test. |
|
|
117
118
|
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
118
119
|
| `get_current_user_stats` | ✅ | ✅ | |
|
|
119
|
-
| `update_user`
|
|
120
|
+
| `update_user` | ❌ | ❌ | |
|
|
121
|
+
| `get_all_backups` | ✅ | ✅ | Tested in backup lifecycle test. |
|
|
122
|
+
| `trigger_a_new_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
123
|
+
| `get_a_single_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
124
|
+
| `delete_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
125
|
+
| `download_a_backup` | ✅ | ❌ | Tested in backup lifecycle test. |
|
|
120
126
|
|
|
121
127
|
## Installation
|
|
122
128
|
|
|
@@ -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.5.0",
|
|
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",
|
|
@@ -892,4 +892,134 @@ def test_asset_lifecycle_with_pdf(karakeep_client: KarakeepAPI):
|
|
|
892
892
|
logger.info("\nNo bookmark to clean up")
|
|
893
893
|
|
|
894
894
|
|
|
895
|
+
def test_backup_lifecycle(karakeep_client: KarakeepAPI):
|
|
896
|
+
"""Test creating, retrieving, downloading, and deleting a backup."""
|
|
897
|
+
created_backup_id = None
|
|
898
|
+
|
|
899
|
+
try:
|
|
900
|
+
# 1. Get initial backup count
|
|
901
|
+
logger.info("\nGetting initial backup list")
|
|
902
|
+
initial_backups = karakeep_client.get_all_backups()
|
|
903
|
+
assert isinstance(initial_backups, list), "Response should be a list"
|
|
904
|
+
initial_backup_count = len(initial_backups)
|
|
905
|
+
logger.info(f" Initial backup count: {initial_backup_count}")
|
|
906
|
+
|
|
907
|
+
# 2. Trigger a new backup
|
|
908
|
+
logger.info("\nTriggering a new backup")
|
|
909
|
+
created_backup = karakeep_client.trigger_a_new_backup()
|
|
910
|
+
assert isinstance(created_backup, datatypes.Backup), (
|
|
911
|
+
"Response should be a Backup model"
|
|
912
|
+
)
|
|
913
|
+
assert created_backup.id, "Created backup must have an ID"
|
|
914
|
+
assert created_backup.status in ["pending", "success", "failure"], (
|
|
915
|
+
"Backup status should be one of the valid enum values"
|
|
916
|
+
)
|
|
917
|
+
created_backup_id = created_backup.id
|
|
918
|
+
logger.info(f"✓ Successfully triggered backup with ID: {created_backup_id}")
|
|
919
|
+
logger.info(f" Backup status: {created_backup.status}")
|
|
920
|
+
|
|
921
|
+
# 3. Verify the backup appears in get_all_backups
|
|
922
|
+
logger.info(f"\nVerifying backup {created_backup_id} appears in backup list")
|
|
923
|
+
current_backups = karakeep_client.get_all_backups()
|
|
924
|
+
assert len(current_backups) >= initial_backup_count + 1, (
|
|
925
|
+
"Backup count should increase after creation"
|
|
926
|
+
)
|
|
927
|
+
assert any(backup.id == created_backup_id for backup in current_backups), (
|
|
928
|
+
"Created backup should be present in the list of all backups"
|
|
929
|
+
)
|
|
930
|
+
logger.info(
|
|
931
|
+
f"✓ Verified backup {created_backup_id} is present in get_all_backups"
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
# 4. Get the backup by ID to verify it exists
|
|
935
|
+
logger.info(f"\nRetrieving backup {created_backup_id} by ID")
|
|
936
|
+
retrieved_backup = karakeep_client.get_a_single_backup(
|
|
937
|
+
backup_id=created_backup_id
|
|
938
|
+
)
|
|
939
|
+
assert isinstance(retrieved_backup, datatypes.Backup)
|
|
940
|
+
assert retrieved_backup.id == created_backup_id
|
|
941
|
+
logger.info(f"✓ Successfully retrieved backup by ID")
|
|
942
|
+
logger.info(f" Status: {retrieved_backup.status}")
|
|
943
|
+
logger.info(f" Bookmark count: {retrieved_backup.bookmarkCount}")
|
|
944
|
+
logger.info(f" Size: {retrieved_backup.size} bytes")
|
|
945
|
+
|
|
946
|
+
# 5. Try to download the backup (only if status is "success")
|
|
947
|
+
if retrieved_backup.status == "success" and retrieved_backup.assetId:
|
|
948
|
+
logger.info(f"\nAttempting to download backup {created_backup_id}")
|
|
949
|
+
try:
|
|
950
|
+
backup_data = karakeep_client.download_a_backup(
|
|
951
|
+
backup_id=created_backup_id
|
|
952
|
+
)
|
|
953
|
+
assert isinstance(backup_data, bytes), (
|
|
954
|
+
"Downloaded backup should be bytes"
|
|
955
|
+
)
|
|
956
|
+
assert len(backup_data) > 0, "Downloaded backup should not be empty"
|
|
957
|
+
# Verify it's a zip file by checking the magic number
|
|
958
|
+
assert backup_data.startswith(b"PK\x03\x04") or backup_data.startswith(
|
|
959
|
+
b"PK\x05\x06"
|
|
960
|
+
), "Downloaded file should be a valid ZIP archive"
|
|
961
|
+
logger.info(
|
|
962
|
+
f"✓ Successfully downloaded backup ({len(backup_data)} bytes)"
|
|
963
|
+
)
|
|
964
|
+
except APIError as e:
|
|
965
|
+
# Some backups might not be downloadable immediately, log but don't fail
|
|
966
|
+
logger.info(f" Note: Could not download backup: {e}")
|
|
967
|
+
else:
|
|
968
|
+
logger.info(
|
|
969
|
+
f" Skipping download test (status: {retrieved_backup.status}, assetId: {retrieved_backup.assetId})"
|
|
970
|
+
)
|
|
971
|
+
|
|
972
|
+
# 6. Test CLI equivalent for getting all backups
|
|
973
|
+
logger.info("\n Running CLI equivalent: get-all-backups")
|
|
974
|
+
try:
|
|
975
|
+
subprocess.run(
|
|
976
|
+
"python -m karakeep_python_api get-all-backups",
|
|
977
|
+
shell=True,
|
|
978
|
+
check=True,
|
|
979
|
+
capture_output=True,
|
|
980
|
+
text=True,
|
|
981
|
+
)
|
|
982
|
+
logger.info("✓ CLI command executed successfully.")
|
|
983
|
+
except subprocess.CalledProcessError as e:
|
|
984
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
985
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
986
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
987
|
+
pytest.fail(f"CLI command 'get-all-backups' failed: {e}")
|
|
988
|
+
|
|
989
|
+
except (APIError, AuthenticationError) as e:
|
|
990
|
+
pytest.fail(f"API error during backup lifecycle test: {e}")
|
|
991
|
+
except Exception as e:
|
|
992
|
+
pytest.fail(f"An unexpected error occurred during backup lifecycle test: {e}")
|
|
993
|
+
finally:
|
|
994
|
+
# 7. Clean up: Delete the backup
|
|
995
|
+
if created_backup_id:
|
|
996
|
+
logger.info(f"\nCleaning up: Deleting backup {created_backup_id}")
|
|
997
|
+
try:
|
|
998
|
+
karakeep_client.delete_a_backup(backup_id=created_backup_id)
|
|
999
|
+
logger.info(f"✓ Successfully deleted backup {created_backup_id}")
|
|
1000
|
+
|
|
1001
|
+
# 8. Verify the backup is deleted
|
|
1002
|
+
try:
|
|
1003
|
+
karakeep_client.get_a_single_backup(backup_id=created_backup_id)
|
|
1004
|
+
pytest.fail(
|
|
1005
|
+
f"Backup {created_backup_id} should not exist after deletion, but get_a_single_backup succeeded."
|
|
1006
|
+
)
|
|
1007
|
+
except APIError as e:
|
|
1008
|
+
assert e.status_code == 404, (
|
|
1009
|
+
f"Expected 404 Not Found when getting deleted backup, but got status {e.status_code}"
|
|
1010
|
+
)
|
|
1011
|
+
logger.info(
|
|
1012
|
+
f"✓ Confirmed backup {created_backup_id} is deleted (received 404)"
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
except (APIError, AuthenticationError) as e:
|
|
1016
|
+
logger.info(
|
|
1017
|
+
f" Error during cleanup - failed to delete backup {created_backup_id}: {e}"
|
|
1018
|
+
)
|
|
1019
|
+
except Exception as e:
|
|
1020
|
+
logger.info(f" Unexpected error during cleanup: {e}")
|
|
1021
|
+
else:
|
|
1022
|
+
logger.info("\nNo backup to clean up")
|
|
1023
|
+
|
|
1024
|
+
|
|
895
1025
|
# --- End of Tests ---
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{karakeep_python_api-1.4.0 → karakeep_python_api-1.5.0}/karakeep_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|