chunkr-ai 0.0.2__tar.gz → 0.0.3__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. chunkr_ai-0.0.3/PKG-INFO +124 -0
  2. chunkr_ai-0.0.3/README.md +107 -0
  3. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/pyproject.toml +4 -4
  4. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai/api/auth.py +0 -2
  5. chunkr_ai-0.0.3/src/chunkr_ai/api/base.py +173 -0
  6. chunkr_ai-0.0.3/src/chunkr_ai/api/chunkr.py +108 -0
  7. chunkr_ai-0.0.3/src/chunkr_ai/api/chunkr_async.py +105 -0
  8. chunkr_ai-0.0.3/src/chunkr_ai/api/config.py +130 -0
  9. chunkr_ai-0.0.3/src/chunkr_ai/api/protocol.py +19 -0
  10. chunkr_ai-0.0.3/src/chunkr_ai/api/task.py +124 -0
  11. chunkr_ai-0.0.3/src/chunkr_ai/models.py +49 -0
  12. chunkr_ai-0.0.3/src/chunkr_ai.egg-info/PKG-INFO +124 -0
  13. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai.egg-info/SOURCES.txt +6 -1
  14. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai.egg-info/requires.txt +1 -2
  15. chunkr_ai-0.0.3/tests/test_chunkr.py +141 -0
  16. chunkr_ai-0.0.2/PKG-INFO +0 -16
  17. chunkr_ai-0.0.2/src/chunkr_ai/api/chunkr.py +0 -125
  18. chunkr_ai-0.0.2/src/chunkr_ai/api/chunkr_async.py +0 -39
  19. chunkr_ai-0.0.2/src/chunkr_ai/api/models.py +0 -231
  20. chunkr_ai-0.0.2/src/chunkr_ai.egg-info/PKG-INFO +0 -16
  21. chunkr_ai-0.0.2/tests/test_chunkr.py +0 -69
  22. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/LICENSE +0 -0
  23. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/setup.cfg +0 -0
  24. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai/__init__.py +0 -0
  25. /chunkr_ai-0.0.2/README.md → /chunkr_ai-0.0.3/src/chunkr_ai/api/__init__.py +0 -0
  26. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai/api/api.py +0 -0
  27. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai/main.py +0 -0
  28. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai.egg-info/dependency_links.txt +0 -0
  29. {chunkr_ai-0.0.2 → chunkr_ai-0.0.3}/src/chunkr_ai.egg-info/top_level.txt +0 -0
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.2
2
+ Name: chunkr-ai
3
+ Version: 0.0.3
4
+ Summary: Python client for Chunkr: open source document intelligence
5
+ Author-email: Ishaan Kapoor <ishaan@lumina.sh>
6
+ Project-URL: Homepage, https://chunkr.ai
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: httpx>=0.28.1
10
+ Requires-Dist: pillow>=11.1.0
11
+ Requires-Dist: pydantic>=2.10.4
12
+ Requires-Dist: python-dotenv>=1.0.1
13
+ Requires-Dist: requests>=2.32.3
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=8.3.4; extra == "test"
16
+ Requires-Dist: pytest-xdist>=3.6.1; extra == "test"
17
+
18
+ # Chunkr Python Client
19
+
20
+ This is the Python client for the Chunkr API. It provides a simple interface to interact with Chunkr's services.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install chunkr-ai
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ We provide two clients: `Chunkr` for synchronous operations and `ChunkrAsync` for asynchronous operations.
31
+
32
+ ### Synchronous Usage
33
+
34
+ ```python
35
+ from chunkr_ai import Chunkr
36
+
37
+ # Initialize client
38
+ chunkr = Chunkr()
39
+
40
+ # Upload a file and wait for processing
41
+ task = chunkr.upload("document.pdf")
42
+
43
+ # Print the response
44
+ print(task)
45
+
46
+ # Get output from task
47
+ output = task.output
48
+
49
+ # If you want to upload without waiting for processing
50
+ task = chunkr.start_upload("document.pdf")
51
+ # ... do other things ...
52
+ task.poll() # Check status when needed
53
+ ```
54
+
55
+ ### Asynchronous Usage
56
+
57
+ ```python
58
+ from chunkr_ai import ChunkrAsync
59
+
60
+ async def process_document():
61
+ # Initialize client
62
+ chunkr = ChunkrAsync()
63
+
64
+ # Upload a file and wait for processing
65
+ task = await chunkr.upload("document.pdf")
66
+
67
+ # Print the response
68
+ print(task)
69
+
70
+ # Get output from task
71
+ output = task.output
72
+
73
+ # If you want to upload without waiting for processing
74
+ task = await chunkr.start_upload("document.pdf")
75
+ # ... do other things ...
76
+ await task.poll_async() # Check status when needed
77
+ ```
78
+
79
+ ### Additional Features
80
+
81
+ Both clients support various input types:
82
+
83
+ ```python
84
+ # Upload from file path
85
+ chunkr.upload("document.pdf")
86
+
87
+ # Upload from opened file
88
+ with open("document.pdf", "rb") as f:
89
+ chunkr.upload(f)
90
+
91
+ # Upload from URL
92
+ chunkr.upload("https://example.com/document.pdf")
93
+
94
+ # Upload from base64 string
95
+ chunkr.upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
96
+
97
+ # Upload an image
98
+ from PIL import Image
99
+ img = Image.open("photo.jpg")
100
+ chunkr.upload(img)
101
+ ```
102
+
103
+ ### Configuration
104
+
105
+ You can provide your API key and URL in several ways:
106
+ 1. Environment variables: `CHUNKR_API_KEY` and `CHUNKR_URL`
107
+ 2. `.env` file
108
+ 3. Direct initialization:
109
+ ```python
110
+ chunkr = Chunkr(
111
+ api_key="your-api-key",
112
+ url="https://api.chunkr.ai"
113
+ )
114
+ ```
115
+
116
+ ## Run tests
117
+
118
+ ```python
119
+ # Install dependencies
120
+ uv pip install -e ".[test]"
121
+
122
+ # Run tests
123
+ uv run pytest
124
+ ```
@@ -0,0 +1,107 @@
1
+ # Chunkr Python Client
2
+
3
+ This is the Python client for the Chunkr API. It provides a simple interface to interact with Chunkr's services.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install chunkr-ai
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ We provide two clients: `Chunkr` for synchronous operations and `ChunkrAsync` for asynchronous operations.
14
+
15
+ ### Synchronous Usage
16
+
17
+ ```python
18
+ from chunkr_ai import Chunkr
19
+
20
+ # Initialize client
21
+ chunkr = Chunkr()
22
+
23
+ # Upload a file and wait for processing
24
+ task = chunkr.upload("document.pdf")
25
+
26
+ # Print the response
27
+ print(task)
28
+
29
+ # Get output from task
30
+ output = task.output
31
+
32
+ # If you want to upload without waiting for processing
33
+ task = chunkr.start_upload("document.pdf")
34
+ # ... do other things ...
35
+ task.poll() # Check status when needed
36
+ ```
37
+
38
+ ### Asynchronous Usage
39
+
40
+ ```python
41
+ from chunkr_ai import ChunkrAsync
42
+
43
+ async def process_document():
44
+ # Initialize client
45
+ chunkr = ChunkrAsync()
46
+
47
+ # Upload a file and wait for processing
48
+ task = await chunkr.upload("document.pdf")
49
+
50
+ # Print the response
51
+ print(task)
52
+
53
+ # Get output from task
54
+ output = task.output
55
+
56
+ # If you want to upload without waiting for processing
57
+ task = await chunkr.start_upload("document.pdf")
58
+ # ... do other things ...
59
+ await task.poll_async() # Check status when needed
60
+ ```
61
+
62
+ ### Additional Features
63
+
64
+ Both clients support various input types:
65
+
66
+ ```python
67
+ # Upload from file path
68
+ chunkr.upload("document.pdf")
69
+
70
+ # Upload from opened file
71
+ with open("document.pdf", "rb") as f:
72
+ chunkr.upload(f)
73
+
74
+ # Upload from URL
75
+ chunkr.upload("https://example.com/document.pdf")
76
+
77
+ # Upload from base64 string
78
+ chunkr.upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
79
+
80
+ # Upload an image
81
+ from PIL import Image
82
+ img = Image.open("photo.jpg")
83
+ chunkr.upload(img)
84
+ ```
85
+
86
+ ### Configuration
87
+
88
+ You can provide your API key and URL in several ways:
89
+ 1. Environment variables: `CHUNKR_API_KEY` and `CHUNKR_URL`
90
+ 2. `.env` file
91
+ 3. Direct initialization:
92
+ ```python
93
+ chunkr = Chunkr(
94
+ api_key="your-api-key",
95
+ url="https://api.chunkr.ai"
96
+ )
97
+ ```
98
+
99
+ ## Run tests
100
+
101
+ ```python
102
+ # Install dependencies
103
+ uv pip install -e ".[test]"
104
+
105
+ # Run tests
106
+ uv run pytest
107
+ ```
@@ -4,22 +4,22 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "chunkr-ai"
7
- version = "0.0.2"
7
+ version = "0.0.3"
8
8
  authors = [{"name" = "Ishaan Kapoor", "email" = "ishaan@lumina.sh"}]
9
- description = "Python client for chunkr: open source document intelligence"
9
+ description = "Python client for Chunkr: open source document intelligence"
10
10
  readme = "README.md"
11
11
  license = {"file" = "LICENSE"}
12
+ urls = {Homepage = "https://chunkr.ai"}
12
13
  dependencies = [
13
- "build>=1.2.2.post1",
14
14
  "httpx>=0.28.1",
15
15
  "pillow>=11.1.0",
16
16
  "pydantic>=2.10.4",
17
17
  "python-dotenv>=1.0.1",
18
18
  "requests>=2.32.3",
19
- "twine>=6.0.1",
20
19
  ]
21
20
 
22
21
  [project.optional-dependencies]
23
22
  test = [
24
23
  "pytest>=8.3.4",
24
+ "pytest-xdist>=3.6.1",
25
25
  ]
@@ -1,5 +1,3 @@
1
- from typing import Optional
2
-
3
1
  class HeadersMixin:
4
2
  """Mixin class for handling authorization headers"""
5
3
 
@@ -0,0 +1,173 @@
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
+ class ChunkrBase(HeadersMixin):
15
+ """Base class with shared functionality for Chunkr API clients."""
16
+
17
+ def __init__(self, url: str = None, api_key: str = None):
18
+ load_dotenv()
19
+ self.url = (
20
+ url or
21
+ os.getenv('CHUNKR_URL') or
22
+ 'https://api.chunkr.ai'
23
+ )
24
+ self._api_key = (
25
+ api_key or
26
+ os.getenv('CHUNKR_API_KEY')
27
+ )
28
+ if not self._api_key:
29
+ raise ValueError("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")
30
+
31
+ self.url = self.url.rstrip("/")
32
+
33
+ def _prepare_file(
34
+ self,
35
+ file: Union[str, Path, BinaryIO, Image.Image]
36
+ ) -> Tuple[str, BinaryIO]:
37
+ """Convert various file types into a tuple of (filename, file-like object).
38
+
39
+ Args:
40
+ file: Input file, can be:
41
+ - String or Path to a file
42
+ - URL string starting with http:// or https://
43
+ - Base64 string
44
+ - Opened binary file (mode='rb')
45
+ - PIL/Pillow Image object
46
+
47
+ Returns:
48
+ Tuple[str, BinaryIO]: (filename, file-like object) ready for upload
49
+
50
+ Raises:
51
+ FileNotFoundError: If the file path doesn't exist
52
+ TypeError: If the file type is not supported
53
+ ValueError: If the URL is invalid or unreachable
54
+ ValueError: If the MIME type is unsupported
55
+ """
56
+ # Handle URLs
57
+ if isinstance(file, str) and (file.startswith('http://') or file.startswith('https://')):
58
+ response = requests.get(file)
59
+ response.raise_for_status()
60
+ file_obj = io.BytesIO(response.content)
61
+ filename = Path(file.split('/')[-1]).name or 'downloaded_file'
62
+ return filename, file_obj
63
+
64
+ # Handle base64 strings
65
+ if isinstance(file, str) and ',' in file and ';base64,' in file:
66
+ try:
67
+ # Split header and data
68
+ header, base64_data = file.split(',', 1)
69
+ import base64
70
+ file_bytes = base64.b64decode(base64_data)
71
+ file_obj = io.BytesIO(file_bytes)
72
+
73
+ # Try to determine format from header
74
+ format = 'bin'
75
+ mime_type = header.split(':')[-1].split(';')[0].lower()
76
+
77
+ # Map MIME types to file extensions
78
+ mime_to_ext = {
79
+ 'application/pdf': 'pdf',
80
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
81
+ 'application/msword': 'doc',
82
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
83
+ 'application/vnd.ms-powerpoint': 'ppt',
84
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
85
+ 'application/vnd.ms-excel': 'xls',
86
+ 'image/jpeg': 'jpg',
87
+ 'image/png': 'png',
88
+ 'image/jpg': 'jpg'
89
+ }
90
+
91
+ if mime_type in mime_to_ext:
92
+ format = mime_to_ext[mime_type]
93
+ else:
94
+ raise ValueError(f"Unsupported MIME type: {mime_type}")
95
+
96
+ return f"file.{format}", file_obj
97
+ except Exception as e:
98
+ raise ValueError(f"Invalid base64 string: {str(e)}")
99
+
100
+ # Handle file paths
101
+ if isinstance(file, (str, Path)):
102
+ path = Path(file).resolve()
103
+ if not path.exists():
104
+ raise FileNotFoundError(f"File not found: {file}")
105
+ return path.name, open(path, 'rb')
106
+
107
+ # Handle PIL Images
108
+ if isinstance(file, Image.Image):
109
+ img_byte_arr = io.BytesIO()
110
+ format = file.format or 'PNG'
111
+ file.save(img_byte_arr, format=format)
112
+ img_byte_arr.seek(0)
113
+ return f"image.{format.lower()}", img_byte_arr
114
+
115
+ # Handle file-like objects
116
+ if hasattr(file, 'read') and hasattr(file, 'seek'):
117
+ # Try to get the filename from the file object if possible
118
+ name = getattr(file, 'name', 'document') if hasattr(file, 'name') else 'document'
119
+ return Path(name).name, file
120
+
121
+ raise TypeError(f"Unsupported file type: {type(file)}")
122
+
123
+ def _prepare_upload_data(
124
+ self,
125
+ file: Union[str, Path, BinaryIO, Image.Image],
126
+ config: Configuration = None
127
+ ) -> Tuple[dict, dict]:
128
+ """Prepare files and data dictionaries for upload.
129
+
130
+ Args:
131
+ file: The file to upload
132
+ config: Optional configuration settings
133
+
134
+ Returns:
135
+ Tuple[dict, dict]: (files dict, data dict) ready for upload
136
+ """
137
+ filename, file_obj = self._prepare_file(file)
138
+ files = {"file": (filename, file_obj)}
139
+ data = {}
140
+
141
+ if config:
142
+ config_dict = config.model_dump(mode="json", exclude_none=True)
143
+ for key, value in config_dict.items():
144
+ if isinstance(value, dict):
145
+ files[key] = (None, json.dumps(value), 'application/json')
146
+ else:
147
+ data[key] = value
148
+
149
+ return files, data
150
+
151
+ @abstractmethod
152
+ def upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
153
+ """Upload a file and wait for processing to complete.
154
+
155
+ Must be implemented by subclasses.
156
+ """
157
+ pass
158
+
159
+ @abstractmethod
160
+ def start_upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
161
+ """Upload a file for processing and immediately return the task response.
162
+
163
+ Must be implemented by subclasses.
164
+ """
165
+ pass
166
+
167
+ @abstractmethod
168
+ def get_task(self, task_id: str) -> TaskResponse:
169
+ """Get a task response by its ID.
170
+
171
+ Must be implemented by subclasses.
172
+ """
173
+ pass
@@ -0,0 +1,108 @@
1
+ from .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
+
9
+ class Chunkr(ChunkrBase):
10
+ """Chunkr API client"""
11
+
12
+ def __init__(self, url: str = None, api_key: str = None):
13
+ super().__init__(url, api_key)
14
+ self._session = requests.Session()
15
+
16
+ def upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
17
+ """Upload a file and wait for processing to complete.
18
+
19
+ Args:
20
+ file: The file to upload.
21
+ config: Configuration options for processing. Optional.
22
+
23
+ Examples:
24
+ ```
25
+ # Upload from file path
26
+ chunkr.upload("document.pdf")
27
+
28
+ # Upload from URL
29
+ chunkr.upload("https://example.com/document.pdf")
30
+
31
+ # Upload from base64 string (must include MIME type header)
32
+ chunkr.upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
33
+
34
+ # Upload from opened file
35
+ with open("document.pdf", "rb") as f:
36
+ chunkr.upload(f)
37
+
38
+ # Upload an image
39
+ from PIL import Image
40
+ img = Image.open("photo.jpg")
41
+ chunkr.upload(img)
42
+ ```
43
+ Returns:
44
+ TaskResponse: The completed task response
45
+ """
46
+ task = self.start_upload(file, config)
47
+ return task.poll()
48
+
49
+ def start_upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
50
+ """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()`
51
+
52
+ Args:
53
+ file: The file to upload.
54
+ config: Configuration options for processing. Optional.
55
+
56
+ Examples:
57
+ ```
58
+ # Upload from file path
59
+ task = chunkr.start_upload("document.pdf")
60
+
61
+ # Upload from opened file
62
+ with open("document.pdf", "rb") as f:
63
+ task = chunkr.start_upload(f)
64
+
65
+ # Upload from URL
66
+ task = chunkr.start_upload("https://example.com/document.pdf")
67
+
68
+ # Upload from base64 string (must include MIME type header)
69
+ task = chunkr.start_upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
70
+
71
+ # Upload an image
72
+ from PIL import Image
73
+ img = Image.open("photo.jpg")
74
+ task = chunkr.start_upload(img)
75
+
76
+ # Wait for the task to complete - this can be done when needed
77
+ task.poll()
78
+ ```
79
+
80
+ Returns:
81
+ TaskResponse: The initial task response
82
+ """
83
+ files, data = self._prepare_upload_data(file, config)
84
+ r = self._session.post(
85
+ f"{self.url}/api/v1/task",
86
+ files=files,
87
+ data=data,
88
+ headers=self._headers()
89
+ )
90
+ r.raise_for_status()
91
+ return TaskResponse(**r.json()).with_client(self)
92
+
93
+ def get_task(self, task_id: str) -> TaskResponse:
94
+ """Get a task response by its ID.
95
+
96
+ Args:
97
+ task_id: The ID of the task to get
98
+
99
+ Returns:
100
+ TaskResponse: The task response
101
+ """
102
+ r = self._session.get(
103
+ f"{self.url}/api/v1/task/{task_id}",
104
+ headers=self._headers()
105
+ )
106
+ r.raise_for_status()
107
+ return TaskResponse(**r.json()).with_client(self)
108
+
@@ -0,0 +1,105 @@
1
+ from .base import ChunkrBase
2
+ from .task import TaskResponse
3
+ from .config import Configuration
4
+ import httpx
5
+ from pathlib import Path
6
+ from PIL import Image
7
+ from typing import Union, BinaryIO
8
+
9
+ class ChunkrAsync(ChunkrBase):
10
+ """Asynchronous Chunkr API client"""
11
+
12
+ def __init__(self, url: str = None, api_key: str = None):
13
+ super().__init__(url, api_key)
14
+ self._client = httpx.AsyncClient()
15
+
16
+ async def upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
17
+ """Upload a file and wait for processing to complete.
18
+
19
+ Args:
20
+ file: The file to upload.
21
+ config: Configuration options for processing. Optional.
22
+
23
+ Examples:
24
+ ```python
25
+ # Upload from file path
26
+ await chunkr.upload("document.pdf")
27
+
28
+ # Upload from opened file
29
+ with open("document.pdf", "rb") as f:
30
+ await chunkr.upload(f)
31
+
32
+ # Upload from URL
33
+ await chunkr.upload("https://example.com/document.pdf")
34
+
35
+ # Upload from base64 string (must include MIME type header)
36
+ await chunkr.upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
37
+
38
+ # Upload an image
39
+ from PIL import Image
40
+ img = Image.open("photo.jpg")
41
+ await chunkr.upload(img)
42
+ ```
43
+ Returns:
44
+ TaskResponse: The completed task response
45
+ """
46
+ task = await self.start_upload(file, config)
47
+ return await task.poll_async()
48
+
49
+ async def start_upload(self, file: Union[str, Path, BinaryIO, Image.Image], config: Configuration = None) -> TaskResponse:
50
+ """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_async()`.
51
+
52
+ Args:
53
+ file: The file to upload.
54
+ config: Configuration options for processing. Optional.
55
+
56
+ Examples:
57
+ ```
58
+ # Upload from file path
59
+ task = await chunkr.start_upload("document.pdf")
60
+
61
+ # Upload from opened file
62
+ with open("document.pdf", "rb") as f:
63
+ task = await chunkr.start_upload(f)
64
+
65
+ # Upload from URL
66
+ task = await chunkr.start_upload("https://example.com/document.pdf")
67
+
68
+ # Upload from base64 string (must include MIME type header)
69
+ task = await chunkr.start_upload("data:application/pdf;base64,JVBERi0xLjcKCjEgMCBvYmo...")
70
+
71
+ # Upload an image
72
+ from PIL import Image
73
+ img = Image.open("photo.jpg")
74
+ task = await chunkr.start_upload(img)
75
+
76
+ # Wait for the task to complete - this can be done when needed
77
+ await task.poll_async()
78
+ ```
79
+
80
+ Returns:
81
+ TaskResponse: The initial task response
82
+ """
83
+ files, data = self._prepare_upload_data(file, config)
84
+ r = await self._client.post(
85
+ f"{self.url}/api/v1/task",
86
+ files=files,
87
+ json=config.model_dump() if config else {},
88
+ headers=self._headers()
89
+ )
90
+ r.raise_for_status()
91
+ return TaskResponse(**r.json()).with_client(self)
92
+
93
+ async def get_task(self, task_id: str) -> TaskResponse:
94
+ r = await self._client.get(
95
+ f"{self.url}/api/v1/task/{task_id}",
96
+ headers=self._headers()
97
+ )
98
+ r.raise_for_status()
99
+ return TaskResponse(**r.json()).with_client(self)
100
+
101
+ async def __aenter__(self):
102
+ return self
103
+
104
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
105
+ await self._client.aclose()