hammad-python 0.0.24__py3-none-any.whl → 0.0.26__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/data/types/file.py CHANGED
@@ -9,7 +9,13 @@ from urllib.parse import urlparse
9
9
  from ..models.model import Model
10
10
  from ..models.fields import field
11
11
 
12
- __all__ = ("File", "FileSource")
12
+ __all__ = (
13
+ "File",
14
+ "FileSource",
15
+ "read_file_from_path",
16
+ "read_file_from_url",
17
+ "read_file_from_bytes",
18
+ )
13
19
 
14
20
 
15
21
  _FILE_SIGNATURES = {
@@ -356,3 +362,70 @@ class File(Model, kw_only=True, dict=True):
356
362
  path=Path(name) if name else None,
357
363
  ),
358
364
  )
365
+
366
+
367
+ def read_file_from_path(
368
+ path: str | Path,
369
+ *,
370
+ encoding: str | None = None,
371
+ ) -> File:
372
+ """Read a file from a filesystem path.
373
+
374
+ Args:
375
+ path: The path to the file to read.
376
+ encoding: Optional text encoding to use when reading the file.
377
+ If not provided, will attempt to detect automatically.
378
+
379
+ Returns:
380
+ A File instance containing the file data.
381
+
382
+ Raises:
383
+ FileNotFoundError: If the file does not exist.
384
+ PermissionError: If the file cannot be read due to permissions.
385
+ IsADirectoryError: If the path points to a directory.
386
+ """
387
+ return File.from_path(path, encoding=encoding)
388
+
389
+
390
+ def read_file_from_url(
391
+ url: str,
392
+ *,
393
+ encoding: str | None = None,
394
+ timeout: float = 30.0,
395
+ ) -> File:
396
+ """Read a file from a URL.
397
+
398
+ Args:
399
+ url: The URL to fetch the file from.
400
+ encoding: Optional text encoding to use when reading the response.
401
+ If not provided, will attempt to detect automatically.
402
+ timeout: Request timeout in seconds. Defaults to 30.0.
403
+
404
+ Returns:
405
+ A File instance containing the downloaded data.
406
+
407
+ Raises:
408
+ httpx.RequestError: If the request fails.
409
+ httpx.HTTPStatusError: If the response has an error status code.
410
+ """
411
+ return File.from_url(url, encoding=encoding, timeout=timeout)
412
+
413
+
414
+ def read_file_from_bytes(
415
+ data: bytes,
416
+ *,
417
+ type: str | None = None,
418
+ name: str | None = None,
419
+ ) -> File:
420
+ """Create a file from raw bytes data.
421
+
422
+ Args:
423
+ data: The bytes data to create the file from.
424
+ type: Optional MIME type of the data. If not provided,
425
+ will attempt to detect from content signatures.
426
+ name: Optional name for the file data.
427
+
428
+ Returns:
429
+ A File instance containing the bytes data.
430
+ """
431
+ return File.from_bytes(data, type=type, name=name)
@@ -7,13 +7,25 @@ from typing import TYPE_CHECKING
7
7
  from ...._internal import create_getattr_importer
8
8
 
9
9
  if TYPE_CHECKING:
10
- from .image import Image
11
- from .audio import Audio
10
+ from .image import (
11
+ Image,
12
+ read_image_from_path,
13
+ read_image_from_url,
14
+ )
15
+ from .audio import (
16
+ Audio,
17
+ read_audio_from_path,
18
+ read_audio_from_url,
19
+ )
12
20
 
13
21
 
14
22
  __all__ = (
15
23
  "Image",
16
24
  "Audio",
25
+ "read_audio_from_path",
26
+ "read_audio_from_url",
27
+ "read_image_from_path",
28
+ "read_image_from_url",
17
29
  )
18
30
 
19
31
 
@@ -1,12 +1,19 @@
1
1
  """hammad.data.types.multimodal.audio"""
2
2
 
3
3
  import httpx
4
+ import mimetypes
5
+ from pathlib import Path
4
6
  from typing import Self
5
7
 
6
- from ...types.file import File, FileSource
8
+ from ...types.file import File, FileSource, _FILE_SIGNATURES
7
9
  from ...models.fields import field
8
10
 
9
- __all__ = ("Audio",)
11
+
12
+ __all__ = (
13
+ "Audio",
14
+ "read_audio_from_path",
15
+ "read_audio_from_url",
16
+ )
10
17
 
11
18
 
12
19
  class Audio(File):
@@ -94,3 +101,100 @@ class Audio(File):
94
101
  size=size,
95
102
  ),
96
103
  )
104
+
105
+ @classmethod
106
+ def from_path(
107
+ cls,
108
+ path: str | Path,
109
+ ) -> Self:
110
+ """Create an audio file from a file path.
111
+
112
+ Args:
113
+ path: The path to the audio file.
114
+
115
+ Returns:
116
+ A new Audio instance.
117
+
118
+ Raises:
119
+ FileNotFoundError: If the file does not exist.
120
+ ValueError: If the file is not a valid audio file.
121
+ """
122
+ path_obj = Path(path)
123
+
124
+ if not path_obj.exists():
125
+ raise FileNotFoundError(f"Audio file not found: {path}")
126
+
127
+ if not path_obj.is_file():
128
+ raise ValueError(f"Path is not a file: {path}")
129
+
130
+ # Read file data
131
+ data = path_obj.read_bytes()
132
+
133
+ # Determine MIME type
134
+ type = None
135
+
136
+ # Check file signature first
137
+ for signature, mime_type in _FILE_SIGNATURES.items():
138
+ if data.startswith(signature) and mime_type.startswith("audio/"):
139
+ type = mime_type
140
+ break
141
+
142
+ # Fall back to mimetypes module
143
+ if not type:
144
+ type, _ = mimetypes.guess_type(str(path))
145
+
146
+ # Validate it's an audio file
147
+ if type and not type.startswith("audio/"):
148
+ raise ValueError(f"File is not an audio file: {type}")
149
+
150
+ return cls(
151
+ data=data,
152
+ type=type,
153
+ source=FileSource(
154
+ is_file=True,
155
+ path=path_obj,
156
+ size=len(data),
157
+ ),
158
+ )
159
+
160
+
161
+ def read_audio_from_url(
162
+ url: str,
163
+ *,
164
+ lazy: bool = True,
165
+ timeout: float = 30.0,
166
+ ) -> Audio:
167
+ """Download and create an audio file from a URL.
168
+
169
+ Args:
170
+ url: The URL to download from.
171
+ lazy: If True, defer loading content until needed.
172
+ timeout: Request timeout in seconds.
173
+
174
+ Returns:
175
+ A new Audio instance.
176
+
177
+ Raises:
178
+ httpx.RequestError: If the request fails.
179
+ httpx.HTTPStatusError: If the response has an error status code.
180
+ ValueError: If the URL does not point to an audio file.
181
+ """
182
+ return Audio.from_url(url, lazy=lazy, timeout=timeout)
183
+
184
+
185
+ def read_audio_from_path(
186
+ path: str | Path,
187
+ ) -> Audio:
188
+ """Create an audio file from a file path.
189
+
190
+ Args:
191
+ path: The path to the audio file.
192
+
193
+ Returns:
194
+ A new Audio instance.
195
+
196
+ Raises:
197
+ FileNotFoundError: If the file does not exist.
198
+ ValueError: If the file is not a valid audio file.
199
+ """
200
+ return Audio.from_path(path)
@@ -1,12 +1,19 @@
1
1
  """hammad.data.types.multimodal.image"""
2
2
 
3
3
  import httpx
4
+ import mimetypes
5
+ from pathlib import Path
4
6
  from typing import Self
5
7
 
6
- from ...types.file import File, FileSource
8
+ from ...types.file import File, FileSource, _FILE_SIGNATURES
7
9
  from ...models.fields import field
8
10
 
9
- __all__ = ("Image",)
11
+
12
+ __all__ = (
13
+ "Image",
14
+ "read_image_from_path",
15
+ "read_image_from_url",
16
+ )
10
17
 
11
18
 
12
19
  class Image(File):
@@ -78,3 +85,98 @@ class Image(File):
78
85
  size=size,
79
86
  ),
80
87
  )
88
+
89
+ @classmethod
90
+ def from_path(
91
+ cls,
92
+ path: str | Path,
93
+ ) -> Self:
94
+ """Create an image from a file path.
95
+
96
+ Args:
97
+ path: The path to the image file.
98
+
99
+ Returns:
100
+ A new Image instance.
101
+
102
+ Raises:
103
+ FileNotFoundError: If the file does not exist.
104
+ ValueError: If the file is not a valid image.
105
+ """
106
+ path_obj = Path(path)
107
+
108
+ if not path_obj.exists():
109
+ raise FileNotFoundError(f"Image file not found: {path}")
110
+
111
+ if not path_obj.is_file():
112
+ raise ValueError(f"Path is not a file: {path}")
113
+
114
+ # Read the file data
115
+ data = path_obj.read_bytes()
116
+
117
+ # Detect MIME type
118
+ type = None
119
+ for sig, mime in _FILE_SIGNATURES.items():
120
+ if data.startswith(sig):
121
+ type = mime
122
+ break
123
+
124
+ # Fallback to mimetypes module
125
+ if not type:
126
+ type, _ = mimetypes.guess_type(str(path))
127
+
128
+ # Validate it's an image
129
+ if type and not type.startswith("image/"):
130
+ raise ValueError(f"File is not an image: {type}")
131
+
132
+ return cls(
133
+ data=data,
134
+ type=type,
135
+ source=FileSource(
136
+ is_file=True,
137
+ path=path_obj,
138
+ size=len(data),
139
+ ),
140
+ )
141
+
142
+
143
+ def read_image_from_url(
144
+ url: str,
145
+ *,
146
+ lazy: bool = True,
147
+ timeout: float = 30.0,
148
+ ) -> Image:
149
+ """Download and create an image from a URL.
150
+
151
+ Args:
152
+ url: The URL to download from.
153
+ lazy: If True, defer loading content until needed.
154
+ timeout: Request timeout in seconds.
155
+
156
+ Returns:
157
+ A new Image instance.
158
+
159
+ Raises:
160
+ httpx.RequestError: If the request fails.
161
+ httpx.HTTPStatusError: If the response has an error status code.
162
+ ValueError: If the URL does not point to an image.
163
+ """
164
+ return Image.from_url(url, lazy=lazy, timeout=timeout)
165
+
166
+
167
+ def read_image_from_path(
168
+ path: str | Path,
169
+ ) -> Image:
170
+ """Create an image from a file path.
171
+
172
+ Args:
173
+ path: The path to the image file.
174
+
175
+ Returns:
176
+ A new Image instance.
177
+
178
+ Raises:
179
+ FileNotFoundError: If the file does not exist.
180
+ ValueError: If the file is not a valid image.
181
+ """
182
+ return Image.from_path(path)
hammad/data/types/text.py CHANGED
@@ -1054,6 +1054,242 @@ class Text(BaseText, Generic[T]):
1054
1054
  return self.render(OutputFormat.ANY)
1055
1055
 
1056
1056
 
1057
+ def convert_to_simple_text(
1058
+ obj: Any,
1059
+ *,
1060
+ title: Optional[str] = None,
1061
+ description: Optional[str] = None,
1062
+ type: str = "simple",
1063
+ heading_level: int = 2,
1064
+ show_in_toc: bool = True,
1065
+ collapsible: bool = False,
1066
+ metadata: Optional[Dict[str, Any]] = None,
1067
+ sections: Optional[List["BaseText"]] = None,
1068
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1069
+ **kwargs,
1070
+ ) -> "SimpleText":
1071
+ """Convert any object to a SimpleText instance (fully parameterized)."""
1072
+ content = convert_to_text(obj)
1073
+ return SimpleText(
1074
+ content=content,
1075
+ title=title,
1076
+ description=description,
1077
+ type=type,
1078
+ heading_level=heading_level,
1079
+ show_in_toc=show_in_toc,
1080
+ collapsible=collapsible,
1081
+ metadata=metadata or {},
1082
+ sections=sections or [],
1083
+ format_config=format_config or {},
1084
+ **kwargs,
1085
+ )
1086
+
1087
+
1088
+ def convert_to_output_text(
1089
+ obj: Any,
1090
+ *,
1091
+ title: Optional[str] = None,
1092
+ description: Optional[str] = None,
1093
+ type: str = "output",
1094
+ heading_level: int = 2,
1095
+ show_in_toc: bool = True,
1096
+ collapsible: bool = False,
1097
+ metadata: Optional[Dict[str, Any]] = None,
1098
+ sections: Optional[List["BaseText"]] = None,
1099
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1100
+ output_schema: Optional[Any] = None,
1101
+ examples: Optional[List[Any]] = None,
1102
+ validation_rules: Optional[List[str]] = None,
1103
+ error_cases: Optional[List[Dict[str, Any]]] = None,
1104
+ **kwargs,
1105
+ ) -> "OutputText":
1106
+ """Convert any object to an OutputText instance (fully parameterized)."""
1107
+ content = convert_to_text(obj)
1108
+ return OutputText(
1109
+ content=content,
1110
+ title=title,
1111
+ description=description,
1112
+ type=type,
1113
+ heading_level=heading_level,
1114
+ show_in_toc=show_in_toc,
1115
+ collapsible=collapsible,
1116
+ metadata=metadata or {},
1117
+ sections=sections or [],
1118
+ format_config=format_config or {},
1119
+ output_schema=output_schema,
1120
+ examples=examples or [],
1121
+ validation_rules=validation_rules or [],
1122
+ error_cases=error_cases or [],
1123
+ **kwargs,
1124
+ )
1125
+
1126
+
1127
+ def convert_to_output_instructions(
1128
+ obj: Any,
1129
+ *,
1130
+ title: Optional[str] = None,
1131
+ description: Optional[str] = None,
1132
+ type: str = "output",
1133
+ heading_level: int = 2,
1134
+ show_in_toc: bool = True,
1135
+ collapsible: bool = False,
1136
+ metadata: Optional[Dict[str, Any]] = None,
1137
+ sections: Optional[List["BaseText"]] = None,
1138
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1139
+ output_schema: Optional[Any] = None,
1140
+ examples: Optional[List[Any]] = None,
1141
+ validation_rules: Optional[List[str]] = None,
1142
+ error_cases: Optional[List[Dict[str, Any]]] = None,
1143
+ as_message: bool = False,
1144
+ role: str = "user",
1145
+ as_message_content: bool = False,
1146
+ **kwargs,
1147
+ ) -> Union[str, Dict[str, Any]]:
1148
+ """
1149
+ Convert any object to output instructions, returning either:
1150
+ - a string (default)
1151
+ - a chat message dict (if as_message=True)
1152
+ - a chat message content param dict (if as_message_content=True)
1153
+
1154
+ Only one of as_message or as_message_content can be True.
1155
+ """
1156
+ if as_message and as_message_content:
1157
+ raise ValueError("Only one of as_message or as_message_content can be True.")
1158
+
1159
+ content = convert_to_text(obj)
1160
+ output_text = OutputText(
1161
+ content=content,
1162
+ title=title,
1163
+ description=description,
1164
+ type=type,
1165
+ heading_level=heading_level,
1166
+ show_in_toc=show_in_toc,
1167
+ collapsible=collapsible,
1168
+ metadata=metadata or {},
1169
+ sections=sections or [],
1170
+ format_config=format_config or {},
1171
+ output_schema=output_schema,
1172
+ examples=examples or [],
1173
+ validation_rules=validation_rules or [],
1174
+ error_cases=error_cases or [],
1175
+ **kwargs,
1176
+ )
1177
+ text_str = str(output_text)
1178
+
1179
+ if as_message:
1180
+ # Return a chat message dict
1181
+ return {"role": role, "content": text_str}
1182
+ elif as_message_content:
1183
+ # Return a chat message content param dict
1184
+ return {"type": "text", "text": text_str}
1185
+ else:
1186
+ # Return as plain string
1187
+ return text_str
1188
+
1189
+
1190
+ def convert_to_code_section(
1191
+ obj: Any,
1192
+ *,
1193
+ language: str = "python",
1194
+ title: Optional[str] = None,
1195
+ description: Optional[str] = None,
1196
+ type: str = "code",
1197
+ heading_level: int = 2,
1198
+ show_in_toc: bool = True,
1199
+ collapsible: bool = False,
1200
+ metadata: Optional[Dict[str, Any]] = None,
1201
+ sections: Optional[List["BaseText"]] = None,
1202
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1203
+ line_numbers: bool = False,
1204
+ **kwargs,
1205
+ ) -> "CodeSection":
1206
+ """Convert any object to a CodeSection instance (fully parameterized)."""
1207
+ content = convert_to_text(obj)
1208
+ return CodeSection(
1209
+ content=content,
1210
+ language=language,
1211
+ title=title,
1212
+ description=description,
1213
+ type=type,
1214
+ heading_level=heading_level,
1215
+ show_in_toc=show_in_toc,
1216
+ collapsible=collapsible,
1217
+ metadata=metadata or {},
1218
+ sections=sections or [],
1219
+ format_config=format_config or {},
1220
+ line_numbers=line_numbers,
1221
+ **kwargs,
1222
+ )
1223
+
1224
+
1225
+ def convert_to_schema_section(
1226
+ obj: Any,
1227
+ *,
1228
+ title: Optional[str] = None,
1229
+ description: Optional[str] = None,
1230
+ type: str = "schema",
1231
+ heading_level: int = 2,
1232
+ show_in_toc: bool = True,
1233
+ collapsible: bool = False,
1234
+ metadata: Optional[Dict[str, Any]] = None,
1235
+ sections: Optional[List["BaseText"]] = None,
1236
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1237
+ schema_object: Optional[Any] = None,
1238
+ show_examples: bool = True,
1239
+ table_format: bool = True,
1240
+ **kwargs,
1241
+ ) -> "SchemaSection":
1242
+ """Convert any object to a SchemaSection instance (fully parameterized)."""
1243
+ content = convert_to_text(obj)
1244
+ return SchemaSection(
1245
+ content=content,
1246
+ title=title,
1247
+ description=description,
1248
+ type=type,
1249
+ heading_level=heading_level,
1250
+ show_in_toc=show_in_toc,
1251
+ collapsible=collapsible,
1252
+ metadata=metadata or {},
1253
+ sections=sections or [],
1254
+ format_config=format_config or {},
1255
+ schema_object=schema_object or obj,
1256
+ show_examples=show_examples,
1257
+ table_format=table_format,
1258
+ **kwargs,
1259
+ )
1260
+
1261
+
1262
+ def convert_to_base_text(
1263
+ obj: Any,
1264
+ *,
1265
+ title: Optional[str] = None,
1266
+ description: Optional[str] = None,
1267
+ type: str = "base",
1268
+ heading_level: int = 2,
1269
+ show_in_toc: bool = True,
1270
+ collapsible: bool = False,
1271
+ metadata: Optional[Dict[str, Any]] = None,
1272
+ sections: Optional[List["BaseText"]] = None,
1273
+ format_config: Optional[Dict["OutputFormat", Dict[str, Any]]] = None,
1274
+ **kwargs,
1275
+ ) -> "BaseText":
1276
+ """Convert any object to a BaseText instance (fully parameterized)."""
1277
+ content = convert_to_text(obj)
1278
+ return BaseText(
1279
+ content=content,
1280
+ title=title,
1281
+ description=description,
1282
+ type=type,
1283
+ heading_level=heading_level,
1284
+ show_in_toc=show_in_toc,
1285
+ collapsible=collapsible,
1286
+ metadata=metadata or {},
1287
+ sections=sections or [],
1288
+ format_config=format_config or {},
1289
+ **kwargs,
1290
+ )
1291
+
1292
+
1057
1293
  __all__ = (
1058
1294
  "OutputFormat",
1059
1295
  "HeadingStyle",
@@ -1063,4 +1299,10 @@ __all__ = (
1063
1299
  "SimpleText",
1064
1300
  "OutputText",
1065
1301
  "Text",
1302
+ "convert_to_simple_text",
1303
+ "convert_to_output_text",
1304
+ "convert_to_output_instructions",
1305
+ "convert_to_code_section",
1306
+ "convert_to_schema_section",
1307
+ "convert_to_base_text",
1066
1308
  )
hammad/genai/__init__.py CHANGED
@@ -4,10 +4,17 @@ from typing import TYPE_CHECKING
4
4
  from .._internal import create_getattr_importer
5
5
 
6
6
 
7
+ if TYPE_CHECKING:
8
+ from .a2a import (
9
+ as_a2a_app,
10
+ GraphWorker,
11
+ AgentWorker,
12
+ )
7
13
  if TYPE_CHECKING:
8
14
  from .agents import (
9
15
  Agent,
10
16
  AgentEvent,
17
+ AgentSettings,
11
18
  AgentResponse,
12
19
  AgentStream,
13
20
  AgentContext,
@@ -20,6 +27,7 @@ if TYPE_CHECKING:
20
27
  run_agent_iter,
21
28
  async_run_agent,
22
29
  async_run_agent_iter,
30
+ agent_decorator,
23
31
  )
24
32
  from .graphs import (
25
33
  GraphBuilder,
@@ -46,6 +54,9 @@ if TYPE_CHECKING:
46
54
  ActionSettings,
47
55
  action,
48
56
  plugin,
57
+ select,
58
+ SelectionStrategy,
59
+ ActionDecorator,
49
60
  )
50
61
  from .models.embeddings import (
51
62
  Embedding,
@@ -68,6 +79,7 @@ if TYPE_CHECKING:
68
79
  LanguageModelStream,
69
80
  run_language_model,
70
81
  async_run_language_model,
82
+ language_model_decorator,
71
83
  create_language_model,
72
84
  )
73
85
  from .models.reranking import run_reranking_model, async_run_reranking_model
@@ -99,9 +111,14 @@ if TYPE_CHECKING:
99
111
 
100
112
 
101
113
  __all__ = [
114
+ # hammad.genai.a2a
115
+ "as_a2a_app",
116
+ "GraphWorker",
117
+ "AgentWorker",
102
118
  # hammad.genai.agents.agent
103
119
  "Agent",
104
120
  "AgentEvent",
121
+ "AgentSettings",
105
122
  "AgentResponse",
106
123
  "AgentStream",
107
124
  "AgentContext",
@@ -113,6 +130,7 @@ __all__ = [
113
130
  "run_agent_iter",
114
131
  "async_run_agent",
115
132
  "async_run_agent_iter",
133
+ "agent_decorator",
116
134
  # hammad.genai.graphs
117
135
  "GraphBuilder",
118
136
  "GraphContext",
@@ -138,6 +156,9 @@ __all__ = [
138
156
  "ActionSettings",
139
157
  "action",
140
158
  "plugin",
159
+ "select",
160
+ "SelectionStrategy",
161
+ "ActionDecorator",
141
162
  # hammad.genai.models.embeddings
142
163
  "Embedding",
143
164
  "EmbeddingModel",
@@ -159,6 +180,7 @@ __all__ = [
159
180
  "run_language_model",
160
181
  "async_run_language_model",
161
182
  "create_language_model",
183
+ "language_model_decorator",
162
184
  # hammad.genai.models.reranking
163
185
  "run_reranking_model",
164
186
  "async_run_reranking_model",