karakeep-python-api 0.1.6__tar.gz → 0.1.7__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-0.1.6/karakeep_python_api.egg-info → karakeep_python_api-0.1.7}/PKG-INFO +8 -2
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/README.md +7 -1
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api/__main__.py +113 -9
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api/datatypes.py +10 -10
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api/karakeep_api.py +22 -8
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api/openapi_reference.json +1 -1
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7/karakeep_python_api.egg-info}/PKG-INFO +8 -2
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/setup.py +1 -1
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/tests/test_karakeep_api.py +58 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/LICENSE +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/MANIFEST.in +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api/__init__.py +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/entry_points.txt +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/requires.txt +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/top_level.txt +0 -0
- {karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/setup.cfg +0 -0
{karakeep_python_api-0.1.6/karakeep_python_api.egg-info → karakeep_python_api-0.1.7}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: karakeep_python_api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -57,7 +57,7 @@ A community-developed Python client for the [Karakeep](https://karakeep.app/) AP
|
|
|
57
57
|
|
|
58
58
|
## Overview
|
|
59
59
|
|
|
60
|
-
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API.
|
|
60
|
+
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API. The author also developed [freshrss_to_karakeep](https://github.com/thiswillbeyourgithub/freshrss_to_karakeep), a Python script that periodically sends FreshRSS "favourite" articles to Karakeep (a bookmarking and read-it-later app, see [Karakeep on GitHub](https://github.com/karakeep-app/karakeep)).
|
|
61
61
|
|
|
62
62
|
The development process involved:
|
|
63
63
|
|
|
@@ -238,6 +238,12 @@ except Exception as e:
|
|
|
238
238
|
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
### Usage Examples
|
|
242
|
+
|
|
243
|
+
Examples of the API being used can be found in the [`./examples`](./examples) folder. Don't hesitate to submit yours!
|
|
244
|
+
|
|
245
|
+
- **Omnivore Archiving Status Updater**: A script to fix the archive status of bookmarks imported from Omnivore into Karakeep. See [`examples/omnivore_archiving_status_updater/README.md`](./examples/omnivore_archiving_status_updater/README.md) for details.
|
|
246
|
+
|
|
241
247
|
## Development
|
|
242
248
|
|
|
243
249
|
1. Clone the repository.
|
|
@@ -22,7 +22,7 @@ A community-developed Python client for the [Karakeep](https://karakeep.app/) AP
|
|
|
22
22
|
|
|
23
23
|
## Overview
|
|
24
24
|
|
|
25
|
-
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API.
|
|
25
|
+
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API. The author also developed [freshrss_to_karakeep](https://github.com/thiswillbeyourgithub/freshrss_to_karakeep), a Python script that periodically sends FreshRSS "favourite" articles to Karakeep (a bookmarking and read-it-later app, see [Karakeep on GitHub](https://github.com/karakeep-app/karakeep)).
|
|
26
26
|
|
|
27
27
|
The development process involved:
|
|
28
28
|
|
|
@@ -203,6 +203,12 @@ except Exception as e:
|
|
|
203
203
|
|
|
204
204
|
```
|
|
205
205
|
|
|
206
|
+
### Usage Examples
|
|
207
|
+
|
|
208
|
+
Examples of the API being used can be found in the [`./examples`](./examples) folder. Don't hesitate to submit yours!
|
|
209
|
+
|
|
210
|
+
- **Omnivore Archiving Status Updater**: A script to fix the archive status of bookmarks imported from Omnivore into Karakeep. See [`examples/omnivore_archiving_status_updater/README.md`](./examples/omnivore_archiving_status_updater/README.md) for details.
|
|
211
|
+
|
|
206
212
|
## Development
|
|
207
213
|
|
|
208
214
|
1. Clone the repository.
|
|
@@ -286,10 +286,97 @@ def create_click_command(
|
|
|
286
286
|
|
|
287
287
|
# Call the API method
|
|
288
288
|
try:
|
|
289
|
-
|
|
290
|
-
f"
|
|
291
|
-
|
|
292
|
-
|
|
289
|
+
if method_name == "get_all_bookmarks":
|
|
290
|
+
logger.debug(f"Special CLI pagination handling for '{method_name}'.")
|
|
291
|
+
cli_total_limit = call_args.pop('limit', None)
|
|
292
|
+
# Other relevant params for get_all_bookmarks
|
|
293
|
+
archived_filter = call_args.get('archived')
|
|
294
|
+
favourited_filter = call_args.get('favourited')
|
|
295
|
+
include_content_cli = call_args.get('include_content', True)
|
|
296
|
+
|
|
297
|
+
call_args.pop('cursor', None) # Ignore CLI cursor
|
|
298
|
+
|
|
299
|
+
all_bookmarks_data = []
|
|
300
|
+
current_page_api_cursor = None
|
|
301
|
+
fetched_count = 0
|
|
302
|
+
API_INTERNAL_PAGE_SIZE = 50 # Define a page size for API calls
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
api_call_limit = API_INTERNAL_PAGE_SIZE
|
|
306
|
+
if cli_total_limit is not None:
|
|
307
|
+
remaining_needed = cli_total_limit - fetched_count
|
|
308
|
+
if remaining_needed <= 0:
|
|
309
|
+
break # Reached or exceeded CLI total limit
|
|
310
|
+
api_call_limit = min(API_INTERNAL_PAGE_SIZE, remaining_needed)
|
|
311
|
+
|
|
312
|
+
if api_call_limit <= 0 and cli_total_limit is not None : # Avoid asking for 0 or negative items unless fetching all
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
logger.debug(f"Fetching page for '{method_name}' with cursor: {current_page_api_cursor}, api_limit: {api_call_limit}")
|
|
317
|
+
|
|
318
|
+
page_call_args = {
|
|
319
|
+
'archived': archived_filter,
|
|
320
|
+
'favourited': favourited_filter,
|
|
321
|
+
'limit': api_call_limit,
|
|
322
|
+
'cursor': current_page_api_cursor,
|
|
323
|
+
'include_content': include_content_cli,
|
|
324
|
+
}
|
|
325
|
+
page_call_args_filtered = {k: v for k, v in page_call_args.items() if v is not None}
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
page_result_obj = instance_method(**page_call_args_filtered)
|
|
329
|
+
except TypeError as call_error_page:
|
|
330
|
+
logger.error(f"Error calling API method '{method_name}' (paginated): {call_error_page}")
|
|
331
|
+
logger.error(f"Provided arguments for page: {page_call_args_filtered}")
|
|
332
|
+
if verbose: logger.debug(traceback.format_exc())
|
|
333
|
+
ctx.exit(1)
|
|
334
|
+
|
|
335
|
+
bookmarks_on_this_page = []
|
|
336
|
+
next_api_cursor = None
|
|
337
|
+
|
|
338
|
+
# Convert Pydantic model to dict using model_dump if available
|
|
339
|
+
if hasattr(page_result_obj, 'model_dump'):
|
|
340
|
+
result_dict = page_result_obj.model_dump()
|
|
341
|
+
elif isinstance(page_result_obj, dict):
|
|
342
|
+
result_dict = page_result_obj
|
|
343
|
+
else:
|
|
344
|
+
logger.warning(f"Unexpected result type: {type(page_result_obj)}")
|
|
345
|
+
result_dict = {}
|
|
346
|
+
|
|
347
|
+
# Extract data and cursor from the dict
|
|
348
|
+
bookmarks_on_this_page = result_dict.get("bookmarks", [])
|
|
349
|
+
next_api_cursor = result_dict.get("nextCursor")
|
|
350
|
+
|
|
351
|
+
logger.debug(f"Extracted {len(bookmarks_on_this_page)} bookmarks and cursor: {next_api_cursor}")
|
|
352
|
+
|
|
353
|
+
if not isinstance(bookmarks_on_this_page, list):
|
|
354
|
+
logger.warning(f"Expected a list of bookmarks, got {type(bookmarks_on_this_page)}. Stopping pagination.")
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
all_bookmarks_data.extend(bookmarks_on_this_page)
|
|
358
|
+
fetched_count += len(bookmarks_on_this_page)
|
|
359
|
+
logger.debug(f"Fetched {len(bookmarks_on_this_page)} bookmarks this page. Total fetched: {fetched_count}.")
|
|
360
|
+
|
|
361
|
+
current_page_api_cursor = next_api_cursor
|
|
362
|
+
if not current_page_api_cursor:
|
|
363
|
+
logger.debug("No nextCursor from API, pagination complete.")
|
|
364
|
+
break
|
|
365
|
+
if cli_total_limit is not None and fetched_count >= cli_total_limit:
|
|
366
|
+
logger.debug(f"CLI total limit of {cli_total_limit} reached or exceeded.")
|
|
367
|
+
break
|
|
368
|
+
if not bookmarks_on_this_page and api_call_limit > 0:
|
|
369
|
+
logger.debug("API returned an empty list of bookmarks while a positive limit was set, assuming end of data.")
|
|
370
|
+
break
|
|
371
|
+
|
|
372
|
+
result = all_bookmarks_data # This will be a list of Bookmark models or dicts
|
|
373
|
+
else:
|
|
374
|
+
# Original behavior for other commands
|
|
375
|
+
logger.debug(
|
|
376
|
+
f"Calling API method '{method_name}' with args: {call_args}"
|
|
377
|
+
)
|
|
378
|
+
result = instance_method(**call_args)
|
|
379
|
+
|
|
293
380
|
except TypeError as call_error:
|
|
294
381
|
logger.error(
|
|
295
382
|
f"Error calling API method '{method_name}': {call_error}"
|
|
@@ -460,16 +547,33 @@ def create_click_command(
|
|
|
460
547
|
|
|
461
548
|
click_required = is_required_in_sig and default_value is None and not is_flag
|
|
462
549
|
|
|
550
|
+
# Make copies of properties that might be modified for specific commands/params
|
|
551
|
+
current_param_help = param_help
|
|
552
|
+
current_click_required = click_required
|
|
553
|
+
current_default_value = default_value
|
|
554
|
+
current_is_flag = is_flag # Though is_flag interpretation might change help/required
|
|
555
|
+
|
|
556
|
+
# Special handling for 'get_all_bookmarks' command parameters
|
|
557
|
+
if api_method_name == "get_all_bookmarks":
|
|
558
|
+
if param.name == "cursor":
|
|
559
|
+
current_param_help = "[Ignored by CLI for get-all-bookmarks] " + param_help
|
|
560
|
+
current_click_required = False # Cursor is handled by CLI, not required from user
|
|
561
|
+
current_default_value = None # Explicitly set default to None for ignored param
|
|
562
|
+
elif param.name == "limit":
|
|
563
|
+
current_param_help = "Total maximum number of bookmarks to fetch across pages for get-all-bookmarks. If omitted, all are fetched."
|
|
564
|
+
# For 'limit', required status and default remain as derived from its Optional[int] type hint
|
|
565
|
+
# current_click_required and current_default_value will be correctly False and None respectively.
|
|
566
|
+
|
|
463
567
|
# Add the Click Option
|
|
464
568
|
click_params.append(
|
|
465
569
|
click.Option(
|
|
466
570
|
option_names,
|
|
467
571
|
type=click_type,
|
|
468
|
-
required=
|
|
469
|
-
default=
|
|
470
|
-
help=
|
|
471
|
-
is_flag=(
|
|
472
|
-
show_default=not
|
|
572
|
+
required=current_click_required,
|
|
573
|
+
default=current_default_value if not current_is_flag else None,
|
|
574
|
+
help=current_param_help,
|
|
575
|
+
is_flag=(current_is_flag if len(option_names) == 1 else False),
|
|
576
|
+
show_default=not current_is_flag and current_default_value is not None,
|
|
473
577
|
# Click derives the Python identifier (e.g., 'bookmark_id') from the first long option name
|
|
474
578
|
)
|
|
475
579
|
)
|
|
@@ -30,13 +30,13 @@ class HighlightId(RootModel[str]):
|
|
|
30
30
|
root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class TaggingStatus(Enum):
|
|
33
|
+
class TaggingStatus(str, Enum):
|
|
34
34
|
success = "success"
|
|
35
35
|
failure = "failure"
|
|
36
36
|
pending = "pending"
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
class AttachedBy(Enum):
|
|
39
|
+
class AttachedBy(str, Enum):
|
|
40
40
|
ai = "ai"
|
|
41
41
|
human = "human"
|
|
42
42
|
|
|
@@ -47,7 +47,7 @@ class Tag(BaseModel):
|
|
|
47
47
|
attachedBy: AttachedBy
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
class Type(Enum):
|
|
50
|
+
class Type(str, Enum):
|
|
51
51
|
link = "link"
|
|
52
52
|
|
|
53
53
|
|
|
@@ -71,7 +71,7 @@ class Content(BaseModel):
|
|
|
71
71
|
dateModified: Optional[str] = None
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
class Type1(Enum):
|
|
74
|
+
class Type1(str, Enum):
|
|
75
75
|
text = "text"
|
|
76
76
|
|
|
77
77
|
|
|
@@ -81,11 +81,11 @@ class Content1(BaseModel):
|
|
|
81
81
|
sourceUrl: Optional[str] = None
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
class Type2(Enum):
|
|
84
|
+
class Type2(str, Enum):
|
|
85
85
|
asset = "asset"
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
class AssetType(Enum):
|
|
88
|
+
class AssetType(str, Enum):
|
|
89
89
|
image = "image"
|
|
90
90
|
pdf = "pdf"
|
|
91
91
|
|
|
@@ -100,7 +100,7 @@ class Content2(BaseModel):
|
|
|
100
100
|
content: Optional[str] = None
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
class Type3(Enum):
|
|
103
|
+
class Type3(str, Enum):
|
|
104
104
|
unknown = "unknown"
|
|
105
105
|
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ class Content3(BaseModel):
|
|
|
108
108
|
type: Type3
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
class AssetType1(Enum):
|
|
111
|
+
class AssetType1(str, Enum):
|
|
112
112
|
screenshot = "screenshot"
|
|
113
113
|
assetScreenshot = "assetScreenshot"
|
|
114
114
|
bannerImage = "bannerImage"
|
|
@@ -148,7 +148,7 @@ class Cursor(RootModel[str]):
|
|
|
148
148
|
root: str
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
class Color(Enum):
|
|
151
|
+
class Color(str, Enum):
|
|
152
152
|
yellow = "yellow"
|
|
153
153
|
red = "red"
|
|
154
154
|
green = "green"
|
|
@@ -167,7 +167,7 @@ class Highlight(BaseModel):
|
|
|
167
167
|
createdAt: str
|
|
168
168
|
|
|
169
169
|
|
|
170
|
-
class Type4(Enum):
|
|
170
|
+
class Type4(str, Enum):
|
|
171
171
|
manual = "manual"
|
|
172
172
|
smart = "smart"
|
|
173
173
|
|
|
@@ -85,7 +85,7 @@ class KarakeepAPI:
|
|
|
85
85
|
"""
|
|
86
86
|
|
|
87
87
|
# Version reflects the client library version, updated by bumpver
|
|
88
|
-
VERSION: str = "0.1.
|
|
88
|
+
VERSION: str = "0.1.7"
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
@@ -578,9 +578,10 @@ class KarakeepAPI:
|
|
|
578
578
|
) from e
|
|
579
579
|
|
|
580
580
|
@optional_typecheck
|
|
581
|
-
def _enforce_rate_limit(self, min_interval_sec: float =
|
|
581
|
+
def _enforce_rate_limit(self, min_interval_sec: float = 0.5) -> None:
|
|
582
582
|
"""
|
|
583
583
|
Ensures a minimum time interval between consecutive API calls.
|
|
584
|
+
Otherwise karakeep tends to error out.
|
|
584
585
|
|
|
585
586
|
If the time since the last call is less than `min_interval_sec`, this method
|
|
586
587
|
will sleep for the remaining duration. It then updates the timestamp of the
|
|
@@ -610,23 +611,36 @@ class KarakeepAPI:
|
|
|
610
611
|
self,
|
|
611
612
|
archived: Optional[bool] = None,
|
|
612
613
|
favourited: Optional[bool] = None,
|
|
613
|
-
limit: Optional[int] = None,
|
|
614
|
+
limit: Optional[int] = None, # This is the per-page limit for the API
|
|
614
615
|
cursor: Optional[str] = None,
|
|
615
616
|
include_content: bool = True, # Default from spec
|
|
616
617
|
) -> Any: # Returns PaginatedBookmarks or raw dict/list
|
|
617
618
|
"""
|
|
618
|
-
Get
|
|
619
|
+
Get bookmarks, one page at a time. Corresponds to GET /bookmarks.
|
|
620
|
+
|
|
621
|
+
This method fetches a single page of bookmarks.
|
|
622
|
+
The 'limit' parameter controls the number of items per page for this API call.
|
|
623
|
+
The 'cursor' parameter is used for pagination to get the next page.
|
|
624
|
+
|
|
625
|
+
CLI Usage Notes:
|
|
626
|
+
- When the 'get-all-bookmarks' command is used via the CLI:
|
|
627
|
+
- The `--cursor` CLI option is ignored; pagination is handled automatically.
|
|
628
|
+
- The `--limit` CLI option specifies the *total* number of bookmarks to fetch
|
|
629
|
+
across multiple pages. If omitted, all bookmarks are fetched.
|
|
630
|
+
- The CLI internally calls this API method multiple times to achieve this.
|
|
619
631
|
|
|
620
632
|
Args:
|
|
621
633
|
archived: Filter by archived status (optional).
|
|
622
634
|
favourited: Filter by favourited status (optional).
|
|
623
|
-
limit: Maximum number of bookmarks to return (optional).
|
|
624
|
-
|
|
635
|
+
limit: Maximum number of bookmarks to return *per page* in this API call (optional).
|
|
636
|
+
When used from the CLI's 'get-all-bookmarks' command, this translates to an
|
|
637
|
+
internal per-page fetching limit, while the CLI's `--limit` controls the total.
|
|
638
|
+
cursor: Pagination cursor for fetching the next page (optional).
|
|
625
639
|
include_content: If set to true, bookmark's content will be included (default: True).
|
|
626
640
|
|
|
627
641
|
Returns:
|
|
628
|
-
datatypes.PaginatedBookmarks: Paginated list of bookmarks.
|
|
629
|
-
If response validation is disabled, returns the raw API response (dict/list).
|
|
642
|
+
datatypes.PaginatedBookmarks: Paginated list of bookmarks for the current page.
|
|
643
|
+
If response validation is disabled, returns the raw API response (dict/list) for the current page.
|
|
630
644
|
|
|
631
645
|
Raises:
|
|
632
646
|
APIError: If the API request fails.
|
{karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7/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: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -57,7 +57,7 @@ A community-developed Python client for the [Karakeep](https://karakeep.app/) AP
|
|
|
57
57
|
|
|
58
58
|
## Overview
|
|
59
59
|
|
|
60
|
-
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API.
|
|
60
|
+
This library provides a Python interface (both a class and a command-line tool) to interact with a Karakeep instance's API. The author also developed [freshrss_to_karakeep](https://github.com/thiswillbeyourgithub/freshrss_to_karakeep), a Python script that periodically sends FreshRSS "favourite" articles to Karakeep (a bookmarking and read-it-later app, see [Karakeep on GitHub](https://github.com/karakeep-app/karakeep)).
|
|
61
61
|
|
|
62
62
|
The development process involved:
|
|
63
63
|
|
|
@@ -238,6 +238,12 @@ except Exception as e:
|
|
|
238
238
|
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
### Usage Examples
|
|
242
|
+
|
|
243
|
+
Examples of the API being used can be found in the [`./examples`](./examples) folder. Don't hesitate to submit yours!
|
|
244
|
+
|
|
245
|
+
- **Omnivore Archiving Status Updater**: A script to fix the archive status of bookmarks imported from Omnivore into Karakeep. See [`examples/omnivore_archiving_status_updater/README.md`](./examples/omnivore_archiving_status_updater/README.md) for details.
|
|
246
|
+
|
|
241
247
|
## Development
|
|
242
248
|
|
|
243
249
|
1. Clone the repository.
|
|
@@ -7,7 +7,7 @@ with open("README.md", "r") as readme:
|
|
|
7
7
|
|
|
8
8
|
setup(
|
|
9
9
|
name="karakeep_python_api",
|
|
10
|
-
version="0.1.
|
|
10
|
+
version="0.1.7",
|
|
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",
|
|
@@ -600,6 +600,64 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
600
600
|
|
|
601
601
|
# --- Test User Info/Stats Endpoints ---
|
|
602
602
|
|
|
603
|
+
def test_cli_get_bookmarks_count_with_jq(karakeep_client: KarakeepAPI):
|
|
604
|
+
"""Test that CLI get-all-bookmarks with --limit returns the expected number of items."""
|
|
605
|
+
# Skip test if jq is not installed
|
|
606
|
+
try:
|
|
607
|
+
subprocess.run(["jq", "--version"], check=True, capture_output=True)
|
|
608
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
609
|
+
pytest.skip("jq is not installed. This test requires jq for JSON processing.")
|
|
610
|
+
|
|
611
|
+
# Define the limit we want to test
|
|
612
|
+
test_limit = 200
|
|
613
|
+
|
|
614
|
+
try:
|
|
615
|
+
logger.info(f"\nRunning CLI command: get-all-bookmarks --limit={test_limit}")
|
|
616
|
+
# Use a two-command pipe: Run the CLI command and pipe to jq to count array length
|
|
617
|
+
cmd = f"python -m karakeep_python_api --verbose get-all-bookmarks --limit={test_limit} | jq 'length'"
|
|
618
|
+
|
|
619
|
+
# Execute the piped command
|
|
620
|
+
result = subprocess.run(
|
|
621
|
+
cmd,
|
|
622
|
+
shell=True,
|
|
623
|
+
check=True,
|
|
624
|
+
capture_output=True,
|
|
625
|
+
text=True,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# Parse the output (should be just a number)
|
|
629
|
+
try:
|
|
630
|
+
actual_count = int(result.stdout.strip())
|
|
631
|
+
logger.info(f"✓ Command returned {actual_count} bookmarks")
|
|
632
|
+
|
|
633
|
+
# Check if we got exactly the requested number or fewer (if there aren't enough bookmarks)
|
|
634
|
+
assert actual_count <= test_limit, f"Expected at most {test_limit} bookmarks, got {actual_count}"
|
|
635
|
+
|
|
636
|
+
# Check if we got any bookmarks at all (to ensure the test is meaningful)
|
|
637
|
+
# This could fail if the account has no bookmarks
|
|
638
|
+
assert actual_count > 0, "Expected at least some bookmarks to be returned"
|
|
639
|
+
|
|
640
|
+
# If the account has enough bookmarks, we should get exactly the limit
|
|
641
|
+
# But we can't assert this because we don't know how many bookmarks exist
|
|
642
|
+
if actual_count < test_limit:
|
|
643
|
+
logger.info(f"Note: Only {actual_count} bookmarks were returned, which is less than the requested limit of {test_limit}. This is acceptable if the account doesn't have {test_limit} bookmarks.")
|
|
644
|
+
else:
|
|
645
|
+
logger.info(f"✓ Command returned exactly the requested limit of {test_limit} bookmarks")
|
|
646
|
+
|
|
647
|
+
except ValueError:
|
|
648
|
+
logger.error(f"Failed to parse jq output as integer: '{result.stdout}'")
|
|
649
|
+
pytest.fail(f"jq output is not a valid integer: '{result.stdout}'")
|
|
650
|
+
|
|
651
|
+
except subprocess.CalledProcessError as e:
|
|
652
|
+
logger.error(f"Command failed with exit code {e.returncode}")
|
|
653
|
+
logger.error(f"Stdout: {e.stdout}")
|
|
654
|
+
logger.error(f"Stderr: {e.stderr}")
|
|
655
|
+
pytest.fail(f"CLI command failed: {e}")
|
|
656
|
+
except Exception as e:
|
|
657
|
+
logger.error(f"An unexpected error occurred: {e}")
|
|
658
|
+
pytest.fail(f"Unexpected error: {e}")
|
|
659
|
+
|
|
660
|
+
|
|
603
661
|
def test_get_current_user_stats(karakeep_client: KarakeepAPI):
|
|
604
662
|
"""Test retrieving statistics for the current user."""
|
|
605
663
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{karakeep_python_api-0.1.6 → karakeep_python_api-0.1.7}/karakeep_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|