gluerpy 1.0.25__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.
gluer/__init__.py ADDED
File without changes
gluer/client.py ADDED
@@ -0,0 +1,204 @@
1
+ import asyncio
2
+ import traceback
3
+ import time
4
+ import json
5
+ import uuid
6
+ import sys
7
+ import os
8
+ import rel
9
+ import websocket
10
+ import importlib
11
+ from websockets.sync.client import connect
12
+
13
+ from mako.lookup import TemplateLookup
14
+ from mako.template import Template
15
+
16
+ project = "sm"
17
+ websocket_url = "ws://localhost:9001/server"
18
+ session_id = str(uuid.uuid4())
19
+ heartbeat_interval = 5
20
+ retry_delay = 5
21
+ ws = None
22
+
23
+ imported_files = {}
24
+ methods_map = {}
25
+
26
+
27
+ def set_project(p):
28
+ global project
29
+ project = p
30
+
31
+
32
+ def set_websocket_url(url):
33
+ global websocket_url
34
+ websocket_url = url
35
+
36
+
37
+ # async def start():
38
+ # print("Start")
39
+ # sub_server()
40
+ # await asyncio.get_event_loop().run_until_complete(start_ws())
41
+ # await asyncio.to_thread(queue())
42
+
43
+ def build_header_from_message(job):
44
+ smh = job["smh"].split(":")
45
+ return {"d": smh[0], "session_id": smh[1].split('>'), "project": smh[2], "plugin": smh[3] or "", "action": smh[4] or ""}
46
+
47
+
48
+ def build_message_from_header(header):
49
+ return f'{header["d"]}:{">".join(header["session_id"])}>{session_id}:{header["project"]}:{header["plugin"] or ""}:{header["action"] or ""}'
50
+
51
+
52
+ def on_message(wsapp, message):
53
+ # print("on_message")
54
+ # print(message)
55
+ job = json.loads(message)
56
+ header = build_header_from_message(job)
57
+ job["smh"] = build_message_from_header(header)
58
+ # print("header")
59
+ # print(header)
60
+ try:
61
+ mtd = None
62
+ if header["plugin"] in methods_map and header["action"] in methods_map[header["plugin"]]:
63
+ mtd = methods_map[header["plugin"]][header["action"]]
64
+ elif header["plugin"] in imported_files:
65
+ if hasattr(imported_files[header["plugin"]], header["action"]):
66
+ mtd = getattr(
67
+ imported_files[header["plugin"]], header["action"])
68
+ if mtd:
69
+ ret = None
70
+ if "data" in job:
71
+ ret = mtd(job["data"])
72
+ else:
73
+ ret = mtd(job)
74
+ # print("Pre Process")
75
+ process_dict(ret)
76
+ # print(ret)
77
+ # print("Post Process")
78
+ job["data"] = ret
79
+ # if "htmx" in job:
80
+ # # print("HTMX found")
81
+ # try:
82
+ # act_template = lookup.get_template(
83
+ # f'{header["plugin"]}/{header["action"]}.mako')
84
+ # # print("act_template")
85
+ # # print(act_template)
86
+ # if act_template:
87
+ # # print("Template Found")
88
+ # # print(ret)
89
+ # # print(job["data"])
90
+ # job["data"] = act_template.render(data=ret)
91
+ # # print(job["data"])
92
+ # except Exception as error:
93
+ # print(error)
94
+ else:
95
+ job["data"] = {
96
+ "error": f"no server actions found for {header['plugin']}:{header['action']}"
97
+ }
98
+ except Exception as error:
99
+ print("ERROR")
100
+ print(traceback.format_exc())
101
+ # print(error)
102
+ error_handler(job, error)
103
+ # if "ws" in job:
104
+
105
+ job["smh"] = "<" + job["smh"][1:]
106
+ # print("Sending job back")
107
+ # print(job["data"])
108
+ # job["d"] = "<"
109
+ wsapp.send(json.dumps(job))
110
+
111
+
112
+ def on_open(ws):
113
+ print("Opened")
114
+ job = f'{{"smh":"+:{session_id}:{project}","channel":"{session_id},{project}"}}'
115
+ ws.send(job)
116
+
117
+
118
+ def on_error(ws, error):
119
+ print("Error")
120
+ print(ws)
121
+ print(error)
122
+ # job = f'{{"smh":"+:{session_id}:{project}","channel":"{session_id},{project}"}}'
123
+ # ws.send(job)
124
+
125
+
126
+ def start():
127
+ global ws
128
+ # with connect(websocket_url) as websocket:
129
+ # ws = websocket
130
+ # message = websocket.recv()
131
+ # print(f"Received: {message}")
132
+ # websocket.enableTrace(false)
133
+ ws = websocket.WebSocketApp(
134
+ websocket_url, on_message=on_message, on_open=on_open, on_error=on_error)
135
+ ws.run_forever(dispatcher=rel, ping_interval=60, ping_timeout=10, reconnect=5,
136
+ ping_payload="This is an optional ping payload")
137
+
138
+ rel.dispatch()
139
+
140
+
141
+ lookup = None
142
+
143
+
144
+ def import_directories(path):
145
+ global lookup
146
+ files = os.listdir(path)
147
+ # print(files)
148
+ sys.path.append(path)
149
+ for file in files:
150
+ if file.endswith(".py"):
151
+ try:
152
+ file = file[:-3]
153
+ imported_files.setdefault(file, importlib.import_module(file))
154
+ print(f"Imported {file}")
155
+ except ImportError as err:
156
+ print('Error:', err)
157
+ lookup = TemplateLookup(directories=[f'{path}/templates'])
158
+
159
+
160
+ def add_method(plugin, action, fn):
161
+ print("Adding method", plugin, action)
162
+ methods_map.setdefault(plugin, {})
163
+ methods_map[plugin].setdefault(action, fn)
164
+
165
+
166
+ def error_handler(job, error):
167
+ job["data"] = {"error": {
168
+ "message": error.content[0]['message'], "code": error.content[0]['errorCode']}}
169
+
170
+
171
+ def set_error_handler(fn):
172
+ global error_handler
173
+ error_handler = fn
174
+
175
+
176
+ def process_dict(data):
177
+ for key, value in data.items():
178
+ # Check if the value has a 'model_dump' method
179
+ if hasattr(value, "model_dump") and callable(getattr(value, "model_dump")):
180
+ # Call model_dump() if it's available and callable
181
+ data[key] = value.model_dump()
182
+ elif isinstance(value, dict):
183
+ # If the value is a nested dictionary, recursively process it
184
+ process_dict(value)
185
+ elif isinstance(value, list):
186
+ # If the value is a list, process each item
187
+ for i, item in enumerate(value):
188
+ if hasattr(item, "model_dump") and callable(getattr(item, "model_dump")):
189
+ value[i] = item.model_dump()
190
+ elif isinstance(item, dict):
191
+ process_dict(item)
192
+
193
+
194
+ if __name__ == '__main__':
195
+ print('start')
196
+ # redis.set_user("Teste")
197
+ # try:
198
+ # # loop = asyncio.get_event_loop()
199
+ # # loop.run_until_complete(start())
200
+ # asyncio.run(start())
201
+ # except Exception as error:
202
+ # print(error)
203
+ # traceback.print_exc()
204
+ # # asyncio.run(start())
gluer/client_redis.py ADDED
@@ -0,0 +1,269 @@
1
+ import datetime
2
+ import asyncio
3
+ import threading
4
+ import traceback
5
+ import time
6
+ import json
7
+ import uuid
8
+ import sys
9
+ import os
10
+ import importlib
11
+ import redis
12
+
13
+ import gluer.lib.redis_lib as redis_lib
14
+ import gluer.lib.acl as acl
15
+
16
+ project = "sm"
17
+ session_id = str(uuid.uuid4())
18
+ heartbeat_interval = 5
19
+ retry_delay = 5
20
+
21
+ imported_files = {}
22
+ methods_map = {}
23
+
24
+ # ssh -R 80:localhost:9001 serveo.net
25
+ # autossh -M 0 -R gluer.serveo.net:80:localhost:9001 serveo.net
26
+ # autossh -M 0 -R gluer:80:localhost:9001 serveo.net
27
+ # ssh -R gluer.serveo.net:443:localhost:9001 serveo.net
28
+ # ssh -R 443:localhost:9001 serveo.net
29
+
30
+ # def import_directories():
31
+ # files = os.listdir('./plugins')
32
+ # # print(files)
33
+ # sys.path.append('./plugins')
34
+ # for file in files:
35
+ # if file.endswith(".py"):
36
+ # try:
37
+ # file = file[:-3]
38
+ # imported_files.setdefault(file, importlib.import_module(file))
39
+ # except ImportError as err:
40
+ # print('Error:', err)
41
+ # # print(imported_files)
42
+
43
+
44
+ def set_project(p):
45
+ global project
46
+ project = p
47
+
48
+
49
+ def set_user(u):
50
+ redis_lib.set_redis_user(u)
51
+
52
+
53
+ def set_password(p):
54
+ redis_lib.set_redis_password(p)
55
+
56
+
57
+ def set_redis_url(url):
58
+ redis_lib.set_redis_url(url)
59
+
60
+
61
+ def set_redis_api_key(key):
62
+ acl.set_api_key(key)
63
+
64
+
65
+ def set_redis_api_secret(secret):
66
+ acl.set_api_secret(secret)
67
+
68
+
69
+ async def start():
70
+ # print("Start")
71
+ sub_server()
72
+ await asyncio.to_thread(queue())
73
+
74
+
75
+ def import_directories(path):
76
+ files = os.listdir(path)
77
+ # print(files)
78
+ sys.path.append(path)
79
+ for file in files:
80
+ if file.endswith(".py"):
81
+ try:
82
+ file = file[:-3]
83
+ imported_files.setdefault(file, importlib.import_module(file))
84
+ print(f"Imported {file}")
85
+ except ImportError as err:
86
+ print('Error:', err)
87
+
88
+
89
+ def sub_server():
90
+ print("Sub Server")
91
+ # r = await redis.from_url("redis://admin:V!6xU8Kf*sQqJS@redis-10264.c277.us-east-1-3.ec2.redns.redis-cloud.com:10264")
92
+
93
+ # acl.create_acl("test2", "test123")
94
+ ev = {
95
+ "channel": f'{project}:bl',
96
+ "action": "server_up",
97
+ "created": time.time_ns()
98
+ }
99
+ # get_redis_connection().hset(
100
+ # f'pp1:servers:{session_id}', mapping=session_data)
101
+ # get_redis_connection().expire(
102
+ # f'pp1:servers:{session_id}', heartbeat_interval)
103
+ # ev = {
104
+ # "channel": f'{project}:bl',
105
+ # "action": "server_up",
106
+ # "created": time.time_ns()
107
+ # }
108
+ redis_lib.get_redis_connection().publish(f'{project}:bl', json.dumps(ev))
109
+ pubsub = redis_lib.get_redis_connection().pubsub()
110
+ subscribe_channel(pubsub, f'{project}:br')
111
+
112
+ # update_redis_hash()
113
+ # Run the update_redis_hash function in a separate thread
114
+ interval_thread = threading.Thread(target=update_redis_hash)
115
+ interval_thread.daemon = True # Daemon thread will exit when the main program exits
116
+ interval_thread.start()
117
+
118
+ # while True:
119
+ # try:
120
+ # # Listen for messages
121
+ # for message in pubsub.listen():
122
+ # if message['type'] == 'message':
123
+ # print(f"Message received: {message['data']}")
124
+
125
+ # except Exception as e:
126
+ # time.sleep(5)
127
+ # pubsub = get_redis_connection().pubsub()
128
+ # subscribe_channel(pubsub, f'{project}:br')
129
+
130
+
131
+ def update_redis_hash():
132
+ while True:
133
+ session_data = {
134
+ "type": "plugin",
135
+ "sessionId": session_id,
136
+ "project": project
137
+ }
138
+ try:
139
+ redis_lib.get_redis_connection().hset(
140
+ f'pp1:servers:{session_id}', mapping=session_data)
141
+ redis_lib.get_redis_connection().expire(
142
+ f'pp1:servers:{session_id}', heartbeat_interval)
143
+ # print("Ping Redis", session_id)
144
+ time.sleep(heartbeat_interval-1)
145
+ except (redis.exceptions.ConnectionError, redis.exceptions.TimeoutError) as e:
146
+ print(e)
147
+ except Exception as e:
148
+ print(e)
149
+
150
+
151
+ def message_handler(message):
152
+ print("message_handler")
153
+ print(message.get("data"))
154
+ job = json.loads(message.get("data"))
155
+ job["connected"] = True
156
+ if "action" in job and job["action"] == "ping":
157
+ print("PING")
158
+ if "ws" in job:
159
+ redis_lib.get_redis_connection().rpush(
160
+ job["ws"], json.dumps(job))
161
+ redis_lib.get_redis_connection().expire(
162
+ job["ws"], 60)
163
+ # redis_lib.get_redis_connection().publish(f'{project}:bl', json.dumps(job))
164
+
165
+ elif "plugin" in job and job["plugin"] in imported_files:
166
+ if hasattr(imported_files[job["plugin"]], job["action"]):
167
+ mtd = getattr(
168
+ imported_files[job["plugin"]], job["action"])
169
+ ret = mtd(job["data"])
170
+ job["data"] = ret
171
+ print('job with data')
172
+ print(job)
173
+ if "ws" in job:
174
+ redis_lib.get_redis_connection().rpush(
175
+ job["ws"], json.dumps(job))
176
+ redis_lib.get_redis_connection().expire(
177
+ job["ws"], 60)
178
+
179
+
180
+ def subscribe_channel(pubsub, channel):
181
+ print("Subscribe", channel)
182
+ # pubsub.subscribe(channel)
183
+ pubsub.subscribe(**{channel: message_handler})
184
+ pubsub.run_in_thread(sleep_time=0.001)
185
+
186
+
187
+ def add_method(plugin, action, fn):
188
+ print("Adding method", plugin, action)
189
+ methods_map.setdefault(plugin, {})
190
+ methods_map[plugin].setdefault(action, fn)
191
+
192
+
193
+ def error_handler(job, error):
194
+ job["data"] = {"error": {
195
+ "message": error.content[0]['message'], "code": error.content[0]['errorCode']}}
196
+
197
+
198
+ def set_error_handler(fn):
199
+ global error_handler
200
+ error_handler = fn
201
+
202
+
203
+ def queue():
204
+ # print('Queue')
205
+ # r = await redis.from_url("redis://admin:V!6xU8Kf*sQqJS@redis-10264.c277.us-east-1-3.ec2.redns.redis-cloud.com:10264")
206
+ while True:
207
+ # print(f'{project}:r')
208
+ try:
209
+ val = redis_lib.get_redis_connection().blpop(f'{project}:r', 5)
210
+ if val:
211
+ job = json.loads(val[1])
212
+ try:
213
+ mtd = None
214
+ if job["plugin"] in methods_map and job["action"] in methods_map[job["plugin"]]:
215
+ mtd = methods_map[job["plugin"]][job["action"]]
216
+ elif job["plugin"] in imported_files:
217
+ if hasattr(imported_files[job["plugin"]], job["action"]):
218
+ mtd = getattr(
219
+ imported_files[job["plugin"]], job["action"])
220
+ if mtd:
221
+ ret = None
222
+ if "data" in job:
223
+ ret = mtd(job["data"])
224
+ else:
225
+ ret = mtd(job)
226
+ job["data"] = ret
227
+ else:
228
+ job["data"] = {
229
+ "error": f"no server actions found for {job['plugin']}:{job['action']}"
230
+ }
231
+ except Exception as error:
232
+ print("ERROR")
233
+ print(traceback.format_exc())
234
+ # print(error)
235
+ error_handler(job, error)
236
+ if "ws" in job:
237
+ redis_lib.get_redis_connection().rpush(
238
+ job["ws"], json.dumps(job))
239
+ redis_lib.get_redis_connection().expire(
240
+ job["ws"], 60)
241
+ except (redis.exceptions.ConnectionError, redis.exceptions.TimeoutError) as e:
242
+ print("Redis connection lost. Reconnecting...")
243
+ print(e)
244
+ time.sleep(retry_delay)
245
+
246
+ except redis.exceptions.RedisError as e:
247
+ # Catch other general Redis-related exceptions
248
+ print("General Redis error")
249
+ print(e)
250
+ # traceback.print_exc()
251
+
252
+ except Exception as error:
253
+ print("Unexpected error on brpop queue")
254
+ print(error)
255
+
256
+ def queue_ws():
257
+ print("Queue WS")
258
+
259
+ if __name__ == '__main__':
260
+ print('start')
261
+ # redis.set_user("Teste")
262
+ # try:
263
+ # # loop = asyncio.get_event_loop()
264
+ # # loop.run_until_complete(start())
265
+ # asyncio.run(start())
266
+ # except Exception as error:
267
+ # print(error)
268
+ # traceback.print_exc()
269
+ # # asyncio.run(start())
gluer/lib/__init__.py ADDED
File without changes
gluer/lib/acl.py ADDED
@@ -0,0 +1,148 @@
1
+ import requests
2
+ import time
3
+ import os
4
+
5
+ API_KEY = os.getenv('REDIS_API_KEY',None)
6
+ API_SECRET_KEY = os.getenv('REDIS_API_SECRET',None)
7
+
8
+ headers = {
9
+ "x-api-key": "Ay9adtha5prx8za3413t4v9y5vptrvst1aulowgz6r30uz2oa7",
10
+ "x-api-secret-key": "S3ctl9u1zjg5cbcyma38bgnbrq6q2ia6pud7lwvaonramcpanq9",
11
+ "Content-Type": "application/json"
12
+ }
13
+
14
+
15
+ def set_api_key(key):
16
+ API_KEY = key
17
+
18
+
19
+ def set_api_secret(secret):
20
+ API_SECRET_KEY = secret
21
+
22
+
23
+ def check_task_status(task_url):
24
+ while True:
25
+ response = requests.get(task_url, headers=headers)
26
+ if response.status_code == 200:
27
+ task_status = response.json()
28
+ status = task_status.get('status')
29
+
30
+ if status == 'processing-completed':
31
+ return task_status
32
+ elif status in ['received', 'processing', 'processing-in-progress']:
33
+ print(f"Task is still processing: {status}")
34
+ time.sleep(5) # Non-blocking sleep
35
+ elif status == 'processing-error':
36
+ raise Exception(
37
+ f"Task failed with status: {status}. Error details: {task_status.get('response', {}).get('error', {}).get('description', 'No details provided')}")
38
+ else:
39
+ raise Exception(f"Unexpected task status: {status}")
40
+ else:
41
+ response.raise_for_status()
42
+
43
+
44
+ def create_acl(name, password):
45
+ rule = create_rule(name)
46
+ role = create_role(name)
47
+ user = create_user(name, password)
48
+ print("ACL Created")
49
+ print(rule)
50
+ print(role)
51
+ print(user)
52
+ return {"rule": rule, "role": role, "user": user}
53
+
54
+
55
+ def delete_acl(name):
56
+ delete_user(name)
57
+ # create_rule(name)
58
+ # create_role(name)
59
+
60
+
61
+ def create_rule(name):
62
+ print('Create Rule')
63
+ print(name)
64
+ data = {
65
+ "name": name,
66
+ "redisRule": f'+hset +unlink +pubsub +subscribe +brpop +blpop +rpush +lpush ~{name}* +pubsub ~pp1:* ~ws:*'
67
+ }
68
+ print(data)
69
+ response = requests.post(
70
+ 'https://api.redislabs.com/v1/acl/redisRules', json=data, headers=headers)
71
+ response.raise_for_status()
72
+ data = response.json()
73
+ print(data)
74
+ if "links" in data and len(data["links"]) > 0:
75
+ status = check_task_status(data["links"][0]["href"])
76
+ print(status)
77
+ return status.get('response').get('resourceId')
78
+
79
+
80
+ def create_role(name):
81
+ data = {
82
+ "name": name,
83
+ "redisRules": [
84
+ {
85
+ "ruleName": name,
86
+ "databases": [
87
+ {
88
+ "subscriptionId": 2350147,
89
+ "databaseId": 12364997
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ }
95
+ response = requests.post(
96
+ 'https://api.redislabs.com/v1/acl/roles', json=data, headers=headers)
97
+ response.raise_for_status()
98
+ data = response.json()
99
+ print(data)
100
+ if "links" in data and len(data["links"]) > 0:
101
+ status = check_task_status(data["links"][0]["href"])
102
+ print(status)
103
+ return status.get('response').get('resourceId')
104
+
105
+
106
+ def create_user(name, password):
107
+ if not password:
108
+ passowrd = generate_random_password()
109
+ data = {
110
+ "name": name,
111
+ "role": name,
112
+ "password": password
113
+ }
114
+ response = requests.post(
115
+ 'https://api.redislabs.com/v1/acl/users', json=data, headers=headers)
116
+ response.raise_for_status()
117
+ data = response.json()
118
+ print(data)
119
+ if "links" in data and len(data["links"]) > 0:
120
+ status = check_task_status(data["links"][0]["href"])
121
+ print(status)
122
+ return status.get('response').get('resourceId')
123
+ # return data
124
+
125
+
126
+ def delete_user(name):
127
+ data = {
128
+ "name": name,
129
+ "role": name,
130
+ "password": password
131
+ }
132
+ response = requests.post(
133
+ 'https://api.redislabs.com/v1/acl/users', json=data, headers=headers)
134
+ response.raise_for_status()
135
+ data = response.json()
136
+ print(data)
137
+ if "links" in data and len(data["links"]) > 0:
138
+ status = check_task_status(data["links"][0]["href"])
139
+ print(status)
140
+ return data
141
+
142
+
143
+ def generate_random_password(length=12):
144
+ # Define the character sets to use
145
+ characters = string.ascii_letters + string.digits + '!@$'
146
+ # Generate a random password
147
+ password = ''.join(random.choice(characters) for _ in range(length))
148
+ return password
gluer/lib/redis_lib.py ADDED
@@ -0,0 +1,69 @@
1
+ # redis_connection.py
2
+ import redis
3
+ import os
4
+
5
+ redis_client = None
6
+ redis_pubsub = None
7
+
8
+ redis_url = os.getenv('REDIS_URL', None)
9
+ redis_user = os.getenv('REDIS_USER', None)
10
+ redis_password = os.getenv('REDIS_PASSWORD', None)
11
+ redis_host = os.getenv(
12
+ 'REDIS_HOST', "redis-10264.c277.us-east-1-3.ec2.redns.redis-cloud.com")
13
+ redis_port = os.getenv('REDIS_PORT', "10264")
14
+
15
+
16
+ def set_redis_url(u):
17
+ global redis_url
18
+ redis_url = u
19
+
20
+
21
+ def set_redis_user(u):
22
+ global redis_user
23
+ redis_user = u
24
+
25
+
26
+ def set_redis_password(p):
27
+ global redis_password
28
+ redis_password = p
29
+
30
+
31
+ events = {}
32
+
33
+ # pool = None
34
+ # r = redis.Redis(connection_pool=pool)
35
+
36
+
37
+ def get_url():
38
+ global redis_url, redis_user, redis_password
39
+ if redis_url:
40
+ return redis_url
41
+ elif redis_user and redis_password:
42
+ return f"redis://{redis_user}:{redis_password}@{redis_host}:{redis_port}"
43
+
44
+
45
+ def get_redis_connection():
46
+ global redis_client, redis_url
47
+ if redis_client is None:
48
+ redis_client = redis.from_url(redis_url,
49
+ decode_responses=True)
50
+ return redis_client
51
+ # if pool is None:
52
+ # pool = redis.ConnectionPool.from_url(get_url())
53
+ # return redis.Redis(connection_pool=pool, decode_responses=True)
54
+
55
+ # if redis_client is None:
56
+ # if redis_user and redis_password:
57
+ # redis_url = f"redis://{redis_user}:{redis_password}@redis-10264.c277.us-east-1-3.ec2.redns.redis-cloud.com:10264"
58
+ # redis_client = redis.from_url(redis_url,
59
+ # decode_responses=True)
60
+ # # redis_client = redis.Redis(
61
+ # # host='localhost', port=6379, decode_responses=True)
62
+
63
+
64
+ def get_redis_pubsub():
65
+ global redis_pubsub
66
+ if redis_pubsub is None:
67
+ redis_pubsub = get_redis_connection().pubsub()
68
+ redis_pubsub.run_in_thread(sleep_time=0.001)
69
+ return redis_pubsub
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: gluerpy
3
+ Version: 1.0.25
4
+ Summary: Python library to connect to gluer services
5
+ Home-page: https://www.gluer.io
6
+ Author: Thiago Magro
7
+ Author-email: thiago.magro@gmail.com
8
+ License: UNKNOWN
9
+ Platform: UNKNOWN
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.6
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: redis
16
+
17
+ Gluer Library for Python
18
+
@@ -0,0 +1,10 @@
1
+ gluer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ gluer/client.py,sha256=KwyP0beFHwp-Fja3FILXl3G5GHWLp55eTf-jArCyq4w,6109
3
+ gluer/client_redis.py,sha256=P1x24i9it9p7H0mNAp-8ZfiuohDvCItneSJiQeoLwSU,8355
4
+ gluer/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ gluer/lib/acl.py,sha256=7laiX-NqpzqTb3LXlZkk6LBGQPBPir_6vxnmupyx_D4,4342
6
+ gluer/lib/redis_lib.py,sha256=hjPGkacuiJLHYrEj-5rg3rMcAm83lp63BPetvrlY964,1891
7
+ gluerpy-1.0.25.dist-info/METADATA,sha256=iFcYy2C3YurZyFYJtzUvy-g84PT62AoJwWUHUrBPPC0,487
8
+ gluerpy-1.0.25.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
9
+ gluerpy-1.0.25.dist-info/top_level.txt,sha256=V1vaafqCPsG50XMIBcZxsIHrkXQK6EWMM_8DFVbvbH8,6
10
+ gluerpy-1.0.25.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.37.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ gluer