karakeep-python-api 0.1.1__tar.gz → 0.1.3__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.1/karakeep_python_api.egg-info → karakeep_python_api-0.1.3}/PKG-INFO +45 -38
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/README.md +44 -37
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api/__main__.py +72 -26
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api/karakeep_api.py +36 -4
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api/openapi_reference.json +1 -1
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3/karakeep_python_api.egg-info}/PKG-INFO +45 -38
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/setup.py +1 -1
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/tests/test_karakeep_api.py +212 -105
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/LICENSE +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/MANIFEST.in +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api/__init__.py +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api/datatypes.py +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/entry_points.txt +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/requires.txt +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/top_level.txt +0 -0
- {karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/setup.cfg +0 -0
{karakeep_python_api-0.1.1/karakeep_python_api.egg-info → karakeep_python_api-0.1.3}/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.3
|
|
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
|
|
@@ -72,43 +72,46 @@ The development process involved:
|
|
|
72
72
|
|
|
73
73
|
## API Method Coverage
|
|
74
74
|
|
|
75
|
-
The following table lists the public methods available in the `KarakeepAPI` class
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
75
|
+
The following table lists the public methods available in the `KarakeepAPI` class.
|
|
76
|
+
* The "Pytest" column indicates whether the Python library method is covered by the automated test suite (`tests/test_karakeep_api.py`).
|
|
77
|
+
* The "CLI" column indicates whether the corresponding CLI command for that method is tested within the Pytest suite (typically via `subprocess`).
|
|
78
|
+
Methods or CLI commands marked with ❌ should be used with caution as their behavior has not been automatically verified within the test suite.
|
|
79
|
+
|
|
80
|
+
| Method Name | Pytest | CLI | Remarks |
|
|
81
|
+
| -------------------------------- | :----: | :--: | -------------------------------------------- |
|
|
82
|
+
| `get_all_bookmarks` | ✅ | ✅ | Tested with pagination. |
|
|
83
|
+
| `create_a_new_bookmark` | ✅ | ❌ | Pytest for `type="link"` via fixture. CLI not directly tested. |
|
|
84
|
+
| `search_bookmarks` | ✅ | ✅ | Seems to be nondeterministic and fails if using more than 3 words |
|
|
85
|
+
| `get_a_single_bookmark` | ✅ | ❌ | |
|
|
86
|
+
| `delete_a_bookmark` | ✅ | ❌ | |
|
|
87
|
+
| `update_a_bookmark` | ✅ | ✅ | Tested for title updates. |
|
|
88
|
+
| `summarize_a_bookmark` | ❌ | ❌ | |
|
|
89
|
+
| `attach_tags_to_a_bookmark` | ✅ | ❌ | |
|
|
90
|
+
| `detach_tags_from_a_bookmark` | ✅ | ❌ | |
|
|
91
|
+
| `get_highlights_of_a_bookmark` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
92
|
+
| `attach_asset` | ❌ | ❌ | |
|
|
93
|
+
| `replace_asset` | ❌ | ❌ | |
|
|
94
|
+
| `detach_asset` | ❌ | ❌ | |
|
|
95
|
+
| `get_all_lists` | ✅ | ✅ | |
|
|
96
|
+
| `create_a_new_list` | ✅ | ❌ | |
|
|
97
|
+
| `get_a_single_list` | ✅ | ❌ | |
|
|
98
|
+
| `delete_a_list` | ✅ | ❌ | |
|
|
99
|
+
| `update_a_list` | ❌ | ❌ | |
|
|
100
|
+
| `get_a_bookmarks_in_a_list` | ❌ | ❌ | |
|
|
101
|
+
| `add_a_bookmark_to_a_list` | ❌ | ❌ | |
|
|
102
|
+
| `remove_a_bookmark_from_a_list` | ❌ | ❌ | |
|
|
103
|
+
| `get_all_tags` | ✅ | ✅ | |
|
|
104
|
+
| `get_a_single_tag` | ✅ | ❌ | |
|
|
105
|
+
| `delete_a_tag` | ✅ | ❌ | |
|
|
106
|
+
| `update_a_tag` | ✅ | ❌ | No output validation due to [server bug](https://github.com/karakeep-app/karakeep/issues/1365). |
|
|
107
|
+
| `get_a_bookmarks_with_the_tag` | ❌ | ❌ | |
|
|
108
|
+
| `get_all_highlights` | ✅ | ✅ | Tested with pagination. |
|
|
109
|
+
| `create_a_new_highlight` | ❌ | ❌ | |
|
|
110
|
+
| `get_a_single_highlight` | ❌ | ❌ | |
|
|
111
|
+
| `delete_a_highlight` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
112
|
+
| `update_a_highlight` | ❌ | ❌ | |
|
|
113
|
+
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
114
|
+
| `get_current_user_stats` | ✅ | ✅ | |
|
|
112
115
|
|
|
113
116
|
## Installation
|
|
114
117
|
|
|
@@ -137,6 +140,7 @@ The client can be configured using the following environment variables:
|
|
|
137
140
|
* `KARAKEEP_PYTHON_API_VERIFY_SSL`: Set to `false` to disable SSL certificate verification (default: `true`).
|
|
138
141
|
* `KARAKEEP_PYTHON_API_VERBOSE`: Set to `true` to enable verbose debug logging for the client and CLI (default: `false`).
|
|
139
142
|
* `KARAKEEP_PYTHON_API_DISABLE_RESPONSE_VALIDATION`: Set to `true` to disable Pydantic validation of API responses. The client will return raw dictionary/list data instead of Pydantic models (default: `false`).
|
|
143
|
+
* `KARAKEEP_PYTHON_API_ENSURE_ASCII`: Set to `true` to escape non-ASCII characters in the JSON output (default: `false`, which means Unicode characters are kept).
|
|
140
144
|
|
|
141
145
|
### Command Line Interface (CLI)
|
|
142
146
|
|
|
@@ -174,6 +178,9 @@ python -m karakeep_python_api get-all-lists | jq '.[0]'
|
|
|
174
178
|
# Create a new bookmark from a link (body provided as JSON string)
|
|
175
179
|
python -m karakeep_python_api create-a-new-bookmark --data '{"type": "link", "url": "https://example.com"}'
|
|
176
180
|
|
|
181
|
+
# Get all tags and ensure ASCII output (e.g., for compatibility with systems that don't handle Unicode well)
|
|
182
|
+
python -m karakeep_python_api --ascii get-all-tags
|
|
183
|
+
|
|
177
184
|
# Dump the raw OpenAPI spec used by the client
|
|
178
185
|
python -m karakeep_python_api --dump-openapi-specification
|
|
179
186
|
```
|
|
@@ -37,43 +37,46 @@ The development process involved:
|
|
|
37
37
|
|
|
38
38
|
## API Method Coverage
|
|
39
39
|
|
|
40
|
-
The following table lists the public methods available in the `KarakeepAPI` class
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
40
|
+
The following table lists the public methods available in the `KarakeepAPI` class.
|
|
41
|
+
* The "Pytest" column indicates whether the Python library method is covered by the automated test suite (`tests/test_karakeep_api.py`).
|
|
42
|
+
* The "CLI" column indicates whether the corresponding CLI command for that method is tested within the Pytest suite (typically via `subprocess`).
|
|
43
|
+
Methods or CLI commands marked with ❌ should be used with caution as their behavior has not been automatically verified within the test suite.
|
|
44
|
+
|
|
45
|
+
| Method Name | Pytest | CLI | Remarks |
|
|
46
|
+
| -------------------------------- | :----: | :--: | -------------------------------------------- |
|
|
47
|
+
| `get_all_bookmarks` | ✅ | ✅ | Tested with pagination. |
|
|
48
|
+
| `create_a_new_bookmark` | ✅ | ❌ | Pytest for `type="link"` via fixture. CLI not directly tested. |
|
|
49
|
+
| `search_bookmarks` | ✅ | ✅ | Seems to be nondeterministic and fails if using more than 3 words |
|
|
50
|
+
| `get_a_single_bookmark` | ✅ | ❌ | |
|
|
51
|
+
| `delete_a_bookmark` | ✅ | ❌ | |
|
|
52
|
+
| `update_a_bookmark` | ✅ | ✅ | Tested for title updates. |
|
|
53
|
+
| `summarize_a_bookmark` | ❌ | ❌ | |
|
|
54
|
+
| `attach_tags_to_a_bookmark` | ✅ | ❌ | |
|
|
55
|
+
| `detach_tags_from_a_bookmark` | ✅ | ❌ | |
|
|
56
|
+
| `get_highlights_of_a_bookmark` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
57
|
+
| `attach_asset` | ❌ | ❌ | |
|
|
58
|
+
| `replace_asset` | ❌ | ❌ | |
|
|
59
|
+
| `detach_asset` | ❌ | ❌ | |
|
|
60
|
+
| `get_all_lists` | ✅ | ✅ | |
|
|
61
|
+
| `create_a_new_list` | ✅ | ❌ | |
|
|
62
|
+
| `get_a_single_list` | ✅ | ❌ | |
|
|
63
|
+
| `delete_a_list` | ✅ | ❌ | |
|
|
64
|
+
| `update_a_list` | ❌ | ❌ | |
|
|
65
|
+
| `get_a_bookmarks_in_a_list` | ❌ | ❌ | |
|
|
66
|
+
| `add_a_bookmark_to_a_list` | ❌ | ❌ | |
|
|
67
|
+
| `remove_a_bookmark_from_a_list` | ❌ | ❌ | |
|
|
68
|
+
| `get_all_tags` | ✅ | ✅ | |
|
|
69
|
+
| `get_a_single_tag` | ✅ | ❌ | |
|
|
70
|
+
| `delete_a_tag` | ✅ | ❌ | |
|
|
71
|
+
| `update_a_tag` | ✅ | ❌ | No output validation due to [server bug](https://github.com/karakeep-app/karakeep/issues/1365). |
|
|
72
|
+
| `get_a_bookmarks_with_the_tag` | ❌ | ❌ | |
|
|
73
|
+
| `get_all_highlights` | ✅ | ✅ | Tested with pagination. |
|
|
74
|
+
| `create_a_new_highlight` | ❌ | ❌ | |
|
|
75
|
+
| `get_a_single_highlight` | ❌ | ❌ | |
|
|
76
|
+
| `delete_a_highlight` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
77
|
+
| `update_a_highlight` | ❌ | ❌ | |
|
|
78
|
+
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
79
|
+
| `get_current_user_stats` | ✅ | ✅ | |
|
|
77
80
|
|
|
78
81
|
## Installation
|
|
79
82
|
|
|
@@ -102,6 +105,7 @@ The client can be configured using the following environment variables:
|
|
|
102
105
|
* `KARAKEEP_PYTHON_API_VERIFY_SSL`: Set to `false` to disable SSL certificate verification (default: `true`).
|
|
103
106
|
* `KARAKEEP_PYTHON_API_VERBOSE`: Set to `true` to enable verbose debug logging for the client and CLI (default: `false`).
|
|
104
107
|
* `KARAKEEP_PYTHON_API_DISABLE_RESPONSE_VALIDATION`: Set to `true` to disable Pydantic validation of API responses. The client will return raw dictionary/list data instead of Pydantic models (default: `false`).
|
|
108
|
+
* `KARAKEEP_PYTHON_API_ENSURE_ASCII`: Set to `true` to escape non-ASCII characters in the JSON output (default: `false`, which means Unicode characters are kept).
|
|
105
109
|
|
|
106
110
|
### Command Line Interface (CLI)
|
|
107
111
|
|
|
@@ -139,6 +143,9 @@ python -m karakeep_python_api get-all-lists | jq '.[0]'
|
|
|
139
143
|
# Create a new bookmark from a link (body provided as JSON string)
|
|
140
144
|
python -m karakeep_python_api create-a-new-bookmark --data '{"type": "link", "url": "https://example.com"}'
|
|
141
145
|
|
|
146
|
+
# Get all tags and ensure ASCII output (e.g., for compatibility with systems that don't handle Unicode well)
|
|
147
|
+
python -m karakeep_python_api --ascii get-all-tags
|
|
148
|
+
|
|
142
149
|
# Dump the raw OpenAPI spec used by the client
|
|
143
150
|
python -m karakeep_python_api --dump-openapi-specification
|
|
144
151
|
```
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
1
2
|
import inspect
|
|
2
3
|
import json
|
|
3
4
|
import sys
|
|
4
5
|
import os
|
|
5
6
|
import functools
|
|
7
|
+
import re # Import re module
|
|
6
8
|
import click
|
|
7
9
|
import traceback # Moved import to top
|
|
8
10
|
from typing import Any, List, Dict, Optional, Callable, Union, get_origin, get_args, Literal
|
|
@@ -79,6 +81,14 @@ shared_options = [
|
|
|
79
81
|
envvar="KARAKEEP_PYTHON_API_DISABLE_RESPONSE_VALIDATION",
|
|
80
82
|
help="Disable Pydantic validation of API responses (returns raw data).",
|
|
81
83
|
),
|
|
84
|
+
click.option(
|
|
85
|
+
"--ascii",
|
|
86
|
+
"ensure_ascii", # Use 'ensure_ascii' as the destination variable name
|
|
87
|
+
is_flag=True,
|
|
88
|
+
default=False, # Default is False, meaning ensure_ascii=False by default
|
|
89
|
+
envvar="KARAKEEP_PYTHON_API_ENSURE_ASCII",
|
|
90
|
+
help="Escape non-ASCII characters in the JSON output (default: keep Unicode).",
|
|
91
|
+
),
|
|
82
92
|
]
|
|
83
93
|
|
|
84
94
|
|
|
@@ -126,9 +136,9 @@ def print_openapi_spec(ctx, param, value):
|
|
|
126
136
|
is_eager=True, # Process this option before others
|
|
127
137
|
help="Dump the OpenAPI specification JSON to stdout and exit.",
|
|
128
138
|
)
|
|
129
|
-
@add_options(shared_options) # Apply shared options to the group
|
|
139
|
+
@add_options(shared_options) # Apply shared options to the group (ensure_ascii is now included)
|
|
130
140
|
@click.pass_context
|
|
131
|
-
def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation):
|
|
141
|
+
def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation, ensure_ascii):
|
|
132
142
|
"""
|
|
133
143
|
Karakeep Python API Command Line Interface.
|
|
134
144
|
|
|
@@ -162,6 +172,7 @@ def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation
|
|
|
162
172
|
ctx.obj["DISABLE_RESPONSE_VALIDATION"] = (
|
|
163
173
|
disable_response_validation # Store the flag
|
|
164
174
|
)
|
|
175
|
+
ctx.obj["ENSURE_ASCII"] = ensure_ascii # Store the ensure_ascii flag
|
|
165
176
|
|
|
166
177
|
# --- Configure Logger ---
|
|
167
178
|
log_level = "DEBUG" if verbose else "INFO"
|
|
@@ -198,6 +209,7 @@ def create_click_command(
|
|
|
198
209
|
verify_ssl = ctx.obj["VERIFY_SSL"]
|
|
199
210
|
verbose = ctx.obj["VERBOSE"]
|
|
200
211
|
disable_validation = ctx.obj["DISABLE_RESPONSE_VALIDATION"] # Retrieve flag
|
|
212
|
+
ensure_ascii_output = ctx.obj["ENSURE_ASCII"] # Retrieve ensure_ascii flag
|
|
201
213
|
|
|
202
214
|
if not api_key:
|
|
203
215
|
click.echo(
|
|
@@ -238,9 +250,32 @@ def create_click_command(
|
|
|
238
250
|
k: v for k, v in call_args.items() if k in valid_arg_names
|
|
239
251
|
}
|
|
240
252
|
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
|
|
253
|
+
# --- JSON Parsing for Dict/List Parameters ---
|
|
254
|
+
# Iterate through the expected parameters from the signature
|
|
255
|
+
for param_name, param_sig in signature.parameters.items():
|
|
256
|
+
if param_name in call_args:
|
|
257
|
+
param_value = call_args[param_name]
|
|
258
|
+
param_annotation = param_sig.annotation
|
|
259
|
+
origin = getattr(param_annotation, "__origin__", None)
|
|
260
|
+
|
|
261
|
+
# Check if the annotation is dict/list or typing.Dict/List
|
|
262
|
+
# and if the received value is a string (needs parsing)
|
|
263
|
+
if (
|
|
264
|
+
(param_annotation in (dict, list) or origin in (dict, list, Dict, List))
|
|
265
|
+
and isinstance(param_value, str)
|
|
266
|
+
):
|
|
267
|
+
try:
|
|
268
|
+
# Attempt to parse the JSON string
|
|
269
|
+
call_args[param_name] = json.loads(param_value)
|
|
270
|
+
logger.debug(f"Parsed JSON string for parameter '{param_name}'.")
|
|
271
|
+
except json.JSONDecodeError as json_err:
|
|
272
|
+
# Handle invalid JSON input from the user
|
|
273
|
+
click.echo(
|
|
274
|
+
f"Error: Invalid JSON provided for parameter '{param_name.replace('_', '-')}': {json_err}",
|
|
275
|
+
err=True,
|
|
276
|
+
)
|
|
277
|
+
click.echo(f"Provided value: {param_value}", err=True)
|
|
278
|
+
ctx.exit(1)
|
|
244
279
|
|
|
245
280
|
# Call the API method
|
|
246
281
|
try:
|
|
@@ -262,9 +297,10 @@ def create_click_command(
|
|
|
262
297
|
# Serialize and print the result
|
|
263
298
|
if result is not None:
|
|
264
299
|
output_data = serialize_output(result)
|
|
300
|
+
# Use ensure_ascii_output flag to control JSON encoding
|
|
265
301
|
click.echo(
|
|
266
|
-
json.dumps(output_data, indent=2)
|
|
267
|
-
)
|
|
302
|
+
json.dumps(output_data, indent=2, ensure_ascii=ensure_ascii_output)
|
|
303
|
+
)
|
|
268
304
|
else:
|
|
269
305
|
# Handle None result (e.g., 204 No Content) gracefully
|
|
270
306
|
# Verbose check is implicitly handled by logger level
|
|
@@ -302,35 +338,45 @@ def create_click_command(
|
|
|
302
338
|
click_params = []
|
|
303
339
|
# Use the docstring from the original method (captured by functools.update_wrapper)
|
|
304
340
|
docstring = api_method.__doc__ or f"Execute the {api_method_name} API operation."
|
|
341
|
+
docstring = dedent(docstring)
|
|
305
342
|
docstring_lines = docstring.split("\n")
|
|
306
|
-
help_text =
|
|
307
|
-
|
|
343
|
+
help_text = " ".join(docstring.split("\n\n")[0].splitlines()).strip() # First lines as short help
|
|
344
|
+
# Full docstring as help
|
|
345
|
+
full_help = docstring
|
|
346
|
+
|
|
347
|
+
# tweak the whitespaces in the full help:
|
|
348
|
+
# full_help = full_help.replace("\n", "\n\n").replace("\n ", " ")
|
|
349
|
+
full_help = full_help.replace("\n ", " ")
|
|
350
|
+
full_help = full_help.replace("\n", "\n\n")
|
|
308
351
|
|
|
309
352
|
# Extract parameter descriptions from the Args section of the docstring
|
|
310
353
|
param_descriptions = {}
|
|
311
354
|
in_args_section = False
|
|
312
355
|
args_section_lines = []
|
|
356
|
+
assert "Returns:" in docstring
|
|
357
|
+
assert "Raises:" in docstring
|
|
313
358
|
for line in docstring_lines:
|
|
314
|
-
stripped_line = line.strip()
|
|
315
|
-
if stripped_line == "Args:":
|
|
316
|
-
in_args_section = True
|
|
359
|
+
stripped_line = line.strip()
|
|
360
|
+
if stripped_line == "Args:":
|
|
361
|
+
in_args_section = True
|
|
317
362
|
elif (
|
|
318
363
|
stripped_line == "Returns:" or stripped_line == "Raises:"
|
|
319
|
-
):
|
|
320
|
-
in_args_section = False # Stop capturing when Returns/Raises section starts
|
|
321
|
-
elif in_args_section and stripped_line:
|
|
322
|
-
args_section_lines.append(stripped_line)
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
]
|
|
364
|
+
):
|
|
365
|
+
in_args_section = False # Stop capturing when Returns/Raises section starts
|
|
366
|
+
elif in_args_section and stripped_line:
|
|
367
|
+
args_section_lines.append(stripped_line)
|
|
368
|
+
# Use regex to capture 'param_name: description' structure, allowing leading whitespace
|
|
369
|
+
# Pattern: ^\s+ (parameter_name): \s* (description) $
|
|
370
|
+
match = re.match(r"^\s+([a-zA-Z_][a-zA-Z0-9_]*):\s+(.*)$", line)
|
|
371
|
+
if match and match.group(1) != "Example":
|
|
372
|
+
param_name = match.group(1)
|
|
373
|
+
description = match.group(2).strip()
|
|
374
|
+
param_descriptions[param_name] = description
|
|
375
|
+
logger.trace(f"Parsed docstring param: '{param_name}' -> '{description}'")
|
|
376
|
+
else:
|
|
377
|
+
param_descriptions[param_name] += " " + stripped_line
|
|
333
378
|
|
|
379
|
+
# Removed breakpoint() that was added for debugging
|
|
334
380
|
# Add parameters from signature to Click command
|
|
335
381
|
for param in params: # Use the filtered list from signature inspection
|
|
336
382
|
param_name_cli = param.name.replace("_", "-") # Use kebab-case for CLI options
|
|
@@ -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.3"
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
@@ -209,7 +209,7 @@ class KarakeepAPI:
|
|
|
209
209
|
self.strict_response_parsing = (
|
|
210
210
|
strict_response_parsing # Currently unused but kept
|
|
211
211
|
)
|
|
212
|
-
self.last_request_time: float =
|
|
212
|
+
self.last_request_time: float = time.monotonic() # Initialize timestamp for rate limiting
|
|
213
213
|
|
|
214
214
|
# --- Response Validation Setting ---
|
|
215
215
|
# Argument takes precedence over environment variable
|
|
@@ -463,7 +463,7 @@ class KarakeepAPI:
|
|
|
463
463
|
logger.warning(
|
|
464
464
|
f"Error encounterd during requests. Trial={trial}/{max_trial}. Retrying after a small wait.\nError: {e}"
|
|
465
465
|
)
|
|
466
|
-
time.sleep(trial *
|
|
466
|
+
time.sleep(trial * 3)
|
|
467
467
|
else:
|
|
468
468
|
raise
|
|
469
469
|
|
|
@@ -590,7 +590,7 @@ class KarakeepAPI:
|
|
|
590
590
|
) from e
|
|
591
591
|
|
|
592
592
|
@optional_typecheck
|
|
593
|
-
def _enforce_rate_limit(self, min_interval_sec: float =
|
|
593
|
+
def _enforce_rate_limit(self, min_interval_sec: float = 2.0) -> None:
|
|
594
594
|
"""
|
|
595
595
|
Ensures a minimum time interval between consecutive API calls.
|
|
596
596
|
|
|
@@ -855,6 +855,7 @@ class KarakeepAPI:
|
|
|
855
855
|
"""
|
|
856
856
|
Delete a bookmark by its ID. Corresponds to DELETE /bookmarks/{bookmarkId}.
|
|
857
857
|
|
|
858
|
+
Args:
|
|
858
859
|
bookmark_id: The ID (string) of the bookmark to delete.
|
|
859
860
|
|
|
860
861
|
Returns:
|
|
@@ -901,6 +902,7 @@ class KarakeepAPI:
|
|
|
901
902
|
Summarize a bookmark by its ID. Corresponds to POST /bookmarks/{bookmarkId}/summarize.
|
|
902
903
|
This triggers the summarization process and returns the updated bookmark record (partially).
|
|
903
904
|
|
|
905
|
+
Args:
|
|
904
906
|
bookmark_id: The ID (string) of the bookmark to summarize.
|
|
905
907
|
|
|
906
908
|
Returns:
|
|
@@ -924,6 +926,7 @@ class KarakeepAPI:
|
|
|
924
926
|
"""
|
|
925
927
|
Attach one or more tags to a bookmark. Corresponds to POST /bookmarks/{bookmarkId}/tags.
|
|
926
928
|
|
|
929
|
+
Args:
|
|
927
930
|
bookmark_id: The ID (string) of the bookmark.
|
|
928
931
|
tags_data: Dictionary specifying the tags to attach. Must contain a "tags" key
|
|
929
932
|
which is a list of objects, each having *either* "tagId" (string) *or* "tagName" (string).
|
|
@@ -950,6 +953,7 @@ class KarakeepAPI:
|
|
|
950
953
|
"""
|
|
951
954
|
Detach one or more tags from a bookmark. Corresponds to DELETE /bookmarks/{bookmarkId}/tags.
|
|
952
955
|
|
|
956
|
+
Args:
|
|
953
957
|
bookmark_id: The ID (string) of the bookmark.
|
|
954
958
|
tags_data: Dictionary specifying the tags to detach. Must contain a "tags" key
|
|
955
959
|
which is a list of objects, each having *either* "tagId" (string) *or* "tagName" (string).
|
|
@@ -976,6 +980,7 @@ class KarakeepAPI:
|
|
|
976
980
|
"""
|
|
977
981
|
Get all highlights associated with a specific bookmark. Corresponds to GET /bookmarks/{bookmarkId}/highlights.
|
|
978
982
|
|
|
983
|
+
Args:
|
|
979
984
|
bookmark_id: The ID (string) of the bookmark.
|
|
980
985
|
|
|
981
986
|
Returns:
|
|
@@ -1023,6 +1028,7 @@ class KarakeepAPI:
|
|
|
1023
1028
|
"""
|
|
1024
1029
|
Attach a new asset to a bookmark. Corresponds to POST /bookmarks/{bookmarkId}/assets.
|
|
1025
1030
|
|
|
1031
|
+
Args:
|
|
1026
1032
|
bookmark_id: The ID (string) of the bookmark.
|
|
1027
1033
|
asset_data: Dictionary specifying the asset to attach. Must contain "id" (string) and "assetType" (string enum).
|
|
1028
1034
|
Example: `{"id": "asset_id_string", "assetType": "screenshot"}`
|
|
@@ -1054,6 +1060,7 @@ class KarakeepAPI:
|
|
|
1054
1060
|
Replace an existing asset associated with a bookmark with a new one.
|
|
1055
1061
|
Corresponds to PUT /bookmarks/{bookmarkId}/assets/{assetId}.
|
|
1056
1062
|
|
|
1063
|
+
Args:
|
|
1057
1064
|
bookmark_id: The ID (string) of the bookmark.
|
|
1058
1065
|
asset_id: The ID (string) of the asset to be replaced.
|
|
1059
1066
|
new_asset_data: Dictionary specifying the new asset ID. Must contain "assetId" (string).
|
|
@@ -1074,6 +1081,7 @@ class KarakeepAPI:
|
|
|
1074
1081
|
"""
|
|
1075
1082
|
Detach an asset from a bookmark. Corresponds to DELETE /bookmarks/{bookmarkId}/assets/{assetId}.
|
|
1076
1083
|
|
|
1084
|
+
Args:
|
|
1077
1085
|
bookmark_id: The ID (string) of the bookmark.
|
|
1078
1086
|
asset_id: The ID (string) of the asset to detach.
|
|
1079
1087
|
|
|
@@ -1092,6 +1100,7 @@ class KarakeepAPI:
|
|
|
1092
1100
|
"""
|
|
1093
1101
|
Get all lists for the current user. Corresponds to GET /lists.
|
|
1094
1102
|
|
|
1103
|
+
Returns:
|
|
1095
1104
|
List[datatypes.ListModel]: A list of list objects.
|
|
1096
1105
|
If response validation is disabled, returns the raw API response (dict/list).
|
|
1097
1106
|
|
|
@@ -1135,6 +1144,7 @@ class KarakeepAPI:
|
|
|
1135
1144
|
"""
|
|
1136
1145
|
Create a new list (manual or smart). Corresponds to POST /lists.
|
|
1137
1146
|
|
|
1147
|
+
Args:
|
|
1138
1148
|
list_data: Dictionary containing the data for the new list. Requires "name" (string) and "icon" (string).
|
|
1139
1149
|
Optional fields include "description", "parentId", "type" ('manual' or 'smart'), "query".
|
|
1140
1150
|
See the OpenAPI spec for details. Example: `{"name": "My List", "icon": "📚"}`
|
|
@@ -1163,6 +1173,7 @@ class KarakeepAPI:
|
|
|
1163
1173
|
"""
|
|
1164
1174
|
Get a single list by its ID. Corresponds to GET /lists/{listId}.
|
|
1165
1175
|
|
|
1176
|
+
Args:
|
|
1166
1177
|
list_id: The ID (string) of the list to retrieve.
|
|
1167
1178
|
|
|
1168
1179
|
Returns:
|
|
@@ -1188,6 +1199,7 @@ class KarakeepAPI:
|
|
|
1188
1199
|
"""
|
|
1189
1200
|
Delete a list by its ID. Corresponds to DELETE /lists/{listId}.
|
|
1190
1201
|
|
|
1202
|
+
Args:
|
|
1191
1203
|
list_id: The ID (string) of the list to delete.
|
|
1192
1204
|
|
|
1193
1205
|
Returns:
|
|
@@ -1208,6 +1220,7 @@ class KarakeepAPI:
|
|
|
1208
1220
|
Update a list by its ID. Corresponds to PATCH /lists/{listId}.
|
|
1209
1221
|
Allows updating fields like "name", "description", "icon", "parentId", "query".
|
|
1210
1222
|
|
|
1223
|
+
Args:
|
|
1211
1224
|
list_id: The ID (string) of the list to update.
|
|
1212
1225
|
update_data: A dictionary containing the fields to update (e.g., `{"name": "new name"}`).
|
|
1213
1226
|
See the OpenAPI spec for allowed fields.
|
|
@@ -1241,6 +1254,7 @@ class KarakeepAPI:
|
|
|
1241
1254
|
"""
|
|
1242
1255
|
Get the bookmarks contained within a specific list. Corresponds to GET /lists/{listId}/bookmarks.
|
|
1243
1256
|
|
|
1257
|
+
Args:
|
|
1244
1258
|
list_id: The ID (string) of the list.
|
|
1245
1259
|
limit: Maximum number of bookmarks to return (optional).
|
|
1246
1260
|
cursor: Pagination cursor for the next page (optional).
|
|
@@ -1274,6 +1288,7 @@ class KarakeepAPI:
|
|
|
1274
1288
|
"""
|
|
1275
1289
|
Add a bookmark to a specific list. Corresponds to PUT /lists/{listId}/bookmarks/{bookmarkId}.
|
|
1276
1290
|
|
|
1291
|
+
Args:
|
|
1277
1292
|
list_id: The ID (string) of the list.
|
|
1278
1293
|
bookmark_id: The ID (string) of the bookmark to add.
|
|
1279
1294
|
|
|
@@ -1292,6 +1307,7 @@ class KarakeepAPI:
|
|
|
1292
1307
|
"""
|
|
1293
1308
|
Remove a bookmark from a specific list. Corresponds to DELETE /lists/{listId}/bookmarks/{bookmarkId}.
|
|
1294
1309
|
|
|
1310
|
+
Args:
|
|
1295
1311
|
list_id: The ID (string) of the list.
|
|
1296
1312
|
bookmark_id: The ID (string) of the bookmark to remove.
|
|
1297
1313
|
|
|
@@ -1310,6 +1326,7 @@ class KarakeepAPI:
|
|
|
1310
1326
|
"""
|
|
1311
1327
|
Get all tags for the current user. Corresponds to GET /tags.
|
|
1312
1328
|
|
|
1329
|
+
Returns:
|
|
1313
1330
|
List[datatypes.Tag1]: A list of tag objects, including bookmark counts.
|
|
1314
1331
|
If response validation is disabled, returns the raw API response (dict/list).
|
|
1315
1332
|
|
|
@@ -1351,6 +1368,7 @@ class KarakeepAPI:
|
|
|
1351
1368
|
"""
|
|
1352
1369
|
Get a single tag by its ID. Corresponds to GET /tags/{tagId}.
|
|
1353
1370
|
|
|
1371
|
+
Args:
|
|
1354
1372
|
tag_id: The ID (string) of the tag to retrieve.
|
|
1355
1373
|
|
|
1356
1374
|
Returns:
|
|
@@ -1376,6 +1394,7 @@ class KarakeepAPI:
|
|
|
1376
1394
|
"""
|
|
1377
1395
|
Delete a tag by its ID. Corresponds to DELETE /tags/{tagId}.
|
|
1378
1396
|
|
|
1397
|
+
Args:
|
|
1379
1398
|
tag_id: The ID (string) of the tag to delete.
|
|
1380
1399
|
|
|
1381
1400
|
Returns:
|
|
@@ -1396,6 +1415,7 @@ class KarakeepAPI:
|
|
|
1396
1415
|
Update a tag by its ID. Currently only supports updating the "name".
|
|
1397
1416
|
Corresponds to PATCH /tags/{tagId}.
|
|
1398
1417
|
|
|
1418
|
+
Args:
|
|
1399
1419
|
tag_id: The ID (string) of the tag to update.
|
|
1400
1420
|
update_data: A dictionary containing the fields to update. Must include "name" (string).
|
|
1401
1421
|
Example: `{"name": "new tag name"}`
|
|
@@ -1415,6 +1435,11 @@ class KarakeepAPI:
|
|
|
1415
1435
|
logger.debug("Skipping response validation as requested.")
|
|
1416
1436
|
return response_data
|
|
1417
1437
|
else:
|
|
1438
|
+
# As of version 0.24.1 of karakeep: we do not check the correct
|
|
1439
|
+
# validation type because there is an error on the
|
|
1440
|
+
# server side: https://github.com/karakeep-app/karakeep/issues/1365
|
|
1441
|
+
return response_data
|
|
1442
|
+
|
|
1418
1443
|
# Response should match Tag1 schema
|
|
1419
1444
|
return datatypes.Tag1.model_validate(response_data)
|
|
1420
1445
|
|
|
@@ -1429,6 +1454,7 @@ class KarakeepAPI:
|
|
|
1429
1454
|
"""
|
|
1430
1455
|
Get the bookmarks associated with a specific tag. Corresponds to GET /tags/{tagId}/bookmarks.
|
|
1431
1456
|
|
|
1457
|
+
Args:
|
|
1432
1458
|
tag_id: The ID (string) of the tag.
|
|
1433
1459
|
limit: Maximum number of bookmarks to return (optional).
|
|
1434
1460
|
cursor: Pagination cursor for the next page (optional).
|
|
@@ -1493,6 +1519,7 @@ class KarakeepAPI:
|
|
|
1493
1519
|
"""
|
|
1494
1520
|
Create a new highlight on a bookmark. Corresponds to POST /highlights.
|
|
1495
1521
|
|
|
1522
|
+
Args:
|
|
1496
1523
|
highlight_data: Dictionary containing the data for the new highlight. Requires "bookmarkId" (string),
|
|
1497
1524
|
"startOffset" (number), "endOffset" (number). Optional fields include "color", "text", "note".
|
|
1498
1525
|
See the OpenAPI spec for details. Example: `{"bookmarkId": "...", "startOffset": 10, "endOffset": 25}`
|
|
@@ -1521,6 +1548,7 @@ class KarakeepAPI:
|
|
|
1521
1548
|
"""
|
|
1522
1549
|
Get a single highlight by its ID. Corresponds to GET /highlights/{highlightId}.
|
|
1523
1550
|
|
|
1551
|
+
Args:
|
|
1524
1552
|
highlight_id: The ID (string) of the highlight to retrieve.
|
|
1525
1553
|
|
|
1526
1554
|
Returns:
|
|
@@ -1549,6 +1577,7 @@ class KarakeepAPI:
|
|
|
1549
1577
|
Delete a highlight by its ID. Corresponds to DELETE /highlights/{highlightId}.
|
|
1550
1578
|
Note: Unlike most DELETE endpoints, this returns the deleted highlight object on success (status 200).
|
|
1551
1579
|
|
|
1580
|
+
Args:
|
|
1552
1581
|
highlight_id: The ID (string) of the highlight to delete.
|
|
1553
1582
|
|
|
1554
1583
|
Returns:
|
|
@@ -1577,6 +1606,7 @@ class KarakeepAPI:
|
|
|
1577
1606
|
Update a highlight by its ID. Currently only supports updating the "color".
|
|
1578
1607
|
Corresponds to PATCH /highlights/{highlightId}.
|
|
1579
1608
|
|
|
1609
|
+
Args:
|
|
1580
1610
|
highlight_id: The ID (string) of the highlight to update.
|
|
1581
1611
|
update_data: A dictionary containing the fields to update. Must include "color" (string enum).
|
|
1582
1612
|
See `datatypes.Color` enum. Example: `{"color": "red"}`
|
|
@@ -1604,6 +1634,7 @@ class KarakeepAPI:
|
|
|
1604
1634
|
"""
|
|
1605
1635
|
Get information about the current authenticated user. Corresponds to GET /users/me.
|
|
1606
1636
|
|
|
1637
|
+
Returns:
|
|
1607
1638
|
dict: A dictionary containing user information ('id', 'name', 'email').
|
|
1608
1639
|
Validation is not performed on this response type by default.
|
|
1609
1640
|
|
|
@@ -1619,6 +1650,7 @@ class KarakeepAPI:
|
|
|
1619
1650
|
"""
|
|
1620
1651
|
Get statistics about the current authenticated user's data. Corresponds to GET /users/me/stats.
|
|
1621
1652
|
|
|
1653
|
+
Returns:
|
|
1622
1654
|
dict: A dictionary containing user statistics ('numBookmarks', 'numFavorites', 'numArchived', etc.).
|
|
1623
1655
|
Validation is not performed on this response type by default.
|
|
1624
1656
|
|
{karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3/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.3
|
|
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
|
|
@@ -72,43 +72,46 @@ The development process involved:
|
|
|
72
72
|
|
|
73
73
|
## API Method Coverage
|
|
74
74
|
|
|
75
|
-
The following table lists the public methods available in the `KarakeepAPI` class
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
| `
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
75
|
+
The following table lists the public methods available in the `KarakeepAPI` class.
|
|
76
|
+
* The "Pytest" column indicates whether the Python library method is covered by the automated test suite (`tests/test_karakeep_api.py`).
|
|
77
|
+
* The "CLI" column indicates whether the corresponding CLI command for that method is tested within the Pytest suite (typically via `subprocess`).
|
|
78
|
+
Methods or CLI commands marked with ❌ should be used with caution as their behavior has not been automatically verified within the test suite.
|
|
79
|
+
|
|
80
|
+
| Method Name | Pytest | CLI | Remarks |
|
|
81
|
+
| -------------------------------- | :----: | :--: | -------------------------------------------- |
|
|
82
|
+
| `get_all_bookmarks` | ✅ | ✅ | Tested with pagination. |
|
|
83
|
+
| `create_a_new_bookmark` | ✅ | ❌ | Pytest for `type="link"` via fixture. CLI not directly tested. |
|
|
84
|
+
| `search_bookmarks` | ✅ | ✅ | Seems to be nondeterministic and fails if using more than 3 words |
|
|
85
|
+
| `get_a_single_bookmark` | ✅ | ❌ | |
|
|
86
|
+
| `delete_a_bookmark` | ✅ | ❌ | |
|
|
87
|
+
| `update_a_bookmark` | ✅ | ✅ | Tested for title updates. |
|
|
88
|
+
| `summarize_a_bookmark` | ❌ | ❌ | |
|
|
89
|
+
| `attach_tags_to_a_bookmark` | ✅ | ❌ | |
|
|
90
|
+
| `detach_tags_from_a_bookmark` | ✅ | ❌ | |
|
|
91
|
+
| `get_highlights_of_a_bookmark` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
92
|
+
| `attach_asset` | ❌ | ❌ | |
|
|
93
|
+
| `replace_asset` | ❌ | ❌ | |
|
|
94
|
+
| `detach_asset` | ❌ | ❌ | |
|
|
95
|
+
| `get_all_lists` | ✅ | ✅ | |
|
|
96
|
+
| `create_a_new_list` | ✅ | ❌ | |
|
|
97
|
+
| `get_a_single_list` | ✅ | ❌ | |
|
|
98
|
+
| `delete_a_list` | ✅ | ❌ | |
|
|
99
|
+
| `update_a_list` | ❌ | ❌ | |
|
|
100
|
+
| `get_a_bookmarks_in_a_list` | ❌ | ❌ | |
|
|
101
|
+
| `add_a_bookmark_to_a_list` | ❌ | ❌ | |
|
|
102
|
+
| `remove_a_bookmark_from_a_list` | ❌ | ❌ | |
|
|
103
|
+
| `get_all_tags` | ✅ | ✅ | |
|
|
104
|
+
| `get_a_single_tag` | ✅ | ❌ | |
|
|
105
|
+
| `delete_a_tag` | ✅ | ❌ | |
|
|
106
|
+
| `update_a_tag` | ✅ | ❌ | No output validation due to [server bug](https://github.com/karakeep-app/karakeep/issues/1365). |
|
|
107
|
+
| `get_a_bookmarks_with_the_tag` | ❌ | ❌ | |
|
|
108
|
+
| `get_all_highlights` | ✅ | ✅ | Tested with pagination. |
|
|
109
|
+
| `create_a_new_highlight` | ❌ | ❌ | |
|
|
110
|
+
| `get_a_single_highlight` | ❌ | ❌ | |
|
|
111
|
+
| `delete_a_highlight` | ❌ | ❌ | Works from the CLI; not yet added to Pytest. |
|
|
112
|
+
| `update_a_highlight` | ❌ | ❌ | |
|
|
113
|
+
| `get_current_user_info` | ✅ | ❌ | Pytest: Tested indirectly during client init. CLI not directly tested. |
|
|
114
|
+
| `get_current_user_stats` | ✅ | ✅ | |
|
|
112
115
|
|
|
113
116
|
## Installation
|
|
114
117
|
|
|
@@ -137,6 +140,7 @@ The client can be configured using the following environment variables:
|
|
|
137
140
|
* `KARAKEEP_PYTHON_API_VERIFY_SSL`: Set to `false` to disable SSL certificate verification (default: `true`).
|
|
138
141
|
* `KARAKEEP_PYTHON_API_VERBOSE`: Set to `true` to enable verbose debug logging for the client and CLI (default: `false`).
|
|
139
142
|
* `KARAKEEP_PYTHON_API_DISABLE_RESPONSE_VALIDATION`: Set to `true` to disable Pydantic validation of API responses. The client will return raw dictionary/list data instead of Pydantic models (default: `false`).
|
|
143
|
+
* `KARAKEEP_PYTHON_API_ENSURE_ASCII`: Set to `true` to escape non-ASCII characters in the JSON output (default: `false`, which means Unicode characters are kept).
|
|
140
144
|
|
|
141
145
|
### Command Line Interface (CLI)
|
|
142
146
|
|
|
@@ -174,6 +178,9 @@ python -m karakeep_python_api get-all-lists | jq '.[0]'
|
|
|
174
178
|
# Create a new bookmark from a link (body provided as JSON string)
|
|
175
179
|
python -m karakeep_python_api create-a-new-bookmark --data '{"type": "link", "url": "https://example.com"}'
|
|
176
180
|
|
|
181
|
+
# Get all tags and ensure ASCII output (e.g., for compatibility with systems that don't handle Unicode well)
|
|
182
|
+
python -m karakeep_python_api --ascii get-all-tags
|
|
183
|
+
|
|
177
184
|
# Dump the raw OpenAPI spec used by the client
|
|
178
185
|
python -m karakeep_python_api --dump-openapi-specification
|
|
179
186
|
```
|
|
@@ -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.3",
|
|
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",
|
|
@@ -4,6 +4,7 @@ import subprocess
|
|
|
4
4
|
import random
|
|
5
5
|
import string
|
|
6
6
|
import time
|
|
7
|
+
import json # Added for CLI test payload generation
|
|
7
8
|
|
|
8
9
|
# Import API, errors, and datatypes from the main package
|
|
9
10
|
from karakeep_python_api import KarakeepAPI, APIError, AuthenticationError, datatypes
|
|
@@ -339,55 +340,66 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
339
340
|
print("\nSkipping deletion because list creation failed or ID was not obtained.")
|
|
340
341
|
|
|
341
342
|
|
|
342
|
-
def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI):
|
|
343
|
-
"""
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
|
|
344
|
+
"""
|
|
345
|
+
Test verifying a created bookmark (via fixture) and searching for it.
|
|
346
|
+
The fixture handles creation and deletion.
|
|
347
|
+
"""
|
|
348
|
+
created_bookmark_id = managed_bookmark.id
|
|
349
|
+
test_url = managed_bookmark.content.url # Get URL from fixture
|
|
350
|
+
original_title = managed_bookmark.title # Get title from fixture
|
|
351
|
+
|
|
346
352
|
try:
|
|
347
|
-
# 1.
|
|
348
|
-
print(f"\
|
|
353
|
+
# 1. Bookmark is already created by the 'managed_bookmark' fixture.
|
|
354
|
+
print(f"\nUsing managed bookmark ID: {created_bookmark_id}, URL: '{test_url}', Title: '{original_title}'")
|
|
349
355
|
|
|
350
|
-
# 2.
|
|
351
|
-
# Call the method with keyword arguments matching its signature
|
|
352
|
-
created_bookmark = karakeep_client.create_a_new_bookmark(
|
|
353
|
-
type="link", url=test_url
|
|
354
|
-
)
|
|
355
|
-
assert isinstance(
|
|
356
|
-
created_bookmark, datatypes.Bookmark
|
|
357
|
-
), "Response should be a Bookmark model"
|
|
358
|
-
assert created_bookmark.content.url == test_url, "Created bookmark URL should match"
|
|
359
|
-
assert created_bookmark.id, "Created bookmark must have an ID"
|
|
360
|
-
created_bookmark_id = created_bookmark.id
|
|
361
|
-
print(f"✓ Successfully created bookmark with ID: {created_bookmark_id}")
|
|
362
|
-
|
|
363
|
-
# 3. Verify the bookmark exists by getting it directly
|
|
356
|
+
# 2. Verify the bookmark exists by getting it directly
|
|
364
357
|
retrieved_bookmark = karakeep_client.get_a_single_bookmark(
|
|
365
358
|
bookmark_id=created_bookmark_id
|
|
366
359
|
)
|
|
367
360
|
assert isinstance(retrieved_bookmark, datatypes.Bookmark)
|
|
368
361
|
assert retrieved_bookmark.id == created_bookmark_id
|
|
369
362
|
assert retrieved_bookmark.content.url == test_url
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
print(f"
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
363
|
+
assert retrieved_bookmark.title == original_title
|
|
364
|
+
print(f"✓ Successfully retrieved the managed bookmark by ID.")
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# 3. Search for the created bookmark
|
|
368
|
+
# Use a search query that is likely to match the fixture's title
|
|
369
|
+
# The fixture title is "Managed Fixture Bookmark {timestamp}-{random_suffix}"
|
|
370
|
+
# A simple search for "Managed Fixture Bookmark" should work.
|
|
371
|
+
# If the title is very dynamic, searching by URL might be more robust if supported,
|
|
372
|
+
# or by a known part of the title.
|
|
373
|
+
|
|
374
|
+
# waiting a bit for the indexation just in case
|
|
375
|
+
time.sleep(30)
|
|
376
|
+
|
|
377
|
+
search_query_component = original_title.split(" ")[0] + " " + original_title.split(" ")[1]
|
|
378
|
+
print(f"\nAttempting to search for bookmark with query based on title: '{search_query_component}'. Retrying multiple times because search is nondeterministic.")
|
|
379
|
+
|
|
380
|
+
for trial in range(5):
|
|
381
|
+
search_results = karakeep_client.search_bookmarks(q=search_query_component, limit=100, include_content=False)
|
|
382
|
+
assert isinstance(
|
|
383
|
+
search_results, datatypes.PaginatedBookmarks
|
|
384
|
+
), "Search response should be PaginatedBookmarks model"
|
|
385
|
+
assert isinstance(
|
|
386
|
+
search_results.bookmarks, list
|
|
387
|
+
), "Search results bookmarks attribute should be a list"
|
|
388
|
+
|
|
389
|
+
titles_in_search = [b.title for b in search_results.bookmarks]
|
|
390
|
+
found_in_search = any(b.id == created_bookmark_id for b in search_results.bookmarks)
|
|
391
|
+
if found_in_search:
|
|
392
|
+
break
|
|
393
|
+
else:
|
|
394
|
+
time.sleep(3)
|
|
395
|
+
assert found_in_search, \
|
|
396
|
+
f"Managed bookmark {created_bookmark_id} (Title: '{original_title}') not found in {trial} different search results for '{search_query_component}'. Titles were: '{titles_in_search}'."
|
|
397
|
+
print(f"✓ Found managed bookmark in search results for '{search_query_component}'.")
|
|
398
|
+
|
|
399
|
+
# 4. Test CLI search equivalent
|
|
400
|
+
print(f"\n Running CLI equivalent: search-bookmarks --q '{search_query_component}' --limit 10 --include-content false")
|
|
389
401
|
try:
|
|
390
|
-
cli_search_command = f"python -m karakeep_python_api search-bookmarks --q '{
|
|
402
|
+
cli_search_command = f"python -m karakeep_python_api search-bookmarks --q '{search_query_component}' --limit 10 --include-content false"
|
|
391
403
|
search_cli_output = subprocess.run(
|
|
392
404
|
cli_search_command,
|
|
393
405
|
shell=True,
|
|
@@ -395,93 +407,188 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI):
|
|
|
395
407
|
capture_output=True,
|
|
396
408
|
text=True,
|
|
397
409
|
)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
assert created_bookmark_id in search_cli_output.stdout, f"Created bookmark ID {created_bookmark_id} not found in CLI search output for '{search_query}'"
|
|
410
|
+
assert created_bookmark_id in search_cli_output.stdout, \
|
|
411
|
+
f"Managed bookmark ID {created_bookmark_id} not found in CLI search output for '{search_query_component}'"
|
|
401
412
|
print("✓ CLI search command executed successfully and contained the bookmark ID.")
|
|
402
413
|
except subprocess.CalledProcessError as e:
|
|
403
414
|
print(f" CLI search command failed with exit code {e.returncode}")
|
|
404
415
|
print(f" Stdout: {e.stdout}")
|
|
405
416
|
print(f" Stderr: {e.stderr}")
|
|
406
|
-
pytest.fail(f"CLI command 'search-bookmarks --q {
|
|
417
|
+
pytest.fail(f"CLI command 'search-bookmarks --q \"{search_query_component}\"' failed: {e}")
|
|
407
418
|
except Exception as e:
|
|
408
419
|
pytest.fail(f"An unexpected error occurred running the CLI search command: {e}")
|
|
409
420
|
|
|
410
|
-
|
|
411
421
|
except (APIError, AuthenticationError) as e:
|
|
412
|
-
pytest.fail(f"API error during bookmark
|
|
422
|
+
pytest.fail(f"API error during bookmark verification/search: {e}")
|
|
413
423
|
except Exception as e:
|
|
414
424
|
pytest.fail(
|
|
415
|
-
f"An unexpected error occurred during bookmark
|
|
425
|
+
f"An unexpected error occurred during bookmark verification/search: {e}"
|
|
426
|
+
)
|
|
427
|
+
# No 'finally' block for deletion needed, as 'managed_bookmark' fixture handles it.
|
|
428
|
+
# The fixture also handles verification of deletion.
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def test_update_bookmark_title(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
|
|
432
|
+
"""Test updating a bookmark's title via API and CLI, using a managed bookmark."""
|
|
433
|
+
created_bookmark_id = managed_bookmark.id
|
|
434
|
+
original_title = managed_bookmark.title # Get the original title from the fixture
|
|
435
|
+
|
|
436
|
+
target_api_title = "this is a test title"
|
|
437
|
+
target_cli_title = "this is a test title (CLI)"
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
# The bookmark is already created by the 'managed_bookmark' fixture.
|
|
441
|
+
# We have its ID in created_bookmark_id and its original title.
|
|
442
|
+
print(f"\nUsing managed bookmark ID: {created_bookmark_id}, Original Title: '{original_title}'")
|
|
443
|
+
|
|
444
|
+
# 1. Update the bookmark's title using the API client
|
|
445
|
+
print(f"\nAttempting to update bookmark ID {created_bookmark_id} title to: '{target_api_title}' via API")
|
|
446
|
+
update_payload_api = {"title": target_api_title}
|
|
447
|
+
updated_bookmark_partial = karakeep_client.update_a_bookmark(
|
|
448
|
+
bookmark_id=created_bookmark_id, update_data=update_payload_api
|
|
449
|
+
)
|
|
450
|
+
assert isinstance(updated_bookmark_partial, dict), "Update response should be a dict"
|
|
451
|
+
assert updated_bookmark_partial.get("title") == target_api_title, \
|
|
452
|
+
f"Partial response title '{updated_bookmark_partial.get('title')}' does not match target API title '{target_api_title}'"
|
|
453
|
+
print(f"✓ API call to update_a_bookmark successful. Partial response title: '{updated_bookmark_partial.get('title')}'")
|
|
454
|
+
|
|
455
|
+
# 2. Verify the API update by fetching the bookmark again
|
|
456
|
+
print(f"\nFetching bookmark ID {created_bookmark_id} to verify API title update.")
|
|
457
|
+
retrieved_bookmark_after_api_update = karakeep_client.get_a_single_bookmark(
|
|
458
|
+
bookmark_id=created_bookmark_id
|
|
459
|
+
)
|
|
460
|
+
assert isinstance(retrieved_bookmark_after_api_update, datatypes.Bookmark)
|
|
461
|
+
assert retrieved_bookmark_after_api_update.title == target_api_title, \
|
|
462
|
+
f"Retrieved bookmark title '{retrieved_bookmark_after_api_update.title}' does not match expected API-updated title '{target_api_title}'"
|
|
463
|
+
print(f"✓ Successfully verified bookmark title updated by API to: '{retrieved_bookmark_after_api_update.title}'")
|
|
464
|
+
|
|
465
|
+
# 3. Test CLI equivalent for updating the bookmark's title
|
|
466
|
+
print(f"\n Running CLI equivalent to update title to: '{target_cli_title}'")
|
|
467
|
+
cli_update_payload_json = json.dumps({"title": target_cli_title})
|
|
468
|
+
# Ensure the JSON string is properly quoted for the shell command
|
|
469
|
+
cli_update_command = f"python -m karakeep_python_api update-a-bookmark --bookmark-id {created_bookmark_id} --update-data '{cli_update_payload_json}'"
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
subprocess.run(
|
|
473
|
+
cli_update_command,
|
|
474
|
+
shell=True,
|
|
475
|
+
check=True,
|
|
476
|
+
capture_output=True,
|
|
477
|
+
text=True,
|
|
478
|
+
)
|
|
479
|
+
print("✓ CLI update command executed successfully.")
|
|
480
|
+
|
|
481
|
+
# 4. Verify CLI update by fetching the bookmark again
|
|
482
|
+
print(f"\nFetching bookmark ID {created_bookmark_id} to verify CLI title update.")
|
|
483
|
+
retrieved_bookmark_after_cli_update = karakeep_client.get_a_single_bookmark(
|
|
484
|
+
bookmark_id=created_bookmark_id
|
|
485
|
+
)
|
|
486
|
+
assert isinstance(retrieved_bookmark_after_cli_update, datatypes.Bookmark)
|
|
487
|
+
assert retrieved_bookmark_after_cli_update.title == target_cli_title, \
|
|
488
|
+
f"Retrieved bookmark title '{retrieved_bookmark_after_cli_update.title}' after CLI update does not match expected '{target_cli_title}'"
|
|
489
|
+
print(f"✓ Successfully verified bookmark title updated by CLI to: '{retrieved_bookmark_after_cli_update.title}'")
|
|
490
|
+
|
|
491
|
+
except subprocess.CalledProcessError as e:
|
|
492
|
+
print(f" CLI update command failed with exit code {e.returncode}")
|
|
493
|
+
print(f" Command: {cli_update_command}")
|
|
494
|
+
print(f" Stdout: {e.stdout}")
|
|
495
|
+
print(f" Stderr: {e.stderr}")
|
|
496
|
+
pytest.fail(f"CLI command for update-a-bookmark failed: {e}")
|
|
497
|
+
except Exception as e:
|
|
498
|
+
pytest.fail(f"An unexpected error occurred running the CLI update command: {e}")
|
|
499
|
+
|
|
500
|
+
except (APIError, AuthenticationError) as e:
|
|
501
|
+
pytest.fail(f"API error during bookmark title update test: {e}")
|
|
502
|
+
except Exception as e:
|
|
503
|
+
pytest.fail(f"An unexpected error occurred during bookmark title update test: {e}")
|
|
504
|
+
# No finally block needed for deletion, as 'managed_bookmark' fixture handles it.
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
|
|
508
|
+
"""
|
|
509
|
+
Test attaching a tag to a bookmark, updating the tag, detaching it, and deleting it.
|
|
510
|
+
Uses the managed_bookmark fixture.
|
|
511
|
+
"""
|
|
512
|
+
bookmark_id = managed_bookmark.id
|
|
513
|
+
timestamp = int(time.time())
|
|
514
|
+
random_chars = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
|
|
515
|
+
initial_tag_name = f"test-tag-{timestamp}-{random_chars}"
|
|
516
|
+
updated_tag_name = f"updated-tag-{timestamp}-{random_chars}"
|
|
517
|
+
tag_id_to_manage = None
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
# 1. Attach a new tag by name to the bookmark
|
|
521
|
+
print(f"\nAttempting to attach tag '{initial_tag_name}' to bookmark {bookmark_id}")
|
|
522
|
+
attach_payload = {"tags": [{"tagName": initial_tag_name}]}
|
|
523
|
+
attach_response = karakeep_client.attach_tags_to_a_bookmark(
|
|
524
|
+
bookmark_id=bookmark_id, tags_data=attach_payload
|
|
525
|
+
)
|
|
526
|
+
assert "attached" in attach_response and len(attach_response["attached"]) == 1, \
|
|
527
|
+
"Failed to attach tag or response format incorrect"
|
|
528
|
+
tag_id_to_manage = attach_response["attached"][0]
|
|
529
|
+
assert isinstance(tag_id_to_manage, str), "Attached tag ID should be a string"
|
|
530
|
+
print(f"✓ Tag '{initial_tag_name}' attached with ID: {tag_id_to_manage}")
|
|
531
|
+
|
|
532
|
+
# 2. Update the tag's name
|
|
533
|
+
print(f"\nAttempting to update tag {tag_id_to_manage} to name '{updated_tag_name}'")
|
|
534
|
+
update_payload = {"name": updated_tag_name}
|
|
535
|
+
updated_tag = karakeep_client.update_a_tag(
|
|
536
|
+
tag_id=tag_id_to_manage, update_data=update_payload
|
|
537
|
+
)
|
|
538
|
+
# Do not check the type because karakeep 0.24.1 has a server side bug
|
|
539
|
+
# assert isinstance(updated_tag, datatypes.Tag1), "Update tag response should be Tag1 model"
|
|
540
|
+
# assert updated_tag.name == updated_tag_name, "Tag name was not updated as expected"
|
|
541
|
+
# print(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag.name}'")
|
|
542
|
+
assert updated_tag["name"] == updated_tag_name, "Tag name was not updated as expected"
|
|
543
|
+
print(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag['name']}'")
|
|
544
|
+
|
|
545
|
+
# 3. Verify tag update by getting it directly
|
|
546
|
+
print(f"\nFetching tag {tag_id_to_manage} to verify its name is '{updated_tag_name}'")
|
|
547
|
+
retrieved_tag = karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
|
|
548
|
+
assert isinstance(retrieved_tag, datatypes.Tag1), "Get single tag response should be Tag1 model"
|
|
549
|
+
assert retrieved_tag.name == updated_tag_name, "Retrieved tag name does not match updated name"
|
|
550
|
+
assert retrieved_tag.id == tag_id_to_manage, "Retrieved tag ID does not match"
|
|
551
|
+
print(f"✓ Verified tag {tag_id_to_manage} has name '{retrieved_tag.name}'")
|
|
552
|
+
|
|
553
|
+
# 4. Detach the tag from the bookmark
|
|
554
|
+
print(f"\nAttempting to detach tag {tag_id_to_manage} from bookmark {bookmark_id}")
|
|
555
|
+
detach_payload = {"tags": [{"tagId": tag_id_to_manage}]}
|
|
556
|
+
detach_response = karakeep_client.detach_tags_from_a_bookmark(
|
|
557
|
+
bookmark_id=bookmark_id, tags_data=detach_payload
|
|
416
558
|
)
|
|
559
|
+
assert "detached" in detach_response and tag_id_to_manage in detach_response["detached"], \
|
|
560
|
+
"Failed to detach tag or response format incorrect"
|
|
561
|
+
print(f"✓ Tag {tag_id_to_manage} detached from bookmark {bookmark_id}")
|
|
562
|
+
|
|
563
|
+
except (APIError, AuthenticationError) as e:
|
|
564
|
+
pytest.fail(f"API error during tag lifecycle test: {e}")
|
|
565
|
+
except Exception as e:
|
|
566
|
+
pytest.fail(f"An unexpected error occurred during tag lifecycle test: {e}")
|
|
417
567
|
finally:
|
|
418
|
-
# 5. Delete the
|
|
419
|
-
if
|
|
420
|
-
print(f"\nAttempting to delete
|
|
568
|
+
# 5. Delete the tag (ensure cleanup even if assertions fail mid-test)
|
|
569
|
+
if tag_id_to_manage:
|
|
570
|
+
print(f"\nAttempting to delete tag {tag_id_to_manage} (cleanup)")
|
|
421
571
|
try:
|
|
422
|
-
karakeep_client.
|
|
423
|
-
print(f"✓ Successfully deleted
|
|
572
|
+
karakeep_client.delete_a_tag(tag_id=tag_id_to_manage)
|
|
573
|
+
print(f"✓ Successfully deleted tag {tag_id_to_manage}")
|
|
424
574
|
|
|
425
|
-
# 6. Verify the
|
|
575
|
+
# 6. Verify the tag is gone by trying to get it (should fail with 404)
|
|
426
576
|
try:
|
|
427
|
-
karakeep_client.
|
|
428
|
-
bookmark_id=created_bookmark_id
|
|
429
|
-
)
|
|
577
|
+
karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
|
|
430
578
|
pytest.fail(
|
|
431
|
-
f"
|
|
579
|
+
f"Tag {tag_id_to_manage} should not exist after deletion, but get_a_single_tag succeeded."
|
|
432
580
|
)
|
|
433
581
|
except APIError as e:
|
|
434
|
-
assert
|
|
435
|
-
e.status_code
|
|
436
|
-
|
|
437
|
-
print(
|
|
438
|
-
f"✓ Confirmed bookmark {created_bookmark_id} is deleted via API (received 404)."
|
|
439
|
-
)
|
|
440
|
-
|
|
441
|
-
# 7. Attempt to delete the same bookmark via CLI (should ideally fail or do nothing)
|
|
442
|
-
print(f"\n Attempting CLI deletion for already deleted bookmark ID: {created_bookmark_id}")
|
|
443
|
-
try:
|
|
444
|
-
cli_delete_command = f"python -m karakeep_python_api delete-a-bookmark --bookmark-id {created_bookmark_id}"
|
|
445
|
-
subprocess.run(
|
|
446
|
-
cli_delete_command,
|
|
447
|
-
shell=True,
|
|
448
|
-
check=True, # Set to False if CLI errors on 404, True if it exits 0
|
|
449
|
-
capture_output=True,
|
|
450
|
-
text=True,
|
|
451
|
-
)
|
|
452
|
-
# If check=True and it succeeds, it means the CLI might not error on 404.
|
|
453
|
-
print(f" ✓ CLI delete command executed (check=True implies it might not error on 404).")
|
|
454
|
-
except subprocess.CalledProcessError as e:
|
|
455
|
-
# If check=True, this block means the CLI command failed, which *might* be expected if it errors on 404.
|
|
456
|
-
print(f" ✓ CLI delete command failed as expected (exit code {e.returncode}), likely because bookmark was already deleted.")
|
|
457
|
-
# Optional: Assert specific exit code if known
|
|
458
|
-
# assert e.returncode == EXPECTED_EXIT_CODE_FOR_NOT_FOUND
|
|
459
|
-
except Exception as cli_e:
|
|
460
|
-
pytest.fail(f"An unexpected error occurred running the CLI delete command: {cli_e}")
|
|
461
|
-
|
|
462
|
-
# 8. Verify again via API that the bookmark is still gone after CLI attempt
|
|
463
|
-
try:
|
|
464
|
-
karakeep_client.get_a_single_bookmark(bookmark_id=created_bookmark_id)
|
|
465
|
-
pytest.fail(
|
|
466
|
-
f"Bookmark {created_bookmark_id} should *still* not exist after CLI deletion attempt, but get_a_single_bookmark succeeded."
|
|
467
|
-
)
|
|
468
|
-
except APIError as e:
|
|
469
|
-
assert (
|
|
470
|
-
e.status_code == 404
|
|
471
|
-
), f"Expected 404 Not Found after CLI deletion attempt, but got status {e.status_code}"
|
|
472
|
-
print(
|
|
473
|
-
f"✓ Confirmed bookmark {created_bookmark_id} is *still* deleted after CLI attempt (received 404)."
|
|
474
|
-
)
|
|
475
|
-
|
|
582
|
+
assert e.status_code == 404, \
|
|
583
|
+
f"Expected 404 Not Found when getting deleted tag, but got status {e.status_code}"
|
|
584
|
+
print(f"✓ Confirmed tag {tag_id_to_manage} is deleted (received 404).")
|
|
476
585
|
except (APIError, AuthenticationError) as e:
|
|
477
|
-
#
|
|
478
|
-
|
|
586
|
+
# Log error during cleanup but don't let it mask original test failure
|
|
587
|
+
print(f" API error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
|
|
479
588
|
except Exception as e:
|
|
480
|
-
|
|
589
|
+
print(f" Unexpected error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
|
|
481
590
|
else:
|
|
482
|
-
print(
|
|
483
|
-
"\nSkipping deletion because bookmark creation failed or ID was not obtained."
|
|
484
|
-
)
|
|
591
|
+
print("\nSkipping tag deletion (cleanup) because tag_id was not obtained or test failed before creation.")
|
|
485
592
|
|
|
486
593
|
|
|
487
594
|
# --- Test User Info/Stats Endpoints ---
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{karakeep_python_api-0.1.1 → karakeep_python_api-0.1.3}/karakeep_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|