chunkr-ai 0.0.12__py3-none-any.whl → 0.0.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
chunkr_ai/api/schema.py CHANGED
@@ -2,17 +2,22 @@ from pydantic import BaseModel
2
2
  from typing import Optional, List, Union, Type
3
3
  import json
4
4
 
5
+
5
6
  class Property(BaseModel):
6
7
  name: str
7
8
  prop_type: str
8
9
  description: Optional[str] = None
9
10
  default: Optional[str] = None
10
11
 
12
+
11
13
  class JsonSchema(BaseModel):
12
14
  title: str
13
15
  properties: List[Property]
14
16
 
15
- def from_pydantic(pydantic: Union[BaseModel, Type[BaseModel]], current_depth: int = 0) -> dict:
17
+
18
+ def from_pydantic(
19
+ pydantic: Union[BaseModel, Type[BaseModel]], current_depth: int = 0
20
+ ) -> dict:
16
21
  """Convert a Pydantic model to a Chunk json schema."""
17
22
  MAX_DEPTH = 5
18
23
  model = pydantic if isinstance(pydantic, type) else pydantic.__class__
@@ -21,108 +26,111 @@ def from_pydantic(pydantic: Union[BaseModel, Type[BaseModel]], current_depth: in
21
26
 
22
27
  def get_enum_description(details: dict) -> str:
23
28
  """Get description including enum values if they exist"""
24
- description = details.get('description', '')
25
-
29
+ description = details.get("description", "")
30
+
26
31
  # First check if this is a direct enum
27
- if 'enum' in details:
28
- enum_values = details['enum']
29
- enum_str = '\nAllowed values:\n' + '\n'.join(f'- {val}' for val in enum_values)
32
+ if "enum" in details:
33
+ enum_values = details["enum"]
34
+ enum_str = "\nAllowed values:\n" + "\n".join(
35
+ f"- {val}" for val in enum_values
36
+ )
30
37
  return f"{description}{enum_str}"
31
-
38
+
32
39
  # Then check if it's a reference to an enum
33
- if '$ref' in details:
34
- ref_schema = resolve_ref(details['$ref'], schema.get('$defs', {}))
35
- if 'enum' in ref_schema:
36
- enum_values = ref_schema['enum']
37
- enum_str = '\nAllowed values:\n' + '\n'.join(f'- {val}' for val in enum_values)
40
+ if "$ref" in details:
41
+ ref_schema = resolve_ref(details["$ref"], schema.get("$defs", {}))
42
+ if "enum" in ref_schema:
43
+ enum_values = ref_schema["enum"]
44
+ enum_str = "\nAllowed values:\n" + "\n".join(
45
+ f"- {val}" for val in enum_values
46
+ )
38
47
  return f"{description}{enum_str}"
39
-
48
+
40
49
  return description
41
50
 
42
51
  def resolve_ref(ref: str, definitions: dict) -> dict:
43
52
  """Resolve a $ref reference to its actual schema"""
44
- if not ref.startswith('#/$defs/'):
53
+ if not ref.startswith("#/$defs/"):
45
54
  return {}
46
- ref_name = ref[len('#/$defs/'):]
55
+ ref_name = ref[len("#/$defs/") :]
47
56
  return definitions.get(ref_name, {})
48
57
 
49
58
  def get_nested_schema(field_schema: dict, depth: int) -> dict:
50
59
  if depth >= MAX_DEPTH:
51
60
  return {}
52
-
61
+
53
62
  # If there's a $ref, resolve it first
54
- if '$ref' in field_schema:
55
- field_schema = resolve_ref(field_schema['$ref'], schema.get('$defs', {}))
56
-
63
+ if "$ref" in field_schema:
64
+ field_schema = resolve_ref(field_schema["$ref"], schema.get("$defs", {}))
65
+
57
66
  nested_props = {}
58
- if field_schema.get('type') == 'object':
59
- for name, details in field_schema.get('properties', {}).items():
60
- if details.get('type') == 'object' or '$ref' in details:
67
+ if field_schema.get("type") == "object":
68
+ for name, details in field_schema.get("properties", {}).items():
69
+ if details.get("type") == "object" or "$ref" in details:
61
70
  ref_schema = details
62
- if '$ref' in details:
63
- ref_schema = resolve_ref(details['$ref'], schema.get('$defs', {}))
71
+ if "$ref" in details:
72
+ ref_schema = resolve_ref(
73
+ details["$ref"], schema.get("$defs", {})
74
+ )
64
75
  nested_schema = get_nested_schema(ref_schema, depth + 1)
65
76
  nested_props[name] = {
66
- 'type': 'object',
67
- 'description': get_enum_description(details),
68
- 'properties': nested_schema
77
+ "type": "object",
78
+ "description": get_enum_description(details),
79
+ "properties": nested_schema,
69
80
  }
70
81
  else:
71
82
  nested_props[name] = {
72
- 'type': details.get('type', 'string'),
73
- 'description': get_enum_description(details)
83
+ "type": details.get("type", "string"),
84
+ "description": get_enum_description(details),
74
85
  }
75
86
  return nested_props
76
87
 
77
- for name, details in schema.get('properties', {}).items():
88
+ for name, details in schema.get("properties", {}).items():
78
89
  # Handle arrays
79
- if details.get('type') == 'array':
80
- items = details.get('items', {})
81
- if '$ref' in items:
82
- items = resolve_ref(items['$ref'], schema.get('$defs', {}))
83
-
90
+ if details.get("type") == "array":
91
+ items = details.get("items", {})
92
+ if "$ref" in items:
93
+ items = resolve_ref(items["$ref"], schema.get("$defs", {}))
94
+
84
95
  # Get nested schema for array items
85
96
  item_schema = get_nested_schema(items, current_depth)
86
97
  description = get_enum_description(details)
87
-
98
+
88
99
  if item_schema:
89
100
  description = f"{description}\nList items schema:\n{json.dumps(item_schema, indent=2)}"
90
-
91
- prop = Property(
92
- name=name,
93
- prop_type='list',
94
- description=description
95
- )
101
+
102
+ prop = Property(name=name, prop_type="list", description=description)
96
103
  # Handle objects and references
97
- elif details.get('type') == 'object' or '$ref' in details:
98
- prop_type = 'object'
104
+ elif details.get("type") == "object" or "$ref" in details:
105
+ prop_type = "object"
99
106
  ref_schema = details
100
- if '$ref' in details:
101
- ref_schema = resolve_ref(details['$ref'], schema.get('$defs', {}))
102
-
107
+ if "$ref" in details:
108
+ ref_schema = resolve_ref(details["$ref"], schema.get("$defs", {}))
109
+
103
110
  nested_schema = get_nested_schema(ref_schema, current_depth)
104
-
111
+
105
112
  prop = Property(
106
113
  name=name,
107
114
  prop_type=prop_type,
108
115
  description=get_enum_description(details),
109
- properties=nested_schema
116
+ properties=nested_schema,
110
117
  )
111
-
118
+
112
119
  # Handle primitive types
113
120
  else:
114
121
  prop = Property(
115
122
  name=name,
116
- prop_type=details.get('type', 'string'),
123
+ prop_type=details.get("type", "string"),
117
124
  description=get_enum_description(details),
118
- default=str(details.get('default')) if details.get('default') is not None else None
125
+ default=str(details.get("default"))
126
+ if details.get("default") is not None
127
+ else None,
119
128
  )
120
-
129
+
121
130
  properties.append(prop)
122
-
131
+
123
132
  json_schema = JsonSchema(
124
- title=schema.get('title', model.__name__),
125
- properties=properties
133
+ title=schema.get("title", model.__name__), properties=properties
126
134
  )
127
-
128
- return json_schema.model_dump(mode="json", exclude_none=True)
135
+
136
+ return json_schema.model_dump(mode="json", exclude_none=True)
chunkr_ai/api/task.py CHANGED
@@ -3,50 +3,56 @@ from .misc import prepare_upload_data
3
3
  from .task_base import TaskBase
4
4
  import time
5
5
 
6
+
6
7
  class TaskResponse(TaskBase):
7
8
  def _poll_request(self) -> dict:
8
9
  while True:
9
10
  try:
10
- r = self._client._session.get(self.task_url, headers=self._client._headers())
11
+ if not self.task_url:
12
+ raise ValueError("Task URL not found in response")
13
+ if not self._client._session:
14
+ raise ValueError("Client session not found")
15
+ r = self._client._session.get(
16
+ self.task_url, headers=self._client._headers()
17
+ )
11
18
  r.raise_for_status()
12
19
  return r.json()
13
20
  except (ConnectionError, TimeoutError) as _:
14
21
  print("Connection error while polling the task, retrying...")
15
22
  time.sleep(0.5)
16
- except Exception as e:
23
+ except Exception:
17
24
  raise
18
25
 
19
- def poll(self) -> 'TaskResponse':
20
- if not self.task_url:
21
- raise ValueError("Task URL not found in response")
26
+ def poll(self) -> "TaskResponse":
22
27
  while True:
23
- response = self._poll_request_sync()
28
+ response = self._poll_request()
24
29
  updated_task = TaskResponse(**response).with_client(self._client)
25
30
  self.__dict__.update(updated_task.__dict__)
26
31
  if result := self._check_status():
27
32
  return result
28
33
  time.sleep(0.5)
29
-
30
- def update(self, config: Configuration) -> 'TaskResponse':
34
+
35
+ def update(self, config: Configuration) -> "TaskResponse":
31
36
  if not self.task_url:
32
37
  raise ValueError("Task URL not found")
38
+ if not self._client._session:
39
+ raise ValueError("Client session not found")
33
40
  files = prepare_upload_data(None, config)
34
41
  r = self._client._session.patch(
35
- f"{self.task_url}",
36
- files=files,
37
- headers=self._client._headers()
42
+ self.task_url, files=files, headers=self._client._headers()
38
43
  )
39
44
  r.raise_for_status()
40
45
  updated = TaskResponse(**r.json()).with_client(self._client)
41
46
  self.__dict__.update(updated.__dict__)
42
47
  return self.poll()
43
-
48
+
44
49
  def cancel(self):
45
50
  if not self.task_url:
46
51
  raise ValueError("Task URL not found")
52
+ if not self._client._session:
53
+ raise ValueError("Client session not found")
47
54
  r = self._client._session.get(
48
- f"{self.task_url}/cancel",
49
- headers=self._client._headers()
55
+ f"{self.task_url}/cancel", headers=self._client._headers()
50
56
  )
51
57
  r.raise_for_status()
52
58
  self.poll()
@@ -54,8 +60,7 @@ class TaskResponse(TaskBase):
54
60
  def delete(self):
55
61
  if not self.task_url:
56
62
  raise ValueError("Task URL not found")
57
- r = self._client._session.delete(
58
- self.task_url,
59
- headers=self._client._headers()
60
- )
63
+ if not self._client._session:
64
+ raise ValueError("Client session not found")
65
+ r = self._client._session.delete(self.task_url, headers=self._client._headers())
61
66
  r.raise_for_status()
@@ -3,21 +3,28 @@ from .misc import prepare_upload_data
3
3
  from .task_base import TaskBase
4
4
  import asyncio
5
5
 
6
+
6
7
  class TaskResponseAsync(TaskBase):
7
8
  async def _poll_request(self) -> dict:
8
9
  try:
9
- r = await self._client._client.get(self.task_url, headers=self._client._headers())
10
+ if not self._client._client:
11
+ raise ValueError("Client not found")
12
+ r = await self._client._client.get(
13
+ self.task_url, headers=self._client._headers()
14
+ )
10
15
  r.raise_for_status()
11
16
  return r.json()
12
17
  except (ConnectionError, TimeoutError) as _:
13
18
  print("Connection error while polling the task, retrying...")
14
19
  await asyncio.sleep(0.5)
15
- except Exception as e:
20
+ except Exception:
16
21
  raise
17
22
 
18
- async def poll(self) -> 'TaskResponseAsync':
23
+ async def poll(self) -> "TaskResponseAsync":
19
24
  if not self.task_url:
20
25
  raise ValueError("Task URL not found")
26
+ if not self._client._client:
27
+ raise ValueError("Client not found")
21
28
  while True:
22
29
  j = await self._poll_request()
23
30
  updated = TaskResponseAsync(**j).with_client(self._client)
@@ -26,11 +33,15 @@ class TaskResponseAsync(TaskBase):
26
33
  return res
27
34
  await asyncio.sleep(0.5)
28
35
 
29
- async def update(self, config: Configuration) -> 'TaskResponseAsync':
36
+ async def update(self, config: Configuration) -> "TaskResponseAsync":
30
37
  if not self.task_url:
31
38
  raise ValueError("Task URL not found")
39
+ if not self._client._client:
40
+ raise ValueError("Client not found")
32
41
  f = prepare_upload_data(None, config)
33
- r = await self._client._client.patch(self.task_url, files=f, headers=self._client._headers())
42
+ r = await self._client._client.patch(
43
+ self.task_url, files=f, headers=self._client._headers()
44
+ )
34
45
  r.raise_for_status()
35
46
  updated = TaskResponseAsync(**r.json()).with_client(self._client)
36
47
  self.__dict__.update(updated.__dict__)
@@ -39,12 +50,20 @@ class TaskResponseAsync(TaskBase):
39
50
  async def cancel(self):
40
51
  if not self.task_url:
41
52
  raise ValueError("Task URL not found")
42
- r = await self._client._client.get(f"{self.task_url}/cancel", headers=self._client._headers())
53
+ if not self._client._client:
54
+ raise ValueError("Client not found")
55
+ r = await self._client._client.get(
56
+ f"{self.task_url}/cancel", headers=self._client._headers()
57
+ )
43
58
  r.raise_for_status()
44
59
  return await self.poll()
45
60
 
46
61
  async def delete(self):
47
62
  if not self.task_url:
48
63
  raise ValueError("Task URL not found")
49
- r = await self._client._client.delete(self.task_url, headers=self._client._headers())
50
- r.raise_for_status()
64
+ if not self._client._client:
65
+ raise ValueError("Client not found")
66
+ r = await self._client._client.delete(
67
+ self.task_url, headers=self._client._headers()
68
+ )
69
+ r.raise_for_status()
@@ -1,12 +1,12 @@
1
- from .config import Configuration
1
+ from .config import Configuration, Status, OutputResponse
2
2
  from .protocol import ChunkrClientProtocol
3
- from ..models import Status, OutputResponse
4
3
  from abc import ABC, abstractmethod
5
- from typing import TypeVar, Optional, Generic, Union
4
+ from typing import TypeVar, Optional, Generic
6
5
  from pydantic import BaseModel, PrivateAttr
7
6
  from datetime import datetime
8
7
 
9
- T = TypeVar('T', bound='TaskBase')
8
+ T = TypeVar("T", bound="TaskBase")
9
+
10
10
 
11
11
  class TaskBase(BaseModel, ABC, Generic[T]):
12
12
  configuration: Configuration
@@ -23,7 +23,7 @@ class TaskBase(BaseModel, ABC, Generic[T]):
23
23
  status: Status
24
24
  task_id: str
25
25
  task_url: Optional[str]
26
- _client: Optional[Union[ChunkrClientProtocol]] = PrivateAttr(default=None)
26
+ _client: Optional[ChunkrClientProtocol] = PrivateAttr(default=None)
27
27
 
28
28
  @abstractmethod
29
29
  def _poll_request(self) -> dict:
@@ -50,7 +50,7 @@ class TaskBase(BaseModel, ABC, Generic[T]):
50
50
  """Delete the task."""
51
51
  pass
52
52
 
53
- def with_client(self, client: Union[ChunkrClientProtocol]) -> T:
53
+ def with_client(self, client: ChunkrClientProtocol) -> T:
54
54
  self._client = client
55
55
  return self
56
56
 
chunkr_ai/models.py CHANGED
@@ -24,26 +24,25 @@ from .api.task import TaskResponse
24
24
  from .api.task_async import TaskResponseAsync
25
25
 
26
26
  __all__ = [
27
- 'BoundingBox',
28
- 'Chunk',
29
- 'ChunkProcessing',
30
- 'Configuration',
31
- 'CroppingStrategy',
32
- 'ExtractedJson',
33
- 'GenerationConfig',
34
- 'GenerationStrategy',
35
- 'JsonSchema',
36
- 'LlmConfig',
37
- 'Model',
38
- 'OCRResult',
39
- 'OcrStrategy',
40
- 'OutputResponse',
41
- 'Property',
42
- 'Segment',
43
- 'SegmentProcessing',
44
- 'SegmentType',
45
- 'SegmentationStrategy',
46
- 'Status',
47
- 'TaskResponse',
48
- 'TaskResponseAsync',
27
+ "BoundingBox",
28
+ "Chunk",
29
+ "ChunkProcessing",
30
+ "Configuration",
31
+ "CroppingStrategy",
32
+ "ExtractedJson",
33
+ "GenerationConfig",
34
+ "GenerationStrategy",
35
+ "JsonSchema",
36
+ "Model",
37
+ "OCRResult",
38
+ "OcrStrategy",
39
+ "OutputResponse",
40
+ "Property",
41
+ "Segment",
42
+ "SegmentProcessing",
43
+ "SegmentType",
44
+ "SegmentationStrategy",
45
+ "Status",
46
+ "TaskResponse",
47
+ "TaskResponseAsync",
49
48
  ]
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: chunkr-ai
3
- Version: 0.0.12
3
+ Version: 0.0.15
4
4
  Summary: Python client for Chunkr: open source document intelligence
5
5
  Author-email: Ishaan Kapoor <ishaan@lumina.sh>
6
6
  Project-URL: Homepage, https://chunkr.ai
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: httpx>=0.25.0
10
- Requires-Dist: httpx>=0.25.0
11
10
  Requires-Dist: pillow>=10.0.0
12
11
  Requires-Dist: pydantic>=2.0.0
13
12
  Requires-Dist: pytest-asyncio>=0.21.0
@@ -81,7 +80,7 @@ async def process_document():
81
80
  # If you want to upload without waiting for processing
82
81
  task = await chunkr.start_upload("document.pdf")
83
82
  # ... do other things ...
84
- await task.poll_async() # Check status when needed
83
+ await task.poll() # Check status when needed
85
84
  ```
86
85
 
87
86
  ### Additional Features
@@ -0,0 +1,21 @@
1
+ chunkr_ai/__init__.py,sha256=q5YosvCNXPNGjV10pZY1gcvdosqUh38nVQTQA9g8EuM,110
2
+ chunkr_ai/models.py,sha256=KPcZDkRAziyke33ciEZymvCA28_QuSozoR11fXtjats,886
3
+ chunkr_ai/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ chunkr_ai/api/api.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ chunkr_ai/api/auth.py,sha256=hlv0GiUmlsbFO1wLL9sslqOnsBSoBqkL_6Mk2SDvxgE,413
6
+ chunkr_ai/api/base.py,sha256=QvHl8FInKHYKPLWDeEPpCchB1uktzOwTW7iPnyXccUc,6449
7
+ chunkr_ai/api/chunkr.py,sha256=0extAWVeZtI7B-g14smTfFZD_csdJNCcVNXx2_L69OQ,2617
8
+ chunkr_ai/api/chunkr_async.py,sha256=aa0s_tnYoujHBsfe8uLiPpVEnb2l9A3CXwPP34w9Mk8,4127
9
+ chunkr_ai/api/chunkr_base.py,sha256=k34Dyt1f21NBWZvZJ3w6Svvpg4SKnzr2ldGQ4ib96Wc,4951
10
+ chunkr_ai/api/config.py,sha256=0vbPMAYvs-tb7zIDIoSFQekz8bK0-iw3iz7BqMnHbDI,4930
11
+ chunkr_ai/api/misc.py,sha256=Dk8lWrgX-pZYcvcP2SzRhCdyBwMSSTrE8y7MzVQnScw,4878
12
+ chunkr_ai/api/protocol.py,sha256=lxIR_qoCA2a1OXjpq3LrWMdS0jRHct1bEmBlUzV8gvE,526
13
+ chunkr_ai/api/schema.py,sha256=yYesvueGgtmRa7Fi_Tpdv8A2bzHlx-B-5DxRAPlaDHo,4926
14
+ chunkr_ai/api/task.py,sha256=28J4dR8BDjvtkh3CQjW_YUEkgPXhCHBGu0wH6AQKKuE,2474
15
+ chunkr_ai/api/task_async.py,sha256=K5hTEOnmD42snPZg_JtJsVWg6QBUFZ1aBz1Abwv58-A,2529
16
+ chunkr_ai/api/task_base.py,sha256=esEawvntjzfaPnV78KW2IbIFqI74-eJV_g70BVIubbY,2329
17
+ chunkr_ai-0.0.15.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ chunkr_ai-0.0.15.dist-info/METADATA,sha256=097bhYg2V6cuxLkiVmiSTNuGzlBvGFV1Hw2bFWxBYKc,4839
19
+ chunkr_ai-0.0.15.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
+ chunkr_ai-0.0.15.dist-info/top_level.txt,sha256=0IZY7PZIiS8bw5r4NUQRUQ-ATi-L_3vLQVq3ZLouOW8,10
21
+ chunkr_ai-0.0.15.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- chunkr_ai/__init__.py,sha256=eXygrEhGxxIHXNYIlHF2eied8rGsx2RphgR8Wo4lRyo,110
2
- chunkr_ai/models.py,sha256=-dbwtTHTcGhH3LXUdVUPkobbPoeFNXRizeAW8BCGSkE,903
3
- chunkr_ai/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- chunkr_ai/api/auth.py,sha256=iSd5Jek2BFaHGw9HY-RrqgwP56BHFU0xbSuJS4fU6AA,425
5
- chunkr_ai/api/chunkr.py,sha256=0qpV9b1hOpDhA9EuKkXW9X_laUmw5NY3ZYq0cUOTbww,5190
6
- chunkr_ai/api/chunkr_async.py,sha256=ZkLBrn4cqzu3sqMfS8cfZZgSvpdyQuWZP95lfGxuHx0,4900
7
- chunkr_ai/api/chunkr_base.py,sha256=IYO0pmoL02GchIggj6_Q5nvtAUoOvYAAvT7VLFU6scY,2506
8
- chunkr_ai/api/config.py,sha256=joTn7jiOlJXTwwza-jHauLV-39CMzaxZVGB9JBm8Cok,4862
9
- chunkr_ai/api/misc.py,sha256=9vnfrbJ7sFlZqwEIQ4NTMb5rhPOmETT7e1jR-b42PXM,4977
10
- chunkr_ai/api/protocol.py,sha256=XKS9RmtvBpJItYhPg18qlOCKpaSHdOuQTRSUxAdUz2g,479
11
- chunkr_ai/api/schema.py,sha256=OeLOhBRXeRBgEImg0Q6O9Z10ojT6aSEVvwnDR8UeENo,4971
12
- chunkr_ai/api/task.py,sha256=4insrdGEVzBHs1ejZvde8bbEetVzgJELa47UjhfBqCA,2116
13
- chunkr_ai/api/task_async.py,sha256=LqS-LL-mCOgfGsgvuSXhKkSEUM6MMro-EZHl_ZedQQk,1998
14
- chunkr_ai/api/task_base.py,sha256=iS5UVIDEPIiDoWrn21Oh_dQurkd_hvKQ8ng32j6sGoA,2369
15
- chunkr_ai-0.0.12.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- chunkr_ai-0.0.12.dist-info/METADATA,sha256=dfo9myRizW2A5W0H6FpIoBzHa4QxmEe3lsedPYhwjXM,4874
17
- chunkr_ai-0.0.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
18
- chunkr_ai-0.0.12.dist-info/top_level.txt,sha256=0IZY7PZIiS8bw5r4NUQRUQ-ATi-L_3vLQVq3ZLouOW8,10
19
- chunkr_ai-0.0.12.dist-info/RECORD,,