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.
Files changed (18) hide show
  1. {karakeep_python_api-0.1.3/karakeep_python_api.egg-info → karakeep_python_api-0.1.4}/PKG-INFO +1 -1
  2. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/__main__.py +0 -3
  3. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/karakeep_api.py +1 -6
  4. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4/karakeep_python_api.egg-info}/PKG-INFO +1 -1
  5. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/setup.py +1 -1
  6. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/tests/test_karakeep_api.py +113 -106
  7. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/LICENSE +0 -0
  8. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/MANIFEST.in +0 -0
  9. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/README.md +0 -0
  10. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/__init__.py +0 -0
  11. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/datatypes.py +0 -0
  12. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api/openapi_reference.json +0 -0
  13. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
  14. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
  15. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/entry_points.txt +0 -0
  16. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/requires.txt +0 -0
  17. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/top_level.txt +0 -0
  18. {karakeep_python_api-0.1.3 → karakeep_python_api-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -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.3"
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 ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.1.3
3
+ Version: 0.1.4
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
@@ -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.3",
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
- print(f"✓ Retrieved first page with {len(page1.bookmarks)} bookmarks.")
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
- print(f" Attempting to fetch next page with cursor: {page1.nextCursor}")
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
- print(f"✓ Retrieved second page with {len(page2.bookmarks)} bookmarks.")
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
- print(" No next cursor found, pagination test ends.")
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
- print("\n Running CLI equivalent: get-all-bookmarks --limit 2")
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 printing it during tests unless verbose
61
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
61
62
  text=True,
62
63
  )
63
- print("✓ CLI command executed successfully.")
64
+ logger.info("✓ CLI command executed successfully.")
64
65
  except subprocess.CalledProcessError as e:
65
- print(f" CLI command failed with exit code {e.returncode}")
66
- # Print stdout/stderr only if the command failed to aid debugging
67
- print(f" Stdout: {e.stdout}")
68
- print(f" Stderr: {e.stderr}")
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
- print(f"✓ Successfully retrieved {len(lists)} lists.")
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
- print("\n Running CLI equivalent: get-all-lists")
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 printing it during tests unless verbose
98
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
98
99
  text=True,
99
100
  )
100
- print("✓ CLI command executed successfully.")
101
+ logger.info("✓ CLI command executed successfully.")
101
102
  except subprocess.CalledProcessError as e:
102
- print(f" CLI command failed with exit code {e.returncode}")
103
- # Print stdout/stderr only if the command failed to aid debugging
104
- print(f" Stdout: {e.stdout}")
105
- print(f" Stderr: {e.stderr}")
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
- print(f"✓ Successfully retrieved {len(tags)} tags.")
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
- print("\n Running CLI equivalent: get-all-tags")
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 printing it during tests unless verbose
135
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
135
136
  text=True,
136
137
  )
137
- print("✓ CLI command executed successfully.")
138
+ logger.info("✓ CLI command executed successfully.")
138
139
  except subprocess.CalledProcessError as e:
139
- print(f" CLI command failed with exit code {e.returncode}")
140
- # Print stdout/stderr only if the command failed to aid debugging
141
- print(f" Stdout: {e.stdout}")
142
- print(f" Stderr: {e.stderr}")
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
- print(f"✓ Retrieved first page with {len(page1.highlights)} highlights.")
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
- print(f" Attempting to fetch next page with cursor: {page1.nextCursor}")
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
- print(f"✓ Retrieved second page with {len(page2.highlights)} highlights.")
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
- print(" No next cursor found, pagination test ends.")
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
- print("\n Running CLI equivalent: get-all-highlights --limit 3")
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 printing it during tests unless verbose
194
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
194
195
  text=True,
195
196
  )
196
- print("✓ CLI command executed successfully.")
197
+ logger.info("✓ CLI command executed successfully.")
197
198
  except subprocess.CalledProcessError as e:
198
- print(f" CLI command failed with exit code {e.returncode}")
199
- # Print stdout/stderr only if the command failed to aid debugging
200
- print(f" Stdout: {e.stdout}")
201
- print(f" Stderr: {e.stderr}")
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
- print(
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
- print("\n Running CLI equivalent: --dump-openapi-specification")
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 printing it during tests unless verbose
236
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
236
237
  text=True,
237
238
  )
238
- print("✓ CLI command executed successfully.")
239
+ logger.info("✓ CLI command executed successfully.")
239
240
  except subprocess.CalledProcessError as e:
240
- print(f" CLI command failed with exit code {e.returncode}")
241
- # Print stdout/stderr only if the command failed to aid debugging
242
- print(f" Stdout: {e.stdout}")
243
- print(f" Stderr: {e.stderr}")
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
- print(f"\nAttempting to create list: Name='{list_name}', Icon='{list_icon}'")
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
- print(f" Initial list count: {initial_list_count}")
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
- print(f"✓ Successfully created list with ID: {created_list_id}")
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
- print(f" List count after creation: {len(current_lists_after_create)}")
290
- print(f"✓ Verified list {created_list_id} is present in get_all_lists.")
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
- print(f"✓ Successfully retrieved the created list by ID.")
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
- print(f"\nAttempting to delete list with ID: {created_list_id}")
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
- print(f"✓ Successfully deleted list with ID: {created_list_id}")
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
- print(
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
- print(f" Final list count: {len(final_lists)}")
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
- print("\nSkipping deletion because list creation failed or ID was not obtained.")
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
- print(f"\nUsing managed bookmark ID: {created_bookmark_id}, URL: '{test_url}', Title: '{original_title}'")
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
- print(f"✓ Successfully retrieved the managed bookmark by ID.")
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
- 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):
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
- print(f"✓ Found managed bookmark in search results for '{search_query_component}'.")
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
- print(f"\n Running CLI equivalent: search-bookmarks --q '{search_query_component}' --limit 10 --include-content false")
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
- print("✓ CLI search command executed successfully and contained the bookmark ID.")
419
+ logger.info("✓ CLI search command executed successfully and contained the bookmark ID.")
413
420
  except subprocess.CalledProcessError as e:
414
- print(f" CLI search command failed with exit code {e.returncode}")
415
- print(f" Stdout: {e.stdout}")
416
- print(f" Stderr: {e.stderr}")
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
- print(f"\nUsing managed bookmark ID: {created_bookmark_id}, Original Title: '{original_title}'")
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
- print(f"\nAttempting to update bookmark ID {created_bookmark_id} title to: '{target_api_title}' via API")
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
- print(f"✓ API call to update_a_bookmark successful. Partial response title: '{updated_bookmark_partial.get('title')}'")
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
- print(f"\nFetching bookmark ID {created_bookmark_id} to verify API title update.")
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
- print(f"✓ Successfully verified bookmark title updated by API to: '{retrieved_bookmark_after_api_update.title}'")
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
- print(f"\n Running CLI equivalent to update title to: '{target_cli_title}'")
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
- print("✓ CLI update command executed successfully.")
486
+ logger.info("✓ CLI update command executed successfully.")
480
487
 
481
488
  # 4. Verify CLI update by fetching the bookmark again
482
- print(f"\nFetching bookmark ID {created_bookmark_id} to verify CLI title update.")
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
- print(f"✓ Successfully verified bookmark title updated by CLI to: '{retrieved_bookmark_after_cli_update.title}'")
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
- 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}")
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
- print(f"\nAttempting to attach tag '{initial_tag_name}' to bookmark {bookmark_id}")
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
- print(f"✓ Tag '{initial_tag_name}' attached with ID: {tag_id_to_manage}")
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
- print(f"\nAttempting to update tag {tag_id_to_manage} to name '{updated_tag_name}'")
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
- # print(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag.name}'")
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
- print(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag['name']}'")
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
- print(f"\nFetching tag {tag_id_to_manage} to verify its name is '{updated_tag_name}'")
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
- print(f"✓ Verified tag {tag_id_to_manage} has name '{retrieved_tag.name}'")
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
- print(f"\nAttempting to detach tag {tag_id_to_manage} from bookmark {bookmark_id}")
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
- print(f"✓ Tag {tag_id_to_manage} detached from bookmark {bookmark_id}")
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
- print(f"\nAttempting to delete tag {tag_id_to_manage} (cleanup)")
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
- print(f"✓ Successfully deleted tag {tag_id_to_manage}")
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
- print(f"✓ Confirmed tag {tag_id_to_manage} is deleted (received 404).")
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
- print(f" API error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
594
+ logger.info(f" API error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
588
595
  except Exception as e:
589
- print(f" Unexpected error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
596
+ logger.info(f" Unexpected error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
590
597
  else:
591
- print("\nSkipping tag deletion (cleanup) because tag_id was not obtained or test failed before creation.")
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
- print(f"✓ Successfully retrieved user stats: {stats}")
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
- print("\n Running CLI equivalent: get-current-user-stats")
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 printing it during tests unless verbose
634
+ capture_output=True, # Capture output to avoid logger.infoing it during tests unless verbose
628
635
  text=True,
629
636
  )
630
- print("✓ CLI command executed successfully.")
637
+ logger.info("✓ CLI command executed successfully.")
631
638
  except subprocess.CalledProcessError as e:
632
- print(f" CLI command failed with exit code {e.returncode}")
633
- # Print stdout/stderr only if the command failed to aid debugging
634
- print(f" Stdout: {e.stdout}")
635
- print(f" Stderr: {e.stderr}")
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}")