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/based/utils.py ADDED
@@ -0,0 +1,455 @@
1
+ """hammad.based.utils"""
2
+
3
+ from functools import lru_cache
4
+ from typing import Any, Callable, Optional, Pattern, Union, Tuple, Dict, TYPE_CHECKING
5
+ import ast
6
+ import inspect
7
+
8
+ from msgspec.structs import Struct
9
+
10
+ from .fields import BasedFieldInfo, basedfield, BasedField
11
+ from .model import BasedModel
12
+
13
+ __all__ = (
14
+ "create_basedmodel",
15
+ "get_field_info",
16
+ "is_basedfield",
17
+ "is_basedmodel",
18
+ "based_validator",
19
+ "create_lazy_loader",
20
+ "auto_create_lazy_loader",
21
+ "install",
22
+ )
23
+
24
+
25
+ def create_basedmodel(
26
+ __model_name: str,
27
+ *,
28
+ __base__: Optional[Union[type, Tuple[type, ...]]] = None,
29
+ __module__: Optional[str] = None,
30
+ __qualname__: Optional[str] = None,
31
+ __doc__: Optional[str] = None,
32
+ __validators__: Optional[Dict[str, Any]] = None,
33
+ __config__: Optional[type] = None,
34
+ **field_definitions: Any,
35
+ ) -> type[BasedModel]:
36
+ """Create a BasedModel dynamically with Pydantic-compatible interface.
37
+
38
+ This function provides a drop-in replacement for pydantic.create_model()
39
+ that creates BasedModel classes instead of pydantic BaseModel classes.
40
+
41
+ Args:
42
+ __model_name: Name of the model class to create
43
+ __base__: Base class(es) to inherit from. If None, uses BasedModel.
44
+ Can be a single class or tuple of classes.
45
+ __module__: Module name for the created class
46
+ __qualname__: Qualified name for the created class
47
+ __doc__: Docstring for the created class
48
+ __validators__: Dictionary of validators (for compatibility - not fully implemented)
49
+ __config__: Configuration class (for compatibility - not fully implemented)
50
+ **field_definitions: Field definitions as keyword arguments.
51
+ Each can be:
52
+ - A type annotation (e.g., str, int)
53
+ - A tuple of (type, default_value)
54
+ - A tuple of (type, Field(...))
55
+ - A Field instance
56
+
57
+ Returns:
58
+ A new BasedModel class with the specified fields
59
+
60
+ Examples:
61
+ # Simple model with basic types
62
+ User = create_basedmodel('User', name=str, age=int)
63
+
64
+ # Model with defaults
65
+ Config = create_basedmodel('Config',
66
+ host=(str, 'localhost'),
67
+ port=(int, 8080))
68
+
69
+ # Model with field constraints
70
+ Product = create_basedmodel('Product',
71
+ name=str,
72
+ price=(float, basedfield(gt=0)),
73
+ tags=(List[str], basedfield(default_factory=list)))
74
+
75
+ # Model with custom base class
76
+ class BaseEntity(BasedModel):
77
+ id: int
78
+ created_at: str
79
+
80
+ User = create_basedmodel('User',
81
+ name=str,
82
+ email=str,
83
+ __base__=BaseEntity)
84
+ """
85
+ # Handle base class specification
86
+ if __base__ is not None and __config__ is not None:
87
+ raise ValueError(
88
+ "Cannot specify both '__base__' and '__config__' - "
89
+ "use a base class with the desired configuration instead"
90
+ )
91
+
92
+ # Determine base classes
93
+ if __base__ is None:
94
+ bases = (BasedModel,)
95
+ elif isinstance(__base__, tuple):
96
+ # Ensure all bases are compatible
97
+ for base in __base__:
98
+ if not (issubclass(base, BasedModel) or issubclass(base, Struct)):
99
+ raise ValueError(
100
+ f"Base class {base} must be a subclass of BasedModel or msgspec.Struct"
101
+ )
102
+ bases = __base__
103
+ else:
104
+ if not (issubclass(__base__, BasedModel) or issubclass(__base__, Struct)):
105
+ raise ValueError(
106
+ f"Base class {__base__} must be a subclass of BasedModel or msgspec.Struct"
107
+ )
108
+ bases = (__base__,)
109
+
110
+ # Build class dictionary
111
+ class_dict = {}
112
+ annotations = {}
113
+
114
+ # Set metadata
115
+ if __doc__ is not None:
116
+ class_dict["__doc__"] = __doc__
117
+ if __module__ is not None:
118
+ class_dict["__module__"] = __module__
119
+ if __qualname__ is not None:
120
+ class_dict["__qualname__"] = __qualname__
121
+
122
+ # Process field definitions in two passes to ensure proper ordering
123
+ # First pass: collect required and optional fields separately
124
+ required_fields = {}
125
+ optional_fields = {}
126
+
127
+ for field_name, field_definition in field_definitions.items():
128
+ if field_name.startswith("__") and field_name.endswith("__"):
129
+ # Skip special attributes that were passed as field definitions
130
+ continue
131
+
132
+ # Parse field definition
133
+ is_optional = False
134
+
135
+ if isinstance(field_definition, tuple):
136
+ if len(field_definition) == 2:
137
+ field_type, field_value = field_definition
138
+ annotations[field_name] = field_type
139
+
140
+ # Check if field_value is a Field instance or basedfield
141
+ if hasattr(field_value, "__class__") and (
142
+ "field" in field_value.__class__.__name__.lower()
143
+ or hasattr(field_value, "default")
144
+ or callable(getattr(field_value, "__call__", None))
145
+ ):
146
+ # It's a field descriptor
147
+ optional_fields[field_name] = field_value
148
+ else:
149
+ # It's a default value - create a basedfield with this default
150
+ optional_fields[field_name] = basedfield(default=field_value)
151
+ is_optional = True
152
+ else:
153
+ raise ValueError(
154
+ f"Field definition for '{field_name}' must be a 2-tuple of (type, default/Field)"
155
+ )
156
+ elif hasattr(field_definition, "__origin__") or hasattr(
157
+ field_definition, "__class__"
158
+ ):
159
+ # It's a type annotation (like str, int, List[str], etc.) - required field
160
+ annotations[field_name] = field_definition
161
+ required_fields[field_name] = None
162
+ else:
163
+ # It's likely a default value without type annotation
164
+ # We'll infer the type from the value
165
+ annotations[field_name] = type(field_definition)
166
+ optional_fields[field_name] = basedfield(default=field_definition)
167
+ is_optional = True
168
+
169
+ # Second pass: add fields in correct order (required first, then optional)
170
+ # This ensures msgspec field ordering requirements are met
171
+ for field_name, field_value in required_fields.items():
172
+ if field_value is not None:
173
+ class_dict[field_name] = field_value
174
+
175
+ for field_name, field_value in optional_fields.items():
176
+ class_dict[field_name] = field_value
177
+
178
+ # Set annotations in proper order (required fields first, then optional)
179
+ ordered_annotations = {}
180
+
181
+ # Add required field annotations first
182
+ for field_name in required_fields:
183
+ if field_name in annotations:
184
+ ordered_annotations[field_name] = annotations[field_name]
185
+
186
+ # Add optional field annotations second
187
+ for field_name in optional_fields:
188
+ if field_name in annotations:
189
+ ordered_annotations[field_name] = annotations[field_name]
190
+
191
+ class_dict["__annotations__"] = ordered_annotations
192
+
193
+ # Handle validators (basic implementation for compatibility)
194
+ if __validators__:
195
+ # Store validators for potential future use
196
+ class_dict["_based_validators"] = __validators__
197
+ # Note: Full validator implementation would require more complex integration
198
+
199
+ # Create the dynamic class
200
+ try:
201
+ DynamicModel = type(__model_name, bases, class_dict)
202
+ except Exception as e:
203
+ raise ValueError(f"Failed to create model '{__model_name}': {e}") from e
204
+
205
+ return DynamicModel
206
+
207
+
208
+ @lru_cache(maxsize=None)
209
+ def get_field_info(field: Any) -> Optional[BasedFieldInfo]:
210
+ """Extract FieldInfo from a field descriptor with caching."""
211
+ if isinstance(field, tuple) and len(field) == 2:
212
+ _, field_info = field
213
+ if isinstance(field_info, BasedFieldInfo):
214
+ return field_info
215
+ elif hasattr(field, "_basedfield_info"):
216
+ return field._basedfield_info
217
+ elif hasattr(field, "field_info"):
218
+ return field.field_info
219
+ elif isinstance(field, BasedField):
220
+ return field.field_info
221
+ elif hasattr(field, "__class__") and field.__class__.__name__ == "FieldDescriptor":
222
+ return field.field_info
223
+ return None
224
+
225
+
226
+ def is_basedfield(field: Any) -> bool:
227
+ """Check if a field is a basedfield."""
228
+ return get_field_info(field) is not None
229
+
230
+
231
+ def is_basedmodel(model: Any) -> bool:
232
+ """Check if a model is a basedmodel."""
233
+ # Check if it's an instance of BasedModel
234
+ if isinstance(model, BasedModel):
235
+ return True
236
+
237
+ # Check if it's a BasedModel class (not instance)
238
+ if isinstance(model, type) and issubclass(model, BasedModel):
239
+ return True
240
+
241
+ # Check for BasedModel characteristics using duck typing
242
+ # Look for key BasedModel/msgspec.Struct attributes and methods
243
+ if hasattr(model, "__struct_fields__") and hasattr(model, "model_dump"):
244
+ # Check for BasedModel-specific methods
245
+ if (
246
+ hasattr(model, "model_copy")
247
+ and hasattr(model, "model_validate")
248
+ and hasattr(model, "model_to_pydantic")
249
+ ):
250
+ return True
251
+
252
+ # Check if it's an instance of any msgspec Struct with BasedModel methods
253
+ try:
254
+ if isinstance(model, Struct) and hasattr(model, "model_dump"):
255
+ return True
256
+ except ImportError:
257
+ pass
258
+
259
+ return False
260
+
261
+
262
+ def based_validator(
263
+ *fields: str, pre: bool = False, post: bool = False, always: bool = False
264
+ ):
265
+ """Decorator to create a validator for specific fields.
266
+
267
+ Args:
268
+ *fields: Field names to validate
269
+ pre: Whether this is a pre-validator
270
+ post: Whether this is a post-validator
271
+ always: Whether to run even if the value is not set
272
+
273
+ Returns:
274
+ Decorator function
275
+ """
276
+
277
+ def decorator(func: Callable) -> Callable:
278
+ func._validator_fields = fields
279
+ func._validator_pre = pre
280
+ func._validator_post = post
281
+ func._validator_always = always
282
+ return func
283
+
284
+ return decorator
285
+
286
+
287
+ def create_lazy_loader(
288
+ imports_dict: dict[str, str], package: str
289
+ ) -> Callable[[str], Any]:
290
+ """Create a lazy loader function for __getattr__.
291
+
292
+ Args:
293
+ imports_dict: Dictionary mapping attribute names to their module paths
294
+ package: The package name for import_module
295
+
296
+ Returns:
297
+ A __getattr__ function that lazily imports modules
298
+ """
299
+
300
+ def __getattr__(name: str) -> Any:
301
+ if name in imports_dict:
302
+ from importlib import import_module
303
+
304
+ module_path, original_name = imports_dict[name]
305
+ module = import_module(module_path, package)
306
+ return getattr(module, original_name)
307
+ raise AttributeError(f"module '{package}' has no attribute '{name}'")
308
+
309
+ return __getattr__
310
+
311
+
312
+ def auto_create_lazy_loader(all_exports: tuple[str, ...]) -> Callable[[str], Any]:
313
+ """Automatically create a lazy loader by inspecting the calling module.
314
+
315
+ This function inspects the calling module's source code to extract
316
+ TYPE_CHECKING imports and automatically builds the import map.
317
+
318
+ Args:
319
+ all_exports: The __all__ tuple from the calling module
320
+
321
+ Returns:
322
+ A __getattr__ function that lazily imports modules
323
+ """
324
+ # Get the calling module's frame
325
+ frame = inspect.currentframe()
326
+ if frame is None or frame.f_back is None:
327
+ raise RuntimeError("Cannot determine calling module")
328
+
329
+ calling_frame = frame.f_back
330
+ module_name = calling_frame.f_globals.get("__name__", "")
331
+ package = calling_frame.f_globals.get("__package__", "")
332
+ filename = calling_frame.f_globals.get("__file__", "")
333
+
334
+ # Read the source file
335
+ try:
336
+ with open(filename, "r") as f:
337
+ source_code = f.read()
338
+ except (IOError, OSError):
339
+ # Fallback: try to get source from the module
340
+ import sys
341
+
342
+ module = sys.modules.get(module_name)
343
+ if module:
344
+ source_code = inspect.getsource(module)
345
+ else:
346
+ raise RuntimeError(f"Cannot read source for module {module_name}")
347
+
348
+ # Parse the source to extract TYPE_CHECKING imports
349
+ imports_map = _parse_type_checking_imports(source_code)
350
+
351
+ # Filter to only include exports that are in __all__
352
+ filtered_map = {
353
+ name: path for name, path in imports_map.items() if name in all_exports
354
+ }
355
+
356
+ return create_lazy_loader(filtered_map, package)
357
+
358
+
359
+ def _parse_type_checking_imports(source_code: str) -> dict[str, tuple[str, str]]:
360
+ """Parse TYPE_CHECKING imports from source code to build import map.
361
+
362
+ Args:
363
+ source_code: The source code containing TYPE_CHECKING imports
364
+
365
+ Returns:
366
+ Dictionary mapping local names to (module_path, original_name) tuples
367
+ """
368
+ tree = ast.parse(source_code)
369
+ imports_map = {}
370
+
371
+ class TypeCheckingVisitor(ast.NodeVisitor):
372
+ def __init__(self):
373
+ self.in_type_checking = False
374
+ self.imports = {}
375
+
376
+ def visit_If(self, node):
377
+ # Check if this is a TYPE_CHECKING block
378
+ is_type_checking = False
379
+
380
+ if isinstance(node.test, ast.Name) and node.test.id == "TYPE_CHECKING":
381
+ is_type_checking = True
382
+ elif isinstance(node.test, ast.Attribute):
383
+ if (
384
+ isinstance(node.test.value, ast.Name)
385
+ and node.test.value.id == "typing"
386
+ and node.test.attr == "TYPE_CHECKING"
387
+ ):
388
+ is_type_checking = True
389
+
390
+ if is_type_checking:
391
+ old_state = self.in_type_checking
392
+ self.in_type_checking = True
393
+ for stmt in node.body:
394
+ self.visit(stmt)
395
+ self.in_type_checking = old_state
396
+ else:
397
+ self.generic_visit(node)
398
+
399
+ def visit_ImportFrom(self, node):
400
+ if self.in_type_checking and node.module:
401
+ module_path = f".{node.module}"
402
+ for alias in node.names:
403
+ original_name = alias.name
404
+ local_name = alias.asname or original_name
405
+ self.imports[local_name] = (module_path, original_name)
406
+ self.generic_visit(node)
407
+
408
+ visitor = TypeCheckingVisitor()
409
+ visitor.visit(tree)
410
+
411
+ return visitor.imports
412
+
413
+
414
+ def install(
415
+ traceback: bool = True,
416
+ uvloop: bool = True,
417
+ print: bool = False,
418
+ input: bool = False,
419
+ ):
420
+ """Installs various 'opinionated' and optional resources that help
421
+ enhance the development experience, or increase performance.
422
+
423
+ Args:
424
+ traceback (bool): Whether to install the rich traceback handler.
425
+ uvloop (bool): Whether to install uvloop.
426
+ print (bool): Whether to install the stylized `print` method as a builtin
427
+ from `hammad.cli`. NOTE: IDEs will not recognize this change statically.
428
+ input (bool): Whether to install the stylized `input` method as a builtin
429
+ from `hammad.cli`. NOTE: IDEs will not recognize this change statically.
430
+
431
+ Note:
432
+ IDE limitations: Static analysis tools cannot detect runtime builtin modifications.
433
+ The IDE will still show the original builtin definitions. Consider using explicit
434
+ imports for better IDE support:
435
+
436
+ from hammad.cli.plugins import print, input
437
+ """
438
+ import builtins
439
+
440
+ if traceback:
441
+ from rich.traceback import install
442
+
443
+ install()
444
+ if uvloop:
445
+ from uvloop import install
446
+
447
+ install()
448
+ if print:
449
+ from hammad.cli.plugins import print as print_fn
450
+
451
+ builtins.print = print_fn
452
+ if input:
453
+ from hammad.cli.plugins import input as input_fn
454
+
455
+ builtins.input = input_fn
@@ -0,0 +1,30 @@
1
+ """hammad.cache"""
2
+
3
+ from typing import TYPE_CHECKING
4
+ from ..based.utils import auto_create_lazy_loader
5
+
6
+ if TYPE_CHECKING:
7
+ from ._cache import (
8
+ TTLCache,
9
+ DiskCache,
10
+ CacheParams,
11
+ CacheReturn,
12
+ cached,
13
+ auto_cached,
14
+ create_cache,
15
+ )
16
+
17
+
18
+ __all__ = (
19
+ "auto_cached",
20
+ "cached",
21
+ "create_cache",
22
+ )
23
+
24
+
25
+ __getattr__ = auto_create_lazy_loader(__all__)
26
+
27
+
28
+ def __dir__() -> list[str]:
29
+ """Get the attributes of the cache module."""
30
+ return list(__all__)
@@ -1,4 +1,4 @@
1
- """✼ agentspace.cache
1
+ """hammad.cache._cache
2
2
 
3
3
  Contains helpful resources for creating simple cache systems, and
4
4
  decorators that implement "automatic" hashing & caching of function calls.
@@ -21,13 +21,13 @@ from typing import (
21
21
  TypeVar,
22
22
  Tuple,
23
23
  Optional,
24
- Protocol,
25
24
  overload,
26
25
  ParamSpec,
27
26
  Literal,
28
27
  get_args,
29
28
  TypeAlias,
30
29
  Union,
30
+ overload,
31
31
  )
32
32
 
33
33
  __all__ = [
@@ -39,6 +39,7 @@ __all__ = [
39
39
  "CacheType",
40
40
  "CacheParams",
41
41
  "CacheReturn",
42
+ "create_cache",
42
43
  ]
43
44
 
44
45
 
@@ -47,7 +48,7 @@ __all__ = [
47
48
  # -----------------------------------------------------------------------------
48
49
 
49
50
  CacheType: TypeAlias = Literal["ttl", "disk"]
50
- """Type of caches that can be created using `agentspace`.
51
+ """Type of caches that can be created using `hammad`.
51
52
 
52
53
  - `"ttl"`: Time-to-live cache.
53
54
  - `"disk"`: Disk-based cache.
@@ -65,16 +66,16 @@ CacheReturn = TypeVar("CacheReturn")
65
66
  # -----------------------------------------------------------------------------
66
67
 
67
68
 
68
- _agentspace_CACHE: None | BaseCache = None
69
- """Internal cache for the `agentspace` package. Instantiated when needed."""
69
+ _hammad_CACHE: None | BaseCache = None
70
+ """Internal cache for the `hammad` package. Instantiated when needed."""
70
71
 
71
72
 
72
73
  def _get_cache() -> BaseCache:
73
74
  """Returns the global cache instance, creating it if necessary."""
74
- global _agentspace_CACHE
75
- if _agentspace_CACHE is None:
76
- _agentspace_CACHE = TTLCache(maxsize=1000, ttl=3600)
77
- return _agentspace_CACHE
75
+ global _hammad_CACHE
76
+ if _hammad_CACHE is None:
77
+ _hammad_CACHE = TTLCache(maxsize=1000, ttl=3600)
78
+ return _hammad_CACHE
78
79
 
79
80
 
80
81
  # -----------------------------------------------------------------------------
@@ -84,7 +85,7 @@ def _get_cache() -> BaseCache:
84
85
 
85
86
  @dataclass
86
87
  class BaseCache:
87
- """Base class for all caches created using `agentspace`."""
88
+ """Base class for all caches created using `hammad`."""
88
89
 
89
90
  type: CacheType
90
91
  """Type of cache."""
@@ -491,7 +492,7 @@ def cached(
491
492
  Flexible caching decorator that preserves type hints and signatures.
492
493
 
493
494
  Can be used with or without arguments:
494
- - `@cached`: Uses automatic key generation with the global `agentspace.runtime.cache.CACHE`.
495
+ - `@cached`: Uses automatic key generation with the global `hammad.cache.CACHE`.
495
496
  - `@cached(key=custom_key_func)`: Uses a custom key generation function.
496
497
  - `@cached(ttl=300, maxsize=50)`: Creates a new `TTLCache` instance specifically
497
498
  for the decorated function with the given TTL and maxsize.
@@ -615,7 +616,7 @@ def auto_cached(
615
616
 
616
617
  Example:
617
618
  ```python
618
- from agentspace.runtime.cache import auto_cached, create_cache
619
+ from hammad.cache import auto_cached, create_cache
619
620
 
620
621
  # Example of using a custom cache instance
621
622
  my_user_cache = create_cache(cache_type="ttl", ttl=600, maxsize=50)
@@ -673,3 +674,73 @@ def auto_cached(
673
674
  return configured_cached_decorator(func_to_decorate)
674
675
 
675
676
  return actual_decorator
677
+
678
+
679
+ # -----------------------------------------------------------------------------
680
+ # CACHE FACTORY
681
+ # -----------------------------------------------------------------------------
682
+
683
+
684
+ @overload
685
+ def create_cache(
686
+ cache_type: Literal["ttl"], *, maxsize: int = 128, ttl: Optional[float] = None
687
+ ) -> TTLCache: ...
688
+
689
+
690
+ @overload
691
+ def create_cache(
692
+ cache_type: Literal["disk"],
693
+ *,
694
+ cache_dir: Optional[Union[str, Path]] = None,
695
+ maxsize: int = 128,
696
+ ) -> DiskCache: ...
697
+
698
+
699
+ @overload
700
+ def create_cache(cache_type: CacheType, **kwargs: Any) -> BaseCache: ...
701
+
702
+
703
+ def create_cache(cache_type: CacheType, **kwargs: Any) -> BaseCache:
704
+ """
705
+ Factory function to create cache instances of different types.
706
+
707
+ Args:
708
+ cache_type: The type of cache to create. Can be "ttl" or "disk".
709
+ **kwargs: Additional keyword arguments specific to the cache type.
710
+
711
+ Returns:
712
+ A cache instance of the specified type.
713
+
714
+ Raises:
715
+ ValueError: If an unsupported cache type is provided.
716
+
717
+ Examples:
718
+ ```python
719
+ # Create a TTL cache with custom settings
720
+ ttl_cache = create_cache("ttl", maxsize=256, ttl=300)
721
+
722
+ # Create a disk cache with custom directory
723
+ disk_cache = create_cache("disk", cache_dir="/tmp/my_cache", maxsize=1000)
724
+ ```
725
+ """
726
+ if cache_type == "ttl":
727
+ maxsize = kwargs.pop("maxsize", 128)
728
+ ttl = kwargs.pop("ttl", None)
729
+ if kwargs:
730
+ raise TypeError(
731
+ f"Unexpected keyword arguments for TTL cache: {list(kwargs.keys())}"
732
+ )
733
+ return TTLCache(maxsize=maxsize, ttl=ttl)
734
+ elif cache_type == "disk":
735
+ cache_dir = kwargs.pop("cache_dir", None)
736
+ maxsize = kwargs.pop("maxsize", 128)
737
+ if kwargs:
738
+ raise TypeError(
739
+ f"Unexpected keyword arguments for disk cache: {list(kwargs.keys())}"
740
+ )
741
+ return DiskCache(cache_dir=cache_dir, maxsize=maxsize)
742
+ else:
743
+ valid_types = get_args(CacheType)
744
+ raise ValueError(
745
+ f"Unsupported cache type: {cache_type}. Valid types are: {valid_types}"
746
+ )
hammad/cli/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """hammad.cli
2
+
3
+ Contains resources for styling rendered CLI content as well
4
+ as extensions / utilities for creating CLI interfaces."""
5
+
6
+ from typing import TYPE_CHECKING
7
+ from ..based.utils import auto_create_lazy_loader
8
+
9
+ if TYPE_CHECKING:
10
+ from .plugins import print, input, animate
11
+
12
+
13
+ __all__ = (
14
+ "print",
15
+ "input",
16
+ "animate",
17
+ )
18
+
19
+
20
+ __getattr__ = auto_create_lazy_loader(__all__)
21
+
22
+
23
+ def __dir__() -> list[str]:
24
+ """Get the attributes of the plugins module."""
25
+ return list(__all__)