trpc-redis-python 1.0.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.
trpc_redis/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ # Tencent is pleased to support the open source community by making tRPC-Python-Database available.
2
+ #
3
+ # Copyright (C) 2026 Tencent. All rights reserved.
4
+ #
5
+ # tRPC-Python-Database is licensed under Apache-2.0.
6
+ """Redis client for the tRPC Python framework.
7
+
8
+ Importing this package registers the Redis codec and transport plugins and
9
+ re-exports the public client types.
10
+ """
11
+
12
+ from ._client import create_rsp_object
13
+ from ._client import get_options
14
+ from ._client import Client
15
+ from ._codec import ClientCodec
16
+ from ._constants import RequestMode
17
+ from ._transport import RedisClientTransport
18
+ from ._types import Request
19
+ from ._types import Response
20
+
21
+ __all__ = [
22
+ 'Request',
23
+ 'Response',
24
+ 'RequestMode',
25
+ 'create_rsp_object',
26
+ 'get_options',
27
+ 'Client',
28
+ 'RedisClientTransport',
29
+ 'ClientCodec',
30
+ ]
trpc_redis/_client.py ADDED
@@ -0,0 +1,265 @@
1
+ # Tencent is pleased to support the open source community by making tRPC-Python-Database available.
2
+ #
3
+ # Copyright (C) 2026 Tencent. All rights reserved.
4
+ #
5
+ # tRPC-Python-Database is licensed under Apache-2.0.
6
+ """
7
+ Redis client module for accessing Redis servers via the tRPC framework.
8
+
9
+ This module provides a Client class that wraps the tRPC client for Redis operations, supporting command, pipeline,
10
+ and pubsub modes. It also provides utilities for configuring Redis client options.
11
+
12
+ Usage:
13
+ from trpc_redis import Client as RedisClient
14
+ client = RedisClient("redis", opts)
15
+ await client.execute_command(ctx, "set", ["key", "value"])
16
+ await client.create_pipeline(ctx)
17
+ await client.create_pubsub(ctx, "channel")
18
+ """
19
+
20
+ import threading
21
+ from typing import Callable
22
+ from typing import Dict
23
+ from typing import List
24
+ from typing import Optional
25
+
26
+ from redis.asyncio.client import Pipeline
27
+ from redis.asyncio.client import PubSub
28
+ from redis.typing import ResponseT as RedisResponse
29
+ from redis.typing import KeyT as RedisKey
30
+ from redis.typing import ChannelT as RedisChannelT
31
+
32
+ from trpc_sdk.abc import TrpcContext
33
+ from trpc_sdk.client import Client as TrpcClient
34
+ from trpc_sdk.client import Options
35
+ from trpc_sdk.codec import clone_client_message
36
+ from trpc_sdk.config import TrpcConfig
37
+ from trpc_sdk.serialization import SerializationType
38
+ from ._constants import PASSWORD
39
+ from ._constants import REDIS_NAME
40
+ from ._constants import RequestMode
41
+ from ._types import Request
42
+ from ._types import Response
43
+
44
+
45
+ def create_rsp_object():
46
+ """
47
+ Construct a new response object. This returns None because the transport will override it.
48
+
49
+ Returns:
50
+ None
51
+ """
52
+ return None
53
+
54
+
55
+ def redis_config_option(redis_config: Dict) -> Callable:
56
+ """
57
+ Option setter for specifying Redis backend configuration.
58
+
59
+ Args:
60
+ redis_config (Dict): Redis configuration dictionary.
61
+
62
+ Returns:
63
+ Callable: Option setter function.
64
+ """
65
+
66
+ def func(opts: Options):
67
+ setattr(opts.call_options, REDIS_NAME, redis_config)
68
+
69
+ return func
70
+
71
+
72
+ def get_options(opts: List) -> List:
73
+ """
74
+ Get options for the Redis client, including required Redis fields.
75
+
76
+ Args:
77
+ opts (List): Additional options to include.
78
+
79
+ Returns:
80
+ List: List of options for the client.
81
+ """
82
+ needed = (Options()
83
+ .set_protocol(REDIS_NAME)
84
+ .set_serialization_type(SerializationType.Noop)
85
+ .set_disable_service_router(True))
86
+ options = [needed]
87
+ if opts:
88
+ options.extend(opts)
89
+ return options
90
+
91
+
92
+ class Client:
93
+ """
94
+ Redis client wrapper for the tRPC framework.
95
+
96
+ This class wraps the tRPC client to provide Redis command, pipeline, and pubsub operations.
97
+
98
+ Attributes:
99
+ client (TrpcClient): The underlying tRPC client object.
100
+ __service_name (str): The service name.
101
+ __opts (List): List of client options.
102
+ """
103
+
104
+ def __init__(self, name: str, opts: Optional[List] = None):
105
+ """
106
+ Initialize the Redis client.
107
+
108
+ Args:
109
+ name (str): Service name.
110
+ opts (Optional[List]): List of options.
111
+ """
112
+ self.client = TrpcClient()
113
+ self.__service_name = name
114
+ self.__opts = get_options(opts or [])
115
+ self._is_load_config = False
116
+ self._config_lock = threading.Lock()
117
+
118
+ @property
119
+ def service_name(self):
120
+ """
121
+ Get the service name.
122
+
123
+ Returns:
124
+ str: The service name.
125
+ """
126
+ return self.__service_name
127
+
128
+ def __getattr__(self, item):
129
+ """
130
+ Override attribute access to provide dynamic command methods.
131
+
132
+ Args:
133
+ item (str): The attribute name (command name).
134
+
135
+ Returns:
136
+ Callable: An async function that calls execute_command(ctx, item, args).
137
+ """
138
+
139
+ async def func(ctx, *args, **kwargs):
140
+ return await self.execute_command(ctx, item, *args, **kwargs)
141
+
142
+ return func
143
+
144
+ def _get_config(self):
145
+ """
146
+ Load configuration from global config if available.
147
+
148
+ Safe to call from several handler threads at once: the options are
149
+ appended once, before the loaded flag is set.
150
+ """
151
+ if self._is_load_config:
152
+ return
153
+ with self._config_lock:
154
+ if self._is_load_config:
155
+ return
156
+ self.__opts.extend(self._config_options())
157
+ self._is_load_config = True
158
+
159
+ def _config_options(self) -> List:
160
+ """
161
+ Build the options that the global config adds for this service.
162
+
163
+ Returns:
164
+ List: Password and Redis backend options; empty when not configured.
165
+ """
166
+ parser = TrpcConfig.config_parser()
167
+ if parser is None:
168
+ return []
169
+ client_redis_config_dict = {}
170
+ client_conf_dict = parser.get("client", {}) or {}
171
+ services = client_conf_dict.get('service', []) or []
172
+ for service in services:
173
+ if service.get('name') == self.__service_name:
174
+ client_redis_config_dict = service
175
+ break
176
+ if not client_redis_config_dict:
177
+ return []
178
+ options = []
179
+ password = client_redis_config_dict.get(PASSWORD, None)
180
+ if password:
181
+ options.append(Options().set_password(password))
182
+ redis_config = client_redis_config_dict.get(REDIS_NAME, {})
183
+ if redis_config:
184
+ options.append(redis_config_option(redis_config))
185
+ return options
186
+
187
+ async def execute_command(self, ctx: TrpcContext, cmd: str, *args, **kwargs) -> bytes:
188
+ """
189
+ Execute a Redis command via tRPC.
190
+
191
+ Args:
192
+ ctx (TrpcContext): The client context.
193
+ cmd (str): The Redis command.
194
+ args (Optional[List]): Arguments for the command.
195
+
196
+ Returns:
197
+ bytes: The reply as bytes.
198
+ """
199
+ self._get_config()
200
+ req = Request(command=cmd, mode=RequestMode.COMMAND, args=args, kwargs=kwargs)
201
+ ctx, msg = clone_client_message(ctx)
202
+ msg.set_compress_type(0)
203
+ msg.set_serialization_type(-1)
204
+ msg.set_client_rpc_name(f"/{self.__service_name}/{cmd}")
205
+ msg.set_callee_service_name(self.__service_name)
206
+ msg.set_client_req_head(req)
207
+
208
+ await self.client.invoke(ctx, req, create_rsp_object, self.__opts)
209
+ return msg.client_rsp_head.reply
210
+
211
+ async def delete(self, ctx: TrpcContext, *names: RedisKey) -> RedisResponse:
212
+ """
213
+ Delete a key from Redis.
214
+ """
215
+ return await self.execute_command(ctx, "DEL", *names)
216
+
217
+ async def create_pipeline(self, ctx: TrpcContext) -> Pipeline:
218
+ """
219
+ Create a Redis pipeline via tRPC.
220
+
221
+ Args:
222
+ ctx (TrpcContext): The client context.
223
+
224
+ Returns:
225
+ Pipeline: The pipeline object.
226
+ """
227
+ self._get_config()
228
+ req = Request(mode=RequestMode.PIPELINE)
229
+ resp = Response(None)
230
+ ctx, msg = clone_client_message(ctx)
231
+ msg.set_compress_type(0)
232
+ msg.set_serialization_type(-1)
233
+ msg.set_client_rpc_name(f"/{self.__service_name}/pipeline")
234
+ msg.set_callee_service_name(self.__service_name)
235
+ msg.set_client_req_head(req)
236
+ msg.set_client_rsp_head(resp)
237
+
238
+ await self.client.invoke(ctx, req, create_rsp_object, self.__opts)
239
+ return msg.client_rsp_head.reply
240
+
241
+ async def create_pubsub(self, ctx: TrpcContext, *args: RedisChannelT, **kwargs: Callable) -> PubSub:
242
+ """
243
+ Create a Redis pubsub object via tRPC and subscribe to the given channels.
244
+
245
+ Args:
246
+ ctx (TrpcContext): The client context.
247
+ *args (RedisChannelT): Channels to subscribe to.
248
+ **kwargs (Callable): Channels mapped to their message handlers.
249
+
250
+ Returns:
251
+ PubSub: The pubsub object.
252
+ """
253
+ self._get_config()
254
+ req = Request(mode=RequestMode.PUBSUB, args=args, kwargs=kwargs)
255
+ resp = Response(None)
256
+ ctx, msg = clone_client_message(ctx)
257
+ msg.set_compress_type(0)
258
+ msg.set_serialization_type(-1)
259
+ msg.set_client_rpc_name(f"/{self.__service_name}/pubsub")
260
+ msg.set_callee_service_name(self.__service_name)
261
+ msg.set_client_req_head(req)
262
+ msg.set_client_rsp_head(resp)
263
+
264
+ await self.client.invoke(ctx, req, create_rsp_object, self.__opts)
265
+ return msg.client_rsp_head.reply
trpc_redis/_codec.py ADDED
@@ -0,0 +1,69 @@
1
+ # Tencent is pleased to support the open source community by making tRPC-Python-Database available.
2
+ #
3
+ # Copyright (C) 2026 Tencent. All rights reserved.
4
+ #
5
+ # tRPC-Python-Database is licensed under Apache-2.0.
6
+ """
7
+ Redis protocol codec module for tRPC framework.
8
+
9
+ This module provides a codec for encoding and decoding messages using the Redis protocol.
10
+ It registers a custom codec plugin for Redis, which is used by the tRPC client to handle Redis-specific message
11
+ serialization and deserialization.
12
+
13
+ Usage:
14
+ The codec is registered automatically as a plugin and is used internally by the tRPC Redis client.
15
+ """
16
+
17
+ import os
18
+ from typing import Any
19
+
20
+ from trpc_sdk.abc import BaseCodec
21
+ from trpc_sdk.abc import PluginInitPos
22
+ from trpc_sdk.abc import PluginType
23
+ from trpc_sdk.abc import TrpcContext
24
+ from trpc_sdk.codec import get_message
25
+ from trpc_sdk.plugin import register_plugin
26
+
27
+ from ._constants import REDIS_NAME
28
+
29
+
30
+ @register_plugin(PluginType.CLIENT_CODEC, REDIS_NAME, init_pos=PluginInitPos.WORKER)
31
+ class ClientCodec(BaseCodec):
32
+ """Unary client codec for the Redis protocol.
33
+
34
+ Redis I/O is done entirely by the Redis transport through ``redis-py``,
35
+ so this codec produces no wire bytes. It only fills in client metadata on
36
+ encode and turns the transport reply into bytes on decode.
37
+ """
38
+
39
+ def encode_data(self, context: TrpcContext, body: Any) -> bytes:
40
+ """Encode a request frame; the entry the client invoke path calls.
41
+
42
+ Sets a default caller service name when the message has none.
43
+
44
+ Args:
45
+ context: RPC context whose client message is encoded.
46
+ body: Request body. Unused, the request lives in
47
+ ``message.client_req_head``.
48
+
49
+ Returns:
50
+ Empty bytes; the transport sends the actual Redis command.
51
+ """
52
+ message = get_message(context)
53
+ if message.caller_service_name == "":
54
+ message.set_caller_service_name(f"trpc.redis.{os.path.basename(os.getcwd())}.service")
55
+ return b""
56
+
57
+ def decode_data(self, context: TrpcContext, buffer: Any) -> bytes:
58
+ """Decode the transport reply into bytes; the entry the client invoke path calls.
59
+
60
+ Args:
61
+ context: RPC context of the call. Unused.
62
+ buffer: Reply returned by the Redis transport; may be ``None``.
63
+
64
+ Returns:
65
+ ``b""`` for ``None``, otherwise ``str(buffer)`` encoded as UTF-8.
66
+ """
67
+ if buffer is None:
68
+ return b""
69
+ return bytes(str(buffer), encoding='utf8')
@@ -0,0 +1,52 @@
1
+ # Tencent is pleased to support the open source community by making tRPC-Python-Database available.
2
+ #
3
+ # Copyright (C) 2026 Tencent. All rights reserved.
4
+ #
5
+ # tRPC-Python-Database is licensed under Apache-2.0.
6
+ """
7
+ Redis constants module for tRPC framework.
8
+
9
+ This module defines constants used in the Redis client implementation.
10
+ It includes configuration keys and default values for Redis connections.
11
+
12
+ Usage:
13
+ The constants are used to configure Redis client options and connection parameters.
14
+ """
15
+
16
+ from enum import Enum
17
+
18
+ # Plugin name of the codec and transport, the client protocol name, and the
19
+ # key of the extra ConnectionPool kwargs in YAML and ``call_options``.
20
+ REDIS_NAME: str = "redis"
21
+
22
+ # ``redis.asyncio.ConnectionPool`` kwargs the transport fills with a default
23
+ # when the ``redis:`` config block does not set them.
24
+ MAX_CONNECTIONS: str = "max_connections"
25
+ SOCKET_CONNECT_TIMEOUT: str = "socket_connect_timeout"
26
+ DECODE_RESPONSES: str = "decode_responses"
27
+
28
+ # ConnectionPool kwargs always overwritten from the service target and password.
29
+ # ``PASSWORD`` is also the password key of a client service config entry.
30
+ HOST: str = "host"
31
+ PORT: str = "port"
32
+ PASSWORD: str = "password"
33
+
34
+ # Maximum number of connections in one pool.
35
+ MAX_CONNECTIONS_DEFAULT: int = 1024
36
+ # Seconds. Used as the default ``socket_connect_timeout``, not as an idle timeout.
37
+ IDLE_TIMEOUT_DEFAULT: float = 3.0 * 60
38
+ # Replies stay bytes unless the config enables decoding.
39
+ DECODE_RESPONSES_DEFAULT: bool = False
40
+
41
+
42
+ class RequestMode(Enum):
43
+ """Dispatch key of a Redis request.
44
+
45
+ Attributes:
46
+ COMMAND: Run one Redis command and return its reply.
47
+ PUBSUB: Subscribe and return a ``PubSub`` object.
48
+ PIPELINE: Return a ``Pipeline`` object for batched commands.
49
+ """
50
+ COMMAND = "command"
51
+ PUBSUB = "pubsub"
52
+ PIPELINE = "pipeline"