iris-pex-embedded-python 3.3.1__py3-none-any.whl → 3.4.0__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.

grongier/pex/__init__.py CHANGED
@@ -6,7 +6,7 @@ from iop._business_operation import _BusinessOperation
6
6
  from iop._inbound_adapter import _InboundAdapter
7
7
  from iop._outbound_adapter import _OutboundAdapter
8
8
  from iop._message import _Message
9
- from iop._pickle_message import _PickleMessage
9
+ from iop._message import _PickleMessage
10
10
  from iop._director import _Director
11
11
  from iop._utils import _Utils
12
12
 
iop/__init__.py CHANGED
@@ -3,9 +3,8 @@ from iop._business_process import _BusinessProcess
3
3
  from iop._business_service import _BusinessService
4
4
  from iop._director import _Director
5
5
  from iop._inbound_adapter import _InboundAdapter
6
- from iop._message import _Message
6
+ from iop._message import _Message, _PickleMessage, _PydanticMessage, _PydanticPickleMessage
7
7
  from iop._outbound_adapter import _OutboundAdapter
8
- from iop._pickle_message import _PickleMessage
9
8
  from iop._private_session_duplex import _PrivateSessionDuplex
10
9
  from iop._private_session_process import _PrivateSessionProcess
11
10
  from iop._utils import _Utils
@@ -21,4 +20,6 @@ class DuplexOperation(_PrivateSessionDuplex): pass
21
20
  class DuplexProcess(_PrivateSessionProcess): pass
22
21
  class Message(_Message): pass
23
22
  class PickleMessage(_PickleMessage): pass
23
+ class PydanticMessage(_PydanticMessage): pass
24
+ class PydanticPickleMessage(_PydanticPickleMessage): pass
24
25
  class Director(_Director): pass
iop/_common.py CHANGED
@@ -17,7 +17,27 @@ class _Common(metaclass=abc.ABCMeta):
17
17
  INFO_URL: ClassVar[str]
18
18
  ICON_URL: ClassVar[str]
19
19
  iris_handle: Any = None
20
- log_to_console: bool = False
20
+ _log_to_console: bool = False
21
+ _logger: logging.Logger = None
22
+
23
+ @property
24
+ def logger(self) -> logging.Logger:
25
+ if self._logger is None:
26
+ self._logger = LogManager.get_logger(self.__class__.__name__,self.log_to_console)
27
+ return self._logger
28
+
29
+ @logger.setter
30
+ def logger(self, value: logging.Logger) -> None:
31
+ self._logger = value
32
+
33
+ @property
34
+ def log_to_console(self) -> bool:
35
+ return self._log_to_console
36
+
37
+ @log_to_console.setter
38
+ def log_to_console(self, value: bool) -> None:
39
+ self._log_to_console = value
40
+ self.logger = LogManager.get_logger(self.__class__.__name__,value)
21
41
 
22
42
  # Lifecycle methods
23
43
  def on_init(self) -> None:
@@ -128,7 +148,10 @@ class _Common(metaclass=abc.ABCMeta):
128
148
  if not member[0].startswith('_'):
129
149
  # remove other methods and functions
130
150
  if not inspect.ismethod(member[1]) and not inspect.isfunction(member[1]) and not inspect.isclass(member[1]):
131
- if member[0] not in ('INFO_URL','ICON_URL','PERSISTENT_PROPERTY_LIST') :
151
+ if member[0] not in ('INFO_URL','ICON_URL','PERSISTENT_PROPERTY_LIST'
152
+ ,'log_to_console','logger','iris_handle'
153
+ ,'DISPATCH','adapter','Adapter','buffer'
154
+ ,'BusinessHost','business_host','business_host_python'):
132
155
  name = member[0]
133
156
  req = 0
134
157
  cat = "Additional"
@@ -172,7 +195,7 @@ class _Common(metaclass=abc.ABCMeta):
172
195
  except:
173
196
  pass
174
197
  return ret
175
-
198
+
176
199
  # Logging methods
177
200
  def _log(self) -> Tuple[str, Optional[str]]:
178
201
  """Get class and method name for logging.
@@ -189,15 +212,27 @@ class _Common(metaclass=abc.ABCMeta):
189
212
  pass
190
213
  return current_class, current_method
191
214
 
192
- @property
193
- def logger(self) -> logging.Logger:
194
- """Get a logger instance for this component.
215
+ def _logging(self, message: str, level: int, to_console: Optional[bool] = None) -> None:
216
+ """Write log entry.
195
217
 
196
- Returns:
197
- Logger configured for IRIS integration
218
+ Args:
219
+ message: Message to log
220
+ level: Log level
221
+ to_console: If True, log to console instead of IRIS
198
222
  """
199
- class_name, method_name = self._log()
200
- return LogManager.get_logger(class_name, method_name, self.log_to_console)
223
+ current_class, current_method = self._log()
224
+ if to_console is None:
225
+ to_console = self.log_to_console
226
+ if level == logging.DEBUG:
227
+ self.logger.debug(message, extra={'to_console': to_console, 'class_name': current_class, 'method_name': current_method})
228
+ elif level == logging.INFO:
229
+ self.logger.info(message, extra={'to_console': to_console, 'class_name': current_class, 'method_name': current_method})
230
+ elif level == logging.CRITICAL:
231
+ self.logger.critical(message, extra={'to_console': to_console, 'class_name': current_class, 'method_name': current_method})
232
+ elif level == logging.WARNING:
233
+ self.logger.warning(message, extra={'to_console': to_console, 'class_name': current_class, 'method_name': current_method})
234
+ elif level == logging.ERROR:
235
+ self.logger.error(message, extra={'to_console': to_console, 'class_name': current_class, 'method_name': current_method})
201
236
 
202
237
  def trace(self, message: str, to_console: Optional[bool] = None) -> None:
203
238
  """Write trace log entry.
@@ -206,8 +241,7 @@ class _Common(metaclass=abc.ABCMeta):
206
241
  message: Message to log
207
242
  to_console: If True, log to console instead of IRIS
208
243
  """
209
- self.logger.debug(message, extra={'to_console': to_console})
210
-
244
+ self._logging(message, logging.DEBUG, to_console)
211
245
 
212
246
  def log_info(self, message: str, to_console: Optional[bool] = None) -> None:
213
247
  """Write info log entry.
@@ -216,7 +250,7 @@ class _Common(metaclass=abc.ABCMeta):
216
250
  message: Message to log
217
251
  to_console: If True, log to console instead of IRIS
218
252
  """
219
- self.logger.info(message, extra={'to_console': to_console})
253
+ self._logging(message, logging.INFO, to_console)
220
254
 
221
255
  def log_alert(self, message: str, to_console: Optional[bool] = None) -> None:
222
256
  """Write alert log entry.
@@ -225,7 +259,7 @@ class _Common(metaclass=abc.ABCMeta):
225
259
  message: Message to log
226
260
  to_console: If True, log to console instead of IRIS
227
261
  """
228
- self.logger.critical(message, extra={'to_console': to_console})
262
+ self._logging(message, logging.CRITICAL, to_console)
229
263
 
230
264
  def log_warning(self, message: str, to_console: Optional[bool] = None) -> None:
231
265
  """Write warning log entry.
@@ -234,7 +268,7 @@ class _Common(metaclass=abc.ABCMeta):
234
268
  message: Message to log
235
269
  to_console: If True, log to console instead of IRIS
236
270
  """
237
- self.logger.warning(message, extra={'to_console': to_console})
271
+ self._logging(message, logging.WARNING, to_console)
238
272
 
239
273
  def log_error(self, message: str, to_console: Optional[bool] = None) -> None:
240
274
  """Write error log entry.
@@ -243,7 +277,7 @@ class _Common(metaclass=abc.ABCMeta):
243
277
  message: Message to log
244
278
  to_console: If True, log to console instead of IRIS
245
279
  """
246
- self.logger.error(message, extra={'to_console': to_console})
280
+ self._logging(message, logging.ERROR, to_console)
247
281
 
248
282
  def log_assert(self, message: str) -> None:
249
283
  """Write a log entry of type "assert". Log entries can be viewed in the management portal.
iop/_director.py CHANGED
@@ -6,7 +6,6 @@ import intersystems_iris.dbapi._DBAPI as irisdbapi
6
6
  import signal
7
7
  from dataclasses import dataclass
8
8
 
9
- from iop._business_host import _BusinessHost
10
9
  from iop._dispatch import dispatch_deserializer, dispatch_serializer
11
10
  from iop._utils import _Utils
12
11
 
@@ -278,7 +277,6 @@ class _Director():
278
277
  else:
279
278
  message.json = _Utils.string_to_stream("{}")
280
279
  # serialize the message
281
- business_host = _BusinessHost()
282
280
  serial_message = dispatch_serializer(message)
283
281
  response = iris.cls('IOP.Utils').dispatchTestComponent(target,serial_message)
284
282
  try:
iop/_dispatch.py CHANGED
@@ -1,38 +1,9 @@
1
- import codecs
2
- import importlib
3
1
  from inspect import signature
4
- import json
5
- import pickle
6
- from typing import Any, Dict, List, Type
2
+ from typing import Any
7
3
 
8
- import iris
9
- from dacite import Config, from_dict
10
-
11
- from iop._utils import _Utils
12
- from iop._serialization import IrisJSONEncoder, IrisJSONDecoder
4
+ from iop._serialization import serialize_message, serialize_pickle_message, deserialize_message, deserialize_pickle_message
13
5
  from iop._message_validator import is_message_instance, is_pickle_message_instance, is_iris_object_instance
14
6
 
15
- def serialize_pickle_message(message: Any) -> iris.cls:
16
- """Converts a python dataclass message into an iris iop.message.
17
-
18
- Args:
19
- message: The message to serialize, an instance of a class that is a subclass of Message.
20
-
21
- Returns:
22
- The message in json format.
23
- """
24
- pickle_string = codecs.encode(pickle.dumps(message), "base64").decode()
25
- module = message.__class__.__module__
26
- classname = message.__class__.__name__
27
-
28
- msg = iris.cls('IOP.PickleMessage')._New()
29
- msg.classname = module + "." + classname
30
-
31
- stream = _Utils.string_to_stream(pickle_string)
32
- msg.jstr = stream
33
-
34
- return msg
35
-
36
7
  def dispatch_serializer(message: Any) -> Any:
37
8
  """Serializes the message based on its type.
38
9
 
@@ -58,42 +29,6 @@ def dispatch_serializer(message: Any) -> Any:
58
29
 
59
30
  raise TypeError("The message must be an instance of a class that is a subclass of Message or IRISObject %Persistent class.")
60
31
 
61
- def serialize_message(message: Any) -> iris.cls:
62
- """Converts a python dataclass message into an iris iop.message.
63
-
64
- Args:
65
- message: The message to serialize, an instance of a class that is a subclass of Message.
66
-
67
- Returns:
68
- The message in json format.
69
- """
70
- json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
71
- module = message.__class__.__module__
72
- classname = message.__class__.__name__
73
-
74
- msg = iris.cls('IOP.Message')._New()
75
- msg.classname = module + "." + classname
76
-
77
- if hasattr(msg, 'buffer') and len(json_string) > msg.buffer:
78
- msg.json = _Utils.string_to_stream(json_string, msg.buffer)
79
- else:
80
- msg.json = json_string
81
-
82
- return msg
83
-
84
- def deserialize_pickle_message(serial: iris.cls) -> Any:
85
- """Converts an iris iop.message into a python dataclass message.
86
-
87
- Args:
88
- serial: The serialized message
89
-
90
- Returns:
91
- The deserialized message
92
- """
93
- string = _Utils.stream_to_string(serial.jstr)
94
- msg = pickle.loads(codecs.decode(string.encode(), "base64"))
95
- return msg
96
-
97
32
  def dispatch_deserializer(serial: Any) -> Any:
98
33
  """Deserializes the message based on its type.
99
34
 
@@ -124,59 +59,6 @@ def dispatch_deserializer(serial: Any) -> Any:
124
59
  else:
125
60
  return serial
126
61
 
127
- def deserialize_message(serial: iris.cls) -> Any:
128
- """Converts an iris iop.message into a python dataclass message.
129
-
130
- Args:
131
- serial: The serialized message
132
-
133
- Returns:
134
- The deserialized message
135
- """
136
- if (serial.classname is None):
137
- raise ValueError("JSON message malformed, must include classname")
138
- classname = serial.classname
139
-
140
- j = classname.rindex(".")
141
- if (j <= 0):
142
- raise ValueError("Classname must include a module: " + classname)
143
- try:
144
- module = importlib.import_module(classname[:j])
145
- msg = getattr(module, classname[j+1:])
146
- except Exception:
147
- raise ImportError("Class not found: " + classname)
148
-
149
- string = ""
150
- if (serial.type == 'Stream'):
151
- string = _Utils.stream_to_string(serial.json)
152
- else:
153
- string = serial.json
154
-
155
- jdict = json.loads(string, cls=IrisJSONDecoder)
156
- return dataclass_from_dict(msg, jdict)
157
-
158
- def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
159
- """Converts a dictionary to a dataclass instance.
160
-
161
- Args:
162
- klass: The dataclass to convert to
163
- dikt: The dictionary to convert to a dataclass
164
-
165
- Returns:
166
- A dataclass object with the fields of the dataclass and the fields of the dictionary.
167
- """
168
- ret = from_dict(klass, dikt, Config(check_types=False))
169
-
170
- try:
171
- fieldtypes = klass.__annotations__
172
- except Exception as e:
173
- fieldtypes = []
174
-
175
- for key, val in dikt.items():
176
- if key not in fieldtypes:
177
- setattr(ret, key, val)
178
- return ret
179
-
180
62
  def dispach_message(host, request: Any) -> Any:
181
63
  """Dispatches the message to the appropriate method.
182
64
 
iop/_log_manager.py CHANGED
@@ -1,3 +1,4 @@
1
+ import traceback
1
2
  import iris
2
3
  import logging
3
4
  from typing import Optional, Tuple
@@ -6,7 +7,7 @@ class LogManager:
6
7
  """Manages logging integration between Python's logging module and IRIS."""
7
8
 
8
9
  @staticmethod
9
- def get_logger(class_name: str, method_name: Optional[str] = None, to_console: bool = False) -> logging.Logger:
10
+ def get_logger(class_name: str, to_console: bool = False) -> logging.Logger:
10
11
  """Get a logger instance configured for IRIS integration.
11
12
 
12
13
  Args:
@@ -17,11 +18,11 @@ class LogManager:
17
18
  Returns:
18
19
  Logger instance configured for IRIS integration
19
20
  """
20
- logger = logging.getLogger(f"{class_name}.{method_name}" if method_name else class_name)
21
+ logger = logging.Logger(f"{class_name}")
21
22
 
22
23
  # Only add handler if none exists
23
24
  if not logger.handlers:
24
- handler = IRISLogHandler(class_name, method_name, to_console)
25
+ handler = IRISLogHandler(to_console=to_console)
25
26
  formatter = logging.Formatter('%(message)s')
26
27
  handler.setFormatter(formatter)
27
28
  logger.addHandler(handler)
@@ -33,17 +34,9 @@ class LogManager:
33
34
  class IRISLogHandler(logging.Handler):
34
35
  """Custom logging handler that routes Python logs to IRIS logging system."""
35
36
 
36
- def __init__(self, class_name: str, method_name: Optional[str] = None, to_console: bool = False):
37
- """Initialize the handler with context information.
38
-
39
- Args:
40
- class_name: Name of the class logging the message
41
- method_name: Optional name of the method logging the message
42
- console: If True, log to the console instead of IRIS
43
- """
37
+ def __init__(self, to_console: bool = False):
38
+ """Initialize the IRIS logging handler."""
44
39
  super().__init__()
45
- self.class_name = class_name
46
- self.method_name = method_name
47
40
  self.to_console = to_console
48
41
 
49
42
  # Map Python logging levels to IRIS logging methods
@@ -80,10 +73,11 @@ class IRISLogHandler(logging.Handler):
80
73
  Args:
81
74
  record: The logging record to emit
82
75
  """
83
-
84
- log_func = self.level_map.get(record.levelno, iris.cls("Ens.Util.Log").LogInfo)
76
+ class_name = record.class_name if hasattr(record, "class_name") else record.name
77
+ method_name = record.method_name if hasattr(record, "method_name") else record.funcName
85
78
  if self.to_console or (hasattr(record, "to_console") and record.to_console):
86
79
  iris.cls("%SYS.System").WriteToConsoleLog(self.format(record),
87
- 0,self.level_map_console.get(record.levelno, 0),record.name)
80
+ 0,self.level_map_console.get(record.levelno, 0),class_name+"."+method_name)
88
81
  else:
89
- log_func(self.class_name, self.method_name, self.format(record))
82
+ log_func = self.level_map.get(record.levelno, iris.cls("Ens.Util.Log").LogInfo)
83
+ log_func(class_name, method_name, self.format(record))
iop/_message.py CHANGED
@@ -1,6 +1,28 @@
1
+ from typing import Any
2
+ from pydantic import BaseModel
3
+
1
4
  class _Message:
2
5
  """ The abstract class that is the superclass for persistent messages sent from one component to another.
3
6
  This class has no properties or methods. Users subclass Message and add properties.
4
7
  The IOP framework provides the persistence to objects derived from the Message class.
5
8
  """
6
- pass
9
+ pass
10
+
11
+ class _PickleMessage:
12
+ """ The abstract class that is the superclass for persistent messages sent from one component to another.
13
+ This class has no properties or methods. Users subclass Message and add properties.
14
+ The IOP framework provides the persistence to objects derived from the Message class.
15
+ """
16
+ pass
17
+
18
+ class _PydanticMessage(BaseModel):
19
+ """Base class for Pydantic-based messages that can be serialized to IRIS."""
20
+
21
+ def __init__(self, **data: Any):
22
+ super().__init__(**data)
23
+
24
+ class _PydanticPickleMessage(BaseModel):
25
+ """Base class for Pydantic-based messages that can be serialized to IRIS."""
26
+
27
+ def __init__(self, **data: Any):
28
+ super().__init__(**data)
iop/_message_validator.py CHANGED
@@ -1,9 +1,12 @@
1
1
  import dataclasses
2
2
  from typing import Any, Type
3
+ from iop._message import _Message, _PickleMessage, _PydanticPickleMessage, BaseModel
3
4
 
4
5
 
5
6
  def is_message_instance(obj: Any) -> bool:
6
7
  """Check if object is a valid Message instance."""
8
+ if isinstance(obj, BaseModel):
9
+ return True
7
10
  if is_message_class(type(obj)):
8
11
  if not dataclasses.is_dataclass(obj):
9
12
  raise TypeError(f"{type(obj).__module__}.{type(obj).__qualname__} must be a dataclass")
@@ -13,6 +16,8 @@ def is_message_instance(obj: Any) -> bool:
13
16
 
14
17
  def is_pickle_message_instance(obj: Any) -> bool:
15
18
  """Check if object is a PickleMessage instance."""
19
+ if isinstance(obj, _PydanticPickleMessage):
20
+ return True
16
21
  if is_pickle_message_class(type(obj)):
17
22
  return True
18
23
  return False
@@ -27,15 +32,16 @@ def is_iris_object_instance(obj: Any) -> bool:
27
32
 
28
33
  def is_message_class(klass: Type) -> bool:
29
34
  """Check if class is a Message type."""
30
- name = f"{klass.__module__}.{klass.__qualname__}"
31
- if name in ("iop.Message", "grongier.pex.Message"):
35
+ if issubclass(klass, _Message):
32
36
  return True
33
- return any(is_message_class(c) for c in klass.__bases__)
37
+ return False
38
+
34
39
 
35
40
 
36
41
  def is_pickle_message_class(klass: Type) -> bool:
37
42
  """Check if class is a PickleMessage type."""
38
- name = f"{klass.__module__}.{klass.__qualname__}"
39
- if name in ("iop.PickleMessage", "grongier.pex.PickleMessage"):
43
+ if issubclass(klass, _PickleMessage):
44
+ return True
45
+ if issubclass(klass, _PydanticPickleMessage):
40
46
  return True
41
- return any(is_pickle_message_class(c) for c in klass.__bases__)
47
+ return False
iop/_serialization.py CHANGED
@@ -1,161 +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
10
- from abc import ABC, abstractmethod
11
- from dataclasses import is_dataclass
12
- from typing import Any, Dict, Type, Optional
6
+ import json
7
+ from dataclasses import asdict, is_dataclass
8
+ from typing import Any, Dict, Type
13
9
 
14
- from dacite import Config, from_dict
15
10
  import iris
11
+ from pydantic import BaseModel, TypeAdapter, ValidationError
16
12
 
13
+ from iop._message import _PydanticPickleMessage
17
14
  from iop._utils import _Utils
18
15
 
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
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)}")
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
-
90
- class IrisJSONEncoder(json.JSONEncoder):
91
- """JSONEncoder that handles dates, decimals, UUIDs, etc."""
92
-
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)
111
-
112
- class IrisJSONDecoder(json.JSONDecoder):
113
- """JSONDecoder that handles special type annotations."""
114
-
115
- def __init__(self, *args: Any, **kwargs: Any) -> None:
116
- super().__init__(object_hook=self.object_hook, *args, **kwargs)
117
-
118
- def object_hook(self, obj: Dict) -> Dict:
119
- return {
120
- key: self._process_value(value)
121
- for key, value in obj.items()
122
- }
123
-
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
20
+ class TempPydanticModel(BaseModel):
21
+ model_config = {
22
+ 'arbitrary_types_allowed' : True,
23
+ 'extra' : 'allow'
24
+ }
130
25
 
131
26
  class MessageSerializer:
132
27
  """Handles message serialization and deserialization."""
133
28
 
29
+ @staticmethod
30
+ def _convert_to_json_safe(obj: Any) -> Any:
31
+ """Convert objects to JSON-safe format."""
32
+ if isinstance(obj, BaseModel):
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")
38
+
134
39
  @staticmethod
135
40
  def serialize(message: Any, use_pickle: bool = False) -> iris.cls:
136
41
  """Serializes a message to IRIS format."""
137
- if use_pickle:
42
+ if isinstance(message, _PydanticPickleMessage) or use_pickle:
138
43
  return MessageSerializer._serialize_pickle(message)
139
44
  return MessageSerializer._serialize_json(message)
140
45
 
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
46
  @staticmethod
157
47
  def _serialize_json(message: Any) -> iris.cls:
158
- json_string = json.dumps(message, cls=IrisJSONEncoder, ensure_ascii=False)
48
+ json_string = MessageSerializer._convert_to_json_safe(message)
49
+
159
50
  msg = iris.cls('IOP.Message')._New()
160
51
  msg.classname = f"{message.__class__.__module__}.{message.__class__.__name__}"
161
52
 
@@ -166,9 +57,10 @@ class MessageSerializer:
166
57
  return msg
167
58
 
168
59
  @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"))
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)
172
64
 
173
65
  @staticmethod
174
66
  def _deserialize_json(serial: iris.cls) -> Any:
@@ -186,11 +78,28 @@ class MessageSerializer:
186
78
  if serial.type == 'Stream' else serial.json)
187
79
 
188
80
  try:
189
- json_dict = json.loads(json_string, cls=IrisJSONDecoder)
190
- return dataclass_from_dict(msg_class, json_dict)
81
+ if issubclass(msg_class, BaseModel):
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))
85
+ else:
86
+ raise SerializationError(f"Class {msg_class} must be a Pydantic model or dataclass")
191
87
  except Exception as e:
192
88
  raise SerializationError(f"Failed to deserialize JSON: {str(e)}")
193
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
+
194
103
  @staticmethod
195
104
  def _parse_classname(classname: str) -> tuple[str, str]:
196
105
  j = classname.rindex(".")
@@ -199,18 +108,53 @@ class MessageSerializer:
199
108
  return classname[:j], classname[j+1:]
200
109
 
201
110
  def dataclass_from_dict(klass: Type, dikt: Dict) -> Any:
202
- """Converts a dictionary to a dataclass instance."""
203
- ret = from_dict(klass, dikt, Config(check_types=False))
111
+ """Converts a dictionary to a dataclass instance.
112
+ Handles non attended fields and nested dataclasses."""
113
+
114
+ def process_field(value: Any, field_type: Type) -> Any:
115
+ if value is None:
116
+ return None
117
+ if is_dataclass(field_type):
118
+ return dataclass_from_dict(field_type, value)
119
+ if field_type != inspect.Parameter.empty:
120
+ try:
121
+ return TypeAdapter(field_type).validate_python(value)
122
+ except ValidationError:
123
+ return value
124
+ return value
125
+
126
+ # Get field definitions from class signature
127
+ fields = inspect.signature(klass).parameters
128
+ field_dict = {}
129
+
130
+ # Process each field
131
+ for field_name, field_info in fields.items():
132
+ if field_name not in dikt:
133
+ if field_info.default != field_info.empty:
134
+ field_dict[field_name] = field_info.default
135
+ continue
136
+
137
+ field_dict[field_name] = process_field(dikt[field_name], field_info.annotation)
138
+
139
+ # Create instance
140
+ instance = klass(**field_dict)
204
141
 
205
- try:
206
- fieldtypes = klass.__annotations__
207
- except Exception:
208
- fieldtypes = {}
142
+ # Add any extra fields not in the dataclass definition
143
+ for key, value in dikt.items():
144
+ if key not in field_dict:
145
+ setattr(instance, key, value)
209
146
 
210
- for key, val in dikt.items():
211
- if key not in fieldtypes:
212
- setattr(ret, key, val)
213
- return ret
147
+ return instance
148
+
149
+ def dataclass_to_dict(instance: Any) -> Dict:
150
+ """Converts a class instance to a dictionary.
151
+ Handles non attended fields."""
152
+ dikt = asdict(instance)
153
+ # assign any extra fields
154
+ for k, v in vars(instance).items():
155
+ if k not in dikt:
156
+ dikt[k] = v
157
+ return dikt
214
158
 
215
159
  # Maintain backwards compatibility
216
160
  serialize_pickle_message = lambda msg: MessageSerializer.serialize(msg, use_pickle=True)
iop/_utils.py CHANGED
@@ -6,8 +6,10 @@ import inspect
6
6
  import xmltodict
7
7
  import pkg_resources
8
8
  import importlib
9
+ import importlib.util
9
10
  import json
10
- from dc_schema import get_schema
11
+ from iop._message import _Message, _PydanticMessage
12
+ from pydantic import TypeAdapter
11
13
 
12
14
  class _Utils():
13
15
  @staticmethod
@@ -42,7 +44,13 @@ class _Utils():
42
44
 
43
45
  :param cls: The class to register
44
46
  """
45
- schema = get_schema(cls)
47
+ if issubclass(cls,_PydanticMessage):
48
+ schema = cls.model_json_schema()
49
+ elif issubclass(cls,_Message):
50
+ type_adapter = TypeAdapter(cls)
51
+ schema = type_adapter.json_schema()
52
+ else:
53
+ raise ValueError("The class must be a subclass of _Message or _PydanticMessage")
46
54
  schema_name = cls.__module__ + '.' + cls.__name__
47
55
  schema_str = json.dumps(schema)
48
56
  categories = schema_name
@@ -234,7 +242,7 @@ class _Utils():
234
242
  settings = _Utils.import_module_from_path('settings',filename)
235
243
  else:
236
244
  # import settings from the settings module
237
- import settings
245
+ import settings # type: ignore
238
246
  # get the path of the settings file
239
247
  path = os.path.dirname(inspect.getfile(settings))
240
248
  try:
@@ -254,7 +262,7 @@ class _Utils():
254
262
  except AttributeError:
255
263
  print("No schemas to register")
256
264
  try:
257
- sys.path.remove(path)
265
+ sys.path.remove(os.path.normpath(path))
258
266
  except ValueError:
259
267
  pass
260
268
 
iop/cls/IOP/Common.cls CHANGED
@@ -37,11 +37,17 @@ Method GetModule() As %String
37
37
  Return ..%module
38
38
  }
39
39
 
40
+ Method %OnNew(pConfigName As %String) As %Status
41
+ {
42
+ $$$ThrowOnError(..Connect())
43
+ Quit $method($this,"initConfig",.pConfigName) ; call subclass
44
+ }
45
+
40
46
  Method OnInit() As %Status
41
47
  {
42
48
  set tSC = $$$OK
43
49
  try {
44
- $$$ThrowOnError(..Connect())
50
+
45
51
  do ..%class."_dispatch_on_init"($this)
46
52
  } catch ex {
47
53
  set tSC = ex.AsStatus()
@@ -199,7 +205,6 @@ ClassMethod OnGetConnections(
199
205
  Catch ex {
200
206
  }
201
207
 
202
-
203
208
  // Get settings
204
209
  do pItem.GetModifiedSetting("%classpaths", .tClasspaths)
205
210
  do pItem.GetModifiedSetting("%classname", .tClassname)
iop/cls/IOP/Utils.cls CHANGED
@@ -275,6 +275,54 @@ ClassMethod GenerateProxyClass(
275
275
  set:($Case(tSuperClass,"IOP.InboundAdapter":1,"IOP.OutboundAdapter":1,:0)) type = "Adapter"
276
276
  set tSETTINGSParamValue = $REPLACE(tSETTINGSParamValue,"$type",type)
277
277
 
278
+ #dim tCustomProp As %Dictionary.PropertyDefinition
279
+ #dim tPropInfo,tPropName,tDataType,tDefault,tDesc,tPropCat,tContext As %String
280
+
281
+ set builtins = ##class(%SYS.Python).Import("builtins")
282
+ #; each remote setting is of form $lb(propName,dataType,defaultVal,required,category,description,editorContext)
283
+ For i=0:1:builtins.len(pRemoteSettings)-1 {
284
+ Set tPropInfo = pRemoteSettings."__getitem__"(i)
285
+ Continue:""=tPropInfo ; this shouldn't happen, but just in case
286
+ Set tPropName = tPropInfo."__getitem__"(0)
287
+ Set tDataType = tPropInfo."__getitem__"(1)
288
+ If (""=tPropName) || (""=tDataType) {
289
+ Set tSC = $$$ERROR($$$EnsErrGeneral,"All properties must have a name and datatype defined")
290
+ Quit
291
+ }
292
+ Set tCustomProp = ##class(%Dictionary.PropertyDefinition).%New()
293
+ Set tCustomProp.Name = tPropName
294
+ If $Case(tDataType,"String":1,"Integer":1,"Boolean":1,"Numeric":1,:0) {
295
+ Set tDataType = "%"_tDataType
296
+ } ElseIf '##class(%Dictionary.ClassDefinition).%ExistsId(tDataType) {
297
+ Set tDataType = "%String"
298
+ }
299
+ Set tCustomProp.Type = tDataType
300
+ If tDataType["%String" {
301
+ Set tSC = tCustomProp.Parameters.SetAt("255","MAXLEN")
302
+ Quit:$$$ISERR(tSC)
303
+ }
304
+ Set tDefault = tPropInfo."__getitem__"(2)
305
+ If ""'=tDefault {
306
+ Set tCustomProp.InitialExpression = $$$quote(tDefault)
307
+ }
308
+ Set tCustomProp.Required = tPropInfo."__getitem__"(3)
309
+
310
+ Set tSC = tCOSClass.Properties.Insert(tCustomProp)
311
+ Quit:$$$ISERR(tSC)
312
+
313
+ #dim tMethod As %Dictionary.MethodDefinition = ##class(%Dictionary.MethodDefinition).%New()
314
+ Set tMethod.Name = tPropName_"Set"
315
+ Set tMethod.ReturnType = "%Status"
316
+ Set tMethod.FormalSpec = "value:"_tDataType
317
+ Do tMethod.Implementation.WriteLine(" Set ..%class."""_tPropName_""" = value")
318
+ Do tMethod.Implementation.WriteLine(" Quit $$$OK")
319
+ Set tSC = tCOSClass.Methods.Insert(tMethod)
320
+
321
+ Set tPropCat = "Python Attributes"
322
+ Set tSETTINGSParamValue = tSETTINGSParamValue_","_tPropName_":"_tPropCat
323
+ }
324
+ Quit:$$$ISERR(tSC)
325
+
278
326
  #dim tSETTINGSParam As %Dictionary.ParameterDefinition = ##class(%Dictionary.ParameterDefinition).%New()
279
327
  Set tSETTINGSParam.Name = "SETTINGS"
280
328
  Set tSETTINGSParam.Default = tSETTINGSParamValue
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: iris_pex_embedded_python
3
- Version: 3.3.1
3
+ Version: 3.4.0
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,12 @@ 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
47
+ Requires-Dist: pydantic>=2.0.0
48
48
  Requires-Dist: xmltodict>=0.12.0
49
49
  Requires-Dist: iris-embedded-python-wrapper>=0.0.6
50
50
  Requires-Dist: setuptools>=40.8.0
51
- Requires-Dist: dc-schema>=0.0.8
52
51
  Requires-Dist: jsonpath-ng>=1.7.0
52
+ Dynamic: license-file
53
53
 
54
54
  # IoP (Interoperability On Python)
55
55
 
@@ -19,7 +19,7 @@ grongier/cls/Grongier/PEX/PrivateSession/Message/Poll.cls,sha256=pcUgHgxX1pMH-wQ
19
19
  grongier/cls/Grongier/PEX/PrivateSession/Message/Start.cls,sha256=T3jNoR8RjKr1InQ6SgqBYTgFwpSB0Q60WholjbvForg,433
20
20
  grongier/cls/Grongier/PEX/PrivateSession/Message/Stop.cls,sha256=zy30ZXXN4XcovPij-kOF3PuH1SkP1EUvlEJQRx2S9RU,431
21
21
  grongier/cls/Grongier/Service/WSGI.cls,sha256=7u2SsFmnsubMfdazvaDchKCM3yesPRMfKBzMIkwQ9xc,77
22
- grongier/pex/__init__.py,sha256=zHFF0Znipx1fwHYUEBZjNOYoZH1ro7z4IgYDU32kdn0,1067
22
+ grongier/pex/__init__.py,sha256=CPLDFa4dusvGX9VZYTUk-M0Xa_yR4e4Gqku1rIT75qo,1060
23
23
  grongier/pex/__main__.py,sha256=pQzVtkDhAeI6dpNRC632dVk2SGZZIEDwDufdgZe8VWs,98
24
24
  grongier/pex/_business_host.py,sha256=dlV8CWJad8Pr2TNfD9OjcVKaq5gEYQACZla1FK6-bDM,44
25
25
  grongier/pex/_cli.py,sha256=hOHz3n-aHtULuhdCkqZ_SSb3sv7M6j2WhRxgCTvgR9I,64
@@ -90,7 +90,7 @@ intersystems_iris/pex/_InboundAdapter.py,sha256=gZlWl7afumamkj8pNbpLyKFSzhaTAiAX
90
90
  intersystems_iris/pex/_Message.py,sha256=Ugaa_lsEYke__pI5kdC7phAuyPQ7rxXUcROJL4cUxVQ,320
91
91
  intersystems_iris/pex/_OutboundAdapter.py,sha256=ao2Ubbta2DcrQGdzDUD_j1Zsk8bvUfcZNKTZkzPTNBU,1628
92
92
  intersystems_iris/pex/__init__.py,sha256=l_I1dpnluWawbFrGMDC0GLHpuHwjbpd-nho8otFX6TE,1379
93
- iop/__init__.py,sha256=fX1Osx2Dmcuv8C5hmvoG4fPDIbk0qksJcIgpQibJ_p4,1067
93
+ iop/__init__.py,sha256=1C589HojSVK0yJf1KuTPA39ZjrOYO0QFLv45rqbZpA4,1183
94
94
  iop/__main__.py,sha256=pQzVtkDhAeI6dpNRC632dVk2SGZZIEDwDufdgZe8VWs,98
95
95
  iop/_async_request.py,sha256=btDFNFaO-Yvl6wvgXrCYNWoQb2RtbzEvTJsk7j03b30,2170
96
96
  iop/_business_host.py,sha256=wI2LlVTVjEqE8GWE9nSz1WHvqTduAuJQTk7Ky7qkz4U,9658
@@ -98,31 +98,30 @@ iop/_business_operation.py,sha256=ml4BIn1BfrGx8AUGISFR71DZIUCP8vZ2yn9SQjaSzTM,30
98
98
  iop/_business_process.py,sha256=hj6nDIP5Mz5UYbm0vDjvs9lPSEYVQxGJl6tQRcDGTBk,8548
99
99
  iop/_business_service.py,sha256=lPTp3_tcLTOWvHlE_YwrcImLGf1PJBUgIOrV-8ctFLw,3851
100
100
  iop/_cli.py,sha256=IwvVxglSTVkCDXwANLMqnd8mfxGtWB6GjBSdEy8EMm0,7551
101
- iop/_common.py,sha256=E_Mn2t--klAtT4p1MzW1KtcKcLrB1g4Flq5H4JlV8Vs,11619
101
+ iop/_common.py,sha256=HnrI6l3TOHSkp3TcbcmLXHW_vrnRI80oD2in5_We2e8,13444
102
102
  iop/_decorators.py,sha256=ZpgEETLdKWv58AoSMfXnm3_mA-6qPphIegjG-npDgwg,2324
103
- iop/_director.py,sha256=DrswFoqJ6IG62hkW-0ZffTtZdxw6KNozlZSIq3O6d-o,11629
104
- iop/_dispatch.py,sha256=4317Z0ujXxDGUQaL7WX6zfGzBPOdw70MWiqmLUKxhik,6611
103
+ iop/_director.py,sha256=fmsM9b_i0BHiWX5kjUYO9-k2A5Cz7Kg0435Dh7jwoFI,11542
104
+ iop/_dispatch.py,sha256=I3TAhvTuk8j4VcROI9vAitJ0a7Nk1BYAWKRrLeNsjr0,3203
105
105
  iop/_inbound_adapter.py,sha256=PS5ToqhrYcXq9ZdLbCBqAfVp8kCeRACm_KF66pwBO9U,1652
106
- iop/_log_manager.py,sha256=PZnGWsi-zvWo-bumQO5BK-8kHG2XW-WnZ6ShL7QeZ4c,3499
107
- iop/_message.py,sha256=LSkfZcmEu1aj2hdSg3sUHSWNc8tWWZR8Q2_h9_axVrs,325
108
- iop/_message_validator.py,sha256=xMMPOZOop3Nv12E3_QPI5caTgYgAzoKlRxcMmUkrj2c,1379
106
+ iop/_log_manager.py,sha256=f9T8yhcVJlCVNY7E_tb2e65QB_rJWYNF6J586OV2ukA,3240
107
+ iop/_message.py,sha256=pJQOjRIdw4wSDoJamvItGODMe-UjDU71XihgWdtCAqc,1120
108
+ iop/_message_validator.py,sha256=K6FxLe72gBHQXZTLVrFw87rABGM0F6CTaNtZ2MqJoss,1408
109
109
  iop/_outbound_adapter.py,sha256=YTAhLrRf9chEAd53mV6KKbpaHOKNOKJHoGgj5wakRR0,726
110
- iop/_pickle_message.py,sha256=noKfc2VkXufV3fqjKvNHN_oANQ1YN9ffCaSV0XSTAIE,331
111
110
  iop/_private_session_duplex.py,sha256=mzlFABh-ly51X1uSWw9YwQbktfMvuNdp2ALlRvlDow4,5152
112
111
  iop/_private_session_process.py,sha256=todprfYFSDr-h-BMvWL_IGC6wbQqkMy3mPHWEWCUSE0,1686
113
- iop/_serialization.py,sha256=jHJGTMpKI2QDcF_m7R0WyH_J-vjQyYpNpD0ktn9Rqo4,8106
114
- iop/_utils.py,sha256=Aqtp9Jx3ghzkNs4f2cOZXYwv8cGsjmdBocnkP64fa3M,19574
112
+ iop/_serialization.py,sha256=EEhPbRpgODsGEt3RMiThr5g7Ox6lOT28XFQeAQq2eik,6162
113
+ iop/_utils.py,sha256=jj1UZHVXYg5uB08E0ETKfpmXQX-TWYSdPl4BO43k2RY,19976
115
114
  iop/cls/IOP/BusinessOperation.cls,sha256=lrymqZ8wHl5kJjXwdjbQVs5sScV__yIWGh-oGbiB_X0,914
116
115
  iop/cls/IOP/BusinessProcess.cls,sha256=s3t38w1ykHqM26ETcbCYLt0ocjZyVVahm-_USZkuJ1E,2855
117
116
  iop/cls/IOP/BusinessService.cls,sha256=7ebn32J9PiZXUgXuh5Xxm_7X6zHBiqkJr9c_dWxbPO8,1021
118
- iop/cls/IOP/Common.cls,sha256=mJvfTGxJeqLDlBxdnCt-hYiVZEJpfvmdzYEC_UqKeHo,11234
117
+ iop/cls/IOP/Common.cls,sha256=vrS9yo4aUgMiLwfhBFJvj7hG6zYQfcSIiu5o1hdppuU,11355
119
118
  iop/cls/IOP/Director.cls,sha256=M43LoTb6lwSr0J81RFxi1YLW1mwda09wQ7Xqr3nBtxo,2008
120
119
  iop/cls/IOP/InboundAdapter.cls,sha256=GeoCm6q5HcLJ5e4VxgqXiErJXqolBbpKwpunaNzpvjU,610
121
120
  iop/cls/IOP/Message.cls,sha256=ZrYQHosgfTG9wv7i-WQ8j71YXZMmL4_mN16xtIDwcRg,25180
122
121
  iop/cls/IOP/OutboundAdapter.cls,sha256=9eOwy5ojwcTzwrHs6LNrFQvUD8aqcoNCZrILN1ycdDM,958
123
122
  iop/cls/IOP/PickleMessage.cls,sha256=S3y7AClQ8mAILjxPuHdCjGosBZYzGbUQ5WTv4mYPNMQ,1673
124
123
  iop/cls/IOP/Test.cls,sha256=gAC9PEfMZsvAEWIa241-ug2FWAhITbN1SOispZzJPnI,2094
125
- iop/cls/IOP/Utils.cls,sha256=ZTBr02spm4ppxVBfhnUwb08BmhTjG5-ZbItRshYHs1I,13746
124
+ iop/cls/IOP/Utils.cls,sha256=gQBp0lC4F7FWkv1JEDe45qsaLcVlg995t2th46nDGx8,15745
126
125
  iop/cls/IOP/Duplex/Operation.cls,sha256=K_fmgeLjPZQbHgNrc0kd6DUQoW0fDn1VHQjJxHo95Zk,525
127
126
  iop/cls/IOP/Duplex/Process.cls,sha256=xbefZ4z84a_IUhavWN6P_gZBzqkdJ5XRTXxro6iDvAg,6986
128
127
  iop/cls/IOP/Duplex/Service.cls,sha256=sTMOQUCMBgVitmQkM8bbsrmrRtCdj91VlctJ3I7b8WU,161
@@ -134,11 +133,11 @@ iop/cls/IOP/PrivateSession/Message/Start.cls,sha256=uk-WTe66GicCshgmVy3F5ugHiAyP
134
133
  iop/cls/IOP/PrivateSession/Message/Stop.cls,sha256=7g3gKFUjNg0WXBLuWnj-VnCs5G6hSE09YTzGEp0zbGc,1390
135
134
  iop/cls/IOP/Service/WSGI.cls,sha256=VLNCXEwmHW9dBnE51uGE1nvGX6T4HjhqePT3LVhsjAE,10440
136
135
  iop/wsgi/handlers.py,sha256=NrFLo_YbAh-x_PlWhAiWkQnUUN2Ss9HoEm63dDWCBpQ,2947
136
+ iris_pex_embedded_python-3.4.0.dist-info/licenses/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
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.1.dist-info/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
140
- iris_pex_embedded_python-3.3.1.dist-info/METADATA,sha256=dsJ4pvP-mBaxashySn6A9FgZ6pKVLjidQBu_dRC_fZY,4425
141
- iris_pex_embedded_python-3.3.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
142
- iris_pex_embedded_python-3.3.1.dist-info/entry_points.txt,sha256=pj-i4LSDyiSP6xpHlVjMCbg1Pik7dC3_sdGY3Yp9Vhk,38
143
- iris_pex_embedded_python-3.3.1.dist-info/top_level.txt,sha256=VWDlX4YF4qFVRGrG3-Gs0kgREol02i8gIpsHNbhfFPw,42
144
- iris_pex_embedded_python-3.3.1.dist-info/RECORD,,
139
+ iris_pex_embedded_python-3.4.0.dist-info/METADATA,sha256=TFlJSzZBj5MucQV3yMzyEnjkDEff9XXx5nCkTsnooSU,4417
140
+ iris_pex_embedded_python-3.4.0.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
141
+ iris_pex_embedded_python-3.4.0.dist-info/entry_points.txt,sha256=pj-i4LSDyiSP6xpHlVjMCbg1Pik7dC3_sdGY3Yp9Vhk,38
142
+ iris_pex_embedded_python-3.4.0.dist-info/top_level.txt,sha256=VWDlX4YF4qFVRGrG3-Gs0kgREol02i8gIpsHNbhfFPw,42
143
+ iris_pex_embedded_python-3.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (77.0.3)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
iop/_pickle_message.py DELETED
@@ -1,6 +0,0 @@
1
- class _PickleMessage:
2
- """ The abstract class that is the superclass for persistent messages sent from one component to another.
3
- This class has no properties or methods. Users subclass Message and add properties.
4
- The PEX framework provides the persistence to objects derived from the Message class.
5
- """
6
- pass