iris-pex-embedded-python 3.3.0b6__py3-none-any.whl → 3.3.1b1__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.
Potentially problematic release.
This version of iris-pex-embedded-python might be problematic. Click here for more details.
- iop/_serialization.py +182 -159
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/METADATA +1 -1
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/RECORD +7 -7
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/LICENSE +0 -0
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/entry_points.txt +0 -0
- {iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/top_level.txt +0 -0
iop/_serialization.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import base64
|
|
2
3
|
import codecs
|
|
3
4
|
import datetime
|
|
@@ -6,191 +7,213 @@ import importlib
|
|
|
6
7
|
import json
|
|
7
8
|
import pickle
|
|
8
9
|
import uuid
|
|
9
|
-
from
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from dataclasses import is_dataclass
|
|
12
|
+
from typing import Any, Dict, Type, Optional
|
|
10
13
|
|
|
11
14
|
from dacite import Config, from_dict
|
|
12
15
|
import iris
|
|
13
16
|
|
|
14
17
|
from iop._utils import _Utils
|
|
15
18
|
|
|
19
|
+
# Constants
|
|
20
|
+
DATETIME_FORMAT_LENGTH = 23
|
|
21
|
+
TIME_FORMAT_LENGTH = 12
|
|
22
|
+
TYPE_SEPARATOR = ':'
|
|
23
|
+
SUPPORTED_TYPES = {
|
|
24
|
+
'datetime', 'date', 'time', 'dataframe',
|
|
25
|
+
'decimal', 'uuid', 'bytes'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class SerializationError(Exception):
|
|
29
|
+
"""Base exception for serialization errors."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
class TypeConverter:
|
|
33
|
+
"""Handles type conversion for special data types."""
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def convert_to_string(typ: str, obj: Any) -> str:
|
|
37
|
+
if typ == 'dataframe':
|
|
38
|
+
return obj.to_json(orient="table")
|
|
39
|
+
elif typ == 'datetime':
|
|
40
|
+
return TypeConverter._format_datetime(obj)
|
|
41
|
+
elif typ == 'date':
|
|
42
|
+
return obj.isoformat()
|
|
43
|
+
elif typ == 'time':
|
|
44
|
+
return TypeConverter._format_time(obj)
|
|
45
|
+
elif typ == 'bytes':
|
|
46
|
+
return base64.b64encode(obj).decode("UTF-8")
|
|
47
|
+
return str(obj)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def convert_from_string(typ: str, val: str) -> Any:
|
|
51
|
+
try:
|
|
52
|
+
if typ == 'datetime':
|
|
53
|
+
return datetime.datetime.fromisoformat(val)
|
|
54
|
+
elif typ == 'date':
|
|
55
|
+
return datetime.date.fromisoformat(val)
|
|
56
|
+
elif typ == 'time':
|
|
57
|
+
return datetime.time.fromisoformat(val)
|
|
58
|
+
elif typ == 'dataframe':
|
|
59
|
+
try:
|
|
60
|
+
import pandas as pd
|
|
61
|
+
except ImportError:
|
|
62
|
+
raise SerializationError("Failed to load pandas module")
|
|
63
|
+
return pd.read_json(val, orient="table")
|
|
64
|
+
elif typ == 'decimal':
|
|
65
|
+
return decimal.Decimal(val)
|
|
66
|
+
elif typ == 'uuid':
|
|
67
|
+
return uuid.UUID(val)
|
|
68
|
+
elif typ == 'bytes':
|
|
69
|
+
return base64.b64decode(val.encode("UTF-8"))
|
|
70
|
+
return val
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise SerializationError(f"Failed to convert type {typ}: {str(e)}")
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _format_datetime(dt: datetime.datetime) -> str:
|
|
76
|
+
r = dt.isoformat()
|
|
77
|
+
if dt.microsecond:
|
|
78
|
+
r = r[:DATETIME_FORMAT_LENGTH] + r[26:]
|
|
79
|
+
if r.endswith("+00:00"):
|
|
80
|
+
r = r[:-6] + "Z"
|
|
81
|
+
return r
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def _format_time(t: datetime.time) -> str:
|
|
85
|
+
r = t.isoformat()
|
|
86
|
+
if t.microsecond:
|
|
87
|
+
r = r[:TIME_FORMAT_LENGTH]
|
|
88
|
+
return r
|
|
89
|
+
|
|
16
90
|
class IrisJSONEncoder(json.JSONEncoder):
|
|
17
91
|
"""JSONEncoder that handles dates, decimals, UUIDs, etc."""
|
|
18
92
|
|
|
19
|
-
def default(self,
|
|
20
|
-
if
|
|
21
|
-
return 'dataframe:
|
|
22
|
-
elif isinstance(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
return 'decimal:' + str(o)
|
|
38
|
-
elif isinstance(o, uuid.UUID):
|
|
39
|
-
return 'uuid:' + str(o)
|
|
40
|
-
elif isinstance(o, bytes):
|
|
41
|
-
return 'bytes:' + base64.b64encode(o).decode("UTF-8")
|
|
42
|
-
elif hasattr(o, '__dict__'):
|
|
43
|
-
return o.__dict__
|
|
44
|
-
return super().default(o)
|
|
93
|
+
def default(self, obj: Any) -> Any:
|
|
94
|
+
if obj.__class__.__name__ == 'DataFrame':
|
|
95
|
+
return f'dataframe:{TypeConverter.convert_to_string("dataframe", obj)}'
|
|
96
|
+
elif isinstance(obj, datetime.datetime):
|
|
97
|
+
return f'datetime:{TypeConverter.convert_to_string("datetime", obj)}'
|
|
98
|
+
elif isinstance(obj, datetime.date):
|
|
99
|
+
return f'date:{TypeConverter.convert_to_string("date", obj)}'
|
|
100
|
+
elif isinstance(obj, datetime.time):
|
|
101
|
+
return f'time:{TypeConverter.convert_to_string("time", obj)}'
|
|
102
|
+
elif isinstance(obj, decimal.Decimal):
|
|
103
|
+
return f'decimal:{obj}'
|
|
104
|
+
elif isinstance(obj, uuid.UUID):
|
|
105
|
+
return f'uuid:{obj}'
|
|
106
|
+
elif isinstance(obj, bytes):
|
|
107
|
+
return f'bytes:{TypeConverter.convert_to_string("bytes", obj)}'
|
|
108
|
+
elif hasattr(obj, '__dict__'):
|
|
109
|
+
return obj.__dict__
|
|
110
|
+
return super().default(obj)
|
|
45
111
|
|
|
46
112
|
class IrisJSONDecoder(json.JSONDecoder):
|
|
47
113
|
"""JSONDecoder that handles special type annotations."""
|
|
48
114
|
|
|
49
115
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
50
|
-
|
|
51
|
-
self, object_hook=self.object_hook, *args, **kwargs)
|
|
116
|
+
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
|
52
117
|
|
|
53
118
|
def object_hook(self, obj: Dict) -> Dict:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
i = value.find(":")
|
|
59
|
-
if i > 0:
|
|
60
|
-
typ = value[:i]
|
|
61
|
-
val = value[i+1:]
|
|
62
|
-
ret[key] = self._convert_typed_value(typ, val)
|
|
63
|
-
else:
|
|
64
|
-
ret[key] = value
|
|
65
|
-
return ret
|
|
119
|
+
return {
|
|
120
|
+
key: self._process_value(value)
|
|
121
|
+
for key, value in obj.items()
|
|
122
|
+
}
|
|
66
123
|
|
|
67
|
-
def
|
|
68
|
-
if
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return msg
|
|
106
|
-
|
|
107
|
-
def serialize_message(message: Any) -> iris.cls:
|
|
108
|
-
"""Converts a python dataclass message into an iris iop.message.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
message: The message to serialize, an instance of a class that is a subclass of Message.
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
The message in json format.
|
|
115
|
-
"""
|
|
116
|
-
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
117
|
-
module = message.__class__.__module__
|
|
118
|
-
classname = message.__class__.__name__
|
|
119
|
-
|
|
120
|
-
msg = iris.cls('IOP.Message')._New()
|
|
121
|
-
msg.classname = module + "." + classname
|
|
122
|
-
|
|
123
|
-
if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
|
|
124
|
-
msg.json = _Utils.string_to_stream(json_string, msg.buffer)
|
|
125
|
-
else:
|
|
126
|
-
msg.json = json_string
|
|
127
|
-
|
|
128
|
-
return msg
|
|
129
|
-
|
|
130
|
-
def deserialize_pickle_message(serial: iris.cls) -> Any:
|
|
131
|
-
"""Converts an iris iop.message into a python dataclass message.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
serial: The serialized message
|
|
124
|
+
def _process_value(self, value: Any) -> Any:
|
|
125
|
+
if isinstance(value, str) and TYPE_SEPARATOR in value:
|
|
126
|
+
typ, val = value.split(TYPE_SEPARATOR, 1)
|
|
127
|
+
if typ in SUPPORTED_TYPES:
|
|
128
|
+
return TypeConverter.convert_from_string(typ, val)
|
|
129
|
+
return value
|
|
130
|
+
|
|
131
|
+
class MessageSerializer:
|
|
132
|
+
"""Handles message serialization and deserialization."""
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def serialize(message: Any, use_pickle: bool = False) -> iris.cls:
|
|
136
|
+
"""Serializes a message to IRIS format."""
|
|
137
|
+
if use_pickle:
|
|
138
|
+
return MessageSerializer._serialize_pickle(message)
|
|
139
|
+
return MessageSerializer._serialize_json(message)
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def deserialize(serial: iris.cls, use_pickle: bool = False) -> Any:
|
|
143
|
+
"""Deserializes a message from IRIS format."""
|
|
144
|
+
if use_pickle:
|
|
145
|
+
return MessageSerializer._deserialize_pickle(serial)
|
|
146
|
+
return MessageSerializer._deserialize_json(serial)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def _serialize_pickle(message: Any) -> iris.cls:
|
|
150
|
+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
151
|
+
msg = iris.cls('IOP.PickleMessage')._New()
|
|
152
|
+
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
153
|
+
msg.jstr = _Utils.string_to_stream(pickle_string)
|
|
154
|
+
return msg
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _serialize_json(message: Any) -> iris.cls:
|
|
158
|
+
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
159
|
+
msg = iris.cls('IOP.Message')._New()
|
|
160
|
+
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
135
161
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
162
|
+
if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
|
|
163
|
+
msg.json = _Utils.string_to_stream(json_string, msg.buffer)
|
|
164
|
+
else:
|
|
165
|
+
msg.json = json_string
|
|
166
|
+
return msg
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def _deserialize_pickle(serial: iris.cls) -> Any:
|
|
170
|
+
string = _Utils.stream_to_string(serial.jstr)
|
|
171
|
+
return pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def _deserialize_json(serial: iris.cls) -> Any:
|
|
175
|
+
if not serial.classname:
|
|
176
|
+
raise SerializationError("JSON message malformed, must include classname")
|
|
149
177
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
jdict = json.loads(string, cls=IrisJSONDecoder)
|
|
173
|
-
msg = dataclass_from_dict(msg, jdict)
|
|
174
|
-
return msg
|
|
178
|
+
try:
|
|
179
|
+
module_name, class_name = MessageSerializer._parse_classname(serial.classname)
|
|
180
|
+
module = importlib.import_module(module_name)
|
|
181
|
+
msg_class = getattr(module, class_name)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
raise SerializationError(f"Failed to load class {serial.classname}: {str(e)}")
|
|
184
|
+
|
|
185
|
+
json_string = (_Utils.stream_to_string(serial.json)
|
|
186
|
+
if serial.type == 'Stream' else serial.json)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
json_dict = json.loads(json_string, cls=IrisJSONDecoder)
|
|
190
|
+
return dataclass_from_dict(msg_class, json_dict)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise SerializationError(f"Failed to deserialize JSON: {str(e)}")
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _parse_classname(classname: str) -> tuple[str, str]:
|
|
196
|
+
j = classname.rindex(".")
|
|
197
|
+
if j <= 0:
|
|
198
|
+
raise SerializationError(f"Classname must include a module: {classname}")
|
|
199
|
+
return classname[:j], classname[j+1:]
|
|
175
200
|
|
|
176
201
|
def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
|
|
177
|
-
"""Converts a dictionary to a dataclass instance.
|
|
178
|
-
|
|
179
|
-
Args:
|
|
180
|
-
klass: The dataclass to convert to
|
|
181
|
-
dikt: The dictionary to convert to a dataclass
|
|
182
|
-
|
|
183
|
-
Returns:
|
|
184
|
-
A dataclass object with the fields of the dataclass and the fields of the dictionary.
|
|
185
|
-
"""
|
|
202
|
+
"""Converts a dictionary to a dataclass instance."""
|
|
186
203
|
ret = from_dict(klass, dikt, Config(check_types=False))
|
|
187
204
|
|
|
188
205
|
try:
|
|
189
206
|
fieldtypes = klass.__annotations__
|
|
190
|
-
except Exception
|
|
191
|
-
fieldtypes =
|
|
207
|
+
except Exception:
|
|
208
|
+
fieldtypes = {}
|
|
192
209
|
|
|
193
210
|
for key, val in dikt.items():
|
|
194
211
|
if key not in fieldtypes:
|
|
195
212
|
setattr(ret, key, val)
|
|
196
|
-
return ret
|
|
213
|
+
return ret
|
|
214
|
+
|
|
215
|
+
# Maintain backwards compatibility
|
|
216
|
+
serialize_pickle_message = lambda msg: MessageSerializer.serialize(msg, use_pickle=True)
|
|
217
|
+
serialize_message = lambda msg: MessageSerializer.serialize(msg, use_pickle=False)
|
|
218
|
+
deserialize_pickle_message = lambda serial: MessageSerializer.deserialize(serial, use_pickle=True)
|
|
219
|
+
deserialize_message = lambda serial: MessageSerializer.deserialize(serial, use_pickle=False)
|
{iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/RECORD
RENAMED
|
@@ -110,7 +110,7 @@ iop/_outbound_adapter.py,sha256=YTAhLrRf9chEAd53mV6KKbpaHOKNOKJHoGgj5wakRR0,726
|
|
|
110
110
|
iop/_pickle_message.py,sha256=noKfc2VkXufV3fqjKvNHN_oANQ1YN9ffCaSV0XSTAIE,331
|
|
111
111
|
iop/_private_session_duplex.py,sha256=mzlFABh-ly51X1uSWw9YwQbktfMvuNdp2ALlRvlDow4,5152
|
|
112
112
|
iop/_private_session_process.py,sha256=todprfYFSDr-h-BMvWL_IGC6wbQqkMy3mPHWEWCUSE0,1686
|
|
113
|
-
iop/_serialization.py,sha256=
|
|
113
|
+
iop/_serialization.py,sha256=jHJGTMpKI2QDcF_m7R0WyH_J-vjQyYpNpD0ktn9Rqo4,8106
|
|
114
114
|
iop/_utils.py,sha256=Aqtp9Jx3ghzkNs4f2cOZXYwv8cGsjmdBocnkP64fa3M,19574
|
|
115
115
|
iop/cls/IOP/BusinessOperation.cls,sha256=lrymqZ8wHl5kJjXwdjbQVs5sScV__yIWGh-oGbiB_X0,914
|
|
116
116
|
iop/cls/IOP/BusinessProcess.cls,sha256=s3t38w1ykHqM26ETcbCYLt0ocjZyVVahm-_USZkuJ1E,2855
|
|
@@ -136,9 +136,9 @@ iop/cls/IOP/Service/WSGI.cls,sha256=VLNCXEwmHW9dBnE51uGE1nvGX6T4HjhqePT3LVhsjAE,
|
|
|
136
136
|
iop/wsgi/handlers.py,sha256=NrFLo_YbAh-x_PlWhAiWkQnUUN2Ss9HoEm63dDWCBpQ,2947
|
|
137
137
|
irisnative/_IRISNative.py,sha256=HQ4nBhc8t8_5OtxdMG-kx1aa-T1znf2I8obZOPLOPzg,665
|
|
138
138
|
irisnative/__init__.py,sha256=6YmvBLQSURsCPKaNg7LK-xpo4ipDjrlhKuwdfdNb3Kg,341
|
|
139
|
-
iris_pex_embedded_python-3.3.
|
|
140
|
-
iris_pex_embedded_python-3.3.
|
|
141
|
-
iris_pex_embedded_python-3.3.
|
|
142
|
-
iris_pex_embedded_python-3.3.
|
|
143
|
-
iris_pex_embedded_python-3.3.
|
|
144
|
-
iris_pex_embedded_python-3.3.
|
|
139
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
|
|
140
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/METADATA,sha256=BcT-Xi5r6ps-VXr5Kk_e7njZ8JU_Gqg6CoSQzUH9i5U,4427
|
|
141
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
142
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/entry_points.txt,sha256=pj-i4LSDyiSP6xpHlVjMCbg1Pik7dC3_sdGY3Yp9Vhk,38
|
|
143
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/top_level.txt,sha256=VWDlX4YF4qFVRGrG3-Gs0kgREol02i8gIpsHNbhfFPw,42
|
|
144
|
+
iris_pex_embedded_python-3.3.1b1.dist-info/RECORD,,
|
{iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/LICENSE
RENAMED
|
File without changes
|
{iris_pex_embedded_python-3.3.0b6.dist-info → iris_pex_embedded_python-3.3.1b1.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|