hammad-python 0.0.14__py3-none-any.whl → 0.0.15__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad_python-0.0.15.dist-info/METADATA +184 -0
- hammad_python-0.0.15.dist-info/RECORD +4 -0
- hammad/__init__.py +0 -1
- hammad/ai/__init__.py +0 -1
- hammad/ai/_utils.py +0 -142
- hammad/ai/completions/__init__.py +0 -45
- hammad/ai/completions/client.py +0 -684
- hammad/ai/completions/create.py +0 -710
- hammad/ai/completions/settings.py +0 -100
- hammad/ai/completions/types.py +0 -792
- hammad/ai/completions/utils.py +0 -486
- hammad/ai/embeddings/__init__.py +0 -35
- hammad/ai/embeddings/client/__init__.py +0 -1
- hammad/ai/embeddings/client/base_embeddings_client.py +0 -26
- hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +0 -200
- hammad/ai/embeddings/client/litellm_embeddings_client.py +0 -288
- hammad/ai/embeddings/create.py +0 -159
- hammad/ai/embeddings/types.py +0 -69
- hammad/cache/__init__.py +0 -40
- hammad/cache/base_cache.py +0 -181
- hammad/cache/cache.py +0 -169
- hammad/cache/decorators.py +0 -261
- hammad/cache/file_cache.py +0 -80
- hammad/cache/ttl_cache.py +0 -74
- hammad/cli/__init__.py +0 -33
- hammad/cli/animations.py +0 -573
- hammad/cli/plugins.py +0 -781
- hammad/cli/styles/__init__.py +0 -55
- hammad/cli/styles/settings.py +0 -139
- hammad/cli/styles/types.py +0 -358
- hammad/cli/styles/utils.py +0 -480
- hammad/data/__init__.py +0 -56
- hammad/data/collections/__init__.py +0 -34
- hammad/data/collections/base_collection.py +0 -58
- hammad/data/collections/collection.py +0 -452
- hammad/data/collections/searchable_collection.py +0 -556
- hammad/data/collections/vector_collection.py +0 -596
- hammad/data/configurations/__init__.py +0 -35
- hammad/data/configurations/configuration.py +0 -564
- hammad/data/databases/__init__.py +0 -21
- hammad/data/databases/database.py +0 -902
- hammad/data/models/__init__.py +0 -44
- hammad/data/models/base/__init__.py +0 -35
- hammad/data/models/base/fields.py +0 -546
- hammad/data/models/base/model.py +0 -1078
- hammad/data/models/base/utils.py +0 -280
- hammad/data/models/pydantic/__init__.py +0 -55
- hammad/data/models/pydantic/converters.py +0 -632
- hammad/data/models/pydantic/models/__init__.py +0 -28
- hammad/data/models/pydantic/models/arbitrary_model.py +0 -46
- hammad/data/models/pydantic/models/cacheable_model.py +0 -79
- hammad/data/models/pydantic/models/fast_model.py +0 -318
- hammad/data/models/pydantic/models/function_model.py +0 -176
- hammad/data/models/pydantic/models/subscriptable_model.py +0 -63
- hammad/data/types/__init__.py +0 -41
- hammad/data/types/file.py +0 -358
- hammad/data/types/multimodal/__init__.py +0 -24
- hammad/data/types/multimodal/audio.py +0 -96
- hammad/data/types/multimodal/image.py +0 -80
- hammad/data/types/text.py +0 -1066
- hammad/formatting/__init__.py +0 -38
- hammad/formatting/json/__init__.py +0 -21
- hammad/formatting/json/converters.py +0 -152
- hammad/formatting/text/__init__.py +0 -63
- hammad/formatting/text/converters.py +0 -723
- hammad/formatting/text/markdown.py +0 -131
- hammad/formatting/yaml/__init__.py +0 -26
- hammad/formatting/yaml/converters.py +0 -5
- hammad/logging/__init__.py +0 -35
- hammad/logging/decorators.py +0 -834
- hammad/logging/logger.py +0 -954
- hammad/mcp/__init__.py +0 -50
- hammad/mcp/client/__init__.py +0 -1
- hammad/mcp/client/client.py +0 -523
- hammad/mcp/client/client_service.py +0 -393
- hammad/mcp/client/settings.py +0 -178
- hammad/mcp/servers/__init__.py +0 -1
- hammad/mcp/servers/launcher.py +0 -1161
- hammad/performance/__init__.py +0 -36
- hammad/performance/imports.py +0 -231
- hammad/performance/runtime/__init__.py +0 -32
- hammad/performance/runtime/decorators.py +0 -142
- hammad/performance/runtime/run.py +0 -299
- hammad/py.typed +0 -0
- hammad/service/__init__.py +0 -49
- hammad/service/create.py +0 -532
- hammad/service/decorators.py +0 -285
- hammad/typing/__init__.py +0 -407
- hammad/web/__init__.py +0 -43
- hammad/web/http/__init__.py +0 -1
- hammad/web/http/client.py +0 -944
- hammad/web/models.py +0 -245
- hammad/web/openapi/__init__.py +0 -1
- hammad/web/openapi/client.py +0 -740
- hammad/web/search/__init__.py +0 -1
- hammad/web/search/client.py +0 -988
- hammad/web/utils.py +0 -472
- hammad_python-0.0.14.dist-info/METADATA +0 -70
- hammad_python-0.0.14.dist-info/RECORD +0 -99
- {hammad_python-0.0.14.dist-info → hammad_python-0.0.15.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.14.dist-info → hammad_python-0.0.15.dist-info}/licenses/LICENSE +0 -0
hammad/data/types/file.py
DELETED
@@ -1,358 +0,0 @@
|
|
1
|
-
"""hammad.types.file"""
|
2
|
-
|
3
|
-
from pathlib import Path
|
4
|
-
import httpx
|
5
|
-
from typing import Any, Self
|
6
|
-
import mimetypes
|
7
|
-
from urllib.parse import urlparse
|
8
|
-
|
9
|
-
from ..models.base.model import Model
|
10
|
-
from ..models.base.fields import field
|
11
|
-
|
12
|
-
__all__ = ("File", "FileSource")
|
13
|
-
|
14
|
-
|
15
|
-
_FILE_SIGNATURES = {
|
16
|
-
b"\x89PNG": "image/png",
|
17
|
-
b"\xff\xd8\xff": "image/jpeg",
|
18
|
-
b"GIF87a": "image/gif",
|
19
|
-
b"GIF89a": "image/gif",
|
20
|
-
b"%PDF": "application/pdf",
|
21
|
-
b"PK": "application/zip",
|
22
|
-
}
|
23
|
-
|
24
|
-
|
25
|
-
_mime_cache: dict[str, str] = {}
|
26
|
-
"""Cache for MIME types."""
|
27
|
-
|
28
|
-
|
29
|
-
class FileSource(Model, kw_only=True, dict=True, frozen=True):
|
30
|
-
"""Represents the source of a `File` object."""
|
31
|
-
|
32
|
-
is_file: bool = field(default=False)
|
33
|
-
"""Whether this data represents a file."""
|
34
|
-
is_dir: bool = field(default=False)
|
35
|
-
"""Whether this data represents a directory."""
|
36
|
-
is_url: bool = field(default=False)
|
37
|
-
"""Whether this data originates from a URL."""
|
38
|
-
path: Path | None = field(default=None)
|
39
|
-
"""The file path if this is file-based data."""
|
40
|
-
url: str | None = field(default=None)
|
41
|
-
"""The URL if this is URL-based data."""
|
42
|
-
size: int | None = field(default=None)
|
43
|
-
"""Size in bytes if available."""
|
44
|
-
encoding: str | None = field(default=None)
|
45
|
-
"""Text encoding if applicable."""
|
46
|
-
|
47
|
-
|
48
|
-
class File(Model, kw_only=True, dict=True):
|
49
|
-
"""Base object for all file-like structure types within
|
50
|
-
the `hammad` ecosystem."""
|
51
|
-
|
52
|
-
data: Any | None = field(default=None)
|
53
|
-
"""The actual data content (bytes, string, path object, etc.)"""
|
54
|
-
type: str | None = field(default=None)
|
55
|
-
"""The MIME type or identifier for the data."""
|
56
|
-
|
57
|
-
source: FileSource = field(default_factory=FileSource)
|
58
|
-
"""The source of the data. Contains metadata as well."""
|
59
|
-
|
60
|
-
# Private cached attributes
|
61
|
-
_name: str | None = field(default=None)
|
62
|
-
_extension: str | None = field(default=None)
|
63
|
-
_repr: str | None = field(default=None)
|
64
|
-
|
65
|
-
@property
|
66
|
-
def name(self) -> str | None:
|
67
|
-
"""Returns the name of this data object."""
|
68
|
-
if self._name is not None:
|
69
|
-
return self._name
|
70
|
-
|
71
|
-
if self.source.path:
|
72
|
-
self._name = self.source.path.name
|
73
|
-
elif self.source.url:
|
74
|
-
parsed = urlparse(self.source.url)
|
75
|
-
self._name = parsed.path.split("/")[-1] or parsed.netloc
|
76
|
-
else:
|
77
|
-
self._name = "" # Cache empty result
|
78
|
-
|
79
|
-
return self._name if self._name else None
|
80
|
-
|
81
|
-
@property
|
82
|
-
def extension(self) -> str | None:
|
83
|
-
"""Returns the extension of this data object."""
|
84
|
-
if self._extension is not None:
|
85
|
-
return self._extension
|
86
|
-
|
87
|
-
if self.source.path:
|
88
|
-
self._extension = self.source.path.suffix
|
89
|
-
elif name := self.name:
|
90
|
-
if "." in name:
|
91
|
-
self._extension = f".{name.rsplit('.', 1)[-1]}"
|
92
|
-
else:
|
93
|
-
self._extension = "" # Cache empty result
|
94
|
-
else:
|
95
|
-
self._extension = "" # Cache empty result
|
96
|
-
|
97
|
-
return self._extension if self._extension else None
|
98
|
-
|
99
|
-
@property
|
100
|
-
def exists(self) -> bool:
|
101
|
-
"""Returns whether this data object exists."""
|
102
|
-
if self.data is not None:
|
103
|
-
return True
|
104
|
-
if self.source.path and (self.source.is_file or self.source.is_dir):
|
105
|
-
return self.source.path.exists()
|
106
|
-
return False
|
107
|
-
|
108
|
-
def read(self) -> bytes | str:
|
109
|
-
"""Reads the data content.
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
The data content as bytes or string depending on the source.
|
113
|
-
|
114
|
-
Raises:
|
115
|
-
ValueError: If the data cannot be read.
|
116
|
-
"""
|
117
|
-
if self.data is not None:
|
118
|
-
return self.data
|
119
|
-
|
120
|
-
if self.source.path and self.source.is_file and self.source.path.exists():
|
121
|
-
if self.source.encoding:
|
122
|
-
return self.source.path.read_text(encoding=self.source.encoding)
|
123
|
-
return self.source.path.read_bytes()
|
124
|
-
|
125
|
-
raise ValueError(f"Cannot read data from {self.name or 'unknown source'}")
|
126
|
-
|
127
|
-
def to_file(self, path: str | Path, *, overwrite: bool = False) -> Path:
|
128
|
-
"""Save the data to a file.
|
129
|
-
|
130
|
-
Args:
|
131
|
-
path: The path to save to.
|
132
|
-
overwrite: If True, overwrite existing files.
|
133
|
-
|
134
|
-
Returns:
|
135
|
-
The path where the file was saved.
|
136
|
-
|
137
|
-
Raises:
|
138
|
-
FileExistsError: If file exists and overwrite is False.
|
139
|
-
ValueError: If data cannot be saved.
|
140
|
-
"""
|
141
|
-
save_path = Path(path)
|
142
|
-
|
143
|
-
if save_path.exists() and not overwrite:
|
144
|
-
raise FileExistsError(f"File already exists: {save_path}")
|
145
|
-
|
146
|
-
# Ensure parent directory exists
|
147
|
-
save_path.parent.mkdir(parents=True, exist_ok=True)
|
148
|
-
|
149
|
-
data = self.read()
|
150
|
-
if isinstance(data, str):
|
151
|
-
save_path.write_text(data, encoding=self.source.encoding or "utf-8")
|
152
|
-
else:
|
153
|
-
save_path.write_bytes(data)
|
154
|
-
|
155
|
-
return save_path
|
156
|
-
|
157
|
-
def __repr__(self) -> str:
|
158
|
-
"""Returns a string representation of the data object."""
|
159
|
-
if self._repr is not None:
|
160
|
-
return self._repr
|
161
|
-
|
162
|
-
parts = []
|
163
|
-
|
164
|
-
if self.source.path:
|
165
|
-
parts.append(f"path={self.source.path!r}")
|
166
|
-
elif self.source.url:
|
167
|
-
parts.append(f"url={self.source.url!r}")
|
168
|
-
elif self.data is not None:
|
169
|
-
parts.append(f"data={self.data!r}")
|
170
|
-
|
171
|
-
if self.source.is_file:
|
172
|
-
parts.append("is_file=True")
|
173
|
-
elif self.source.is_dir:
|
174
|
-
parts.append("is_dir=True")
|
175
|
-
elif self.source.is_url:
|
176
|
-
parts.append("is_url=True")
|
177
|
-
|
178
|
-
if (size := self.source.size) is not None:
|
179
|
-
if size < 1024:
|
180
|
-
size_str = f"{size}B"
|
181
|
-
elif size < 1048576: # 1024 * 1024
|
182
|
-
size_str = f"{size / 1024:.1f}KB"
|
183
|
-
elif size < 1073741824: # 1024 * 1024 * 1024
|
184
|
-
size_str = f"{size / 1048576:.1f}MB"
|
185
|
-
else:
|
186
|
-
size_str = f"{size / 1073741824:.1f}GB"
|
187
|
-
parts.append(f"size={size_str}")
|
188
|
-
|
189
|
-
if self.source.encoding:
|
190
|
-
parts.append(f"encoding={self.source.encoding!r}")
|
191
|
-
|
192
|
-
self._repr = f"<{', '.join(parts)}>"
|
193
|
-
return self._repr
|
194
|
-
|
195
|
-
def __eq__(self, other: Any) -> bool:
|
196
|
-
"""Returns whether this data object is equal to another."""
|
197
|
-
return isinstance(other, File) and self.data == other.data
|
198
|
-
|
199
|
-
@classmethod
|
200
|
-
def from_path(
|
201
|
-
cls,
|
202
|
-
path: str | Path,
|
203
|
-
*,
|
204
|
-
encoding: str | None = None,
|
205
|
-
lazy: bool = True,
|
206
|
-
) -> Self:
|
207
|
-
"""Creates a data object from a filepath and
|
208
|
-
assigns the appropriate type and flags.
|
209
|
-
|
210
|
-
Args:
|
211
|
-
path: The file or directory path.
|
212
|
-
encoding: Text encoding for reading text files.
|
213
|
-
lazy: If True, defer loading content until needed.
|
214
|
-
|
215
|
-
Returns:
|
216
|
-
A new Data instance representing the file or directory.
|
217
|
-
"""
|
218
|
-
path = Path(path)
|
219
|
-
|
220
|
-
# Use cached stat call
|
221
|
-
try:
|
222
|
-
stat = path.stat()
|
223
|
-
is_file = stat.st_mode & 0o170000 == 0o100000 # S_IFREG
|
224
|
-
is_dir = stat.st_mode & 0o170000 == 0o040000 # S_IFDIR
|
225
|
-
size = stat.st_size if is_file else None
|
226
|
-
except OSError:
|
227
|
-
is_file = is_dir = False
|
228
|
-
size = None
|
229
|
-
|
230
|
-
# Get MIME type for files using cache
|
231
|
-
mime_type = None
|
232
|
-
if is_file:
|
233
|
-
path_str = str(path)
|
234
|
-
if path_str in _mime_cache:
|
235
|
-
mime_type = _mime_cache[path_str]
|
236
|
-
else:
|
237
|
-
mime_type, _ = mimetypes.guess_type(path_str)
|
238
|
-
_mime_cache[path_str] = mime_type
|
239
|
-
|
240
|
-
# Load data if not lazy and it's a file
|
241
|
-
data = None
|
242
|
-
if not lazy and is_file and size is not None:
|
243
|
-
if encoding or (mime_type and mime_type.startswith("text/")):
|
244
|
-
data = path.read_text(encoding=encoding or "utf-8")
|
245
|
-
else:
|
246
|
-
data = path.read_bytes()
|
247
|
-
|
248
|
-
return cls(
|
249
|
-
data=data,
|
250
|
-
type=mime_type,
|
251
|
-
source=FileSource(
|
252
|
-
is_file=is_file,
|
253
|
-
is_dir=is_dir,
|
254
|
-
is_url=False,
|
255
|
-
path=path,
|
256
|
-
size=size,
|
257
|
-
encoding=encoding,
|
258
|
-
),
|
259
|
-
)
|
260
|
-
|
261
|
-
@classmethod
|
262
|
-
def from_url(
|
263
|
-
cls,
|
264
|
-
url: str,
|
265
|
-
*,
|
266
|
-
type: str | None = None,
|
267
|
-
lazy: bool = True,
|
268
|
-
) -> Self:
|
269
|
-
"""Creates a data object from either a downloadable
|
270
|
-
URL (treated as a file), or a web page itself treated as a
|
271
|
-
document.
|
272
|
-
|
273
|
-
Args:
|
274
|
-
url: The URL to create data from.
|
275
|
-
type: Optional MIME type override.
|
276
|
-
lazy: If True, defer loading content until needed.
|
277
|
-
|
278
|
-
Returns:
|
279
|
-
A new Data instance representing the URL.
|
280
|
-
"""
|
281
|
-
data = None
|
282
|
-
size = None
|
283
|
-
encoding = None
|
284
|
-
|
285
|
-
# Load data if not lazy
|
286
|
-
if not lazy:
|
287
|
-
try:
|
288
|
-
with httpx.Client() as client:
|
289
|
-
response = client.get(url)
|
290
|
-
response.raise_for_status()
|
291
|
-
|
292
|
-
data = response.content
|
293
|
-
size = len(data)
|
294
|
-
|
295
|
-
# Get content type from response headers if not provided
|
296
|
-
if not type:
|
297
|
-
content_type = response.headers.get("content-type", "")
|
298
|
-
type = content_type.split(";")[0] if content_type else None
|
299
|
-
|
300
|
-
# Get encoding from response if it's text content
|
301
|
-
if response.headers.get("content-type", "").startswith("text/"):
|
302
|
-
encoding = response.encoding
|
303
|
-
data = response.text
|
304
|
-
|
305
|
-
except Exception:
|
306
|
-
# If download fails, still create the object but without data
|
307
|
-
pass
|
308
|
-
|
309
|
-
return cls(
|
310
|
-
data=data,
|
311
|
-
type=type,
|
312
|
-
source=FileSource(
|
313
|
-
is_url=True,
|
314
|
-
is_file=False,
|
315
|
-
is_dir=False,
|
316
|
-
url=url,
|
317
|
-
size=size,
|
318
|
-
encoding=encoding,
|
319
|
-
),
|
320
|
-
)
|
321
|
-
|
322
|
-
@classmethod
|
323
|
-
def from_bytes(
|
324
|
-
cls,
|
325
|
-
data: bytes,
|
326
|
-
*,
|
327
|
-
type: str | None = None,
|
328
|
-
name: str | None = None,
|
329
|
-
) -> Self:
|
330
|
-
"""Creates a data object from a bytes object.
|
331
|
-
|
332
|
-
Args:
|
333
|
-
data: The bytes data.
|
334
|
-
type: Optional MIME type.
|
335
|
-
name: Optional name for the data.
|
336
|
-
|
337
|
-
Returns:
|
338
|
-
A new Data instance containing the bytes data.
|
339
|
-
"""
|
340
|
-
# Try to detect type from content if not provided
|
341
|
-
if not type and data:
|
342
|
-
# Check against pre-compiled signatures
|
343
|
-
for sig, mime in _FILE_SIGNATURES.items():
|
344
|
-
if data.startswith(sig):
|
345
|
-
type = mime
|
346
|
-
break
|
347
|
-
|
348
|
-
return cls(
|
349
|
-
data=data,
|
350
|
-
type=type,
|
351
|
-
source=FileSource(
|
352
|
-
is_file=True,
|
353
|
-
is_dir=False,
|
354
|
-
is_url=False,
|
355
|
-
size=len(data),
|
356
|
-
path=Path(name) if name else None,
|
357
|
-
),
|
358
|
-
)
|
@@ -1,24 +0,0 @@
|
|
1
|
-
"""hammad.multimodal
|
2
|
-
|
3
|
-
Contains types and model like objects for working with various
|
4
|
-
types of multimodal data."""
|
5
|
-
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
from ....performance.imports import create_getattr_importer
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from .image import Image
|
11
|
-
from .audio import Audio
|
12
|
-
|
13
|
-
|
14
|
-
__all__ = (
|
15
|
-
"Image",
|
16
|
-
"Audio",
|
17
|
-
)
|
18
|
-
|
19
|
-
|
20
|
-
__getattr__ = create_getattr_importer(__all__)
|
21
|
-
|
22
|
-
|
23
|
-
def __dir__() -> list[str]:
|
24
|
-
return list(__all__)
|
@@ -1,96 +0,0 @@
|
|
1
|
-
"""hammad.data.types.files.audio"""
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from typing import Self
|
5
|
-
|
6
|
-
from ...types.file import File, FileSource
|
7
|
-
from ...models.base.fields import field
|
8
|
-
|
9
|
-
__all__ = ("Audio",)
|
10
|
-
|
11
|
-
|
12
|
-
class Audio(File):
|
13
|
-
"""A representation of an audio file, that is loadable from both a URL, file path
|
14
|
-
or bytes."""
|
15
|
-
|
16
|
-
# Audio-specific metadata
|
17
|
-
_duration: float | None = field(default=None)
|
18
|
-
_sample_rate: int | None = field(default=None)
|
19
|
-
_channels: int | None = field(default=None)
|
20
|
-
_format: str | None = field(default=None)
|
21
|
-
|
22
|
-
@property
|
23
|
-
def is_valid_audio(self) -> bool:
|
24
|
-
"""Check if this is a valid audio file based on MIME type."""
|
25
|
-
return self.type is not None and self.type.startswith("audio/")
|
26
|
-
|
27
|
-
@property
|
28
|
-
def format(self) -> str | None:
|
29
|
-
"""Get the audio format from MIME type."""
|
30
|
-
if self._format is None and self.type:
|
31
|
-
# Extract format from MIME type (e.g., 'audio/mp3' -> 'mp3')
|
32
|
-
self._format = self.type.split("/")[-1].upper()
|
33
|
-
return self._format
|
34
|
-
|
35
|
-
@property
|
36
|
-
def duration(self) -> float | None:
|
37
|
-
"""Get the duration of the audio file in seconds."""
|
38
|
-
return self._duration
|
39
|
-
|
40
|
-
@property
|
41
|
-
def sample_rate(self) -> int | None:
|
42
|
-
"""Get the sample rate of the audio file in Hz."""
|
43
|
-
return self._sample_rate
|
44
|
-
|
45
|
-
@property
|
46
|
-
def channels(self) -> int | None:
|
47
|
-
"""Get the number of channels in the audio file."""
|
48
|
-
return self._channels
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
def from_url(
|
52
|
-
cls,
|
53
|
-
url: str,
|
54
|
-
*,
|
55
|
-
lazy: bool = True,
|
56
|
-
timeout: float = 30.0,
|
57
|
-
) -> Self:
|
58
|
-
"""Download and create an audio file from a URL.
|
59
|
-
|
60
|
-
Args:
|
61
|
-
url: The URL to download from.
|
62
|
-
lazy: If True, defer loading content until needed.
|
63
|
-
timeout: Request timeout in seconds.
|
64
|
-
|
65
|
-
Returns:
|
66
|
-
A new Audio instance.
|
67
|
-
"""
|
68
|
-
data = None
|
69
|
-
size = None
|
70
|
-
type = None
|
71
|
-
|
72
|
-
if not lazy:
|
73
|
-
with httpx.Client(timeout=timeout) as client:
|
74
|
-
response = client.get(url)
|
75
|
-
response.raise_for_status()
|
76
|
-
|
77
|
-
data = response.content
|
78
|
-
size = len(data)
|
79
|
-
|
80
|
-
# Get content type
|
81
|
-
content_type = response.headers.get("content-type", "")
|
82
|
-
type = content_type.split(";")[0] if content_type else None
|
83
|
-
|
84
|
-
# Validate it's audio
|
85
|
-
if type and not type.startswith("audio/"):
|
86
|
-
raise ValueError(f"URL does not point to an audio file: {type}")
|
87
|
-
|
88
|
-
return cls(
|
89
|
-
data=data,
|
90
|
-
type=type,
|
91
|
-
source=FileSource(
|
92
|
-
is_url=True,
|
93
|
-
url=url,
|
94
|
-
size=size,
|
95
|
-
),
|
96
|
-
)
|
@@ -1,80 +0,0 @@
|
|
1
|
-
"""hammad.data.types.files.image"""
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from typing import Self
|
5
|
-
|
6
|
-
from ...types.file import File, FileSource
|
7
|
-
from ...models.base.fields import field
|
8
|
-
|
9
|
-
__all__ = ("Image",)
|
10
|
-
|
11
|
-
|
12
|
-
class Image(File):
|
13
|
-
"""A representation of an image, that is loadable from both a URL, file path
|
14
|
-
or bytes."""
|
15
|
-
|
16
|
-
# Image-specific metadata
|
17
|
-
_width: int | None = field(default=None)
|
18
|
-
_height: int | None = field(default=None)
|
19
|
-
_format: str | None = field(default=None)
|
20
|
-
|
21
|
-
@property
|
22
|
-
def is_valid_image(self) -> bool:
|
23
|
-
"""Check if this is a valid image based on MIME type."""
|
24
|
-
return self.type is not None and self.type.startswith("image/")
|
25
|
-
|
26
|
-
@property
|
27
|
-
def format(self) -> str | None:
|
28
|
-
"""Get the image format from MIME type."""
|
29
|
-
if self._format is None and self.type:
|
30
|
-
# Extract format from MIME type (e.g., 'image/png' -> 'png')
|
31
|
-
self._format = self.type.split("/")[-1].upper()
|
32
|
-
return self._format
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def from_url(
|
36
|
-
cls,
|
37
|
-
url: str,
|
38
|
-
*,
|
39
|
-
lazy: bool = True,
|
40
|
-
timeout: float = 30.0,
|
41
|
-
) -> Self:
|
42
|
-
"""Download and create an image from a URL.
|
43
|
-
|
44
|
-
Args:
|
45
|
-
url: The URL to download from.
|
46
|
-
lazy: If True, defer loading content until needed.
|
47
|
-
timeout: Request timeout in seconds.
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
A new Image instance.
|
51
|
-
"""
|
52
|
-
data = None
|
53
|
-
size = None
|
54
|
-
type = None
|
55
|
-
|
56
|
-
if not lazy:
|
57
|
-
with httpx.Client(timeout=timeout) as client:
|
58
|
-
response = client.get(url)
|
59
|
-
response.raise_for_status()
|
60
|
-
|
61
|
-
data = response.content
|
62
|
-
size = len(data)
|
63
|
-
|
64
|
-
# Get content type
|
65
|
-
content_type = response.headers.get("content-type", "")
|
66
|
-
type = content_type.split(";")[0] if content_type else None
|
67
|
-
|
68
|
-
# Validate it's an image
|
69
|
-
if type and not type.startswith("image/"):
|
70
|
-
raise ValueError(f"URL does not point to an image: {type}")
|
71
|
-
|
72
|
-
return cls(
|
73
|
-
data=data,
|
74
|
-
type=type,
|
75
|
-
source=FileSource(
|
76
|
-
is_url=True,
|
77
|
-
url=url,
|
78
|
-
size=size,
|
79
|
-
),
|
80
|
-
)
|