gocache 0.1.0__tar.gz
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.
- gocache-0.1.0/.gitignore +8 -0
- gocache-0.1.0/PKG-INFO +328 -0
- gocache-0.1.0/README.md +306 -0
- gocache-0.1.0/examples.py +151 -0
- gocache-0.1.0/gocache/__init__.py +13 -0
- gocache-0.1.0/gocache/client.py +380 -0
- gocache-0.1.0/pyproject.toml +35 -0
- gocache-0.1.0/tests/test_client.py +323 -0
gocache-0.1.0/.gitignore
ADDED
gocache-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gocache
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client library for GoCache — a Redis-compatible in-memory cache server
|
|
5
|
+
Project-URL: Homepage, https://github.com/erickim73/gocache
|
|
6
|
+
Project-URL: Repository, https://github.com/erickim73/gocache
|
|
7
|
+
Project-URL: Issues, https://github.com/erickim73/gocache/issues
|
|
8
|
+
Author-email: Eric Kim <seyoon2006@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: cache,client,gocache,redis,resp
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# GoCache Python Client
|
|
24
|
+
|
|
25
|
+
A pure-Python client library for [GoCache](../../../README.md) — a Redis-compatible in-memory cache server. No external dependencies. Python 3.10+ only.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
No package manager needed. Copy `client.py` into your project and import from it directly.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Clone the repo
|
|
35
|
+
git clone https://github.com/erickim73/gocache.git
|
|
36
|
+
cd gocache
|
|
37
|
+
|
|
38
|
+
# Verify Python version (3.10+ required)
|
|
39
|
+
python --version
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That's it. `client.py` uses only the Python standard library (`socket`, `time`).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Running the Server
|
|
47
|
+
|
|
48
|
+
Before using the client, start the GoCache server:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# From the repo root
|
|
52
|
+
go run cmd/server/main.go
|
|
53
|
+
|
|
54
|
+
# Or if you've built the binary
|
|
55
|
+
./bin/gocache
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The server listens on `localhost:6379` by default.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from client import GoCacheClient
|
|
66
|
+
|
|
67
|
+
with GoCacheClient("localhost", 6379) as cache:
|
|
68
|
+
# Health check
|
|
69
|
+
cache.ping() # → 'PONG'
|
|
70
|
+
|
|
71
|
+
# Store and retrieve a value
|
|
72
|
+
cache.set("user:1000", "Eric") # → 'OK'
|
|
73
|
+
cache.get("user:1000") # → 'Eric'
|
|
74
|
+
|
|
75
|
+
# Missing keys return None, not an error
|
|
76
|
+
cache.get("user:9999") # → None
|
|
77
|
+
|
|
78
|
+
# Set a key that expires after 60 seconds
|
|
79
|
+
cache.set("session:token", "abc123", ex=60)
|
|
80
|
+
|
|
81
|
+
# Delete one or more keys
|
|
82
|
+
cache.delete("user:1000") # → 1 (number of keys removed)
|
|
83
|
+
cache.delete("k1", "k2", "k3") # → 3
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `with` statement guarantees the TCP connection is closed when the block exits, even if an exception occurs. For long-lived processes, you can also manage the lifecycle manually:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
cache = GoCacheClient("localhost", 6379)
|
|
90
|
+
cache.set("key", "value")
|
|
91
|
+
cache.close()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
### `GoCacheClient(host, port)`
|
|
99
|
+
|
|
100
|
+
Opens a TCP connection to the GoCache server. Raises `GoCacheConnectionError` if the server is not reachable.
|
|
101
|
+
|
|
102
|
+
| Parameter | Type | Default | Description |
|
|
103
|
+
|-----------|------|---------|-------------|
|
|
104
|
+
| `host` | `str` | `"localhost"` | Server hostname or IP address |
|
|
105
|
+
| `port` | `int` | `6379` | Server port |
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
client = GoCacheClient("localhost", 6379)
|
|
109
|
+
client = GoCacheClient("10.0.0.5", 6380) # custom host and port
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### `ping() → str`
|
|
115
|
+
|
|
116
|
+
Sends a PING to the server. Returns `'PONG'` on a healthy connection. Use this to verify the server is reachable before issuing commands.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
response = client.ping() # → 'PONG'
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `get(key) → str | None`
|
|
125
|
+
|
|
126
|
+
Retrieves the value stored at `key`. Returns `None` if the key does not exist — it does not raise an exception.
|
|
127
|
+
|
|
128
|
+
| Parameter | Type | Description |
|
|
129
|
+
|-----------|------|-------------|
|
|
130
|
+
| `key` | `str` | The key to look up |
|
|
131
|
+
|
|
132
|
+
**Returns:** `str` if the key exists, `None` if it does not.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
client.set("color", "blue")
|
|
136
|
+
|
|
137
|
+
client.get("color") # → 'blue'
|
|
138
|
+
client.get("missing") # → None
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### `set(key, value, ex=None) → str`
|
|
144
|
+
|
|
145
|
+
Stores `value` at `key`. Overwrites any existing value. Returns `'OK'` on success.
|
|
146
|
+
|
|
147
|
+
| Parameter | Type | Default | Description |
|
|
148
|
+
|-----------|------|---------|-------------|
|
|
149
|
+
| `key` | `str` | — | The key to write |
|
|
150
|
+
| `value` | `str` | — | The value to store |
|
|
151
|
+
| `ex` | `int \| None` | `None` | Optional TTL in seconds. The key is deleted automatically after this many seconds. |
|
|
152
|
+
|
|
153
|
+
**Returns:** `'OK'`
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
client.set("name", "Eric") # → 'OK' (persists until deleted)
|
|
157
|
+
client.set("session", "xyz", ex=3600) # → 'OK' (expires after 1 hour)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### `delete(*keys) → int`
|
|
163
|
+
|
|
164
|
+
Deletes one or more keys. Keys that do not exist are silently ignored and do not affect the count.
|
|
165
|
+
|
|
166
|
+
| Parameter | Type | Description |
|
|
167
|
+
|-----------|------|-------------|
|
|
168
|
+
| `*keys` | `str` | One or more keys to delete |
|
|
169
|
+
|
|
170
|
+
**Returns:** `int` — the number of keys that were actually deleted (keys that did not exist count as 0).
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
client.set("a", "1")
|
|
174
|
+
client.set("b", "2")
|
|
175
|
+
|
|
176
|
+
client.delete("a") # → 1
|
|
177
|
+
client.delete("b", "c", "d") # → 1 ("c" and "d" didn't exist)
|
|
178
|
+
client.delete("already_gone") # → 0
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### `close() → None`
|
|
184
|
+
|
|
185
|
+
Closes the TCP connection and releases the socket. After calling this, any further method calls on the client will raise an `OSError`.
|
|
186
|
+
|
|
187
|
+
If you use the client as a context manager (`with GoCacheClient(...) as c:`), `close()` is called automatically when the block exits. You only need to call it manually if you're managing the lifecycle yourself.
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
client = GoCacheClient("localhost", 6379)
|
|
191
|
+
client.set("key", "value")
|
|
192
|
+
client.close()
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### Context Manager Support
|
|
198
|
+
|
|
199
|
+
`GoCacheClient` implements `__enter__` and `__exit__`, so it can be used as a context manager. This is the recommended usage pattern — it guarantees the socket is closed regardless of whether the block exits normally or via an exception.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
with GoCacheClient("localhost", 6379) as c:
|
|
203
|
+
c.set("key", "value")
|
|
204
|
+
value = c.get("key")
|
|
205
|
+
# Socket is closed here automatically
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Error Handling
|
|
211
|
+
|
|
212
|
+
The client defines three exception types, all in `client.py`:
|
|
213
|
+
|
|
214
|
+
### `GoCacheError`
|
|
215
|
+
|
|
216
|
+
Base class for all GoCache-specific errors. Catch this if you want to handle any GoCache error in one place.
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from client import GoCacheError
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
client.set("key", "value")
|
|
223
|
+
except GoCacheError as e:
|
|
224
|
+
print(f"Something went wrong: {e}")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### `GoCacheConnectionError(GoCacheError)`
|
|
230
|
+
|
|
231
|
+
Raised when the client cannot connect to the server, or when the connection is lost mid-command.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from client import GoCacheConnectionError
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
client = GoCacheClient("localhost", 19999) # nothing listening here
|
|
238
|
+
except GoCacheConnectionError as e:
|
|
239
|
+
print(e)
|
|
240
|
+
# → Could not connect to GoCache at localhost:19999. Is the server running?
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
### `GoCacheCommandError(GoCacheError)`
|
|
246
|
+
|
|
247
|
+
Raised when the server returns a RESP error response (a `-` type message). This indicates the server understood the command but rejected it — for example, an unknown command name or wrong number of arguments.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from client import GoCacheCommandError
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
client._send("NOT_A_COMMAND")
|
|
254
|
+
client._read_response()
|
|
255
|
+
except GoCacheCommandError as e:
|
|
256
|
+
print(e) # → ERR unknown command 'NOT_A_COMMAND'
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Running the Examples
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cd pkg/client/python
|
|
265
|
+
|
|
266
|
+
# Make sure GoCache is running first
|
|
267
|
+
go run ../../../cmd/server/main.go &
|
|
268
|
+
|
|
269
|
+
python examples.py
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Expected output:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
──────────────────────────────────────────────────
|
|
276
|
+
PING — health check
|
|
277
|
+
──────────────────────────────────────────────────
|
|
278
|
+
ping() → 'PONG'
|
|
279
|
+
✓ server is reachable
|
|
280
|
+
|
|
281
|
+
──────────────────────────────────────────────────
|
|
282
|
+
SET / GET — basic read-write
|
|
283
|
+
──────────────────────────────────────────────────
|
|
284
|
+
set('user:1000:name', 'Eric') → 'OK'
|
|
285
|
+
get('user:1000:name') → 'Eric'
|
|
286
|
+
...
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Running the Tests
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
cd pkg/client/python
|
|
295
|
+
|
|
296
|
+
# Unit tests only — no server required
|
|
297
|
+
python -m unittest test_client.TestRespEncoder test_client.TestRespParser -v
|
|
298
|
+
|
|
299
|
+
# All tests — integration tests run if GoCache is reachable, skip otherwise
|
|
300
|
+
python -m unittest test_client -v
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The test suite has two layers:
|
|
304
|
+
|
|
305
|
+
- **Unit tests** (`TestRespEncoder`, `TestRespParser`) — 24 tests covering the RESP encoder and parser in isolation. No server needed.
|
|
306
|
+
- **Integration tests** (`TestGoCacheClient`) — 21 tests exercising every method against a live server. Skipped automatically with a clear message if the server is not running.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## File Structure
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
pkg/client/python/
|
|
314
|
+
├── client.py # Client library — all code lives here
|
|
315
|
+
├── examples.py # End-to-end demo script
|
|
316
|
+
├── test_client.py # Unit and integration tests
|
|
317
|
+
└── README.md # This file
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Compatibility
|
|
323
|
+
|
|
324
|
+
| | Requirement |
|
|
325
|
+
|-|-------------|
|
|
326
|
+
| Python | 3.10+ |
|
|
327
|
+
| GoCache server | any version supporting RESP protocol |
|
|
328
|
+
| Dependencies | none (standard library only) |
|
gocache-0.1.0/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# GoCache Python Client
|
|
2
|
+
|
|
3
|
+
A pure-Python client library for [GoCache](../../../README.md) — a Redis-compatible in-memory cache server. No external dependencies. Python 3.10+ only.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
No package manager needed. Copy `client.py` into your project and import from it directly.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Clone the repo
|
|
13
|
+
git clone https://github.com/erickim73/gocache.git
|
|
14
|
+
cd gocache
|
|
15
|
+
|
|
16
|
+
# Verify Python version (3.10+ required)
|
|
17
|
+
python --version
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it. `client.py` uses only the Python standard library (`socket`, `time`).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Running the Server
|
|
25
|
+
|
|
26
|
+
Before using the client, start the GoCache server:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# From the repo root
|
|
30
|
+
go run cmd/server/main.go
|
|
31
|
+
|
|
32
|
+
# Or if you've built the binary
|
|
33
|
+
./bin/gocache
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The server listens on `localhost:6379` by default.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from client import GoCacheClient
|
|
44
|
+
|
|
45
|
+
with GoCacheClient("localhost", 6379) as cache:
|
|
46
|
+
# Health check
|
|
47
|
+
cache.ping() # → 'PONG'
|
|
48
|
+
|
|
49
|
+
# Store and retrieve a value
|
|
50
|
+
cache.set("user:1000", "Eric") # → 'OK'
|
|
51
|
+
cache.get("user:1000") # → 'Eric'
|
|
52
|
+
|
|
53
|
+
# Missing keys return None, not an error
|
|
54
|
+
cache.get("user:9999") # → None
|
|
55
|
+
|
|
56
|
+
# Set a key that expires after 60 seconds
|
|
57
|
+
cache.set("session:token", "abc123", ex=60)
|
|
58
|
+
|
|
59
|
+
# Delete one or more keys
|
|
60
|
+
cache.delete("user:1000") # → 1 (number of keys removed)
|
|
61
|
+
cache.delete("k1", "k2", "k3") # → 3
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `with` statement guarantees the TCP connection is closed when the block exits, even if an exception occurs. For long-lived processes, you can also manage the lifecycle manually:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
cache = GoCacheClient("localhost", 6379)
|
|
68
|
+
cache.set("key", "value")
|
|
69
|
+
cache.close()
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
### `GoCacheClient(host, port)`
|
|
77
|
+
|
|
78
|
+
Opens a TCP connection to the GoCache server. Raises `GoCacheConnectionError` if the server is not reachable.
|
|
79
|
+
|
|
80
|
+
| Parameter | Type | Default | Description |
|
|
81
|
+
|-----------|------|---------|-------------|
|
|
82
|
+
| `host` | `str` | `"localhost"` | Server hostname or IP address |
|
|
83
|
+
| `port` | `int` | `6379` | Server port |
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
client = GoCacheClient("localhost", 6379)
|
|
87
|
+
client = GoCacheClient("10.0.0.5", 6380) # custom host and port
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `ping() → str`
|
|
93
|
+
|
|
94
|
+
Sends a PING to the server. Returns `'PONG'` on a healthy connection. Use this to verify the server is reachable before issuing commands.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
response = client.ping() # → 'PONG'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### `get(key) → str | None`
|
|
103
|
+
|
|
104
|
+
Retrieves the value stored at `key`. Returns `None` if the key does not exist — it does not raise an exception.
|
|
105
|
+
|
|
106
|
+
| Parameter | Type | Description |
|
|
107
|
+
|-----------|------|-------------|
|
|
108
|
+
| `key` | `str` | The key to look up |
|
|
109
|
+
|
|
110
|
+
**Returns:** `str` if the key exists, `None` if it does not.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
client.set("color", "blue")
|
|
114
|
+
|
|
115
|
+
client.get("color") # → 'blue'
|
|
116
|
+
client.get("missing") # → None
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `set(key, value, ex=None) → str`
|
|
122
|
+
|
|
123
|
+
Stores `value` at `key`. Overwrites any existing value. Returns `'OK'` on success.
|
|
124
|
+
|
|
125
|
+
| Parameter | Type | Default | Description |
|
|
126
|
+
|-----------|------|---------|-------------|
|
|
127
|
+
| `key` | `str` | — | The key to write |
|
|
128
|
+
| `value` | `str` | — | The value to store |
|
|
129
|
+
| `ex` | `int \| None` | `None` | Optional TTL in seconds. The key is deleted automatically after this many seconds. |
|
|
130
|
+
|
|
131
|
+
**Returns:** `'OK'`
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
client.set("name", "Eric") # → 'OK' (persists until deleted)
|
|
135
|
+
client.set("session", "xyz", ex=3600) # → 'OK' (expires after 1 hour)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
### `delete(*keys) → int`
|
|
141
|
+
|
|
142
|
+
Deletes one or more keys. Keys that do not exist are silently ignored and do not affect the count.
|
|
143
|
+
|
|
144
|
+
| Parameter | Type | Description |
|
|
145
|
+
|-----------|------|-------------|
|
|
146
|
+
| `*keys` | `str` | One or more keys to delete |
|
|
147
|
+
|
|
148
|
+
**Returns:** `int` — the number of keys that were actually deleted (keys that did not exist count as 0).
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
client.set("a", "1")
|
|
152
|
+
client.set("b", "2")
|
|
153
|
+
|
|
154
|
+
client.delete("a") # → 1
|
|
155
|
+
client.delete("b", "c", "d") # → 1 ("c" and "d" didn't exist)
|
|
156
|
+
client.delete("already_gone") # → 0
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### `close() → None`
|
|
162
|
+
|
|
163
|
+
Closes the TCP connection and releases the socket. After calling this, any further method calls on the client will raise an `OSError`.
|
|
164
|
+
|
|
165
|
+
If you use the client as a context manager (`with GoCacheClient(...) as c:`), `close()` is called automatically when the block exits. You only need to call it manually if you're managing the lifecycle yourself.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
client = GoCacheClient("localhost", 6379)
|
|
169
|
+
client.set("key", "value")
|
|
170
|
+
client.close()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### Context Manager Support
|
|
176
|
+
|
|
177
|
+
`GoCacheClient` implements `__enter__` and `__exit__`, so it can be used as a context manager. This is the recommended usage pattern — it guarantees the socket is closed regardless of whether the block exits normally or via an exception.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
with GoCacheClient("localhost", 6379) as c:
|
|
181
|
+
c.set("key", "value")
|
|
182
|
+
value = c.get("key")
|
|
183
|
+
# Socket is closed here automatically
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Error Handling
|
|
189
|
+
|
|
190
|
+
The client defines three exception types, all in `client.py`:
|
|
191
|
+
|
|
192
|
+
### `GoCacheError`
|
|
193
|
+
|
|
194
|
+
Base class for all GoCache-specific errors. Catch this if you want to handle any GoCache error in one place.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from client import GoCacheError
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
client.set("key", "value")
|
|
201
|
+
except GoCacheError as e:
|
|
202
|
+
print(f"Something went wrong: {e}")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `GoCacheConnectionError(GoCacheError)`
|
|
208
|
+
|
|
209
|
+
Raised when the client cannot connect to the server, or when the connection is lost mid-command.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from client import GoCacheConnectionError
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
client = GoCacheClient("localhost", 19999) # nothing listening here
|
|
216
|
+
except GoCacheConnectionError as e:
|
|
217
|
+
print(e)
|
|
218
|
+
# → Could not connect to GoCache at localhost:19999. Is the server running?
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### `GoCacheCommandError(GoCacheError)`
|
|
224
|
+
|
|
225
|
+
Raised when the server returns a RESP error response (a `-` type message). This indicates the server understood the command but rejected it — for example, an unknown command name or wrong number of arguments.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from client import GoCacheCommandError
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
client._send("NOT_A_COMMAND")
|
|
232
|
+
client._read_response()
|
|
233
|
+
except GoCacheCommandError as e:
|
|
234
|
+
print(e) # → ERR unknown command 'NOT_A_COMMAND'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Running the Examples
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
cd pkg/client/python
|
|
243
|
+
|
|
244
|
+
# Make sure GoCache is running first
|
|
245
|
+
go run ../../../cmd/server/main.go &
|
|
246
|
+
|
|
247
|
+
python examples.py
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Expected output:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
──────────────────────────────────────────────────
|
|
254
|
+
PING — health check
|
|
255
|
+
──────────────────────────────────────────────────
|
|
256
|
+
ping() → 'PONG'
|
|
257
|
+
✓ server is reachable
|
|
258
|
+
|
|
259
|
+
──────────────────────────────────────────────────
|
|
260
|
+
SET / GET — basic read-write
|
|
261
|
+
──────────────────────────────────────────────────
|
|
262
|
+
set('user:1000:name', 'Eric') → 'OK'
|
|
263
|
+
get('user:1000:name') → 'Eric'
|
|
264
|
+
...
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Running the Tests
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
cd pkg/client/python
|
|
273
|
+
|
|
274
|
+
# Unit tests only — no server required
|
|
275
|
+
python -m unittest test_client.TestRespEncoder test_client.TestRespParser -v
|
|
276
|
+
|
|
277
|
+
# All tests — integration tests run if GoCache is reachable, skip otherwise
|
|
278
|
+
python -m unittest test_client -v
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The test suite has two layers:
|
|
282
|
+
|
|
283
|
+
- **Unit tests** (`TestRespEncoder`, `TestRespParser`) — 24 tests covering the RESP encoder and parser in isolation. No server needed.
|
|
284
|
+
- **Integration tests** (`TestGoCacheClient`) — 21 tests exercising every method against a live server. Skipped automatically with a clear message if the server is not running.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## File Structure
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
pkg/client/python/
|
|
292
|
+
├── client.py # Client library — all code lives here
|
|
293
|
+
├── examples.py # End-to-end demo script
|
|
294
|
+
├── test_client.py # Unit and integration tests
|
|
295
|
+
└── README.md # This file
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Compatibility
|
|
301
|
+
|
|
302
|
+
| | Requirement |
|
|
303
|
+
|-|-------------|
|
|
304
|
+
| Python | 3.10+ |
|
|
305
|
+
| GoCache server | any version supporting RESP protocol |
|
|
306
|
+
| Dependencies | none (standard library only) |
|