unaiverse 0.1.12__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.
- unaiverse/__init__.py +19 -0
- unaiverse/agent.py +2226 -0
- unaiverse/agent_basics.py +2389 -0
- unaiverse/clock.py +234 -0
- unaiverse/dataprops.py +1282 -0
- unaiverse/hsm.py +2471 -0
- unaiverse/modules/__init__.py +18 -0
- unaiverse/modules/cnu/__init__.py +17 -0
- unaiverse/modules/cnu/cnus.py +536 -0
- unaiverse/modules/cnu/layers.py +261 -0
- unaiverse/modules/cnu/psi.py +60 -0
- unaiverse/modules/hl/__init__.py +15 -0
- unaiverse/modules/hl/hl_utils.py +411 -0
- unaiverse/modules/networks.py +1509 -0
- unaiverse/modules/utils.py +748 -0
- unaiverse/networking/__init__.py +16 -0
- unaiverse/networking/node/__init__.py +18 -0
- unaiverse/networking/node/connpool.py +1332 -0
- unaiverse/networking/node/node.py +2752 -0
- unaiverse/networking/node/profile.py +446 -0
- unaiverse/networking/node/tokens.py +79 -0
- unaiverse/networking/p2p/__init__.py +188 -0
- unaiverse/networking/p2p/go.mod +127 -0
- unaiverse/networking/p2p/go.sum +548 -0
- unaiverse/networking/p2p/golibp2p.py +18 -0
- unaiverse/networking/p2p/golibp2p.pyi +136 -0
- unaiverse/networking/p2p/lib.go +2765 -0
- unaiverse/networking/p2p/lib_types.py +311 -0
- unaiverse/networking/p2p/message_pb2.py +50 -0
- unaiverse/networking/p2p/messages.py +360 -0
- unaiverse/networking/p2p/mylogger.py +78 -0
- unaiverse/networking/p2p/p2p.py +900 -0
- unaiverse/networking/p2p/proto-go/message.pb.go +846 -0
- unaiverse/stats.py +1506 -0
- unaiverse/streamlib/__init__.py +15 -0
- unaiverse/streamlib/streamlib.py +210 -0
- unaiverse/streams.py +804 -0
- unaiverse/utils/__init__.py +16 -0
- unaiverse/utils/lone_wolf.json +28 -0
- unaiverse/utils/misc.py +441 -0
- unaiverse/utils/sandbox.py +292 -0
- unaiverse/world.py +384 -0
- unaiverse-0.1.12.dist-info/METADATA +366 -0
- unaiverse-0.1.12.dist-info/RECORD +47 -0
- unaiverse-0.1.12.dist-info/WHEEL +5 -0
- unaiverse-0.1.12.dist-info/licenses/LICENSE +177 -0
- unaiverse-0.1.12.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
"""
|
|
2
|
+
█████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
|
|
3
|
+
░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
|
|
4
|
+
░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
|
|
5
|
+
░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
|
|
6
|
+
░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
|
|
7
|
+
░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
|
|
8
|
+
░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
|
|
9
|
+
░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
|
|
10
|
+
A Collectionless AI Project (https://collectionless.ai)
|
|
11
|
+
Registration/Login: https://unaiverse.io
|
|
12
|
+
Code Repositories: https://github.com/collectionlessai/
|
|
13
|
+
Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
|
|
14
|
+
"""
|
|
15
|
+
import io
|
|
16
|
+
import gzip
|
|
17
|
+
import json
|
|
18
|
+
import torch
|
|
19
|
+
from PIL import Image
|
|
20
|
+
from typing import Any
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from unaiverse.dataprops import FileContainer
|
|
23
|
+
from google.protobuf.json_format import MessageToDict, ParseDict
|
|
24
|
+
from google.protobuf.struct_pb2 import Value, ListValue, NULL_VALUE
|
|
25
|
+
|
|
26
|
+
# Import the Protobuf-generated module
|
|
27
|
+
try:
|
|
28
|
+
from . import message_pb2 as pb
|
|
29
|
+
except ImportError:
|
|
30
|
+
print("Error: message_pb2.py not found. Please compile the .proto file first.")
|
|
31
|
+
raise
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Msg:
|
|
35
|
+
|
|
36
|
+
# Message content types
|
|
37
|
+
PROFILE = "profile"
|
|
38
|
+
WORLD_APPROVAL = "world_approval"
|
|
39
|
+
AGENT_APPROVAL = "agent_approval"
|
|
40
|
+
PROFILE_REQUEST = "profile_request"
|
|
41
|
+
ADDRESS_UPDATE = "address_update"
|
|
42
|
+
STREAM_SAMPLE = "stream_sample"
|
|
43
|
+
ACTION_REQUEST = "action_request"
|
|
44
|
+
ROLE_SUGGESTION = "role_suggestion"
|
|
45
|
+
HSM = "hsm"
|
|
46
|
+
MISC = "misc"
|
|
47
|
+
GET_CV_FROM_ROOT = "get_cv_from_root"
|
|
48
|
+
BADGE_SUGGESTIONS = "badge_suggestions"
|
|
49
|
+
INSPECT_ON = "inspect_on"
|
|
50
|
+
INSPECT_CMD = "inspect_cmd"
|
|
51
|
+
WORLD_AGENTS_LIST = "world_agents_list"
|
|
52
|
+
CONSOLE_AND_BEHAV_STATUS = "console_and_behav_status"
|
|
53
|
+
STATS_UPDATE = "stats_update" # agent -> world
|
|
54
|
+
STATS_REQUEST = "stats_request"
|
|
55
|
+
STATS_RESPONSE = 'stats_response' # world -> agent
|
|
56
|
+
|
|
57
|
+
# Collections
|
|
58
|
+
CONTENT_TYPES = {PROFILE, WORLD_APPROVAL, AGENT_APPROVAL, PROFILE_REQUEST, ADDRESS_UPDATE,
|
|
59
|
+
STREAM_SAMPLE, ACTION_REQUEST, ROLE_SUGGESTION, HSM, MISC, GET_CV_FROM_ROOT,
|
|
60
|
+
BADGE_SUGGESTIONS, INSPECT_ON, INSPECT_CMD, WORLD_AGENTS_LIST, CONSOLE_AND_BEHAV_STATUS,
|
|
61
|
+
STATS_UPDATE, STATS_REQUEST, STATS_RESPONSE}
|
|
62
|
+
|
|
63
|
+
def __init__(self,
|
|
64
|
+
sender: str | None = None,
|
|
65
|
+
content: any = None,
|
|
66
|
+
timestamp_net: str | None = None,
|
|
67
|
+
channel: str | None = None,
|
|
68
|
+
content_type: str = MISC,
|
|
69
|
+
piggyback: str | None = None,
|
|
70
|
+
_proto_msg: pb.Message = None):
|
|
71
|
+
"""The constructor should be used either to create a new message filling the fields,
|
|
72
|
+
or to parse an existing Protobuf message (passing _proto_msg). In the latter case,
|
|
73
|
+
the other fields are ignored and the Protobuf message is used as-is. The message is
|
|
74
|
+
simply stored in the internal `_proto_msg` field and other fields can be accessed
|
|
75
|
+
through properties."""
|
|
76
|
+
|
|
77
|
+
self._decoded_content: any = None # Cache for decompressed content
|
|
78
|
+
|
|
79
|
+
if _proto_msg is not None:
|
|
80
|
+
|
|
81
|
+
# Check if any other arguments were simultaneously provided
|
|
82
|
+
other_args = [sender, content, timestamp_net, channel, piggyback]
|
|
83
|
+
if any(arg is not None for arg in other_args):
|
|
84
|
+
raise ValueError("Cannot specify other arguments when creating a Msg from a _proto_msg.")
|
|
85
|
+
|
|
86
|
+
# This path is used by from_bytes, message is already built
|
|
87
|
+
self._proto_msg = _proto_msg
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
# Sanity checks
|
|
91
|
+
assert sender is not None, "Sender must be specified for a new message."
|
|
92
|
+
assert isinstance(sender, str), "Sender must be a string"
|
|
93
|
+
assert timestamp_net is None or isinstance(timestamp_net, str), "Invalid timestamp_net"
|
|
94
|
+
assert channel is None or isinstance(channel, str), "Invalid channel"
|
|
95
|
+
assert content_type in Msg.CONTENT_TYPES, "Invalid content type"
|
|
96
|
+
|
|
97
|
+
# --- SMART CONSTRUCTOR: Populates the correct 'oneof' field ---
|
|
98
|
+
self._proto_msg = pb.Message()
|
|
99
|
+
self._proto_msg.sender = sender if sender is not None else ""
|
|
100
|
+
self._proto_msg.timestamp_net = timestamp_net if timestamp_net is not None else \
|
|
101
|
+
datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
102
|
+
self._proto_msg.content_type = content_type if content_type is not None else self.MISC
|
|
103
|
+
self._proto_msg.channel = channel if channel is not None else "<unknown>"
|
|
104
|
+
self._proto_msg.piggyback = piggyback if piggyback is not None else ""
|
|
105
|
+
|
|
106
|
+
if content is None or content == "<empty>":
|
|
107
|
+
return # Nothing to set in the 'oneof'
|
|
108
|
+
|
|
109
|
+
# Route the content to the correct builder
|
|
110
|
+
if content_type == Msg.STREAM_SAMPLE:
|
|
111
|
+
self._build_stream_sample_content(content)
|
|
112
|
+
elif content_type == Msg.STATS_UPDATE:
|
|
113
|
+
self._build_stats_update_content(content)
|
|
114
|
+
else:
|
|
115
|
+
# All other structured types use the generic json_content field
|
|
116
|
+
self._build_json_content(content)
|
|
117
|
+
|
|
118
|
+
def __str__(self):
|
|
119
|
+
return (f"Msg(sender={self.sender[:12]}..., content_type={self.content_type}, "
|
|
120
|
+
f"channel='{self.channel}', content_len={len(self.to_bytes())} bytes)")
|
|
121
|
+
|
|
122
|
+
# --- Properties for lazy-loading and easy access ---
|
|
123
|
+
@property
|
|
124
|
+
def sender(self):
|
|
125
|
+
return self._proto_msg.sender
|
|
126
|
+
|
|
127
|
+
@sender.setter
|
|
128
|
+
def sender(self, value: str):
|
|
129
|
+
self._proto_msg.sender = value if value is not None else ""
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def content_type(self):
|
|
133
|
+
return self._proto_msg.content_type
|
|
134
|
+
|
|
135
|
+
@content_type.setter
|
|
136
|
+
def content_type(self, value: str):
|
|
137
|
+
self._proto_msg.content_type = value if value is not None else self.MISC
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def channel(self):
|
|
141
|
+
return self._proto_msg.channel
|
|
142
|
+
|
|
143
|
+
@channel.setter
|
|
144
|
+
def channel(self, value: str):
|
|
145
|
+
self._proto_msg.channel = value if value is not None else "<unknown>"
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def piggyback(self):
|
|
149
|
+
return self._proto_msg.piggyback
|
|
150
|
+
|
|
151
|
+
@piggyback.setter
|
|
152
|
+
def piggyback(self, value: str):
|
|
153
|
+
self._proto_msg.piggyback = value if value is not None else ""
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def timestamp_net(self):
|
|
157
|
+
return self._proto_msg.timestamp_net
|
|
158
|
+
|
|
159
|
+
@timestamp_net.setter
|
|
160
|
+
def timestamp_net(self, value: str):
|
|
161
|
+
self._proto_msg.timestamp_net = value if value is not None else ""
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def content(self) -> any:
|
|
165
|
+
"""The main content of the message, decoded on-the-fly with caching."""
|
|
166
|
+
if self._decoded_content is not None:
|
|
167
|
+
return self._decoded_content
|
|
168
|
+
|
|
169
|
+
payload_type = self._proto_msg.WhichOneof("content")
|
|
170
|
+
if payload_type == "stream_sample":
|
|
171
|
+
self._decoded_content = self._parse_stream_sample_content()
|
|
172
|
+
elif payload_type == "stats_update":
|
|
173
|
+
self._decoded_content = self._parse_stats_update_content()
|
|
174
|
+
elif payload_type == "json_content":
|
|
175
|
+
self._decoded_content = self._parse_json_content()
|
|
176
|
+
else:
|
|
177
|
+
self._decoded_content = "<empty>"
|
|
178
|
+
|
|
179
|
+
return self._decoded_content
|
|
180
|
+
|
|
181
|
+
# --- Serialization / Deserialization ---
|
|
182
|
+
def to_bytes(self) -> bytes:
|
|
183
|
+
"""Serializes the internal Protobuf message to bytes."""
|
|
184
|
+
return self._proto_msg.SerializeToString()
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def from_bytes(cls, msg_bytes: bytes) -> 'Msg':
|
|
188
|
+
"""Deserializes a byte array into a new Msg instance."""
|
|
189
|
+
pb_msg = pb.Message()
|
|
190
|
+
pb_msg.ParseFromString(msg_bytes)
|
|
191
|
+
|
|
192
|
+
# Pass the parsed protobuf message to the constructor
|
|
193
|
+
return cls(_proto_msg=pb_msg)
|
|
194
|
+
|
|
195
|
+
# --- Internal Helper Methods ---
|
|
196
|
+
def _build_json_content(self, content: dict):
|
|
197
|
+
"""Populates the generic json_content field."""
|
|
198
|
+
self._proto_msg.json_content = json.dumps(content)
|
|
199
|
+
|
|
200
|
+
def _parse_json_content(self) -> dict:
|
|
201
|
+
"""Parses the generic json_content field back to a dict."""
|
|
202
|
+
return json.loads(self._proto_msg.json_content)
|
|
203
|
+
|
|
204
|
+
def _build_stream_sample_content(self, samples_dict: dict):
|
|
205
|
+
"""Builds the complex StreamSampleContent message from a dict."""
|
|
206
|
+
content_pb = self._proto_msg.stream_sample
|
|
207
|
+
for name, sample_info in samples_dict.items():
|
|
208
|
+
data = sample_info.get('data')
|
|
209
|
+
if data is None:
|
|
210
|
+
continue
|
|
211
|
+
|
|
212
|
+
stream_sample_pb = content_pb.samples[name]
|
|
213
|
+
stream_sample_pb.data_tag = sample_info.get('data_tag', -1)
|
|
214
|
+
uuid = sample_info.get('data_uuid')
|
|
215
|
+
|
|
216
|
+
# Only set the field if the uuid is not None
|
|
217
|
+
if uuid is not None:
|
|
218
|
+
stream_sample_pb.data_uuid = uuid
|
|
219
|
+
|
|
220
|
+
if isinstance(data, torch.Tensor):
|
|
221
|
+
raw_bytes = data.detach().cpu().numpy().tobytes()
|
|
222
|
+
with io.BytesIO() as buffer:
|
|
223
|
+
with gzip.GzipFile(fileobj=buffer, mode='wb') as f:
|
|
224
|
+
f.write(raw_bytes)
|
|
225
|
+
stream_sample_pb.data.tensor_data.data = buffer.getvalue()
|
|
226
|
+
stream_sample_pb.data.tensor_data.dtype = str(data.dtype).split('.')[-1]
|
|
227
|
+
stream_sample_pb.data.tensor_data.shape.extend(list(data.shape))
|
|
228
|
+
|
|
229
|
+
elif isinstance(data, Image.Image):
|
|
230
|
+
with io.BytesIO() as buffer:
|
|
231
|
+
data.save(buffer, format="PNG", optimize=True, compress_level=9)
|
|
232
|
+
stream_sample_pb.data.image_data.data = buffer.getvalue()
|
|
233
|
+
|
|
234
|
+
elif isinstance(data, str):
|
|
235
|
+
stream_sample_pb.data.text_data.data = data
|
|
236
|
+
|
|
237
|
+
elif isinstance(data, FileContainer):
|
|
238
|
+
# Auto-convert string content to bytes if needed
|
|
239
|
+
raw_bytes = data.content.encode('utf-8') if isinstance(data.content, str) else data.content
|
|
240
|
+
stream_sample_pb.data.file_data.content = raw_bytes
|
|
241
|
+
stream_sample_pb.data.file_data.filename = data.filename
|
|
242
|
+
stream_sample_pb.data.file_data.mime_type = data.mime_type
|
|
243
|
+
|
|
244
|
+
def _parse_stream_sample_content(self) -> dict:
|
|
245
|
+
"""
|
|
246
|
+
Parses the internal StreamSampleContent message back into a Python
|
|
247
|
+
dictionary of tensors and images.
|
|
248
|
+
"""
|
|
249
|
+
py_dict = {}
|
|
250
|
+
|
|
251
|
+
# Iterate through the Protobuf map ('samples')
|
|
252
|
+
for name, sample_pb in self._proto_msg.stream_sample.samples.items():
|
|
253
|
+
data_payload = sample_pb.data
|
|
254
|
+
data = None
|
|
255
|
+
|
|
256
|
+
# Check which field in the 'oneof' is set
|
|
257
|
+
payload_type = data_payload.WhichOneof("data_payload")
|
|
258
|
+
|
|
259
|
+
if payload_type == "tensor_data":
|
|
260
|
+
tensor_data = data_payload.tensor_data
|
|
261
|
+
|
|
262
|
+
# Decompress and reconstruct the tensor
|
|
263
|
+
with gzip.GzipFile(fileobj=io.BytesIO(tensor_data.data), mode='rb') as f:
|
|
264
|
+
raw_bytes = f.read()
|
|
265
|
+
data = torch.frombuffer(
|
|
266
|
+
bytearray(raw_bytes),
|
|
267
|
+
dtype=getattr(torch, tensor_data.dtype)
|
|
268
|
+
).reshape(list(tensor_data.shape))
|
|
269
|
+
|
|
270
|
+
elif payload_type == "image_data":
|
|
271
|
+
data = Image.open(io.BytesIO(data_payload.image_data.data))
|
|
272
|
+
|
|
273
|
+
elif payload_type == "text_data":
|
|
274
|
+
data = data_payload.text_data.data
|
|
275
|
+
|
|
276
|
+
elif payload_type == "file_data":
|
|
277
|
+
f_data = data_payload.file_data
|
|
278
|
+
data = FileContainer(
|
|
279
|
+
content=f_data.content,
|
|
280
|
+
filename=f_data.filename,
|
|
281
|
+
mime_type=f_data.mime_type
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Build the final Python dictionary for this sample
|
|
285
|
+
py_dict[name] = {
|
|
286
|
+
'data': data,
|
|
287
|
+
'data_tag': sample_pb.data_tag,
|
|
288
|
+
'data_uuid': sample_pb.data_uuid if sample_pb.HasField("data_uuid") else None
|
|
289
|
+
}
|
|
290
|
+
return py_dict
|
|
291
|
+
|
|
292
|
+
def _py_value_to_proto_value(self, py_val: Any) -> Value:
|
|
293
|
+
"""Helper to convert a Python type into a google.protobuf.Value."""
|
|
294
|
+
if py_val is None:
|
|
295
|
+
return Value(null_value=NULL_VALUE)
|
|
296
|
+
if isinstance(py_val, (int, float)):
|
|
297
|
+
return Value(number_value=py_val)
|
|
298
|
+
if isinstance(py_val, str):
|
|
299
|
+
return Value(string_value=py_val)
|
|
300
|
+
if isinstance(py_val, bool):
|
|
301
|
+
return Value(bool_value=py_val)
|
|
302
|
+
if isinstance(py_val, list):
|
|
303
|
+
lv = ListValue()
|
|
304
|
+
for item in py_val:
|
|
305
|
+
lv.values.append(self._py_value_to_proto_value(item))
|
|
306
|
+
return Value(list_value=lv)
|
|
307
|
+
if isinstance(py_val, dict):
|
|
308
|
+
# This is recursive for dicts/structs
|
|
309
|
+
s = Value(struct_value={})
|
|
310
|
+
ParseDict(py_val, s.struct_value)
|
|
311
|
+
return s
|
|
312
|
+
|
|
313
|
+
# Fallback
|
|
314
|
+
return Value(string_value=str(py_val))
|
|
315
|
+
|
|
316
|
+
def _build_stats_update_content(self, payload_list: list):
|
|
317
|
+
"""Builds the StatBatch message from a List[Dict]."""
|
|
318
|
+
batch_pb = self._proto_msg.stats_update
|
|
319
|
+
|
|
320
|
+
for update_dict in payload_list:
|
|
321
|
+
update_pb = batch_pb.updates.add()
|
|
322
|
+
update_pb.peer_id = update_dict['peer_id']
|
|
323
|
+
update_pb.stat_name = update_dict['stat_name']
|
|
324
|
+
update_pb.timestamp = int(update_dict['timestamp']) # Ensure int
|
|
325
|
+
|
|
326
|
+
# Convert the Python 'value' to a Protobuf 'Value'
|
|
327
|
+
py_value = update_dict['value']
|
|
328
|
+
update_pb.value.CopyFrom(self._py_value_to_proto_value(py_value))
|
|
329
|
+
|
|
330
|
+
def _proto_value_to_py_value(self, proto_val: Value) -> Any:
|
|
331
|
+
"""Helper to convert a google.protobuf.Value into a Python type."""
|
|
332
|
+
kind = proto_val.WhichOneof("kind")
|
|
333
|
+
if kind == "null_value":
|
|
334
|
+
return None
|
|
335
|
+
if kind == "number_value":
|
|
336
|
+
return proto_val.number_value
|
|
337
|
+
if kind == "string_value":
|
|
338
|
+
return proto_val.string_value
|
|
339
|
+
if kind == "bool_value":
|
|
340
|
+
return proto_val.bool_value
|
|
341
|
+
if kind == "list_value":
|
|
342
|
+
return [self._proto_value_to_py_value(v) for v in proto_val.list_value.values]
|
|
343
|
+
if kind == "struct_value":
|
|
344
|
+
# Use the helper to convert a Struct to a dict
|
|
345
|
+
return MessageToDict(proto_val.struct_value)
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
def _parse_stats_update_content(self) -> list:
|
|
349
|
+
"""Parses the StatBatch message back into a List[Dict]."""
|
|
350
|
+
py_list = []
|
|
351
|
+
batch_pb = self._proto_msg.stats_update
|
|
352
|
+
|
|
353
|
+
for update_pb in batch_pb.updates:
|
|
354
|
+
py_list.append({
|
|
355
|
+
"peer_id": update_pb.peer_id,
|
|
356
|
+
"stat_name": update_pb.stat_name,
|
|
357
|
+
"timestamp": update_pb.timestamp,
|
|
358
|
+
"value": self._proto_value_to_py_value(update_pb.value)
|
|
359
|
+
})
|
|
360
|
+
return py_list
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
█████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
|
|
3
|
+
░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
|
|
4
|
+
░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
|
|
5
|
+
░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
|
|
6
|
+
░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
|
|
7
|
+
░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
|
|
8
|
+
░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
|
|
9
|
+
░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
|
|
10
|
+
A Collectionless AI Project (https://collectionless.ai)
|
|
11
|
+
Registration/Login: https://unaiverse.io
|
|
12
|
+
Code Repositories: https://github.com/collectionlessai/
|
|
13
|
+
Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
|
|
14
|
+
"""
|
|
15
|
+
import os
|
|
16
|
+
import logging
|
|
17
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
18
|
+
|
|
19
|
+
LOG_FOLDER = "unaiverse/networking/p2p/logs"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def setup_logger(module_name: str, when: str = 'midnight', backup_count: int = 7) -> logging.Logger:
|
|
23
|
+
"""
|
|
24
|
+
Sets up a logger for a specific module with a timed rotating file handler.
|
|
25
|
+
Args:
|
|
26
|
+
module_name (str): The name of the module for which the logger is being set up.
|
|
27
|
+
when (str, optional): Specifies the type of interval for log rotation.
|
|
28
|
+
Defaults to 'midnight'. Common values include 'S', 'M', 'H', 'D', 'midnight', etc.
|
|
29
|
+
backup_count (int, optional): The number of backup log files to keep. Defaults to 7.
|
|
30
|
+
Returns:
|
|
31
|
+
logging.Logger: A configured logger instance for the specified module.
|
|
32
|
+
Notes:
|
|
33
|
+
- Log files are stored in a predefined folder (`LOG_FOLDER`).
|
|
34
|
+
- Log rotation occurs based on the specified interval (`when`).
|
|
35
|
+
- The logger writes logs in UTF-8 encoding and uses UTC time by default.
|
|
36
|
+
- The log format includes timestamp, log level, filename, line number, and the log message.
|
|
37
|
+
- If the logger for the module already exists, it will not add duplicate handlers.
|
|
38
|
+
"""
|
|
39
|
+
do_log = False # Stefano
|
|
40
|
+
|
|
41
|
+
# Ensure log folder exists
|
|
42
|
+
if do_log:
|
|
43
|
+
os.makedirs(LOG_FOLDER, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
# Log file base path (without date suffix)
|
|
46
|
+
log_base_filename = os.path.join(LOG_FOLDER, f"{module_name}.log")
|
|
47
|
+
|
|
48
|
+
# Create logger
|
|
49
|
+
logger = logging.getLogger(module_name)
|
|
50
|
+
logger.setLevel(logging.DEBUG)
|
|
51
|
+
|
|
52
|
+
if do_log and not logger.handlers:
|
|
53
|
+
|
|
54
|
+
# Create rotating file handler
|
|
55
|
+
handler = TimedRotatingFileHandler(
|
|
56
|
+
log_base_filename,
|
|
57
|
+
when=when,
|
|
58
|
+
interval=1,
|
|
59
|
+
backupCount=backup_count,
|
|
60
|
+
encoding='utf-8',
|
|
61
|
+
utc=True # Set to False if you want local time
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
formatter = logging.Formatter(
|
|
65
|
+
fmt="%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s",
|
|
66
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
67
|
+
)
|
|
68
|
+
handler.setFormatter(formatter)
|
|
69
|
+
logger.addHandler(handler)
|
|
70
|
+
|
|
71
|
+
# Avoid logging to root handlers
|
|
72
|
+
logger.propagate = False
|
|
73
|
+
|
|
74
|
+
if not do_log:
|
|
75
|
+
logger.handlers.clear()
|
|
76
|
+
logger.addHandler(logging.NullHandler())
|
|
77
|
+
|
|
78
|
+
return logger
|