karakeep-python-api 0.1.7__tar.gz → 0.2.0__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.7/karakeep_python_api.egg-info → karakeep_python_api-0.2.0}/PKG-INFO +4 -3
  2. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/README.md +3 -2
  3. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api/__main__.py +130 -65
  4. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api/datatypes.py +42 -41
  5. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api/karakeep_api.py +251 -83
  6. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api/openapi_reference.json +157 -7
  7. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0/karakeep_python_api.egg-info}/PKG-INFO +4 -3
  8. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/setup.py +1 -1
  9. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/tests/test_karakeep_api.py +10 -6
  10. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/LICENSE +0 -0
  11. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/MANIFEST.in +0 -0
  12. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api/__init__.py +0 -0
  13. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api.egg-info/SOURCES.txt +0 -0
  14. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api.egg-info/dependency_links.txt +0 -0
  15. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api.egg-info/entry_points.txt +0 -0
  16. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api.egg-info/requires.txt +0 -0
  17. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/karakeep_python_api.egg-info/top_level.txt +0 -0
  18. {karakeep_python_api-0.1.7 → karakeep_python_api-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 0.1.7
3
+ Version: 0.2.0
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
@@ -97,14 +97,15 @@ Methods or CLI commands marked with ❌ should be used with caution as their beh
97
97
  | `get_a_single_list` | ✅ | ❌ | |
98
98
  | `delete_a_list` | ✅ | ❌ | |
99
99
  | `update_a_list` | ❌ | ❌ | |
100
- | `get_a_bookmarks_in_a_list` | ❌ | ❌ | |
100
+ | `get_bookmarks_in_the_list` | ❌ | ❌ | |
101
101
  | `add_a_bookmark_to_a_list` | ❌ | ❌ | |
102
102
  | `remove_a_bookmark_from_a_list` | ❌ | ❌ | |
103
103
  | `get_all_tags` | ✅ | ✅ | |
104
+ | `create_a_new_tag` | ❌ | ❌ | |
104
105
  | `get_a_single_tag` | ✅ | ❌ | |
105
106
  | `delete_a_tag` | ✅ | ❌ | |
106
107
  | `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_bookmarks_with_the_tag` | ❌ | ❌ | |
108
109
  | `get_all_highlights` | ✅ | ✅ | Tested with pagination. |
109
110
  | `create_a_new_highlight` | ❌ | ❌ | |
110
111
  | `get_a_single_highlight` | ❌ | ❌ | |
@@ -62,14 +62,15 @@ Methods or CLI commands marked with ❌ should be used with caution as their beh
62
62
  | `get_a_single_list` | ✅ | ❌ | |
63
63
  | `delete_a_list` | ✅ | ❌ | |
64
64
  | `update_a_list` | ❌ | ❌ | |
65
- | `get_a_bookmarks_in_a_list` | ❌ | ❌ | |
65
+ | `get_bookmarks_in_the_list` | ❌ | ❌ | |
66
66
  | `add_a_bookmark_to_a_list` | ❌ | ❌ | |
67
67
  | `remove_a_bookmark_from_a_list` | ❌ | ❌ | |
68
68
  | `get_all_tags` | ✅ | ✅ | |
69
+ | `create_a_new_tag` | ❌ | ❌ | |
69
70
  | `get_a_single_tag` | ✅ | ❌ | |
70
71
  | `delete_a_tag` | ✅ | ❌ | |
71
72
  | `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_bookmarks_with_the_tag` | ❌ | ❌ | |
73
74
  | `get_all_highlights` | ✅ | ✅ | Tested with pagination. |
74
75
  | `create_a_new_highlight` | ❌ | ❌ | |
75
76
  | `get_a_single_highlight` | ❌ | ❌ | |
@@ -7,7 +7,17 @@ import functools
7
7
  import re # Import re module
8
8
  import click
9
9
  import traceback # Moved import to top
10
- from typing import Any, List, Dict, Optional, Callable, Union, get_origin, get_args, Literal
10
+ from typing import (
11
+ Any,
12
+ List,
13
+ Dict,
14
+ Optional,
15
+ Callable,
16
+ Union,
17
+ get_origin,
18
+ get_args,
19
+ Literal,
20
+ )
11
21
  from pydantic import BaseModel, ValidationError
12
22
  from loguru import logger # Import logger
13
23
 
@@ -82,9 +92,9 @@ shared_options = [
82
92
  ),
83
93
  click.option(
84
94
  "--ascii",
85
- "ensure_ascii", # Use 'ensure_ascii' as the destination variable name
95
+ "ensure_ascii", # Use 'ensure_ascii' as the destination variable name
86
96
  is_flag=True,
87
- default=False, # Default is False, meaning ensure_ascii=False by default
97
+ default=False, # Default is False, meaning ensure_ascii=False by default
88
98
  envvar="KARAKEEP_PYTHON_API_ENSURE_ASCII",
89
99
  help="Escape non-ASCII characters in the JSON output (default: keep Unicode).",
90
100
  ),
@@ -135,9 +145,19 @@ def print_openapi_spec(ctx, param, value):
135
145
  is_eager=True, # Process this option before others
136
146
  help="Dump the OpenAPI specification JSON to stdout and exit.",
137
147
  )
138
- @add_options(shared_options) # Apply shared options to the group (ensure_ascii is now included)
148
+ @add_options(
149
+ shared_options
150
+ ) # Apply shared options to the group (ensure_ascii is now included)
139
151
  @click.pass_context
140
- def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation, ensure_ascii):
152
+ def cli(
153
+ ctx,
154
+ base_url,
155
+ api_key,
156
+ verify_ssl,
157
+ verbose,
158
+ disable_response_validation,
159
+ ensure_ascii,
160
+ ):
141
161
  """
142
162
  Karakeep Python API Command Line Interface.
143
163
 
@@ -171,7 +191,7 @@ def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation
171
191
  ctx.obj["DISABLE_RESPONSE_VALIDATION"] = (
172
192
  disable_response_validation # Store the flag
173
193
  )
174
- ctx.obj["ENSURE_ASCII"] = ensure_ascii # Store the ensure_ascii flag
194
+ ctx.obj["ENSURE_ASCII"] = ensure_ascii # Store the ensure_ascii flag
175
195
 
176
196
  # --- Configure Logger ---
177
197
  log_level = "DEBUG" if verbose else "INFO"
@@ -184,7 +204,7 @@ def cli(ctx, base_url, api_key, verify_ssl, verbose, disable_response_validation
184
204
  )
185
205
  logger.debug("Verbose logging enabled with detailed format.")
186
206
  else:
187
- logger.add(sys.stderr, level=log_level) # Default format for INFO
207
+ logger.add(sys.stderr, level=log_level) # Default format for INFO
188
208
  logger.debug("Logger configured for level: {}", log_level)
189
209
  logger.debug("CLI context initialized.")
190
210
 
@@ -216,7 +236,7 @@ def create_click_command(
216
236
  verify_ssl = ctx.obj["VERIFY_SSL"]
217
237
  verbose = ctx.obj["VERBOSE"]
218
238
  disable_validation = ctx.obj["DISABLE_RESPONSE_VALIDATION"] # Retrieve flag
219
- ensure_ascii_output = ctx.obj["ENSURE_ASCII"] # Retrieve ensure_ascii flag
239
+ ensure_ascii_output = ctx.obj["ENSURE_ASCII"] # Retrieve ensure_ascii flag
220
240
 
221
241
  if not api_key:
222
242
  click.echo(
@@ -253,9 +273,7 @@ def create_click_command(
253
273
  # Remove arguments that are not part of the method signature
254
274
  # (e.g., if extra options were somehow passed)
255
275
  valid_arg_names = set(signature.parameters.keys())
256
- call_args = {
257
- k: v for k, v in call_args.items() if k in valid_arg_names
258
- }
276
+ call_args = {k: v for k, v in call_args.items() if k in valid_arg_names}
259
277
 
260
278
  # --- JSON Parsing for Dict/List Parameters ---
261
279
  # Iterate through the expected parameters from the signature
@@ -268,13 +286,15 @@ def create_click_command(
268
286
  # Check if the annotation is dict/list or typing.Dict/List
269
287
  # and if the received value is a string (needs parsing)
270
288
  if (
271
- (param_annotation in (dict, list) or origin in (dict, list, Dict, List))
272
- and isinstance(param_value, str)
273
- ):
289
+ param_annotation in (dict, list)
290
+ or origin in (dict, list, Dict, List)
291
+ ) and isinstance(param_value, str):
274
292
  try:
275
293
  # Attempt to parse the JSON string
276
294
  call_args[param_name] = json.loads(param_value)
277
- logger.debug(f"Parsed JSON string for parameter '{param_name}'.")
295
+ logger.debug(
296
+ f"Parsed JSON string for parameter '{param_name}'."
297
+ )
278
298
  except json.JSONDecodeError as json_err:
279
299
  # Handle invalid JSON input from the user
280
300
  click.echo(
@@ -287,89 +307,122 @@ def create_click_command(
287
307
  # Call the API method
288
308
  try:
289
309
  if method_name == "get_all_bookmarks":
290
- logger.debug(f"Special CLI pagination handling for '{method_name}'.")
291
- cli_total_limit = call_args.pop('limit', None)
310
+ logger.debug(
311
+ f"Special CLI pagination handling for '{method_name}'."
312
+ )
313
+ cli_total_limit = call_args.pop("limit", None)
292
314
  # Other relevant params for get_all_bookmarks
293
- archived_filter = call_args.get('archived')
294
- favourited_filter = call_args.get('favourited')
295
- include_content_cli = call_args.get('include_content', True)
296
-
297
- call_args.pop('cursor', None) # Ignore CLI cursor
315
+ archived_filter = call_args.get("archived")
316
+ favourited_filter = call_args.get("favourited")
317
+ include_content_cli = call_args.get("include_content", True)
318
+
319
+ call_args.pop("cursor", None) # Ignore CLI cursor
298
320
 
299
321
  all_bookmarks_data = []
300
322
  current_page_api_cursor = None
301
323
  fetched_count = 0
302
- API_INTERNAL_PAGE_SIZE = 50 # Define a page size for API calls
324
+ API_INTERNAL_PAGE_SIZE = 50 # Define a page size for API calls
303
325
 
304
326
  while True:
305
327
  api_call_limit = API_INTERNAL_PAGE_SIZE
306
328
  if cli_total_limit is not None:
307
329
  remaining_needed = cli_total_limit - fetched_count
308
330
  if remaining_needed <= 0:
309
- break # Reached or exceeded CLI total limit
310
- api_call_limit = min(API_INTERNAL_PAGE_SIZE, remaining_needed)
311
-
312
- if api_call_limit <= 0 and cli_total_limit is not None : # Avoid asking for 0 or negative items unless fetching all
331
+ break # Reached or exceeded CLI total limit
332
+ api_call_limit = min(
333
+ API_INTERNAL_PAGE_SIZE, remaining_needed
334
+ )
335
+
336
+ if (
337
+ api_call_limit <= 0 and cli_total_limit is not None
338
+ ): # Avoid asking for 0 or negative items unless fetching all
313
339
  break
314
340
 
341
+ logger.debug(
342
+ f"Fetching page for '{method_name}' with cursor: {current_page_api_cursor}, api_limit: {api_call_limit}"
343
+ )
315
344
 
316
- logger.debug(f"Fetching page for '{method_name}' with cursor: {current_page_api_cursor}, api_limit: {api_call_limit}")
317
-
318
345
  page_call_args = {
319
- 'archived': archived_filter,
320
- 'favourited': favourited_filter,
321
- 'limit': api_call_limit,
322
- 'cursor': current_page_api_cursor,
323
- 'include_content': include_content_cli,
346
+ "archived": archived_filter,
347
+ "favourited": favourited_filter,
348
+ "limit": api_call_limit,
349
+ "cursor": current_page_api_cursor,
350
+ "include_content": include_content_cli,
351
+ }
352
+ page_call_args_filtered = {
353
+ k: v for k, v in page_call_args.items() if v is not None
324
354
  }
325
- page_call_args_filtered = {k: v for k, v in page_call_args.items() if v is not None}
326
355
 
327
356
  try:
328
- page_result_obj = instance_method(**page_call_args_filtered)
357
+ page_result_obj = instance_method(
358
+ **page_call_args_filtered
359
+ )
329
360
  except TypeError as call_error_page:
330
- logger.error(f"Error calling API method '{method_name}' (paginated): {call_error_page}")
331
- logger.error(f"Provided arguments for page: {page_call_args_filtered}")
332
- if verbose: logger.debug(traceback.format_exc())
361
+ logger.error(
362
+ f"Error calling API method '{method_name}' (paginated): {call_error_page}"
363
+ )
364
+ logger.error(
365
+ f"Provided arguments for page: {page_call_args_filtered}"
366
+ )
367
+ if verbose:
368
+ logger.debug(traceback.format_exc())
333
369
  ctx.exit(1)
334
370
 
335
371
  bookmarks_on_this_page = []
336
372
  next_api_cursor = None
337
373
 
338
374
  # Convert Pydantic model to dict using model_dump if available
339
- if hasattr(page_result_obj, 'model_dump'):
375
+ if hasattr(page_result_obj, "model_dump"):
340
376
  result_dict = page_result_obj.model_dump()
341
377
  elif isinstance(page_result_obj, dict):
342
378
  result_dict = page_result_obj
343
379
  else:
344
- logger.warning(f"Unexpected result type: {type(page_result_obj)}")
380
+ logger.warning(
381
+ f"Unexpected result type: {type(page_result_obj)}"
382
+ )
345
383
  result_dict = {}
346
384
 
347
385
  # Extract data and cursor from the dict
348
386
  bookmarks_on_this_page = result_dict.get("bookmarks", [])
349
387
  next_api_cursor = result_dict.get("nextCursor")
350
388
 
351
- logger.debug(f"Extracted {len(bookmarks_on_this_page)} bookmarks and cursor: {next_api_cursor}")
352
-
389
+ logger.debug(
390
+ f"Extracted {len(bookmarks_on_this_page)} bookmarks and cursor: {next_api_cursor}"
391
+ )
392
+
353
393
  if not isinstance(bookmarks_on_this_page, list):
354
- logger.warning(f"Expected a list of bookmarks, got {type(bookmarks_on_this_page)}. Stopping pagination.")
394
+ logger.warning(
395
+ f"Expected a list of bookmarks, got {type(bookmarks_on_this_page)}. Stopping pagination."
396
+ )
355
397
  break
356
-
398
+
357
399
  all_bookmarks_data.extend(bookmarks_on_this_page)
358
400
  fetched_count += len(bookmarks_on_this_page)
359
- logger.debug(f"Fetched {len(bookmarks_on_this_page)} bookmarks this page. Total fetched: {fetched_count}.")
401
+ logger.debug(
402
+ f"Fetched {len(bookmarks_on_this_page)} bookmarks this page. Total fetched: {fetched_count}."
403
+ )
360
404
 
361
405
  current_page_api_cursor = next_api_cursor
362
406
  if not current_page_api_cursor:
363
- logger.debug("No nextCursor from API, pagination complete.")
407
+ logger.debug(
408
+ "No nextCursor from API, pagination complete."
409
+ )
364
410
  break
365
- if cli_total_limit is not None and fetched_count >= cli_total_limit:
366
- logger.debug(f"CLI total limit of {cli_total_limit} reached or exceeded.")
411
+ if (
412
+ cli_total_limit is not None
413
+ and fetched_count >= cli_total_limit
414
+ ):
415
+ logger.debug(
416
+ f"CLI total limit of {cli_total_limit} reached or exceeded."
417
+ )
367
418
  break
368
419
  if not bookmarks_on_this_page and api_call_limit > 0:
369
- logger.debug("API returned an empty list of bookmarks while a positive limit was set, assuming end of data.")
420
+ logger.debug(
421
+ "API returned an empty list of bookmarks while a positive limit was set, assuming end of data."
422
+ )
370
423
  break
371
-
372
- result = all_bookmarks_data # This will be a list of Bookmark models or dicts
424
+
425
+ result = all_bookmarks_data # This will be a list of Bookmark models or dicts
373
426
  else:
374
427
  # Original behavior for other commands
375
428
  logger.debug(
@@ -393,7 +446,9 @@ def create_click_command(
393
446
  output_data = serialize_output(result)
394
447
  # Use ensure_ascii_output flag to control JSON encoding
395
448
  click.echo(
396
- json.dumps(output_data, indent=2, ensure_ascii=ensure_ascii_output)
449
+ json.dumps(
450
+ output_data, indent=2, ensure_ascii=ensure_ascii_output
451
+ )
397
452
  )
398
453
  else:
399
454
  # Handle None result (e.g., 204 No Content) gracefully
@@ -434,7 +489,9 @@ def create_click_command(
434
489
  docstring = api_method.__doc__ or f"Execute the {api_method_name} API operation."
435
490
  docstring = dedent(docstring)
436
491
  docstring_lines = docstring.split("\n")
437
- help_text = " ".join(docstring.split("\n\n")[0].splitlines()).strip() # First lines as short help
492
+ help_text = " ".join(
493
+ docstring.split("\n\n")[0].splitlines()
494
+ ).strip() # First lines as short help
438
495
  # Full docstring as help
439
496
  full_help = docstring
440
497
 
@@ -452,9 +509,7 @@ def create_click_command(
452
509
  stripped_line = line.strip()
453
510
  if stripped_line == "Args:":
454
511
  in_args_section = True
455
- elif (
456
- stripped_line == "Returns:" or stripped_line == "Raises:"
457
- ):
512
+ elif stripped_line == "Returns:" or stripped_line == "Raises:":
458
513
  in_args_section = False # Stop capturing when Returns/Raises section starts
459
514
  elif in_args_section and stripped_line:
460
515
  args_section_lines.append(stripped_line)
@@ -465,7 +520,9 @@ def create_click_command(
465
520
  param_name = match.group(1)
466
521
  description = match.group(2).strip()
467
522
  param_descriptions[param_name] = description
468
- logger.trace(f"Parsed docstring param: '{param_name}' -> '{description}'")
523
+ logger.trace(
524
+ f"Parsed docstring param: '{param_name}' -> '{description}'"
525
+ )
469
526
  else:
470
527
  param_descriptions[param_name] += " " + stripped_line
471
528
 
@@ -524,7 +581,7 @@ def create_click_command(
524
581
  logger.warning(
525
582
  f"Parameter '{param.name}' is Literal but contains non-string types. Treating as STRING."
526
583
  )
527
- click_type = click.STRING # Fallback
584
+ click_type = click.STRING # Fallback
528
585
 
529
586
  # Determine option name(s) and help text
530
587
  option_names = [f"--{param_name_cli}"]
@@ -543,7 +600,7 @@ def create_click_command(
543
600
  ):
544
601
  param_help += " (Provide as JSON string)"
545
602
  elif isinstance(click_type, click.Choice):
546
- param_help += f" (Choices: {', '.join(click_type.choices)})"
603
+ param_help += f" (Choices: {', '.join(click_type.choices)})"
547
604
 
548
605
  click_required = is_required_in_sig and default_value is None and not is_flag
549
606
 
@@ -551,14 +608,22 @@ def create_click_command(
551
608
  current_param_help = param_help
552
609
  current_click_required = click_required
553
610
  current_default_value = default_value
554
- current_is_flag = is_flag # Though is_flag interpretation might change help/required
555
-
611
+ current_is_flag = (
612
+ is_flag # Though is_flag interpretation might change help/required
613
+ )
614
+
556
615
  # Special handling for 'get_all_bookmarks' command parameters
557
616
  if api_method_name == "get_all_bookmarks":
558
617
  if param.name == "cursor":
559
- current_param_help = "[Ignored by CLI for get-all-bookmarks] " + param_help
560
- current_click_required = False # Cursor is handled by CLI, not required from user
561
- current_default_value = None # Explicitly set default to None for ignored param
618
+ current_param_help = (
619
+ "[Ignored by CLI for get-all-bookmarks] " + param_help
620
+ )
621
+ current_click_required = (
622
+ False # Cursor is handled by CLI, not required from user
623
+ )
624
+ current_default_value = (
625
+ None # Explicitly set default to None for ignored param
626
+ )
562
627
  elif param.name == "limit":
563
628
  current_param_help = "Total maximum number of bookmarks to fetch across pages for get-all-bookmarks. If omitted, all are fetched."
564
629
  # For 'limit', required status and default remain as derived from its Optional[int] type hint
@@ -22,11 +22,11 @@ class ListId(RootModel[str]):
22
22
  root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
23
23
 
24
24
 
25
- class TagId(RootModel[str]):
25
+ class HighlightId(RootModel[str]):
26
26
  root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
27
27
 
28
28
 
29
- class HighlightId(RootModel[str]):
29
+ class TagId(RootModel[str]):
30
30
  root: str = Field(..., example="ieidlxygmwj87oxz5hxttoc8")
31
31
 
32
32
 
@@ -41,12 +41,19 @@ class AttachedBy(str, Enum):
41
41
  human = "human"
42
42
 
43
43
 
44
- class Tag(BaseModel):
44
+ class TagShort(BaseModel):
45
45
  id: str
46
46
  name: str
47
47
  attachedBy: AttachedBy
48
48
 
49
49
 
50
+ class Tag(BaseModel):
51
+ id: str
52
+ name: str
53
+ numBookmarks: float
54
+ numBookmarksByAttachedType: NumBookmarksByAttachedType
55
+
56
+
50
57
  class Type(str, Enum):
51
58
  link = "link"
52
59
 
@@ -71,28 +78,36 @@ class Content(BaseModel):
71
78
  dateModified: Optional[str] = None
72
79
 
73
80
 
74
- class Type1(str, Enum):
81
+ class TypeText(str, Enum):
75
82
  text = "text"
76
83
 
77
84
 
78
- class Content1(BaseModel):
79
- type: Type1
80
- text: str
81
- sourceUrl: Optional[str] = None
85
+ class TypeAsset(str, Enum):
86
+ asset = "asset"
82
87
 
83
88
 
84
- class Type2(str, Enum):
85
- asset = "asset"
89
+ class TypeUnknown(str, Enum):
90
+ unknown = "unknown"
86
91
 
87
92
 
88
- class AssetType(str, Enum):
93
+ class ContentUnknown(BaseModel):
94
+ type: TypeUnknown
95
+
96
+
97
+ class AssetTypeAssetType(str, Enum):
89
98
  image = "image"
90
99
  pdf = "pdf"
91
100
 
92
101
 
93
- class Content2(BaseModel):
94
- type: Type2
95
- assetType: AssetType
102
+ class ContentTypeText(BaseModel):
103
+ type: TypeText
104
+ text: str
105
+ sourceUrl: Optional[str] = None
106
+
107
+
108
+ class ContentTypeAsset(BaseModel):
109
+ type: TypeAsset
110
+ assetType: AssetTypeAssetType
96
111
  assetId: str
97
112
  fileName: Optional[str] = None
98
113
  sourceUrl: Optional[str] = None
@@ -100,15 +115,7 @@ class Content2(BaseModel):
100
115
  content: Optional[str] = None
101
116
 
102
117
 
103
- class Type3(str, Enum):
104
- unknown = "unknown"
105
-
106
-
107
- class Content3(BaseModel):
108
- type: Type3
109
-
110
-
111
- class AssetType1(str, Enum):
118
+ class AssetType(str, Enum):
112
119
  screenshot = "screenshot"
113
120
  assetScreenshot = "assetScreenshot"
114
121
  bannerImage = "bannerImage"
@@ -121,27 +128,28 @@ class AssetType1(str, Enum):
121
128
 
122
129
  class Asset(BaseModel):
123
130
  id: str
124
- assetType: AssetType1
131
+ assetType: AssetType
125
132
 
126
133
 
127
134
  class Bookmark(BaseModel):
128
135
  id: str
129
136
  createdAt: str
130
- modifiedAt: str
137
+ modifiedAt: Optional[str]
131
138
  title: Optional[str] = None
132
139
  archived: bool
133
140
  favourited: bool
134
141
  taggingStatus: TaggingStatus
142
+ summarizationStatus: Optional[TaggingStatus] = None
135
143
  note: Optional[str] = None
136
144
  summary: Optional[str] = None
137
- tags: List[Tag]
138
- content: Union[Content, Content1, Content2, Content3]
145
+ tags: List[TagShort]
146
+ content: Union[Content, ContentTypeText, ContentTypeAsset, ContentUnknown]
139
147
  assets: List[Asset]
140
148
 
141
149
 
142
150
  class PaginatedBookmarks(BaseModel):
143
151
  bookmarks: List[Bookmark]
144
- nextCursor: Optional[str] = None # Allow nextCursor to be None
152
+ nextCursor: Optional[str]
145
153
 
146
154
 
147
155
  class Cursor(RootModel[str]):
@@ -160,14 +168,14 @@ class Highlight(BaseModel):
160
168
  startOffset: float
161
169
  endOffset: float
162
170
  color: Optional[Color] = "yellow"
163
- text: Optional[str] = None # Allow None for text
164
- note: Optional[str] = None # Allow None for note
171
+ text: Optional[str]
172
+ note: Optional[str]
165
173
  id: str
166
174
  userId: str
167
175
  createdAt: str
168
176
 
169
177
 
170
- class Type4(str, Enum):
178
+ class ListType(str, Enum):
171
179
  manual = "manual"
172
180
  smart = "smart"
173
181
 
@@ -177,8 +185,8 @@ class ListModel(BaseModel):
177
185
  name: str
178
186
  description: Optional[str] = None
179
187
  icon: str
180
- parentId: Optional[str] = None # Allow parentId to be None (nullable)
181
- type: Optional[Type4] = "manual"
188
+ parentId: Optional[str]
189
+ type: Optional[ListType] = "manual"
182
190
  query: Optional[str] = None
183
191
 
184
192
 
@@ -187,13 +195,6 @@ class NumBookmarksByAttachedType(BaseModel):
187
195
  human: Optional[float] = None
188
196
 
189
197
 
190
- class Tag1(BaseModel):
191
- id: str
192
- name: str
193
- numBookmarks: float
194
- numBookmarksByAttachedType: NumBookmarksByAttachedType
195
-
196
-
197
198
  class PaginatedHighlights(BaseModel):
198
199
  highlights: List[Highlight]
199
- nextCursor: Optional[str] = None # Allow nextCursor to be None
200
+ nextCursor: Optional[str]