lfx-valkey 0.1.0__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.
lfx_valkey/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """lfx-valkey: Valkey bundle.
2
+
3
+ Distribution unit ``lfx-valkey``. At runtime Langflow's loader
4
+ discovers ``extension.json`` shipped alongside this ``__init__.py`` and
5
+ registers the bundle's components under the namespaced IDs
6
+ ``ext:valkey:<Class>@official``.
7
+ """
8
+
9
+ from lfx_valkey.components.valkey.valkey import ValkeyVectorStoreComponent
10
+ from lfx_valkey.components.valkey.valkey_chat import ValkeyIndexChatMemory
11
+
12
+ __all__ = [
13
+ "ValkeyIndexChatMemory",
14
+ "ValkeyVectorStoreComponent",
15
+ ]
File without changes
@@ -0,0 +1,45 @@
1
+ """Lazy component re-exports for the ``valkey`` bundle.
2
+
3
+ Mirrors the pre-extraction layout of ``lfx.components.valkey`` so saved
4
+ flows that referenced the module-level class
5
+ (e.g. ``lfx.components.valkey.<Class>``) keep resolving via the
6
+ migration table after rewrite to
7
+ ``lfx_valkey.components.valkey.<Class>``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from lfx.components._importing import import_mod
15
+
16
+ if TYPE_CHECKING:
17
+ from .valkey import ValkeyVectorStoreComponent
18
+ from .valkey_chat import ValkeyIndexChatMemory
19
+
20
+ _dynamic_imports = {
21
+ "ValkeyIndexChatMemory": "valkey_chat",
22
+ "ValkeyVectorStoreComponent": "valkey",
23
+ }
24
+
25
+ __all__ = [
26
+ "ValkeyIndexChatMemory",
27
+ "ValkeyVectorStoreComponent",
28
+ ]
29
+
30
+
31
+ def __getattr__(attr_name: str) -> Any:
32
+ if attr_name not in _dynamic_imports:
33
+ msg = f"module {__name__!r} has no attribute {attr_name!r}"
34
+ raise AttributeError(msg)
35
+ try:
36
+ result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent)
37
+ except (ModuleNotFoundError, ImportError, AttributeError) as e:
38
+ msg = f"Could not import {attr_name!r} from {__name__!r}: {e}"
39
+ raise AttributeError(msg) from e
40
+ globals()[attr_name] = result
41
+ return result
42
+
43
+
44
+ def __dir__() -> list[str]:
45
+ return list(__all__)
@@ -0,0 +1,143 @@
1
+ import structlog
2
+ from lfx.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store
3
+ from lfx.helpers.data import docs_to_data
4
+ from lfx.io import HandleInput, IntInput, SecretStrInput, StrInput
5
+ from lfx.schema.data import Data
6
+
7
+ logger = structlog.get_logger(__name__)
8
+
9
+
10
+ def _patch_check_index_exists():
11
+ """Patch langchain-aws check_index_exists to handle valkey-glide RequestError.
12
+
13
+ langchain-aws's check_index_exists only catches KeyError/ValueError/RuntimeError,
14
+ but valkey-glide raises glide_shared.exceptions.RequestError when an index doesn't
15
+ exist. This patch catches that specific exception so _create_index_if_not_exist
16
+ can proceed to create the index.
17
+
18
+ This is a workaround until langchain-aws handles the GLIDE exception itself.
19
+ """
20
+ try:
21
+ import langchain_aws.vectorstores.valkey.base as _valkey_base
22
+ except (ImportError, AttributeError):
23
+ return
24
+
25
+ if getattr(_valkey_base, "patched_check_index_exists", False):
26
+ return
27
+
28
+ try:
29
+ from glide_shared.exceptions import RequestError as GlideRequestError
30
+ except ImportError:
31
+ return
32
+
33
+ _original_check = _valkey_base.check_index_exists
34
+
35
+ def _patched_check(client, index_name):
36
+ try:
37
+ return _original_check(client, index_name)
38
+ except GlideRequestError:
39
+ logger.debug("Valkey index does not exist yet", index_name=index_name)
40
+ return False
41
+
42
+ _valkey_base.check_index_exists = _patched_check
43
+ _valkey_base.patched_check_index_exists = True
44
+
45
+
46
+ class ValkeyVectorStoreComponent(LCVectorStoreComponent):
47
+ """A custom component for implementing a Vector Store using Valkey."""
48
+
49
+ display_name: str = "Valkey"
50
+ description: str = "Implementation of Vector Store using Valkey"
51
+ name = "Valkey"
52
+ icon = "Valkey"
53
+
54
+ inputs = [
55
+ SecretStrInput(name="valkey_server_url", display_name="Valkey Server Connection String", required=True),
56
+ StrInput(name="valkey_index_name", display_name="Valkey Index"),
57
+ *LCVectorStoreComponent.inputs,
58
+ IntInput(
59
+ name="number_of_results",
60
+ display_name="Number of Results",
61
+ info="Number of results to return.",
62
+ value=4,
63
+ advanced=True,
64
+ ),
65
+ HandleInput(name="embedding", display_name="Embedding", input_types=["Embeddings"]),
66
+ ]
67
+
68
+ @check_cached_vector_store
69
+ def build_vector_store(self):
70
+ import json
71
+
72
+ # Imported lazily because it pulls valkey-glide, which ships no Windows
73
+ # wheels -- the ``valkey`` extra of langchain-aws is gated off Windows
74
+ # via the platform marker in this bundle's pyproject.toml. Keeping the
75
+ # import out of module scope lets the bundle load on Windows even though
76
+ # this vector store cannot run there; the Valkey Chat Memory component
77
+ # (redis-py based) still works.
78
+ try:
79
+ from langchain_aws.vectorstores import ValkeyVectorStore
80
+ except ImportError as e:
81
+ msg = (
82
+ "Could not import valkey-glide, which backs the Valkey Vector Store. "
83
+ "It ships no Windows wheels and is not installed on Windows; run this "
84
+ "component on Linux or macOS. Elsewhere, install it with "
85
+ "'pip install langchain-aws[valkey]'."
86
+ )
87
+ raise ImportError(msg) from e
88
+
89
+ _patch_check_index_exists()
90
+
91
+ self.ingest_data = self._prepare_ingest_data()
92
+
93
+ documents = []
94
+ for _input in self.ingest_data or []:
95
+ if isinstance(_input, Data):
96
+ documents.append(_input.to_lc_document())
97
+ else:
98
+ documents.append(_input)
99
+
100
+ # Filter out documents with empty page_content — embedding models
101
+ # (e.g. Bedrock) reject zero-length input strings.
102
+ valid_documents = []
103
+ for doc in documents:
104
+ content = getattr(doc, "page_content", None) or ""
105
+ if content.strip():
106
+ valid_documents.append(doc)
107
+ elif doc.metadata:
108
+ doc.page_content = json.dumps(doc.metadata, default=str)
109
+ valid_documents.append(doc)
110
+ else:
111
+ logger.warning("Valkey: skipping document with no content and no metadata")
112
+ documents = valid_documents
113
+
114
+ if not documents:
115
+ if not self.valkey_index_name:
116
+ msg = "If no documents are provided, an index name must be provided."
117
+ raise ValueError(msg)
118
+
119
+ return ValkeyVectorStore.from_existing_index(
120
+ embedding=self.embedding,
121
+ valkey_url=self.valkey_server_url,
122
+ index_name=self.valkey_index_name,
123
+ )
124
+
125
+ return ValkeyVectorStore.from_documents(
126
+ documents=documents,
127
+ embedding=self.embedding,
128
+ valkey_url=self.valkey_server_url,
129
+ index_name=self.valkey_index_name,
130
+ )
131
+
132
+ def search_documents(self) -> list[Data]:
133
+ vector_store = self.build_vector_store()
134
+
135
+ if self.search_query and isinstance(self.search_query, str) and self.search_query.strip():
136
+ docs = vector_store.similarity_search(
137
+ query=self.search_query,
138
+ k=self.number_of_results,
139
+ )
140
+ data = docs_to_data(docs)
141
+ self.status = data
142
+ return data
143
+ return []
@@ -0,0 +1,62 @@
1
+ from urllib import parse
2
+
3
+ from lfx.base.memory.model import LCChatMemoryComponent
4
+ from lfx.field_typing.constants import Memory
5
+ from lfx.inputs.inputs import IntInput, MessageTextInput, SecretStrInput, StrInput
6
+
7
+
8
+ class ValkeyIndexChatMemory(LCChatMemoryComponent):
9
+ display_name = "Valkey Chat Memory"
10
+ description = "Retrieves and stores chat messages from Valkey."
11
+ name = "ValkeyChatMemory"
12
+ icon = "Valkey"
13
+
14
+ inputs = [
15
+ StrInput(
16
+ name="host", display_name="Hostname", required=True, value="localhost", info="IP address or hostname."
17
+ ),
18
+ IntInput(name="port", display_name="Port", required=True, value=6379, info="Valkey Port Number."),
19
+ StrInput(name="database", display_name="Database", required=True, value="0", info="Valkey database."),
20
+ MessageTextInput(
21
+ name="username", display_name="Username", value="", info="The Valkey user name.", advanced=True
22
+ ),
23
+ SecretStrInput(
24
+ name="password",
25
+ display_name="Valkey Password",
26
+ info="Password for the specified username.",
27
+ advanced=True,
28
+ load_from_db=False,
29
+ ),
30
+ StrInput(name="key_prefix", display_name="Key prefix", info="Key prefix.", advanced=True),
31
+ MessageTextInput(
32
+ name="session_id", display_name="Session ID", info="Session ID for the message.", advanced=True
33
+ ),
34
+ ]
35
+
36
+ def build_message_history(self) -> Memory:
37
+ from langchain_community.chat_message_histories.redis import RedisChatMessageHistory
38
+
39
+ kwargs = {}
40
+ if self.key_prefix:
41
+ kwargs["key_prefix"] = self.key_prefix
42
+
43
+ # Build URL, only include auth if credentials are actually provided
44
+ password = getattr(self, "password", None)
45
+ username = getattr(self, "username", None)
46
+ has_password = bool(password) and str(password).strip() not in ("", "None")
47
+ has_username = bool(username) and str(username).strip() not in ("", "None")
48
+
49
+ if has_password:
50
+ # Use quote(safe="") so every reserved char (space, +, /, :, @) is
51
+ # percent-encoded. redis-py's from_url parses the URL with urlparse
52
+ # and decodes credentials with unquote (not unquote_plus), so
53
+ # quote_plus would turn a space into "+" and round-trip to a wrong
54
+ # password.
55
+ encoded_pw = parse.quote(str(password), safe="")
56
+ encoded_username = parse.quote(str(username), safe="") if has_username else ""
57
+ auth = f"{encoded_username}:{encoded_pw}@"
58
+ else:
59
+ auth = ""
60
+
61
+ url = f"redis://{auth}{self.host}:{self.port}/{self.database}"
62
+ return RedisChatMessageHistory(session_id=self.session_id, url=url, **kwargs)
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://schemas.langflow.org/extension/v1.json",
3
+ "id": "lfx-valkey",
4
+ "version": "0.1.0",
5
+ "name": "Valkey",
6
+ "description": "Valkey component(s) as a standalone Langflow Extension Bundle.",
7
+ "lfx": {
8
+ "compat": ["1"]
9
+ },
10
+ "bundles": [
11
+ {
12
+ "name": "valkey",
13
+ "path": "components/valkey"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,48 @@
1
+ Metadata-Version: 2.4
2
+ Name: lfx-valkey
3
+ Version: 0.1.0
4
+ Summary: Valkey component(s) as a standalone Langflow Extension Bundle.
5
+ Project-URL: Homepage, https://github.com/langflow-ai/langflow
6
+ Project-URL: Documentation, https://docs.langflow.org/extensions
7
+ Project-URL: Repository, https://github.com/langflow-ai/langflow
8
+ Author-email: Langflow <contact@langflow.org>
9
+ License: MIT
10
+ Keywords: bundle,extension,langflow,lfx,valkey
11
+ Requires-Python: <3.15,>=3.10
12
+ Requires-Dist: langchain-aws<2.0.0,>=1.4.1; sys_platform == 'win32'
13
+ Requires-Dist: langchain-aws[valkey]<2.0.0,>=1.4.1; sys_platform != 'win32'
14
+ Requires-Dist: langchain-community<1.0.0,>=0.4.1
15
+ Requires-Dist: lfx<2.0.0,>=1.11.0.dev0
16
+ Requires-Dist: redis<8.0.0,>=7.4.0
17
+ Requires-Dist: structlog<26.0.0,>=25.4.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # lfx-valkey
21
+
22
+ Valkey component(s) as a standalone Langflow Extension Bundle.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install lfx-valkey
28
+ ```
29
+
30
+ The bundle is registered automatically via the `langflow.extensions`
31
+ entry-point. After install, restart your Langflow server; the bundle's
32
+ components will appear in the palette under the `valkey` group with
33
+ the namespaced IDs `ext:valkey:<Class>@official`.
34
+
35
+ ## Develop
36
+
37
+ ```bash
38
+ cd src/bundles/valkey
39
+ pip install -e .
40
+ lfx extension validate src/lfx_valkey
41
+ ```
42
+
43
+ ## Migration
44
+
45
+ Saved flows referencing the legacy class name(s) or the old import paths
46
+ under `lfx.components.valkey.*` are rewritten to the new namespaced
47
+ IDs by the migration table in
48
+ `src/lfx/src/lfx/extension/migration/migration_table.json`.
@@ -0,0 +1,10 @@
1
+ lfx_valkey/__init__.py,sha256=dMLv1-FD9nXNuFmcmT0EhM144Q-jLin1liPr7q3VY7I,492
2
+ lfx_valkey/extension.json,sha256=0mA53PZCLS7awsSGVIkYSbrIivcg6cKCr_i2heNM2Jo,339
3
+ lfx_valkey/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ lfx_valkey/components/valkey/__init__.py,sha256=tlutRPy8SOLsiGIq5rP77JkEO12M1VTlhQTPrm10Y6E,1320
5
+ lfx_valkey/components/valkey/valkey.py,sha256=kQDK3HOoKUgYT5nNV5abAc0sF1CDxBscC567vOtdQ4Q,5445
6
+ lfx_valkey/components/valkey/valkey_chat.py,sha256=HO-EUS9304gVptBZFzZLeYG2NiHhzrinrDzn4eXQ3w0,2771
7
+ lfx_valkey-0.1.0.dist-info/METADATA,sha256=JtzAdg6NO4EMNJFfAz4y2iT62DkpGbRHg3uwmHCTS4Q,1537
8
+ lfx_valkey-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
9
+ lfx_valkey-0.1.0.dist-info/entry_points.txt,sha256=Qj9LWe52n0t0zzzYlzbau-b5QcboVEz4_h1XZETPTr4,46
10
+ lfx_valkey-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [langflow.extensions]
2
+ lfx-valkey = lfx_valkey