msgflux 0.1.0a1__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 (108) hide show
  1. msgflux/__init__.py +37 -0
  2. msgflux/_private/__init__.py +0 -0
  3. msgflux/_private/client.py +91 -0
  4. msgflux/_private/core.py +14 -0
  5. msgflux/_private/executor.py +77 -0
  6. msgflux/cache.py +50 -0
  7. msgflux/data/__init__.py +0 -0
  8. msgflux/data/audio.py +1 -0
  9. msgflux/data/databases/__init__.py +0 -0
  10. msgflux/data/databases/base.py +32 -0
  11. msgflux/data/databases/database.py +125 -0
  12. msgflux/data/databases/providers/__init__.py +0 -0
  13. msgflux/data/databases/providers/cachetools.py +64 -0
  14. msgflux/data/databases/providers/diskcache.py +60 -0
  15. msgflux/data/databases/providers/faiss.py +217 -0
  16. msgflux/data/databases/types.py +20 -0
  17. msgflux/data/parsers/__init__.py +0 -0
  18. msgflux/data/parsers/base.py +11 -0
  19. msgflux/data/parsers/parser.py +88 -0
  20. msgflux/data/parsers/providers/__init__.py +0 -0
  21. msgflux/data/parsers/providers/openpyxl.py +166 -0
  22. msgflux/data/parsers/providers/pypdf.py +49 -0
  23. msgflux/data/parsers/providers/python_pptx.py +167 -0
  24. msgflux/data/parsers/types.py +23 -0
  25. msgflux/data/retrievers/__init__.py +0 -0
  26. msgflux/data/retrievers/base.py +12 -0
  27. msgflux/data/retrievers/providers/__init__.py +0 -0
  28. msgflux/data/retrievers/providers/bm25.py +188 -0
  29. msgflux/data/retrievers/providers/rank_bm25.py +187 -0
  30. msgflux/data/retrievers/retriever.py +80 -0
  31. msgflux/data/retrievers/types.py +15 -0
  32. msgflux/dotdict.py +169 -0
  33. msgflux/dsl/__init__.py +0 -0
  34. msgflux/dsl/inline.py +524 -0
  35. msgflux/dsl/signature.py +400 -0
  36. msgflux/envs.py +106 -0
  37. msgflux/exceptions.py +31 -0
  38. msgflux/generation/__init__.py +0 -0
  39. msgflux/generation/reasoning/__init__.py +6 -0
  40. msgflux/generation/reasoning/cot.py +70 -0
  41. msgflux/generation/reasoning/react.py +47 -0
  42. msgflux/generation/reasoning/self_consistency.py +42 -0
  43. msgflux/generation/reasoning/tot.py +57 -0
  44. msgflux/generation/templates.py +87 -0
  45. msgflux/logger.py +148 -0
  46. msgflux/message.py +43 -0
  47. msgflux/models/__init__.py +0 -0
  48. msgflux/models/base.py +31 -0
  49. msgflux/models/gateway.py +242 -0
  50. msgflux/models/httpx.py +39 -0
  51. msgflux/models/model.py +170 -0
  52. msgflux/models/providers/__init__.py +0 -0
  53. msgflux/models/providers/base.py +123 -0
  54. msgflux/models/providers/ctranslate2.py +144 -0
  55. msgflux/models/providers/jinaai.py +237 -0
  56. msgflux/models/providers/mistral.py +1 -0
  57. msgflux/models/providers/ollama.py +34 -0
  58. msgflux/models/providers/openai.py +955 -0
  59. msgflux/models/providers/openrouter.py +20 -0
  60. msgflux/models/providers/pylate.py +0 -0
  61. msgflux/models/providers/sambanova.py +32 -0
  62. msgflux/models/providers/timm.py +64 -0
  63. msgflux/models/providers/together.py +45 -0
  64. msgflux/models/providers/torchaudio.py +74 -0
  65. msgflux/models/providers/torchvision.py +120 -0
  66. msgflux/models/providers/vllm.py +86 -0
  67. msgflux/models/response.py +76 -0
  68. msgflux/models/tool_call_agg.py +93 -0
  69. msgflux/models/types.py +180 -0
  70. msgflux/nn/__init__.py +7 -0
  71. msgflux/nn/functional.py +472 -0
  72. msgflux/nn/modules/__init__.py +23 -0
  73. msgflux/nn/modules/agent.py +1005 -0
  74. msgflux/nn/modules/container.py +601 -0
  75. msgflux/nn/modules/designer.py +273 -0
  76. msgflux/nn/modules/module.py +1958 -0
  77. msgflux/nn/modules/predictor.py +116 -0
  78. msgflux/nn/modules/retriever.py +245 -0
  79. msgflux/nn/modules/speaker.py +140 -0
  80. msgflux/nn/modules/tool.py +296 -0
  81. msgflux/nn/modules/transcriber.py +181 -0
  82. msgflux/nn/parameter.py +75 -0
  83. msgflux/py.typed +0 -0
  84. msgflux/telemetry/__init__.py +0 -0
  85. msgflux/telemetry/span.py +141 -0
  86. msgflux/telemetry/tracer.py +48 -0
  87. msgflux/utils/__init__.py +0 -0
  88. msgflux/utils/chat.py +508 -0
  89. msgflux/utils/common.py +25 -0
  90. msgflux/utils/convert.py +41 -0
  91. msgflux/utils/encode.py +101 -0
  92. msgflux/utils/hooks.py +68 -0
  93. msgflux/utils/imports.py +39 -0
  94. msgflux/utils/inspect.py +110 -0
  95. msgflux/utils/logging.py +15 -0
  96. msgflux/utils/mermaid.py +11 -0
  97. msgflux/utils/msgspec.py +489 -0
  98. msgflux/utils/pooling.py +45 -0
  99. msgflux/utils/tenacity.py +19 -0
  100. msgflux/utils/tool.py +121 -0
  101. msgflux/utils/torch.py +8 -0
  102. msgflux/utils/validation.py +22 -0
  103. msgflux/utils/xml.py +125 -0
  104. msgflux/version.py +1 -0
  105. msgflux-0.1.0a1.dist-info/METADATA +45 -0
  106. msgflux-0.1.0a1.dist-info/RECORD +108 -0
  107. msgflux-0.1.0a1.dist-info/WHEEL +4 -0
  108. msgflux-0.1.0a1.dist-info/licenses/LICENSE +21 -0
msgflux/__init__.py ADDED
@@ -0,0 +1,37 @@
1
+ from .cache import response_cache
2
+ from .data.databases.database import DataBase
3
+ from .data.retrievers.retriever import Retriever
4
+ from .dotdict import dotdict
5
+ from .dsl.inline import inline
6
+ from .dsl.signature import InputField, OutputField, Signature
7
+ from .envs import set_envs
8
+ from .message import Message
9
+ from .models.gateway import ModelGateway
10
+ from .models.model import Model
11
+ from .utils.chat import ChatML
12
+ from .utils.inspect import get_fn_name
13
+ from .utils.msgspec import load, save
14
+ from .utils.tool import tool_config
15
+ from .telemetry.span import instrument
16
+
17
+
18
+ __all__ = [
19
+ "ChatML",
20
+ "DataBase",
21
+ "InputField",
22
+ "Message",
23
+ "Model",
24
+ "ModelGateway",
25
+ "OutputField",
26
+ "Retriever",
27
+ "Signature",
28
+ "dotdict",
29
+ "get_fn_name",
30
+ "inline",
31
+ "instrument",
32
+ "load",
33
+ "response_cache",
34
+ "save",
35
+ "set_envs",
36
+ "tool_config"
37
+ ]
File without changes
@@ -0,0 +1,91 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any, Dict
3
+ from msgflux._private.core import Core
4
+
5
+
6
+ class BaseClient(ABC, Core):
7
+ """
8
+ BaseClient is an abstract base class that defines the interface and common behavior
9
+ for client classes in the msgflux system.
10
+
11
+ This class inherits from `Core` to leverage its state management capabilities and
12
+ extends it with additional functionality specific to clients. It requires subclasses
13
+ to implement certain methods, ensuring a consistent interface across different client
14
+ implementations.
15
+
16
+ Subclasses must implement the `_initialize` and `__call__` methods, which are
17
+ essential for client initialization and operation, respectively. Additionally, this
18
+ class provides methods for serialization and deserialization, allowing client instances
19
+ to be easily saved and restored.
20
+
21
+ Attributes:
22
+ to_ignore ([optional[list[str]]): A list of attribute keys to be ignored during serialization.
23
+ msgflux_type (str): A string identifying the type of the client in the msgflux system.
24
+ provider (optional[str]): The provider associated with the client, if applicable.
25
+ instance_type (optional[callable]): A method or callable that returns additional
26
+ instance-specific data to be included during serialization.
27
+ """
28
+
29
+ @abstractmethod
30
+ def __call__(self):
31
+ """
32
+ Execute the client's main functionality. This method must be implemented by subclasses.
33
+
34
+ This method defines the primary behavior of the client and is called when the instance
35
+ is invoked as a function.
36
+
37
+ Raises:
38
+ NotImplementedError: If the method is not implemented by the subclass.
39
+ """
40
+ raise NotImplementedError
41
+
42
+ async def acall(self, *args, **kwargs):
43
+ """ Async interface to __call__ """
44
+ return self.__call__(*args, **kwargs)
45
+
46
+ def serialize(self) -> Dict[str, Any]:
47
+ """
48
+ Serialize the client instance into a dictionary.
49
+
50
+ This method prepares the client's state for serialization by removing unnecessary
51
+ attributes and including additional metadata such as the client type and provider.
52
+
53
+ Returns:
54
+ Dict[str, Any]: A dictionary containing the serialized state of the client,
55
+ including metadata and the client's internal state.
56
+ """
57
+ state = self.__getstate__()
58
+ if hasattr(self, "to_ignore"):
59
+ for key in self.to_ignore:
60
+ state.pop(key, None)
61
+ data = {"msgflux_type": self.msgflux_type}
62
+ if hasattr(self, "provider"):
63
+ data["provider"] = self.provider
64
+ if hasattr(self, "instance_type"):
65
+ instance_type = self.instance_type()
66
+ data.update(instance_type)
67
+ data["state"] = state
68
+ return data
69
+
70
+ @classmethod
71
+ def from_serialized(cls, state: Dict[str, Any]):
72
+ """
73
+ Deserialize a client instance from a dictionary.
74
+
75
+ This method creates a new instance of the client and restores its state from the
76
+ provided dictionary. It also ensures that the client is properly initialized
77
+ after deserialization.
78
+
79
+ Args:
80
+ state (Dict[str, Any]): A dictionary containing the serialized state of the client.
81
+
82
+ Returns:
83
+ BaseClient: A new instance of the client with its state restored.
84
+ """
85
+ if "msgflux_type" in state:
86
+ state.pop("msgflux_type")
87
+ instance = cls()
88
+ instance.__setstate__(state)
89
+ if hasattr(instance, "_initialize"):
90
+ instance._initialize()
91
+ return instance
@@ -0,0 +1,14 @@
1
+ class Core:
2
+ """
3
+ Core is a set of methods that are common among some classes in msgflux.
4
+
5
+ This class provides basic functionality for state management, allowing instances
6
+ to be serialized and deserialized. It is intended to be inherited by other classes
7
+ that require these common features.
8
+ """
9
+ def __getstate__(self):
10
+ state = self.__dict__.copy()
11
+ return state
12
+
13
+ def __setstate__(self, state):
14
+ self.__dict__.update(state)
@@ -0,0 +1,77 @@
1
+ import asyncio
2
+ import threading
3
+ from concurrent.futures import ThreadPoolExecutor
4
+ from typing import Callable
5
+ import uvloop
6
+ from msgflux.envs import envs
7
+
8
+
9
+ class AsyncWorker:
10
+ def __init__(self):
11
+ """Initializes a worker with its own event loop in a separate thread."""
12
+ self.loop = uvloop.new_event_loop()
13
+ self.thread = threading.Thread(target=self.loop.run_forever)
14
+ self.thread.start()
15
+
16
+ def submit(self, coro):
17
+ """Submits a coroutine to the worker's event loop."""
18
+ return asyncio.run_coroutine_threadsafe(coro, self.loop)
19
+
20
+ def shutdown(self):
21
+ """Terminates the event loop and worker thread."""
22
+ self.loop.call_soon_threadsafe(self.loop.stop)
23
+ self.thread.join()
24
+
25
+
26
+ class Executor:
27
+ """
28
+ Async pool to manage the execution of synchronous and asynchronous code.
29
+ Executor distributes tasks among a ThreadPool or among Async Workers.
30
+ """
31
+ _instance = None
32
+ _lock = threading.Lock()
33
+
34
+ def __init__(self):
35
+ self.num_threads = envs.executor_num_threads
36
+ self.num_async_workers = envs.executor_num_async_workers
37
+ self.async_worker_index = 0
38
+ self.thread_pool = ThreadPoolExecutor(max_workers=self.num_threads)
39
+ self.async_workers = [AsyncWorker() for _ in range(self.num_async_workers)]
40
+
41
+ @classmethod
42
+ def get_instance(cls):
43
+ """Returns the singleton instance in a thread-safe manner."""
44
+ if cls._instance is None:
45
+ with cls._lock:
46
+ if cls._instance is None:
47
+ cls._instance = cls()
48
+ return cls._instance
49
+
50
+ def submit(self, f: Callable, *args, **kwargs):
51
+ """
52
+ Submits a task to the appropriate pool based on the function type.
53
+ Returns a Future to track the result.
54
+ """
55
+ if hasattr(f, "acall"):
56
+ coro = f.acall(*args, **kwargs)
57
+ return self._submit_to_async_worker(coro)
58
+ elif asyncio.iscoroutinefunction(f):
59
+ coro = f(*args, **kwargs)
60
+ return self._submit_to_async_worker(coro)
61
+ else:
62
+ return self.thread_pool.submit(f, *args, **kwargs)
63
+
64
+ def _submit_to_async_worker(self, coro):
65
+ """Distribute a coroutine to an asynchronous worker using round-robin."""
66
+ worker = self.async_workers[self.async_worker_index]
67
+ self.async_worker_index = (self.async_worker_index + 1) % self.num_async_workers
68
+ return worker.submit(coro)
69
+
70
+ def shutdown(self):
71
+ """Shutdown the executor, closing the pools."""
72
+ self.thread_pool.shutdown()
73
+ for worker in self.async_workers:
74
+ worker.shutdown()
75
+
76
+ def __del__(self):
77
+ self.shutdown()
msgflux/cache.py ADDED
@@ -0,0 +1,50 @@
1
+ from functools import wraps
2
+ from typing import Optional
3
+ from msgflux.data.databases.base import BaseDB
4
+ from msgflux.message import Message
5
+ from msgflux.models.types import TextEmbedderModel
6
+
7
+
8
+ def response_cache(
9
+ db: BaseDB,
10
+ task_inputs: Optional[str] = None,
11
+ response_mode: Optional[str] = None,
12
+ model: Optional[TextEmbedderModel] = None,
13
+ ):
14
+ def decorator(func):
15
+ @wraps(func)
16
+ def wrapper(msg):
17
+ if isinstance(msg, Message):
18
+ if task_inputs is None:
19
+ raise ValueError(
20
+ "`task_inputs` is required when input "
21
+ "is a Message instance"
22
+ )
23
+ key = msg.get(task_inputs)
24
+ else:
25
+ key = msg
26
+
27
+ if model:
28
+ key = model(key)
29
+
30
+ cached_response = db(key)
31
+
32
+ if cached_response is not None:
33
+ if isinstance(msg, Message):
34
+ if response_mode is None:
35
+ raise ValueError(
36
+ "`response_mode` is required when "
37
+ "input is a Message instance"
38
+ )
39
+ msg.set(response_mode, cached_response)
40
+ return msg
41
+ return cached_response
42
+
43
+ response = func(msg)
44
+
45
+ if response is not None:
46
+ db.add(key, response)
47
+
48
+ return response
49
+ return wrapper
50
+ return decorator
File without changes
msgflux/data/audio.py ADDED
@@ -0,0 +1 @@
1
+ #https://github.com/openai/openai-python/blob/384e6b23ce0366d6b2f31cc98d35525da5b22c10/src/openai/helpers/local_audio_player.py#L19
File without changes
@@ -0,0 +1,32 @@
1
+ from abc import abstractmethod
2
+ from typing import Dict
3
+ from msgflux._private.client import BaseClient
4
+
5
+
6
+ class BaseDB(BaseClient):
7
+
8
+ msgflux_type = "db"
9
+ to_ignore = ["client", "documents"]
10
+
11
+ def instance_type(self) -> Dict[str, str]:
12
+ return {"db_type": self.db_type}
13
+
14
+ @abstractmethod
15
+ def add(self):
16
+ """
17
+ Insert data on database
18
+ """
19
+ raise NotImplementedError
20
+
21
+ @abstractmethod
22
+ def _initialize(self):
23
+ """
24
+ Initialize the class. This method must be implemented by subclasses.
25
+
26
+ This method is called during the deserialization process to ensure that the client
27
+ is properly initialized after its state has been restored.
28
+
29
+ Raises:
30
+ NotImplementedError: If the method is not implemented by the subclass.
31
+ """
32
+ raise NotImplementedError
@@ -0,0 +1,125 @@
1
+ from typing import Any, Dict, Type
2
+ from msgflux.data.databases.base import BaseDB
3
+ from msgflux.data.databases.types import (
4
+ #GeoDB,
5
+ #GraphDB,
6
+ KVDB,
7
+ #NoSQLDB,
8
+ #RelationalDB,
9
+ #TimeSeriesDB,
10
+ VectorDB,
11
+ )
12
+ from msgflux.utils.imports import import_module_from_lib
13
+
14
+
15
+ _SUPPORTED_DB_TYPES = [
16
+ "kv",
17
+ "vector",
18
+ #"graph",
19
+ #"relational",
20
+ #"nosql",
21
+ #"time_series",
22
+ #"geo",
23
+ ]
24
+ _DB_NAMESPACE_TRANSLATOR = {
25
+ "cachetools": "CacheTools",
26
+ "diskmanager": "DiskManager",
27
+ "faiss": "FAISS",
28
+ #"qdrant": "Qdrant",
29
+ #"postgres": "Postgres",
30
+ #"weavite": "Weavite",
31
+ #"milvus": "Milvus",
32
+ #"lance_db": "LanceDB",
33
+ #"vespa": "Vespa",
34
+ #"pinecone": "Pinecone",
35
+ #"cassandra": "Cassandra",
36
+ #"redis": "Redis",
37
+ #"deep_lake": "DeepLake",
38
+ #"marqo": "Marqo",
39
+ #"azure": "AzureAISearch",
40
+ #"supabase": "SupaBase",
41
+ #"elastic_search": "ElasticSearch",
42
+ #"chroma": "Chroma",
43
+ #"sqlite": "SQLite",
44
+ #"neo4j": "Neo4J",
45
+ }
46
+ _VECTOR_DB_PROVIDERS = [
47
+ #"qdrant",
48
+ #"postgres",
49
+ #"weavite",
50
+ #"milvus",
51
+ #"lance_db",
52
+ #"vespa",
53
+ #"redis",
54
+ #"cassandra",
55
+ #"deep_lake",
56
+ #"marqo",
57
+ #"elastic_search",
58
+ #"sqlite",
59
+ "faiss",
60
+ #"chroma",
61
+ ]
62
+ #_GRAPH_DB_PROVIDERS = ["neo4j"]
63
+ _KV_DB_PROVIDERS = ["cachetools", "diskmanager"]
64
+
65
+ _PROVIDERS_BY_DB_TYPE = {
66
+ "kv": _KV_DB_PROVIDERS,
67
+ "vector": _VECTOR_DB_PROVIDERS,
68
+ }
69
+
70
+
71
+ class DataBase:
72
+ supported_db_types = _SUPPORTED_DB_TYPES
73
+ providers_by_db_type = _PROVIDERS_BY_DB_TYPE
74
+
75
+ @classmethod
76
+ def _get_db_class(cls, db_type: str, provider: str) -> Type[BaseDB]:
77
+ if db_type not in cls.supported_db_types:
78
+ raise ValueError(f"DB type `{db_type}` is not supported")
79
+
80
+ providers = cls.providers_by_db_type[db_type]
81
+ if provider not in providers:
82
+ raise ValueError(f"Provider `{provider}` is not supported for {db_type}")
83
+
84
+ if len(db_type) <= 3:
85
+ db_type = db_type.upper()
86
+ else:
87
+ db_type = db_type.title().replace("_", "")
88
+
89
+ provider_class_name = f"{_DB_NAMESPACE_TRANSLATOR[provider]}{db_type}DB"
90
+ module_name = f"msgflux.data.databases.providers.{provider}"
91
+ return import_module_from_lib(provider_class_name, module_name)
92
+
93
+ @classmethod
94
+ def _create_db(cls, db_type: str, provider: str, **kwargs) -> Type[BaseDB]:
95
+ db_cls = cls._get_db_class(db_type, provider)
96
+ return db_cls(**kwargs)
97
+
98
+ @classmethod
99
+ def from_serialized(cls, provider: str, db_type: str, params: Dict[str, Any]) -> Type[BaseDB]:
100
+ """
101
+ Creates a db instance from serialized parameters without calling __init__.
102
+
103
+ Args:
104
+ provider: The db provider (e.g., "faiss", "cachetools")
105
+ db_type: The type of db (e.g., "vector", "kv")
106
+ params: Dictionary containing the serialized db parameters
107
+
108
+ Returns:
109
+ An instance of the appropriate db class with restored state
110
+ """
111
+ db_cls = cls._get_db_class(db_type, provider)
112
+ # Create instance without calling __init__
113
+ instance = object.__new__(db_cls)
114
+ # Restore the instance state
115
+ instance.from_serialized(params)
116
+ return instance
117
+
118
+ @classmethod
119
+ def kv(cls, provider: str, **kwargs) -> KVDB:
120
+ return cls._create_db("kv", provider, **kwargs)
121
+
122
+ @classmethod
123
+ def vector(cls, provider: str, **kwargs) -> VectorDB:
124
+ return cls._create_db("vector", provider, **kwargs)
125
+
File without changes
@@ -0,0 +1,64 @@
1
+ from cachetools import TTLCache
2
+ from typing import Any, Dict, List, Optional, Union
3
+ import msgspec
4
+ from msgflux.data.databases.base import BaseDB
5
+ from msgflux.data.databases.types import KVDB
6
+ from msgflux.utils.convert import convert_str_to_hash
7
+
8
+
9
+ class CacheToolsKVDB(BaseDB, KVDB):
10
+ """CacheTools Key-Value DB."""
11
+
12
+ provider = "cachetools"
13
+
14
+ def __init__(
15
+ self,
16
+ ttl: Optional[int] = 3600,
17
+ maxsize: Optional[int] = 10000,
18
+ hash_key: Optional[bool] = True
19
+ ):
20
+ """
21
+ Args:
22
+ ttl:
23
+ The time-to-live (TTL) for each cache entry in seconds.
24
+ maxsize:
25
+ The maximum number of items the cache can store.
26
+ hash_key:
27
+ Whether to hash the keys before storing them in the cache.
28
+ """
29
+ self.hash_key = hash_key
30
+ self.maxsize = maxsize
31
+ self.ttl = ttl
32
+ self._initialize()
33
+
34
+ def _initialize(self):
35
+ self.client = TTLCache(maxsize=self.maxsize, ttl=self.ttl)
36
+
37
+ def add(self, documents: Union[List[Dict[str, Any]], Dict[str, Any]]):
38
+ if not isinstance(documents, list):
39
+ documents = [documents]
40
+ for document in documents:
41
+ for k, v in document.items():
42
+ if self.hash_key:
43
+ k = convert_str_to_hash(k)
44
+ v = msgspec.msgpack.encode(v)
45
+ self.client[k] = v
46
+
47
+ def _search(self, queries: Union[str, List[str]]):
48
+ if not isinstance(queries, list):
49
+ queries = [queries]
50
+ query_results = []
51
+ for query in queries:
52
+ if self.hash_key:
53
+ query = convert_str_to_hash(query)
54
+ v = self.client.get(query)
55
+ if v is not None:
56
+ v = msgspec.msgpack.decode(v)
57
+ query_results.append(v)
58
+ return query_results
59
+
60
+ def __call__(self, queries: Union[str, List[str]]) -> Union[Any, List[Any]]:
61
+ query_results = self._search(queries)
62
+ if len(query_results) == 1:
63
+ return query_results[0]
64
+ return query_results
@@ -0,0 +1,60 @@
1
+ from typing import Any, Dict, List, Optional, Union
2
+ import msgspec
3
+ try:
4
+ from diskcache import Cache
5
+ except:
6
+ raise ImportError("`diskcache` is not detected, please install"
7
+ "using `pip install diskcache`")
8
+ from msgflux.data.databases.base import BaseDB
9
+ from msgflux.data.databases.types import KVDB
10
+ from msgflux.utils.convert import convert_str_to_hash
11
+
12
+
13
+ class DiskCacheKVDB(BaseDB, KVDB):
14
+ """DiskCache Key-Value DB."""
15
+
16
+ provider = "diskcache"
17
+
18
+ def __init__(self, ttl: Optional[int] = 3600, hash_key: Optional[bool] = True):
19
+ """
20
+ Args:
21
+ ttl:
22
+ The time-to-live (TTL) for each cache entry in seconds.
23
+ hash_key:
24
+ Whether to hash the keys before storing them in the cache.
25
+ """
26
+ self.hash_key = hash_key
27
+ self.ttl = ttl
28
+ self._initialize()
29
+
30
+ def _initialize(self):
31
+ self.client = Cache(timeout=1)
32
+
33
+ def add(self, documents: Union[List[Dict[str, Any]], Dict[str, Any]]):
34
+ if not isinstance(documents, list):
35
+ documents = [documents]
36
+ for document in documents:
37
+ for k, v in document.items():
38
+ if self.hash_key:
39
+ k = convert_str_to_hash(k)
40
+ v = msgspec.msgpack.encode(v)
41
+ self.client.set(k, v, expire=self.ttl)
42
+
43
+ def _search(self, queries: Union[str, List[str]]):
44
+ if not isinstance(queries, list):
45
+ queries = [queries]
46
+ query_results = []
47
+ for query in queries:
48
+ if self.hash_key:
49
+ query = convert_str_to_hash(query)
50
+ v = self.client.get(query)
51
+ if v is not None:
52
+ v = msgspec.msgpack.decode(v)
53
+ query_results.append(v)
54
+ return query_results
55
+
56
+ def __call__(self, queries: Union[str, List[str]]) -> Union[Any, List[Any]]:
57
+ query_results = self._search(queries)
58
+ if len(query_results) == 1:
59
+ return query_results[0]
60
+ return query_results