hammad-python 0.0.29__py3-none-any.whl → 0.0.31__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 (137) hide show
  1. ham/__init__.py +10 -0
  2. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/METADATA +6 -32
  3. hammad_python-0.0.31.dist-info/RECORD +6 -0
  4. hammad/__init__.py +0 -84
  5. hammad/_internal.py +0 -256
  6. hammad/_main.py +0 -226
  7. hammad/cache/__init__.py +0 -40
  8. hammad/cache/base_cache.py +0 -181
  9. hammad/cache/cache.py +0 -169
  10. hammad/cache/decorators.py +0 -261
  11. hammad/cache/file_cache.py +0 -80
  12. hammad/cache/ttl_cache.py +0 -74
  13. hammad/cli/__init__.py +0 -33
  14. hammad/cli/animations.py +0 -573
  15. hammad/cli/plugins.py +0 -867
  16. hammad/cli/styles/__init__.py +0 -55
  17. hammad/cli/styles/settings.py +0 -139
  18. hammad/cli/styles/types.py +0 -358
  19. hammad/cli/styles/utils.py +0 -634
  20. hammad/data/__init__.py +0 -90
  21. hammad/data/collections/__init__.py +0 -49
  22. hammad/data/collections/collection.py +0 -326
  23. hammad/data/collections/indexes/__init__.py +0 -37
  24. hammad/data/collections/indexes/qdrant/__init__.py +0 -1
  25. hammad/data/collections/indexes/qdrant/index.py +0 -723
  26. hammad/data/collections/indexes/qdrant/settings.py +0 -94
  27. hammad/data/collections/indexes/qdrant/utils.py +0 -210
  28. hammad/data/collections/indexes/tantivy/__init__.py +0 -1
  29. hammad/data/collections/indexes/tantivy/index.py +0 -426
  30. hammad/data/collections/indexes/tantivy/settings.py +0 -40
  31. hammad/data/collections/indexes/tantivy/utils.py +0 -176
  32. hammad/data/configurations/__init__.py +0 -35
  33. hammad/data/configurations/configuration.py +0 -564
  34. hammad/data/models/__init__.py +0 -50
  35. hammad/data/models/extensions/__init__.py +0 -4
  36. hammad/data/models/extensions/pydantic/__init__.py +0 -42
  37. hammad/data/models/extensions/pydantic/converters.py +0 -759
  38. hammad/data/models/fields.py +0 -546
  39. hammad/data/models/model.py +0 -1078
  40. hammad/data/models/utils.py +0 -280
  41. hammad/data/sql/__init__.py +0 -24
  42. hammad/data/sql/database.py +0 -576
  43. hammad/data/sql/types.py +0 -127
  44. hammad/data/types/__init__.py +0 -75
  45. hammad/data/types/file.py +0 -431
  46. hammad/data/types/multimodal/__init__.py +0 -36
  47. hammad/data/types/multimodal/audio.py +0 -200
  48. hammad/data/types/multimodal/image.py +0 -182
  49. hammad/data/types/text.py +0 -1308
  50. hammad/formatting/__init__.py +0 -33
  51. hammad/formatting/json/__init__.py +0 -27
  52. hammad/formatting/json/converters.py +0 -158
  53. hammad/formatting/text/__init__.py +0 -63
  54. hammad/formatting/text/converters.py +0 -723
  55. hammad/formatting/text/markdown.py +0 -131
  56. hammad/formatting/yaml/__init__.py +0 -26
  57. hammad/formatting/yaml/converters.py +0 -5
  58. hammad/genai/__init__.py +0 -217
  59. hammad/genai/a2a/__init__.py +0 -32
  60. hammad/genai/a2a/workers.py +0 -552
  61. hammad/genai/agents/__init__.py +0 -59
  62. hammad/genai/agents/agent.py +0 -1973
  63. hammad/genai/agents/run.py +0 -1024
  64. hammad/genai/agents/types/__init__.py +0 -42
  65. hammad/genai/agents/types/agent_context.py +0 -13
  66. hammad/genai/agents/types/agent_event.py +0 -128
  67. hammad/genai/agents/types/agent_hooks.py +0 -220
  68. hammad/genai/agents/types/agent_messages.py +0 -31
  69. hammad/genai/agents/types/agent_response.py +0 -125
  70. hammad/genai/agents/types/agent_stream.py +0 -327
  71. hammad/genai/graphs/__init__.py +0 -125
  72. hammad/genai/graphs/_utils.py +0 -190
  73. hammad/genai/graphs/base.py +0 -1828
  74. hammad/genai/graphs/plugins.py +0 -316
  75. hammad/genai/graphs/types.py +0 -638
  76. hammad/genai/models/__init__.py +0 -1
  77. hammad/genai/models/embeddings/__init__.py +0 -43
  78. hammad/genai/models/embeddings/model.py +0 -226
  79. hammad/genai/models/embeddings/run.py +0 -163
  80. hammad/genai/models/embeddings/types/__init__.py +0 -37
  81. hammad/genai/models/embeddings/types/embedding_model_name.py +0 -75
  82. hammad/genai/models/embeddings/types/embedding_model_response.py +0 -76
  83. hammad/genai/models/embeddings/types/embedding_model_run_params.py +0 -66
  84. hammad/genai/models/embeddings/types/embedding_model_settings.py +0 -47
  85. hammad/genai/models/language/__init__.py +0 -57
  86. hammad/genai/models/language/model.py +0 -1098
  87. hammad/genai/models/language/run.py +0 -878
  88. hammad/genai/models/language/types/__init__.py +0 -40
  89. hammad/genai/models/language/types/language_model_instructor_mode.py +0 -47
  90. hammad/genai/models/language/types/language_model_messages.py +0 -28
  91. hammad/genai/models/language/types/language_model_name.py +0 -239
  92. hammad/genai/models/language/types/language_model_request.py +0 -127
  93. hammad/genai/models/language/types/language_model_response.py +0 -217
  94. hammad/genai/models/language/types/language_model_response_chunk.py +0 -56
  95. hammad/genai/models/language/types/language_model_settings.py +0 -89
  96. hammad/genai/models/language/types/language_model_stream.py +0 -600
  97. hammad/genai/models/language/utils/__init__.py +0 -28
  98. hammad/genai/models/language/utils/requests.py +0 -421
  99. hammad/genai/models/language/utils/structured_outputs.py +0 -135
  100. hammad/genai/models/model_provider.py +0 -4
  101. hammad/genai/models/multimodal.py +0 -47
  102. hammad/genai/models/reranking.py +0 -26
  103. hammad/genai/types/__init__.py +0 -1
  104. hammad/genai/types/base.py +0 -215
  105. hammad/genai/types/history.py +0 -290
  106. hammad/genai/types/tools.py +0 -507
  107. hammad/logging/__init__.py +0 -35
  108. hammad/logging/decorators.py +0 -834
  109. hammad/logging/logger.py +0 -1018
  110. hammad/mcp/__init__.py +0 -53
  111. hammad/mcp/client/__init__.py +0 -35
  112. hammad/mcp/client/client.py +0 -624
  113. hammad/mcp/client/client_service.py +0 -400
  114. hammad/mcp/client/settings.py +0 -178
  115. hammad/mcp/servers/__init__.py +0 -26
  116. hammad/mcp/servers/launcher.py +0 -1161
  117. hammad/runtime/__init__.py +0 -32
  118. hammad/runtime/decorators.py +0 -142
  119. hammad/runtime/run.py +0 -299
  120. hammad/service/__init__.py +0 -49
  121. hammad/service/create.py +0 -527
  122. hammad/service/decorators.py +0 -283
  123. hammad/types.py +0 -288
  124. hammad/typing/__init__.py +0 -435
  125. hammad/web/__init__.py +0 -43
  126. hammad/web/http/__init__.py +0 -1
  127. hammad/web/http/client.py +0 -944
  128. hammad/web/models.py +0 -275
  129. hammad/web/openapi/__init__.py +0 -1
  130. hammad/web/openapi/client.py +0 -740
  131. hammad/web/search/__init__.py +0 -1
  132. hammad/web/search/client.py +0 -1023
  133. hammad/web/utils.py +0 -472
  134. hammad_python-0.0.29.dist-info/RECORD +0 -135
  135. {hammad → ham}/py.typed +0 -0
  136. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
  137. {hammad_python-0.0.29.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
@@ -1,723 +0,0 @@
1
- """hammad.formatting.text.converters"""
2
-
3
- import json
4
- import logging
5
- import dataclasses
6
- from dataclasses import is_dataclass, fields as dataclass_fields
7
- from docstring_parser import parse
8
- from typing import (
9
- Any,
10
- Optional,
11
- Dict,
12
- List,
13
- Set,
14
- Callable,
15
- Union,
16
- )
17
-
18
- from ...typing import (
19
- inspection,
20
- is_pydantic_basemodel,
21
- is_msgspec_struct,
22
- is_dataclass,
23
- is_pydantic_basemodel_instance,
24
- )
25
- from .markdown import (
26
- markdown_bold,
27
- markdown_italic,
28
- markdown_code,
29
- markdown_code_block,
30
- markdown_heading,
31
- markdown_list_item,
32
- markdown_table_row,
33
- markdown_horizontal_rule,
34
- )
35
-
36
- __all__ = [
37
- "convert_type_to_text",
38
- "convert_docstring_to_text",
39
- "convert_dataclass_to_text",
40
- "convert_pydantic_to_text",
41
- "convert_function_to_text",
42
- "convert_collection_to_text",
43
- "convert_dict_to_text",
44
- "convert_to_text",
45
- ]
46
-
47
-
48
- class TextFormattingError(Exception):
49
- """Exception raised for errors in the markdown converters."""
50
-
51
- def __init__(self, message: str):
52
- self.message = message
53
- super().__init__(self.message)
54
-
55
-
56
- def _escape_markdown(text: str) -> str:
57
- """Escape special Markdown characters."""
58
- # Only escape the most problematic characters
59
- chars_to_escape = ["*", "_", "`", "[", "]", "(", ")", "#", "+", "-", "!"]
60
- for char in chars_to_escape:
61
- text = text.replace(char, f"\\{char}")
62
- return text
63
-
64
-
65
- def convert_type_to_text(cls: Any) -> str:
66
- """Converts a type into a clean & human readable text representation.
67
-
68
- This function uses `typing_inspect` exclusively to infer nested types
69
- within `Optional`, `Union` types, for the cleanest possible string
70
- representation of a type.
71
-
72
- Args:
73
- cls: The type to convert to a text representation.
74
-
75
- Returns:
76
- A clean, human-readable string representation of the type.
77
- """
78
- # Handle None type
79
- if cls is None or cls is type(None):
80
- return "None"
81
-
82
- # Get origin and args using typing_inspect for better type handling
83
- origin = inspection.get_origin(cls)
84
- args = inspection.get_args(cls)
85
-
86
- if origin is not None:
87
- # Handle Optional (Union[T, None])
88
- if inspection.is_optional_type(cls):
89
- # Recursively get the name of the inner type (the one not None)
90
- inner_type = args[0]
91
- inner_type_name = convert_type_to_text(inner_type)
92
- return f"Optional[{inner_type_name}]"
93
-
94
- # Handle other Union types
95
- if inspection.is_union_type(cls):
96
- # Recursively get names of all arguments in the Union
97
- args_str = ", ".join(convert_type_to_text(arg) for arg in args)
98
- return f"Union[{args_str}]"
99
-
100
- # Handle other generic types (List, Dict, Tuple, Set, etc.)
101
- # Use origin.__name__ for built-in generics like list, dict, tuple, set
102
- origin_name = getattr(origin, "__name__", str(origin).split(".")[-1])
103
- if origin_name.startswith("_"): # Handle internal typing names like _List
104
- origin_name = origin_name[1:]
105
-
106
- # Convert to lowercase for built-in types to match modern Python style
107
- if origin_name in ["List", "Dict", "Tuple", "Set"]:
108
- origin_name = origin_name.lower()
109
-
110
- if args: # If there are type arguments
111
- # Recursively get names of type arguments
112
- args_str = ", ".join(convert_type_to_text(arg) for arg in args)
113
- return f"{origin_name}[{args_str}]"
114
- else: # Generic without arguments (e.g., typing.List)
115
- return origin_name
116
-
117
- # Handle special cases with typing_inspect
118
- if inspection.is_typevar(cls):
119
- return str(cls)
120
- if inspection.is_forward_ref(cls):
121
- return str(cls)
122
- if inspection.is_literal_type(cls):
123
- return f"Literal[{', '.join(str(arg) for arg in args)}]"
124
- if inspection.is_final_type(cls):
125
- return f"Final[{convert_type_to_text(args[0])}]" if args else "Final"
126
- if inspection.is_new_type(cls):
127
- return str(cls)
128
-
129
- # Handle Pydantic BaseModel types
130
- if is_pydantic_basemodel(cls):
131
- if hasattr(cls, "__name__"):
132
- return cls.__name__
133
- return "BaseModel"
134
-
135
- # Handle msgspec Struct types
136
- if is_msgspec_struct(cls):
137
- if hasattr(cls, "__name__"):
138
- return cls.__name__
139
- return "Struct"
140
-
141
- # Handle dataclass types
142
- if is_dataclass(cls):
143
- if hasattr(cls, "__name__"):
144
- return cls.__name__
145
- return "dataclass"
146
-
147
- # Handle basic types with __name__ attribute
148
- if hasattr(cls, "__name__") and cls.__name__ != "<lambda>":
149
- return cls.__name__
150
-
151
- # Special handling for Optional type string representation
152
- if str(cls).startswith("typing.Optional"):
153
- # Extract the inner type from the string representation
154
- inner_type_str = str(cls).replace("typing.Optional[", "").rstrip("]")
155
- return f"Optional[{inner_type_str}]"
156
-
157
- # Fallback for any other types
158
- # Clean up 'typing.' prefix and handle other common representations
159
- return str(cls).replace("typing.", "").replace("__main__.", "")
160
-
161
-
162
- def convert_docstring_to_text(
163
- obj: Any,
164
- *,
165
- params_override: Optional[str] = None,
166
- returns_override: Optional[str] = None,
167
- raises_override: Optional[str] = None,
168
- examples_override: Optional[str] = None,
169
- params_prefix: Optional[str] = None,
170
- returns_prefix: Optional[str] = None,
171
- raises_prefix: Optional[str] = None,
172
- exclude_params: bool = False,
173
- exclude_returns: bool = False,
174
- exclude_raises: bool = False,
175
- exclude_examples: bool = False,
176
- ) -> str:
177
- """
178
- Convert an object's docstring to formatted text using docstring_parser.
179
-
180
- Args:
181
- obj: The object to extract docstring from
182
- params_override: Override text for parameters section
183
- returns_override: Override text for returns section
184
- raises_override: Override text for raises section
185
- examples_override: Override text for examples section
186
- params_prefix: Prefix for parameters section
187
- returns_prefix: Prefix for returns section
188
- raises_prefix: Prefix for raises section
189
- exclude_params: Whether to exclude parameters section
190
- exclude_returns: Whether to exclude returns section
191
- exclude_raises: Whether to exclude raises section
192
- exclude_examples: Whether to exclude examples section
193
-
194
- Returns:
195
- Formatted text representation of the docstring
196
- """
197
- # Get the raw docstring
198
- doc = getattr(obj, "__doc__", None)
199
- if not doc:
200
- return ""
201
-
202
- try:
203
- # Parse the docstring using docstring_parser
204
- parsed = parse(doc)
205
-
206
- parts = []
207
-
208
- # Add short description
209
- if parsed.short_description:
210
- parts.append(parsed.short_description)
211
-
212
- # Add long description
213
- if parsed.long_description:
214
- parts.append("") # Empty line separator
215
- parts.append(parsed.long_description)
216
-
217
- # Add parameters section
218
- if not exclude_params and (params_override or parsed.params):
219
- parts.append("") # Empty line separator
220
- if params_override:
221
- parts.append(params_override)
222
- else:
223
- prefix = params_prefix or "Parameters:"
224
- parts.append(prefix)
225
- for param in parsed.params:
226
- param_line = f" {param.arg_name}"
227
- if param.type_name:
228
- param_line += f" ({param.type_name})"
229
- if param.description:
230
- param_line += f": {param.description}"
231
- parts.append(param_line)
232
-
233
- # Add returns section
234
- if not exclude_returns and (returns_override or parsed.returns):
235
- parts.append("") # Empty line separator
236
- if returns_override:
237
- parts.append(returns_override)
238
- else:
239
- prefix = returns_prefix or "Returns:"
240
- parts.append(prefix)
241
- if parsed.returns:
242
- return_line = " "
243
- if parsed.returns.type_name:
244
- return_line += f"{parsed.returns.type_name}: "
245
- if parsed.returns.description:
246
- return_line += parsed.returns.description
247
- parts.append(return_line)
248
-
249
- # Add raises section
250
- if not exclude_raises and (raises_override or parsed.raises):
251
- parts.append("") # Empty line separator
252
- if raises_override:
253
- parts.append(raises_override)
254
- else:
255
- prefix = raises_prefix or "Raises:"
256
- parts.append(prefix)
257
- for exc in parsed.raises:
258
- exc_line = f" {exc.type_name or 'Exception'}"
259
- if exc.description:
260
- exc_line += f": {exc.description}"
261
- parts.append(exc_line)
262
-
263
- # Add examples section (if available in parsed docstring)
264
- if not exclude_examples and examples_override:
265
- parts.append("") # Empty line separator
266
- parts.append(examples_override)
267
-
268
- return "\n".join(parts)
269
-
270
- except Exception:
271
- # Fallback to raw docstring if parsing fails
272
- return doc.strip()
273
-
274
-
275
- def convert_dataclass_to_text(
276
- obj: Any,
277
- title: Optional[str],
278
- description: Optional[str],
279
- table_format: bool,
280
- show_types: bool,
281
- show_defaults: bool,
282
- show_values: bool,
283
- indent_level: int,
284
- ) -> str:
285
- """Convert a dataclass to Markdown format."""
286
- is_class = isinstance(obj, type)
287
- obj_name = title or (obj.__name__ if is_class else obj.__class__.__name__)
288
-
289
- parts = []
290
- parts.append(markdown_heading(obj_name, min(indent_level + 1, 6)))
291
-
292
- if description:
293
- parts.append(f"\n{description}\n")
294
-
295
- fields_data = []
296
- for field in dataclass_fields(obj if is_class else obj.__class__):
297
- field_info = {
298
- "name": field.name,
299
- "type": convert_type_to_text(field.type) if show_types else None,
300
- "default": field.default
301
- if show_defaults and field.default is not dataclasses.MISSING
302
- else None,
303
- "value": getattr(obj, field.name) if show_values and not is_class else None,
304
- }
305
- fields_data.append(field_info)
306
-
307
- if table_format and fields_data:
308
- # Create table headers
309
- headers = ["Field"]
310
- if show_types:
311
- headers.append("Type")
312
- if show_defaults:
313
- headers.append("Default")
314
- if show_values and not is_class:
315
- headers.append("Value")
316
-
317
- parts.append("\n" + markdown_table_row(headers, is_header=True))
318
-
319
- # Add rows
320
- for field_info in fields_data:
321
- row = [markdown_code(field_info["name"])]
322
- if show_types:
323
- row.append(field_info["type"] or "")
324
- if show_defaults:
325
- row.append(
326
- str(field_info["default"])
327
- if field_info["default"] is not None
328
- else ""
329
- )
330
- if show_values and not is_class:
331
- row.append(
332
- str(field_info["value"]) if field_info["value"] is not None else ""
333
- )
334
- parts.append(markdown_table_row(row))
335
- else:
336
- # List format
337
- for field_info in fields_data:
338
- field_desc = markdown_code(field_info["name"])
339
- if field_info["type"]:
340
- field_desc += f" ({field_info['type']})"
341
- if field_info["default"] is not None:
342
- field_desc += f" - default: {field_info['default']}"
343
- if field_info["value"] is not None:
344
- field_desc += f" = {field_info['value']}"
345
- parts.append(markdown_list_item(field_desc, indent_level))
346
-
347
- return "\n".join(parts)
348
-
349
-
350
- def convert_pydantic_to_text(
351
- obj: Any,
352
- title: Optional[str],
353
- description: Optional[str],
354
- table_format: bool,
355
- show_types: bool,
356
- show_defaults: bool,
357
- show_values: bool,
358
- show_required: bool,
359
- indent_level: int,
360
- ) -> str:
361
- """Convert a Pydantic model to Markdown format."""
362
- is_class = isinstance(obj, type)
363
- is_instance = is_pydantic_basemodel_instance(obj)
364
-
365
- obj_name = title or (obj.__name__ if is_class else obj.__class__.__name__)
366
-
367
- parts = []
368
- parts.append(markdown_heading(obj_name, min(indent_level + 1, 6)))
369
-
370
- if description:
371
- parts.append(f"\n{description}\n")
372
-
373
- model_fields = getattr(obj if is_class else obj.__class__, "model_fields", {})
374
- fields_data = []
375
-
376
- for field_name, field_info in model_fields.items():
377
- field_data = {
378
- "name": field_name,
379
- "type": convert_type_to_text(field_info.annotation) if show_types else None,
380
- "required": getattr(field_info, "is_required", lambda: True)()
381
- if show_required
382
- else None,
383
- "default": getattr(field_info, "default", None) if show_defaults else None,
384
- "value": getattr(obj, field_name, None)
385
- if show_values and is_instance
386
- else None,
387
- "description": getattr(field_info, "description", None),
388
- }
389
- fields_data.append(field_data)
390
-
391
- if table_format and fields_data:
392
- # Create table
393
- headers = ["Field"]
394
- if show_types:
395
- headers.append("Type")
396
- if show_required:
397
- headers.append("Required")
398
- if show_defaults:
399
- headers.append("Default")
400
- if show_values and is_instance:
401
- headers.append("Value")
402
-
403
- parts.append("\n" + markdown_table_row(headers, is_header=True))
404
-
405
- for field_data in fields_data:
406
- row = [markdown_code(field_data["name"])]
407
- if show_types:
408
- row.append(field_data["type"] or "")
409
- if show_required:
410
- row.append("Yes" if field_data["required"] else "No")
411
- if show_defaults:
412
- row.append(
413
- str(field_data["default"])
414
- if field_data["default"] is not None
415
- else ""
416
- )
417
- if show_values and is_instance:
418
- row.append(
419
- str(field_data["value"]) if field_data["value"] is not None else ""
420
- )
421
- parts.append(markdown_table_row(row))
422
- else:
423
- # List format
424
- for field_data in fields_data:
425
- field_desc = markdown_code(field_data["name"])
426
- if field_data["type"]:
427
- field_desc += f" ({field_data['type']})"
428
- if field_data["required"] is not None:
429
- field_desc += (
430
- " " + markdown_bold("[Required]")
431
- if field_data["required"]
432
- else " " + markdown_italic("[Optional]")
433
- )
434
- if field_data["default"] is not None:
435
- field_desc += f" - default: {field_data['default']}"
436
- if field_data["value"] is not None:
437
- field_desc += f" = {field_data['value']}"
438
-
439
- parts.append(markdown_list_item(field_desc, indent_level))
440
-
441
- if field_data["description"]:
442
- parts.append(
443
- markdown_list_item(field_data["description"], indent_level + 1)
444
- )
445
-
446
- return "\n".join(parts)
447
-
448
-
449
- def convert_function_to_text(
450
- obj: Callable,
451
- title: Optional[str],
452
- description: Optional[str],
453
- show_signature: bool,
454
- show_docstring: bool,
455
- indent_level: int,
456
- ) -> str:
457
- """Convert a function to Markdown format."""
458
- func_name = title or obj.__name__
459
-
460
- parts = []
461
- parts.append(markdown_heading(func_name, min(indent_level + 1, 6)))
462
-
463
- if show_signature:
464
- import inspect
465
-
466
- try:
467
- sig = inspect.signature(obj)
468
- parts.append(f"\n{markdown_code(f'{func_name}{sig}')}\n")
469
- except Exception:
470
- pass
471
-
472
- if description:
473
- parts.append(f"\n{description}\n")
474
- elif show_docstring and obj.__doc__:
475
- doc_info = parse(obj.__doc__)
476
- if doc_info.short_description:
477
- parts.append(f"\n{doc_info.short_description}\n")
478
- if doc_info.long_description:
479
- parts.append(f"\n{doc_info.long_description}\n")
480
-
481
- return "\n".join(parts)
482
-
483
-
484
- def convert_collection_to_text(
485
- obj: Union[List, Set, tuple],
486
- title: Optional[str],
487
- description: Optional[str],
488
- compact: bool,
489
- show_indices: bool,
490
- indent_level: int,
491
- visited: Set[int],
492
- ) -> str:
493
- """Convert a collection to Markdown format."""
494
- obj_name = title or obj.__class__.__name__
495
-
496
- parts = []
497
- if not compact:
498
- parts.append(markdown_heading(obj_name, min(indent_level + 1, 6)))
499
- if description:
500
- parts.append(f"\n{description}\n")
501
-
502
- if not obj:
503
- parts.append(markdown_italic("(empty)"))
504
- return "\n".join(parts)
505
-
506
- for i, item in enumerate(obj):
507
- if show_indices:
508
- item_text = f"[{i}] {convert_to_text(item, compact=True, _visited=visited)}"
509
- else:
510
- item_text = convert_to_text(item, compact=True, _visited=visited)
511
- parts.append(markdown_list_item(item_text, indent_level))
512
-
513
- return "\n".join(parts)
514
-
515
-
516
- def convert_dict_to_text(
517
- obj: Dict[Any, Any],
518
- title: Optional[str],
519
- description: Optional[str],
520
- table_format: bool,
521
- compact: bool,
522
- indent_level: int,
523
- visited: Set[int],
524
- ) -> str:
525
- """Convert a dictionary to Markdown format."""
526
- obj_name = title or "Dictionary"
527
-
528
- parts = []
529
- if not compact:
530
- parts.append(markdown_heading(obj_name, min(indent_level + 1, 6)))
531
- if description:
532
- parts.append(f"\n{description}\n")
533
-
534
- if not obj:
535
- parts.append(markdown_italic("(empty)"))
536
- return "\n".join(parts)
537
-
538
- if table_format and all(
539
- isinstance(v, (str, int, float, bool, type(None))) for v in obj.values()
540
- ):
541
- # Use table format for simple values
542
- parts.append("\n" + markdown_table_row(["Key", "Value"], is_header=True))
543
- for key, value in obj.items():
544
- parts.append(markdown_table_row([markdown_code(str(key)), str(value)]))
545
- else:
546
- # Use list format
547
- for key, value in obj.items():
548
- key_str = markdown_code(str(key))
549
- value_str = convert_to_text(value, compact=True, _visited=visited)
550
- parts.append(markdown_list_item(f"{key_str}: {value_str}", indent_level))
551
-
552
- return "\n".join(parts)
553
-
554
-
555
- # -----------------------------------------------------------------------------
556
- # Main Converter Function
557
- # -----------------------------------------------------------------------------
558
-
559
-
560
- def convert_to_text(
561
- obj: Any,
562
- *,
563
- # Formatting options
564
- title: Optional[str] = None,
565
- description: Optional[str] = None,
566
- heading_level: int = 1,
567
- table_format: bool = False,
568
- compact: bool = False,
569
- code_block_language: Optional[str] = None,
570
- # Display options
571
- show_types: bool = True,
572
- show_values: bool = True,
573
- show_defaults: bool = True,
574
- show_required: bool = True,
575
- show_docstring: bool = True,
576
- show_signature: bool = True,
577
- show_indices: bool = False,
578
- # Style options
579
- escape_special_chars: bool = False,
580
- add_toc: bool = False,
581
- add_horizontal_rules: bool = False,
582
- # Internal
583
- _visited: Optional[Set[int]] = None,
584
- _indent_level: int = 0,
585
- ) -> str:
586
- """
587
- Converts any object into a Markdown formatted string.
588
-
589
- Args:
590
- obj: The object to convert to Markdown
591
- name: Optional name/title for the object
592
- description: Optional description to add
593
- heading_level: Starting heading level (1-6)
594
- table_format: Use tables for structured data when possible
595
- compact: Minimize formatting for inline usage
596
- code_block_language: If set, wrap entire output in code block
597
- show_types: Include type information
598
- show_values: Show current values (for instances)
599
- show_defaults: Show default values
600
- show_required: Show required/optional status
601
- show_docstring: Include docstrings
602
- show_signature: Show function signatures
603
- show_indices: Show indices for collections
604
- escape_special_chars: Escape Markdown special characters
605
- add_toc: Add table of contents (not implemented)
606
- add_horizontal_rules: Add separators between sections
607
-
608
- Returns:
609
- Markdown formatted string representation of the object
610
- """
611
- # Handle circular references
612
- visited = _visited if _visited is not None else set()
613
- obj_id = id(obj)
614
-
615
- if obj_id in visited:
616
- return markdown_italic("(circular reference)")
617
-
618
- visited_copy = visited.copy()
619
- visited_copy.add(obj_id)
620
-
621
- # Handle None
622
- if obj is None:
623
- return markdown_code("None")
624
-
625
- # Handle primitives
626
- if isinstance(obj, (str, int, float, bool)):
627
- text = str(obj)
628
- if escape_special_chars and isinstance(obj, str):
629
- text = _escape_markdown(text)
630
- return text if compact else markdown_code(text)
631
-
632
- # Handle bytes
633
- if isinstance(obj, bytes):
634
- return markdown_code(f"b'{obj.hex()}'")
635
-
636
- # Wrap in code block if requested
637
- if code_block_language:
638
- try:
639
- if code_block_language.lower() == "json":
640
- content = json.dumps(obj, indent=2)
641
- else:
642
- content = str(obj)
643
- return markdown_code_block(content, code_block_language)
644
- except Exception:
645
- pass
646
-
647
- result = ""
648
-
649
- # Handle dataclasses
650
- if is_dataclass(obj):
651
- result = convert_dataclass_to_text(
652
- obj,
653
- title,
654
- description,
655
- table_format,
656
- show_types,
657
- show_defaults,
658
- show_values,
659
- _indent_level,
660
- )
661
-
662
- # Handle Pydantic models
663
- elif is_pydantic_basemodel(obj):
664
- result = convert_pydantic_to_text(
665
- obj,
666
- title,
667
- description,
668
- table_format,
669
- show_types,
670
- show_defaults,
671
- show_values,
672
- show_required,
673
- _indent_level,
674
- )
675
-
676
- # Handle msgspec structs
677
- elif is_msgspec_struct(obj):
678
- # Similar to dataclass handling
679
- result = convert_dataclass_to_text(
680
- obj,
681
- title,
682
- description,
683
- table_format,
684
- show_types,
685
- show_defaults,
686
- show_values,
687
- _indent_level,
688
- )
689
-
690
- # Handle functions
691
- elif callable(obj) and hasattr(obj, "__name__"):
692
- result = convert_function_to_text(
693
- obj, title, description, show_signature, show_docstring, _indent_level
694
- )
695
-
696
- # Handle collections
697
- elif isinstance(obj, (list, tuple, set)):
698
- result = convert_collection_to_text(
699
- obj, title, description, compact, show_indices, _indent_level, visited_copy
700
- )
701
-
702
- # Handle dictionaries
703
- elif isinstance(obj, dict):
704
- result = convert_dict_to_text(
705
- obj, title, description, table_format, compact, _indent_level, visited_copy
706
- )
707
-
708
- # Default handling
709
- else:
710
- obj_name = title or obj.__class__.__name__
711
- parts = []
712
- if not compact:
713
- parts.append(markdown_heading(obj_name, min(_indent_level + 1, 6)))
714
- if description:
715
- parts.append(f"\n{description}\n")
716
- parts.append(markdown_code(str(obj)))
717
- result = "\n".join(parts)
718
-
719
- # Add horizontal rule if requested
720
- if add_horizontal_rules and not compact and _indent_level == 0:
721
- result += f"\n\n{markdown_horizontal_rule()}\n"
722
-
723
- return result