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 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 json
4
+ import inspect
8
5
  import pickle
9
- import uuid
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
- """Base exception for serialization errors."""
17
+ """Exception raised for serialization errors."""
30
18
  pass
31
19
 
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)}")
20
+ class TempPydanticModel(BaseModel):
21
+ model_config = {
22
+ 'arbitrary_types_allowed' : True,
23
+ 'extra' : 'allow'
24
+ }
73
25
 
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
26
+ class MessageSerializer:
27
+ """Handles message serialization and deserialization."""
82
28
 
83
29
  @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
-
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.model_dump()
96
- if obj.__class__.__name__ == 'DataFrame':
97
- return f'dataframe:{TypeConverter.convert_to_string("dataframe", obj)}'
98
- elif isinstance(obj, datetime.datetime):
99
- return f'datetime:{TypeConverter.convert_to_string("datetime", obj)}'
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
- # Check for PydanticPickleMessage first
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
- if isinstance(message, BaseModel):
167
- json_string = json.dumps(message.model_dump(), cls=IrisJSONEncoder, ensure_ascii=False)
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 _deserialize_pickle(serial: iris.cls) -> Any:
182
- string = _Utils.stream_to_string(serial.jstr)
183
- return pickle.loads(codecs.decode(string.encode(), "base64"))
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.model_validate(json_dict)
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
- return dataclass_from_dict(msg_class, json_dict)
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
- """Converts a dictionary to a dataclass instance."""
218
- ret = from_dict(klass, dikt, Config(check_types=False))
219
-
220
- try:
221
- fieldtypes = klass.__annotations__
222
- except Exception:
223
- fieldtypes = {}
224
-
225
- for key, val in dikt.items():
226
- if key not in fieldtypes:
227
- setattr(ret, key, val)
228
- return ret
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 dc_schema import get_schema
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
- schema = get_schema(cls)
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__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: iris_pex_embedded_python
3
- Version: 3.4.0b4
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)
@@ -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=RIBHjhrkAyfJJC5OXIlrwHcuXvPiDM63PtkVDyVKp1E,8840
113
- iop/_utils.py,sha256=eNVxKGVQbYMoU43sFPkOjZIilN4OqRzycHV9l6rLDcU,19866
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.0b4.dist-info/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
139
- iris_pex_embedded_python-3.4.0b4.dist-info/METADATA,sha256=SFK1Eke3STnTMMddK-npyOBkuEnajg-TJ0zY6McqHSA,4458
140
- iris_pex_embedded_python-3.4.0b4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
141
- iris_pex_embedded_python-3.4.0b4.dist-info/entry_points.txt,sha256=pj-i4LSDyiSP6xpHlVjMCbg1Pik7dC3_sdGY3Yp9Vhk,38
142
- iris_pex_embedded_python-3.4.0b4.dist-info/top_level.txt,sha256=VWDlX4YF4qFVRGrG3-Gs0kgREol02i8gIpsHNbhfFPw,42
143
- iris_pex_embedded_python-3.4.0b4.dist-info/RECORD,,
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,,