hammad-python 0.0.10__py3-none-any.whl → 0.0.12__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.
Files changed (96) hide show
  1. hammad/__init__.py +177 -10
  2. hammad/_core/__init__.py +1 -0
  3. hammad/_core/_utils/__init__.py +4 -0
  4. hammad/_core/_utils/_import_utils.py +182 -0
  5. hammad/ai/__init__.py +59 -0
  6. hammad/ai/_utils.py +142 -0
  7. hammad/ai/completions/__init__.py +44 -0
  8. hammad/ai/completions/client.py +729 -0
  9. hammad/ai/completions/create.py +686 -0
  10. hammad/ai/completions/types.py +711 -0
  11. hammad/ai/completions/utils.py +374 -0
  12. hammad/ai/embeddings/__init__.py +35 -0
  13. hammad/ai/embeddings/client/__init__.py +1 -0
  14. hammad/ai/embeddings/client/base_embeddings_client.py +26 -0
  15. hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +200 -0
  16. hammad/ai/embeddings/client/litellm_embeddings_client.py +288 -0
  17. hammad/ai/embeddings/create.py +159 -0
  18. hammad/ai/embeddings/types.py +69 -0
  19. hammad/base/__init__.py +35 -0
  20. hammad/base/fields.py +546 -0
  21. hammad/base/model.py +1078 -0
  22. hammad/base/utils.py +280 -0
  23. hammad/cache/__init__.py +48 -0
  24. hammad/cache/base_cache.py +181 -0
  25. hammad/cache/cache.py +169 -0
  26. hammad/cache/decorators.py +261 -0
  27. hammad/cache/file_cache.py +80 -0
  28. hammad/cache/ttl_cache.py +74 -0
  29. hammad/cli/__init__.py +33 -0
  30. hammad/cli/animations.py +604 -0
  31. hammad/cli/plugins.py +781 -0
  32. hammad/cli/styles/__init__.py +55 -0
  33. hammad/cli/styles/settings.py +139 -0
  34. hammad/cli/styles/types.py +358 -0
  35. hammad/cli/styles/utils.py +480 -0
  36. hammad/configuration/__init__.py +35 -0
  37. hammad/configuration/configuration.py +564 -0
  38. hammad/data/__init__.py +39 -0
  39. hammad/data/collections/__init__.py +34 -0
  40. hammad/data/collections/base_collection.py +58 -0
  41. hammad/data/collections/collection.py +452 -0
  42. hammad/data/collections/searchable_collection.py +556 -0
  43. hammad/data/collections/vector_collection.py +603 -0
  44. hammad/data/databases/__init__.py +21 -0
  45. hammad/data/databases/database.py +902 -0
  46. hammad/json/__init__.py +21 -0
  47. hammad/{utils/json → json}/converters.py +4 -1
  48. hammad/logging/__init__.py +35 -0
  49. hammad/logging/decorators.py +834 -0
  50. hammad/logging/logger.py +954 -0
  51. hammad/multimodal/__init__.py +24 -0
  52. hammad/multimodal/audio.py +96 -0
  53. hammad/multimodal/image.py +80 -0
  54. hammad/multithreading/__init__.py +304 -0
  55. hammad/pydantic/__init__.py +43 -0
  56. hammad/{utils/pydantic → pydantic}/converters.py +2 -1
  57. hammad/pydantic/models/__init__.py +28 -0
  58. hammad/pydantic/models/arbitrary_model.py +46 -0
  59. hammad/pydantic/models/cacheable_model.py +79 -0
  60. hammad/pydantic/models/fast_model.py +318 -0
  61. hammad/pydantic/models/function_model.py +176 -0
  62. hammad/pydantic/models/subscriptable_model.py +63 -0
  63. hammad/text/__init__.py +82 -0
  64. hammad/text/converters.py +723 -0
  65. hammad/{utils/markdown/formatting.py → text/markdown.py} +25 -23
  66. hammad/text/text.py +1066 -0
  67. hammad/types/__init__.py +11 -0
  68. hammad/types/file.py +358 -0
  69. hammad/{utils/typing/utils.py → typing/__init__.py} +142 -15
  70. hammad/web/__init__.py +43 -0
  71. hammad/web/http/__init__.py +1 -0
  72. hammad/web/http/client.py +944 -0
  73. hammad/web/models.py +245 -0
  74. hammad/web/openapi/client.py +740 -0
  75. hammad/web/search/__init__.py +1 -0
  76. hammad/web/search/client.py +988 -0
  77. hammad/web/utils.py +472 -0
  78. hammad/yaml/__init__.py +30 -0
  79. hammad/yaml/converters.py +19 -0
  80. {hammad_python-0.0.10.dist-info → hammad_python-0.0.12.dist-info}/METADATA +16 -7
  81. hammad_python-0.0.12.dist-info/RECORD +85 -0
  82. hammad/cache.py +0 -675
  83. hammad/database.py +0 -447
  84. hammad/logger.py +0 -273
  85. hammad/types/color.py +0 -951
  86. hammad/utils/markdown/__init__.py +0 -0
  87. hammad/utils/markdown/converters.py +0 -506
  88. hammad/utils/pydantic/__init__.py +0 -0
  89. hammad/utils/text/__init__.py +0 -0
  90. hammad/utils/text/converters.py +0 -229
  91. hammad/utils/typing/__init__.py +0 -0
  92. hammad_python-0.0.10.dist-info/RECORD +0 -22
  93. /hammad/{utils/__init__.py → py.typed} +0 -0
  94. /hammad/{utils/json → web/openapi}/__init__.py +0 -0
  95. {hammad_python-0.0.10.dist-info → hammad_python-0.0.12.dist-info}/WHEEL +0 -0
  96. {hammad_python-0.0.10.dist-info → hammad_python-0.0.12.dist-info}/licenses/LICENSE +0 -0
hammad/types/__init__.py CHANGED
@@ -0,0 +1,11 @@
1
+ """hammad.types
2
+
3
+ Contains functional aliases, types and model-like objects that are used
4
+ internally within the `hammad` package, as well as usable for
5
+ various other cases."""
6
+
7
+ from typing import TYPE_CHECKING
8
+ from .._core._utils._import_utils import _auto_create_getattr_loader
9
+
10
+ if TYPE_CHECKING:
11
+ from .file import File, FileSource
hammad/types/file.py ADDED
@@ -0,0 +1,358 @@
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 ..base.model import Model
10
+ from ..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,18 +1,18 @@
1
- """hammad.utils.typing.utils
1
+ """hammad.typing
2
2
 
3
- Contains various utility functions in regards to typing or working
4
- with type hints. These utils are also exposed in:
3
+ 'Namespace' package extension for various **CORE** typing resources and
4
+ types. This is not a collection of built types, rather resources from the
5
+ core `typing` module, `typing_extensions`, `typing_inspect` and other
6
+ resources."""
5
7
 
6
- ```python
7
- import hammad
8
+ from typing import Any, TYPE_CHECKING
9
+ import typing_inspect as inspection
8
10
 
9
- hammad.typing.get_type_description(Optional[int])
10
- ...
11
- ```
12
- """
11
+ try:
12
+ from typing_extensions import *
13
+ except ImportError:
14
+ from typing import *
13
15
 
14
- from typing import Any
15
- import typing_inspect as inspection
16
16
  from typing_inspect import (
17
17
  is_callable_type,
18
18
  is_classvar,
@@ -40,6 +40,129 @@ from typing_inspection.introspection import (
40
40
  from dataclasses import is_dataclass
41
41
 
42
42
  __all__ = (
43
+ # Super-special typing primitives.
44
+ "Any",
45
+ "ClassVar",
46
+ "Concatenate",
47
+ "Final",
48
+ "LiteralString",
49
+ "ParamSpec",
50
+ "ParamSpecArgs",
51
+ "ParamSpecKwargs",
52
+ "Self",
53
+ "Type",
54
+ "TypeVar",
55
+ "TypeVarTuple",
56
+ "Unpack",
57
+ # ABCs (from collections.abc).
58
+ "Awaitable",
59
+ "AsyncIterator",
60
+ "AsyncIterable",
61
+ "Coroutine",
62
+ "AsyncGenerator",
63
+ "AsyncContextManager",
64
+ "Buffer",
65
+ "ChainMap",
66
+ # Concrete collection types.
67
+ "ContextManager",
68
+ "Counter",
69
+ "Deque",
70
+ "DefaultDict",
71
+ "NamedTuple",
72
+ "OrderedDict",
73
+ "TypedDict",
74
+ # Structural checks, a.k.a. protocols.
75
+ "SupportsAbs",
76
+ "SupportsBytes",
77
+ "SupportsComplex",
78
+ "SupportsFloat",
79
+ "SupportsIndex",
80
+ "SupportsInt",
81
+ "SupportsRound",
82
+ "Reader",
83
+ "Writer",
84
+ # One-off things.
85
+ "Annotated",
86
+ "assert_never",
87
+ "assert_type",
88
+ "clear_overloads",
89
+ "dataclass_transform",
90
+ "deprecated",
91
+ "Doc",
92
+ "evaluate_forward_ref",
93
+ "get_overloads",
94
+ "final",
95
+ "Format",
96
+ "get_annotations",
97
+ "get_args",
98
+ "get_origin",
99
+ "get_original_bases",
100
+ "get_protocol_members",
101
+ "get_type_hints",
102
+ "IntVar",
103
+ "is_protocol",
104
+ "is_typeddict",
105
+ "Literal",
106
+ "NewType",
107
+ "overload",
108
+ "override",
109
+ "Protocol",
110
+ "Sentinel",
111
+ "reveal_type",
112
+ "runtime",
113
+ "runtime_checkable",
114
+ "Text",
115
+ "TypeAlias",
116
+ "TypeAliasType",
117
+ "TypeForm",
118
+ "TypeGuard",
119
+ "TypeIs",
120
+ "TYPE_CHECKING",
121
+ "Never",
122
+ "NoReturn",
123
+ "ReadOnly",
124
+ "Required",
125
+ "NotRequired",
126
+ "NoDefault",
127
+ "NoExtraItems",
128
+ # Pure aliases, have always been in typing
129
+ "AbstractSet",
130
+ "AnyStr",
131
+ "BinaryIO",
132
+ "Callable",
133
+ "Collection",
134
+ "Container",
135
+ "Dict",
136
+ "ForwardRef",
137
+ "FrozenSet",
138
+ "Generator",
139
+ "Generic",
140
+ "Hashable",
141
+ "IO",
142
+ "ItemsView",
143
+ "Iterable",
144
+ "Iterator",
145
+ "KeysView",
146
+ "List",
147
+ "Mapping",
148
+ "MappingView",
149
+ "Match",
150
+ "MutableMapping",
151
+ "MutableSequence",
152
+ "MutableSet",
153
+ "Optional",
154
+ "Pattern",
155
+ "Reversible",
156
+ "Sequence",
157
+ "Set",
158
+ "Sized",
159
+ "TextIO",
160
+ "Tuple",
161
+ "Union",
162
+ "ValuesView",
163
+ "cast",
164
+ "no_type_check",
165
+ "no_type_check_decorator",
43
166
  "TypingError",
44
167
  "get_type_description",
45
168
  "inspection",
@@ -63,6 +186,10 @@ __all__ = (
63
186
  "is_union_origin",
64
187
  "inspect_annotation",
65
188
  "get_literal_values",
189
+ "get_last_args",
190
+ "get_last_origin",
191
+ "get_generic_bases",
192
+ "get_typed_dict_keys",
66
193
  )
67
194
 
68
195
 
@@ -75,7 +202,7 @@ class TypingError(Exception):
75
202
  # ------------------------------------------------------------------------
76
203
 
77
204
 
78
- def is_pydantic_basemodel(t: Any) -> bool:
205
+ def is_pydantic_basemodel(t: "Any") -> bool:
79
206
  """Check if an object is a Pydantic BaseModel class or instance using duck typing.
80
207
 
81
208
  This function uses duck typing to identify Pydantic BaseModel objects by checking
@@ -116,7 +243,7 @@ def is_pydantic_basemodel(t: Any) -> bool:
116
243
  )
117
244
 
118
245
 
119
- def is_pydantic_basemodel_instance(t: Any) -> bool:
246
+ def is_pydantic_basemodel_instance(t: "Any") -> bool:
120
247
  """Check if an object is an instance (not class) of a Pydantic BaseModel using duck typing.
121
248
 
122
249
  This function specifically identifies Pydantic BaseModel instances by ensuring
@@ -147,7 +274,7 @@ def is_pydantic_basemodel_instance(t: Any) -> bool:
147
274
  )
148
275
 
149
276
 
150
- def is_msgspec_struct(t: Any) -> bool:
277
+ def is_msgspec_struct(t: "Any") -> bool:
151
278
  """Check if an object is a msgspec Struct class or instance using duck typing.
152
279
 
153
280
  This function uses duck typing to identify msgspec Struct objects by checking
@@ -175,7 +302,7 @@ def is_msgspec_struct(t: Any) -> bool:
175
302
  return hasattr(t, "__struct_fields__") and hasattr(t, "__struct_config__")
176
303
 
177
304
 
178
- def get_type_description(t: Any) -> str:
305
+ def get_type_description(t: "Any") -> str:
179
306
  """Creates a human-readable description of a type hint.
180
307
 
181
308
  Args:
hammad/web/__init__.py ADDED
@@ -0,0 +1,43 @@
1
+ """hammad.web"""
2
+
3
+ from typing import TYPE_CHECKING
4
+ from .._core._utils._import_utils import _auto_create_getattr_loader
5
+
6
+ if TYPE_CHECKING:
7
+ from .utils import (
8
+ run_web_request,
9
+ read_web_page,
10
+ read_web_pages,
11
+ search_web,
12
+ search_news,
13
+ extract_page_links,
14
+ )
15
+ from .http.client import AsyncHttpClient, HttpClient, create_http_client
16
+ from .openapi.client import AsyncOpenAPIClient, OpenAPIClient, create_openapi_client
17
+ from .search.client import AsyncSearchClient, SearchClient, create_search_client
18
+
19
+ __all__ = (
20
+ "run_web_request",
21
+ "read_web_page",
22
+ "read_web_pages",
23
+ "search_web",
24
+ "search_news",
25
+ "extract_page_links",
26
+ "AsyncHttpClient",
27
+ "HttpClient",
28
+ "create_http_client",
29
+ "AsyncOpenAPIClient",
30
+ "OpenAPIClient",
31
+ "create_openapi_client",
32
+ "AsyncSearchClient",
33
+ "SearchClient",
34
+ "create_search_client",
35
+ )
36
+
37
+
38
+ __getattr__ = _auto_create_getattr_loader(__all__)
39
+
40
+
41
+ def __dir__() -> list[str]:
42
+ """Get the attributes of the web module."""
43
+ return list(__all__)
@@ -0,0 +1 @@
1
+ """hammad.web.http"""