iris-pex-embedded-python 3.4.0b4__py3-none-any.whl → 3.4.0b5__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 +86 -161
- iop/_utils.py +3 -2
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/METADATA +1 -3
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/RECORD +8 -8
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/LICENSE +0 -0
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/WHEEL +0 -0
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/entry_points.txt +0 -0
- {iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/top_level.txt +0 -0
iop/_serialization.py
CHANGED
|
@@ -1,173 +1,52 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import base64
|
|
3
2
|
import codecs
|
|
4
|
-
import datetime
|
|
5
|
-
import decimal
|
|
6
3
|
import importlib
|
|
7
|
-
import
|
|
4
|
+
import inspect
|
|
8
5
|
import pickle
|
|
9
|
-
import
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import asdict, is_dataclass
|
|
10
8
|
from typing import Any, Dict, Type
|
|
11
9
|
|
|
12
|
-
from dacite import Config, from_dict
|
|
13
10
|
import iris
|
|
11
|
+
from pydantic import BaseModel, TypeAdapter
|
|
14
12
|
|
|
15
13
|
from iop._message import _PydanticPickleMessage
|
|
16
14
|
from iop._utils import _Utils
|
|
17
|
-
from pydantic import BaseModel
|
|
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
15
|
|
|
28
16
|
class SerializationError(Exception):
|
|
29
|
-
"""
|
|
17
|
+
"""Exception raised for serialization errors."""
|
|
30
18
|
pass
|
|
31
19
|
|
|
32
|
-
class
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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)}")
|
|
20
|
+
class TempPydanticModel(BaseModel):
|
|
21
|
+
model_config = {
|
|
22
|
+
'arbitrary_types_allowed' : True,
|
|
23
|
+
'extra' : 'allow'
|
|
24
|
+
}
|
|
73
25
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
26
|
+
class MessageSerializer:
|
|
27
|
+
"""Handles message serialization and deserialization."""
|
|
82
28
|
|
|
83
29
|
@staticmethod
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
if t.microsecond:
|
|
87
|
-
r = r[:TIME_FORMAT_LENGTH]
|
|
88
|
-
return r
|
|
89
|
-
|
|
90
|
-
class IrisJSONEncoder(json.JSONEncoder):
|
|
91
|
-
"""JSONEncoder that handles dates, decimals, UUIDs, etc."""
|
|
92
|
-
|
|
93
|
-
def default(self, obj: Any) -> Any:
|
|
30
|
+
def _convert_to_json_safe(obj: Any) -> Any:
|
|
31
|
+
"""Convert objects to JSON-safe format."""
|
|
94
32
|
if isinstance(obj, BaseModel):
|
|
95
|
-
return obj.
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
elif isinstance(obj, datetime.date):
|
|
101
|
-
return f'date:{TypeConverter.convert_to_string("date", obj)}'
|
|
102
|
-
elif isinstance(obj, datetime.time):
|
|
103
|
-
return f'time:{TypeConverter.convert_to_string("time", obj)}'
|
|
104
|
-
elif isinstance(obj, decimal.Decimal):
|
|
105
|
-
return f'decimal:{obj}'
|
|
106
|
-
elif isinstance(obj, uuid.UUID):
|
|
107
|
-
return f'uuid:{obj}'
|
|
108
|
-
elif isinstance(obj, bytes):
|
|
109
|
-
return f'bytes:{TypeConverter.convert_to_string("bytes", obj)}'
|
|
110
|
-
elif hasattr(obj, '__dict__'):
|
|
111
|
-
return obj.__dict__
|
|
112
|
-
return super().default(obj)
|
|
113
|
-
|
|
114
|
-
class IrisJSONDecoder(json.JSONDecoder):
|
|
115
|
-
"""JSONDecoder that handles special type annotations."""
|
|
116
|
-
|
|
117
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
118
|
-
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
|
119
|
-
|
|
120
|
-
def object_hook(self, obj: Dict) -> Dict:
|
|
121
|
-
return {
|
|
122
|
-
key: self._process_value(value)
|
|
123
|
-
for key, value in obj.items()
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
def _process_value(self, value: Any) -> Any:
|
|
127
|
-
if isinstance(value, str) and TYPE_SEPARATOR in value:
|
|
128
|
-
typ, val = value.split(TYPE_SEPARATOR, 1)
|
|
129
|
-
if typ in SUPPORTED_TYPES:
|
|
130
|
-
return TypeConverter.convert_from_string(typ, val)
|
|
131
|
-
return value
|
|
132
|
-
|
|
133
|
-
class MessageSerializer:
|
|
134
|
-
"""Handles message serialization and deserialization."""
|
|
33
|
+
return obj.model_dump_json()
|
|
34
|
+
elif is_dataclass(obj):
|
|
35
|
+
return TempPydanticModel.model_validate(dataclass_to_dict(obj)).model_dump_json()
|
|
36
|
+
else:
|
|
37
|
+
raise SerializationError(f"Object {obj} must be a Pydantic model or dataclass")
|
|
135
38
|
|
|
136
39
|
@staticmethod
|
|
137
40
|
def serialize(message: Any, use_pickle: bool = False) -> iris.cls:
|
|
138
41
|
"""Serializes a message to IRIS format."""
|
|
139
|
-
|
|
140
|
-
if isinstance(message, _PydanticPickleMessage):
|
|
141
|
-
return MessageSerializer._serialize_pickle(message)
|
|
142
|
-
if isinstance(message, BaseModel):
|
|
143
|
-
return (MessageSerializer._serialize_pickle(message)
|
|
144
|
-
if use_pickle else MessageSerializer._serialize_json(message))
|
|
145
|
-
if use_pickle:
|
|
42
|
+
if isinstance(message, _PydanticPickleMessage) or use_pickle:
|
|
146
43
|
return MessageSerializer._serialize_pickle(message)
|
|
147
44
|
return MessageSerializer._serialize_json(message)
|
|
148
45
|
|
|
149
|
-
@staticmethod
|
|
150
|
-
def deserialize(serial: iris.cls, use_pickle: bool = False) -> Any:
|
|
151
|
-
"""Deserializes a message from IRIS format."""
|
|
152
|
-
if use_pickle:
|
|
153
|
-
return MessageSerializer._deserialize_pickle(serial)
|
|
154
|
-
return MessageSerializer._deserialize_json(serial)
|
|
155
|
-
|
|
156
|
-
@staticmethod
|
|
157
|
-
def _serialize_pickle(message: Any) -> iris.cls:
|
|
158
|
-
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
159
|
-
msg = iris.cls('IOP.PickleMessage')._New()
|
|
160
|
-
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
161
|
-
msg.jstr = _Utils.string_to_stream(pickle_string)
|
|
162
|
-
return msg
|
|
163
|
-
|
|
164
46
|
@staticmethod
|
|
165
47
|
def _serialize_json(message: Any) -> iris.cls:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
else:
|
|
169
|
-
json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
|
|
170
|
-
|
|
48
|
+
json_string = MessageSerializer._convert_to_json_safe(message)
|
|
49
|
+
|
|
171
50
|
msg = iris.cls('IOP.Message')._New()
|
|
172
51
|
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
173
52
|
|
|
@@ -178,9 +57,10 @@ class MessageSerializer:
|
|
|
178
57
|
return msg
|
|
179
58
|
|
|
180
59
|
@staticmethod
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
60
|
+
def deserialize(serial: iris.cls, use_pickle: bool = False) -> Any:
|
|
61
|
+
if use_pickle:
|
|
62
|
+
return MessageSerializer._deserialize_pickle(serial)
|
|
63
|
+
return MessageSerializer._deserialize_json(serial)
|
|
184
64
|
|
|
185
65
|
@staticmethod
|
|
186
66
|
def _deserialize_json(serial: iris.cls) -> Any:
|
|
@@ -198,14 +78,28 @@ class MessageSerializer:
|
|
|
198
78
|
if serial.type == 'Stream' else serial.json)
|
|
199
79
|
|
|
200
80
|
try:
|
|
201
|
-
json_dict = json.loads(json_string, cls=IrisJSONDecoder)
|
|
202
81
|
if issubclass(msg_class, BaseModel):
|
|
203
|
-
return msg_class.
|
|
82
|
+
return msg_class.model_validate_json(json_string)
|
|
83
|
+
elif is_dataclass(msg_class):
|
|
84
|
+
return dataclass_from_dict(msg_class, json.loads(json_string))
|
|
204
85
|
else:
|
|
205
|
-
|
|
86
|
+
raise SerializationError(f"Class {msg_class} must be a Pydantic model or dataclass")
|
|
206
87
|
except Exception as e:
|
|
207
88
|
raise SerializationError(f"Failed to deserialize JSON: {str(e)}")
|
|
208
89
|
|
|
90
|
+
@staticmethod
|
|
91
|
+
def _serialize_pickle(message: Any) -> iris.cls:
|
|
92
|
+
pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
|
|
93
|
+
msg = iris.cls('IOP.PickleMessage')._New()
|
|
94
|
+
msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
|
|
95
|
+
msg.jstr = _Utils.string_to_stream(pickle_string)
|
|
96
|
+
return msg
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _deserialize_pickle(serial: iris.cls) -> Any:
|
|
100
|
+
string = _Utils.stream_to_string(serial.jstr)
|
|
101
|
+
return pickle.loads(codecs.decode(string.encode(), "base64"))
|
|
102
|
+
|
|
209
103
|
@staticmethod
|
|
210
104
|
def _parse_classname(classname: str) -> tuple[str, str]:
|
|
211
105
|
j = classname.rindex(".")
|
|
@@ -214,18 +108,49 @@ class MessageSerializer:
|
|
|
214
108
|
return classname[:j], classname[j+1:]
|
|
215
109
|
|
|
216
110
|
def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
111
|
+
field_types = {
|
|
112
|
+
key: val.annotation
|
|
113
|
+
for key, val in inspect.signature(klass).parameters.items()
|
|
114
|
+
}
|
|
115
|
+
processed_dict = {}
|
|
116
|
+
for key, val in inspect.signature(klass).parameters.items():
|
|
117
|
+
if key not in dikt and val.default != val.empty:
|
|
118
|
+
processed_dict[key] = val.default
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
value = dikt.get(key)
|
|
122
|
+
if value is None:
|
|
123
|
+
processed_dict[key] = None
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
field_type = field_types[key]
|
|
128
|
+
if field_type != inspect.Parameter.empty:
|
|
129
|
+
adapter = TypeAdapter(field_type)
|
|
130
|
+
processed_dict[key] = adapter.validate_python(value)
|
|
131
|
+
else:
|
|
132
|
+
processed_dict[key] = value
|
|
133
|
+
except Exception:
|
|
134
|
+
processed_dict[key] = value
|
|
135
|
+
|
|
136
|
+
instance = klass(
|
|
137
|
+
**processed_dict
|
|
138
|
+
)
|
|
139
|
+
# handle any extra fields
|
|
140
|
+
for k, v in dikt.items():
|
|
141
|
+
if k not in processed_dict:
|
|
142
|
+
setattr(instance, k, v)
|
|
143
|
+
return instance
|
|
144
|
+
|
|
145
|
+
def dataclass_to_dict(instance: Any) -> Dict:
|
|
146
|
+
"""Converts a class instance to a dictionary.
|
|
147
|
+
Handles non attended fields."""
|
|
148
|
+
dikt = asdict(instance)
|
|
149
|
+
# assign any extra fields
|
|
150
|
+
for k, v in vars(instance).items():
|
|
151
|
+
if k not in dikt:
|
|
152
|
+
dikt[k] = v
|
|
153
|
+
return dikt
|
|
229
154
|
|
|
230
155
|
# Maintain backwards compatibility
|
|
231
156
|
serialize_pickle_message = lambda msg: MessageSerializer.serialize(msg, use_pickle=True)
|
iop/_utils.py
CHANGED
|
@@ -8,7 +8,7 @@ import pkg_resources
|
|
|
8
8
|
import importlib
|
|
9
9
|
import json
|
|
10
10
|
from iop._message import _Message, _PydanticMessage
|
|
11
|
-
from
|
|
11
|
+
from pydantic import TypeAdapter
|
|
12
12
|
|
|
13
13
|
class _Utils():
|
|
14
14
|
@staticmethod
|
|
@@ -46,7 +46,8 @@ class _Utils():
|
|
|
46
46
|
if issubclass(cls,_PydanticMessage):
|
|
47
47
|
schema = cls.model_json_schema()
|
|
48
48
|
elif issubclass(cls,_Message):
|
|
49
|
-
|
|
49
|
+
type_adapter = TypeAdapter(cls)
|
|
50
|
+
schema = type_adapter.json_schema()
|
|
50
51
|
else:
|
|
51
52
|
raise ValueError("The class must be a subclass of _Message or _PydanticMessage")
|
|
52
53
|
schema_name = cls.__module__ + '.' + cls.__name__
|
{iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: iris_pex_embedded_python
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.0b5
|
|
4
4
|
Summary: Iris Interoperability based on Embedded Python
|
|
5
5
|
Author-email: grongier <guillaume.rongier@intersystems.com>
|
|
6
6
|
License: MIT License
|
|
@@ -44,12 +44,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
44
44
|
Classifier: Topic :: Utilities
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
46
|
License-File: LICENSE
|
|
47
|
-
Requires-Dist: dacite>=1.6.0
|
|
48
47
|
Requires-Dist: pydantic>=2.0.0
|
|
49
48
|
Requires-Dist: xmltodict>=0.12.0
|
|
50
49
|
Requires-Dist: iris-embedded-python-wrapper>=0.0.6
|
|
51
50
|
Requires-Dist: setuptools>=40.8.0
|
|
52
|
-
Requires-Dist: dc-schema>=0.0.8
|
|
53
51
|
Requires-Dist: jsonpath-ng>=1.7.0
|
|
54
52
|
|
|
55
53
|
# IoP (Interoperability On Python)
|
{iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/RECORD
RENAMED
|
@@ -109,8 +109,8 @@ iop/_message_validator.py,sha256=K6FxLe72gBHQXZTLVrFw87rABGM0F6CTaNtZ2MqJoss,140
|
|
|
109
109
|
iop/_outbound_adapter.py,sha256=YTAhLrRf9chEAd53mV6KKbpaHOKNOKJHoGgj5wakRR0,726
|
|
110
110
|
iop/_private_session_duplex.py,sha256=mzlFABh-ly51X1uSWw9YwQbktfMvuNdp2ALlRvlDow4,5152
|
|
111
111
|
iop/_private_session_process.py,sha256=todprfYFSDr-h-BMvWL_IGC6wbQqkMy3mPHWEWCUSE0,1686
|
|
112
|
-
iop/_serialization.py,sha256=
|
|
113
|
-
iop/_utils.py,sha256=
|
|
112
|
+
iop/_serialization.py,sha256=Uf6PzvOGuzzr3_29KkFpvU_NooVx6unkU6EqHMx7r3A,5900
|
|
113
|
+
iop/_utils.py,sha256=CUqxhjaOdAG8IuPnk0j7L__qlj0JhR5Ajhf3Lm5omzA,19921
|
|
114
114
|
iop/cls/IOP/BusinessOperation.cls,sha256=lrymqZ8wHl5kJjXwdjbQVs5sScV__yIWGh-oGbiB_X0,914
|
|
115
115
|
iop/cls/IOP/BusinessProcess.cls,sha256=s3t38w1ykHqM26ETcbCYLt0ocjZyVVahm-_USZkuJ1E,2855
|
|
116
116
|
iop/cls/IOP/BusinessService.cls,sha256=7ebn32J9PiZXUgXuh5Xxm_7X6zHBiqkJr9c_dWxbPO8,1021
|
|
@@ -135,9 +135,9 @@ iop/cls/IOP/Service/WSGI.cls,sha256=VLNCXEwmHW9dBnE51uGE1nvGX6T4HjhqePT3LVhsjAE,
|
|
|
135
135
|
iop/wsgi/handlers.py,sha256=NrFLo_YbAh-x_PlWhAiWkQnUUN2Ss9HoEm63dDWCBpQ,2947
|
|
136
136
|
irisnative/_IRISNative.py,sha256=HQ4nBhc8t8_5OtxdMG-kx1aa-T1znf2I8obZOPLOPzg,665
|
|
137
137
|
irisnative/__init__.py,sha256=6YmvBLQSURsCPKaNg7LK-xpo4ipDjrlhKuwdfdNb3Kg,341
|
|
138
|
-
iris_pex_embedded_python-3.4.
|
|
139
|
-
iris_pex_embedded_python-3.4.
|
|
140
|
-
iris_pex_embedded_python-3.4.
|
|
141
|
-
iris_pex_embedded_python-3.4.
|
|
142
|
-
iris_pex_embedded_python-3.4.
|
|
143
|
-
iris_pex_embedded_python-3.4.
|
|
138
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
|
|
139
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/METADATA,sha256=F_CE19w_JUMNyQJkm_AJQQ5yElTMRZwL7ge38hA6Dyk,4397
|
|
140
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
141
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/entry_points.txt,sha256=pj-i4LSDyiSP6xpHlVjMCbg1Pik7dC3_sdGY3Yp9Vhk,38
|
|
142
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/top_level.txt,sha256=VWDlX4YF4qFVRGrG3-Gs0kgREol02i8gIpsHNbhfFPw,42
|
|
143
|
+
iris_pex_embedded_python-3.4.0b5.dist-info/RECORD,,
|
{iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/LICENSE
RENAMED
|
File without changes
|
{iris_pex_embedded_python-3.4.0b4.dist-info → iris_pex_embedded_python-3.4.0b5.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|