karakeep-python-api 0.1.2__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.2/karakeep_python_api.egg-info → karakeep_python_api-0.1.4}/PKG-INFO +41 -38
  2. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/README.md +40 -37
  3. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api/__main__.py +0 -3
  4. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api/karakeep_api.py +6 -6
  5. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api/openapi_reference.json +1 -1
  6. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4/karakeep_python_api.egg-info}/PKG-INFO +41 -38
  7. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/setup.py +1 -1
  8. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/tests/test_karakeep_api.py +241 -226
  9. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/LICENSE +0 -0
  10. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/MANIFEST.in +0 -0
  11. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api/__init__.py +0 -0
  12. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api/datatypes.py +0 -0
  13. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
  14. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
  15. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/entry_points.txt +0 -0
  16. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/requires.txt +0 -0
  17. {karakeep_python_api-0.1.2 → karakeep_python_api-0.1.4}/karakeep_python_api.egg-info/top_level.txt +0 -0
  18. {karakeep_python_api-0.1.2 → 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.2
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
@@ -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 and indicates whether they are currently covered by the automated test suite (`tests/test_karakeep_api.py`). Methods marked as "No" should be used with caution as their behavior has not been automatically verified.
76
-
77
- | Method Name | Tested | Remarks |
78
- | -------------------------------- | :----: | -------------------------------------------- |
79
- | `get_all_bookmarks` | ✅ | Tested with pagination. |
80
- | `create_a_new_bookmark` | ✅ | Only tested for `type="link"`. |
81
- | `search_bookmarks` | ✅ | Tested as part of create/delete flow. |
82
- | `get_a_single_bookmark` | ✅ | Tested as part of create/delete flow. |
83
- | `delete_a_bookmark` | ✅ | Tested as part of create/delete flow. |
84
- | `update_a_bookmark` | ✅ | Tested for title updates. |
85
- | `summarize_a_bookmark` | | |
86
- | `attach_tags_to_a_bookmark` | | |
87
- | `detach_tags_from_a_bookmark` | | |
88
- | `get_highlights_of_a_bookmark` | ❌ | |
89
- | `attach_asset` | | |
90
- | `replace_asset` | | |
91
- | `detach_asset` | ❌ | |
92
- | `get_all_lists` | | |
93
- | `create_a_new_list` | | Tested as part of create/delete flow. |
94
- | `get_a_single_list` | | Tested as part of create/delete flow. |
95
- | `delete_a_list` | ✅ | Tested as part of create/delete flow. |
96
- | `update_a_list` | | |
97
- | `get_a_bookmarks_in_a_list` | | |
98
- | `add_a_bookmark_to_a_list` | | |
99
- | `remove_a_bookmark_from_a_list` | ❌ | |
100
- | `get_all_tags` | | |
101
- | `get_a_single_tag` | ❌ | |
102
- | `delete_a_tag` | ❌ | |
103
- | `update_a_tag` | | |
104
- | `get_a_bookmarks_with_the_tag` | | |
105
- | `get_all_highlights` | ✅ | Tested with pagination. |
106
- | `create_a_new_highlight` | | |
107
- | `get_a_single_highlight` | ❌ | |
108
- | `delete_a_highlight` | | |
109
- | `update_a_highlight` | ❌ | |
110
- | `get_current_user_info` | | Tested indirectly during client initialization. |
111
- | `get_current_user_stats` | | |
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
 
@@ -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 and indicates whether they are currently covered by the automated test suite (`tests/test_karakeep_api.py`). Methods marked as "No" should be used with caution as their behavior has not been automatically verified.
41
-
42
- | Method Name | Tested | Remarks |
43
- | -------------------------------- | :----: | -------------------------------------------- |
44
- | `get_all_bookmarks` | ✅ | Tested with pagination. |
45
- | `create_a_new_bookmark` | ✅ | Only tested for `type="link"`. |
46
- | `search_bookmarks` | ✅ | Tested as part of create/delete flow. |
47
- | `get_a_single_bookmark` | ✅ | Tested as part of create/delete flow. |
48
- | `delete_a_bookmark` | ✅ | Tested as part of create/delete flow. |
49
- | `update_a_bookmark` | ✅ | Tested for title updates. |
50
- | `summarize_a_bookmark` | | |
51
- | `attach_tags_to_a_bookmark` | | |
52
- | `detach_tags_from_a_bookmark` | | |
53
- | `get_highlights_of_a_bookmark` | ❌ | |
54
- | `attach_asset` | | |
55
- | `replace_asset` | | |
56
- | `detach_asset` | ❌ | |
57
- | `get_all_lists` | | |
58
- | `create_a_new_list` | | Tested as part of create/delete flow. |
59
- | `get_a_single_list` | | Tested as part of create/delete flow. |
60
- | `delete_a_list` | ✅ | Tested as part of create/delete flow. |
61
- | `update_a_list` | | |
62
- | `get_a_bookmarks_in_a_list` | | |
63
- | `add_a_bookmark_to_a_list` | | |
64
- | `remove_a_bookmark_from_a_list` | ❌ | |
65
- | `get_all_tags` | | |
66
- | `get_a_single_tag` | ❌ | |
67
- | `delete_a_tag` | ❌ | |
68
- | `update_a_tag` | | |
69
- | `get_a_bookmarks_with_the_tag` | | |
70
- | `get_all_highlights` | ✅ | Tested with pagination. |
71
- | `create_a_new_highlight` | | |
72
- | `get_a_single_highlight` | ❌ | |
73
- | `delete_a_highlight` | | |
74
- | `update_a_highlight` | ❌ | |
75
- | `get_current_user_info` | | Tested indirectly during client initialization. |
76
- | `get_current_user_stats` | | |
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
 
@@ -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.2"
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 ---
@@ -1435,6 +1430,11 @@ class KarakeepAPI:
1435
1430
  logger.debug("Skipping response validation as requested.")
1436
1431
  return response_data
1437
1432
  else:
1433
+ # As of version 0.24.1 of karakeep: we do not check the correct
1434
+ # validation type because there is an error on the
1435
+ # server side: https://github.com/karakeep-app/karakeep/issues/1365
1436
+ return response_data
1437
+
1438
1438
  # Response should match Tag1 schema
1439
1439
  return datatypes.Tag1.model_validate(response_data)
1440
1440
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.1.2
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
@@ -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 and indicates whether they are currently covered by the automated test suite (`tests/test_karakeep_api.py`). Methods marked as "No" should be used with caution as their behavior has not been automatically verified.
76
-
77
- | Method Name | Tested | Remarks |
78
- | -------------------------------- | :----: | -------------------------------------------- |
79
- | `get_all_bookmarks` | ✅ | Tested with pagination. |
80
- | `create_a_new_bookmark` | ✅ | Only tested for `type="link"`. |
81
- | `search_bookmarks` | ✅ | Tested as part of create/delete flow. |
82
- | `get_a_single_bookmark` | ✅ | Tested as part of create/delete flow. |
83
- | `delete_a_bookmark` | ✅ | Tested as part of create/delete flow. |
84
- | `update_a_bookmark` | ✅ | Tested for title updates. |
85
- | `summarize_a_bookmark` | | |
86
- | `attach_tags_to_a_bookmark` | | |
87
- | `detach_tags_from_a_bookmark` | | |
88
- | `get_highlights_of_a_bookmark` | ❌ | |
89
- | `attach_asset` | | |
90
- | `replace_asset` | | |
91
- | `detach_asset` | ❌ | |
92
- | `get_all_lists` | | |
93
- | `create_a_new_list` | | Tested as part of create/delete flow. |
94
- | `get_a_single_list` | | Tested as part of create/delete flow. |
95
- | `delete_a_list` | ✅ | Tested as part of create/delete flow. |
96
- | `update_a_list` | | |
97
- | `get_a_bookmarks_in_a_list` | | |
98
- | `add_a_bookmark_to_a_list` | | |
99
- | `remove_a_bookmark_from_a_list` | ❌ | |
100
- | `get_all_tags` | | |
101
- | `get_a_single_tag` | ❌ | |
102
- | `delete_a_tag` | ❌ | |
103
- | `update_a_tag` | | |
104
- | `get_a_bookmarks_with_the_tag` | | |
105
- | `get_all_highlights` | ✅ | Tested with pagination. |
106
- | `create_a_new_highlight` | | |
107
- | `get_a_single_highlight` | ❌ | |
108
- | `delete_a_highlight` | | |
109
- | `update_a_highlight` | ❌ | |
110
- | `get_current_user_info` | | Tested indirectly during client initialization. |
111
- | `get_current_user_stats` | | |
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
 
@@ -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.2",
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,65 +331,82 @@ 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
- def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI):
344
- """Test creating a new URL bookmark and then deleting it."""
345
- created_bookmark_id = None
346
- test_url = "https://en.wikipedia.org/wiki/Example"
344
+ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
345
+ """
346
+ Test verifying a created bookmark (via fixture) and searching for it.
347
+ The fixture handles creation and deletion.
348
+ """
349
+ created_bookmark_id = managed_bookmark.id
350
+ test_url = managed_bookmark.content.url # Get URL from fixture
351
+ original_title = managed_bookmark.title # Get title from fixture
352
+
347
353
  try:
348
- # 1. Define bookmark payload
349
- print(f"\nAttempting to create bookmark for URL: {test_url}")
354
+ # 1. Bookmark is already created by the 'managed_bookmark' fixture.
355
+ logger.info(f"\nUsing managed bookmark ID: {created_bookmark_id}, URL: '{test_url}', Title: '{original_title}'")
350
356
 
351
- # 2. Create the bookmark
352
- # Call the method with keyword arguments matching its signature
353
- created_bookmark = karakeep_client.create_a_new_bookmark(
354
- type="link", url=test_url
355
- )
356
- assert isinstance(
357
- created_bookmark, datatypes.Bookmark
358
- ), "Response should be a Bookmark model"
359
- assert created_bookmark.content.url == test_url, "Created bookmark URL should match"
360
- assert created_bookmark.id, "Created bookmark must have an ID"
361
- created_bookmark_id = created_bookmark.id
362
- print(f"✓ Successfully created bookmark with ID: {created_bookmark_id}")
363
-
364
- # 3. Verify the bookmark exists by getting it directly
357
+ # 2. Verify the bookmark exists by getting it directly
365
358
  retrieved_bookmark = karakeep_client.get_a_single_bookmark(
366
359
  bookmark_id=created_bookmark_id
367
360
  )
368
361
  assert isinstance(retrieved_bookmark, datatypes.Bookmark)
369
362
  assert retrieved_bookmark.id == created_bookmark_id
370
363
  assert retrieved_bookmark.content.url == test_url
371
- print(f"✓ Successfully retrieved the created bookmark by ID.")
364
+ assert retrieved_bookmark.title == original_title
365
+ logger.info(f"✓ Successfully retrieved the managed bookmark by ID.")
372
366
 
373
- # 4. Search for the created bookmark
374
- print(f"\nAttempting to search for bookmark with query: 'wikipedia'")
375
- search_query = "Example - Wikipedia"
376
- search_results = karakeep_client.search_bookmarks(q=search_query, limit=50)
377
- assert isinstance(
378
- search_results, datatypes.PaginatedBookmarks
379
- ), "Search response should be PaginatedBookmarks model"
380
- assert isinstance(
381
- search_results.bookmarks, list
382
- ), "Search results bookmarks attribute should be a list"
383
- assert any(
384
- b.id == created_bookmark_id for b in search_results.bookmarks
385
- ), f"Created bookmark {created_bookmark_id} not found in search results for '{search_query}'"
386
- print(f"✓ Found created bookmark in search results for '{search_query}'.")
387
367
 
388
- # 4a. Test CLI search equivalent
389
- print(f"\n Running CLI equivalent: search-bookmarks --q '{search_query}' --limit 10")
368
+ # 3. Search for the created bookmark
369
+ # Use a search query that is likely to match the fixture's title
370
+ # The fixture title is "Managed Fixture Bookmark {timestamp}-{random_suffix}"
371
+ # A simple search for "Managed Fixture Bookmark" should work.
372
+ # If the title is very dynamic, searching by URL might be more robust if supported,
373
+ # or by a known part of the title.
374
+
375
+ # waiting a bit for the indexation just in case
376
+ time.sleep(30)
377
+
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.")
388
+ search_results = karakeep_client.search_bookmarks(q=search_query_component, limit=100, include_content=False)
389
+ assert isinstance(
390
+ search_results, datatypes.PaginatedBookmarks
391
+ ), "Search response should be PaginatedBookmarks model"
392
+ assert isinstance(
393
+ search_results.bookmarks, list
394
+ ), "Search results bookmarks attribute should be a list"
395
+
396
+ titles_in_search = [b.title for b in search_results.bookmarks]
397
+ found_in_search = any(b.id == created_bookmark_id for b in search_results.bookmarks)
398
+ if found_in_search:
399
+ break
400
+ else:
401
+ time.sleep(3)
402
+ assert found_in_search, \
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}'.")
405
+
406
+ # 4. Test CLI search equivalent
407
+ logger.info(f"\n Running CLI equivalent: search-bookmarks --q '{search_query_component}' --limit 10 --include-content false")
390
408
  try:
391
- cli_search_command = f"python -m karakeep_python_api search-bookmarks --q '{search_query}' --limit 10"
409
+ cli_search_command = f"python -m karakeep_python_api search-bookmarks --q '{search_query_component}' --limit 10 --include-content false"
392
410
  search_cli_output = subprocess.run(
393
411
  cli_search_command,
394
412
  shell=True,
@@ -396,121 +414,42 @@ def test_create_and_delete_bookmark(karakeep_client: KarakeepAPI):
396
414
  capture_output=True,
397
415
  text=True,
398
416
  )
399
- # Basic check: Ensure the created ID is somewhere in the output JSON
400
- # A more robust check would parse the JSON and verify structure/content
401
- 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}'"
402
- print("✓ CLI search command executed successfully and contained the bookmark ID.")
417
+ assert created_bookmark_id in search_cli_output.stdout, \
418
+ f"Managed bookmark ID {created_bookmark_id} not found in CLI search output for '{search_query_component}'"
419
+ logger.info(" CLI search command executed successfully and contained the bookmark ID.")
403
420
  except subprocess.CalledProcessError as e:
404
- print(f" CLI search command failed with exit code {e.returncode}")
405
- print(f" Stdout: {e.stdout}")
406
- print(f" Stderr: {e.stderr}")
407
- pytest.fail(f"CLI command 'search-bookmarks --q {search_query}' failed: {e}")
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}")
424
+ pytest.fail(f"CLI command 'search-bookmarks --q \"{search_query_component}\"' failed: {e}")
408
425
  except Exception as e:
409
426
  pytest.fail(f"An unexpected error occurred running the CLI search command: {e}")
410
427
 
411
-
412
428
  except (APIError, AuthenticationError) as e:
413
- pytest.fail(f"API error during bookmark creation/verification/search: {e}")
429
+ pytest.fail(f"API error during bookmark verification/search: {e}")
414
430
  except Exception as e:
415
431
  pytest.fail(
416
- f"An unexpected error occurred during bookmark creation/verification/search: {e}"
432
+ f"An unexpected error occurred during bookmark verification/search: {e}"
417
433
  )
418
- finally:
419
- # 5. Delete the bookmark (ensure cleanup)
420
- if created_bookmark_id:
421
- print(f"\nAttempting to delete bookmark with ID: {created_bookmark_id}")
422
- try:
423
- karakeep_client.delete_a_bookmark(bookmark_id=created_bookmark_id)
424
- print(f"✓ Successfully deleted bookmark with ID: {created_bookmark_id}")
425
-
426
- # 6. Verify the bookmark is gone by trying to get it (should fail)
427
- try:
428
- karakeep_client.get_a_single_bookmark(
429
- bookmark_id=created_bookmark_id
430
- )
431
- pytest.fail(
432
- f"Bookmark with ID {created_bookmark_id} should not exist after deletion, but get_a_single_bookmark succeeded."
433
- )
434
- except APIError as e:
435
- assert (
436
- e.status_code == 404
437
- ), f"Expected 404 Not Found when getting deleted bookmark, but got status {e.status_code}"
438
- print(
439
- f"✓ Confirmed bookmark {created_bookmark_id} is deleted via API (received 404)."
440
- )
434
+ # No 'finally' block for deletion needed, as 'managed_bookmark' fixture handles it.
435
+ # The fixture also handles verification of deletion.
441
436
 
442
- # 7. Attempt to delete the same bookmark via CLI (should ideally fail or do nothing)
443
- print(f"\n Attempting CLI deletion for already deleted bookmark ID: {created_bookmark_id}")
444
- try:
445
- cli_delete_command = f"python -m karakeep_python_api delete-a-bookmark --bookmark-id {created_bookmark_id}"
446
- subprocess.run(
447
- cli_delete_command,
448
- shell=True,
449
- check=True, # Set to False if CLI errors on 404, True if it exits 0
450
- capture_output=True,
451
- text=True,
452
- )
453
- # If check=True and it succeeds, it means the CLI might not error on 404.
454
- print(f" ✓ CLI delete command executed (check=True implies it might not error on 404).")
455
- except subprocess.CalledProcessError as e:
456
- # If check=True, this block means the CLI command failed, which *might* be expected if it errors on 404.
457
- print(f" ✓ CLI delete command failed as expected (exit code {e.returncode}), likely because bookmark was already deleted.")
458
- # Optional: Assert specific exit code if known
459
- # assert e.returncode == EXPECTED_EXIT_CODE_FOR_NOT_FOUND
460
- except Exception as cli_e:
461
- pytest.fail(f"An unexpected error occurred running the CLI delete command: {cli_e}")
462
-
463
- # 8. Verify again via API that the bookmark is still gone after CLI attempt
464
- try:
465
- karakeep_client.get_a_single_bookmark(bookmark_id=created_bookmark_id)
466
- pytest.fail(
467
- f"Bookmark {created_bookmark_id} should *still* not exist after CLI deletion attempt, but get_a_single_bookmark succeeded."
468
- )
469
- except APIError as e:
470
- assert (
471
- e.status_code == 404
472
- ), f"Expected 404 Not Found after CLI deletion attempt, but got status {e.status_code}"
473
- print(
474
- f"✓ Confirmed bookmark {created_bookmark_id} is *still* deleted after CLI attempt (received 404)."
475
- )
476
437
 
477
- except (APIError, AuthenticationError) as e:
478
- # Catch errors from the initial Python API deletion or subsequent checks
479
- pytest.fail(f"API error during bookmark deletion/verification: {e}")
480
- except Exception as e:
481
- pytest.fail(f"An unexpected error occurred during bookmark deletion: {e}")
482
- else:
483
- print(
484
- "\nSkipping deletion because bookmark creation failed or ID was not obtained."
485
- )
486
-
487
-
488
- def test_update_bookmark_title(karakeep_client: KarakeepAPI):
489
- """Test updating a bookmark's title via API and CLI."""
490
- created_bookmark_id = None
491
- # Generate a unique suffix for the initial title to avoid collisions
492
- timestamp = int(time.time())
493
- random_suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
494
- original_title = f"Initial Test Title {timestamp}-{random_suffix}"
495
- test_url = "https://en.wikipedia.org/wiki/PageForBookmarkUpdateTest" # A distinct URL for this test
438
+ def test_update_bookmark_title(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
439
+ """Test updating a bookmark's title via API and CLI, using a managed bookmark."""
440
+ created_bookmark_id = managed_bookmark.id
441
+ original_title = managed_bookmark.title # Get the original title from the fixture
496
442
 
497
443
  target_api_title = "this is a test title"
498
444
  target_cli_title = "this is a test title (CLI)"
499
445
 
500
446
  try:
501
- # 1. Create a bookmark with an initial title
502
- print(f"\nAttempting to create bookmark for URL: {test_url} with original title: '{original_title}'")
503
- created_bookmark = karakeep_client.create_a_new_bookmark(
504
- type="link", url=test_url, title=original_title
505
- )
506
- assert isinstance(created_bookmark, datatypes.Bookmark), "Response should be a Bookmark model"
507
- created_bookmark_id = created_bookmark.id
508
- assert created_bookmark.id, "Created bookmark must have an ID"
509
- assert created_bookmark.title == original_title, f"Initial title '{created_bookmark.title}' does not match expected '{original_title}'"
510
- print(f"✓ Successfully created bookmark ID: {created_bookmark_id}, Title: '{created_bookmark.title}'")
511
-
512
- # 2. Update the bookmark's title using the API client
513
- print(f"\nAttempting to update bookmark ID {created_bookmark_id} title to: '{target_api_title}' via API")
447
+ # The bookmark is already created by the 'managed_bookmark' fixture.
448
+ # We have its ID in created_bookmark_id and its original title.
449
+ logger.info(f"\nUsing managed bookmark ID: {created_bookmark_id}, Original Title: '{original_title}'")
450
+
451
+ # 1. Update the bookmark's title using the API client
452
+ logger.info(f"\nAttempting to update bookmark ID {created_bookmark_id} title to: '{target_api_title}' via API")
514
453
  update_payload_api = {"title": target_api_title}
515
454
  updated_bookmark_partial = karakeep_client.update_a_bookmark(
516
455
  bookmark_id=created_bookmark_id, update_data=update_payload_api
@@ -518,20 +457,20 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI):
518
457
  assert isinstance(updated_bookmark_partial, dict), "Update response should be a dict"
519
458
  assert updated_bookmark_partial.get("title") == target_api_title, \
520
459
  f"Partial response title '{updated_bookmark_partial.get('title')}' does not match target API title '{target_api_title}'"
521
- 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')}'")
522
461
 
523
- # 3. Verify the API update by fetching the bookmark again
524
- print(f"\nFetching bookmark ID {created_bookmark_id} to verify API title update.")
462
+ # 2. Verify the API update by fetching the bookmark again
463
+ logger.info(f"\nFetching bookmark ID {created_bookmark_id} to verify API title update.")
525
464
  retrieved_bookmark_after_api_update = karakeep_client.get_a_single_bookmark(
526
465
  bookmark_id=created_bookmark_id
527
466
  )
528
467
  assert isinstance(retrieved_bookmark_after_api_update, datatypes.Bookmark)
529
468
  assert retrieved_bookmark_after_api_update.title == target_api_title, \
530
469
  f"Retrieved bookmark title '{retrieved_bookmark_after_api_update.title}' does not match expected API-updated title '{target_api_title}'"
531
- 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}'")
532
471
 
533
- # 4. Test CLI equivalent for updating the bookmark's title
534
- print(f"\n Running CLI equivalent to update title to: '{target_cli_title}'")
472
+ # 3. Test CLI equivalent for updating the bookmark's title
473
+ logger.info(f"\n Running CLI equivalent to update title to: '{target_cli_title}'")
535
474
  cli_update_payload_json = json.dumps({"title": target_cli_title})
536
475
  # Ensure the JSON string is properly quoted for the shell command
537
476
  cli_update_command = f"python -m karakeep_python_api update-a-bookmark --bookmark-id {created_bookmark_id} --update-data '{cli_update_payload_json}'"
@@ -544,23 +483,23 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI):
544
483
  capture_output=True,
545
484
  text=True,
546
485
  )
547
- print("✓ CLI update command executed successfully.")
486
+ logger.info("✓ CLI update command executed successfully.")
548
487
 
549
- # 5. Verify CLI update by fetching the bookmark again
550
- print(f"\nFetching bookmark ID {created_bookmark_id} to verify CLI title update.")
488
+ # 4. Verify CLI update by fetching the bookmark again
489
+ logger.info(f"\nFetching bookmark ID {created_bookmark_id} to verify CLI title update.")
551
490
  retrieved_bookmark_after_cli_update = karakeep_client.get_a_single_bookmark(
552
491
  bookmark_id=created_bookmark_id
553
492
  )
554
493
  assert isinstance(retrieved_bookmark_after_cli_update, datatypes.Bookmark)
555
494
  assert retrieved_bookmark_after_cli_update.title == target_cli_title, \
556
495
  f"Retrieved bookmark title '{retrieved_bookmark_after_cli_update.title}' after CLI update does not match expected '{target_cli_title}'"
557
- 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}'")
558
497
 
559
498
  except subprocess.CalledProcessError as e:
560
- print(f" CLI update command failed with exit code {e.returncode}")
561
- print(f" Command: {cli_update_command}")
562
- print(f" Stdout: {e.stdout}")
563
- 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}")
564
503
  pytest.fail(f"CLI command for update-a-bookmark failed: {e}")
565
504
  except Exception as e:
566
505
  pytest.fail(f"An unexpected error occurred running the CLI update command: {e}")
@@ -569,18 +508,94 @@ def test_update_bookmark_title(karakeep_client: KarakeepAPI):
569
508
  pytest.fail(f"API error during bookmark title update test: {e}")
570
509
  except Exception as e:
571
510
  pytest.fail(f"An unexpected error occurred during bookmark title update test: {e}")
511
+ # No finally block needed for deletion, as 'managed_bookmark' fixture handles it.
512
+
513
+
514
+ def test_tag_lifecycle_on_bookmark(karakeep_client: KarakeepAPI, managed_bookmark: datatypes.Bookmark):
515
+ """
516
+ Test attaching a tag to a bookmark, updating the tag, detaching it, and deleting it.
517
+ Uses the managed_bookmark fixture.
518
+ """
519
+ bookmark_id = managed_bookmark.id
520
+ timestamp = int(time.time())
521
+ random_chars = "".join(random.choices(string.ascii_lowercase + string.digits, k=6))
522
+ initial_tag_name = f"test-tag-{timestamp}-{random_chars}"
523
+ updated_tag_name = f"updated-tag-{timestamp}-{random_chars}"
524
+ tag_id_to_manage = None
525
+
526
+ try:
527
+ # 1. Attach a new tag by name to the bookmark
528
+ logger.info(f"\nAttempting to attach tag '{initial_tag_name}' to bookmark {bookmark_id}")
529
+ attach_payload = {"tags": [{"tagName": initial_tag_name}]}
530
+ attach_response = karakeep_client.attach_tags_to_a_bookmark(
531
+ bookmark_id=bookmark_id, tags_data=attach_payload
532
+ )
533
+ assert "attached" in attach_response and len(attach_response["attached"]) == 1, \
534
+ "Failed to attach tag or response format incorrect"
535
+ tag_id_to_manage = attach_response["attached"][0]
536
+ assert isinstance(tag_id_to_manage, str), "Attached tag ID should be a string"
537
+ logger.info(f"✓ Tag '{initial_tag_name}' attached with ID: {tag_id_to_manage}")
538
+
539
+ # 2. Update the tag's name
540
+ logger.info(f"\nAttempting to update tag {tag_id_to_manage} to name '{updated_tag_name}'")
541
+ update_payload = {"name": updated_tag_name}
542
+ updated_tag = karakeep_client.update_a_tag(
543
+ tag_id=tag_id_to_manage, update_data=update_payload
544
+ )
545
+ # Do not check the type because karakeep 0.24.1 has a server side bug
546
+ # assert isinstance(updated_tag, datatypes.Tag1), "Update tag response should be Tag1 model"
547
+ # assert updated_tag.name == updated_tag_name, "Tag name was not updated as expected"
548
+ # logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag.name}'")
549
+ assert updated_tag["name"] == updated_tag_name, "Tag name was not updated as expected"
550
+ logger.info(f"✓ Tag {tag_id_to_manage} updated to name '{updated_tag['name']}'")
551
+
552
+ # 3. Verify tag update by getting it directly
553
+ logger.info(f"\nFetching tag {tag_id_to_manage} to verify its name is '{updated_tag_name}'")
554
+ retrieved_tag = karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
555
+ assert isinstance(retrieved_tag, datatypes.Tag1), "Get single tag response should be Tag1 model"
556
+ assert retrieved_tag.name == updated_tag_name, "Retrieved tag name does not match updated name"
557
+ assert retrieved_tag.id == tag_id_to_manage, "Retrieved tag ID does not match"
558
+ logger.info(f"✓ Verified tag {tag_id_to_manage} has name '{retrieved_tag.name}'")
559
+
560
+ # 4. Detach the tag from the bookmark
561
+ logger.info(f"\nAttempting to detach tag {tag_id_to_manage} from bookmark {bookmark_id}")
562
+ detach_payload = {"tags": [{"tagId": tag_id_to_manage}]}
563
+ detach_response = karakeep_client.detach_tags_from_a_bookmark(
564
+ bookmark_id=bookmark_id, tags_data=detach_payload
565
+ )
566
+ assert "detached" in detach_response and tag_id_to_manage in detach_response["detached"], \
567
+ "Failed to detach tag or response format incorrect"
568
+ logger.info(f"✓ Tag {tag_id_to_manage} detached from bookmark {bookmark_id}")
569
+
570
+ except (APIError, AuthenticationError) as e:
571
+ pytest.fail(f"API error during tag lifecycle test: {e}")
572
+ except Exception as e:
573
+ pytest.fail(f"An unexpected error occurred during tag lifecycle test: {e}")
572
574
  finally:
573
- # 6. Delete the bookmark (ensure cleanup)
574
- if created_bookmark_id:
575
- print(f"\nAttempting to delete bookmark ID: {created_bookmark_id} during cleanup.")
575
+ # 5. Delete the tag (ensure cleanup even if assertions fail mid-test)
576
+ if tag_id_to_manage:
577
+ logger.info(f"\nAttempting to delete tag {tag_id_to_manage} (cleanup)")
576
578
  try:
577
- karakeep_client.delete_a_bookmark(bookmark_id=created_bookmark_id)
578
- print(f"✓ Successfully deleted bookmark ID: {created_bookmark_id}")
579
+ karakeep_client.delete_a_tag(tag_id=tag_id_to_manage)
580
+ logger.info(f"✓ Successfully deleted tag {tag_id_to_manage}")
581
+
582
+ # 6. Verify the tag is gone by trying to get it (should fail with 404)
583
+ try:
584
+ karakeep_client.get_a_single_tag(tag_id=tag_id_to_manage)
585
+ pytest.fail(
586
+ f"Tag {tag_id_to_manage} should not exist after deletion, but get_a_single_tag succeeded."
587
+ )
588
+ except APIError as e:
589
+ assert e.status_code == 404, \
590
+ f"Expected 404 Not Found when getting deleted tag, but got status {e.status_code}"
591
+ logger.info(f"✓ Confirmed tag {tag_id_to_manage} is deleted (received 404).")
592
+ except (APIError, AuthenticationError) as e:
593
+ # Log error during cleanup but don't let it mask original test failure
594
+ logger.info(f" API error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
579
595
  except Exception as e:
580
- # Log cleanup failure but don't obscure original test failure if any
581
- print(f"Error during cleanup: Failed to delete bookmark {created_bookmark_id}: {e}")
582
- # Depending on test policy, you might re-raise or just log
583
- # pytest.fail(f"Failed to delete bookmark {created_bookmark_id} during cleanup: {e}")
596
+ logger.info(f" Unexpected error during tag deletion (cleanup) for ID {tag_id_to_manage}: {e}")
597
+ else:
598
+ logger.info("\nSkipping tag deletion (cleanup) because tag_id was not obtained or test failed before creation.")
584
599
 
585
600
 
586
601
  # --- Test User Info/Stats Endpoints ---
@@ -601,7 +616,7 @@ def test_get_current_user_stats(karakeep_client: KarakeepAPI):
601
616
  assert isinstance(stats["numLists"], int) and stats["numLists"] >= 0
602
617
  assert isinstance(stats["numTags"], int) and stats["numTags"] >= 0
603
618
 
604
- print(f"✓ Successfully retrieved user stats: {stats}")
619
+ logger.info(f"✓ Successfully retrieved user stats: {stats}")
605
620
 
606
621
  except (APIError, AuthenticationError) as e:
607
622
  pytest.fail(f"API error during user stats retrieval: {e}")
@@ -610,21 +625,21 @@ def test_get_current_user_stats(karakeep_client: KarakeepAPI):
610
625
 
611
626
  # --- Add CLI call ---
612
627
  try:
613
- print("\n Running CLI equivalent: get-current-user-stats")
628
+ logger.info("\n Running CLI equivalent: get-current-user-stats")
614
629
  # Assumes KARAKEEP_PYTHON_API_BASE_URL and KARAKEEP_PYTHON_API_KEY are set in env
615
630
  subprocess.run(
616
631
  "python -m karakeep_python_api get-current-user-stats",
617
632
  shell=True,
618
633
  check=True,
619
- 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
620
635
  text=True,
621
636
  )
622
- print("✓ CLI command executed successfully.")
637
+ logger.info("✓ CLI command executed successfully.")
623
638
  except subprocess.CalledProcessError as e:
624
- print(f" CLI command failed with exit code {e.returncode}")
625
- # Print stdout/stderr only if the command failed to aid debugging
626
- print(f" Stdout: {e.stdout}")
627
- 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}")
628
643
  pytest.fail(f"CLI command 'get-current-user-stats' failed: {e}")
629
644
  except Exception as e:
630
645
  pytest.fail(f"An unexpected error occurred running the CLI command: {e}")