hammad-python 0.0.30__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.30.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.30.dist-info/RECORD +0 -135
  135. {hammad → ham}/py.typed +0 -0
  136. {hammad_python-0.0.30.dist-info → hammad_python-0.0.31.dist-info}/WHEEL +0 -0
  137. {hammad_python-0.0.30.dist-info → hammad_python-0.0.31.dist-info}/licenses/LICENSE +0 -0
hammad/cli/plugins.py DELETED
@@ -1,867 +0,0 @@
1
- """hammad.cli.plugins
2
-
3
- Contains the following 'builtin' plugins or extensions:
4
-
5
- - `print()`
6
- - `input()`
7
- - `animate()`
8
- """
9
-
10
- from __future__ import annotations
11
-
12
- import builtins
13
- import json
14
- from typing import (
15
- Optional,
16
- IO,
17
- overload,
18
- Any,
19
- Dict,
20
- Literal,
21
- List,
22
- Union,
23
- Callable,
24
- TYPE_CHECKING,
25
- )
26
-
27
- if TYPE_CHECKING:
28
- from rich import get_console
29
- from rich.console import (
30
- JustifyMethod,
31
- OverflowMethod,
32
- Console,
33
- RenderableType,
34
- )
35
- from rich.panel import PaddingDimensions
36
- from rich.prompt import Prompt, Confirm
37
- from prompt_toolkit import prompt as pt_prompt
38
- from prompt_toolkit.completion import WordCompleter
39
- from .animations import (
40
- CLIFlashingAnimation,
41
- CLIPulsingAnimation,
42
- CLIShakingAnimation,
43
- CLITypingAnimation,
44
- CLISpinningAnimation,
45
- CLIRainbowAnimation,
46
- RainbowPreset,
47
- )
48
- from .styles.types import (
49
- CLIStyleType,
50
- CLIStyleBackgroundType,
51
- CLIStyleColorName,
52
- CLIStyleBoxName,
53
- )
54
- from .styles.settings import (
55
- CLIStyleRenderableSettings,
56
- CLIStyleBackgroundSettings,
57
- CLIStyleLiveSettings,
58
- )
59
- from .styles.utils import (
60
- live_render,
61
- style_renderable,
62
- )
63
-
64
- # Lazy import cache
65
- _IMPORT_CACHE = {}
66
-
67
-
68
- def _get_rich_console():
69
- """Lazy import for rich.get_console"""
70
- if "get_console" not in _IMPORT_CACHE:
71
- from rich import get_console
72
-
73
- _IMPORT_CACHE["get_console"] = get_console
74
- return _IMPORT_CACHE["get_console"]
75
-
76
-
77
- def _get_rich_console_classes():
78
- """Lazy import for rich.console classes"""
79
- if "console_classes" not in _IMPORT_CACHE:
80
- from rich.console import Console, RenderableType
81
-
82
- _IMPORT_CACHE["console_classes"] = (Console, RenderableType)
83
- return _IMPORT_CACHE["console_classes"]
84
-
85
-
86
- def _get_rich_prompts():
87
- """Lazy import for rich.prompt classes"""
88
- if "prompts" not in _IMPORT_CACHE:
89
- from rich.prompt import Prompt, Confirm
90
-
91
- _IMPORT_CACHE["prompts"] = (Prompt, Confirm)
92
- return _IMPORT_CACHE["prompts"]
93
-
94
-
95
- def _get_prompt_toolkit():
96
- """Lazy import for prompt_toolkit"""
97
- if "prompt_toolkit" not in _IMPORT_CACHE:
98
- from prompt_toolkit import prompt as pt_prompt
99
- from prompt_toolkit.completion import WordCompleter
100
-
101
- _IMPORT_CACHE["prompt_toolkit"] = (pt_prompt, WordCompleter)
102
- return _IMPORT_CACHE["prompt_toolkit"]
103
-
104
-
105
- def _get_style_utils():
106
- """Lazy import for style utilities"""
107
- if "style_utils" not in _IMPORT_CACHE:
108
- from .styles.utils import live_render, style_renderable
109
-
110
- _IMPORT_CACHE["style_utils"] = (live_render, style_renderable)
111
- return _IMPORT_CACHE["style_utils"]
112
-
113
-
114
- def _get_animation_classes():
115
- """Lazy import for animation classes"""
116
- if "animations" not in _IMPORT_CACHE:
117
- from .animations import (
118
- CLIFlashingAnimation,
119
- CLIPulsingAnimation,
120
- CLIShakingAnimation,
121
- CLITypingAnimation,
122
- CLISpinningAnimation,
123
- CLIRainbowAnimation,
124
- RainbowPreset,
125
- )
126
-
127
- _IMPORT_CACHE["animations"] = {
128
- "CLIFlashingAnimation": CLIFlashingAnimation,
129
- "CLIPulsingAnimation": CLIPulsingAnimation,
130
- "CLIShakingAnimation": CLIShakingAnimation,
131
- "CLITypingAnimation": CLITypingAnimation,
132
- "CLISpinningAnimation": CLISpinningAnimation,
133
- "CLIRainbowAnimation": CLIRainbowAnimation,
134
- "RainbowPreset": RainbowPreset,
135
- }
136
- return _IMPORT_CACHE["animations"]
137
-
138
-
139
- def print(
140
- *values: object,
141
- sep: str = " ",
142
- end: str = "\n",
143
- file: Optional[IO[str]] = None,
144
- flush: bool = False,
145
- style: "CLIStyleType | None" = None,
146
- style_settings: "CLIStyleRenderableSettings | None" = None,
147
- bg: "CLIStyleBackgroundType | None" = None,
148
- bg_settings: "CLIStyleBackgroundSettings | None" = None,
149
- justify: Optional["JustifyMethod"] = None,
150
- overflow: Optional["OverflowMethod"] = None,
151
- no_wrap: Optional[bool] = None,
152
- emoji: Optional[bool] = None,
153
- markup: Optional[bool] = None,
154
- highlight: Optional[bool] = None,
155
- width: Optional[int] = None,
156
- height: Optional[int] = None,
157
- border: Optional["CLIStyleBoxName"] = None,
158
- padding: Optional["PaddingDimensions"] = None,
159
- title: Optional[str] = None,
160
- expand: Optional[bool] = None,
161
- live: "CLIStyleLiveSettings | int | None" = None,
162
- duration: Optional[float] = None,
163
- transient: bool = False,
164
- new_line_start: bool = False,
165
- ) -> None:
166
- """
167
- Stylized print function built with `rich`. This method maintains
168
- all standard functionality of the print function, with no overhead
169
- unless the styled parameters are provided.
170
-
171
- Args:
172
- *values : The values to print.
173
- sep : The separator between values.
174
- end : The end character.
175
- file : The file to write to.
176
- flush : Whether to flush the file.
177
- style : A color or style name to apply to the content.
178
- style_settings : A dictionary of style settings to apply to the content.
179
- bg : A color or box name to apply to the background.
180
- bg_settings : A dictionary of background settings to apply to the content.
181
- justify : Text justification method ("left", "center", "right", "full").
182
- overflow : Text overflow method ("fold", "crop", "ellipsis", "ignore").
183
- no_wrap : Disable text wrapping.
184
- emoji : Enable/disable emoji rendering.
185
- markup : Enable/disable Rich markup rendering.
186
- highlight : Enable/disable automatic highlighting.
187
- width : Override the width of the output.
188
- height : Override the height of the output.
189
- border : Border style for panel rendering.
190
- padding : Padding dimensions for panel rendering.
191
- title : Title for panel rendering.
192
- expand : Whether to expand panel to full width.
193
- live : A dictionary of live settings or an integer in seconds to run the print in a live renderable.
194
- duration : The duration of the live renderable.
195
- transient : Whether to clear the output after completion.
196
- new_line_start : Start with a new line before printing.
197
-
198
- NOTE: If `live` is set as an integer, transient is True.
199
-
200
- Returns:
201
- None
202
-
203
- Raises:
204
- PrintError : If the renderable is not a RenderableType.
205
- """
206
-
207
- # If no styling parameters are provided, use built-in print to avoid rich's default styling
208
- if (
209
- style is None
210
- and style_settings is None
211
- and bg is None
212
- and bg_settings is None
213
- and live is None
214
- and justify is None
215
- and overflow is None
216
- and no_wrap is None
217
- and emoji is None
218
- and markup is None
219
- and highlight is None
220
- and width is None
221
- and height is None
222
- and border is None
223
- and padding is None
224
- and title is None
225
- and expand is None
226
- and not transient
227
- ):
228
- builtins.print(*values, sep=sep, end=end, file=file, flush=flush)
229
- return
230
-
231
- # Convert values to string for styling
232
- content = sep.join(str(value) for value in values)
233
-
234
- # Apply styling and background
235
- live_render, style_renderable = _get_style_utils()
236
- styled_content = style_renderable(
237
- content,
238
- style=style,
239
- style_settings=style_settings,
240
- bg=bg,
241
- bg_settings=bg_settings,
242
- border=border,
243
- padding=padding,
244
- title=title,
245
- expand=expand,
246
- )
247
-
248
- # Handle live rendering
249
- if live is not None:
250
- if isinstance(live, int):
251
- # If live is an integer, treat it as duration in seconds
252
- from .styles.settings import CLIStyleLiveSettings
253
-
254
- live_settings: CLIStyleLiveSettings = {
255
- "duration": float(live),
256
- "transient": transient, # Use the transient parameter
257
- }
258
- else:
259
- live_settings = live
260
-
261
- # For very short durations or testing, just print normally
262
- duration = live if isinstance(live, int) else live_settings.get("duration", 2.0)
263
- if duration <= 1:
264
- get_console = _get_rich_console()
265
- Console, _ = _get_rich_console_classes()
266
- console = get_console() if file is None else Console(file=file)
267
- console.print(
268
- styled_content,
269
- end=end,
270
- justify=justify,
271
- overflow=overflow,
272
- no_wrap=no_wrap,
273
- emoji=emoji,
274
- markup=markup,
275
- highlight=highlight,
276
- width=width,
277
- height=height,
278
- new_line_start=new_line_start,
279
- )
280
- else:
281
- live_render(styled_content, live_settings)
282
- else:
283
- # Regular print with styling
284
- get_console = _get_rich_console()
285
- Console, _ = _get_rich_console_classes()
286
- console = get_console() if file is None else Console(file=file)
287
-
288
- if transient:
289
- # Use Rich's Live with transient for temporary output
290
- import time
291
- from rich.live import Live
292
-
293
- # Auto-set duration to 2.5 when transient=True and duration is None
294
- display_duration = duration if duration is not None else 2.5
295
-
296
- with Live(
297
- styled_content,
298
- console=console,
299
- refresh_per_second=1,
300
- transient=True,
301
- auto_refresh=False,
302
- ) as live:
303
- live.update(styled_content)
304
- live.refresh()
305
- time.sleep(
306
- display_duration
307
- ) # Use duration parameter for transient content
308
- else:
309
- console.print(
310
- styled_content,
311
- end=end,
312
- justify=justify,
313
- overflow=overflow,
314
- no_wrap=no_wrap,
315
- emoji=emoji,
316
- markup=markup,
317
- highlight=highlight,
318
- width=width,
319
- height=height,
320
- new_line_start=new_line_start,
321
- )
322
-
323
-
324
- class InputError(Exception):
325
- """Exception raised for errors in the Input module."""
326
-
327
- def __init__(self, message: str) -> None:
328
- self.message = message
329
- super().__init__(self.message)
330
-
331
-
332
- def _validate_against_schema(value: str, schema: Any) -> Any:
333
- """Validate and convert input value against a schema.
334
-
335
- Args:
336
- value: The input value as a string.
337
- schema: The schema to validate against.
338
-
339
- Returns:
340
- The converted/validated value.
341
-
342
- Raises:
343
- InputError: If validation fails.
344
- """
345
- if schema is None:
346
- return value
347
-
348
- try:
349
- # Handle basic types
350
- if schema == str:
351
- return value
352
- elif schema == int:
353
- return int(value)
354
- elif schema == float:
355
- return float(value)
356
- elif schema == bool:
357
- return value.lower() in ("true", "t", "yes", "y", "1", "on")
358
-
359
- # Handle dict - expect JSON input
360
- elif schema == dict or (
361
- hasattr(schema, "__origin__") and schema.__origin__ is dict
362
- ):
363
- try:
364
- return json.loads(value)
365
- except json.JSONDecodeError:
366
- raise InputError(f"Invalid JSON format for dictionary input")
367
-
368
- # Handle list - expect JSON input
369
- elif schema == list or (
370
- hasattr(schema, "__origin__") and schema.__origin__ is list
371
- ):
372
- try:
373
- result = json.loads(value)
374
- if not isinstance(result, list):
375
- raise InputError("Expected a list")
376
- return result
377
- except json.JSONDecodeError:
378
- raise InputError(f"Invalid JSON format for list input")
379
-
380
- # Handle Union types (including Optional)
381
- elif hasattr(schema, "__origin__") and schema.__origin__ is Union:
382
- args = schema.__args__
383
- if len(args) == 2 and type(None) in args:
384
- # This is Optional[T]
385
- if not value or value.lower() == "none":
386
- return None
387
- non_none_type = args[0] if args[1] is type(None) else args[1]
388
- return _validate_against_schema(value, non_none_type)
389
-
390
- # Handle Pydantic models
391
- elif hasattr(schema, "model_validate_json"):
392
- try:
393
- return schema.model_validate_json(value)
394
- except Exception as e:
395
- raise InputError(f"Invalid input for {schema.__name__}: {e}")
396
-
397
- # Handle BasedModels
398
- elif hasattr(schema, "model_validate_json") or (
399
- hasattr(schema, "__bases__")
400
- and any("BasedModel" in str(base) for base in schema.__bases__)
401
- ):
402
- try:
403
- return schema.model_validate_json(value)
404
- except Exception as e:
405
- raise InputError(f"Invalid input for {schema.__name__}: {e}")
406
-
407
- # Handle dataclasses
408
- elif hasattr(schema, "__dataclass_fields__"):
409
- try:
410
- data = json.loads(value)
411
- return schema(**data)
412
- except Exception as e:
413
- raise InputError(f"Invalid input for {schema.__name__}: {e}")
414
-
415
- # Handle TypedDict
416
- elif hasattr(schema, "__annotations__") and hasattr(schema, "__total__"):
417
- try:
418
- return json.loads(value)
419
- except json.JSONDecodeError:
420
- raise InputError(f"Invalid JSON format for {schema.__name__}")
421
-
422
- # Fallback - try to parse as JSON
423
- else:
424
- try:
425
- return json.loads(value)
426
- except json.JSONDecodeError:
427
- return value
428
-
429
- except InputError:
430
- raise
431
- except Exception as e:
432
- raise InputError(f"Validation error: {e}")
433
-
434
-
435
- def _collect_fields_sequentially(schema: Any, console) -> Dict[str, Any]:
436
- """Collect field values sequentially for structured schemas.
437
-
438
- Args:
439
- schema: The schema to collect fields for.
440
- console: The console to use for output.
441
-
442
- Returns:
443
- Dictionary of field names to values.
444
- """
445
- result = {}
446
-
447
- try:
448
- # Handle Pydantic models
449
- if hasattr(schema, "model_fields"):
450
- fields_info = schema.model_fields
451
- console.print(
452
- f"\n[bold blue]Entering data for {schema.__name__}:[/bold blue]"
453
- )
454
-
455
- for field_name, field_info in fields_info.items():
456
- field_type = (
457
- field_info.annotation if hasattr(field_info, "annotation") else str
458
- )
459
- default = getattr(field_info, "default", None)
460
-
461
- prompt_text = f" {field_name}"
462
- if default is not None and default != "...":
463
- prompt_text += f" (default: {default})"
464
- prompt_text += ": "
465
-
466
- Prompt, _ = _get_rich_prompts()
467
- value = Prompt.ask(prompt_text)
468
- if not value and default is not None and default != "...":
469
- result[field_name] = default
470
- else:
471
- try:
472
- result[field_name] = _validate_against_schema(value, field_type)
473
- except InputError as e:
474
- console.print(f"[red]Error: {e}[/red]")
475
- result[field_name] = value
476
-
477
- # Handle BasedModels
478
- elif hasattr(schema, "_get_fields_info"):
479
- fields_info = schema._get_fields_info()
480
- console.print(
481
- f"\n[bold blue]Entering data for {schema.__name__}:[/bold blue]"
482
- )
483
-
484
- for field_name, field_info in fields_info.items():
485
- field_type = field_info.get("type", str)
486
- default = field_info.get("default")
487
- required = field_info.get("required", True)
488
-
489
- prompt_text = f" {field_name}"
490
- if not required and default is not None:
491
- prompt_text += f" (default: {default})"
492
- elif not required:
493
- prompt_text += " (optional)"
494
- prompt_text += ": "
495
-
496
- Prompt, _ = _get_rich_prompts()
497
- value = Prompt.ask(prompt_text)
498
- if not value and not required and default is not None:
499
- result[field_name] = default
500
- elif not value and not required:
501
- continue
502
- else:
503
- try:
504
- result[field_name] = _validate_against_schema(value, field_type)
505
- except InputError as e:
506
- console.print(f"[red]Error: {e}[/red]")
507
- result[field_name] = value
508
-
509
- # Handle dataclasses
510
- elif hasattr(schema, "__dataclass_fields__"):
511
- from ..typing import get_type_description
512
- import dataclasses
513
-
514
- fields_info = schema.__dataclass_fields__
515
- console.print(
516
- f"\n[bold blue]Entering data for {schema.__name__}:[/bold blue]"
517
- )
518
-
519
- for field_name, field_info in fields_info.items():
520
- field_type = field_info.type
521
- default = getattr(field_info, "default", None)
522
-
523
- prompt_text = f" {field_name}"
524
- if default is not None and default is not dataclasses.MISSING:
525
- prompt_text += f" (default: {default})"
526
- elif hasattr(field_type, "__name__"):
527
- prompt_text += f" ({field_type.__name__})"
528
- else:
529
- prompt_text += f" ({get_type_description(field_type)})"
530
- prompt_text += ""
531
-
532
- Prompt, _ = _get_rich_prompts()
533
- value = Prompt.ask(prompt_text)
534
- if (
535
- not value
536
- and default is not None
537
- and default is not dataclasses.MISSING
538
- ):
539
- result[field_name] = default
540
- else:
541
- try:
542
- result[field_name] = _validate_against_schema(value, field_type)
543
- except InputError as e:
544
- console.print(f"[red]Error: {e}[/red]")
545
- result[field_name] = value
546
-
547
- # Handle TypedDict
548
- elif hasattr(schema, "__annotations__"):
549
- annotations = getattr(schema, "__annotations__", {})
550
- console.print(
551
- f"\n[bold blue]Entering data for {schema.__name__}:[/bold blue]"
552
- )
553
-
554
- for field_name, field_type in annotations.items():
555
- prompt_text = f" {field_name}: "
556
- Prompt, _ = _get_rich_prompts()
557
- value = Prompt.ask(prompt_text)
558
-
559
- if value:
560
- try:
561
- result[field_name] = _validate_against_schema(value, field_type)
562
- except InputError as e:
563
- console.print(f"[red]Error: {e}[/red]")
564
- result[field_name] = value
565
-
566
- except Exception as e:
567
- console.print(f"[red]Error collecting fields: {e}[/red]")
568
-
569
- return result
570
-
571
-
572
- @overload
573
- def input(
574
- prompt: str = "",
575
- schema: Any = None,
576
- sequential: bool = True,
577
- style: "CLIStyleType | None" = None,
578
- style_settings: "CLIStyleRenderableSettings | None" = None,
579
- bg: "CLIStyleBackgroundType | None" = None,
580
- bg_settings: "CLIStyleBackgroundSettings | None" = None,
581
- multiline: bool = False,
582
- password: bool = False,
583
- complete: Optional[List[str]] = None,
584
- validate: Optional[callable] = None,
585
- ) -> Any: ...
586
-
587
-
588
- def input(
589
- prompt: str = "",
590
- schema: Any = None,
591
- sequential: bool = True,
592
- style: "CLIStyleType | None" = None,
593
- style_settings: "CLIStyleRenderableSettings | None" = None,
594
- bg: "CLIStyleBackgroundType | None" = None,
595
- bg_settings: "CLIStyleBackgroundSettings | None" = None,
596
- multiline: bool = False,
597
- password: bool = False,
598
- complete: Optional[List[str]] = None,
599
- validate: Optional[callable] = None,
600
- ) -> Any:
601
- """
602
- Stylized input function built with `rich` and `prompt_toolkit`. This method maintains
603
- compatibility with the standard input function while adding advanced features like
604
- schema validation, styling, and structured data input.
605
-
606
- Args:
607
- prompt: The prompt message to display.
608
- schema: A type, model class, or schema to validate against. Supports:
609
- - Basic types (str, int, float, bool)
610
- - Collections (dict, list)
611
- - Pydantic models
612
- - BasedModels
613
- - Dataclasses
614
- - TypedDict
615
- sequential: For schemas with multiple fields, request one field at a time.
616
- style: A color or dictionary of style settings to apply to the prompt.
617
- bg: A color or dictionary of background settings to apply to the prompt.
618
- multiline: Whether to allow multiline input.
619
- password: Whether to hide the input (password mode).
620
- complete: List of completion options.
621
- validate: Custom validation function.
622
-
623
- Returns:
624
- The validated input value, converted to the appropriate type based on schema.
625
-
626
- Raises:
627
- InputError: If validation fails or input is invalid.
628
- """
629
- get_console = _get_rich_console()
630
- console = get_console()
631
-
632
- try:
633
- # If no special features are requested, use built-in input for compatibility
634
- if (
635
- schema is None
636
- and style is None
637
- and style_settings is None
638
- and bg is None
639
- and bg_settings is None
640
- and not multiline
641
- and not password
642
- and complete is None
643
- and validate is None
644
- ):
645
- return builtins.input(prompt)
646
-
647
- # Apply styling to prompt if provided
648
- _, style_renderable = _get_style_utils()
649
- styled_prompt = style_renderable(
650
- prompt,
651
- style=style,
652
- style_settings=style_settings,
653
- bg=bg,
654
- bg_settings=bg_settings,
655
- )
656
-
657
- # Handle schema-based input
658
- if schema is not None:
659
- # Handle bool schema with Confirm.ask
660
- if schema == bool:
661
- Prompt, Confirm = _get_rich_prompts()
662
- return Confirm.ask(styled_prompt)
663
-
664
- # Handle structured schemas with multiple fields
665
- if sequential and (
666
- hasattr(schema, "__annotations__")
667
- or hasattr(schema, "model_fields")
668
- or hasattr(schema, "_get_fields_info")
669
- or hasattr(schema, "__dataclass_fields__")
670
- ):
671
- field_data = _collect_fields_sequentially(schema, console)
672
-
673
- try:
674
- # Create instance from collected data
675
- if hasattr(schema, "model_validate"):
676
- # Pydantic model
677
- return schema.model_validate(field_data)
678
- elif hasattr(schema, "__call__"):
679
- # BasedModel, dataclass, or other callable
680
- return schema(**field_data)
681
- else:
682
- # TypedDict or similar - return the dict
683
- return field_data
684
- except Exception as e:
685
- console.print(f"[red]Error creating {schema.__name__}: {e}[/red]")
686
- return field_data
687
-
688
- # Handle single value input
689
- Prompt, Confirm = _get_rich_prompts()
690
- if password:
691
- value = Prompt.ask(styled_prompt, password=True)
692
- elif complete:
693
- # Use prompt_toolkit for completion
694
- pt_prompt, WordCompleter = _get_prompt_toolkit()
695
- completer = WordCompleter(complete)
696
- value = pt_prompt(str(styled_prompt), completer=completer)
697
- elif multiline:
698
- console.print(styled_prompt, end="")
699
- lines = []
700
- console.print("[dim](Enter empty line to finish)[/dim]")
701
- pt_prompt, _ = _get_prompt_toolkit()
702
- while True:
703
- line = pt_prompt("... ")
704
- if not line:
705
- break
706
- lines.append(line)
707
- value = "\n".join(lines)
708
- else:
709
- # Regular input with Rich prompt
710
- value = Prompt.ask(styled_prompt)
711
-
712
- # Apply custom validation
713
- if validate:
714
- try:
715
- if not validate(value):
716
- raise InputError("Custom validation failed")
717
- except Exception as e:
718
- raise InputError(f"Validation error: {e}")
719
-
720
- # Apply schema validation
721
- if schema is not None:
722
- return _validate_against_schema(value, schema)
723
-
724
- return value
725
-
726
- except KeyboardInterrupt:
727
- console.print("\n[yellow]Input cancelled by user[/yellow]")
728
- raise
729
- except InputError:
730
- raise
731
- except Exception as e:
732
- raise InputError(f"Input error: {e}")
733
-
734
-
735
- def animate(
736
- renderable: "RenderableType | str",
737
- type: Literal[
738
- "flashing", "pulsing", "shaking", "typing", "spinning", "rainbow"
739
- ] = "pulsing",
740
- duration: Optional[float] = None,
741
- # Animation parameters (defaults are handled by the specific animation classes)
742
- speed: Optional[float] = None,
743
- colors: "Optional[List[CLIStyleColorName]]" = None,
744
- on_color: "Optional[CLIStyleColorName]" = None,
745
- off_color: "Optional[CLIStyleColorName]" = None,
746
- min_opacity: Optional[float] = None,
747
- max_opacity: Optional[float] = None,
748
- color: "Optional[CLIStyleColorName]" = None,
749
- intensity: Optional[int] = None,
750
- typing_speed: Optional[float] = None,
751
- cursor: Optional[str] = None,
752
- show_cursor: Optional[bool] = None,
753
- frames: Optional[List[str]] = None,
754
- prefix: Optional[bool] = None,
755
- # Rich.Live parameters
756
- refresh_rate: int = 20,
757
- transient: bool = True,
758
- auto_refresh: bool = True,
759
- console: Optional["Console"] = None,
760
- screen: bool = False,
761
- vertical_overflow: str = "ellipsis",
762
- ) -> None:
763
- """Create and run an animation based on the specified type.
764
-
765
- Args:
766
- renderable: The object to animate (text, panel, etc.)
767
- type: The type of animation to create
768
- duration: Duration of the animation in seconds (defaults to 2.0)
769
- speed: Animation speed (defaults to the specific animation class's default)
770
- colors: Color list (used by flashing, rainbow) (defaults to the specific animation class's default)
771
- on_color: Color when flashing "on" (used by flashing) (defaults to the specific animation class's default)
772
- off_color: Color when flashing "off" (used by flashing) (defaults to the specific animation class's default)
773
- min_opacity: Minimum opacity for pulsing animation (defaults to the specific animation class's default)
774
- max_opacity: Maximum opacity for pulsing animation (defaults to the specific animation class's default)
775
- color: Color for pulsing animation (defaults to the specific animation class's default)
776
- intensity: Shaking intensity for shaking animation (defaults to the specific animation class's default)
777
- typing_speed: Speed for typing animation (used by typing) (defaults to the specific animation class's default)
778
- cursor: Cursor character for typing animation (used by typing) (defaults to the specific animation class's default)
779
- show_cursor: Whether to show cursor for typing animation (used by typing) (defaults to the specific animation class's default)
780
- frames: Custom frames for spinning animation (defaults to the specific animation class's default)
781
- prefix: Whether to show spinner as prefix for spinning animation (defaults to the specific animation class's default)
782
- refresh_rate: Refresh rate per second for Live rendering
783
- transient: Whether to clear animation after completion
784
- auto_refresh: Whether to auto-refresh the display
785
- console: Console to use for rendering
786
- screen: Whether to use alternate screen buffer
787
- vertical_overflow: How to handle vertical overflow
788
-
789
- Examples:
790
- >>> animate("Hello!", type="flashing", duration=3.0, speed=0.3)
791
- >>> animate(Panel("Loading"), type="pulsing", min_opacity=0.1)
792
- >>> animate("Hello World!", type="typing", typing_speed=0.1)
793
- >>> animate("Colorful!", type="rainbow", colors=["red", "blue"])
794
- """
795
- animations = _get_animation_classes()
796
-
797
- if type == "flashing":
798
- animation = animations["CLIFlashingAnimation"](
799
- renderable,
800
- speed=speed if speed is not None else 0.5,
801
- colors=colors, # Class handles default if None
802
- on_color=on_color if on_color is not None else "white",
803
- off_color=off_color if off_color is not None else "dim white",
804
- duration=duration, # Base class handles default if None
805
- )
806
- elif type == "pulsing":
807
- animation = animations["CLIPulsingAnimation"](
808
- renderable,
809
- speed=speed if speed is not None else 2.0,
810
- min_opacity=min_opacity if min_opacity is not None else 0.3,
811
- max_opacity=max_opacity if max_opacity is not None else 1.0,
812
- color=color if color is not None else "white",
813
- duration=duration, # Base class handles default if None
814
- )
815
- elif type == "shaking":
816
- animation = animations["CLIShakingAnimation"](
817
- renderable,
818
- intensity=intensity if intensity is not None else 1,
819
- speed=speed if speed is not None else 0.1,
820
- duration=duration, # Base class handles default if None
821
- )
822
- elif type == "typing":
823
- # Note: CLITypingAnimation expects 'text', assuming renderable is a string here.
824
- animation = animations[
825
- "CLITypingAnimation"
826
- ](
827
- renderable,
828
- speed=speed
829
- if speed is not None
830
- else 0.05, # Pass animate's speed, using CLITypingAnimation's speed default
831
- typing_speed=typing_speed, # Pass animate's typing_speed, CLITypingAnimation handles its None default
832
- cursor=cursor if cursor is not None else "█",
833
- show_cursor=show_cursor if show_cursor is not None else True,
834
- duration=duration, # Base class handles default if None
835
- )
836
- elif type == "spinning":
837
- animation = animations["CLISpinningAnimation"](
838
- renderable,
839
- frames=frames, # Class handles default if None
840
- speed=speed if speed is not None else 0.1,
841
- prefix=prefix if prefix is not None else True,
842
- duration=duration, # Base class handles default if None
843
- )
844
- elif type == "rainbow":
845
- animation = animations["CLIRainbowAnimation"](
846
- renderable,
847
- speed=speed if speed is not None else 0.5,
848
- colors=colors, # Class handles default if None
849
- duration=duration, # Base class handles default if None
850
- )
851
- else:
852
- raise ValueError(f"Unknown animation type: {type}")
853
-
854
- # The animation object's animate method handles its own duration default
855
- # and the Live parameters are passed directly from the function args
856
- animation.animate(
857
- duration=duration,
858
- refresh_rate=refresh_rate,
859
- transient=transient,
860
- auto_refresh=auto_refresh,
861
- console=console,
862
- screen=screen,
863
- vertical_overflow=vertical_overflow,
864
- )
865
-
866
-
867
- __all__ = ("print", "input", "animate")