Skip to content

Commit c3d8f32

Browse files
authored
Python client connection pooling fix and add context manager capability to protocol (deven96#69)
* utilize connection pool properly. Add ability to use protocol as a context * add formatting and linting to python actions * Refactor: Make protocol dep internal - extract base class needed by clients. - Add ai_response, ai_query - Update tests and Readme * format * fix python import path and update readme
1 parent 5c397a1 commit c3d8f32

File tree

15 files changed

+1055
-154
lines changed

15 files changed

+1055
-154
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ jobs:
7777
- name: Installing poetry dependencies
7878
working-directory: ./sdk/ahnlich-client-py
7979
run: poetry install
80+
81+
- name: Check Python Linting
82+
working-directory: ./sdk/ahnlich-client-py
83+
run: |
84+
poetry run isort . -c --profile black; echo $?
85+
poetry run black . --check; echo $?
8086
8187
- name: Set up Rust cache
8288
uses: Swatinem/rust-cache@v2

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,13 @@ Available languages are:
3535
- typescript.
3636

3737
In order to communicate effectively with the ahnlich db, you would have to extend the bincode serialization protocol automatically provided by `serde_generate`.
38-
Your message(in bytes) should be serialized and deserialized in the following format => `AHNLICH_HEADERS` + `VERSION` + `QUERY/SERVER_RESPONSE`. Bytes are `Little Endian`.
38+
Your message(in bytes) should be serialized and deserialized in the following format => `AHNLICH_HEADERS` + `VERSION` + `QUERY/SERVER_RESPONSE`. Bytes are `Little Endian`.
39+
40+
41+
## How Client Releases Work
42+
43+
The clients follow a similar process when deploying new releases.
44+
[Example with python client](https://github.com/deven96/ahnlich/blob/main/sdk/ahnlich-client-py/README.md#deploy-to-artifactory).
45+
46+
47+

ahnlich/typegen/src/tracers/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,15 @@ impl<'a> OutputFile<'a> {
134134
let _ = std::fs::create_dir_all(&output_dir);
135135
let output_file = output_dir.join(format!("{}.{extension}", self.output_file));
136136
let mut buffer = self.get_output_buffer(output_file);
137-
let installer = serde_generate::python3::Installer::new(
138-
output_dir,
139-
Some(format!("ahnlich_client_{extension}.internals")),
140-
);
137+
let import_path = Some(format!("ahnlich_client_{extension}.internals"));
138+
139+
let installer =
140+
serde_generate::python3::Installer::new(output_dir, import_path.clone());
141141
installer.install_bincode_runtime().unwrap();
142142
installer.install_serde_runtime().unwrap();
143-
serde_generate::python3::CodeGenerator::new(config).output(&mut buffer, registry)
143+
serde_generate::python3::CodeGenerator::new(config)
144+
.with_serde_package_name(import_path)
145+
.output(&mut buffer, registry)
144146
}
145147
Language::Golang => {
146148
// All packages are already published

sdk/ahnlich-client-py/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The following topics are covered:
3131
* [Delete Key](#delete-key)
3232
* [Delete Predicate](#delete-predicate)
3333
* [Bulk Requests](#bulk-requests)
34+
* [Client As Context Manager](#client-as-context-manager)
3435
* [How to Deploy to Artifactory](#deploy-to-artifactory)
3536
* [Type Meanings](#type-meanings)
3637
* [Change Log](#change-log)
@@ -334,6 +335,23 @@ response: server_response.ServerResult = client.exec()
334335

335336

336337

338+
## Client As Context Manager
339+
340+
The DB client class can be used as a context manager hereby closing the connection pool automatically upon context end.
341+
342+
343+
```py
344+
from ahnlich_client_py import AhnlichDBClient
345+
346+
347+
with client.AhnlichDBClient(address="127.0.0.1", port=port) as db_client:
348+
response: server_response.ServerResult = db_client.ping()
349+
350+
```
351+
However, closing the connection pool can be done by calling `cleanup()` on the client.
352+
353+
354+
337355
## Deploy to Artifactory
338356

339357
Replace the contents of `MSG_TAG` file with your new tag message
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from ahnlich_client_py import builders, libs
22
from ahnlich_client_py.client import AhnlichDBClient
33
from ahnlich_client_py.config import AhnlichDBPoolSettings
4-
from ahnlich_client_py.internals import db_query, db_response
5-
from ahnlich_client_py.protocol import AhnlichProtocol
4+
from ahnlich_client_py.internals import ai_query, ai_response, db_query, db_response

sdk/ahnlich-client-py/ahnlich_client_py/builders/db.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from ahnlich_client_py import exceptions as ah_exceptions
66
from ahnlich_client_py.internals import db_query
77
from ahnlich_client_py.internals import serde_types as st
8-
from ahnlich_client_py.protocol import AhnlichProtocol
98

109

1110
class NonZeroSizeInteger:
@@ -142,7 +141,3 @@ def to_server_query(self) -> db_query.ServerQuery:
142141
server_query = db_query.ServerQuery(queries=queries)
143142
self.drop()
144143
return server_query
145-
146-
def execute_requests(self, protocol: AhnlichProtocol):
147-
response = protocol.process_request(message=self.to_server_query())
148-
return response
Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,47 @@
11
import typing
22

3-
from ahnlich_client_py import builders, protocol
3+
from ahnlich_client_py import builders
4+
from ahnlich_client_py.config import AhnlichDBPoolSettings
45
from ahnlich_client_py.internals import db_query, db_response
56
from ahnlich_client_py.internals import serde_types as st
7+
from ahnlich_client_py.internals.base_client import BaseClient
68

79

8-
class AhnlichDBClient:
9-
"""Wrapper for interacting with Ahnlich database or ai"""
10+
class AhnlichDBClient(BaseClient):
11+
"""Wrapper for interacting with Ahnlich database"""
1012

11-
def __init__(self, address: str, port: int, timeout_sec: float = 5.0) -> None:
12-
self.protocol = protocol.AhnlichProtocol(
13-
address=address, port=port, timeout_sec=timeout_sec
13+
def __init__(
14+
self,
15+
address: str,
16+
port: int,
17+
connect_timeout_sec: float = 5.0,
18+
pool_settings: AhnlichDBPoolSettings = AhnlichDBPoolSettings(),
19+
) -> None:
20+
21+
super().__init__(
22+
address=address,
23+
port=port,
24+
connect_timeout_sec=connect_timeout_sec,
25+
pool_settings=pool_settings,
1426
)
15-
# would abstract this away eventually, but for now easy does it
27+
1628
self.builder = builders.AhnlichDBRequestBuilder()
1729

30+
def get_response_class(self):
31+
return db_response.ServerResult
32+
1833
def get_key(
1934
self, store_name: str, keys: typing.Sequence[db_query.Array]
2035
) -> db_response.ServerResult:
2136

2237
self.builder.get_key(store_name=store_name, keys=keys)
23-
return self.protocol.process_request(self.builder.to_server_query())
38+
return self.process_request(self.builder.to_server_query())
2439

2540
def get_by_predicate(
2641
self, store_name: str, condition: db_query.PredicateCondition
2742
) -> db_response.ServerResult:
2843
self.builder.get_by_predicate(store_name=store_name, condition=condition)
29-
return self.protocol.process_request(self.builder.to_server_query())
44+
return self.process_request(self.builder.to_server_query())
3045

3146
def get_sim_n(
3247
self,
@@ -43,13 +58,13 @@ def get_sim_n(
4358
algorithm=algorithm,
4459
condition=condition,
4560
)
46-
return self.protocol.process_request(self.builder.to_server_query())
61+
return self.process_request(self.builder.to_server_query())
4762

4863
def create_pred_index(
4964
self, store_name: str, predicates: typing.Sequence[str]
5065
) -> db_response.ServerResult:
5166
self.builder.create_pred_index(store_name=store_name, predicates=predicates)
52-
return self.protocol.process_request(self.builder.to_server_query())
67+
return self.process_request(self.builder.to_server_query())
5368

5469
def drop_pred_index(
5570
self,
@@ -62,7 +77,7 @@ def drop_pred_index(
6277
predicates=predicates,
6378
error_if_not_exists=error_if_not_exists,
6479
)
65-
return self.protocol.process_request(self.builder.to_server_query())
80+
return self.process_request(self.builder.to_server_query())
6681

6782
def set(
6883
self,
@@ -72,27 +87,27 @@ def set(
7287
],
7388
) -> db_response.ServerResult:
7489
self.builder.set(store_name=store_name, inputs=inputs)
75-
return self.protocol.process_request(self.builder.to_server_query())
90+
return self.process_request(self.builder.to_server_query())
7691

7792
def delete_key(
7893
self, store_name: str, keys: typing.Sequence[db_query.Array]
7994
) -> db_response.ServerResult:
8095
self.builder.delete_key(store_name=store_name, keys=keys)
81-
return self.protocol.process_request(self.builder.to_server_query())
96+
return self.process_request(self.builder.to_server_query())
8297

8398
def delete_predicate(
8499
self, store_name: str, condition: db_query.PredicateCondition
85100
) -> db_response.ServerResult:
86101
self.builder.delete_predicate(store_name=store_name, condition=condition)
87-
return self.protocol.process_request(self.builder.to_server_query())
102+
return self.process_request(self.builder.to_server_query())
88103

89104
def drop_store(
90105
self, store_name: str, error_if_not_exists: bool
91106
) -> db_response.ServerResult:
92107
self.builder.drop_store(
93108
store_name=store_name, error_if_not_exists=error_if_not_exists
94109
)
95-
return self.protocol.process_request(self.builder.to_server_query())
110+
return self.process_request(self.builder.to_server_query())
96111

97112
def create_store(
98113
self,
@@ -112,41 +127,32 @@ def create_store(
112127
error_if_exists=error_if_exists,
113128
)
114129
message = self.builder.to_server_query()
115-
return self.protocol.process_request(message=message)
130+
return self.process_request(message=message)
116131

117132
def list_stores(self) -> db_response.ServerResult:
118133
self.builder.list_stores()
119-
return self.protocol.process_request(self.builder.to_server_query())
134+
return self.process_request(self.builder.to_server_query())
120135

121136
def info_server(self) -> db_response.ServerResult:
122137
self.builder.info_server()
123-
return self.protocol.process_request(
138+
return self.process_request(
124139
message=self.builder.to_server_query(),
125140
)
126141

127142
def list_clients(self) -> db_response.ServerResult:
128143
self.builder.list_clients()
129-
return self.protocol.process_request(
144+
return self.process_request(
130145
message=self.builder.to_server_query(),
131146
)
132147

133148
def ping(self) -> db_response.ServerResult:
134149
self.builder.ping()
135-
return self.protocol.process_request(message=self.builder.to_server_query())
150+
return self.process_request(message=self.builder.to_server_query())
136151

137152
def pipeline(self) -> builders.AhnlichDBRequestBuilder:
138153
"""Gives you a request builder to create multple requests"""
139154
return self.builder
140155

141156
def exec(self) -> db_response.ServerResult:
142157
"""Executes a pipelined request"""
143-
return self.protocol.process_request(message=self.builder.to_server_query())
144-
145-
def close(self):
146-
"""closes the socket connection"""
147-
self.protocol.close()
148-
149-
def cleanup(self):
150-
"""closes the socket connection as well as connection pool"""
151-
self.close()
152-
self.protocol.cleanup()
158+
return self.process_request(message=self.builder.to_server_query())

0 commit comments

Comments
 (0)