callite 0.1.4__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.
- callite-0.1.4/PKG-INFO +99 -0
- callite-0.1.4/README.md +85 -0
- callite-0.1.4/callite/__init__.py +5 -0
- callite-0.1.4/callite/client/__init__.py +2 -0
- callite-0.1.4/callite/client/rpc_client.py +64 -0
- callite-0.1.4/callite/rpctypes/__init__.py +6 -0
- callite-0.1.4/callite/rpctypes/message_base.py +7 -0
- callite-0.1.4/callite/rpctypes/request.py +25 -0
- callite-0.1.4/callite/rpctypes/response.py +18 -0
- callite-0.1.4/callite/rpctypes/rpc_exception.py +10 -0
- callite-0.1.4/callite/server/__init__.py +3 -0
- callite-0.1.4/callite/server/rpc_server.py +89 -0
- callite-0.1.4/callite/shared/__init__.py +2 -0
- callite-0.1.4/callite/shared/redis_connection.py +22 -0
- callite-0.1.4/pyproject.toml +20 -0
- callite-0.1.4/setup.py +34 -0
callite-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: callite
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Slim Redis RPC implementation
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Emrah Gozcu
|
|
7
|
+
Author-email: gozcu@gri.ai
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Requires-Dist: redis (>=5.0.3,<6.0.0)
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# RPClite
|
|
16
|
+
|
|
17
|
+
RPClite is a lightweight Remote Procedure Call (RPC) implementation over Redis, designed to facilitate communication between different components of a distributed system. It minimizes dependencies and offers a simple yet effective solution for decoupling complex systems, thus alleviating potential library conflicts.
|
|
18
|
+
|
|
19
|
+
## Setting up RPClite
|
|
20
|
+
|
|
21
|
+
Before using RPClite, ensure you have a Redis instance running. You can start a Redis server using the default settings or configure it as per your requirements.
|
|
22
|
+
|
|
23
|
+
## Implementing the Server
|
|
24
|
+
|
|
25
|
+
To implement the RPClite server, follow these steps:
|
|
26
|
+
|
|
27
|
+
1. Import the `RPCService` class from `server.rpc_server`.
|
|
28
|
+
2. Define your main class and initialize the RPC service with the Redis URL and service name.
|
|
29
|
+
3. Register your functions with the RPC service using the `register` decorator.
|
|
30
|
+
4. Run the RPC service indefinitely.
|
|
31
|
+
|
|
32
|
+
Here's an example implementation:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from callite.server import RPCService
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Main:
|
|
39
|
+
def __init__(self):
|
|
40
|
+
self.service = "service"
|
|
41
|
+
self.redis_url = "redis://redis:6379/0"
|
|
42
|
+
self.rpc_service = RPCService(self.redis_url, self.service)
|
|
43
|
+
|
|
44
|
+
def run(self):
|
|
45
|
+
@self.rpc_service.register
|
|
46
|
+
def healthcheck():
|
|
47
|
+
return "OK"
|
|
48
|
+
|
|
49
|
+
self.rpc_service.run_forever()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
Main().run()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Calling the Function from Client
|
|
57
|
+
|
|
58
|
+
Once the server is set up, you can call functions remotely from the client side. Follow these steps to call functions:
|
|
59
|
+
|
|
60
|
+
1. Import the `RPCClient` class from `client.rpc_client`.
|
|
61
|
+
2. Define your client class and initialize the RPC client with the Redis URL and service name.
|
|
62
|
+
3. Call the function using the `execute` method of the RPC client.
|
|
63
|
+
4. Optionally, you can pass arguments and keyword arguments to the function.
|
|
64
|
+
|
|
65
|
+
Here's an example client implementation:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import time
|
|
69
|
+
from callite.client.rpc_client import RPCClient
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Healthcheck():
|
|
73
|
+
def __init__(self):
|
|
74
|
+
self.status = "OK"
|
|
75
|
+
self.r = RPCClient("redis://redis:6379/0", "service")
|
|
76
|
+
|
|
77
|
+
def get_status(self):
|
|
78
|
+
start = time.perf_counter()
|
|
79
|
+
self.status = self.r.execute('healthcheck')
|
|
80
|
+
end = time.perf_counter()
|
|
81
|
+
print(f"Healthcheck took {end - start:0.4f} seconds")
|
|
82
|
+
return self.status
|
|
83
|
+
|
|
84
|
+
def check(self):
|
|
85
|
+
return self.get_status()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
Healthcheck().check()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can pass arguments and keyword arguments to the `execute` method as follows:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
self.status = self.r.execute('healthcheck', [True], {'a': 1, 'b': 2})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This setup allows for efficient communication between components of a distributed system, promoting modularity and scalability.
|
|
99
|
+
```
|
callite-0.1.4/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# RPClite
|
|
2
|
+
|
|
3
|
+
RPClite is a lightweight Remote Procedure Call (RPC) implementation over Redis, designed to facilitate communication between different components of a distributed system. It minimizes dependencies and offers a simple yet effective solution for decoupling complex systems, thus alleviating potential library conflicts.
|
|
4
|
+
|
|
5
|
+
## Setting up RPClite
|
|
6
|
+
|
|
7
|
+
Before using RPClite, ensure you have a Redis instance running. You can start a Redis server using the default settings or configure it as per your requirements.
|
|
8
|
+
|
|
9
|
+
## Implementing the Server
|
|
10
|
+
|
|
11
|
+
To implement the RPClite server, follow these steps:
|
|
12
|
+
|
|
13
|
+
1. Import the `RPCService` class from `server.rpc_server`.
|
|
14
|
+
2. Define your main class and initialize the RPC service with the Redis URL and service name.
|
|
15
|
+
3. Register your functions with the RPC service using the `register` decorator.
|
|
16
|
+
4. Run the RPC service indefinitely.
|
|
17
|
+
|
|
18
|
+
Here's an example implementation:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from callite.server import RPCService
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Main:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.service = "service"
|
|
27
|
+
self.redis_url = "redis://redis:6379/0"
|
|
28
|
+
self.rpc_service = RPCService(self.redis_url, self.service)
|
|
29
|
+
|
|
30
|
+
def run(self):
|
|
31
|
+
@self.rpc_service.register
|
|
32
|
+
def healthcheck():
|
|
33
|
+
return "OK"
|
|
34
|
+
|
|
35
|
+
self.rpc_service.run_forever()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
Main().run()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Calling the Function from Client
|
|
43
|
+
|
|
44
|
+
Once the server is set up, you can call functions remotely from the client side. Follow these steps to call functions:
|
|
45
|
+
|
|
46
|
+
1. Import the `RPCClient` class from `client.rpc_client`.
|
|
47
|
+
2. Define your client class and initialize the RPC client with the Redis URL and service name.
|
|
48
|
+
3. Call the function using the `execute` method of the RPC client.
|
|
49
|
+
4. Optionally, you can pass arguments and keyword arguments to the function.
|
|
50
|
+
|
|
51
|
+
Here's an example client implementation:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import time
|
|
55
|
+
from callite.client.rpc_client import RPCClient
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Healthcheck():
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.status = "OK"
|
|
61
|
+
self.r = RPCClient("redis://redis:6379/0", "service")
|
|
62
|
+
|
|
63
|
+
def get_status(self):
|
|
64
|
+
start = time.perf_counter()
|
|
65
|
+
self.status = self.r.execute('healthcheck')
|
|
66
|
+
end = time.perf_counter()
|
|
67
|
+
print(f"Healthcheck took {end - start:0.4f} seconds")
|
|
68
|
+
return self.status
|
|
69
|
+
|
|
70
|
+
def check(self):
|
|
71
|
+
return self.get_status()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
Healthcheck().check()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
You can pass arguments and keyword arguments to the `execute` method as follows:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
self.status = self.r.execute('healthcheck', [True], {'a': 1, 'b': 2})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
This setup allows for efficient communication between components of a distributed system, promoting modularity and scalability.
|
|
85
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import threading
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from callite.shared.redis_connection import RedisConnection
|
|
6
|
+
from callite.rpctypes.request import Request
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# import pydevd_pycharm
|
|
10
|
+
# pydevd_pycharm.settrace('host.docker.internal', port=4444, stdoutToServer=True, stderrToServer=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RPCClient(RedisConnection):
|
|
14
|
+
|
|
15
|
+
def __init__(self, conn_url: str, service: str, *args, **kwargs) -> None:
|
|
16
|
+
super().__init__(conn_url, service, *args, **kwargs)
|
|
17
|
+
self._request_pool = {}
|
|
18
|
+
self._subscribe()
|
|
19
|
+
self._logger = logging.getLogger(__name__)
|
|
20
|
+
self._logger.addHandler(logging.StreamHandler())
|
|
21
|
+
self._logger.setLevel(logging.INFO)
|
|
22
|
+
|
|
23
|
+
def _subscribe(self):
|
|
24
|
+
self.channel = self._rds.pubsub()
|
|
25
|
+
self.channel.subscribe(f'{self._queue_prefix}/response/{self._connection_id}')
|
|
26
|
+
thread = threading.Thread(target=self._pull_from_redis, daemon=True)
|
|
27
|
+
thread.start()
|
|
28
|
+
|
|
29
|
+
def _pull_from_redis(self):
|
|
30
|
+
def _on_delivery(message):
|
|
31
|
+
self._logger.info(f"Received message {message}")
|
|
32
|
+
data = json.loads(message['data'].decode('utf-8'))
|
|
33
|
+
request_guid = data['request_id']
|
|
34
|
+
if request_guid not in self._request_pool:
|
|
35
|
+
return
|
|
36
|
+
lock, _ = self._request_pool.pop(request_guid)
|
|
37
|
+
self._request_pool[request_guid] = (lock, data)
|
|
38
|
+
lock.release()
|
|
39
|
+
|
|
40
|
+
# TODO: handle poisonous messages from redis (e.g. non-json, old messages, etc.)
|
|
41
|
+
while self._running:
|
|
42
|
+
message: dict | None = self.channel.get_message(ignore_subscribe_messages=True, timeout=100)
|
|
43
|
+
if not message: continue
|
|
44
|
+
if not message['data']: continue
|
|
45
|
+
_on_delivery(message)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def execute(self, method: str, *args, **kwargs) -> dict:
|
|
49
|
+
request = Request(method, self._connection_id, None, *args, **kwargs)
|
|
50
|
+
request_uuid = request.request_id
|
|
51
|
+
|
|
52
|
+
request_lock = threading.Lock()
|
|
53
|
+
self._request_pool[request_uuid] = (request_lock, None)
|
|
54
|
+
request_lock.acquire()
|
|
55
|
+
|
|
56
|
+
self._rds.xadd(f'{self._queue_prefix}/request/{self._service}', {'data': json.dumps(request.payload_json())})
|
|
57
|
+
self._logger.info(f"Sent message {request_uuid} to {self._queue_prefix}/request/{self._service}")
|
|
58
|
+
# TODO: parameterize timeout
|
|
59
|
+
lock_success = request_lock.acquire(timeout=30)
|
|
60
|
+
lock, response = self._request_pool.pop(request_uuid)
|
|
61
|
+
if lock_success:
|
|
62
|
+
self._logger.info(f"Received response to message {request_uuid} {type(response['data'])}")
|
|
63
|
+
return response['data']
|
|
64
|
+
raise Exception('Timeout')
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class MessageBase(object):
|
|
2
|
+
def __init__(self, method, message_id):
|
|
3
|
+
self.message_id: str = message_id
|
|
4
|
+
self.method: str = method
|
|
5
|
+
|
|
6
|
+
def __str__(self):
|
|
7
|
+
return "MessageBase: message_id: %s, method: %s, message_data: %s %s" % (self.message_id, self.method, self.args, self.kwargs)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from callite.rpctypes.message_base import MessageBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Request(MessageBase):
|
|
7
|
+
def __init__(self, method: str, client_id:str, message_id = None, *args, **kwargs):
|
|
8
|
+
super(Request, self).__init__(method, message_id)
|
|
9
|
+
self.request_id = message_id if message_id else uuid.uuid4().hex
|
|
10
|
+
self.client_id = client_id
|
|
11
|
+
self.args = args
|
|
12
|
+
self.kwargs = kwargs
|
|
13
|
+
|
|
14
|
+
def set_data(self, data):
|
|
15
|
+
self.data = data
|
|
16
|
+
def payload_json(self):
|
|
17
|
+
return {
|
|
18
|
+
'request_id': self.request_id,
|
|
19
|
+
'client_id': self.client_id,
|
|
20
|
+
'method': self.method,
|
|
21
|
+
'params': {'args': self.args, 'kwargs': self.kwargs}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
return "Request: request_id: %s, method: %s" % (self.request_id, self.method)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from callite.rpctypes.message_base import MessageBase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Response(MessageBase):
|
|
7
|
+
def __init__(self, method: str, message_id = None):
|
|
8
|
+
super(Response, self).__init__(method, message_id)
|
|
9
|
+
self.data = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def response_json(self):
|
|
13
|
+
# get the json string of the current instance
|
|
14
|
+
response_data = json.dumps(self).encode('utf-8')
|
|
15
|
+
return response_data
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
return "Response: message_id: %s, response_data: %s" % (self.message_id, self.data)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
from types import FunctionType
|
|
6
|
+
from typing import Any, Callable
|
|
7
|
+
|
|
8
|
+
import redis
|
|
9
|
+
|
|
10
|
+
from callite.rpctypes.response import Response
|
|
11
|
+
from callite.shared.redis_connection import RedisConnection
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# import pydevd_pycharm
|
|
15
|
+
# pydevd_pycharm.settrace('host.docker.internal', port=4444, stdoutToServer=True, stderrToServer=True)
|
|
16
|
+
# TODO: Check method calls and parameters
|
|
17
|
+
class RPCServer(RedisConnection):
|
|
18
|
+
def __init__(self, conn_url: str, service: str, *args, **kwargs):
|
|
19
|
+
super().__init__(conn_url, service, *args, **kwargs)
|
|
20
|
+
self._registered_methods = {}
|
|
21
|
+
self._xread_groupname = kwargs.get('xread_groupname', 'generic')
|
|
22
|
+
|
|
23
|
+
t = threading.Thread(target=self._subscribe_redis, daemon=True)
|
|
24
|
+
t.start()
|
|
25
|
+
self._logger = logging.getLogger(__name__)
|
|
26
|
+
self._logger.addHandler(logging.StreamHandler())
|
|
27
|
+
self._logger.setLevel(logging.INFO)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def register(self, handler: FunctionType | Callable, method_name: str | None = None) -> Callable:
|
|
31
|
+
method_name = method_name or handler.__name__
|
|
32
|
+
self._registered_methods[method_name] = handler
|
|
33
|
+
return handler
|
|
34
|
+
|
|
35
|
+
def run_forever(self) -> None:
|
|
36
|
+
while self._running: time.sleep(1000000)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _subscribe_redis(self):
|
|
40
|
+
self._create_redis_group()
|
|
41
|
+
while self._running:
|
|
42
|
+
messages = self._read_messages_from_redis()
|
|
43
|
+
self._process_messages(messages)
|
|
44
|
+
|
|
45
|
+
def _create_redis_group(self):
|
|
46
|
+
try:
|
|
47
|
+
self._rds.xgroup_create(f'{self._queue_prefix}/request/{self._service}', self._xread_groupname, mkstream=True)
|
|
48
|
+
except redis.exceptions.ResponseError as e:
|
|
49
|
+
if "name already exists" not in str(e): raise
|
|
50
|
+
|
|
51
|
+
def _read_messages_from_redis(self):
|
|
52
|
+
messages = self._rds.xreadgroup(self._xread_groupname, self._connection_id, {f'{self._queue_prefix}/request/{self._service}': '>'}, count=1, block=1000, noack=True)
|
|
53
|
+
self._logger.info(f"{len(messages)} messages received from {self._queue_prefix}/request/{self._service}")
|
|
54
|
+
return messages
|
|
55
|
+
|
|
56
|
+
def _process_messages(self, messages):
|
|
57
|
+
for _, message_list in messages:
|
|
58
|
+
for _message in message_list:
|
|
59
|
+
message_id, message_data = _message
|
|
60
|
+
message_data = json.loads(message_data[b'data'])
|
|
61
|
+
self._logger.info(f"Processing message {message_id} with data: {message_data}")
|
|
62
|
+
self._handle_messages(message_data, message_id)
|
|
63
|
+
|
|
64
|
+
def _handle_messages(self, message_data, message_id):
|
|
65
|
+
response = self._call_registered_method(message_data['method'], message_id, message_data['params'])
|
|
66
|
+
request_id = message_data['request_id']
|
|
67
|
+
self._logger.info(f"Response to message {message_id} is {response}")
|
|
68
|
+
payload = json.dumps({'data': response, 'request_id': request_id})
|
|
69
|
+
self._rds.publish(f'{self._queue_prefix}/response/{message_data["client_id"]}', payload)
|
|
70
|
+
self._rds.xack(f'{self._queue_prefix}/request/{self._service}', self._xread_groupname, message_id)
|
|
71
|
+
self._logger.info(f"Processed message {message_id} and response published to {self._queue_prefix}/response/{message_data['request_id']}")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _call_registered_method(self, method: str, message_id, params: dict) -> Any:
|
|
75
|
+
if method not in self._registered_methods:
|
|
76
|
+
self._logger.warn(f"Method {method} not registered")
|
|
77
|
+
return
|
|
78
|
+
try:
|
|
79
|
+
data = self._registered_methods[method](*params['args'], **params['kwargs'])
|
|
80
|
+
|
|
81
|
+
# TODO: Check why message_id is bytes
|
|
82
|
+
message_id = message_id.decode('utf-8') if isinstance(message_id, bytes) else message_id
|
|
83
|
+
response = Response(self._service, message_id)
|
|
84
|
+
response.data = data
|
|
85
|
+
return response.__dict__
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self._logger.error(e)
|
|
88
|
+
# TODO: log and return exception
|
|
89
|
+
return
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import uuid
|
|
3
|
+
import redis
|
|
4
|
+
from abc import ABC
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RedisConnection(ABC):
|
|
9
|
+
def __init__(self, conn_url: str, service: str, *args, **kwargs):
|
|
10
|
+
self._methods = {}
|
|
11
|
+
self._service = service
|
|
12
|
+
self._running = True
|
|
13
|
+
self._running_threads = []
|
|
14
|
+
self._connection_id = uuid.uuid4().hex
|
|
15
|
+
self._queue_prefix = kwargs.get('queue_prefix', '/rpclite')
|
|
16
|
+
self._rds = redis.Redis.from_url(conn_url)
|
|
17
|
+
self._rds.ping()
|
|
18
|
+
self._logger = logging.getLogger(f'RPC{service}')
|
|
19
|
+
self._logger.setLevel(logging.INFO)
|
|
20
|
+
|
|
21
|
+
def close(self) -> None:
|
|
22
|
+
self._running = False
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "callite"
|
|
3
|
+
version = "0.1.4"
|
|
4
|
+
description = "Slim Redis RPC implementation"
|
|
5
|
+
authors = ["Emrah Gozcu <gozcu@gri.ai>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.10"
|
|
11
|
+
redis = "^5.0.3"
|
|
12
|
+
|
|
13
|
+
[tool.poetry.dev-dependencies]
|
|
14
|
+
mypy = "^1.9.0"
|
|
15
|
+
setuptools = "^69.1.1"
|
|
16
|
+
pydevd-pycharm = "~=233.14475.28"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["poetry-core>=1.0.0"]
|
|
20
|
+
build-backend = "poetry.core.masonry.api"
|
callite-0.1.4/setup.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
packages = \
|
|
5
|
+
['callite',
|
|
6
|
+
'callite.client',
|
|
7
|
+
'callite.rpctypes',
|
|
8
|
+
'callite.server',
|
|
9
|
+
'callite.shared']
|
|
10
|
+
|
|
11
|
+
package_data = \
|
|
12
|
+
{'': ['*']}
|
|
13
|
+
|
|
14
|
+
install_requires = \
|
|
15
|
+
['redis>=5.0.3,<6.0.0']
|
|
16
|
+
|
|
17
|
+
setup_kwargs = {
|
|
18
|
+
'name': 'callite',
|
|
19
|
+
'version': '0.1.4',
|
|
20
|
+
'description': 'Slim Redis RPC implementation',
|
|
21
|
+
'long_description': '# RPClite\n\nRPClite is a lightweight Remote Procedure Call (RPC) implementation over Redis, designed to facilitate communication between different components of a distributed system. It minimizes dependencies and offers a simple yet effective solution for decoupling complex systems, thus alleviating potential library conflicts.\n\n## Setting up RPClite\n\nBefore using RPClite, ensure you have a Redis instance running. You can start a Redis server using the default settings or configure it as per your requirements.\n\n## Implementing the Server\n\nTo implement the RPClite server, follow these steps:\n\n1. Import the `RPCService` class from `server.rpc_server`.\n2. Define your main class and initialize the RPC service with the Redis URL and service name.\n3. Register your functions with the RPC service using the `register` decorator.\n4. Run the RPC service indefinitely.\n\nHere\'s an example implementation:\n\n```python\nfrom callite.server import RPCService\n\n\nclass Main:\n def __init__(self):\n self.service = "service"\n self.redis_url = "redis://redis:6379/0"\n self.rpc_service = RPCService(self.redis_url, self.service)\n\n def run(self):\n @self.rpc_service.register\n def healthcheck():\n return "OK"\n\n self.rpc_service.run_forever()\n\n\nif __name__ == "__main__":\n Main().run()\n```\n\n## Calling the Function from Client\n\nOnce the server is set up, you can call functions remotely from the client side. Follow these steps to call functions:\n\n1. Import the `RPCClient` class from `client.rpc_client`.\n2. Define your client class and initialize the RPC client with the Redis URL and service name.\n3. Call the function using the `execute` method of the RPC client.\n4. Optionally, you can pass arguments and keyword arguments to the function.\n\nHere\'s an example client implementation:\n\n```python\nimport time\nfrom callite.client.rpc_client import RPCClient\n\n\nclass Healthcheck():\n def __init__(self):\n self.status = "OK"\n self.r = RPCClient("redis://redis:6379/0", "service")\n\n def get_status(self):\n start = time.perf_counter()\n self.status = self.r.execute(\'healthcheck\')\n end = time.perf_counter()\n print(f"Healthcheck took {end - start:0.4f} seconds")\n return self.status\n\n def check(self):\n return self.get_status()\n\n\nif __name__ == "__main__":\n Healthcheck().check()\n```\n\nYou can pass arguments and keyword arguments to the `execute` method as follows:\n\n```python\nself.status = self.r.execute(\'healthcheck\', [True], {\'a\': 1, \'b\': 2})\n```\n\nThis setup allows for efficient communication between components of a distributed system, promoting modularity and scalability.\n```',
|
|
22
|
+
'author': 'Emrah Gozcu',
|
|
23
|
+
'author_email': 'gozcu@gri.ai',
|
|
24
|
+
'maintainer': None,
|
|
25
|
+
'maintainer_email': None,
|
|
26
|
+
'url': None,
|
|
27
|
+
'packages': packages,
|
|
28
|
+
'package_data': package_data,
|
|
29
|
+
'install_requires': install_requires,
|
|
30
|
+
'python_requires': '>=3.10,<4.0',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
setup(**setup_kwargs)
|