chunkr-ai 0.0.12__tar.gz → 0.0.15__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 (30) hide show
  1. {chunkr_ai-0.0.12/src/chunkr_ai.egg-info → chunkr_ai-0.0.15}/PKG-INFO +2 -3
  2. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/README.md +1 -1
  3. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/pyproject.toml +1 -2
  4. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/__init__.py +1 -1
  5. chunkr_ai-0.0.15/src/chunkr_ai/api/api.py +0 -0
  6. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/auth.py +4 -4
  7. chunkr_ai-0.0.15/src/chunkr_ai/api/base.py +183 -0
  8. chunkr_ai-0.0.15/src/chunkr_ai/api/chunkr.py +78 -0
  9. chunkr_ai-0.0.15/src/chunkr_ai/api/chunkr_async.py +120 -0
  10. chunkr_ai-0.0.15/src/chunkr_ai/api/chunkr_base.py +160 -0
  11. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/config.py +38 -14
  12. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/misc.py +51 -44
  13. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/protocol.py +6 -4
  14. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/schema.py +66 -58
  15. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/task.py +23 -18
  16. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/task_async.py +27 -8
  17. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/task_base.py +6 -6
  18. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/models.py +21 -22
  19. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15/src/chunkr_ai.egg-info}/PKG-INFO +2 -3
  20. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai.egg-info/SOURCES.txt +2 -0
  21. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai.egg-info/requires.txt +0 -1
  22. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/tests/test_chunkr.py +219 -135
  23. chunkr_ai-0.0.12/src/chunkr_ai/api/chunkr.py +0 -165
  24. chunkr_ai-0.0.12/src/chunkr_ai/api/chunkr_async.py +0 -144
  25. chunkr_ai-0.0.12/src/chunkr_ai/api/chunkr_base.py +0 -85
  26. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/LICENSE +0 -0
  27. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/setup.cfg +0 -0
  28. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai/api/__init__.py +0 -0
  29. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai.egg-info/dependency_links.txt +0 -0
  30. {chunkr_ai-0.0.12 → chunkr_ai-0.0.15}/src/chunkr_ai.egg-info/top_level.txt +0 -0
@@ -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
@@ -62,7 +62,7 @@ async def process_document():
62
62
  # If you want to upload without waiting for processing
63
63
  task = await chunkr.start_upload("document.pdf")
64
64
  # ... do other things ...
65
- await task.poll_async() # Check status when needed
65
+ await task.poll() # Check status when needed
66
66
  ```
67
67
 
68
68
  ### Additional Features
@@ -4,14 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chunkr-ai"
7
- version = "0.0.12"
7
+ version = "0.0.15"
8
8
  authors = [{"name" = "Ishaan Kapoor", "email" = "ishaan@lumina.sh"}]
9
9
  description = "Python client for Chunkr: open source document intelligence"
10
10
  readme = "README.md"
11
11
  license = {"file" = "LICENSE"}
12
12
  urls = {Homepage = "https://chunkr.ai"}
13
13
  dependencies = [
14
- "httpx>=0.25.0",
15
14
  "httpx>=0.25.0",
16
15
  "pillow>=10.0.0",
17
16
  "pydantic>=2.0.0",
@@ -1,4 +1,4 @@
1
1
  from .api.chunkr import Chunkr
2
2
  from .api.chunkr_async import ChunkrAsync
3
3
 
4
- __all__ = ['Chunkr', 'ChunkrAsync']
4
+ __all__ = ["Chunkr", "ChunkrAsync"]
File without changes
@@ -1,12 +1,12 @@
1
1
  class HeadersMixin:
2
2
  """Mixin class for handling authorization headers"""
3
-
3
+
4
4
  def get_api_key(self) -> str:
5
5
  """Get the API key"""
6
- if not hasattr(self, '_api_key') or not self._api_key:
6
+ if not hasattr(self, "_api_key") or not self._api_key:
7
7
  raise ValueError("API key not set")
8
8
  return self._api_key
9
-
9
+
10
10
  def _headers(self) -> dict:
11
11
  """Generate authorization headers"""
12
- return {"Authorization": self.get_api_key()}
12
+ return {"Authorization": self.get_api_key()}
@@ -0,0 +1,183 @@
1
+ from .config import Configuration
2
+ from .task import TaskResponse
3
+ from .auth import HeadersMixin
4
+ from abc import abstractmethod
5
+ from dotenv import load_dotenv
6
+ import io
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ from PIL import Image
11
+ import requests
12
+ from typing import BinaryIO, Tuple, Union
13
+
14
+
15
+ class ChunkrBase(HeadersMixin):
16
+ """Base class with shared functionality for Chunkr API clients."""
17
+
18
+ def __init__(self, url: str = None, api_key: str = None):
19
+ load_dotenv()
20
+ self.url = url or os.getenv("CHUNKR_URL") or "https://api.chunkr.ai"
21
+ self._api_key = api_key or os.getenv("CHUNKR_API_KEY")
22
+ if not self._api_key:
23
+ raise ValueError(
24
+ "API key must be provided either directly, in .env file, or as CHUNKR_API_KEY environment variable. You can get an api key at: https://www.chunkr.ai"
25
+ )
26
+
27
+ self.url = self.url.rstrip("/")
28
+
29
+ def _prepare_file(
30
+ self, file: Union[str, Path, BinaryIO, Image.Image]
31
+ ) -> Tuple[str, BinaryIO]:
32
+ """Convert various file types into a tuple of (filename, file-like object).
33
+
34
+ Args:
35
+ file: Input file, can be:
36
+ - String or Path to a file
37
+ - URL string starting with http:// or https://
38
+ - Base64 string
39
+ - Opened binary file (mode='rb')
40
+ - PIL/Pillow Image object
41
+
42
+ Returns:
43
+ Tuple[str, BinaryIO]: (filename, file-like object) ready for upload
44
+
45
+ Raises:
46
+ FileNotFoundError: If the file path doesn't exist
47
+ TypeError: If the file type is not supported
48
+ ValueError: If the URL is invalid or unreachable
49
+ ValueError: If the MIME type is unsupported
50
+ """
51
+ # Handle URLs
52
+ if isinstance(file, str) and (
53
+ file.startswith("http://") or file.startswith("https://")
54
+ ):
55
+ response = requests.get(file)
56
+ response.raise_for_status()
57
+ file_obj = io.BytesIO(response.content)
58
+ filename = Path(file.split("/")[-1]).name or "downloaded_file"
59
+ return filename, file_obj
60
+
61
+ # Handle base64 strings
62
+ if isinstance(file, str) and "," in file and ";base64," in file:
63
+ try:
64
+ # Split header and data
65
+ header, base64_data = file.split(",", 1)
66
+ import base64
67
+
68
+ file_bytes = base64.b64decode(base64_data)
69
+ file_obj = io.BytesIO(file_bytes)
70
+
71
+ # Try to determine format from header
72
+ format = "bin"
73
+ mime_type = header.split(":")[-1].split(";")[0].lower()
74
+
75
+ # Map MIME types to file extensions
76
+ mime_to_ext = {
77
+ "application/pdf": "pdf",
78
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
79
+ "application/msword": "doc",
80
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
81
+ "application/vnd.ms-powerpoint": "ppt",
82
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
83
+ "application/vnd.ms-excel": "xls",
84
+ "image/jpeg": "jpg",
85
+ "image/png": "png",
86
+ "image/jpg": "jpg",
87
+ }
88
+
89
+ if mime_type in mime_to_ext:
90
+ format = mime_to_ext[mime_type]
91
+ else:
92
+ raise ValueError(f"Unsupported MIME type: {mime_type}")
93
+
94
+ return f"file.{format}", file_obj
95
+ except Exception as e:
96
+ raise ValueError(f"Invalid base64 string: {str(e)}")
97
+
98
+ # Handle file paths
99
+ if isinstance(file, (str, Path)):
100
+ path = Path(file).resolve()
101
+ if not path.exists():
102
+ raise FileNotFoundError(f"File not found: {file}")
103
+ return path.name, open(path, "rb")
104
+
105
+ # Handle PIL Images
106
+ if isinstance(file, Image.Image):
107
+ img_byte_arr = io.BytesIO()
108
+ format = file.format or "PNG"
109
+ file.save(img_byte_arr, format=format)
110
+ img_byte_arr.seek(0)
111
+ return f"image.{format.lower()}", img_byte_arr
112
+
113
+ # Handle file-like objects
114
+ if hasattr(file, "read") and hasattr(file, "seek"):
115
+ # Try to get the filename from the file object if possible
116
+ name = (
117
+ getattr(file, "name", "document")
118
+ if hasattr(file, "name")
119
+ else "document"
120
+ )
121
+ return Path(name).name, file
122
+
123
+ raise TypeError(f"Unsupported file type: {type(file)}")
124
+
125
+ def _prepare_upload_data(
126
+ self,
127
+ file: Union[str, Path, BinaryIO, Image.Image],
128
+ config: Configuration = None,
129
+ ) -> Tuple[dict, dict]:
130
+ """Prepare files and data dictionaries for upload.
131
+
132
+ Args:
133
+ file: The file to upload
134
+ config: Optional configuration settings
135
+
136
+ Returns:
137
+ Tuple[dict, dict]: (files dict, data dict) ready for upload
138
+ """
139
+ filename, file_obj = self._prepare_file(file)
140
+ files = {"file": (filename, file_obj)}
141
+ data = {}
142
+
143
+ if config:
144
+ config_dict = config.model_dump(mode="json", exclude_none=True)
145
+ for key, value in config_dict.items():
146
+ if isinstance(value, dict):
147
+ files[key] = (None, json.dumps(value), "application/json")
148
+ else:
149
+ data[key] = value
150
+
151
+ return files, data
152
+
153
+ @abstractmethod
154
+ def upload(
155
+ self,
156
+ file: Union[str, Path, BinaryIO, Image.Image],
157
+ config: Configuration = None,
158
+ ) -> TaskResponse:
159
+ """Upload a file and wait for processing to complete.
160
+
161
+ Must be implemented by subclasses.
162
+ """
163
+ pass
164
+
165
+ @abstractmethod
166
+ def start_upload(
167
+ self,
168
+ file: Union[str, Path, BinaryIO, Image.Image],
169
+ config: Configuration = None,
170
+ ) -> TaskResponse:
171
+ """Upload a file for processing and immediately return the task response.
172
+
173
+ Must be implemented by subclasses.
174
+ """
175
+ pass
176
+
177
+ @abstractmethod
178
+ def get_task(self, task_id: str) -> TaskResponse:
179
+ """Get a task response by its ID.
180
+
181
+ Must be implemented by subclasses.
182
+ """
183
+ pass
@@ -0,0 +1,78 @@
1
+ from .chunkr_base import ChunkrBase
2
+ from .config import Configuration
3
+ from .task import TaskResponse
4
+ from pathlib import Path
5
+ from PIL import Image
6
+ import requests
7
+ from typing import Union, BinaryIO
8
+ from .misc import prepare_upload_data
9
+
10
+
11
+ class Chunkr(ChunkrBase):
12
+ """Chunkr API client"""
13
+
14
+ def __init__(self, url: str = None, api_key: str = None):
15
+ super().__init__(url, api_key)
16
+ self._session = requests.Session()
17
+
18
+ def upload(
19
+ self,
20
+ file: Union[str, Path, BinaryIO, Image.Image],
21
+ config: Configuration = None,
22
+ ) -> TaskResponse:
23
+ task = self.create_task(file, config)
24
+ return task.poll()
25
+
26
+ def update(self, task_id: str, config: Configuration) -> TaskResponse:
27
+ task = self.update_task(task_id, config)
28
+ return task.poll()
29
+
30
+ def create_task(
31
+ self,
32
+ file: Union[str, Path, BinaryIO, Image.Image],
33
+ config: Configuration = None,
34
+ ) -> TaskResponse:
35
+ files = prepare_upload_data(file, config)
36
+ if not self._session:
37
+ raise ValueError("Session not found")
38
+ r = self._session.post(
39
+ f"{self.url}/api/v1/task", files=files, headers=self._headers()
40
+ )
41
+ r.raise_for_status()
42
+ return TaskResponse(**r.json()).with_client(self)
43
+
44
+ def update_task(self, task_id: str, config: Configuration) -> TaskResponse:
45
+ files = prepare_upload_data(None, config)
46
+ if not self._session:
47
+ raise ValueError("Session not found")
48
+ r = self._session.patch(
49
+ f"{self.url}/api/v1/task/{task_id}", files=files, headers=self._headers()
50
+ )
51
+
52
+ r.raise_for_status()
53
+ return TaskResponse(**r.json()).with_client(self)
54
+
55
+ def get_task(self, task_id: str) -> TaskResponse:
56
+ if not self._session:
57
+ raise ValueError("Session not found")
58
+ r = self._session.get(
59
+ f"{self.url}/api/v1/task/{task_id}", headers=self._headers()
60
+ )
61
+ r.raise_for_status()
62
+ return TaskResponse(**r.json()).with_client(self)
63
+
64
+ def delete_task(self, task_id: str) -> None:
65
+ if not self._session:
66
+ raise ValueError("Session not found")
67
+ r = self._session.delete(
68
+ f"{self.url}/api/v1/task/{task_id}", headers=self._headers()
69
+ )
70
+ r.raise_for_status()
71
+
72
+ def cancel_task(self, task_id: str) -> None:
73
+ if not self._session:
74
+ raise ValueError("Session not found")
75
+ r = self._session.get(
76
+ f"{self.url}/api/v1/task/{task_id}/cancel", headers=self._headers()
77
+ )
78
+ r.raise_for_status()
@@ -0,0 +1,120 @@
1
+ from .chunkr_base import ChunkrBase
2
+ from .config import Configuration
3
+ from .misc import prepare_upload_data
4
+ from .task_async import TaskResponseAsync
5
+ import httpx
6
+ from pathlib import Path
7
+ from PIL import Image
8
+ from typing import Union, BinaryIO
9
+
10
+
11
+ class ChunkrAsync(ChunkrBase):
12
+ """Asynchronous Chunkr API client"""
13
+
14
+ def __init__(self, url: str = None, api_key: str = None):
15
+ super().__init__(url, api_key)
16
+ self._client = httpx.AsyncClient()
17
+
18
+ async def upload(
19
+ self,
20
+ file: Union[str, Path, BinaryIO, Image.Image],
21
+ config: Configuration = None,
22
+ ) -> TaskResponseAsync:
23
+ if not self._client or self._client.is_closed:
24
+ self._client = httpx.AsyncClient()
25
+ try:
26
+ task = await self.create_task(file, config)
27
+ return await task.poll()
28
+ except Exception as e:
29
+ await self._client.aclose()
30
+ raise e
31
+
32
+ async def update(self, task_id: str, config: Configuration) -> TaskResponseAsync:
33
+ if not self._client or self._client.is_closed:
34
+ self._client = httpx.AsyncClient()
35
+ try:
36
+ task = await self.update_task(task_id, config)
37
+ return await task.poll()
38
+ except Exception as e:
39
+ await self._client.aclose()
40
+ raise e
41
+
42
+ async def create_task(
43
+ self,
44
+ file: Union[str, Path, BinaryIO, Image.Image],
45
+ config: Configuration = None,
46
+ ) -> TaskResponseAsync:
47
+ if not self._client or self._client.is_closed:
48
+ self._client = httpx.AsyncClient()
49
+ try:
50
+ files = prepare_upload_data(file, config)
51
+ r = await self._client.post(
52
+ f"{self.url}/api/v1/task", files=files, headers=self._headers()
53
+ )
54
+ r.raise_for_status()
55
+ return TaskResponseAsync(**r.json()).with_client(self)
56
+ except Exception as e:
57
+ await self._client.aclose()
58
+ raise e
59
+
60
+ async def update_task(
61
+ self, task_id: str, config: Configuration
62
+ ) -> TaskResponseAsync:
63
+ if not self._client or self._client.is_closed:
64
+ self._client = httpx.AsyncClient()
65
+ try:
66
+ files = prepare_upload_data(None, config)
67
+ r = await self._client.patch(
68
+ f"{self.url}/api/v1/task/{task_id}",
69
+ files=files,
70
+ headers=self._headers(),
71
+ )
72
+
73
+ r.raise_for_status()
74
+ return TaskResponseAsync(**r.json()).with_client(self)
75
+ except Exception as e:
76
+ await self._client.aclose()
77
+ raise e
78
+
79
+ async def get_task(self, task_id: str) -> TaskResponseAsync:
80
+ if not self._client or self._client.is_closed:
81
+ self._client = httpx.AsyncClient()
82
+ try:
83
+ r = await self._client.get(
84
+ f"{self.url}/api/v1/task/{task_id}", headers=self._headers()
85
+ )
86
+ r.raise_for_status()
87
+ return TaskResponseAsync(**r.json()).with_client(self)
88
+ except Exception as e:
89
+ await self._client.aclose()
90
+ raise e
91
+
92
+ async def delete_task(self, task_id: str) -> None:
93
+ if not self._client or self._client.is_closed:
94
+ self._client = httpx.AsyncClient()
95
+ try:
96
+ r = await self._client.delete(
97
+ f"{self.url}/api/v1/task/{task_id}", headers=self._headers()
98
+ )
99
+ r.raise_for_status()
100
+ except Exception as e:
101
+ await self._client.aclose()
102
+ raise e
103
+
104
+ async def cancel_task(self, task_id: str) -> None:
105
+ if not self._client or self._client.is_closed:
106
+ self._client = httpx.AsyncClient()
107
+ try:
108
+ r = await self._client.get(
109
+ f"{self.url}/api/v1/task/{task_id}/cancel", headers=self._headers()
110
+ )
111
+ r.raise_for_status()
112
+ except Exception as e:
113
+ await self._client.aclose()
114
+ raise e
115
+
116
+ async def __aenter__(self):
117
+ return self
118
+
119
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
120
+ await self._client.aclose()
@@ -0,0 +1,160 @@
1
+ from .config import Configuration
2
+ from .task import TaskResponse
3
+ from .task_async import TaskResponseAsync
4
+ from .auth import HeadersMixin
5
+ from abc import abstractmethod
6
+ from dotenv import load_dotenv
7
+ import os
8
+ from pathlib import Path
9
+ from PIL import Image
10
+ from typing import BinaryIO, Union
11
+
12
+
13
+ class ChunkrBase(HeadersMixin):
14
+ """Base class with shared functionality for Chunkr API clients."""
15
+
16
+ def __init__(self, url: str = None, api_key: str = None):
17
+ load_dotenv()
18
+ self.url = url or os.getenv("CHUNKR_URL") or "https://api.chunkr.ai"
19
+ self._api_key = api_key or os.getenv("CHUNKR_API_KEY")
20
+ if not self._api_key:
21
+ raise ValueError(
22
+ "API key must be provided either directly, in .env file, or as CHUNKR_API_KEY environment variable. You can get an api key at: https://www.chunkr.ai"
23
+ )
24
+
25
+ self.url = self.url.rstrip("/")
26
+
27
+ @abstractmethod
28
+ def upload(
29
+ self,
30
+ file: Union[str, Path, BinaryIO, Image.Image],
31
+ config: Configuration = None,
32
+ ) -> Union[TaskResponse, TaskResponseAsync]:
33
+ """Upload a file and wait for processing to complete.
34
+
35
+ Args:
36
+ file: The file to upload.
37
+ config: Configuration options for processing. Optional.
38
+
39
+ Examples:
40
+ ```python
41
+ # Upload from file path
42
+ await chunkr.upload("document.pdf")
43
+
44
+ # Upload from opened file
45
+ with open("document.pdf", "rb") as f:
46
+ await chunkr.upload(f)
47
+
48
+ # Upload from URL
49
+ await chunkr.upload("https://example.com/document.pdf")
50
+
51
+ # Upload from base64 string (must include MIME type header)
52
+ await chunkr.upload("data:application/pdf;base64,JVBERi0...")
53
+
54
+ # Upload an image
55
+ from PIL import Image
56
+ img = Image.open("photo.jpg")
57
+ await chunkr.upload(img)
58
+ ```
59
+ Returns:
60
+ TaskResponse: The completed task response
61
+ """
62
+ pass
63
+
64
+ @abstractmethod
65
+ def update(
66
+ self, task_id: str, config: Configuration
67
+ ) -> Union[TaskResponse, TaskResponseAsync]:
68
+ """Update a task by its ID and wait for processing to complete.
69
+
70
+ Args:
71
+ task_id: The ID of the task to update
72
+ config: Configuration options for processing. Optional.
73
+
74
+ Returns:
75
+ TaskResponse: The updated task response
76
+ """
77
+ pass
78
+
79
+ @abstractmethod
80
+ def create_task(
81
+ self,
82
+ file: Union[str, Path, BinaryIO, Image.Image],
83
+ config: Configuration = None,
84
+ ) -> Union[TaskResponse, TaskResponseAsync]:
85
+ """Upload a file for processing and immediately return the task response. It will not wait for processing to complete. To wait for the full processing to complete, use `task.poll()`.
86
+
87
+ Args:
88
+ file: The file to upload.
89
+ config: Configuration options for processing. Optional.
90
+
91
+ Examples:
92
+ ```
93
+ # Upload from file path
94
+ task = await chunkr.create_task("document.pdf")
95
+
96
+ # Upload from opened file
97
+ with open("document.pdf", "rb") as f:
98
+ task = await chunkr.create_task(f)
99
+
100
+ # Upload from URL
101
+ task = await chunkr.create_task("https://example.com/document.pdf")
102
+
103
+ # Upload from base64 string (must include MIME type header)
104
+ task = await chunkr.create_task("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
105
+
106
+ # Upload an image
107
+ from PIL import Image
108
+ img = Image.open("photo.jpg")
109
+ task = await chunkr.create_task(img)
110
+
111
+ # Wait for the task to complete - this can be done when needed
112
+ await task.poll()
113
+ ```
114
+ """
115
+ pass
116
+
117
+ @abstractmethod
118
+ def update_task(
119
+ self, task_id: str, config: Configuration
120
+ ) -> Union[TaskResponse, TaskResponseAsync]:
121
+ """Update a task by its ID and immediately return the task response. It will not wait for processing to complete. To wait for the full processing to complete, use `task.poll()`.
122
+
123
+ Args:
124
+ task_id: The ID of the task to update
125
+ config: Configuration options for processing. Optional.
126
+
127
+ Returns:
128
+ TaskResponse: The updated task response
129
+ """
130
+ pass
131
+
132
+ @abstractmethod
133
+ def get_task(self, task_id: str) -> Union[TaskResponse, TaskResponseAsync]:
134
+ """Get a task response by its ID.
135
+
136
+ Args:
137
+ task_id: The ID of the task to get
138
+
139
+ Returns:
140
+ TaskResponse: The task response
141
+ """
142
+ pass
143
+
144
+ @abstractmethod
145
+ def delete_task(self, task_id: str) -> None:
146
+ """Delete a task by its ID.
147
+
148
+ Args:
149
+ task_id: The ID of the task to delete
150
+ """
151
+ pass
152
+
153
+ @abstractmethod
154
+ def cancel_task(self, task_id: str) -> None:
155
+ """Cancel a task by its ID.
156
+
157
+ Args:
158
+ task_id: The ID of the task to cancel
159
+ """
160
+ pass