-
Notifications
You must be signed in to change notification settings - Fork 7
Add embedded FalkorDB lite mode support #164
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
cb08776
6f581fc
b187279
67136fb
bab69e6
cb34d7c
a78af3a
a750095
0c328cd
87b0c11
f562609
e8f0e66
7a9e66e
1b1b8ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import asyncio | ||
| import redis.asyncio as redis | ||
| from .cluster import * | ||
| from .graph import AsyncGraph | ||
|
|
@@ -64,31 +65,77 @@ def __init__( | |
| reinitialize_steps=5, | ||
| read_from_replicas=False, | ||
| address_remap=None, | ||
| embedded=False, | ||
| db_path=None, | ||
| embedded_config=None, | ||
| startup_timeout=10.0, | ||
| connection_acquire_timeout=5.0, | ||
| ): | ||
| self._embedded_server = None | ||
|
|
||
| conn = redis.Redis(host=host, port=port, db=0, password=password, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| connection_pool=connection_pool, | ||
| unix_socket_path=unix_socket_path, | ||
| encoding=encoding, encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, ssl=ssl, | ||
| ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, | ||
| ssl_cert_reqs=ssl_cert_reqs, | ||
| ssl_ca_certs=ssl_ca_certs, | ||
| ssl_ca_data=ssl_ca_data, | ||
| ssl_check_hostname=ssl_check_hostname, | ||
| max_connections=max_connections, | ||
| single_connection_client=single_connection_client, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, lib_name=lib_name, | ||
| lib_version=lib_version, username=username, | ||
| retry=retry, redis_connect_func=connect_func, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol) | ||
| if embedded: | ||
| from ..lite.server import EmbeddedServer | ||
|
|
||
| if max_connections is None: | ||
| max_connections = 16 | ||
|
|
||
| server = EmbeddedServer( | ||
| db_path=db_path, | ||
| config=embedded_config, | ||
| startup_timeout=startup_timeout, | ||
| ) | ||
| self._embedded_server = server | ||
| connection_pool = redis.BlockingConnectionPool( | ||
| connection_class=redis.UnixDomainSocketConnection, | ||
| path=server.unix_socket_path, | ||
| max_connections=max_connections, | ||
| timeout=connection_acquire_timeout, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| encoding=encoding, | ||
| encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, | ||
| retry=retry, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, | ||
| lib_name=lib_name, | ||
| lib_version=lib_version, | ||
| username=username, | ||
| password=password, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol, | ||
| ) | ||
|
Comment on lines
+84
to
+118
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking synchronous calls inside async
Consider wrapping the server startup in Example: async factory method`@classmethod`
async def create(cls, *, embedded=False, db_path=None, embedded_config=None,
startup_timeout=10.0, max_connections=None, **kwargs):
if embedded:
import asyncio
from ..lite.server import EmbeddedServer
if max_connections is None:
max_connections = 16
server = await asyncio.to_thread(
EmbeddedServer,
db_path=db_path,
config=embedded_config,
startup_timeout=startup_timeout,
)
kwargs["connection_pool"] = redis.BlockingConnectionPool(
connection_class=redis.UnixDomainSocketConnection,
path=server.unix_socket_path,
max_connections=max_connections,
...
)
instance = cls(**kwargs)
instance._embedded_server = server
return instance
else:
return cls(**kwargs)
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if embedded: | ||
| conn = redis.Redis(connection_pool=connection_pool, | ||
| single_connection_client=single_connection_client) | ||
| else: | ||
| conn = redis.Redis(host=host, port=port, db=0, password=password, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| connection_pool=connection_pool, | ||
| unix_socket_path=unix_socket_path, | ||
| encoding=encoding, encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, ssl=ssl, | ||
| ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, | ||
| ssl_cert_reqs=ssl_cert_reqs, | ||
| ssl_ca_certs=ssl_ca_certs, | ||
| ssl_ca_data=ssl_ca_data, | ||
| ssl_check_hostname=ssl_check_hostname, | ||
| max_connections=max_connections, | ||
| single_connection_client=single_connection_client, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, lib_name=lib_name, | ||
| lib_version=lib_version, username=username, | ||
| retry=retry, redis_connect_func=connect_func, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol) | ||
|
|
||
| if Is_Cluster(conn): | ||
| conn = Cluster_Conn( | ||
|
|
@@ -106,6 +153,19 @@ def __init__( | |
| self.flushdb = conn.flushdb | ||
| self.execute_command = conn.execute_command | ||
|
|
||
| async def close(self): | ||
| if hasattr(self, "connection") and self.connection is not None: | ||
| await self.connection.aclose() | ||
| if self._embedded_server is not None: | ||
| await asyncio.to_thread(self._embedded_server.stop) | ||
| self._embedded_server = None | ||
|
|
||
| async def __aenter__(self): | ||
| return self | ||
|
|
||
| async def __aexit__(self, exc_type, exc, tb): | ||
| await self.close() | ||
|
|
||
| @classmethod | ||
| def from_url(cls, url: str, **kwargs) -> "FalkorDB": | ||
| """ | ||
|
|
@@ -287,4 +347,3 @@ async def udf_delete(self, lib: str): | |
| resp = await self.connection.execute_command(UDF_CMD, "DELETE", lib) | ||
|
|
||
| return resp | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,48 +72,95 @@ def __init__( | |
| dynamic_startup_nodes=True, | ||
| url=None, | ||
| address_remap=None, | ||
| embedded=False, | ||
| db_path=None, | ||
| embedded_config=None, | ||
| startup_timeout=10.0, | ||
| ): | ||
| self._embedded_server = None | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Same root cause as the async variant: instances reaching 🐛 Proposed fix class FalkorDB:
"""
FalkorDB Class for interacting with a FalkorDB server.
...
"""
+ _embedded_server = None
+
def __init__(
self,
...🧰 Tools🪛 GitHub Actions: Lint[error] Ruff format check would reformat this file. 🤖 Prompt for AI Agents |
||
|
|
||
| conn = redis.Redis( | ||
| host=host, | ||
| port=port, | ||
| db=0, | ||
| password=password, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| connection_pool=connection_pool, | ||
| unix_socket_path=unix_socket_path, | ||
| encoding=encoding, | ||
| encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, | ||
| ssl=ssl, | ||
| ssl_keyfile=ssl_keyfile, | ||
| ssl_certfile=ssl_certfile, | ||
| ssl_cert_reqs=ssl_cert_reqs, | ||
| ssl_ca_certs=ssl_ca_certs, | ||
| ssl_ca_path=ssl_ca_path, | ||
| ssl_ca_data=ssl_ca_data, | ||
| ssl_check_hostname=ssl_check_hostname, | ||
| ssl_password=ssl_password, | ||
| ssl_validate_ocsp=ssl_validate_ocsp, | ||
| ssl_validate_ocsp_stapled=ssl_validate_ocsp_stapled, | ||
| ssl_ocsp_context=ssl_ocsp_context, | ||
| ssl_ocsp_expected_cert=ssl_ocsp_expected_cert, | ||
| max_connections=max_connections, | ||
| single_connection_client=single_connection_client, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, | ||
| lib_name=lib_name, | ||
| lib_version=lib_version, | ||
| username=username, | ||
| retry=retry, | ||
| redis_connect_func=connect_func, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol, | ||
| ) | ||
| if embedded: | ||
| from .lite.server import EmbeddedServer | ||
|
|
||
| if max_connections is None: | ||
| max_connections = 16 | ||
|
|
||
| server = EmbeddedServer( | ||
| db_path=db_path, | ||
| config=embedded_config, | ||
| startup_timeout=startup_timeout, | ||
| ) | ||
| self._embedded_server = server | ||
|
|
||
| connection_pool = redis.ConnectionPool( | ||
| connection_class=redis.UnixDomainSocketConnection, | ||
| path=server.unix_socket_path, | ||
| max_connections=max_connections, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| encoding=encoding, | ||
| encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, | ||
| retry=retry, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, | ||
| lib_name=lib_name, | ||
| lib_version=lib_version, | ||
| username=username, | ||
| password=password, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol, | ||
| ) | ||
|
|
||
| if embedded: | ||
| conn = redis.Redis( | ||
| connection_pool=connection_pool, | ||
| single_connection_client=single_connection_client, | ||
| ) | ||
| else: | ||
| conn = redis.Redis( | ||
| host=host, | ||
| port=port, | ||
| db=0, | ||
| password=password, | ||
| socket_timeout=socket_timeout, | ||
| socket_connect_timeout=socket_connect_timeout, | ||
| socket_keepalive=socket_keepalive, | ||
| socket_keepalive_options=socket_keepalive_options, | ||
| connection_pool=connection_pool, | ||
| unix_socket_path=unix_socket_path, | ||
| encoding=encoding, | ||
| encoding_errors=encoding_errors, | ||
| decode_responses=True, | ||
| retry_on_error=retry_on_error, | ||
| ssl=ssl, | ||
| ssl_keyfile=ssl_keyfile, | ||
| ssl_certfile=ssl_certfile, | ||
| ssl_cert_reqs=ssl_cert_reqs, | ||
| ssl_ca_certs=ssl_ca_certs, | ||
| ssl_ca_path=ssl_ca_path, | ||
| ssl_ca_data=ssl_ca_data, | ||
| ssl_check_hostname=ssl_check_hostname, | ||
| ssl_password=ssl_password, | ||
| ssl_validate_ocsp=ssl_validate_ocsp, | ||
| ssl_validate_ocsp_stapled=ssl_validate_ocsp_stapled, | ||
| ssl_ocsp_context=ssl_ocsp_context, | ||
| ssl_ocsp_expected_cert=ssl_ocsp_expected_cert, | ||
| max_connections=max_connections, | ||
| single_connection_client=single_connection_client, | ||
| health_check_interval=health_check_interval, | ||
| client_name=client_name, | ||
| lib_name=lib_name, | ||
| lib_version=lib_version, | ||
| username=username, | ||
| retry=retry, | ||
| redis_connect_func=connect_func, | ||
| credential_provider=credential_provider, | ||
| protocol=protocol, | ||
| ) | ||
|
|
||
| if Is_Sentinel(conn): | ||
| self.sentinel, self.service_name = Sentinel_Conn(conn, ssl) | ||
|
|
@@ -137,6 +184,26 @@ def __init__( | |
| self.flushdb = conn.flushdb | ||
| self.execute_command = conn.execute_command | ||
|
|
||
| def close(self) -> None: | ||
| """Close the current DB connection and stop the embedded server if present.""" | ||
| if hasattr(self, "connection") and self.connection is not None: | ||
| self.connection.close() | ||
| if self._embedded_server is not None: | ||
| self._embedded_server.stop() | ||
| self._embedded_server = None | ||
|
|
||
| def __enter__(self) -> "FalkorDB": | ||
| return self | ||
|
|
||
| def __exit__(self, exc_type, exc, tb): | ||
| self.close() | ||
|
|
||
| def __del__(self): | ||
| try: | ||
| self.close() | ||
| except Exception: | ||
github-code-quality[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
github-code-quality[bot] marked this conversation as resolved.
Fixed
Show fixed
Hide fixed
|
||
| pass | ||
|
|
||
| @classmethod | ||
| def from_url(cls, url: str, **kwargs) -> "FalkorDB": | ||
| """ | ||
|
|
@@ -326,4 +393,3 @@ def udf_delete(self, lib: str): | |
| resp = self.connection.execute_command(UDF_CMD, "DELETE", lib) | ||
|
|
||
| return resp | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| """ | ||
| Embedded FalkorDB support. | ||
|
|
||
| Install with: | ||
| pip install falkordb[lite] | ||
| """ | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AttributeError: 'FalkorDB' object has no attribute '_embedded_server'— CI test failure.Although
__init__setsself._embedded_server = Noneat line 80, the CI test fails withAttributeErrorat line 282, indicating instances are reachingaclose()without_embedded_serverset (e.g., viaobject.__new__, a patched__init__, or mock-created instances in existing tests). Add a class-level declaration as a safe default:🐛 Proposed fix
class FalkorDB: """ Asynchronous FalkorDB Class for interacting with a FalkorDB server. ... """ + _embedded_server = None + def __init__( self, ...🧰 Tools
🪛 GitHub Actions: Lint
[error] Ruff format check would reformat this file.
🤖 Prompt for AI Agents