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
@@ -1,576 +0,0 @@
1
- """hammad.data.sql.database"""
2
-
3
- from datetime import datetime, timezone, timedelta
4
- from pathlib import Path
5
- from typing import (
6
- Any,
7
- Dict,
8
- Generic,
9
- List,
10
- Optional,
11
- Type,
12
- Union,
13
- Literal,
14
- final,
15
- )
16
- import uuid
17
- import json
18
-
19
- try:
20
- from sqlalchemy import (
21
- create_engine,
22
- Column,
23
- String,
24
- Text,
25
- DateTime,
26
- Integer,
27
- MetaData,
28
- Table,
29
- and_,
30
- or_,
31
- select,
32
- insert,
33
- update,
34
- delete,
35
- Engine,
36
- )
37
- from sqlalchemy.orm import sessionmaker, Session
38
- from sqlalchemy.sql import Select
39
-
40
- SQLALCHEMY_AVAILABLE = True
41
- except ImportError:
42
- # SQLAlchemy not available
43
- SQLALCHEMY_AVAILABLE = False
44
- create_engine = None
45
- Engine = None
46
- Session = None
47
-
48
- from .types import (
49
- DatabaseItemType,
50
- DatabaseItemFilters,
51
- DatabaseItem,
52
- QueryOperator,
53
- QueryCondition,
54
- QueryFilter,
55
- )
56
-
57
- __all__ = [
58
- "create_database",
59
- "Database",
60
- "DatabaseError",
61
- ]
62
-
63
-
64
- class DatabaseError(Exception):
65
- """Exception raised when an error occurs in the Database."""
66
-
67
-
68
- @final
69
- class Database(Generic[DatabaseItemType]):
70
- """
71
- A clean SQL-based database implementation using SQLAlchemy that provides
72
- the lowest-level storage backend for collections.
73
-
74
- Features:
75
- - Optional schema validation
76
- - Custom path format support (memory or file-based)
77
- - Pythonic query interface with type-safe operators
78
- - TTL support with automatic cleanup
79
- - JSON serialization for complex objects
80
- - Transaction support
81
- """
82
-
83
- def __init__(
84
- self,
85
- *,
86
- name: str = "default",
87
- schema: Optional[Type[DatabaseItemType]] = None,
88
- ttl: Optional[int] = None,
89
- path: Optional[Union[Path, str]] = None,
90
- table_name: str = "items",
91
- auto_cleanup_expired: bool = True,
92
- ) -> None:
93
- """
94
- Initialize a new Database instance.
95
-
96
- Args:
97
- name: The name of the database
98
- schema: Optional schema type for validation
99
- ttl: Default time-to-live in seconds
100
- path: File path for persistent storage (None = in-memory)
101
- table_name: Name of the primary table
102
- auto_cleanup_expired: Whether to automatically clean up expired items
103
- """
104
- if not SQLALCHEMY_AVAILABLE:
105
- raise DatabaseError(
106
- "SQLAlchemy is required for Database. "
107
- "Install with: pip install sqlalchemy"
108
- )
109
-
110
- self.name = name
111
- self.schema = schema
112
- self.ttl = ttl
113
- self.path = Path(path) if path else None
114
- self.table_name = table_name
115
- self.auto_cleanup_expired = auto_cleanup_expired
116
-
117
- # Initialize SQLAlchemy components
118
- self._engine: Optional[Engine] = None
119
- self._session_factory = None
120
- self._metadata: Optional[MetaData] = None
121
- self._table: Optional[Table] = None
122
-
123
- self._init_database()
124
-
125
- def _init_database(self) -> None:
126
- """Initialize the database engine and create tables."""
127
- # Determine connection string
128
- if self.path is None:
129
- # In-memory database
130
- connection_string = "sqlite:///:memory:"
131
- else:
132
- # File-based database
133
- # Create directory if it doesn't exist
134
- if self.path.parent != Path("."):
135
- self.path.parent.mkdir(parents=True, exist_ok=True)
136
- connection_string = f"sqlite:///{self.path}"
137
-
138
- # Create engine
139
- self._engine = create_engine(
140
- connection_string,
141
- echo=False,
142
- pool_pre_ping=True,
143
- )
144
-
145
- # Create session factory
146
- self._session_factory = sessionmaker(bind=self._engine)
147
-
148
- # Create metadata and table
149
- self._metadata = MetaData()
150
- self._create_table()
151
-
152
- def _create_table(self) -> None:
153
- """Create the main table for storing items."""
154
- self._table = Table(
155
- self.table_name,
156
- self._metadata,
157
- Column("id", String, primary_key=True),
158
- Column("item_data", Text, nullable=False), # JSON serialized item
159
- Column("filters", Text), # JSON serialized filters
160
- Column("created_at", DateTime, nullable=False),
161
- Column("updated_at", DateTime, nullable=False),
162
- Column("ttl", Integer), # TTL in seconds
163
- Column("table_name", String, default=self.table_name),
164
- )
165
-
166
- # Create all tables
167
- self._metadata.create_all(self._engine)
168
-
169
- def _serialize_item(self, item: DatabaseItemType) -> str:
170
- """Serialize an item to JSON string."""
171
- from dataclasses import is_dataclass, asdict
172
-
173
- if isinstance(item, (str, int, float, bool, type(None))):
174
- return json.dumps(item)
175
- elif isinstance(item, (list, dict)):
176
- return json.dumps(item)
177
- elif is_dataclass(item):
178
- return json.dumps(asdict(item))
179
- elif hasattr(item, "__dict__"):
180
- return json.dumps(item.__dict__)
181
- else:
182
- return json.dumps(str(item))
183
-
184
- def _deserialize_item(self, data: str) -> DatabaseItemType:
185
- """Deserialize an item from JSON string."""
186
- return json.loads(data)
187
-
188
- def _validate_schema(self, item: DatabaseItemType) -> None:
189
- """Validate item against schema if one is set."""
190
- if self.schema is not None:
191
- if not isinstance(item, self.schema):
192
- raise ValueError(f"Item is not of type {self.schema.__name__}")
193
-
194
- def _build_query_conditions(
195
- self,
196
- query_filter: QueryFilter,
197
- table: Table,
198
- ) -> Any:
199
- """Build SQLAlchemy query conditions from QueryFilter."""
200
- conditions = []
201
-
202
- for condition in query_filter.conditions:
203
- column = getattr(table.c, condition.field, None)
204
- if column is None:
205
- continue
206
-
207
- if condition.operator == "eq":
208
- conditions.append(column == condition.value)
209
- elif condition.operator == "ne":
210
- conditions.append(column != condition.value)
211
- elif condition.operator == "gt":
212
- conditions.append(column > condition.value)
213
- elif condition.operator == "gte":
214
- conditions.append(column >= condition.value)
215
- elif condition.operator == "lt":
216
- conditions.append(column < condition.value)
217
- elif condition.operator == "lte":
218
- conditions.append(column <= condition.value)
219
- elif condition.operator == "in":
220
- conditions.append(column.in_(condition.value))
221
- elif condition.operator == "not_in":
222
- conditions.append(~column.in_(condition.value))
223
- elif condition.operator == "like":
224
- conditions.append(column.like(condition.value))
225
- elif condition.operator == "ilike":
226
- conditions.append(column.ilike(condition.value))
227
- elif condition.operator == "is_null":
228
- conditions.append(column.is_(None))
229
- elif condition.operator == "is_not_null":
230
- conditions.append(column.isnot(None))
231
- elif condition.operator == "startswith":
232
- conditions.append(column.like(f"{condition.value}%"))
233
- elif condition.operator == "endswith":
234
- conditions.append(column.like(f"%{condition.value}"))
235
- elif condition.operator == "contains":
236
- conditions.append(column.like(f"%{condition.value}%"))
237
-
238
- if not conditions:
239
- return None
240
-
241
- if query_filter.logic == "and":
242
- return and_(*conditions)
243
- else: # or
244
- return or_(*conditions)
245
-
246
- def _cleanup_expired_items(self, session: Session) -> int:
247
- """Remove expired items from the database."""
248
- if not self.auto_cleanup_expired:
249
- return 0
250
-
251
- now = datetime.now(timezone.utc)
252
-
253
- # Find expired items by checking created_at + ttl < now
254
- stmt = select(self._table).where(
255
- and_(
256
- self._table.c.ttl.isnot(None),
257
- self._table.c.created_at + (self._table.c.ttl * timedelta(seconds=1))
258
- < now,
259
- )
260
- )
261
-
262
- expired_items = session.execute(stmt).fetchall()
263
- expired_ids = [item.id for item in expired_items]
264
-
265
- if expired_ids:
266
- delete_stmt = delete(self._table).where(self._table.c.id.in_(expired_ids))
267
- session.execute(delete_stmt)
268
-
269
- return len(expired_ids)
270
-
271
- def add(
272
- self,
273
- item: DatabaseItemType,
274
- *,
275
- id: Optional[str] = None,
276
- filters: Optional[DatabaseItemFilters] = None,
277
- ttl: Optional[int] = None,
278
- ) -> str:
279
- """
280
- Add an item to the database.
281
-
282
- Args:
283
- item: The item to store
284
- id: Optional ID (will generate UUID if not provided)
285
- filters: Optional filters/metadata
286
- ttl: Optional TTL in seconds
287
-
288
- Returns:
289
- The ID of the stored item
290
- """
291
- self._validate_schema(item)
292
-
293
- item_id = id or str(uuid.uuid4())
294
- item_ttl = ttl or self.ttl
295
- now = datetime.now(timezone.utc)
296
-
297
- serialized_item = self._serialize_item(item)
298
- serialized_filters = json.dumps(filters or {})
299
-
300
- with self._session_factory() as session:
301
- # Check if item already exists
302
- existing = session.execute(
303
- select(self._table).where(self._table.c.id == item_id)
304
- ).fetchone()
305
-
306
- if existing:
307
- # Update existing item
308
- stmt = (
309
- update(self._table)
310
- .where(self._table.c.id == item_id)
311
- .values(
312
- item_data=serialized_item,
313
- filters=serialized_filters,
314
- updated_at=now,
315
- ttl=item_ttl,
316
- )
317
- )
318
- else:
319
- # Insert new item
320
- stmt = insert(self._table).values(
321
- id=item_id,
322
- item_data=serialized_item,
323
- filters=serialized_filters,
324
- created_at=now,
325
- updated_at=now,
326
- ttl=item_ttl,
327
- table_name=self.table_name,
328
- )
329
-
330
- session.execute(stmt)
331
-
332
- # Cleanup expired items
333
- self._cleanup_expired_items(session)
334
-
335
- session.commit()
336
-
337
- return item_id
338
-
339
- def get(
340
- self,
341
- id: str,
342
- *,
343
- filters: Optional[DatabaseItemFilters] = None,
344
- ) -> Optional[DatabaseItem[DatabaseItemType]]:
345
- """
346
- Get an item by ID.
347
-
348
- Args:
349
- id: The item ID
350
- filters: Optional filters to match
351
-
352
- Returns:
353
- The database item or None if not found
354
- """
355
- with self._session_factory() as session:
356
- stmt = select(self._table).where(self._table.c.id == id)
357
- result = session.execute(stmt).fetchone()
358
-
359
- if not result:
360
- return None
361
-
362
- # Check if expired
363
- if result.ttl is not None:
364
- expires_at = result.created_at + timedelta(seconds=result.ttl)
365
- if datetime.now(timezone.utc) >= expires_at:
366
- # Delete expired item
367
- session.execute(delete(self._table).where(self._table.c.id == id))
368
- session.commit()
369
- return None
370
-
371
- # Check filters if provided
372
- if filters:
373
- stored_filters = json.loads(result.filters or "{}")
374
- if not all(stored_filters.get(k) == v for k, v in filters.items()):
375
- return None
376
-
377
- # Deserialize and return
378
- item_data = self._deserialize_item(result.item_data)
379
- stored_filters = json.loads(result.filters or "{}")
380
-
381
- return DatabaseItem(
382
- id=result.id,
383
- item=item_data,
384
- created_at=result.created_at,
385
- updated_at=result.updated_at,
386
- ttl=result.ttl,
387
- filters=stored_filters,
388
- table_name=result.table_name,
389
- )
390
-
391
- def query(
392
- self,
393
- query_filter: Optional[QueryFilter] = None,
394
- *,
395
- limit: Optional[int] = None,
396
- offset: int = 0,
397
- order_by: Optional[str] = None,
398
- ascending: bool = True,
399
- ) -> List[DatabaseItem[DatabaseItemType]]:
400
- """
401
- Query items from the database.
402
-
403
- Args:
404
- query_filter: Filter conditions to apply
405
- limit: Maximum number of results
406
- offset: Number of results to skip
407
- order_by: Field to order by
408
- ascending: Sort direction
409
-
410
- Returns:
411
- List of matching database items
412
- """
413
- with self._session_factory() as session:
414
- # Cleanup expired items first
415
- self._cleanup_expired_items(session)
416
-
417
- stmt = select(self._table)
418
-
419
- # Apply filters
420
- if query_filter:
421
- conditions = self._build_query_conditions(query_filter, self._table)
422
- if conditions is not None:
423
- stmt = stmt.where(conditions)
424
-
425
- # Apply ordering
426
- if order_by:
427
- column = getattr(self._table.c, order_by, None)
428
- if column is not None:
429
- if ascending:
430
- stmt = stmt.order_by(column.asc())
431
- else:
432
- stmt = stmt.order_by(column.desc())
433
- else:
434
- # Default order by created_at desc
435
- stmt = stmt.order_by(self._table.c.created_at.desc())
436
-
437
- # Apply pagination
438
- if offset > 0:
439
- stmt = stmt.offset(offset)
440
- if limit is not None:
441
- stmt = stmt.limit(limit)
442
-
443
- results = session.execute(stmt).fetchall()
444
-
445
- items = []
446
- for result in results:
447
- # Double-check expiration (in case of race conditions)
448
- if result.ttl is not None:
449
- expires_at = result.created_at + timedelta(seconds=result.ttl)
450
- if datetime.now(timezone.utc) >= expires_at:
451
- continue
452
-
453
- item_data = self._deserialize_item(result.item_data)
454
- stored_filters = json.loads(result.filters or "{}")
455
-
456
- items.append(
457
- DatabaseItem(
458
- id=result.id,
459
- item=item_data,
460
- created_at=result.created_at,
461
- updated_at=result.updated_at,
462
- ttl=result.ttl,
463
- filters=stored_filters,
464
- table_name=result.table_name,
465
- )
466
- )
467
-
468
- return items
469
-
470
- def delete(self, id: str) -> bool:
471
- """
472
- Delete an item by ID.
473
-
474
- Args:
475
- id: The item ID
476
-
477
- Returns:
478
- True if item was deleted, False if not found
479
- """
480
- with self._session_factory() as session:
481
- stmt = delete(self._table).where(self._table.c.id == id)
482
- result = session.execute(stmt)
483
- session.commit()
484
- return result.rowcount > 0
485
-
486
- def count(
487
- self,
488
- query_filter: Optional[QueryFilter] = None,
489
- ) -> int:
490
- """
491
- Count items matching the filter.
492
-
493
- Args:
494
- query_filter: Filter conditions to apply
495
-
496
- Returns:
497
- Number of matching items
498
- """
499
- with self._session_factory() as session:
500
- # Cleanup expired items first
501
- self._cleanup_expired_items(session)
502
-
503
- from sqlalchemy import func
504
-
505
- stmt = select(func.count(self._table.c.id))
506
-
507
- if query_filter:
508
- conditions = self._build_query_conditions(query_filter, self._table)
509
- if conditions is not None:
510
- stmt = stmt.where(conditions)
511
-
512
- result = session.execute(stmt).fetchone()
513
- return result[0] if result else 0
514
-
515
- def clear(self) -> int:
516
- """
517
- Clear all items from the database.
518
-
519
- Returns:
520
- Number of items deleted
521
- """
522
- with self._session_factory() as session:
523
- stmt = delete(self._table)
524
- result = session.execute(stmt)
525
- session.commit()
526
- return result.rowcount
527
-
528
- def cleanup_expired(self) -> int:
529
- """
530
- Manually cleanup expired items.
531
-
532
- Returns:
533
- Number of items cleaned up
534
- """
535
- with self._session_factory() as session:
536
- count = self._cleanup_expired_items(session)
537
- session.commit()
538
- return count
539
-
540
- def __repr__(self) -> str:
541
- """String representation of the database."""
542
- location = str(self.path) if self.path else "memory"
543
- return f"<Database name='{self.name}' location='{location}' table='{self.table_name}'>"
544
-
545
-
546
- def create_database(
547
- name: str,
548
- *,
549
- schema: Optional[Type[DatabaseItemType]] = None,
550
- ttl: Optional[int] = None,
551
- path: Optional[Union[Path, str]] = None,
552
- table_name: str = "items",
553
- auto_cleanup_expired: bool = True,
554
- ) -> Database[DatabaseItemType]:
555
- """
556
- Create a new database instance.
557
-
558
- Args:
559
- name: The name of the database
560
- schema: Optional schema type for validation
561
- ttl: Default time-to-live in seconds
562
- path: File path for storage (None = in-memory)
563
- table_name: Name of the primary table
564
- auto_cleanup_expired: Whether to automatically clean up expired items
565
-
566
- Returns:
567
- A Database instance
568
- """
569
- return Database(
570
- name=name,
571
- schema=schema,
572
- ttl=ttl,
573
- path=path,
574
- table_name=table_name,
575
- auto_cleanup_expired=auto_cleanup_expired,
576
- )
hammad/data/sql/types.py DELETED
@@ -1,127 +0,0 @@
1
- """hammad.data.sql.types"""
2
-
3
- from datetime import datetime, timezone
4
- from dataclasses import dataclass, field
5
- from typing import (
6
- Any,
7
- Dict,
8
- Generic,
9
- Optional,
10
- Type,
11
- TypeVar,
12
- TypeAlias,
13
- Literal,
14
- Union,
15
- )
16
- import uuid
17
-
18
- __all__ = (
19
- "DatabaseItemType",
20
- "DatabaseItemFilters",
21
- "DatabaseItem",
22
- "QueryOperator",
23
- "QueryCondition",
24
- "QueryFilter",
25
- )
26
-
27
-
28
- DatabaseItemType = TypeVar("DatabaseItemType")
29
- """Generic type variable for any valid item type that can be stored
30
- within a database."""
31
-
32
-
33
- DatabaseItemFilters: TypeAlias = Dict[str, object]
34
- """A dictionary of filters that can be used to query the database."""
35
-
36
-
37
- QueryOperator = Literal[
38
- "eq", # equal
39
- "ne", # not equal
40
- "gt", # greater than
41
- "gte", # greater than or equal
42
- "lt", # less than
43
- "lte", # less than or equal
44
- "in", # in list
45
- "not_in", # not in list
46
- "like", # SQL LIKE
47
- "ilike", # case insensitive LIKE
48
- "is_null", # IS NULL
49
- "is_not_null", # IS NOT NULL
50
- "contains", # for JSON contains
51
- "startswith", # string starts with
52
- "endswith", # string ends with
53
- ]
54
- """Supported query operators for database queries."""
55
-
56
-
57
- @dataclass
58
- class QueryCondition:
59
- """Represents a single query condition for database filtering."""
60
-
61
- field: str
62
- """The field name to filter on."""
63
-
64
- operator: QueryOperator
65
- """The operator to use for comparison."""
66
-
67
- value: Any = None
68
- """The value to compare against (not needed for is_null/is_not_null)."""
69
-
70
-
71
- @dataclass
72
- class QueryFilter:
73
- """Represents a collection of query conditions with logical operators."""
74
-
75
- conditions: list[QueryCondition] = field(default_factory=list)
76
- """List of individual query conditions."""
77
-
78
- logic: Literal["and", "or"] = "and"
79
- """Logical operator to combine conditions."""
80
-
81
-
82
- @dataclass
83
- class DatabaseItem(Generic[DatabaseItemType]):
84
- """Base class for all items that can be stored within a database."""
85
-
86
- id: str = field(default_factory=lambda: str(uuid.uuid4()))
87
- """The unique identifier for this item."""
88
-
89
- item: DatabaseItemType = field(default_factory=lambda: None)
90
- """The item that is stored within this database item."""
91
-
92
- created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
93
- """The timestamp when this item was created."""
94
-
95
- updated_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
96
- """The timestamp when this item was last updated."""
97
-
98
- ttl: Optional[int] = field(default=None)
99
- """The time to live for this item in seconds."""
100
-
101
- filters: DatabaseItemFilters = field(default_factory=dict)
102
- """The filters that are associated with this item."""
103
-
104
- table_name: str = field(default="default")
105
- """The table/collection name where this item is stored."""
106
-
107
- score: Optional[float] = field(default=None)
108
- """The similarity score for this item (used in vector search results)."""
109
-
110
- def is_expired(self) -> bool:
111
- """Check if this item has expired based on its TTL."""
112
- if self.ttl is None:
113
- return False
114
-
115
- from datetime import timedelta
116
-
117
- expires_at = self.created_at + timedelta(seconds=self.ttl)
118
- return datetime.now(timezone.utc) >= expires_at
119
-
120
- def expires_at(self) -> Optional[datetime]:
121
- """Calculate when this item will expire."""
122
- if self.ttl is None:
123
- return None
124
-
125
- from datetime import timedelta
126
-
127
- return self.created_at + timedelta(seconds=self.ttl)