hammad-python 0.0.10__py3-none-any.whl → 0.0.11__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 (74) hide show
  1. hammad/__init__.py +64 -10
  2. hammad/based/__init__.py +52 -0
  3. hammad/based/fields.py +546 -0
  4. hammad/based/model.py +968 -0
  5. hammad/based/utils.py +455 -0
  6. hammad/cache/__init__.py +30 -0
  7. hammad/{cache.py → cache/_cache.py} +83 -12
  8. hammad/cli/__init__.py +25 -0
  9. hammad/cli/plugins/__init__.py +786 -0
  10. hammad/cli/styles/__init__.py +5 -0
  11. hammad/cli/styles/animations.py +548 -0
  12. hammad/cli/styles/settings.py +135 -0
  13. hammad/cli/styles/types.py +358 -0
  14. hammad/cli/styles/utils.py +480 -0
  15. hammad/data/__init__.py +51 -0
  16. hammad/data/collections/__init__.py +32 -0
  17. hammad/data/collections/base_collection.py +58 -0
  18. hammad/data/collections/collection.py +227 -0
  19. hammad/data/collections/searchable_collection.py +556 -0
  20. hammad/data/collections/vector_collection.py +497 -0
  21. hammad/data/databases/__init__.py +21 -0
  22. hammad/data/databases/database.py +551 -0
  23. hammad/data/types/__init__.py +33 -0
  24. hammad/data/types/files/__init__.py +1 -0
  25. hammad/data/types/files/audio.py +81 -0
  26. hammad/data/types/files/configuration.py +475 -0
  27. hammad/data/types/files/document.py +195 -0
  28. hammad/data/types/files/file.py +358 -0
  29. hammad/data/types/files/image.py +80 -0
  30. hammad/json/__init__.py +21 -0
  31. hammad/{utils/json → json}/converters.py +4 -1
  32. hammad/logging/__init__.py +27 -0
  33. hammad/logging/decorators.py +432 -0
  34. hammad/logging/logger.py +534 -0
  35. hammad/pydantic/__init__.py +43 -0
  36. hammad/{utils/pydantic → pydantic}/converters.py +2 -1
  37. hammad/pydantic/models/__init__.py +28 -0
  38. hammad/pydantic/models/arbitrary_model.py +46 -0
  39. hammad/pydantic/models/cacheable_model.py +79 -0
  40. hammad/pydantic/models/fast_model.py +318 -0
  41. hammad/pydantic/models/function_model.py +176 -0
  42. hammad/pydantic/models/subscriptable_model.py +63 -0
  43. hammad/text/__init__.py +37 -0
  44. hammad/text/text.py +1068 -0
  45. hammad/text/utils/__init__.py +1 -0
  46. hammad/{utils/text → text/utils}/converters.py +2 -2
  47. hammad/text/utils/markdown/__init__.py +1 -0
  48. hammad/{utils → text/utils}/markdown/converters.py +3 -3
  49. hammad/{utils → text/utils}/markdown/formatting.py +1 -1
  50. hammad/{utils/typing/utils.py → typing/__init__.py} +75 -2
  51. hammad/web/__init__.py +42 -0
  52. hammad/web/http/__init__.py +1 -0
  53. hammad/web/http/client.py +944 -0
  54. hammad/web/openapi/client.py +740 -0
  55. hammad/web/search/__init__.py +1 -0
  56. hammad/web/search/client.py +936 -0
  57. hammad/web/utils.py +463 -0
  58. hammad/yaml/__init__.py +30 -0
  59. hammad/yaml/converters.py +19 -0
  60. {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/METADATA +14 -8
  61. hammad_python-0.0.11.dist-info/RECORD +65 -0
  62. hammad/database.py +0 -447
  63. hammad/logger.py +0 -273
  64. hammad/types/color.py +0 -951
  65. hammad/utils/json/__init__.py +0 -0
  66. hammad/utils/markdown/__init__.py +0 -0
  67. hammad/utils/pydantic/__init__.py +0 -0
  68. hammad/utils/text/__init__.py +0 -0
  69. hammad/utils/typing/__init__.py +0 -0
  70. hammad_python-0.0.10.dist-info/RECORD +0 -22
  71. /hammad/{types/__init__.py → py.typed} +0 -0
  72. /hammad/{utils → web/openapi}/__init__.py +0 -0
  73. {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/WHEEL +0 -0
  74. {hammad_python-0.0.10.dist-info → hammad_python-0.0.11.dist-info}/licenses/LICENSE +0 -0
hammad/text/text.py ADDED
@@ -0,0 +1,1068 @@
1
+ """hammad.text
2
+
3
+ Contains the `BaseText` type, which is a functional type & object
4
+ for created intelligently rendered strings and markdown strings
5
+ from various input types and objects."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from abc import ABC
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import (
14
+ Any,
15
+ Dict,
16
+ List,
17
+ Literal,
18
+ Optional,
19
+ Union,
20
+ Callable,
21
+ Type,
22
+ ClassVar,
23
+ overload,
24
+ TypeVar,
25
+ Generic,
26
+ )
27
+
28
+ from .utils.markdown.formatting import (
29
+ heading as md_heading,
30
+ code_block as md_code_block,
31
+ )
32
+ from .utils.markdown.converters import (
33
+ convert_to_markdown,
34
+ )
35
+
36
+
37
+ # -----------------------------------------------------------------------------
38
+ # Enums and Types
39
+ # -----------------------------------------------------------------------------
40
+
41
+
42
+ class OutputFormat(Enum):
43
+ """Supported output formats for text conversion."""
44
+
45
+ TEXT = "text"
46
+ MARKDOWN = "markdown"
47
+ JSON = "json"
48
+ TYPE = "type"
49
+ ANY = "any"
50
+
51
+
52
+ class HeadingStyle(Enum):
53
+ """Heading styles for different formats."""
54
+
55
+ # Text formats
56
+ HASH = "#"
57
+ BRACKET = "[]"
58
+ ANGLE = "<>"
59
+ BRACE = "{}"
60
+ # Markdown specific
61
+ UNDERLINE = "="
62
+
63
+
64
+ # -----------------------------------------------------------------------------
65
+ # Base Text Class (Unified Type System)
66
+ # -----------------------------------------------------------------------------
67
+
68
+
69
+ @dataclass(repr=False, eq=False)
70
+ class BaseText(ABC):
71
+ """
72
+ Abstract base class for structured text conversion.
73
+
74
+ This class provides a unified interface for converting objects
75
+ to various text formats with extensive customization options.
76
+ All sections are also BaseText instances, creating a unified type system.
77
+ """
78
+
79
+ # Class-level configuration
80
+ DEFAULT_FORMAT: ClassVar[OutputFormat] = OutputFormat.TEXT
81
+ SUPPORTED_FORMATS: ClassVar[List[OutputFormat]] = [
82
+ OutputFormat.TEXT,
83
+ OutputFormat.MARKDOWN,
84
+ OutputFormat.JSON,
85
+ OutputFormat.TYPE,
86
+ OutputFormat.ANY,
87
+ ]
88
+
89
+ # Core attributes (both for documents and sections)
90
+ type: str = "base"
91
+ """The type identifier for this text object/section."""
92
+
93
+ title: Optional[str] = None
94
+ """Title for the document or section."""
95
+
96
+ description: Optional[str] = None
97
+ """Description for the document or section."""
98
+
99
+ content: Optional[Union[str, Any]] = ""
100
+ """The main content (for sections)."""
101
+
102
+ metadata: Dict[str, Any] = field(default_factory=dict)
103
+ """Metadata for the document/section."""
104
+
105
+ language: Optional[str] = None
106
+ """Programming language for code sections (if applicable)."""
107
+
108
+ # Hierarchical structure
109
+ sections: List[BaseText] = field(default_factory=list)
110
+ """Child sections (all are BaseText instances)."""
111
+
112
+ # Formatting options
113
+ heading_level: int = 2
114
+ """Heading level (1-6) for this section."""
115
+
116
+ show_in_toc: bool = True
117
+ """Whether to include in table of contents."""
118
+
119
+ collapsible: bool = False
120
+ """Whether this section should be collapsible (for supported formats)."""
121
+
122
+ format_config: Dict[OutputFormat, Dict[str, Any]] = field(default_factory=dict)
123
+ """Format-specific configuration options."""
124
+
125
+ def build_sections(self) -> List[BaseText]:
126
+ """
127
+ Build and return the sections for this text object.
128
+ Default implementation returns existing sections.
129
+ Subclasses can override to dynamically build sections.
130
+ """
131
+ return self.sections
132
+
133
+ def add_section(self, section: BaseText) -> BaseText:
134
+ """Add a section to this text object."""
135
+ self.sections.append(section)
136
+ return self
137
+
138
+ def get_format_config(self, format: OutputFormat) -> Dict[str, Any]:
139
+ """Get configuration for a specific format."""
140
+ default_config = {
141
+ OutputFormat.TEXT: {
142
+ "compact": False,
143
+ "show_types": True,
144
+ "title_style": "##",
145
+ "bullet_style": "-",
146
+ },
147
+ OutputFormat.MARKDOWN: {
148
+ "table_format": False,
149
+ "escape_special_chars": False,
150
+ "add_toc": False,
151
+ "add_horizontal_rules": False,
152
+ },
153
+ OutputFormat.JSON: {
154
+ "indent": 2,
155
+ "sort_keys": False,
156
+ },
157
+ OutputFormat.TYPE: {
158
+ "show_full_path": True,
159
+ "include_module": True,
160
+ },
161
+ OutputFormat.ANY: {
162
+ "fallback_format": OutputFormat.TEXT,
163
+ "auto_detect": True,
164
+ },
165
+ }
166
+
167
+ config = default_config.get(format, {}).copy()
168
+ config.update(self.format_config.get(format, {}))
169
+ return config
170
+
171
+ def to_format(self, format: OutputFormat = None, **kwargs) -> str:
172
+ """
173
+ Convert to the specified format.
174
+
175
+ Args:
176
+ format: The output format (defaults to DEFAULT_FORMAT)
177
+ **kwargs: Additional format-specific options
178
+
179
+ Returns:
180
+ Formatted string representation
181
+ """
182
+ if format is None:
183
+ format = self.DEFAULT_FORMAT
184
+
185
+ if format not in self.SUPPORTED_FORMATS:
186
+ raise ValueError(f"Unsupported format: {format}")
187
+
188
+ # Ensure sections are built
189
+ if not self.sections:
190
+ self.sections = self.build_sections()
191
+
192
+ # Merge configurations
193
+ config = self.get_format_config(format)
194
+ config.update(kwargs)
195
+
196
+ # Convert based on format
197
+ if format == OutputFormat.TEXT:
198
+ return self._to_text(**config)
199
+ elif format == OutputFormat.MARKDOWN:
200
+ return self._to_markdown(**config)
201
+ elif format == OutputFormat.JSON:
202
+ return self._to_json(**config)
203
+ elif format == OutputFormat.TYPE:
204
+ return self._to_type(**config)
205
+ elif format == OutputFormat.ANY:
206
+ return self._to_any(**config)
207
+ else:
208
+ raise NotImplementedError(f"Format {format} not implemented")
209
+
210
+ def _to_text(self, **kwargs) -> str:
211
+ """Convert to plain text format."""
212
+ parts = []
213
+
214
+ # Handle title
215
+ if self.title:
216
+ title_style = kwargs.get("title_style", "##")
217
+ if title_style == "#":
218
+ parts.append("#" * self.heading_level + " " + self.title)
219
+ elif title_style == "[]":
220
+ parts.append(f"[{self.title}]")
221
+ elif title_style == "<>":
222
+ parts.append(f"<{self.title}>")
223
+ elif title_style == "{}":
224
+ parts.append(f"{{{self.title}}}")
225
+ else:
226
+ parts.append(self.title)
227
+
228
+ # Handle description
229
+ if self.description:
230
+ parts.append(self.description)
231
+
232
+ # Handle content (for sections)
233
+ if self.content:
234
+ if isinstance(self.content, str):
235
+ parts.append(self.content)
236
+ else:
237
+ parts.append(convert_to_markdown(self.content, **kwargs))
238
+
239
+ # Handle subsections
240
+ for section in self.sections:
241
+ sub_kwargs = kwargs.copy()
242
+ sub_kwargs["indent"] = kwargs.get("indent", 0) + 1
243
+ parts.append(section.to_format(OutputFormat.TEXT, **sub_kwargs))
244
+
245
+ return "\n\n".join(filter(None, parts))
246
+
247
+ def _to_markdown(self, **kwargs) -> str:
248
+ """Convert to Markdown format."""
249
+ parts = []
250
+
251
+ # Handle title
252
+ if self.title:
253
+ parts.append(md_heading(self.title, self.heading_level))
254
+
255
+ # Handle description
256
+ if self.description:
257
+ parts.append(self.description)
258
+
259
+ # Handle content (for sections)
260
+ if self.content:
261
+ if isinstance(self.content, str):
262
+ parts.append(self.content)
263
+ else:
264
+ parts.append(convert_to_markdown(self.content, **kwargs))
265
+
266
+ # Add table of contents if requested (only for top-level documents)
267
+ if kwargs.get("add_toc", False) and self.heading_level == 1:
268
+ toc_headings = []
269
+ for section in self.sections:
270
+ if section.show_in_toc and section.title:
271
+ toc_headings.append((section.heading_level, section.title))
272
+
273
+ if toc_headings:
274
+ from .utils.markdown.converters import create_markdown_toc
275
+
276
+ parts.append(create_markdown_toc(toc_headings))
277
+
278
+ # Handle subsections
279
+ for section in self.sections:
280
+ sub_kwargs = kwargs.copy()
281
+ sub_kwargs["_indent_level"] = self.heading_level
282
+ parts.append(section.to_format(OutputFormat.MARKDOWN, **sub_kwargs))
283
+
284
+ return "\n\n".join(filter(None, parts))
285
+
286
+ def _to_json(self, **kwargs) -> str:
287
+ """Convert to JSON format."""
288
+ data = {
289
+ "type": self.type,
290
+ "title": self.title,
291
+ "description": self.description,
292
+ "content": self.content,
293
+ "metadata": self.metadata,
294
+ "sections": [
295
+ json.loads(s.to_format(OutputFormat.JSON, **kwargs))
296
+ for s in self.sections
297
+ ],
298
+ }
299
+
300
+ indent = kwargs.get("indent", 2)
301
+ sort_keys = kwargs.get("sort_keys", False)
302
+
303
+ return json.dumps(data, indent=indent, sort_keys=sort_keys)
304
+
305
+ def _to_type(self, **kwargs) -> str:
306
+ """Convert to type annotation format."""
307
+ show_full_path = kwargs.get("show_full_path", True)
308
+ include_module = kwargs.get("include_module", True)
309
+
310
+ type_info = self.__class__.__name__
311
+
312
+ if include_module:
313
+ module = self.__class__.__module__
314
+ if module != "__main__" and show_full_path:
315
+ type_info = f"{module}.{type_info}"
316
+
317
+ # Include key attributes in the type representation
318
+ attrs = []
319
+ if self.type != "base":
320
+ attrs.append(f"type={self.type!r}")
321
+ if self.title:
322
+ attrs.append(f"title={self.title!r}")
323
+ if self.sections:
324
+ attrs.append(f"sections={len(self.sections)}")
325
+
326
+ if attrs:
327
+ type_info += f"({', '.join(attrs)})"
328
+
329
+ return type_info
330
+
331
+ def _to_any(self, **kwargs) -> str:
332
+ """Convert using automatic format detection or fallback."""
333
+ fallback_format = kwargs.get("fallback_format", OutputFormat.TEXT)
334
+ auto_detect = kwargs.get("auto_detect", True)
335
+
336
+ if auto_detect:
337
+ # Simple heuristics for format detection
338
+ if self.content and isinstance(self.content, str):
339
+ content_lower = self.content.lower().strip()
340
+
341
+ # Check for JSON content
342
+ if content_lower.startswith("{") and content_lower.endswith("}"):
343
+ return self.to_format(OutputFormat.JSON, **kwargs)
344
+
345
+ # Check for code content
346
+ if any(
347
+ keyword in content_lower
348
+ for keyword in ["def ", "class ", "import ", "from "]
349
+ ):
350
+ return self.to_format(OutputFormat.MARKDOWN, **kwargs)
351
+
352
+ # Check if this looks like schema documentation
353
+ if hasattr(self, "output_schema") or self.type in ["schema", "output"]:
354
+ return self.to_format(OutputFormat.MARKDOWN, **kwargs)
355
+
356
+ # Use fallback format
357
+ return self.to_format(fallback_format, **kwargs)
358
+
359
+ def to_dict(self) -> Dict[str, Any]:
360
+ """Convert to dictionary representation."""
361
+ return {
362
+ "type": self.type,
363
+ "title": self.title,
364
+ "description": self.description,
365
+ "content": self.content,
366
+ "metadata": self.metadata,
367
+ "sections": [s.to_dict() for s in self.sections],
368
+ }
369
+
370
+ # Convenience methods
371
+ def __str__(self) -> str:
372
+ """String representation using default format."""
373
+ return self.to_format()
374
+
375
+ def __repr__(self) -> str:
376
+ """Developer-friendly representation."""
377
+ section_count = len(self.sections)
378
+ section_text = "section" if section_count == 1 else "sections"
379
+ if section_count > 0:
380
+ return f"{self.__class__.__name__}(type={self.type!r}, title={self.title!r}, {section_count} {section_text})"
381
+ else:
382
+ return f"{self.__class__.__name__}(type={self.type!r}, title={self.title!r}, sections={section_count})"
383
+
384
+ @property
385
+ def text(self) -> str:
386
+ """Quick access to text format."""
387
+ return self.to_format(OutputFormat.TEXT)
388
+
389
+ @property
390
+ def markdown(self) -> str:
391
+ """Quick access to markdown format."""
392
+ return self.to_format(OutputFormat.MARKDOWN)
393
+
394
+ @property
395
+ def json(self) -> str:
396
+ """Quick access to JSON format."""
397
+ return self.to_format(OutputFormat.JSON)
398
+
399
+ @property
400
+ def type_info(self) -> str:
401
+ """Quick access to type format."""
402
+ return self.to_format(OutputFormat.TYPE)
403
+
404
+ @property
405
+ def any_format(self) -> str:
406
+ """Quick access to any format (auto-detected)."""
407
+ return self.to_format(OutputFormat.ANY)
408
+
409
+ def __len__(self) -> int:
410
+ """Return the length of the content."""
411
+ if self.content is None:
412
+ return 0
413
+ return len(str(self.content))
414
+
415
+ def __eq__(self, other) -> bool:
416
+ """Check equality based on content."""
417
+ if not isinstance(other, BaseText):
418
+ return False
419
+ return str(self.content or "") == str(other.content or "")
420
+
421
+
422
+ # -----------------------------------------------------------------------------
423
+ # Specialized Section Classes (Now BaseText Subclasses)
424
+ # -----------------------------------------------------------------------------
425
+
426
+
427
+ @dataclass(repr=False, eq=False)
428
+ class CodeSection(BaseText):
429
+ """Section specifically for code content."""
430
+
431
+ type: str = "code"
432
+ line_numbers: bool = False
433
+ highlight_lines: Optional[List[int]] = None
434
+
435
+ def _to_markdown(self, **kwargs) -> str:
436
+ """Convert to Markdown with code block."""
437
+ parts = []
438
+
439
+ if self.title:
440
+ parts.append(md_heading(self.title, self.heading_level))
441
+
442
+ if self.description:
443
+ parts.append(self.description)
444
+
445
+ if self.content:
446
+ parts.append(md_code_block(str(self.content), self.language or ""))
447
+
448
+ # Handle subsections
449
+ for section in self.sections:
450
+ sub_kwargs = kwargs.copy()
451
+ sub_kwargs["_indent_level"] = self.heading_level
452
+ parts.append(section.to_format(OutputFormat.MARKDOWN, **sub_kwargs))
453
+
454
+ return "\n\n".join(filter(None, parts))
455
+
456
+
457
+ @dataclass(repr=False, eq=False)
458
+ class SchemaSection(BaseText):
459
+ """Section for schema/model documentation."""
460
+
461
+ type: str = "schema"
462
+ schema_object: Optional[Any] = None
463
+ show_examples: bool = True
464
+ table_format: bool = True
465
+
466
+ def _to_markdown(self, **kwargs) -> str:
467
+ """Convert schema to Markdown documentation."""
468
+ if self.schema_object:
469
+ return convert_to_markdown(
470
+ self.schema_object,
471
+ name=self.title,
472
+ description=self.description,
473
+ table_format=self.table_format,
474
+ **kwargs,
475
+ )
476
+ return super()._to_markdown(**kwargs)
477
+
478
+
479
+ # -----------------------------------------------------------------------------
480
+ # Concrete Implementation Classes
481
+ # -----------------------------------------------------------------------------
482
+
483
+
484
+ @dataclass(repr=False, eq=False)
485
+ class SimpleText(BaseText):
486
+ """Simple concrete implementation of BaseText for basic use cases."""
487
+
488
+ type: str = "simple"
489
+
490
+ def build_sections(self) -> List[BaseText]:
491
+ """Simple text doesn't build sections dynamically."""
492
+ return self.sections
493
+
494
+
495
+ @dataclass(repr=False, eq=False)
496
+ class OutputText(BaseText):
497
+ """
498
+ Implementation for structured output documentation.
499
+ """
500
+
501
+ DEFAULT_FORMAT: ClassVar[OutputFormat] = OutputFormat.MARKDOWN
502
+ type: str = "output"
503
+
504
+ # Specific attributes for output documentation
505
+ output_schema: Optional[Any] = None
506
+ """The schema/model to document."""
507
+
508
+ examples: List[Dict[str, Any]] = field(default_factory=list)
509
+ """Example outputs."""
510
+
511
+ validation_rules: List[str] = field(default_factory=list)
512
+ """Validation rules for the output."""
513
+
514
+ error_cases: List[Dict[str, str]] = field(default_factory=list)
515
+ """Common error cases and messages."""
516
+
517
+ def build_sections(self) -> List[BaseText]:
518
+ """Build sections for output documentation."""
519
+ sections = []
520
+
521
+ # Schema section
522
+ if self.output_schema:
523
+ sections.append(
524
+ SchemaSection(
525
+ title="Output Schema",
526
+ schema_object=self.output_schema,
527
+ description="The following schema defines the structure of the output:",
528
+ table_format=True,
529
+ )
530
+ )
531
+
532
+ # Examples section
533
+ if self.examples:
534
+ examples_section = SimpleText(
535
+ type="examples",
536
+ title="Examples",
537
+ description="Here are some example outputs:",
538
+ )
539
+
540
+ for i, example in enumerate(self.examples, 1):
541
+ examples_section.add_section(
542
+ CodeSection(
543
+ title=f"Example {i}",
544
+ content=json.dumps(example, indent=2),
545
+ language="json",
546
+ heading_level=3,
547
+ )
548
+ )
549
+
550
+ sections.append(examples_section)
551
+
552
+ # Validation rules section
553
+ if self.validation_rules:
554
+ rules_content = "\n".join(f"- {rule}" for rule in self.validation_rules)
555
+ sections.append(
556
+ SimpleText(
557
+ type="validation",
558
+ title="Validation Rules",
559
+ content=rules_content,
560
+ )
561
+ )
562
+
563
+ # Error cases section
564
+ if self.error_cases:
565
+ error_section = SimpleText(
566
+ type="errors",
567
+ title="Common Errors",
568
+ description="The following errors may occur:",
569
+ )
570
+
571
+ for error in self.error_cases:
572
+ error_content = f"**Error**: {error.get('error', 'Unknown')}\n"
573
+ error_content += f"**Message**: {error.get('message', 'No message')}\n"
574
+ if "solution" in error:
575
+ error_content += f"**Solution**: {error['solution']}"
576
+
577
+ error_section.add_section(
578
+ SimpleText(
579
+ type="error",
580
+ title=error.get("code", "ERROR"),
581
+ content=error_content,
582
+ heading_level=3,
583
+ )
584
+ )
585
+
586
+ sections.append(error_section)
587
+
588
+ return sections
589
+
590
+ @classmethod
591
+ def from_function(
592
+ cls, func: Callable, include_examples: bool = True, **kwargs
593
+ ) -> OutputText:
594
+ """
595
+ Create OutputText from a function's return type and docstring.
596
+
597
+ Args:
598
+ func: The function to document
599
+ include_examples: Whether to parse examples from docstring
600
+ **kwargs: Additional arguments for OutputText
601
+
602
+ Returns:
603
+ OutputText instance
604
+ """
605
+ from typing import get_type_hints
606
+
607
+ # Extract function information
608
+ func_name = func.__name__
609
+ hints = get_type_hints(func)
610
+ return_type = hints.get("return", Any)
611
+
612
+ # Create instance
613
+ output_text = cls(
614
+ title=kwargs.get("title", f"Output for {func_name}"),
615
+ description=kwargs.get("description", None),
616
+ output_schema=return_type,
617
+ **kwargs,
618
+ )
619
+
620
+ return output_text
621
+
622
+
623
+ # -----------------------------------------------------------------------------
624
+ # Example Usage
625
+ # -----------------------------------------------------------------------------
626
+
627
+ if __name__ == "__main__":
628
+ from dataclasses import dataclass
629
+ from typing import Optional
630
+
631
+ # Define a sample schema
632
+ @dataclass
633
+ class UserResponse:
634
+ """User information response."""
635
+
636
+ id: int
637
+ username: str
638
+ email: str
639
+ is_active: bool = True
640
+ role: Optional[str] = None
641
+
642
+ # Create output documentation
643
+ output_doc = OutputText(
644
+ title="User API Response",
645
+ description="Documentation for the user endpoint response format.",
646
+ output_schema=UserResponse,
647
+ examples=[
648
+ {
649
+ "id": 123,
650
+ "username": "john_doe",
651
+ "email": "john@example.com",
652
+ "is_active": True,
653
+ "role": "admin",
654
+ },
655
+ {
656
+ "id": 456,
657
+ "username": "jane_smith",
658
+ "email": "jane@example.com",
659
+ "is_active": False,
660
+ "role": None,
661
+ },
662
+ ],
663
+ validation_rules=[
664
+ "ID must be a positive integer",
665
+ "Username must be unique and contain only alphanumeric characters and underscores",
666
+ "Email must be a valid email address",
667
+ "Role must be one of: admin, user, guest (or null)",
668
+ ],
669
+ error_cases=[
670
+ {
671
+ "code": "USER_NOT_FOUND",
672
+ "error": "User not found",
673
+ "message": "The requested user ID does not exist",
674
+ "solution": "Verify the user ID and try again",
675
+ },
676
+ {
677
+ "code": "INVALID_EMAIL",
678
+ "error": "Invalid email format",
679
+ "message": "The provided email address is not valid",
680
+ "solution": "Ensure the email follows the format: user@domain.com",
681
+ },
682
+ ],
683
+ )
684
+
685
+ # Get different formats
686
+ print("=== MARKDOWN FORMAT ===")
687
+ print(output_doc.markdown)
688
+
689
+ print("\n\n=== TEXT FORMAT ===")
690
+ print(output_doc.text)
691
+
692
+ print("\n\n=== JSON FORMAT ===")
693
+ print(output_doc.json)
694
+
695
+ print("\n\n=== TYPE FORMAT ===")
696
+ print(output_doc.type_info)
697
+
698
+ print("\n\n=== ANY FORMAT (auto-detected) ===")
699
+ print(output_doc.any_format)
700
+
701
+
702
+ # -----------------------------------------------------------------------------
703
+ # Unified Text Class - Main Entry Point
704
+ # -----------------------------------------------------------------------------
705
+
706
+ T = TypeVar("T")
707
+
708
+
709
+ @dataclass(repr=False, eq=False)
710
+ class Text(BaseText, Generic[T]):
711
+ """
712
+ Unified Text class - the main entry point for all text operations.
713
+
714
+ This class provides a clean, fully-typed interface for creating and managing
715
+ structured text content with support for multiple output formats.
716
+ """
717
+
718
+ DEFAULT_FORMAT: ClassVar[OutputFormat] = OutputFormat.MARKDOWN
719
+ type: str = "text"
720
+
721
+ # Enhanced typing for content
722
+ content: Optional[Union[str, T, Any]] = None
723
+
724
+ def __init__(
725
+ self,
726
+ content: Optional[Union[str, T, Any]] = None,
727
+ *,
728
+ title: Optional[str] = None,
729
+ description: Optional[str] = None,
730
+ type: str = "text",
731
+ format: Optional[OutputFormat] = None,
732
+ heading_level: int = 1,
733
+ show_in_toc: bool = True,
734
+ collapsible: bool = False,
735
+ metadata: Optional[Dict[str, Any]] = None,
736
+ sections: Optional[List[BaseText]] = None,
737
+ format_config: Optional[Dict[OutputFormat, Dict[str, Any]]] = None,
738
+ **kwargs,
739
+ ) -> None:
740
+ """
741
+ Initialize a Text instance.
742
+
743
+ Args:
744
+ content: The main content (string, object, or any serializable type)
745
+ title: Optional title for the text
746
+ description: Optional description
747
+ type: Type identifier (default: "text")
748
+ format: Default output format
749
+ heading_level: Heading level (1-6)
750
+ show_in_toc: Whether to show in table of contents
751
+ collapsible: Whether the section should be collapsible
752
+ metadata: Additional metadata
753
+ sections: Child sections
754
+ format_config: Format-specific configuration
755
+ **kwargs: Additional arguments
756
+ """
757
+ super().__init__(
758
+ type=type,
759
+ title=title,
760
+ description=description,
761
+ content=content,
762
+ metadata=metadata or {},
763
+ sections=sections or [],
764
+ heading_level=heading_level,
765
+ show_in_toc=show_in_toc,
766
+ collapsible=collapsible,
767
+ format_config=format_config or {},
768
+ )
769
+
770
+ # Set default format if provided
771
+ if format is not None:
772
+ self.DEFAULT_FORMAT = format
773
+
774
+ @classmethod
775
+ def from_string(
776
+ cls,
777
+ text: str,
778
+ *,
779
+ title: Optional[str] = None,
780
+ format: OutputFormat = OutputFormat.TEXT,
781
+ **kwargs,
782
+ ) -> "Text[str]":
783
+ """Create Text from a simple string."""
784
+ return cls(content=text, title=title, format=format, **kwargs)
785
+
786
+ @classmethod
787
+ def from_markdown(
788
+ cls, markdown: str, *, title: Optional[str] = None, **kwargs
789
+ ) -> "Text[str]":
790
+ """Create Text from markdown content."""
791
+ return cls(
792
+ content=markdown, title=title, format=OutputFormat.MARKDOWN, **kwargs
793
+ )
794
+
795
+ @classmethod
796
+ def from_object(
797
+ cls,
798
+ obj: T,
799
+ *,
800
+ title: Optional[str] = None,
801
+ format: OutputFormat = OutputFormat.MARKDOWN,
802
+ **kwargs,
803
+ ) -> "Text[T]":
804
+ """Create Text from any object."""
805
+ return cls(
806
+ content=obj,
807
+ title=title or f"{type(obj).__name__} Documentation",
808
+ format=format,
809
+ **kwargs,
810
+ )
811
+
812
+ @classmethod
813
+ def from_schema(
814
+ cls,
815
+ schema: Type[T],
816
+ *,
817
+ title: Optional[str] = None,
818
+ examples: Optional[List[Dict[str, Any]]] = None,
819
+ **kwargs,
820
+ ) -> "Text[Type[T]]":
821
+ """Create Text from a schema/dataclass type."""
822
+ output_text = OutputText(
823
+ title=title or f"{schema.__name__} Schema",
824
+ output_schema=schema,
825
+ examples=examples or [],
826
+ **kwargs,
827
+ )
828
+ return cls(
829
+ content=output_text,
830
+ title=output_text.title,
831
+ format=OutputFormat.MARKDOWN,
832
+ type="schema",
833
+ )
834
+
835
+ @classmethod
836
+ def from_function(
837
+ cls,
838
+ func: Callable[..., T],
839
+ *,
840
+ title: Optional[str] = None,
841
+ include_examples: bool = True,
842
+ **kwargs,
843
+ ) -> "Text[Callable[..., T]]":
844
+ """Create Text from a function's documentation."""
845
+ output_text = OutputText.from_function(
846
+ func, title=title, include_examples=include_examples, **kwargs
847
+ )
848
+ return cls(
849
+ content=output_text,
850
+ title=output_text.title,
851
+ format=OutputFormat.MARKDOWN,
852
+ type="function",
853
+ )
854
+
855
+ def add_code_section(
856
+ self,
857
+ code: str,
858
+ *,
859
+ language: str = "python",
860
+ title: Optional[str] = None,
861
+ description: Optional[str] = None,
862
+ line_numbers: bool = False,
863
+ **kwargs,
864
+ ) -> "Text[T]":
865
+ """Add a code section to this text."""
866
+ code_section = CodeSection(
867
+ content=code,
868
+ language=language,
869
+ title=title,
870
+ description=description,
871
+ line_numbers=line_numbers,
872
+ heading_level=self.heading_level + 1,
873
+ **kwargs,
874
+ )
875
+ self.add_section(code_section)
876
+ return self
877
+
878
+ def add_text_section(
879
+ self,
880
+ content: Union[str, Any],
881
+ *,
882
+ title: Optional[str] = None,
883
+ description: Optional[str] = None,
884
+ **kwargs,
885
+ ) -> "Text[T]":
886
+ """Add a text section to this text."""
887
+ text_section = SimpleText(
888
+ content=content,
889
+ title=title,
890
+ description=description,
891
+ heading_level=self.heading_level + 1,
892
+ **kwargs,
893
+ )
894
+ self.add_section(text_section)
895
+ return self
896
+
897
+ def add_schema_section(
898
+ self,
899
+ schema: Any,
900
+ *,
901
+ title: Optional[str] = None,
902
+ description: Optional[str] = None,
903
+ table_format: bool = True,
904
+ **kwargs,
905
+ ) -> "Text[T]":
906
+ """Add a schema documentation section."""
907
+ schema_section = SchemaSection(
908
+ schema_object=schema,
909
+ title=title,
910
+ description=description,
911
+ table_format=table_format,
912
+ heading_level=self.heading_level + 1,
913
+ **kwargs,
914
+ )
915
+ self.add_section(schema_section)
916
+ return self
917
+
918
+ @overload
919
+ def render(self, format: Literal[OutputFormat.TEXT]) -> str: ...
920
+
921
+ @overload
922
+ def render(self, format: Literal[OutputFormat.MARKDOWN]) -> str: ...
923
+
924
+ @overload
925
+ def render(self, format: Literal[OutputFormat.JSON]) -> str: ...
926
+
927
+ @overload
928
+ def render(self, format: Literal[OutputFormat.TYPE]) -> str: ...
929
+
930
+ @overload
931
+ def render(self, format: Literal[OutputFormat.ANY]) -> str: ...
932
+
933
+ def render(self, format: Optional[OutputFormat] = None, **kwargs) -> str:
934
+ """
935
+ Render the text in the specified format.
936
+
937
+ Args:
938
+ format: Output format (uses DEFAULT_FORMAT if None)
939
+ **kwargs: Format-specific options
940
+
941
+ Returns:
942
+ Formatted string representation
943
+ """
944
+ return self.to_format(format, **kwargs)
945
+
946
+ def save(
947
+ self,
948
+ filepath: str,
949
+ *,
950
+ format: Optional[OutputFormat] = None,
951
+ encoding: str = "utf-8",
952
+ **kwargs,
953
+ ) -> None:
954
+ """
955
+ Save the text to a file.
956
+
957
+ Args:
958
+ filepath: Path to save the file
959
+ format: Output format (auto-detected from extension if None)
960
+ encoding: File encoding
961
+ **kwargs: Format-specific options
962
+ """
963
+ import os
964
+
965
+ # Auto-detect format from file extension if not provided
966
+ if format is None:
967
+ ext = os.path.splitext(filepath)[1].lower()
968
+ format_map = {
969
+ ".md": OutputFormat.MARKDOWN,
970
+ ".markdown": OutputFormat.MARKDOWN,
971
+ ".json": OutputFormat.JSON,
972
+ ".txt": OutputFormat.TEXT,
973
+ ".text": OutputFormat.TEXT,
974
+ }
975
+ format = format_map.get(ext, self.DEFAULT_FORMAT)
976
+
977
+ # Render content
978
+ content = self.render(format, **kwargs)
979
+
980
+ # Write to file
981
+ with open(filepath, "w", encoding=encoding) as f:
982
+ f.write(content)
983
+
984
+ def chain(self, other: "Text") -> "Text[T]":
985
+ """Chain another Text instance as a section."""
986
+ self.add_section(other)
987
+ return self
988
+
989
+ def __add__(self, other: Union["Text", str, BaseText]) -> "Text[T]":
990
+ """Add operator for chaining texts."""
991
+ if isinstance(other, str):
992
+ return self.add_text_section(other)
993
+ elif isinstance(other, BaseText):
994
+ return self.add_section(other)
995
+ else:
996
+ raise TypeError(f"Cannot add {type(other)} to Text")
997
+
998
+ def __or__(self, format: OutputFormat) -> str:
999
+ """Pipe operator for format conversion."""
1000
+ return self.render(format)
1001
+
1002
+ def __getitem__(self, key: Union[int, str]) -> BaseText:
1003
+ """Access sections by index or title."""
1004
+ if isinstance(key, int):
1005
+ return self.sections[key]
1006
+ elif isinstance(key, str):
1007
+ for section in self.sections:
1008
+ if section.title == key:
1009
+ return section
1010
+ raise KeyError(f"Section with title '{key}' not found")
1011
+ else:
1012
+ raise TypeError(f"Invalid key type: {type(key)}")
1013
+
1014
+ def __len__(self) -> int:
1015
+ """Return total character count of all sections."""
1016
+ if not self.sections:
1017
+ return 0
1018
+ total_length = 0
1019
+ for section in self.sections:
1020
+ total_length += len(section)
1021
+ # Add separators between sections (2 chars for \n\n)
1022
+ return total_length + (len(self.sections) - 1) * 2
1023
+
1024
+ def __iter__(self):
1025
+ """Iterate over sections."""
1026
+ return iter(self.sections)
1027
+
1028
+ def __bool__(self) -> bool:
1029
+ """Check if text has content or sections."""
1030
+ return bool(self.content or self.sections or self.title)
1031
+
1032
+ # Enhanced property access with type hints
1033
+ @property
1034
+ def text(self) -> str:
1035
+ """Get text format representation."""
1036
+ return self.render(OutputFormat.TEXT)
1037
+
1038
+ @property
1039
+ def markdown(self) -> str:
1040
+ """Get markdown format representation."""
1041
+ return self.render(OutputFormat.MARKDOWN)
1042
+
1043
+ @property
1044
+ def json(self) -> str:
1045
+ """Get JSON format representation."""
1046
+ return self.render(OutputFormat.JSON)
1047
+
1048
+ @property
1049
+ def type_info(self) -> str:
1050
+ """Get type format representation."""
1051
+ return self.render(OutputFormat.TYPE)
1052
+
1053
+ @property
1054
+ def auto(self) -> str:
1055
+ """Get auto-detected format representation."""
1056
+ return self.render(OutputFormat.ANY)
1057
+
1058
+
1059
+ __all__ = (
1060
+ "OutputFormat",
1061
+ "HeadingStyle",
1062
+ "BaseText",
1063
+ "CodeSection",
1064
+ "SchemaSection",
1065
+ "SimpleText",
1066
+ "OutputText",
1067
+ "Text",
1068
+ )