karakeep-python-api 0.1.3__tar.gz → 0.1.4__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.3/karakeep_python_api.egg-info → karakeep_python_api-0.1.4}/PKG-INFO +1 -1
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/__main__.py +0 -3
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/karakeep_api.py +1 -6
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4/karakeep_python_api.egg-info}/PKG-INFO +1 -1
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/setup.py +1 -1
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/tests/test_karakeep_api.py +113 -106
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/LICENSE +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/MANIFEST.in +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/README.md +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/__init__.py +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/datatypes.py +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/openapi_reference.json +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/entry_points.txt +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/requires.txt +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/top_level.txt +0 -0
- {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/setup.cfg +0 -0
|
@@ -35,7 +35,6 @@ def serialize_output(data: Any) -> Any:
|
|
|
35
35
|
) # Use Pydantic's built-in JSON serialization
|
|
36
36
|
elif isinstance(data, list):
|
|
37
37
|
return [serialize_output(item) for item in data]
|
|
38
|
-
# Removed dataclass handling as API uses Pydantic models primarily
|
|
39
38
|
elif isinstance(data, dict):
|
|
40
39
|
# Serialize dictionary values
|
|
41
40
|
return {k: serialize_output(v) for k, v in data.items()}
|
|
@@ -345,7 +344,6 @@ def create_click_command(
|
|
|
345
344
|
full_help = docstring
|
|
346
345
|
|
|
347
346
|
# tweak the whitespaces in the full help:
|
|
348
|
-
# full_help = full_help.replace("\n", "\n\n").replace("\n ", " ")
|
|
349
347
|
full_help = full_help.replace("\n ", " ")
|
|
350
348
|
full_help = full_help.replace("\n", "\n\n")
|
|
351
349
|
|
|
@@ -452,7 +450,6 @@ def create_click_command(
|
|
|
452
450
|
elif isinstance(click_type, click.Choice):
|
|
453
451
|
param_help += f" (Choices: {', '.join(click_type.choices)})"
|
|
454
452
|
|
|
455
|
-
# Standard parameter handling (no special '--data' mapping anymore)
|
|
456
453
|
click_required = is_required_in_sig and default_value is None and not is_flag
|
|
457
454
|
|
|
458
455
|
# Add the Click Option
|
|
@@ -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.4"
|
|
89
89
|
|
|
90
90
|
def __init__(
|
|
91
91
|
self,
|
|
@@ -94,7 +94,6 @@ class KarakeepAPI:
|
|
|
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
|
-
strict_response_parsing: bool = False, # Kept for potential future use
|
|
98
97
|
disable_response_validation: Optional[bool] = None,
|
|
99
98
|
):
|
|
100
99
|
"""
|
|
@@ -112,7 +111,6 @@ class KarakeepAPI:
|
|
|
112
111
|
Can be overridden with KARAKEEP_PYTHON_API_VERIFY_SSL environment variable (true/false).
|
|
113
112
|
verbose: Enable verbose logging (default: False).
|
|
114
113
|
Can be overridden with KARAKEEP_PYTHON_API_VERBOSE environment variable (true/false).
|
|
115
|
-
strict_response_parsing: (Currently unused) If True, raise an APIError when response parsing fails.
|
|
116
114
|
disable_response_validation: If True, skip Pydantic validation of API responses and return raw data.
|
|
117
115
|
Defaults to False. Can be overridden by setting the
|
|
118
116
|
KARAKEEP_PYTHON_API_DISABLE_RESPONSE_VALIDATION environment variable to "true".
|
|
@@ -206,9 +204,6 @@ class KarakeepAPI:
|
|
|
206
204
|
|
|
207
205
|
self.verify_ssl = verify_ssl
|
|
208
206
|
self.verbose = verbose
|
|
209
|
-
self.strict_response_parsing = (
|
|
210
|
-
strict_response_parsing # Currently unused but kept
|
|
211
|
-
)
|
|
212
207
|
self.last_request_time: float = time.monotonic() # Initialize timestamp for rate limiting
|
|
213
208
|
|
|
214
209
|
# --- Response Validation Setting ---
|
|
@@ -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.4",
|
|
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",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pytest
|
|
2
|
+
from loguru import logger
|
|
2
3
|
import os
|
|
3
4
|
import subprocess
|
|
4
5
|
import random
|
|
@@ -24,23 +25,23 @@ def test_get_all_bookmarks_paginated(karakeep_client: KarakeepAPI):
|
|
|
24
25
|
), "Response should be PaginatedBookmarks model"
|
|
25
26
|
assert isinstance(page1.bookmarks, list), "Bookmarks attribute should be a list"
|
|
26
27
|
assert len(page1.bookmarks) <= 2, "Should return at most 'limit' bookmarks"
|
|
27
|
-
|
|
28
|
+
logger.info(f"✓ Retrieved first page with {len(page1.bookmarks)} bookmarks.")
|
|
28
29
|
|
|
29
30
|
# If there's a next cursor, get the next page
|
|
30
31
|
if page1.nextCursor:
|
|
31
|
-
|
|
32
|
+
logger.info(f" Attempting to fetch next page with cursor: {page1.nextCursor}")
|
|
32
33
|
page2 = karakeep_client.get_all_bookmarks(limit=2, cursor=page1.nextCursor)
|
|
33
34
|
assert isinstance(page2, datatypes.PaginatedBookmarks)
|
|
34
35
|
assert isinstance(page2.bookmarks, list)
|
|
35
36
|
assert len(page2.bookmarks) <= 2
|
|
36
|
-
|
|
37
|
+
logger.info(f"✓ Retrieved second page with {len(page2.bookmarks)} bookmarks.")
|
|
37
38
|
# Ensure bookmarks are different from page 1 (simple check)
|
|
38
39
|
if page1.bookmarks and page2.bookmarks:
|
|
39
40
|
assert (
|
|
40
41
|
page1.bookmarks[0].id != page2.bookmarks[0].id
|
|
41
42
|
), "Bookmarks on page 1 and 2 should differ"
|
|
42
43
|
else:
|
|
43
|
-
|
|
44
|
+
logger.info(" No next cursor found, pagination test ends.")
|
|
44
45
|
|
|
45
46
|
except (APIError, AuthenticationError) as e:
|
|
46
47
|
pytest.fail(f"API error during paginated bookmark retrieval: {e}")
|
|
@@ -51,21 +52,21 @@ def test_get_all_bookmarks_paginated(karakeep_client: KarakeepAPI):
|
|
|
51
52
|
|
|
52
53
|
# --- Add CLI call ---
|
|
53
54
|
try:
|
|
54
|
-
|
|
55
|
+
logger.info("\n Running CLI equivalent: get-all-bookmarks --limit 2")
|
|
55
56
|
# Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
|
|
56
57
|
subprocess.run(
|
|
57
58
|
"python -m karakeep_python_api get-all-bookmarks --limit 2",
|
|
58
59
|
shell=True,
|
|
59
60
|
check=True,
|
|
60
|
-
capture_output=True, # Capture output to avoid
|
|
61
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
61
62
|
text=True,
|
|
62
63
|
)
|
|
63
|
-
|
|
64
|
+
logger.info("✓ CLI command executed successfully.")
|
|
64
65
|
except subprocess.CalledProcessError as e:
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
67
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
68
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
69
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
69
70
|
pytest.fail(f"CLI command 'get-all-bookmarks --limit 2' failed: {e}")
|
|
70
71
|
except Exception as e:
|
|
71
72
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
@@ -80,7 +81,7 @@ def test_get_all_lists(karakeep_client: KarakeepAPI):
|
|
|
80
81
|
assert all(
|
|
81
82
|
isinstance(item, datatypes.ListModel) for item in lists
|
|
82
83
|
), "All items should be ListModel instances"
|
|
83
|
-
|
|
84
|
+
logger.info(f"✓ Successfully retrieved {len(lists)} lists.")
|
|
84
85
|
except (APIError, AuthenticationError) as e:
|
|
85
86
|
pytest.fail(f"API error during list retrieval: {e}")
|
|
86
87
|
except Exception as e:
|
|
@@ -88,21 +89,21 @@ def test_get_all_lists(karakeep_client: KarakeepAPI):
|
|
|
88
89
|
|
|
89
90
|
# --- Add CLI call ---
|
|
90
91
|
try:
|
|
91
|
-
|
|
92
|
+
logger.info("\n Running CLI equivalent: get-all-lists")
|
|
92
93
|
# Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
|
|
93
94
|
subprocess.run(
|
|
94
95
|
"python -m karakeep_python_api get-all-lists",
|
|
95
96
|
shell=True,
|
|
96
97
|
check=True,
|
|
97
|
-
capture_output=True, # Capture output to avoid
|
|
98
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
98
99
|
text=True,
|
|
99
100
|
)
|
|
100
|
-
|
|
101
|
+
logger.info("✓ CLI command executed successfully.")
|
|
101
102
|
except subprocess.CalledProcessError as e:
|
|
102
|
-
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
104
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
105
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
106
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
106
107
|
pytest.fail(f"CLI command 'get-all-lists' failed: {e}")
|
|
107
108
|
except Exception as e:
|
|
108
109
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
@@ -117,7 +118,7 @@ def test_get_all_tags(karakeep_client: KarakeepAPI):
|
|
|
117
118
|
assert all(
|
|
118
119
|
isinstance(item, datatypes.Tag1) for item in tags
|
|
119
120
|
), "All items should be Tag1 instances"
|
|
120
|
-
|
|
121
|
+
logger.info(f"✓ Successfully retrieved {len(tags)} tags.")
|
|
121
122
|
except (APIError, AuthenticationError) as e:
|
|
122
123
|
pytest.fail(f"API error during tag retrieval: {e}")
|
|
123
124
|
except Exception as e:
|
|
@@ -125,21 +126,21 @@ def test_get_all_tags(karakeep_client: KarakeepAPI):
|
|
|
125
126
|
|
|
126
127
|
# --- Add CLI call ---
|
|
127
128
|
try:
|
|
128
|
-
|
|
129
|
+
logger.info("\n Running CLI equivalent: get-all-tags")
|
|
129
130
|
# Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
|
|
130
131
|
subprocess.run(
|
|
131
132
|
"python -m karakeep_python_api get-all-tags",
|
|
132
133
|
shell=True,
|
|
133
134
|
check=True,
|
|
134
|
-
capture_output=True, # Capture output to avoid
|
|
135
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
135
136
|
text=True,
|
|
136
137
|
)
|
|
137
|
-
|
|
138
|
+
logger.info("✓ CLI command executed successfully.")
|
|
138
139
|
except subprocess.CalledProcessError as e:
|
|
139
|
-
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
141
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
142
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
143
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
143
144
|
pytest.fail(f"CLI command 'get-all-tags' failed: {e}")
|
|
144
145
|
except Exception as e:
|
|
145
146
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
@@ -157,23 +158,23 @@ def test_get_all_highlights_paginated(karakeep_client: KarakeepAPI):
|
|
|
157
158
|
page1.highlights, list
|
|
158
159
|
), "Highlights attribute should be a list"
|
|
159
160
|
assert len(page1.highlights) <= 3, "Should return at most 'limit' highlights"
|
|
160
|
-
|
|
161
|
+
logger.info(f"✓ Retrieved first page with {len(page1.highlights)} highlights.")
|
|
161
162
|
|
|
162
163
|
# If there's a next cursor, get the next page
|
|
163
164
|
if page1.nextCursor:
|
|
164
|
-
|
|
165
|
+
logger.info(f" Attempting to fetch next page with cursor: {page1.nextCursor}")
|
|
165
166
|
page2 = karakeep_client.get_all_highlights(limit=3, cursor=page1.nextCursor)
|
|
166
167
|
assert isinstance(page2, datatypes.PaginatedHighlights)
|
|
167
168
|
assert isinstance(page2.highlights, list)
|
|
168
169
|
assert len(page2.highlights) <= 3
|
|
169
|
-
|
|
170
|
+
logger.info(f"✓ Retrieved second page with {len(page2.highlights)} highlights.")
|
|
170
171
|
# Ensure highlights are different from page 1 (simple check)
|
|
171
172
|
if page1.highlights and page2.highlights:
|
|
172
173
|
assert (
|
|
173
174
|
page1.highlights[0].id != page2.highlights[0].id
|
|
174
175
|
), "Highlights on page 1 and 2 should differ"
|
|
175
176
|
else:
|
|
176
|
-
|
|
177
|
+
logger.info(" No next cursor found, pagination test ends.")
|
|
177
178
|
|
|
178
179
|
except (APIError, AuthenticationError) as e:
|
|
179
180
|
pytest.fail(f"API error during paginated highlight retrieval: {e}")
|
|
@@ -184,21 +185,21 @@ def test_get_all_highlights_paginated(karakeep_client: KarakeepAPI):
|
|
|
184
185
|
|
|
185
186
|
# --- Add CLI call ---
|
|
186
187
|
try:
|
|
187
|
-
|
|
188
|
+
logger.info("\n Running CLI equivalent: get-all-highlights --limit 3")
|
|
188
189
|
# Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
|
|
189
190
|
subprocess.run(
|
|
190
191
|
"python -m karakeep_python_api get-all-highlights --limit 3",
|
|
191
192
|
shell=True,
|
|
192
193
|
check=True,
|
|
193
|
-
capture_output=True, # Capture output to avoid
|
|
194
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
194
195
|
text=True,
|
|
195
196
|
)
|
|
196
|
-
|
|
197
|
+
logger.info("✓ CLI command executed successfully.")
|
|
197
198
|
except subprocess.CalledProcessError as e:
|
|
198
|
-
|
|
199
|
-
#
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
200
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
201
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
202
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
202
203
|
pytest.fail(f"CLI command 'get-all-highlights --limit 3' failed: {e}")
|
|
203
204
|
except Exception as e:
|
|
204
205
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
@@ -217,7 +218,7 @@ def test_openapi_spec_accessible(karakeep_client: KarakeepAPI):
|
|
|
217
218
|
assert (
|
|
218
219
|
"openapi" in spec
|
|
219
220
|
), "openapi_spec should contain the 'openapi' version key"
|
|
220
|
-
|
|
221
|
+
logger.info(
|
|
221
222
|
f"✓ Successfully accessed openapi_spec attribute. Version: {spec.get('openapi', 'N/A')}"
|
|
222
223
|
)
|
|
223
224
|
except Exception as e:
|
|
@@ -226,21 +227,21 @@ def test_openapi_spec_accessible(karakeep_client: KarakeepAPI):
|
|
|
226
227
|
# --- Add CLI call ---
|
|
227
228
|
# The closest CLI equivalent is dumping the spec file content
|
|
228
229
|
try:
|
|
229
|
-
|
|
230
|
+
logger.info("\n Running CLI equivalent: --dump-openapi-specification")
|
|
230
231
|
# This command doesn't require API key or base URL
|
|
231
232
|
subprocess.run(
|
|
232
233
|
"python -m karakeep_python_api --dump-openapi-specification",
|
|
233
234
|
shell=True,
|
|
234
235
|
check=True,
|
|
235
|
-
capture_output=True, # Capture output to avoid
|
|
236
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
236
237
|
text=True,
|
|
237
238
|
)
|
|
238
|
-
|
|
239
|
+
logger.info("✓ CLI command executed successfully.")
|
|
239
240
|
except subprocess.CalledProcessError as e:
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
242
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
243
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
244
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
244
245
|
pytest.fail(f"CLI command '--dump-openapi-specification' failed: {e}")
|
|
245
246
|
except Exception as e:
|
|
246
247
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
@@ -259,12 +260,12 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
259
260
|
list_name = f"Test List {timestamp}-{random_suffix}"
|
|
260
261
|
list_icon = "🧪" # Test tube icon
|
|
261
262
|
|
|
262
|
-
|
|
263
|
+
logger.info(f"\nAttempting to create list: Name='{list_name}', Icon='{list_icon}'")
|
|
263
264
|
|
|
264
265
|
# 2. Get initial list count (optional, for comparison)
|
|
265
266
|
initial_lists = karakeep_client.get_all_lists()
|
|
266
267
|
initial_list_count = len(initial_lists)
|
|
267
|
-
|
|
268
|
+
logger.info(f" Initial list count: {initial_list_count}")
|
|
268
269
|
|
|
269
270
|
# 3. Create the new list
|
|
270
271
|
create_payload = {"name": list_name, "icon": list_icon, "type": "manual"}
|
|
@@ -276,7 +277,7 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
276
277
|
assert created_list.icon == list_icon, "Created list icon should match"
|
|
277
278
|
assert created_list.id, "Created list must have an ID"
|
|
278
279
|
created_list_id = created_list.id # Store the ID for deletion
|
|
279
|
-
|
|
280
|
+
logger.info(f"✓ Successfully created list with ID: {created_list_id}")
|
|
280
281
|
|
|
281
282
|
# 4. Verify the list appears in get_all_lists
|
|
282
283
|
current_lists_after_create = karakeep_client.get_all_lists()
|
|
@@ -286,15 +287,15 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
286
287
|
assert any(
|
|
287
288
|
lst.id == created_list_id for lst in current_lists_after_create
|
|
288
289
|
), "Created list should be present in the list of all lists"
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
logger.info(f" List count after creation: {len(current_lists_after_create)}")
|
|
291
|
+
logger.info(f"✓ Verified list {created_list_id} is present in get_all_lists.")
|
|
291
292
|
|
|
292
293
|
|
|
293
294
|
# 5. Verify the list exists by getting it directly (redundant but good check)
|
|
294
295
|
retrieved_list = karakeep_client.get_a_single_list(list_id=created_list_id)
|
|
295
296
|
assert isinstance(retrieved_list, datatypes.ListModel)
|
|
296
297
|
assert retrieved_list.id == created_list_id
|
|
297
|
-
|
|
298
|
+
logger.info(f"✓ Successfully retrieved the created list by ID.")
|
|
298
299
|
|
|
299
300
|
except (APIError, AuthenticationError) as e:
|
|
300
301
|
pytest.fail(f"API error during list creation/verification: {e}")
|
|
@@ -303,10 +304,10 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
303
304
|
finally:
|
|
304
305
|
# 6. Delete the list (ensure cleanup even if assertions fail)
|
|
305
306
|
if created_list_id:
|
|
306
|
-
|
|
307
|
+
logger.info(f"\nAttempting to delete list with ID: {created_list_id}")
|
|
307
308
|
try:
|
|
308
309
|
karakeep_client.delete_a_list(list_id=created_list_id)
|
|
309
|
-
|
|
310
|
+
logger.info(f"✓ Successfully deleted list with ID: {created_list_id}")
|
|
310
311
|
|
|
311
312
|
# 7. Verify the list is gone by trying to get it (should fail)
|
|
312
313
|
try:
|
|
@@ -318,7 +319,7 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
318
319
|
assert (
|
|
319
320
|
e.status_code == 404
|
|
320
321
|
), f"Expected 404 Not Found when getting deleted list, but got status {e.status_code}"
|
|
321
|
-
|
|
322
|
+
logger.info(
|
|
322
323
|
f"✓ Confirmed list {created_list_id} is deleted (received 404)."
|
|
323
324
|
)
|
|
324
325
|
|
|
@@ -330,14 +331,14 @@ def test_create_and_delete_list(karakeep_client: KarakeepAPI):
|
|
|
330
331
|
assert not any(
|
|
331
332
|
lst.id == created_list_id for lst in final_lists
|
|
332
333
|
), "Deleted list should not be present in the final list of all lists"
|
|
333
|
-
|
|
334
|
+
logger.info(f" Final list count: {len(final_lists)}")
|
|
334
335
|
|
|
335
336
|
except (APIError, AuthenticationError) as e:
|
|
336
337
|
pytest.fail(f"API error during list deletion: {e}")
|
|
337
338
|
except Exception as e:
|
|
338
339
|
pytest.fail(f"An unexpected error occurred during list deletion: {e}")
|
|
339
340
|
else:
|
|
340
|
-
|
|
341
|
+
logger.info("\nSkipping deletion because list creation failed or ID was not obtained.")
|
|
341
342
|
|
|
342
343
|
|
|
343
344
|
def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
|
|
@@ -351,7 +352,7 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookma
|
|
|
351
352
|
|
|
352
353
|
try:
|
|
353
354
|
# 1. Bookmark is already created by the 'managed_bookmark' fixture.
|
|
354
|
-
|
|
355
|
+
logger.info(f"\nUsing managed bookmark ID: {created_bookmark_id}, URL: '{test_url}', Title: '{original_title}'")
|
|
355
356
|
|
|
356
357
|
# 2. Verify the bookmark exists by getting it directly
|
|
357
358
|
retrieved_bookmark = karakeep_client.get_a_single_bookmark(
|
|
@@ -361,7 +362,7 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookma
|
|
|
361
362
|
assert retrieved_bookmark.id == created_bookmark_id
|
|
362
363
|
assert retrieved_bookmark.content.url == test_url
|
|
363
364
|
assert retrieved_bookmark.title == original_title
|
|
364
|
-
|
|
365
|
+
logger.info(f"✓ Successfully retrieved the managed bookmark by ID.")
|
|
365
366
|
|
|
366
367
|
|
|
367
368
|
# 3. Search for the created bookmark
|
|
@@ -374,10 +375,16 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookma
|
|
|
374
375
|
# waiting a bit for the indexation just in case
|
|
375
376
|
time.sleep(30)
|
|
376
377
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
search_queries = [
|
|
379
|
+
"Managed Fixture Bookmark",
|
|
380
|
+
"managed fixture bookmark",
|
|
381
|
+
"managed fixture",
|
|
382
|
+
"fixture managed",
|
|
383
|
+
"fixture",
|
|
384
|
+
'"fixture"',
|
|
385
|
+
]
|
|
386
|
+
for trial, search_query_component in enumerate(search_queries):
|
|
387
|
+
logger.info(f"\nAttempting to search for bookmark with query based on title: '{search_query_component}'. Retrying multiple times because search is nondeterministic.")
|
|
381
388
|
search_results = karakeep_client.search_bookmarks(q=search_query_component, limit=100, include_content=False)
|
|
382
389
|
assert isinstance(
|
|
383
390
|
search_results, datatypes.PaginatedBookmarks
|
|
@@ -393,11 +400,11 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookma
|
|
|
393
400
|
else:
|
|
394
401
|
time.sleep(3)
|
|
395
402
|
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
|
-
|
|
403
|
+
f"Managed bookmark {created_bookmark_id} (Title: '{original_title}') not found in {trial + 1} different search results for '{search_query_component}'. Titles were: '{titles_in_search}'."
|
|
404
|
+
logger.info(f"✓ Found managed bookmark in search results for '{search_query_component}'.")
|
|
398
405
|
|
|
399
406
|
# 4. Test CLI search equivalent
|
|
400
|
-
|
|
407
|
+
logger.info(f"\n Running CLI equivalent: search-bookmarks --q '{search_query_component}' --limit 10 --include-content false")
|
|
401
408
|
try:
|
|
402
409
|
cli_search_command = f"python -m karakeep_python_api search-bookmarks --q '{search_query_component}' --limit 10 --include-content false"
|
|
403
410
|
search_cli_output = subprocess.run(
|
|
@@ -409,11 +416,11 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookma
|
|
|
409
416
|
)
|
|
410
417
|
assert created_bookmark_id in search_cli_output.stdout, \
|
|
411
418
|
f"Managed bookmark ID {created_bookmark_id} not found in CLI search output for '{search_query_component}'"
|
|
412
|
-
|
|
419
|
+
logger.info("✓ CLI search command executed successfully and contained the bookmark ID.")
|
|
413
420
|
except subprocess.CalledProcessError as e:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
421
|
+
logger.info(f" CLI search command failed with exit code {e.returncode}")
|
|
422
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
423
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
417
424
|
pytest.fail(f"CLI command 'search-bookmarks --q \"{search_query_component}\"' failed: {e}")
|
|
418
425
|
except Exception as e:
|
|
419
426
|
pytest.fail(f"An unexpected error occurred running the CLI search command: {e}")
|
|
@@ -439,10 +446,10 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI, managed_bookmark: d
|
|
|
439
446
|
try:
|
|
440
447
|
# The bookmark is already created by the 'managed_bookmark' fixture.
|
|
441
448
|
# We have its ID in created_bookmark_id and its original title.
|
|
442
|
-
|
|
449
|
+
logger.info(f"\nUsing managed bookmark ID: {created_bookmark_id}, Original Title: '{original_title}'")
|
|
443
450
|
|
|
444
451
|
# 1. Update the bookmark's title using the API client
|
|
445
|
-
|
|
452
|
+
logger.info(f"\nAttempting to update bookmark ID {created_bookmark_id} title to: '{target_api_title}' via API")
|
|
446
453
|
update_payload_api = {"title": target_api_title}
|
|
447
454
|
updated_bookmark_partial = karakeep_client.update_a_bookmark(
|
|
448
455
|
bookmark_id=created_bookmark_id, update_data=update_payload_api
|
|
@@ -450,20 +457,20 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI, managed_bookmark: d
|
|
|
450
457
|
assert isinstance(updated_bookmark_partial, dict), "Update response should be a dict"
|
|
451
458
|
assert updated_bookmark_partial.get("title") == target_api_title, \
|
|
452
459
|
f"Partial response title '{updated_bookmark_partial.get('title')}' does not match target API title '{target_api_title}'"
|
|
453
|
-
|
|
460
|
+
logger.info(f"✓ API call to update_a_bookmark successful. Partial response title: '{updated_bookmark_partial.get('title')}'")
|
|
454
461
|
|
|
455
462
|
# 2. Verify the API update by fetching the bookmark again
|
|
456
|
-
|
|
463
|
+
logger.info(f"\nFetching bookmark ID {created_bookmark_id} to verify API title update.")
|
|
457
464
|
retrieved_bookmark_after_api_update = karakeep_client.get_a_single_bookmark(
|
|
458
465
|
bookmark_id=created_bookmark_id
|
|
459
466
|
)
|
|
460
467
|
assert isinstance(retrieved_bookmark_after_api_update, datatypes.Bookmark)
|
|
461
468
|
assert retrieved_bookmark_after_api_update.title == target_api_title, \
|
|
462
469
|
f"Retrieved bookmark title '{retrieved_bookmark_after_api_update.title}' does not match expected API-updated title '{target_api_title}'"
|
|
463
|
-
|
|
470
|
+
logger.info(f"✓ Successfully verified bookmark title updated by API to: '{retrieved_bookmark_after_api_update.title}'")
|
|
464
471
|
|
|
465
472
|
# 3. Test CLI equivalent for updating the bookmark's title
|
|
466
|
-
|
|
473
|
+
logger.info(f"\n Running CLI equivalent to update title to: '{target_cli_title}'")
|
|
467
474
|
cli_update_payload_json = json.dumps({"title": target_cli_title})
|
|
468
475
|
# Ensure the JSON string is properly quoted for the shell command
|
|
469
476
|
cli_update_command = f"python -m karakeep_python_api update-a-bookmark --bookmark-id {created_bookmark_id} --update-data '{cli_update_payload_json}'"
|
|
@@ -476,23 +483,23 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI, managed_bookmark: d
|
|
|
476
483
|
capture_output=True,
|
|
477
484
|
text=True,
|
|
478
485
|
)
|
|
479
|
-
|
|
486
|
+
logger.info("✓ CLI update command executed successfully.")
|
|
480
487
|
|
|
481
488
|
# 4. Verify CLI update by fetching the bookmark again
|
|
482
|
-
|
|
489
|
+
logger.info(f"\nFetching bookmark ID {created_bookmark_id} to verify CLI title update.")
|
|
483
490
|
retrieved_bookmark_after_cli_update = karakeep_client.get_a_single_bookmark(
|
|
484
491
|
bookmark_id=created_bookmark_id
|
|
485
492
|
)
|
|
486
493
|
assert isinstance(retrieved_bookmark_after_cli_update, datatypes.Bookmark)
|
|
487
494
|
assert retrieved_bookmark_after_cli_update.title == target_cli_title, \
|
|
488
495
|
f"Retrieved bookmark title '{retrieved_bookmark_after_cli_update.title}' after CLI update does not match expected '{target_cli_title}'"
|
|
489
|
-
|
|
496
|
+
logger.info(f"✓ Successfully verified bookmark title updated by CLI to: '{retrieved_bookmark_after_cli_update.title}'")
|
|
490
497
|
|
|
491
498
|
except subprocess.CalledProcessError as e:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
499
|
+
logger.info(f" CLI update command failed with exit code {e.returncode}")
|
|
500
|
+
logger.info(f" Command: {cli_update_command}")
|
|
501
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
502
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
496
503
|
pytest.fail(f"CLI command for update-a-bookmark failed: {e}")
|
|
497
504
|
except Exception as e:
|
|
498
505
|
pytest.fail(f"An unexpected error occurred running the CLI update command: {e}")
|
|
@@ -518,7 +525,7 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
518
525
|
|
|
519
526
|
try:
|
|
520
527
|
# 1. Attach a new tag by name to the bookmark
|
|
521
|
-
|
|
528
|
+
logger.info(f"\nAttempting to attach tag '{initial_tag_name}' to bookmark {bookmark_id}")
|
|
522
529
|
attach_payload = {"tags": [{"tagName": initial_tag_name}]}
|
|
523
530
|
attach_response = karakeep_client.attach_tags_to_a_bookmark(
|
|
524
531
|
bookmark_id=bookmark_id, tags_data=attach_payload
|
|
@@ -527,10 +534,10 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
527
534
|
"Failed to attach tag or response format incorrect"
|
|
528
535
|
tag_id_to_manage = attach_response["attached"][0]
|
|
529
536
|
assert isinstance(tag_id_to_manage, str), "Attached tag ID should be a string"
|
|
530
|
-
|
|
537
|
+
logger.info(f"✓ Tag '{initial_tag_name}' attached with ID: {tag_id_to_manage}")
|
|
531
538
|
|
|
532
539
|
# 2. Update the tag's name
|
|
533
|
-
|
|
540
|
+
logger.info(f"\nAttempting to update tag {tag_id_to_manage} to name '{updated_tag_name}'")
|
|
534
541
|
update_payload = {"name": updated_tag_name}
|
|
535
542
|
updated_tag = karakeep_client.update_a_tag(
|
|
536
543
|
tag_id=tag_id_to_manage, update_data=update_payload
|
|
@@ -538,27 +545,27 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
538
545
|
# Do not check the type because karakeep 0.24.1 has a server side bug
|
|
539
546
|
# assert isinstance(updated_tag, datatypes.Tag1), "Update tag response should be Tag1 model"
|
|
540
547
|
# assert updated_tag.name == updated_tag_name, "Tag name was not updated as expected"
|
|
541
|
-
#
|
|
548
|
+
# logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag.name}'")
|
|
542
549
|
assert updated_tag["name"] == updated_tag_name, "Tag name was not updated as expected"
|
|
543
|
-
|
|
550
|
+
logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag['name']}'")
|
|
544
551
|
|
|
545
552
|
# 3. Verify tag update by getting it directly
|
|
546
|
-
|
|
553
|
+
logger.info(f"\nFetching tag {tag_id_to_manage} to verify its name is '{updated_tag_name}'")
|
|
547
554
|
retrieved_tag = karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
|
|
548
555
|
assert isinstance(retrieved_tag, datatypes.Tag1), "Get single tag response should be Tag1 model"
|
|
549
556
|
assert retrieved_tag.name == updated_tag_name, "Retrieved tag name does not match updated name"
|
|
550
557
|
assert retrieved_tag.id == tag_id_to_manage, "Retrieved tag ID does not match"
|
|
551
|
-
|
|
558
|
+
logger.info(f"✓ Verified tag {tag_id_to_manage} has name '{retrieved_tag.name}'")
|
|
552
559
|
|
|
553
560
|
# 4. Detach the tag from the bookmark
|
|
554
|
-
|
|
561
|
+
logger.info(f"\nAttempting to detach tag {tag_id_to_manage} from bookmark {bookmark_id}")
|
|
555
562
|
detach_payload = {"tags": [{"tagId": tag_id_to_manage}]}
|
|
556
563
|
detach_response = karakeep_client.detach_tags_from_a_bookmark(
|
|
557
564
|
bookmark_id=bookmark_id, tags_data=detach_payload
|
|
558
565
|
)
|
|
559
566
|
assert "detached" in detach_response and tag_id_to_manage in detach_response["detached"], \
|
|
560
567
|
"Failed to detach tag or response format incorrect"
|
|
561
|
-
|
|
568
|
+
logger.info(f"✓ Tag {tag_id_to_manage} detached from bookmark {bookmark_id}")
|
|
562
569
|
|
|
563
570
|
except (APIError, AuthenticationError) as e:
|
|
564
571
|
pytest.fail(f"API error during tag lifecycle test: {e}")
|
|
@@ -567,10 +574,10 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
567
574
|
finally:
|
|
568
575
|
# 5. Delete the tag (ensure cleanup even if assertions fail mid-test)
|
|
569
576
|
if tag_id_to_manage:
|
|
570
|
-
|
|
577
|
+
logger.info(f"\nAttempting to delete tag {tag_id_to_manage} (cleanup)")
|
|
571
578
|
try:
|
|
572
579
|
karakeep_client.delete_a_tag(tag_id=tag_id_to_manage)
|
|
573
|
-
|
|
580
|
+
logger.info(f"✓ Successfully deleted tag {tag_id_to_manage}")
|
|
574
581
|
|
|
575
582
|
# 6. Verify the tag is gone by trying to get it (should fail with 404)
|
|
576
583
|
try:
|
|
@@ -581,14 +588,14 @@ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmar
|
|
|
581
588
|
except APIError as e:
|
|
582
589
|
assert e.status_code == 404, \
|
|
583
590
|
f"Expected 404 Not Found when getting deleted tag, but got status {e.status_code}"
|
|
584
|
-
|
|
591
|
+
logger.info(f"✓ Confirmed tag {tag_id_to_manage} is deleted (received 404).")
|
|
585
592
|
except (APIError, AuthenticationError) as e:
|
|
586
593
|
# Log error during cleanup but don't let it mask original test failure
|
|
587
|
-
|
|
594
|
+
logger.info(f" API error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
|
|
588
595
|
except Exception as e:
|
|
589
|
-
|
|
596
|
+
logger.info(f" Unexpected error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
|
|
590
597
|
else:
|
|
591
|
-
|
|
598
|
+
logger.info("\nSkipping tag deletion (cleanup) because tag_id was not obtained or test failed before creation.")
|
|
592
599
|
|
|
593
600
|
|
|
594
601
|
# --- Test User Info/Stats Endpoints ---
|
|
@@ -609,7 +616,7 @@ def test_get_current_user_stats(karakeep_client: KarakeepAPI):
|
|
|
609
616
|
assert isinstance(stats["numLists"], int) and stats["numLists"] >= 0
|
|
610
617
|
assert isinstance(stats["numTags"], int) and stats["numTags"] >= 0
|
|
611
618
|
|
|
612
|
-
|
|
619
|
+
logger.info(f"✓ Successfully retrieved user stats: {stats}")
|
|
613
620
|
|
|
614
621
|
except (APIError, AuthenticationError) as e:
|
|
615
622
|
pytest.fail(f"API error during user stats retrieval: {e}")
|
|
@@ -618,21 +625,21 @@ def test_get_current_user_stats(karakeep_client: KarakeepAPI):
|
|
|
618
625
|
|
|
619
626
|
# --- Add CLI call ---
|
|
620
627
|
try:
|
|
621
|
-
|
|
628
|
+
logger.info("\n Running CLI equivalent: get-current-user-stats")
|
|
622
629
|
# Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
|
|
623
630
|
subprocess.run(
|
|
624
631
|
"python -m karakeep_python_api get-current-user-stats",
|
|
625
632
|
shell=True,
|
|
626
633
|
check=True,
|
|
627
|
-
capture_output=True, # Capture output to avoid
|
|
634
|
+
capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
|
|
628
635
|
text=True,
|
|
629
636
|
)
|
|
630
|
-
|
|
637
|
+
logger.info("✓ CLI command executed successfully.")
|
|
631
638
|
except subprocess.CalledProcessError as e:
|
|
632
|
-
|
|
633
|
-
#
|
|
634
|
-
|
|
635
|
-
|
|
639
|
+
logger.info(f" CLI command failed with exit code {e.returncode}")
|
|
640
|
+
# logger.info stdout/stderr only if the command failed to aid debugging
|
|
641
|
+
logger.info(f" Stdout: {e.stdout}")
|
|
642
|
+
logger.info(f" Stderr: {e.stderr}")
|
|
636
643
|
pytest.fail(f"CLI command 'get-current-user-stats' failed: {e}")
|
|
637
644
|
except Exception as e:
|
|
638
645
|
pytest.fail(f"An unexpected error occurred running the CLI command: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/openapi_reference.json
RENAMED
|
File without changes
|
{karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/requires.txt
RENAMED
|
File without changes
|
{karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|