karakeep-python-api 1.2.4__py3-none-any.whl → 1.3.1__py3-none-any.whl

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.
@@ -17,8 +17,8 @@ class StatusTypes(str, Enum):
17
17
 
18
18
 
19
19
  class NumBookmarksByAttachedType(BaseModel):
20
- ai: Optional[float] = None
21
- human: Optional[float] = None
20
+ ai: Optional[int] = None
21
+ human: Optional[int] = None
22
22
 
23
23
 
24
24
  class TagShort(BaseModel):
@@ -30,7 +30,7 @@ class TagShort(BaseModel):
30
30
  class Tag(BaseModel):
31
31
  id: str
32
32
  name: str
33
- numBookmarks: float
33
+ numBookmarks: int
34
34
  numBookmarksByAttachedType: NumBookmarksByAttachedType
35
35
 
36
36
 
@@ -112,6 +112,11 @@ class Bookmark(BaseModel):
112
112
  summarizationStatus: Optional[Literal["success", "failure", "pending"]] = None
113
113
  note: Optional[str] = None
114
114
  summary: Optional[str] = None
115
+ source: Optional[
116
+ Literal[
117
+ "api", "web", "cli", "mobile", "extension", "singlefile", "rss", "import"
118
+ ]
119
+ ] = None
115
120
  tags: List[TagShort]
116
121
  content: Union[
117
122
  ContentTypeLink, ContentTypeText, ContentTypeAsset, ContentTypeUnknown
@@ -121,7 +126,18 @@ class Bookmark(BaseModel):
121
126
 
122
127
  class PaginatedBookmarks(BaseModel):
123
128
  bookmarks: List[Bookmark]
124
- nextCursor: Optional[str]
129
+ nextCursor: Optional[str] = ""
130
+
131
+
132
+ class ListModel(BaseModel):
133
+ id: str
134
+ name: str
135
+ description: Optional[str] = None
136
+ icon: str
137
+ parentId: Optional[str]
138
+ type: Optional[Literal["manual", "smart"]] = "manual"
139
+ query: Optional[str] = None
140
+ public: bool
125
141
 
126
142
 
127
143
  class Highlight(BaseModel):
@@ -136,17 +152,11 @@ class Highlight(BaseModel):
136
152
  createdAt: str
137
153
 
138
154
 
139
- class ListModel(BaseModel):
140
- id: str
141
- name: str
142
- description: Optional[str] = None
143
- icon: str
144
- parentId: Optional[str]
145
- type: Optional[Literal["manual", "smart"]] = "manual"
146
- query: Optional[str] = None
147
- public: bool
148
-
149
-
150
155
  class PaginatedHighlights(BaseModel):
151
156
  highlights: List[Highlight]
152
- nextCursor: Optional[str]
157
+ nextCursor: Optional[str] = ""
158
+
159
+
160
+ class PaginatedTags(BaseModel):
161
+ tags: List[Tag]
162
+ nextCursor: Optional[str] = ""
@@ -85,7 +85,7 @@ class KarakeepAPI:
85
85
  """
86
86
 
87
87
  # Version reflects the client library version, updated by bumpver
88
- VERSION: str = "1.2.4"
88
+ VERSION: str = "1.3.1"
89
89
 
90
90
  def __init__(
91
91
  self,
@@ -758,6 +758,19 @@ class KarakeepAPI:
758
758
  summary: Optional[str] = None,
759
759
  createdAt: Optional[str] = None, # ISO 8601 format string
760
760
  crawlPriority: Optional[Literal["low", "normal"]] = None,
761
+ importSessionId: Optional[str] = None,
762
+ source: Optional[
763
+ Literal[
764
+ "api",
765
+ "web",
766
+ "cli",
767
+ "mobile",
768
+ "extension",
769
+ "singlefile",
770
+ "rss",
771
+ "import",
772
+ ]
773
+ ] = None,
761
774
  # Link specific
762
775
  url: Optional[str] = None,
763
776
  precrawledArchiveId: Optional[str] = None,
@@ -783,6 +796,8 @@ class KarakeepAPI:
783
796
  summary: Optional summary content for the bookmark.
784
797
  createdAt: Optional creation timestamp override (ISO 8601 format string).
785
798
  crawlPriority: Optional either 'low' or 'normal'.
799
+ importSessionId: Optional import session ID for tracking imported bookmarks.
800
+ source: Optional source of the bookmark ("api", "web", "cli", "mobile", "extension", "singlefile", "rss", "import").
786
801
 
787
802
  --- Link Type Specific ---
788
803
  url: The URL for the link bookmark. Required if type='link'.
@@ -799,7 +814,9 @@ class KarakeepAPI:
799
814
  sourceUrl: Optional source URL where the asset originated.
800
815
 
801
816
  Returns:
802
- datatypes.Bookmark: The created bookmark.
817
+ datatypes.Bookmark: The bookmark object.
818
+ Returns with status 200 if the bookmark already exists,
819
+ or status 201 if the bookmark was newly created.
803
820
  If response validation is disabled, returns the raw API response (dict/list).
804
821
 
805
822
  Raises:
@@ -825,6 +842,10 @@ class KarakeepAPI:
825
842
  request_body["createdAt"] = createdAt
826
843
  if crawlPriority is not None:
827
844
  request_body["crawlPriority"] = crawlPriority
845
+ if importSessionId is not None:
846
+ request_body["importSessionId"] = importSessionId
847
+ if source is not None:
848
+ request_body["source"] = source
828
849
 
829
850
  # Add type-specific fields and perform validation
830
851
  if type == "link":
@@ -911,7 +932,9 @@ class KarakeepAPI:
911
932
 
912
933
  @optional_typecheck
913
934
  def get_a_single_bookmark(
914
- self, bookmark_id: str, include_content: bool = True # Default from spec
935
+ self,
936
+ bookmark_id: str,
937
+ include_content: bool = True, # Default from spec
915
938
  ) -> Union[datatypes.Bookmark, Dict[str, Any], List[Any]]:
916
939
  """
917
940
  Get a single bookmark by its ID. Corresponds to GET /bookmarks/{bookmarkId}.
@@ -1169,6 +1192,54 @@ class KarakeepAPI:
1169
1192
  # No Pydantic validation applied here as the spec defines a simple dict response
1170
1193
  return response_data
1171
1194
 
1195
+ @optional_typecheck
1196
+ def get_lists_of_a_bookmark(
1197
+ self, bookmark_id: str
1198
+ ) -> Union[List[datatypes.ListModel], Dict[str, Any], List[Any]]:
1199
+ """
1200
+ Get all lists associated with a specific bookmark. Corresponds to GET /bookmarks/{bookmarkId}/lists.
1201
+
1202
+ Args:
1203
+ bookmark_id: The ID (string) of the bookmark.
1204
+
1205
+ Returns:
1206
+ List[datatypes.ListModel]: A list of list objects associated with the bookmark.
1207
+ If response validation is disabled, returns the raw API response (dict/list).
1208
+
1209
+ Raises:
1210
+ APIError: If the API request fails (e.g., 404 bookmark not found).
1211
+ pydantic.ValidationError: If response validation fails (and is not disabled).
1212
+ """
1213
+ endpoint = f"bookmarks/{bookmark_id}/lists"
1214
+ response_data = self._call("GET", endpoint)
1215
+
1216
+ if self.disable_response_validation:
1217
+ logger.debug("Skipping response validation as requested.")
1218
+ # Return raw data, which might be {"lists": [...]} or something else
1219
+ return response_data
1220
+ else:
1221
+ # Response schema is {"lists": [ListModel]}, extract the list and validate
1222
+ if (
1223
+ isinstance(response_data, dict)
1224
+ and "lists" in response_data
1225
+ and isinstance(response_data["lists"], list)
1226
+ ):
1227
+ try:
1228
+ return [
1229
+ datatypes.ListModel.model_validate(lst)
1230
+ for lst in response_data["lists"]
1231
+ ]
1232
+ except (
1233
+ Exception
1234
+ ) as e: # Catch validation errors during list comprehension
1235
+ logger.error(f"Validation failed for one or more lists: {e}")
1236
+ raise # Re-raise the validation error
1237
+ else:
1238
+ # Raise error if format is unexpected and validation is enabled
1239
+ raise APIError(
1240
+ f"Unexpected response format for get_lists_of_a_bookmark when validation is enabled: {response_data}"
1241
+ )
1242
+
1172
1243
  @optional_typecheck
1173
1244
  def get_highlights_of_a_bookmark(
1174
1245
  self, bookmark_id: str
@@ -1601,46 +1672,47 @@ class KarakeepAPI:
1601
1672
  return None # Explicitly return None for 204
1602
1673
 
1603
1674
  @optional_typecheck
1604
- def get_all_tags(self) -> Union[List[datatypes.Tag], Dict[str, Any], List[Any]]:
1675
+ def get_all_tags(
1676
+ self,
1677
+ name_contains: Optional[str] = None,
1678
+ sort: Optional[Literal["name", "usage", "relevance"]] = None,
1679
+ attached_by: Optional[Literal["ai", "human", "none"]] = None,
1680
+ cursor: Optional[str] = None,
1681
+ limit: Optional[int] = None,
1682
+ ) -> Union[datatypes.PaginatedTags, Dict[str, Any], List[Any]]:
1605
1683
  """
1606
1684
  Get all tags for the current user. Corresponds to GET /tags.
1607
1685
 
1686
+ Args:
1687
+ name_contains: Filter tags by name substring (optional).
1688
+ sort: Sort order for tags ("name", "usage", "relevance"). Default from API is "usage" (optional).
1689
+ attached_by: Filter tags by how they were attached ("ai", "human", "none") (optional).
1690
+ cursor: Pagination cursor for the next page (optional).
1691
+ limit: Maximum number of tags to return (optional).
1692
+
1608
1693
  Returns:
1609
- List[datatypes.Tag]: A list of tag objects, including bookmark counts.
1694
+ datatypes.PaginatedTags: Paginated list of tags with nextCursor for pagination.
1610
1695
  If response validation is disabled, returns the raw API response (dict/list).
1611
1696
 
1612
1697
  Raises:
1613
1698
  APIError: If the API request fails.
1614
1699
  pydantic.ValidationError: If response validation fails (and is not disabled).
1615
1700
  """
1616
- response_data = self._call("GET", "tags")
1701
+ params = {
1702
+ "nameContains": name_contains,
1703
+ "sort": sort,
1704
+ "attachedBy": attached_by,
1705
+ "cursor": cursor,
1706
+ "limit": limit,
1707
+ }
1708
+ response_data = self._call("GET", "tags", params=params)
1617
1709
 
1618
1710
  if self.disable_response_validation:
1619
1711
  logger.debug("Skipping response validation as requested.")
1620
- # Return raw data, which might be {"tags": [...]} or something else
1621
1712
  return response_data
1622
1713
  else:
1623
- # Response schema is {"tags": [Tag]}, extract the list and validate
1624
- if (
1625
- isinstance(response_data, dict)
1626
- and "tags" in response_data
1627
- and isinstance(response_data["tags"], list)
1628
- ):
1629
- try:
1630
- return [
1631
- datatypes.Tag.model_validate(tag)
1632
- for tag in response_data["tags"]
1633
- ]
1634
- except (
1635
- Exception
1636
- ) as e: # Catch validation errors during list comprehension
1637
- logger.error(f"Validation failed for one or more tags: {e}")
1638
- raise # Re-raise the validation error
1639
- else:
1640
- # Raise error if format is unexpected and validation is enabled
1641
- raise APIError(
1642
- f"Unexpected response format for get_all_tags when validation is enabled: {response_data}"
1643
- )
1714
+ # Response should match PaginatedTags schema
1715
+ return datatypes.PaginatedTags.model_validate(response_data)
1644
1716
 
1645
1717
  @optional_typecheck
1646
1718
  def create_a_new_tag(self, name: str) -> Dict[str, Any]:
@@ -94,6 +94,20 @@
94
94
  "type": "string",
95
95
  "nullable": true
96
96
  },
97
+ "source": {
98
+ "type": "string",
99
+ "nullable": true,
100
+ "enum": [
101
+ "api",
102
+ "web",
103
+ "cli",
104
+ "mobile",
105
+ "extension",
106
+ "singlefile",
107
+ "rss",
108
+ "import"
109
+ ]
110
+ },
97
111
  "tags": {
98
112
  "type": "array",
99
113
  "items": {
@@ -349,6 +363,50 @@
349
363
  "Cursor": {
350
364
  "type": "string"
351
365
  },
366
+ "List": {
367
+ "type": "object",
368
+ "properties": {
369
+ "id": {
370
+ "type": "string"
371
+ },
372
+ "name": {
373
+ "type": "string"
374
+ },
375
+ "description": {
376
+ "type": "string",
377
+ "nullable": true
378
+ },
379
+ "icon": {
380
+ "type": "string"
381
+ },
382
+ "parentId": {
383
+ "type": "string",
384
+ "nullable": true
385
+ },
386
+ "type": {
387
+ "type": "string",
388
+ "enum": [
389
+ "manual",
390
+ "smart"
391
+ ],
392
+ "default": "manual"
393
+ },
394
+ "query": {
395
+ "type": "string",
396
+ "nullable": true
397
+ },
398
+ "public": {
399
+ "type": "boolean"
400
+ }
401
+ },
402
+ "required": [
403
+ "id",
404
+ "name",
405
+ "icon",
406
+ "parentId",
407
+ "public"
408
+ ]
409
+ },
352
410
  "Highlight": {
353
411
  "type": "object",
354
412
  "properties": {
@@ -400,50 +458,6 @@
400
458
  "createdAt"
401
459
  ]
402
460
  },
403
- "List": {
404
- "type": "object",
405
- "properties": {
406
- "id": {
407
- "type": "string"
408
- },
409
- "name": {
410
- "type": "string"
411
- },
412
- "description": {
413
- "type": "string",
414
- "nullable": true
415
- },
416
- "icon": {
417
- "type": "string"
418
- },
419
- "parentId": {
420
- "type": "string",
421
- "nullable": true
422
- },
423
- "type": {
424
- "type": "string",
425
- "enum": [
426
- "manual",
427
- "smart"
428
- ],
429
- "default": "manual"
430
- },
431
- "query": {
432
- "type": "string",
433
- "nullable": true
434
- },
435
- "public": {
436
- "type": "boolean"
437
- }
438
- },
439
- "required": [
440
- "id",
441
- "name",
442
- "icon",
443
- "parentId",
444
- "public"
445
- ]
446
- },
447
461
  "Tag": {
448
462
  "type": "object",
449
463
  "properties": {
@@ -693,6 +707,22 @@
693
707
  "low",
694
708
  "normal"
695
709
  ]
710
+ },
711
+ "importSessionId": {
712
+ "type": "string"
713
+ },
714
+ "source": {
715
+ "type": "string",
716
+ "enum": [
717
+ "api",
718
+ "web",
719
+ "cli",
720
+ "mobile",
721
+ "extension",
722
+ "singlefile",
723
+ "rss",
724
+ "import"
725
+ ]
696
726
  }
697
727
  }
698
728
  },
@@ -781,8 +811,18 @@
781
811
  }
782
812
  },
783
813
  "responses": {
814
+ "200": {
815
+ "description": "The bookmark already exists",
816
+ "content": {
817
+ "application/json": {
818
+ "schema": {
819
+ "$ref": "#/components/schemas/Bookmark"
820
+ }
821
+ }
822
+ }
823
+ },
784
824
  "201": {
785
- "description": "The created bookmark",
825
+ "description": "The bookmark got created",
786
826
  "content": {
787
827
  "application/json": {
788
828
  "schema": {
@@ -1135,6 +1175,20 @@
1135
1175
  "summary": {
1136
1176
  "type": "string",
1137
1177
  "nullable": true
1178
+ },
1179
+ "source": {
1180
+ "type": "string",
1181
+ "nullable": true,
1182
+ "enum": [
1183
+ "api",
1184
+ "web",
1185
+ "cli",
1186
+ "mobile",
1187
+ "extension",
1188
+ "singlefile",
1189
+ "rss",
1190
+ "import"
1191
+ ]
1138
1192
  }
1139
1193
  },
1140
1194
  "required": [
@@ -1245,6 +1299,20 @@
1245
1299
  "summary": {
1246
1300
  "type": "string",
1247
1301
  "nullable": true
1302
+ },
1303
+ "source": {
1304
+ "type": "string",
1305
+ "nullable": true,
1306
+ "enum": [
1307
+ "api",
1308
+ "web",
1309
+ "cli",
1310
+ "mobile",
1311
+ "extension",
1312
+ "singlefile",
1313
+ "rss",
1314
+ "import"
1315
+ ]
1248
1316
  }
1249
1317
  },
1250
1318
  "required": [
@@ -1469,6 +1537,70 @@
1469
1537
  }
1470
1538
  }
1471
1539
  },
1540
+ "/bookmarks/{bookmarkId}/lists": {
1541
+ "get": {
1542
+ "description": "Get lists of a bookmark",
1543
+ "summary": "Get lists of a bookmark",
1544
+ "tags": [
1545
+ "Bookmarks"
1546
+ ],
1547
+ "security": [
1548
+ {
1549
+ "bearerAuth": []
1550
+ }
1551
+ ],
1552
+ "parameters": [
1553
+ {
1554
+ "$ref": "#/components/parameters/BookmarkId"
1555
+ }
1556
+ ],
1557
+ "responses": {
1558
+ "200": {
1559
+ "description": "The list of highlights",
1560
+ "content": {
1561
+ "application/json": {
1562
+ "schema": {
1563
+ "type": "object",
1564
+ "properties": {
1565
+ "lists": {
1566
+ "type": "array",
1567
+ "items": {
1568
+ "$ref": "#/components/schemas/List"
1569
+ }
1570
+ }
1571
+ },
1572
+ "required": [
1573
+ "lists"
1574
+ ]
1575
+ }
1576
+ }
1577
+ }
1578
+ },
1579
+ "404": {
1580
+ "description": "Bookmark not found",
1581
+ "content": {
1582
+ "application/json": {
1583
+ "schema": {
1584
+ "type": "object",
1585
+ "properties": {
1586
+ "code": {
1587
+ "type": "string"
1588
+ },
1589
+ "message": {
1590
+ "type": "string"
1591
+ }
1592
+ },
1593
+ "required": [
1594
+ "code",
1595
+ "message"
1596
+ ]
1597
+ }
1598
+ }
1599
+ }
1600
+ }
1601
+ }
1602
+ }
1603
+ },
1472
1604
  "/bookmarks/{bookmarkId}/highlights": {
1473
1605
  "get": {
1474
1606
  "description": "Get highlights of a bookmark",
@@ -2292,6 +2424,60 @@
2292
2424
  "bearerAuth": []
2293
2425
  }
2294
2426
  ],
2427
+ "parameters": [
2428
+ {
2429
+ "schema": {
2430
+ "type": "string"
2431
+ },
2432
+ "required": false,
2433
+ "name": "nameContains",
2434
+ "in": "query"
2435
+ },
2436
+ {
2437
+ "schema": {
2438
+ "type": "string",
2439
+ "enum": [
2440
+ "name",
2441
+ "usage",
2442
+ "relevance"
2443
+ ],
2444
+ "default": "usage"
2445
+ },
2446
+ "required": false,
2447
+ "name": "sort",
2448
+ "in": "query"
2449
+ },
2450
+ {
2451
+ "schema": {
2452
+ "type": "string",
2453
+ "enum": [
2454
+ "ai",
2455
+ "human",
2456
+ "none"
2457
+ ]
2458
+ },
2459
+ "required": false,
2460
+ "name": "attachedBy",
2461
+ "in": "query"
2462
+ },
2463
+ {
2464
+ "schema": {
2465
+ "type": "string"
2466
+ },
2467
+ "required": false,
2468
+ "name": "cursor",
2469
+ "in": "query"
2470
+ },
2471
+ {
2472
+ "schema": {
2473
+ "type": "number",
2474
+ "nullable": true
2475
+ },
2476
+ "required": false,
2477
+ "name": "limit",
2478
+ "in": "query"
2479
+ }
2480
+ ],
2295
2481
  "responses": {
2296
2482
  "200": {
2297
2483
  "description": "Object with all tags data.",
@@ -2305,10 +2491,15 @@
2305
2491
  "items": {
2306
2492
  "$ref": "#/components/schemas/Tag"
2307
2493
  }
2494
+ },
2495
+ "nextCursor": {
2496
+ "type": "string",
2497
+ "nullable": true
2308
2498
  }
2309
2499
  },
2310
2500
  "required": [
2311
- "tags"
2501
+ "tags",
2502
+ "nextCursor"
2312
2503
  ]
2313
2504
  }
2314
2505
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: karakeep_python_api
3
- Version: 1.2.4
3
+ Version: 1.3.1
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
@@ -0,0 +1,11 @@
1
+ karakeep_python_api/__init__.py,sha256=qk3MeIIvnCNAyYzJrv8dMKVXvA4ejLU3mhB3xFzdLDY,606
2
+ karakeep_python_api/__main__.py,sha256=QP_TbmgNZE6Fx7tHvWLYaUKoYIvcv-qFYdNXx_x__9A,31679
3
+ karakeep_python_api/datatypes.py,sha256=WQwugxhxdLR24gIY3tnB5ZoW7H_DD9T5KDOR-mMQZN4,3838
4
+ karakeep_python_api/karakeep_api.py,sha256=ZB2LxjYf0b_W6sIdAbjPhmIsCMYaj7aRnkg7-7XF4cA,98397
5
+ karakeep_python_api/openapi_reference.json,sha256=XCk9W0XRYoGtEqlqHstqjIQPz4QDuYzEzxXfExc7UCc,97877
6
+ karakeep_python_api-1.3.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
+ karakeep_python_api-1.3.1.dist-info/METADATA,sha256=Po19wuFL4NKleh7hnjRtEaBidyA9DbSzRFybHnr_bKw,16382
8
+ karakeep_python_api-1.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ karakeep_python_api-1.3.1.dist-info/entry_points.txt,sha256=n0leQp_IX2NivoWuUcaR9ZHwAvl_6yt-NcHWuCJyxNo,62
10
+ karakeep_python_api-1.3.1.dist-info/top_level.txt,sha256=X3VKqh9YAbPQp144db0Ko2C5Q2y6-nwpPgtnuCiSDmU,20
11
+ karakeep_python_api-1.3.1.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- karakeep_python_api/__init__.py,sha256=qk3MeIIvnCNAyYzJrv8dMKVXvA4ejLU3mhB3xFzdLDY,606
2
- karakeep_python_api/__main__.py,sha256=QP_TbmgNZE6Fx7tHvWLYaUKoYIvcv-qFYdNXx_x__9A,31679
3
- karakeep_python_api/datatypes.py,sha256=EmEpjGgXz6faXDJ4fGJkT52wSW98WUovoCSCOPsJBME,3597
4
- karakeep_python_api/karakeep_api.py,sha256=-Xsz1CqdnOpbKuklo7Yb-a4ZsYiPzFxSZd_wdpZLX84,95389
5
- karakeep_python_api/openapi_reference.json,sha256=sA2b9_LnqnpJ8gscjT6kST93iSXAmeDMEBE7dEI4AGo,92730
6
- karakeep_python_api-1.2.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
7
- karakeep_python_api-1.2.4.dist-info/METADATA,sha256=w16CnUqfIG_5us4U5yNqHI3V3alI-acOeDyzHK6i5jQ,16382
8
- karakeep_python_api-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- karakeep_python_api-1.2.4.dist-info/entry_points.txt,sha256=n0leQp_IX2NivoWuUcaR9ZHwAvl_6yt-NcHWuCJyxNo,62
10
- karakeep_python_api-1.2.4.dist-info/top_level.txt,sha256=X3VKqh9YAbPQp144db0Ko2C5Q2y6-nwpPgtnuCiSDmU,20
11
- karakeep_python_api-1.2.4.dist-info/RECORD,,