karakeep-python-api 0.2.3__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,9 +59,9 @@ def serialize_output(data: Any) -> Any:
59
59
  # Shared options for the API client
60
60
  shared_options = [
61
61
  click.option(
62
- "--base-url",
63
- envvar="KARAKEEP_PYTHON_API_BASE_URL",
64
- help="Full Karakeep API base URL, including /api/v1/ (e.g., https://instance.com/api/v1/).",
62
+ "--api-endpoint",
63
+ envvar="KARAKEEP_PYTHON_API_ENDPOINT",
64
+ help="Full Karakeep API endpoint URL, including /api/v1/ (e.g., https://instance.com/api/v1/).",
65
65
  ),
66
66
  click.option(
67
67
  "--api-key",
@@ -151,7 +151,7 @@ def print_openapi_spec(ctx, param, value):
151
151
  @click.pass_context
152
152
  def cli(
153
153
  ctx,
154
- base_url,
154
+ api_endpoint,
155
155
  api_key,
156
156
  verify_ssl,
157
157
  verbose,
@@ -167,7 +167,7 @@ def cli(
167
167
  # Ensure the context object exists
168
168
  ctx.ensure_object(dict)
169
169
 
170
- # --- Strict Check for API Key and Base URL ---
170
+ # --- Strict Check for API Key and Endpoint ---
171
171
  # Check for API key (must be provided via arg or env)
172
172
  resolved_api_key = api_key or os.environ.get("KARAKEEP_PYTHON_API_KEY")
173
173
  if not resolved_api_key:
@@ -175,16 +175,18 @@ def cli(
175
175
  "API Key is required. Provide --api-key option or set KARAKEEP_PYTHON_API_KEY environment variable."
176
176
  )
177
177
 
178
- # Check for Base URL (must be provided via arg or env)
179
- resolved_base_url = base_url or os.environ.get("KARAKEEP_PYTHON_API_BASE_URL")
180
- if not resolved_base_url:
178
+ # Check for API endpoint (must be provided via arg or env)
179
+ resolved_api_endpoint = api_endpoint or os.environ.get(
180
+ "KARAKEEP_PYTHON_API_ENDPOINT"
181
+ )
182
+ if not resolved_api_endpoint:
181
183
  raise click.UsageError(
182
- "API Base URL is required. Provide --base-url option or set KARAKEEP_PYTHON_API_BASE_URL environment variable. "
184
+ "API endpoint is required. Provide --api-endpoint option or set KARAKEEP_PYTHON_API_ENDPOINT environment variable. "
183
185
  "The URL must include the API path, e.g., 'https://your-instance.com/api/v1/'."
184
186
  )
185
187
 
186
188
  # Store common API parameters in the context for commands to use
187
- ctx.obj["BASE_URL"] = resolved_base_url # Store the resolved URL
189
+ ctx.obj["API_ENDPOINT"] = resolved_api_endpoint # Store the resolved endpoint
188
190
  ctx.obj["API_KEY"] = resolved_api_key # Store the resolved key
189
191
  ctx.obj["VERIFY_SSL"] = verify_ssl
190
192
  ctx.obj["VERBOSE"] = verbose
@@ -231,7 +233,7 @@ def create_click_command(
231
233
  def command_func(ctx, **kwargs):
232
234
  """Dynamically generated command function wrapper."""
233
235
  # Retrieve API parameters from context, ensuring API key is present now
234
- base_url = ctx.obj["BASE_URL"]
236
+ api_endpoint = ctx.obj["API_ENDPOINT"]
235
237
  api_key = ctx.obj["API_KEY"]
236
238
  verify_ssl = ctx.obj["VERIFY_SSL"]
237
239
  verbose = ctx.obj["VERBOSE"]
@@ -250,7 +252,7 @@ def create_click_command(
250
252
  # Method generation already happened during inspection phase or initial load
251
253
  api = KarakeepAPI(
252
254
  api_key=api_key,
253
- base_url=base_url,
255
+ api_endpoint=api_endpoint,
254
256
  verify_ssl=verify_ssl,
255
257
  verbose=verbose,
256
258
  disable_response_validation=disable_validation, # Pass flag to constructor
@@ -135,6 +135,7 @@ class ListModel(BaseModel):
135
135
  parentId: Optional[str]
136
136
  type: Optional[Literal["manual", "smart"]] = "manual"
137
137
  query: Optional[str] = None
138
+ public: bool
138
139
 
139
140
 
140
141
  class PaginatedHighlights(BaseModel):
@@ -77,7 +77,7 @@ class KarakeepAPI:
77
77
 
78
78
  Attributes:
79
79
  api_key (str): The API key used for authentication.
80
- api_base_url (str): The base URL of the Karakeep API instance.
80
+ api_endpoint (str): The endpoint of the Karakeep API instance, including /api/v1 (e.g., https://instance.com/api/v1/).
81
81
  openapi_spec (dict): The parsed content of the OpenAPI specification file.
82
82
  verify_ssl (bool): Whether SSL verification is enabled.
83
83
  verbose (bool): Whether verbose logging is enabled.
@@ -85,17 +85,19 @@ class KarakeepAPI:
85
85
  """
86
86
 
87
87
  # Version reflects the client library version, updated by bumpver
88
- VERSION: str = "0.2.3"
88
+ VERSION: str = "1.1.0"
89
89
 
90
90
  def __init__(
91
91
  self,
92
92
  api_key: Optional[str] = None,
93
- base_url: Optional[str] = None,
93
+ api_endpoint: Optional[str] = None,
94
94
  openapi_spec_path: Optional[str] = None, # Allow None, default handled below
95
95
  verify_ssl: bool = True,
96
96
  verbose: bool = False,
97
97
  disable_response_validation: Optional[bool] = None,
98
- rate_limit: float = 0.0, # Minimum interval between API calls in seconds
98
+ rate_limit: Union[
99
+ float, int
100
+ ] = 0.0, # Minimum interval between API calls in seconds
99
101
  ):
100
102
  """
101
103
  Initialize the Karakeep API client.
@@ -103,8 +105,9 @@ class KarakeepAPI:
103
105
  Args:
104
106
  api_key: Karakeep API key (Bearer token).
105
107
  Defaults to KARAKEEP_PYTHON_API_KEY environment variable if not provided.
106
- base_url: Override the base URL for the API. Must be provided either as an argument
107
- or via the KARAKEEP_PYTHON_API_BASE_URL environment variable.
108
+ api_endpoint: Override the base URL for the API. Must be provided either as an argument
109
+ or via the KARAKEEP_PYTHON_API_ENDPOINT environment variable.
110
+ Example: 'https://karakeep.domain.com/api/v1/'
108
111
  openapi_spec_path: Path to the OpenAPI JSON specification file.
109
112
  Defaults to 'openapi_reference.json' alongside the package code if not provided.
110
113
  The loaded spec is available via the `openapi_spec` attribute.
@@ -129,46 +132,46 @@ class KarakeepAPI:
129
132
  logger.debug("API Key loaded successfully.")
130
133
 
131
134
  # --- Base URL Validation ---
132
- env_base_url = os.environ.get("KARAKEEP_PYTHON_API_BASE_URL")
135
+ env_endpoint = os.environ.get("KARAKEEP_PYTHON_API_ENDPOINT")
133
136
  logger.debug(
134
- f"Checked KARAKEEP_PYTHON_API_BASE_URL environment variable, found: '{env_base_url}'"
137
+ f"Checked KARAKEEP_PYTHON_API_ENDPOINT environment variable, found: '{env_endpoint}'"
135
138
  )
136
- logger.debug(f"Base URL provided as argument: '{base_url}'")
139
+ logger.debug(f"Base URL provided as argument: '{api_endpoint}'")
137
140
 
138
- if base_url:
139
- self.api_base_url = base_url
140
- logger.info(f"Using provided base URL: {self.api_base_url}")
141
- elif env_base_url:
142
- self.api_base_url = env_base_url
141
+ if api_endpoint:
142
+ self.api_endpoint = api_endpoint
143
+ logger.info(f"Using provided base URL: {self.api_endpoint}")
144
+ elif env_endpoint:
145
+ self.api_endpoint = env_endpoint
143
146
  logger.info(
144
- f"Using base URL from KARAKEEP_PYTHON_API_BASE_URL: {self.api_base_url}"
147
+ f"Using base URL from KARAKEEP_PYTHON_API_ENDPOINT: {self.api_endpoint}"
145
148
  )
146
149
  else:
147
- # No base_url from arg or env var - raise error as per requirement
150
+ # No api_endpoint from arg or env var - raise error as per requirement
148
151
  raise ValueError(
149
- "API base URL is required. Provide 'base_url' argument or set KARAKEEP_PYTHON_API_BASE_URL environment variable."
152
+ "API base URL is required. Provide 'api_endpoint' argument or set KARAKEEP_PYTHON_API_ENDPOINT environment variable."
150
153
  )
151
154
 
152
- # Ensure base URL ends with /v1/
153
- resolved_url = self.api_base_url # Use a temporary variable for checks
154
- if resolved_url.endswith("/v1"):
155
- # Ends with /v1, needs a slash
156
- self.api_base_url = resolved_url + "/"
155
+ # Ensure base URL ends with /api/v1/
156
+ resolved_url = self.api_endpoint # Use a temporary variable for checks
157
+ if resolved_url.endswith("/api/v1"):
158
+ # Ends with /api/v1, needs a slash
159
+ self.api_endpoint = resolved_url + "/"
157
160
  logger.info(
158
- f"Appended trailing slash to base URL ending in /v1: {self.api_base_url}"
161
+ f"Appended trailing slash to base URL ending in /api/v1: {self.api_endpoint}"
159
162
  )
160
- elif resolved_url.endswith("/v1/"):
163
+ elif resolved_url.endswith("/api/v1/"):
161
164
  # Already ends correctly, do nothing
162
- logger.debug(f"Base URL already ends with /v1/: {self.api_base_url}")
165
+ logger.debug(f"Base URL already ends with /api/v1/: {self.api_endpoint}")
163
166
  else:
164
- # Doesn't end with /v1 or /v1/, append /v1/
165
- # First, remove any existing trailing slash to avoid //v1/
167
+ # Doesn't end with /api/v1 or /api/v1/, append /api/v1/
168
+ # First, remove any existing trailing slash to avoid //api/v1/
166
169
  if resolved_url.endswith("/"):
167
170
  resolved_url = resolved_url[:-1]
168
- self.api_base_url = resolved_url + "/v1/"
169
- logger.info(f"Appended /v1/ to base URL: {self.api_base_url}")
171
+ self.api_endpoint = resolved_url + "/api/v1/"
172
+ logger.info(f"Appended /api/v1/ to base URL: {self.api_endpoint}")
170
173
 
171
- logger.debug(f"Final API Base URL after /v1/ check: {self.api_base_url}")
174
+ logger.debug(f"Final API Base URL after /api/v1/ check: {self.api_endpoint}")
172
175
 
173
176
  # --- Load and Parse OpenAPI Spec ---
174
177
  if openapi_spec_path is None:
@@ -255,7 +258,7 @@ class KarakeepAPI:
255
258
  # self.verbose is still used for conditional logging within the class methods.
256
259
 
257
260
  logger.debug("KarakeepAPI client initialized.")
258
- logger.debug(f" Base URL: {self.api_base_url}")
261
+ logger.debug(f" Base URL: {self.api_endpoint}")
259
262
  logger.debug(f" Verify SSL: {self.verify_ssl}")
260
263
  logger.debug(f" Verbose: {self.verbose}")
261
264
  logger.debug(
@@ -317,9 +320,9 @@ class KarakeepAPI:
317
320
  AuthenticationError: If authentication fails (401).
318
321
  APIError: For other HTTP errors or request issues.
319
322
  """
320
- # Ensure endpoint doesn't start with / if base_url ends with /
323
+ # Ensure endpoint doesn't start with / if endpoint ends with /
321
324
  safe_endpoint = endpoint.lstrip("/")
322
- url = urljoin(self.api_base_url, safe_endpoint)
325
+ url = urljoin(self.api_endpoint, safe_endpoint)
323
326
 
324
327
  # Default headers
325
328
  headers = {
@@ -905,15 +908,21 @@ class KarakeepAPI:
905
908
  return None # Explicitly return None for 204
906
909
 
907
910
  @optional_typecheck
908
- def update_a_bookmark(self, bookmark_id: str, update_data: dict) -> Dict[str, Any]:
911
+ def update_a_bookmark(
912
+ self,
913
+ bookmark_id: str,
914
+ update_data: Dict[str, Any],
915
+ ) -> Dict[str, Any]:
909
916
  """
910
917
  Update a bookmark by its ID. Corresponds to PATCH /bookmarks/{bookmarkId}.
911
- Allows updating fields like 'archived', 'favourited', 'summary', 'note', 'title', etc.
918
+ Allows updating various bookmark fields including metadata, content, and status.
912
919
 
913
920
  Args:
914
921
  bookmark_id: The ID (string) of the bookmark to update.
915
- update_data: A dictionary containing the fields to update (e.g., `{"archived": True}`).
916
- See the OpenAPI spec for allowed fields in the request body.
922
+ update_data: Dictionary containing the fields to update. Supported keys include:
923
+ 'title', 'archived', 'favourited', 'note', 'summary', 'createdAt',
924
+ 'url', 'description', 'author', 'publisher', 'datePublished',
925
+ 'dateModified', 'text', 'assetContent'.
917
926
 
918
927
  Returns:
919
928
  dict: A dictionary representing the updated bookmark (partial representation).
@@ -922,8 +931,13 @@ class KarakeepAPI:
922
931
  Validation is not performed on this response type by default.
923
932
 
924
933
  Raises:
934
+ ValueError: If update_data is empty or no valid fields are provided to update.
925
935
  APIError: If the API request fails (e.g., 404 bookmark not found).
926
936
  """
937
+ # Ensure at least one field is being updated
938
+ if not update_data:
939
+ raise ValueError("update_data must contain at least one field to update.")
940
+
927
941
  endpoint = f"bookmarks/{bookmark_id}"
928
942
  response_data = self._call("PATCH", endpoint, data=update_data)
929
943
  # The response schema is a subset of Bookmark, return as dict as specified in spec
@@ -979,7 +993,9 @@ class KarakeepAPI:
979
993
  """
980
994
  # Validate that at least one tag source is provided
981
995
  if not tag_ids and not tag_names:
982
- raise ValueError("At least one of 'tag_ids' or 'tag_names' must be provided")
996
+ raise ValueError(
997
+ "At least one of 'tag_ids' or 'tag_names' must be provided"
998
+ )
983
999
 
984
1000
  # Validate input types
985
1001
  if tag_ids is not None and not isinstance(tag_ids, list):
@@ -997,7 +1013,9 @@ class KarakeepAPI:
997
1013
  if tag_names:
998
1014
  for i, tag_name in enumerate(tag_names):
999
1015
  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")
1016
+ raise ValueError(
1017
+ f"Tag name at index {i} must be a non-empty string"
1018
+ )
1001
1019
 
1002
1020
  # Construct the tags_data dict in the format expected by the API
1003
1021
  tags_list = []
@@ -1050,7 +1068,9 @@ class KarakeepAPI:
1050
1068
  """
1051
1069
  # Validate that at least one tag source is provided
1052
1070
  if not tag_ids and not tag_names:
1053
- raise ValueError("At least one of 'tag_ids' or 'tag_names' must be provided")
1071
+ raise ValueError(
1072
+ "At least one of 'tag_ids' or 'tag_names' must be provided"
1073
+ )
1054
1074
 
1055
1075
  # Validate input types
1056
1076
  if tag_ids is not None and not isinstance(tag_ids, list):
@@ -1068,7 +1088,9 @@ class KarakeepAPI:
1068
1088
  if tag_names:
1069
1089
  for i, tag_name in enumerate(tag_names):
1070
1090
  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")
1091
+ raise ValueError(
1092
+ f"Tag name at index {i} must be a non-empty string"
1093
+ )
1072
1094
 
1073
1095
  # Construct the tags_data dict in the format expected by the API
1074
1096
  tags_list = []
@@ -1145,16 +1167,28 @@ class KarakeepAPI:
1145
1167
 
1146
1168
  @optional_typecheck
1147
1169
  def attach_asset(
1148
- self, bookmark_id: str, asset_data: dict
1170
+ self,
1171
+ bookmark_id: str,
1172
+ asset_id: str,
1173
+ asset_type: Literal[
1174
+ "screenshot",
1175
+ "assetScreenshot",
1176
+ "bannerImage",
1177
+ "fullPageArchive",
1178
+ "video",
1179
+ "bookmarkAsset",
1180
+ "precrawledArchive",
1181
+ "unknown",
1182
+ ],
1149
1183
  ) -> Union[datatypes.Asset, Dict[str, Any], List[Any]]:
1150
1184
  """
1151
1185
  Attach a new asset to a bookmark. Corresponds to POST /bookmarks/{bookmarkId}/assets.
1152
1186
 
1153
1187
  Args:
1154
1188
  bookmark_id: The ID (string) of the bookmark.
1155
- asset_data: Dictionary specifying the asset to attach. Must contain "id" (string) and "assetType" (string enum).
1156
- Example: `{"id": "asset_id_string", "assetType": "screenshot"}`
1157
- See `datatypes.AssetType` enum for possible asset types.
1189
+ asset_id: The ID (string) of the asset to attach.
1190
+ asset_type: The type of asset being attached. Must be one of: "screenshot", "assetScreenshot",
1191
+ "bannerImage", "fullPageArchive", "video", "bookmarkAsset", "precrawledArchive", "unknown".
1158
1192
 
1159
1193
  Returns:
1160
1194
  datatypes.Asset: The attached asset object.
@@ -1164,6 +1198,9 @@ class KarakeepAPI:
1164
1198
  APIError: If the API request fails (e.g., 404 bookmark not found).
1165
1199
  pydantic.ValidationError: If response validation fails (and is not disabled).
1166
1200
  """
1201
+ # Construct the asset data dict as expected by the API
1202
+ asset_data = {"id": asset_id, "assetType": asset_type}
1203
+
1167
1204
  endpoint = f"bookmarks/{bookmark_id}/assets"
1168
1205
  response_data = self._call("POST", endpoint, data=asset_data)
1169
1206
 
@@ -1175,9 +1212,7 @@ class KarakeepAPI:
1175
1212
  return datatypes.Asset.model_validate(response_data)
1176
1213
 
1177
1214
  @optional_typecheck
1178
- def replace_asset(
1179
- self, bookmark_id: str, asset_id: str, new_asset_data: dict
1180
- ) -> None:
1215
+ def replace_asset(self, bookmark_id: str, asset_id: str, new_asset_id: str) -> None:
1181
1216
  """
1182
1217
  Replace an existing asset associated with a bookmark with a new one.
1183
1218
  Corresponds to PUT /bookmarks/{bookmarkId}/assets/{assetId}.
@@ -1185,8 +1220,7 @@ class KarakeepAPI:
1185
1220
  Args:
1186
1221
  bookmark_id: The ID (string) of the bookmark.
1187
1222
  asset_id: The ID (string) of the asset to be replaced.
1188
- new_asset_data: Dictionary specifying the new asset ID. Must contain "assetId" (string).
1189
- Example: `{"assetId": "new_asset_id_string"}`
1223
+ new_asset_id: The ID (string) of the new asset to replace with.
1190
1224
 
1191
1225
  Returns:
1192
1226
  None: Returns None upon successful replacement (204 No Content).
@@ -1194,6 +1228,9 @@ class KarakeepAPI:
1194
1228
  Raises:
1195
1229
  APIError: If the API request fails (e.g., 404 bookmark or asset not found).
1196
1230
  """
1231
+ # Construct the request body as expected by the API
1232
+ new_asset_data = {"assetId": new_asset_id}
1233
+
1197
1234
  endpoint = f"bookmarks/{bookmark_id}/assets/{asset_id}"
1198
1235
  self._call("PUT", endpoint, data=new_asset_data) # Expects 204 No Content
1199
1236
  return None # Explicitly return None for 204
@@ -1270,6 +1307,7 @@ class KarakeepAPI:
1270
1307
  parent_id: Optional[str] = None,
1271
1308
  list_type: Optional[Literal["manual", "smart"]] = "manual",
1272
1309
  query: Optional[str] = None,
1310
+ public: bool = False,
1273
1311
  ) -> Union[datatypes.ListModel, Dict[str, Any], List[Any]]:
1274
1312
  """
1275
1313
  Create a new list (manual or smart). Corresponds to POST /lists.
@@ -1281,6 +1319,7 @@ class KarakeepAPI:
1281
1319
  parent_id: Optional parent list ID for nested lists.
1282
1320
  list_type: The type of list ('manual' or 'smart'). Default is 'manual'.
1283
1321
  query: Optional query string for smart lists (required if list_type is 'smart').
1322
+ public: Whether the list is public (default: False).
1284
1323
 
1285
1324
  Returns:
1286
1325
  datatypes.ListModel: The created list object.
@@ -1300,6 +1339,7 @@ class KarakeepAPI:
1300
1339
  "name": name,
1301
1340
  "icon": icon,
1302
1341
  "type": list_type,
1342
+ "public": public,
1303
1343
  }
1304
1344
 
1305
1345
  # Add optional fields if provided
@@ -1367,25 +1407,56 @@ class KarakeepAPI:
1367
1407
 
1368
1408
  @optional_typecheck
1369
1409
  def update_a_list(
1370
- self, list_id: str, update_data: dict
1410
+ self,
1411
+ list_id: str,
1412
+ name: Optional[str] = None,
1413
+ description: Optional[str] = None,
1414
+ icon: Optional[str] = None,
1415
+ parent_id: Optional[str] = None,
1416
+ query: Optional[str] = None,
1417
+ public: Optional[bool] = None,
1371
1418
  ) -> Union[datatypes.ListModel, Dict[str, Any], List[Any]]:
1372
1419
  """
1373
1420
  Update a list by its ID. Corresponds to PATCH /lists/{listId}.
1374
- Allows updating fields like "name", "description", "icon", "parentId", "query".
1421
+ Allows updating various list fields including name, description, icon, parent relationship, query, and public status.
1375
1422
 
1376
1423
  Args:
1377
1424
  list_id: The ID (string) of the list to update.
1378
- update_data: A dictionary containing the fields to update (e.g., `{"name": "new name"}`).
1379
- See the OpenAPI spec for allowed fields.
1425
+ name: Optional new name for the list (1-40 characters).
1426
+ description: Optional new description for the list (0-100 characters, can be None to clear).
1427
+ icon: Optional new icon for the list.
1428
+ parent_id: Optional new parent list ID (can be None to remove parent relationship).
1429
+ query: Optional new query string for smart lists (minimum 1 character).
1430
+ public: Optional new public status for the list.
1380
1431
 
1381
1432
  Returns:
1382
1433
  datatypes.ListModel: The updated list object.
1383
1434
  If response validation is disabled, returns the raw API response (dict/list).
1384
1435
 
1385
1436
  Raises:
1437
+ ValueError: If no fields are provided to update.
1386
1438
  APIError: If the API request fails (e.g., 404 list not found).
1387
1439
  pydantic.ValidationError: If response validation fails (and is not disabled).
1388
1440
  """
1441
+ # Construct update_data from provided arguments, excluding None values that weren't explicitly passed
1442
+ update_data = {}
1443
+ if name is not None:
1444
+ update_data["name"] = name
1445
+ if description is not None:
1446
+ update_data["description"] = description
1447
+ if icon is not None:
1448
+ update_data["icon"] = icon
1449
+ if parent_id is not None:
1450
+ update_data["parentId"] = parent_id
1451
+ if query is not None:
1452
+ update_data["query"] = query
1453
+ if public is not None:
1454
+ update_data["public"] = public
1455
+
1456
+ # Ensure at least one field is being updated
1457
+ if not update_data:
1458
+ raise ValueError("At least one field must be provided to update.")
1459
+
1389
1460
  endpoint = f"lists/{list_id}"
1390
1461
  response_data = self._call("PATCH", endpoint, data=update_data)
1391
1462
 
@@ -1594,23 +1665,28 @@ class KarakeepAPI:
1594
1665
  return None # Explicitly return None for 204
1595
1666
 
1596
1667
  @optional_typecheck
1597
- def update_a_tag(self, tag_id: str, update_data: dict) -> Dict[str, Any]:
1668
+ def update_a_tag(self, tag_id: str, update_data: Dict[str, Any]) -> Dict[str, Any]:
1598
1669
  """
1599
1670
  Update a tag by its ID. Currently only supports updating the "name".
1600
1671
  Corresponds to PATCH /tags/{tagId}.
1601
1672
 
1602
1673
  Args:
1603
1674
  tag_id: The ID (string) of the tag to update.
1604
- update_data: A dictionary containing the fields to update. Must include "name" (string).
1605
- Example: `{"name": "new tag name"}`
1675
+ update_data: Dictionary containing the fields to update. Supported keys include:
1676
+ 'name' (string).
1606
1677
 
1607
1678
  Returns:
1608
1679
  dict: A dictionary containing the updated tag information with "id" and "name" fields.
1609
1680
  Validation is not performed on this response type by default.
1610
1681
 
1611
1682
  Raises:
1683
+ ValueError: If update_data is empty or no valid fields are provided to update.
1612
1684
  APIError: If the API request fails (e.g., 404 tag not found).
1613
1685
  """
1686
+ # Ensure at least one field is being updated
1687
+ if not update_data:
1688
+ raise ValueError("update_data must contain at least one field to update.")
1689
+
1614
1690
  endpoint = f"tags/{tag_id}"
1615
1691
  response_data = self._call("PATCH", endpoint, data=update_data)
1616
1692
  # Response schema is a simple dict with id and name, return as dict
@@ -1693,8 +1769,8 @@ class KarakeepAPI:
1693
1769
  def create_a_new_highlight(
1694
1770
  self,
1695
1771
  bookmark_id: str,
1696
- start_offset: float,
1697
- end_offset: float,
1772
+ start_offset: Union[float, int],
1773
+ end_offset: Union[float, int],
1698
1774
  color: Optional[Literal["yellow", "red", "green", "blue"]] = "yellow",
1699
1775
  text: Optional[str] = None,
1700
1776
  note: Optional[str] = None,
@@ -1796,7 +1872,9 @@ class KarakeepAPI:
1796
1872
 
1797
1873
  @optional_typecheck
1798
1874
  def update_a_highlight(
1799
- self, highlight_id: str, update_data: dict
1875
+ self,
1876
+ highlight_id: str,
1877
+ color: Optional[Literal["yellow", "red", "green", "blue"]] = None,
1800
1878
  ) -> Union[datatypes.Highlight, Dict[str, Any], List[Any]]:
1801
1879
  """
1802
1880
  Update a highlight by its ID. Currently only supports updating the "color".
@@ -1804,17 +1882,26 @@ class KarakeepAPI:
1804
1882
 
1805
1883
  Args:
1806
1884
  highlight_id: The ID (string) of the highlight to update.
1807
- update_data: A dictionary containing the fields to update. Must include "color" (string enum).
1808
- See `datatypes.Color` enum. Example: `{"color": "red"}`
1885
+ color: Optional new color for the highlight ("yellow", "red", "green", "blue").
1809
1886
 
1810
1887
  Returns:
1811
1888
  datatypes.Highlight: The updated highlight object.
1812
1889
  If response validation is disabled, returns the raw API response (dict/list).
1813
1890
 
1814
1891
  Raises:
1892
+ ValueError: If no fields are provided to update.
1815
1893
  APIError: If the API request fails (e.g., 404 highlight not found).
1816
1894
  pydantic.ValidationError: If response validation fails (and is not disabled).
1817
1895
  """
1896
+ # Construct update_data from provided arguments, excluding None values
1897
+ update_data = {}
1898
+ if color is not None:
1899
+ update_data["color"] = color
1900
+
1901
+ # Ensure at least one field is being updated
1902
+ if not update_data:
1903
+ raise ValueError("At least one field must be provided to update.")
1904
+
1818
1905
  endpoint = f"highlights/{highlight_id}"
1819
1906
  response_data = self._call("PATCH", endpoint, data=update_data)
1820
1907
 
@@ -426,13 +426,17 @@
426
426
  "query": {
427
427
  "type": "string",
428
428
  "nullable": true
429
+ },
430
+ "public": {
431
+ "type": "boolean"
429
432
  }
430
433
  },
431
434
  "required": [
432
435
  "id",
433
436
  "name",
434
437
  "icon",
435
- "parentId"
438
+ "parentId",
439
+ "public"
436
440
  ]
437
441
  },
438
442
  "Tag": {
@@ -1982,6 +1986,9 @@
1982
1986
  "query": {
1983
1987
  "type": "string",
1984
1988
  "minLength": 1
1989
+ },
1990
+ "public": {
1991
+ "type": "boolean"
1985
1992
  }
1986
1993
  }
1987
1994
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.2.3
3
+ Version: 1.1.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
@@ -52,6 +52,7 @@ A community-developed Python client for the [Karakeep](https://karakeep.app/) AP
52
52
  - [Environment Variables](#environment-variables)
53
53
  - [Command Line Interface (CLI)](#command-line-interface-cli)
54
54
  - [Python Library](#python-library)
55
+ - [Community Scripts](#community-scripts)
55
56
  - [Development](#development)
56
57
  - [License](#license)
57
58
 
@@ -136,7 +137,7 @@ This package can be used as a Python library or as a command-line interface (CLI
136
137
 
137
138
  The client can be configured using the following environment variables:
138
139
 
139
- * `KARAKEEP_PYTHON_API_BASE_URL`: **Required**. The full base URL of your Karakeep API, including the `/api/v1/` path (e.g., `https://your-karakeep.example.com/api/v1/` or `https://try.karakeep.app/api/v1/`).
140
+ * `KARAKEEP_PYTHON_API_ENDPOINT`: **Required**. The full URL of your Karakeep API, including the `/api/v1/` path (e.g., `https://karakeep.domain.com/api/v1/` or `https://try.karakeep.app/api/v1/`).
140
141
  * `KARAKEEP_PYTHON_API_KEY`: **Required**. Your Karakeep API key (Bearer token).
141
142
  * `KARAKEEP_PYTHON_API_VERIFY_SSL`: Set to `false` to disable SSL certificate verification (default: `true`).
142
143
  * `KARAKEEP_PYTHON_API_VERBOSE`: Set to `true` to enable verbose debug logging for the client and CLI (default: `false`).
@@ -145,7 +146,7 @@ The client can be configured using the following environment variables:
145
146
 
146
147
  ### Command Line Interface (CLI)
147
148
 
148
- The CLI dynamically generates commands based on the API methods. You need to provide your API key and base URL either via environment variables (recommended) or command-line options.
149
+ The CLI dynamically generates commands based on the API methods. You need to provide your API key and endpoint either via environment variables (recommended) or command-line options.
149
150
 
150
151
  **Basic Structure:**
151
152
 
@@ -170,8 +171,8 @@ python -m karakeep_python_api get-all-bookmarks --help
170
171
  python -m karakeep_python_api get-all-tags
171
172
 
172
173
  # Get the first page of bookmarks with a limit, overriding env vars if needed
173
- # Note: Ensure the base URL includes the /api/v1/ path
174
- python -m karakeep_python_api --base-url https://my.karakeep.com/api/v1/ --api-key YOUR_API_KEY get-all-bookmarks --limit 10
174
+ # Note: The /api/v1/ path will be automatically appended if not present
175
+ python -m karakeep_python_api --base-url https://karakeep.domain.com/api/v1/ --api-key YOUR_API_KEY get-all-bookmarks --limit 10
175
176
 
176
177
  # Get all lists and pipe the JSON output to jq to extract the first list
177
178
  python -m karakeep_python_api get-all-lists | jq '.[0]'
@@ -195,14 +196,14 @@ import os
195
196
  from karakeep_python_api import KarakeepAPI, APIError, AuthenticationError, datatypes
196
197
 
197
198
  # Ensure required environment variables are set
198
- # Example: os.environ["KARAKEEP_PYTHON_API_BASE_URL"] = "https://your-karakeep.example.com/api/v1/"
199
+ # Example: os.environ["KARAKEEP_PYTHON_API_ENDPOINT"] = "https://karakeep.domain.com/api/v1/"
199
200
  # Example: os.environ["KARAKEEP_PYTHON_API_KEY"] = "your_secret_api_key"
200
201
 
201
202
  try:
202
203
  # Initialize the client (reads from env vars by default)
203
204
  client = KarakeepAPI(
204
205
  # Optionally override env vars:
205
- # base_url="https://another.karakeep.com",
206
+ # api_endpoint="https://karakeep.domain.com/api/v1/",
206
207
  # api_key="another_key",
207
208
  # verbose=True,
208
209
  # disable_response_validation=False
@@ -232,23 +233,27 @@ except AuthenticationError as e:
232
233
  except APIError as e:
233
234
  print(f"An API error occurred: {e}")
234
235
  except ValueError as e:
235
- # Handles missing API key/base URL during initialization
236
+ # Handles missing API key/endpoint during initialization
236
237
  print(f"Configuration error: {e}")
237
238
  except Exception as e:
238
239
  print(f"An unexpected error occurred: {e}")
239
240
 
240
241
  ```
241
242
 
242
- ### Usage Examples
243
+ ## Community Scripts
243
244
 
244
- Examples of the API being used can be found in the [`./examples`](./examples) folder. Don't hesitate to submit yours!
245
+ Community Scripts are a bunch of scripts made to solve specific issues. They are made by the community so don't hesitate to submit yours or open an issue if you have a bug. They also serve as example of how to use the API.
245
246
 
246
- | Example Script | Description | Documentation |
247
- |----------------|-------------|---------------|
248
- | **Add Time-to-Read Tags** | Automatically adds time-to-read tags (`0-5m`, `5-10m`, etc.) to bookmarks based on content length analysis. | [`README.md`](./examples/add_time_to_read_tag/README.md) |
249
- | **List to Tag Converter** | Converts a Karakeep list into tags by adding a specified tag to all bookmarks within that list. | [`README.md`](./examples/list_to_tag/README.md) |
250
- | **Omnivore Archiving Status Updater** | Fixes the archived status of bookmarks imported from Omnivore by reading export data and updating Karakeep accordingly. | [`README.md`](./examples/omnivore_archiving_status_updater/README.md) |
251
- | **Pocket Archiving Status Updater** | Fixes the archived status of bookmarks imported from Pocket by reading export data and updating Karakeep accordingly. | [`README.md`](./examples/pocket_archiving_status_updater/README.md) |
247
+ They can be found in the [./community_scripts](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts) folder. Don't hesitate to submit yours, the contribution guidelines are in the community_scripts directory README.md file.
248
+
249
+ | Community Script | Description | Documentation |
250
+ |----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
251
+ | **Karakeep-Time-Tagger** | Automatically adds time-to-read tags (`0-5m`, `5-10m`, etc.) to bookmarks based on content length analysis. Includes systemd service and timer files for automated periodic execution. | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/karakeep-time-tagger) |
252
+ | **Karakeep-List-To-Tag** | Converts a Karakeep list into tags by adding a specified tag to all bookmarks within that list. | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/karakeep-list-to-tag) |
253
+ | **Omnivore2Karakeep-Highlights** | Imports highlights from Omnivore export data to Karakeep, with intelligent position detection and bookmark matching. Supports dry-run mode for testing. | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/omnivore2karakeep-highlights) |
254
+ | **Omnivore2Karakeep-Archived** | (Should not be needed anymore) Fixes the archived status of bookmarks imported from Omnivore by reading export data and updating Karakeep accordingly. | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/omnivore2karakeep-archived) |
255
+ | **pocket2karakeep-archived** by [@youenchene](https://github.com/youenchene) | (Should not be needed anymore) Fixes the archived status of bookmarks imported from Pocket by reading export data and updating Karakeep accordingly. | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/pocket2karakeep-archived) |
256
+ | **karakeep-archive-before-date** by [@youenchene](https://github.com/youenchene) | Allow you to archive all not archived post before a given date | [`Link`](https://github.com/thiswillbeyourgithub/karakeep_python_api/tree/main/community_scripts/karakeep-archive-before-date) |
252
257
 
253
258
  ## Development
254
259
 
@@ -259,7 +264,7 @@ Examples of the API being used can be found in the [`./examples`](./examples) fo
259
264
  ```bash
260
265
  uv pip install -e ".[dev]"
261
266
  ```
262
- 4. Set the required environment variables (`KARAKEEP_PYTHON_API_BASE_URL`, `KARAKEEP_PYTHON_API_KEY`) for running tests against a live instance.
267
+ 4. Set the required environment variables (`KARAKEEP_PYTHON_API_ENDPOINT`, `KARAKEEP_PYTHON_API_KEY`) for running tests against a live instance.
263
268
  5. Run tests:
264
269
 
265
270
  ```bash
@@ -0,0 +1,11 @@
1
+ karakeep_python_api/__init__.py,sha256=qk3MeIIvnCNAyYzJrv8dMKVXvA4ejLU3mhB3xFzdLDY,606
2
+ karakeep_python_api/__main__.py,sha256=X1GXwRvAAw8qFmMm2MKdmPNYkmlmEUUQalJX9kWxrS0,32402
3
+ karakeep_python_api/datatypes.py,sha256=vcDqS8r_LRMLqrIvHZIqkvUSiucprz58jOAeC82QwHY,3415
4
+ karakeep_python_api/karakeep_api.py,sha256=nvpynAk4S5G1-sjf0T4sl9kYjHyV12bQobd8fiMLVlg,85276
5
+ karakeep_python_api/openapi_reference.json,sha256=9bvTkzHbgtUg-xg6T6zF3bSdxBMW1Rn5nC-l2jH6FoQ,80553
6
+ karakeep_python_api-1.1.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
+ karakeep_python_api-1.1.0.dist-info/METADATA,sha256=bGJws2bLSvGXZwi6mFlpnh0NhVBXtjsSDZqybwgyQ8E,15732
8
+ karakeep_python_api-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ karakeep_python_api-1.1.0.dist-info/entry_points.txt,sha256=n0leQp_IX2NivoWuUcaR9ZHwAvl_6yt-NcHWuCJyxNo,62
10
+ karakeep_python_api-1.1.0.dist-info/top_level.txt,sha256=X3VKqh9YAbPQp144db0Ko2C5Q2y6-nwpPgtnuCiSDmU,20
11
+ karakeep_python_api-1.1.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- karakeep_python_api/__init__.py,sha256=qk3MeIIvnCNAyYzJrv8dMKVXvA4ejLU3mhB3xFzdLDY,606
2
- karakeep_python_api/__main__.py,sha256=UfL6R-S3rJB7PH3iqTicNq0cvtumnrG_FVyKK36bqdE,32327
3
- karakeep_python_api/datatypes.py,sha256=CScuMq5oT4YwvGH8Af_Oo60xGmC622d74f4WPUw7l8Q,3398
4
- karakeep_python_api/karakeep_api.py,sha256=NYGvU0UIK95k841cz7Y9thWnoTAaWMRVuny7JGLjYQA,81921
5
- karakeep_python_api/openapi_reference.json,sha256=Ku1cO_N4QPLQc7sK8L7FRt2T68QyPCwTGA7RK-046iU,80379
6
- karakeep_python_api-0.2.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
- karakeep_python_api-0.2.3.dist-info/METADATA,sha256=b6rRzFPf1OvnALa37p7CXKnzWQt6Aa3yUQK9k7Lq1UQ,13649
8
- karakeep_python_api-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- karakeep_python_api-0.2.3.dist-info/entry_points.txt,sha256=n0leQp_IX2NivoWuUcaR9ZHwAvl_6yt-NcHWuCJyxNo,62
10
- karakeep_python_api-0.2.3.dist-info/top_level.txt,sha256=X3VKqh9YAbPQp144db0Ko2C5Q2y6-nwpPgtnuCiSDmU,20
11
- karakeep_python_api-0.2.3.dist-info/RECORD,,