poofbg 0.1.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.
@@ -0,0 +1,48 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ develop-eggs/
10
+ dist/
11
+ downloads/
12
+ eggs/
13
+ .eggs/
14
+ lib/
15
+ lib64/
16
+ parts/
17
+ sdist/
18
+ var/
19
+ wheels/
20
+ *.egg-info/
21
+ .installed.cfg
22
+ *.egg
23
+
24
+ # Virtual environments
25
+ .venv/
26
+ venv/
27
+ ENV/
28
+ env/
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+ *~
36
+
37
+ # Testing
38
+ .pytest_cache/
39
+ .coverage
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+
44
+ # mypy
45
+ .mypy_cache/
46
+
47
+ # ruff
48
+ .ruff_cache/
poofbg-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Poof
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
poofbg-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: poofbg
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Poof background removal API
5
+ Project-URL: Homepage, https://poof.bg
6
+ Project-URL: Documentation, https://docs.poof.bg
7
+ Project-URL: Repository, https://github.com/poof-bg/py
8
+ Project-URL: Issues, https://github.com/poof-bg/py/issues
9
+ Author-email: Poof <support@poof.bg>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api,background-removal,image-processing,poof,sdk
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Multimedia :: Graphics
24
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Requires-Dist: httpx>=0.24.0
28
+ Description-Content-Type: text/markdown
29
+
30
+ # poofbg
31
+
32
+ The official Python SDK for the [Poof](https://poof.bg) background removal API.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install poofbg
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ from poof import Poof
44
+
45
+ # Initialize the client
46
+ client = Poof(api_key="your-api-key")
47
+
48
+ # Remove background from an image
49
+ result = client.remove_background("photo.jpg")
50
+
51
+ # Save the result
52
+ result.save("output.png")
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ ### Remove Background
58
+
59
+ ```python
60
+ from poof import Poof
61
+
62
+ client = Poof(api_key="your-api-key")
63
+
64
+ # From a file path
65
+ result = client.remove_background("image.jpg")
66
+
67
+ # From bytes
68
+ with open("image.jpg", "rb") as f:
69
+ image_bytes = f.read()
70
+ result = client.remove_background(image_bytes)
71
+
72
+ # From a file object
73
+ with open("image.jpg", "rb") as f:
74
+ result = client.remove_background(f)
75
+
76
+ # Save the result
77
+ result.save("output.png")
78
+
79
+ # Or get the bytes directly
80
+ image_data = result.data
81
+ # or: image_data = bytes(result)
82
+ ```
83
+
84
+ ### Options
85
+
86
+ ```python
87
+ # Different output format
88
+ result = client.remove_background("photo.jpg", format="webp")
89
+
90
+ # Crop to subject bounds
91
+ result = client.remove_background("photo.jpg", crop=True)
92
+
93
+ # RGB output with background color (no transparency)
94
+ result = client.remove_background(
95
+ "photo.jpg",
96
+ channels="rgb",
97
+ bg_color="#ffffff"
98
+ )
99
+
100
+ # Different size presets
101
+ result = client.remove_background("photo.jpg", size="preview")
102
+ ```
103
+
104
+ ### Result Metadata
105
+
106
+ ```python
107
+ result = client.remove_background("photo.jpg")
108
+
109
+ print(f"Image size: {result.width}x{result.height}")
110
+ print(f"Processing time: {result.processing_time_ms}ms")
111
+ print(f"Content type: {result.content_type}")
112
+ print(f"Request ID: {result.request_id}")
113
+ ```
114
+
115
+ ### Account Info
116
+
117
+ ```python
118
+ info = client.me()
119
+
120
+ print(f"Plan: {info['plan']}")
121
+ print(f"Credits used: {info['usedCredits']} / {info['maxCredits']}")
122
+ ```
123
+
124
+ ### Context Manager
125
+
126
+ ```python
127
+ with Poof(api_key="your-api-key") as client:
128
+ result = client.remove_background("photo.jpg")
129
+ result.save("output.png")
130
+ # Client is automatically closed
131
+ ```
132
+
133
+ ## Error Handling
134
+
135
+ ```python
136
+ from poof import (
137
+ Poof,
138
+ PoofError,
139
+ AuthError,
140
+ RateLimitError,
141
+ PaymentRequiredError,
142
+ ValidationError,
143
+ )
144
+
145
+ client = Poof(api_key="your-api-key")
146
+
147
+ try:
148
+ result = client.remove_background("photo.jpg")
149
+ except AuthError as e:
150
+ print(f"Authentication failed: {e.message}")
151
+ except PaymentRequiredError as e:
152
+ print(f"Insufficient credits: {e.message}")
153
+ except RateLimitError as e:
154
+ print(f"Rate limited: {e.message}")
155
+ except ValidationError as e:
156
+ print(f"Invalid request: {e.message}")
157
+ except PoofError as e:
158
+ print(f"API error: {e.message} (code: {e.code})")
159
+ ```
160
+
161
+ All exceptions include:
162
+ - `message`: Human-readable error description
163
+ - `code`: Machine-readable error code
164
+ - `details`: Additional troubleshooting info
165
+ - `request_id`: Unique ID for support
166
+ - `status_code`: HTTP status code
167
+
168
+ ## Configuration
169
+
170
+ ```python
171
+ # Custom timeout (default: 60 seconds)
172
+ client = Poof(api_key="...", timeout=120)
173
+
174
+ # Custom base URL
175
+ client = Poof(api_key="...", base_url="https://api.example.com/v1")
176
+
177
+ # Bring your own httpx client
178
+ import httpx
179
+
180
+ with httpx.Client(timeout=30, http2=True) as http_client:
181
+ client = Poof(api_key="...", httpx_client=http_client)
182
+ result = client.remove_background("photo.jpg")
183
+ ```
184
+
185
+ ## API Reference
186
+
187
+ ### `Poof(api_key, *, base_url, timeout, httpx_client)`
188
+
189
+ Create a new Poof client.
190
+
191
+ | Parameter | Type | Default | Description |
192
+ |-----------|------|---------|-------------|
193
+ | `api_key` | `str` | required | Your Poof API key |
194
+ | `base_url` | `str` | `https://api.poof.bg/v1` | API base URL |
195
+ | `timeout` | `float` | `60.0` | Request timeout in seconds |
196
+ | `httpx_client` | `httpx.Client` | `None` | Custom HTTP client |
197
+
198
+ ### `client.remove_background(image, *, format, channels, bg_color, size, crop)`
199
+
200
+ Remove background from an image.
201
+
202
+ | Parameter | Type | Default | Description |
203
+ |-----------|------|---------|-------------|
204
+ | `image` | `str \| Path \| bytes \| BinaryIO` | required | Image to process |
205
+ | `format` | `"png" \| "jpg" \| "webp"` | `"png"` | Output format |
206
+ | `channels` | `"rgba" \| "rgb"` | `"rgba"` | Color channels |
207
+ | `bg_color` | `str` | `None` | Background color (when `channels="rgb"`) |
208
+ | `size` | `"full" \| "preview" \| "small" \| "medium" \| "large"` | `"full"` | Output size |
209
+ | `crop` | `bool` | `False` | Crop to subject bounds |
210
+
211
+ Returns a `RemoveBackgroundResult` with:
212
+ - `data`: Image bytes
213
+ - `content_type`: MIME type
214
+ - `width`, `height`: Image dimensions
215
+ - `processing_time_ms`: Processing time
216
+ - `request_id`: Request ID for support
217
+ - `save(path)`: Save to file
218
+
219
+ ### `client.me()`
220
+
221
+ Get account information.
222
+
223
+ Returns an `AccountInfo` dict:
224
+ - `organizationId`: Organization ID
225
+ - `plan`: Plan name
226
+ - `maxCredits`: Total credits
227
+ - `usedCredits`: Used credits
228
+ - `autoRechargeThreshold`: Auto-recharge threshold (if enabled)
229
+
230
+ ## License
231
+
232
+ MIT
poofbg-0.1.0/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # poofbg
2
+
3
+ The official Python SDK for the [Poof](https://poof.bg) background removal API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install poofbg
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ from poof import Poof
15
+
16
+ # Initialize the client
17
+ client = Poof(api_key="your-api-key")
18
+
19
+ # Remove background from an image
20
+ result = client.remove_background("photo.jpg")
21
+
22
+ # Save the result
23
+ result.save("output.png")
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Remove Background
29
+
30
+ ```python
31
+ from poof import Poof
32
+
33
+ client = Poof(api_key="your-api-key")
34
+
35
+ # From a file path
36
+ result = client.remove_background("image.jpg")
37
+
38
+ # From bytes
39
+ with open("image.jpg", "rb") as f:
40
+ image_bytes = f.read()
41
+ result = client.remove_background(image_bytes)
42
+
43
+ # From a file object
44
+ with open("image.jpg", "rb") as f:
45
+ result = client.remove_background(f)
46
+
47
+ # Save the result
48
+ result.save("output.png")
49
+
50
+ # Or get the bytes directly
51
+ image_data = result.data
52
+ # or: image_data = bytes(result)
53
+ ```
54
+
55
+ ### Options
56
+
57
+ ```python
58
+ # Different output format
59
+ result = client.remove_background("photo.jpg", format="webp")
60
+
61
+ # Crop to subject bounds
62
+ result = client.remove_background("photo.jpg", crop=True)
63
+
64
+ # RGB output with background color (no transparency)
65
+ result = client.remove_background(
66
+ "photo.jpg",
67
+ channels="rgb",
68
+ bg_color="#ffffff"
69
+ )
70
+
71
+ # Different size presets
72
+ result = client.remove_background("photo.jpg", size="preview")
73
+ ```
74
+
75
+ ### Result Metadata
76
+
77
+ ```python
78
+ result = client.remove_background("photo.jpg")
79
+
80
+ print(f"Image size: {result.width}x{result.height}")
81
+ print(f"Processing time: {result.processing_time_ms}ms")
82
+ print(f"Content type: {result.content_type}")
83
+ print(f"Request ID: {result.request_id}")
84
+ ```
85
+
86
+ ### Account Info
87
+
88
+ ```python
89
+ info = client.me()
90
+
91
+ print(f"Plan: {info['plan']}")
92
+ print(f"Credits used: {info['usedCredits']} / {info['maxCredits']}")
93
+ ```
94
+
95
+ ### Context Manager
96
+
97
+ ```python
98
+ with Poof(api_key="your-api-key") as client:
99
+ result = client.remove_background("photo.jpg")
100
+ result.save("output.png")
101
+ # Client is automatically closed
102
+ ```
103
+
104
+ ## Error Handling
105
+
106
+ ```python
107
+ from poof import (
108
+ Poof,
109
+ PoofError,
110
+ AuthError,
111
+ RateLimitError,
112
+ PaymentRequiredError,
113
+ ValidationError,
114
+ )
115
+
116
+ client = Poof(api_key="your-api-key")
117
+
118
+ try:
119
+ result = client.remove_background("photo.jpg")
120
+ except AuthError as e:
121
+ print(f"Authentication failed: {e.message}")
122
+ except PaymentRequiredError as e:
123
+ print(f"Insufficient credits: {e.message}")
124
+ except RateLimitError as e:
125
+ print(f"Rate limited: {e.message}")
126
+ except ValidationError as e:
127
+ print(f"Invalid request: {e.message}")
128
+ except PoofError as e:
129
+ print(f"API error: {e.message} (code: {e.code})")
130
+ ```
131
+
132
+ All exceptions include:
133
+ - `message`: Human-readable error description
134
+ - `code`: Machine-readable error code
135
+ - `details`: Additional troubleshooting info
136
+ - `request_id`: Unique ID for support
137
+ - `status_code`: HTTP status code
138
+
139
+ ## Configuration
140
+
141
+ ```python
142
+ # Custom timeout (default: 60 seconds)
143
+ client = Poof(api_key="...", timeout=120)
144
+
145
+ # Custom base URL
146
+ client = Poof(api_key="...", base_url="https://api.example.com/v1")
147
+
148
+ # Bring your own httpx client
149
+ import httpx
150
+
151
+ with httpx.Client(timeout=30, http2=True) as http_client:
152
+ client = Poof(api_key="...", httpx_client=http_client)
153
+ result = client.remove_background("photo.jpg")
154
+ ```
155
+
156
+ ## API Reference
157
+
158
+ ### `Poof(api_key, *, base_url, timeout, httpx_client)`
159
+
160
+ Create a new Poof client.
161
+
162
+ | Parameter | Type | Default | Description |
163
+ |-----------|------|---------|-------------|
164
+ | `api_key` | `str` | required | Your Poof API key |
165
+ | `base_url` | `str` | `https://api.poof.bg/v1` | API base URL |
166
+ | `timeout` | `float` | `60.0` | Request timeout in seconds |
167
+ | `httpx_client` | `httpx.Client` | `None` | Custom HTTP client |
168
+
169
+ ### `client.remove_background(image, *, format, channels, bg_color, size, crop)`
170
+
171
+ Remove background from an image.
172
+
173
+ | Parameter | Type | Default | Description |
174
+ |-----------|------|---------|-------------|
175
+ | `image` | `str \| Path \| bytes \| BinaryIO` | required | Image to process |
176
+ | `format` | `"png" \| "jpg" \| "webp"` | `"png"` | Output format |
177
+ | `channels` | `"rgba" \| "rgb"` | `"rgba"` | Color channels |
178
+ | `bg_color` | `str` | `None` | Background color (when `channels="rgb"`) |
179
+ | `size` | `"full" \| "preview" \| "small" \| "medium" \| "large"` | `"full"` | Output size |
180
+ | `crop` | `bool` | `False` | Crop to subject bounds |
181
+
182
+ Returns a `RemoveBackgroundResult` with:
183
+ - `data`: Image bytes
184
+ - `content_type`: MIME type
185
+ - `width`, `height`: Image dimensions
186
+ - `processing_time_ms`: Processing time
187
+ - `request_id`: Request ID for support
188
+ - `save(path)`: Save to file
189
+
190
+ ### `client.me()`
191
+
192
+ Get account information.
193
+
194
+ Returns an `AccountInfo` dict:
195
+ - `organizationId`: Organization ID
196
+ - `plan`: Plan name
197
+ - `maxCredits`: Total credits
198
+ - `usedCredits`: Used credits
199
+ - `autoRechargeThreshold`: Auto-recharge threshold (if enabled)
200
+
201
+ ## License
202
+
203
+ MIT
@@ -0,0 +1,81 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "poofbg"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the Poof background removal API"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Poof", email = "support@poof.bg" }
14
+ ]
15
+ keywords = [
16
+ "background-removal",
17
+ "image-processing",
18
+ "poof",
19
+ "api",
20
+ "sdk",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Multimedia :: Graphics",
34
+ "Topic :: Software Development :: Libraries :: Python Modules",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = [
38
+ "httpx>=0.24.0",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://poof.bg"
43
+ Documentation = "https://docs.poof.bg"
44
+ Repository = "https://github.com/poof-bg/py"
45
+ Issues = "https://github.com/poof-bg/py/issues"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/poof"]
49
+
50
+ [tool.hatch.build.targets.sdist]
51
+ include = [
52
+ "/src",
53
+ "/README.md",
54
+ "/LICENSE",
55
+ ]
56
+
57
+ [tool.ruff]
58
+ target-version = "py39"
59
+ line-length = 88
60
+
61
+ [tool.ruff.lint]
62
+ select = [
63
+ "E", # pycodestyle errors
64
+ "W", # pycodestyle warnings
65
+ "F", # pyflakes
66
+ "I", # isort
67
+ "B", # flake8-bugbear
68
+ "UP", # pyupgrade
69
+ ]
70
+ ignore = [
71
+ "E501", # line too long (handled by formatter)
72
+ ]
73
+
74
+ [tool.mypy]
75
+ python_version = "3.9"
76
+ strict = true
77
+ warn_return_any = true
78
+ warn_unused_configs = true
79
+
80
+ [tool.pytest.ini_options]
81
+ testpaths = ["tests"]
@@ -0,0 +1,364 @@
1
+ """
2
+ Poof - Python SDK for the Poof background removal API.
3
+
4
+ Simple, clean API for removing backgrounds from images.
5
+
6
+ Example:
7
+ from poof import Poof
8
+
9
+ client = Poof(api_key="your-api-key")
10
+ result = client.remove_background("image.jpg")
11
+
12
+ # Save the result
13
+ with open("output.png", "wb") as f:
14
+ f.write(result)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import mimetypes
20
+ import os
21
+ from io import IOBase
22
+ from pathlib import Path
23
+ from typing import BinaryIO, Literal, TypedDict, Union
24
+
25
+ import httpx
26
+
27
+ from .exceptions import (
28
+ AuthError,
29
+ PaymentRequiredError,
30
+ PermissionDeniedError,
31
+ PoofError,
32
+ RateLimitError,
33
+ ServerError,
34
+ ValidationError,
35
+ )
36
+
37
+ __version__ = "0.1.0"
38
+ __all__ = [
39
+ "Poof",
40
+ "PoofError",
41
+ "AuthError",
42
+ "RateLimitError",
43
+ "PaymentRequiredError",
44
+ "PermissionDeniedError",
45
+ "ValidationError",
46
+ "ServerError",
47
+ "AccountInfo",
48
+ "RemoveBackgroundResult",
49
+ ]
50
+
51
+ DEFAULT_BASE_URL = "https://api.poof.bg/v1"
52
+ DEFAULT_TIMEOUT = 60.0
53
+
54
+ # Type aliases
55
+ ImageInput = Union[str, Path, bytes, BinaryIO]
56
+ Format = Literal["png", "jpg", "webp"]
57
+ Channels = Literal["rgba", "rgb"]
58
+ Size = Literal["full", "preview", "small", "medium", "large"]
59
+
60
+
61
+ class AccountInfo(TypedDict, total=False):
62
+ """Account information returned by the /me endpoint."""
63
+
64
+ organizationId: str
65
+ plan: str
66
+ maxCredits: int
67
+ usedCredits: int
68
+ autoRechargeThreshold: int | None
69
+
70
+
71
+ class RemoveBackgroundResult:
72
+ """Result of a background removal operation.
73
+
74
+ Attributes:
75
+ data: The processed image bytes.
76
+ content_type: MIME type of the image (e.g., "image/png").
77
+ request_id: Unique request identifier for support.
78
+ processing_time_ms: Processing time in milliseconds.
79
+ width: Output image width in pixels.
80
+ height: Output image height in pixels.
81
+ """
82
+
83
+ __slots__ = (
84
+ "data",
85
+ "content_type",
86
+ "request_id",
87
+ "processing_time_ms",
88
+ "width",
89
+ "height",
90
+ )
91
+
92
+ def __init__(
93
+ self,
94
+ data: bytes,
95
+ content_type: str | None = None,
96
+ request_id: str | None = None,
97
+ processing_time_ms: int | None = None,
98
+ width: int | None = None,
99
+ height: int | None = None,
100
+ ) -> None:
101
+ self.data = data
102
+ self.content_type = content_type
103
+ self.request_id = request_id
104
+ self.processing_time_ms = processing_time_ms
105
+ self.width = width
106
+ self.height = height
107
+
108
+ def __bytes__(self) -> bytes:
109
+ """Return the image data as bytes."""
110
+ return self.data
111
+
112
+ def __len__(self) -> int:
113
+ """Return the size of the image data in bytes."""
114
+ return len(self.data)
115
+
116
+ def save(self, path: str | Path) -> None:
117
+ """Save the image to a file.
118
+
119
+ Args:
120
+ path: Destination file path.
121
+ """
122
+ Path(path).write_bytes(self.data)
123
+
124
+ def __repr__(self) -> str:
125
+ return (
126
+ f"RemoveBackgroundResult("
127
+ f"size={len(self.data)}, "
128
+ f"content_type={self.content_type!r}, "
129
+ f"dimensions={self.width}x{self.height})"
130
+ )
131
+
132
+
133
+ class Poof:
134
+ """Client for the Poof background removal API.
135
+
136
+ Args:
137
+ api_key: Your Poof API key. Get one at https://dash.poof.bg
138
+ base_url: API base URL. Defaults to https://api.poof.bg/v1
139
+ timeout: Request timeout in seconds. Defaults to 60.
140
+ httpx_client: Optional custom httpx.Client instance.
141
+
142
+ Example:
143
+ >>> from poof import Poof
144
+ >>> client = Poof(api_key="your-api-key")
145
+ >>> result = client.remove_background("photo.jpg")
146
+ >>> result.save("output.png")
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ api_key: str,
152
+ *,
153
+ base_url: str = DEFAULT_BASE_URL,
154
+ timeout: float = DEFAULT_TIMEOUT,
155
+ httpx_client: httpx.Client | None = None,
156
+ ) -> None:
157
+ if not api_key:
158
+ raise ValueError("api_key is required")
159
+
160
+ self._api_key = api_key
161
+ self._base_url = base_url.rstrip("/")
162
+ self._timeout = timeout
163
+ self._owns_client = httpx_client is None
164
+ self._client = httpx_client or httpx.Client(timeout=timeout)
165
+
166
+ def __enter__(self) -> Poof:
167
+ return self
168
+
169
+ def __exit__(self, *args: object) -> None:
170
+ self.close()
171
+
172
+ def close(self) -> None:
173
+ """Close the underlying HTTP client."""
174
+ if self._owns_client:
175
+ self._client.close()
176
+
177
+ def _get_headers(self) -> dict[str, str]:
178
+ """Get default headers for API requests."""
179
+ return {
180
+ "x-api-key": self._api_key,
181
+ "User-Agent": f"poofbg/{__version__}",
182
+ }
183
+
184
+ def _handle_error(self, response: httpx.Response) -> None:
185
+ """Handle error responses from the API."""
186
+ try:
187
+ error_data = response.json()
188
+ code = error_data.get("code", "unknown_error")
189
+ message = error_data.get("message", "Unknown error")
190
+ details = error_data.get("details")
191
+ request_id = error_data.get("request_id")
192
+ except Exception:
193
+ code = "unknown_error"
194
+ message = response.text or f"HTTP {response.status_code}"
195
+ details = None
196
+ request_id = response.headers.get("X-Request-ID")
197
+
198
+ error_kwargs = {
199
+ "message": message,
200
+ "code": code,
201
+ "details": details,
202
+ "request_id": request_id,
203
+ "status_code": response.status_code,
204
+ }
205
+
206
+ if response.status_code == 401:
207
+ raise AuthError(**error_kwargs)
208
+ elif response.status_code == 402:
209
+ raise PaymentRequiredError(**error_kwargs)
210
+ elif response.status_code == 403:
211
+ raise PermissionDeniedError(**error_kwargs)
212
+ elif response.status_code == 429:
213
+ raise RateLimitError(**error_kwargs)
214
+ elif response.status_code == 400:
215
+ raise ValidationError(**error_kwargs)
216
+ elif response.status_code >= 500:
217
+ raise ServerError(**error_kwargs)
218
+ else:
219
+ raise PoofError(**error_kwargs)
220
+
221
+ def _prepare_image(
222
+ self, image: ImageInput
223
+ ) -> tuple[str, bytes, str]:
224
+ """Prepare image for upload.
225
+
226
+ Returns:
227
+ Tuple of (filename, data, content_type)
228
+ """
229
+ if isinstance(image, (str, Path)):
230
+ path = Path(image)
231
+ if not path.exists():
232
+ raise FileNotFoundError(f"Image file not found: {path}")
233
+ filename = path.name
234
+ data = path.read_bytes()
235
+ content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
236
+ elif isinstance(image, bytes):
237
+ filename = "image"
238
+ data = image
239
+ content_type = "application/octet-stream"
240
+ elif isinstance(image, IOBase) or hasattr(image, "read"):
241
+ # File-like object
242
+ data = image.read() # type: ignore
243
+ filename = getattr(image, "name", "image")
244
+ if isinstance(filename, (str, Path)):
245
+ filename = Path(filename).name
246
+ else:
247
+ filename = "image"
248
+ content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
249
+ else:
250
+ raise TypeError(
251
+ f"image must be a file path, bytes, or file-like object, "
252
+ f"got {type(image).__name__}"
253
+ )
254
+
255
+ return filename, data, content_type
256
+
257
+ def remove_background(
258
+ self,
259
+ image: ImageInput,
260
+ *,
261
+ format: Format | None = None,
262
+ channels: Channels | None = None,
263
+ bg_color: str | None = None,
264
+ size: Size | None = None,
265
+ crop: bool | None = None,
266
+ ) -> RemoveBackgroundResult:
267
+ """Remove background from an image.
268
+
269
+ Args:
270
+ image: Image to process. Can be a file path (str or Path),
271
+ bytes, or a file-like object.
272
+ format: Output format - "png", "jpg", or "webp". Defaults to "png".
273
+ channels: Color channels - "rgba" for transparency, "rgb" for opaque.
274
+ Defaults to "rgba".
275
+ bg_color: Background color when channels="rgb". Can be hex (#ffffff),
276
+ rgb, or color name.
277
+ size: Output size preset - "full", "preview", "small", "medium", "large".
278
+ Defaults to "full".
279
+ crop: Whether to crop the image to the subject bounds.
280
+
281
+ Returns:
282
+ RemoveBackgroundResult containing the processed image and metadata.
283
+
284
+ Raises:
285
+ FileNotFoundError: If image path doesn't exist.
286
+ AuthError: Invalid or missing API key.
287
+ PaymentRequiredError: Insufficient credits.
288
+ RateLimitError: Rate limit exceeded.
289
+ ValidationError: Invalid parameters or image.
290
+ ServerError: Server-side error.
291
+ PoofError: Other API errors.
292
+
293
+ Example:
294
+ >>> result = client.remove_background("photo.jpg", format="webp", crop=True)
295
+ >>> result.save("output.webp")
296
+ """
297
+ filename, data, content_type = self._prepare_image(image)
298
+
299
+ # Build multipart form data
300
+ files = {"image_file": (filename, data, content_type)}
301
+ form_data: dict[str, str] = {}
302
+
303
+ if format is not None:
304
+ form_data["format"] = format
305
+ if channels is not None:
306
+ form_data["channels"] = channels
307
+ if bg_color is not None:
308
+ form_data["bg_color"] = bg_color
309
+ if size is not None:
310
+ form_data["size"] = size
311
+ if crop is not None:
312
+ form_data["crop"] = str(crop).lower()
313
+
314
+ response = self._client.post(
315
+ f"{self._base_url}/remove",
316
+ headers=self._get_headers(),
317
+ files=files,
318
+ data=form_data if form_data else None,
319
+ )
320
+
321
+ if response.status_code != 200:
322
+ self._handle_error(response)
323
+
324
+ # Extract metadata from headers
325
+ headers = response.headers
326
+ processing_time = headers.get("X-Processing-Time-Ms")
327
+ width = headers.get("X-Image-Width")
328
+ height = headers.get("X-Image-Height")
329
+
330
+ return RemoveBackgroundResult(
331
+ data=response.content,
332
+ content_type=headers.get("Content-Type"),
333
+ request_id=headers.get("X-Request-ID"),
334
+ processing_time_ms=int(processing_time) if processing_time else None,
335
+ width=int(width) if width else None,
336
+ height=int(height) if height else None,
337
+ )
338
+
339
+ def me(self) -> AccountInfo:
340
+ """Get account information.
341
+
342
+ Returns information about the authenticated account including
343
+ plan details and credit usage.
344
+
345
+ Returns:
346
+ AccountInfo dict with organization details and credit usage.
347
+
348
+ Raises:
349
+ AuthError: Invalid or missing API key.
350
+ PoofError: Other API errors.
351
+
352
+ Example:
353
+ >>> info = client.me()
354
+ >>> print(f"Used {info['usedCredits']} of {info['maxCredits']} credits")
355
+ """
356
+ response = self._client.get(
357
+ f"{self._base_url}/me",
358
+ headers=self._get_headers(),
359
+ )
360
+
361
+ if response.status_code != 200:
362
+ self._handle_error(response)
363
+
364
+ return response.json()
@@ -0,0 +1,96 @@
1
+ """Custom exceptions for the Poof SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class PoofError(Exception):
7
+ """Base exception for all Poof API errors.
8
+
9
+ Attributes:
10
+ message: Human-readable error message.
11
+ code: Machine-readable error code.
12
+ details: Additional details or troubleshooting steps.
13
+ request_id: Unique request ID for support.
14
+ status_code: HTTP status code.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ message: str,
20
+ *,
21
+ code: str | None = None,
22
+ details: str | None = None,
23
+ request_id: str | None = None,
24
+ status_code: int | None = None,
25
+ ) -> None:
26
+ self.message = message
27
+ self.code = code
28
+ self.details = details
29
+ self.request_id = request_id
30
+ self.status_code = status_code
31
+ super().__init__(message)
32
+
33
+ def __repr__(self) -> str:
34
+ parts = [f"PoofError({self.message!r}"]
35
+ if self.code:
36
+ parts.append(f", code={self.code!r}")
37
+ if self.status_code:
38
+ parts.append(f", status_code={self.status_code}")
39
+ if self.request_id:
40
+ parts.append(f", request_id={self.request_id!r}")
41
+ parts.append(")")
42
+ return "".join(parts)
43
+
44
+
45
+ class AuthError(PoofError):
46
+ """Raised when authentication fails (401).
47
+
48
+ This typically means the API key is invalid, missing, or revoked.
49
+ """
50
+
51
+ pass
52
+
53
+
54
+ class PaymentRequiredError(PoofError):
55
+ """Raised when the account has insufficient credits (402).
56
+
57
+ Upgrade your plan or wait for the next billing cycle.
58
+ """
59
+
60
+ pass
61
+
62
+
63
+ class PermissionDeniedError(PoofError):
64
+ """Raised when access is forbidden (403).
65
+
66
+ The account may be suspended or the API key lacks permissions.
67
+ """
68
+
69
+ pass
70
+
71
+
72
+ class RateLimitError(PoofError):
73
+ """Raised when rate limit is exceeded (429).
74
+
75
+ Implement exponential backoff and retry.
76
+ """
77
+
78
+ pass
79
+
80
+
81
+ class ValidationError(PoofError):
82
+ """Raised when request validation fails (400).
83
+
84
+ Check the parameters and image format.
85
+ """
86
+
87
+ pass
88
+
89
+
90
+ class ServerError(PoofError):
91
+ """Raised when a server-side error occurs (5xx).
92
+
93
+ Please try again later.
94
+ """
95
+
96
+ pass
File without changes