iris-pex-embedded-python 4.0.1b3__py3-none-any.whl → 4.0.1b5__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.
iop/messages/dispatch.py CHANGED
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  import logging
2
3
  from collections.abc import Callable
3
4
  from dataclasses import dataclass
@@ -36,6 +37,8 @@ _PICKLE_MESSAGE_CLASSES = {
36
37
  "IOP.Generator.Message.StartPickle",
37
38
  }
38
39
  _HANDLER_ATTRIBUTE = "__iop_handler_message__"
40
+ _IRIS_MODULE_PREFIX = "iris."
41
+ _UNRESOLVED = object()
39
42
 
40
43
 
41
44
  @dataclass(frozen=True)
@@ -60,7 +63,7 @@ def handler(message_type: Any) -> Callable[[Callable], Callable]:
60
63
  message_type: The message class, fully qualified class name string, or
61
64
  IRIS object instance this method handles.
62
65
  """
63
- message = _message_class_name(message_type)
66
+ message = _message_key_from_declared(message_type)
64
67
  if message is None:
65
68
  raise TypeError("handler() requires a message class or class name")
66
69
 
@@ -154,11 +157,10 @@ def dispatch_message(host: Any, request: Any) -> Any:
154
157
  """
155
158
  call = "on_message"
156
159
 
157
- module = request.__class__.__module__
158
- classname = request.__class__.__name__
160
+ message_names = _message_keys_from_request(request)
159
161
 
160
162
  for msg, method in host.DISPATCH:
161
- if msg == module + "." + classname:
163
+ if msg in message_names:
162
164
  return getattr(host, method)(request)
163
165
 
164
166
  return getattr(host, call)(request)
@@ -202,15 +204,24 @@ def create_dispatch(host: Any) -> None:
202
204
 
203
205
  def _declared_dispatch(host: Any) -> list[tuple[str, str]]:
204
206
  if "DISPATCH" in getattr(host, "__dict__", {}):
205
- return list(host.__dict__["DISPATCH"])
207
+ return _normalized_dispatch(host.__dict__["DISPATCH"])
206
208
 
207
209
  class_dispatch = host.__class__.__dict__.get("DISPATCH")
208
210
  if class_dispatch is not None:
209
- return list(class_dispatch)
211
+ return _normalized_dispatch(class_dispatch)
210
212
 
211
213
  return []
212
214
 
213
215
 
216
+ def _normalized_dispatch(dispatch: Any) -> list[tuple[str, str]]:
217
+ entries = []
218
+ for message, method in dispatch:
219
+ key = _message_key_from_declared(message)
220
+ if key is not None:
221
+ entries.append((key, method))
222
+ return entries
223
+
224
+
214
225
  def _decorated_dispatch(host: Any) -> list[tuple[str, str]]:
215
226
  dispatch = []
216
227
  for method_name in dir(host):
@@ -308,12 +319,7 @@ def get_handler_info(host: Any, method_name: str) -> tuple[str, str] | None:
308
319
 
309
320
  method = getattr(host, method_name)
310
321
  param: Parameter = next(iter(params.values()))
311
- annotation = _resolve_annotation(host, method, param.annotation)
312
-
313
- if annotation == Parameter.empty:
314
- return None
315
-
316
- message = _message_class_name(annotation)
322
+ message = _message_key_from_annotation(host, method, param.annotation)
317
323
  if message is None:
318
324
  return None
319
325
 
@@ -323,48 +329,63 @@ def get_handler_info(host: Any, method_name: str) -> tuple[str, str] | None:
323
329
  return None
324
330
 
325
331
 
326
- def _resolve_annotation(host: Any, method: Callable, annotation: Any) -> Any:
332
+ def _message_key_from_annotation(
333
+ host: Any, method: Callable, annotation: Any
334
+ ) -> str | None:
335
+ if annotation == Parameter.empty:
336
+ return None
337
+
327
338
  if not isinstance(annotation, str):
328
- return annotation
339
+ return _message_key_from_declared(annotation, allow_bare_string=False)
329
340
 
330
- globalns = _annotation_globalns(method)
331
- localns = _annotation_localns(host)
341
+ value: Any = annotation
342
+ for _ in range(3):
343
+ if not isinstance(value, str):
344
+ return _message_key_from_declared(value, allow_bare_string=False)
332
345
 
333
- try:
334
- resolved = eval(annotation, globalns, localns) # noqa: B307
335
- except Exception:
336
- resolved = annotation
346
+ key = _message_key_from_string(value, allow_bare=False)
347
+ if key is not None:
348
+ return key
337
349
 
338
- if isinstance(resolved, str):
339
- # Quoted postponed annotations evaluate to a string first.
340
- try:
341
- resolved = eval(resolved, globalns, localns) # noqa: B307
342
- except Exception:
343
- if "." in resolved:
344
- return resolved
345
- return Parameter.empty
350
+ unquoted = _unquote_string(value)
351
+ if unquoted is not None and unquoted != value:
352
+ value = unquoted
353
+ continue
346
354
 
347
- return resolved
355
+ value = _lookup_annotation_name(host, method, value)
356
+ if value is _UNRESOLVED:
357
+ return None
348
358
 
359
+ return None
349
360
 
350
- def _annotation_globalns(method: Callable) -> dict[str, Any]:
351
- function = getattr(method, "__func__", method)
352
- return getattr(function, "__globals__", {})
353
361
 
362
+ def _message_keys_from_request(request: Any) -> tuple[str, ...]:
363
+ module = request.__class__.__module__
364
+ classname = request.__class__.__name__
365
+ names = [f"{module}.{classname}"]
354
366
 
355
- def _annotation_localns(host: Any) -> dict[str, Any]:
356
- namespace: dict[str, Any] = {}
357
- for klass in reversed(type(host).__mro__):
358
- namespace.update(vars(klass))
359
- return namespace
367
+ if module.startswith("iris"):
368
+ native_name = _native_iris_message_key(request)
369
+ if native_name:
370
+ names.append(native_name)
371
+ names.append(f"{_IRIS_MODULE_PREFIX}{native_name}")
372
+
373
+ return tuple(dict.fromkeys(names))
360
374
 
361
375
 
362
- def _message_class_name(message_type: Any) -> str | None:
376
+ def _message_key_from_declared(
377
+ message_type: Any, *, allow_bare_string: bool = True
378
+ ) -> str | None:
363
379
  if isinstance(message_type, str):
364
- return message_type
380
+ return _message_key_from_string(
381
+ message_type,
382
+ allow_bare=allow_bare_string,
383
+ )
365
384
 
366
385
  if is_iris_object_instance(message_type):
367
- return f"{type(message_type).__module__}.{type(message_type).__name__}"
386
+ return _native_iris_message_key(message_type) or _python_class_key(
387
+ type(message_type)
388
+ )
368
389
 
369
390
  if message_type is Any or message_type is object:
370
391
  return None
@@ -375,7 +396,70 @@ def _message_class_name(message_type: Any) -> str | None:
375
396
  if not _is_dispatch_message_class(message_type):
376
397
  return None
377
398
 
378
- return f"{message_type.__module__}.{message_type.__name__}"
399
+ return _python_class_key(message_type)
400
+
401
+
402
+ def _message_key_from_string(value: str, *, allow_bare: bool) -> str | None:
403
+ if not value or _unquote_string(value) is not None:
404
+ return None
405
+
406
+ normalized = _strip_iris_prefix(value)
407
+ if "." in normalized:
408
+ return normalized if _looks_like_message_key(normalized) else None
409
+
410
+ return normalized if allow_bare else None
411
+
412
+
413
+ def _native_iris_message_key(message: Any) -> str | None:
414
+ iris_classname = get_iris_object_classname(message)
415
+ if not iris_classname:
416
+ return None
417
+ return _strip_iris_prefix(iris_classname)
418
+
419
+
420
+ def _python_class_key(klass: type) -> str:
421
+ return f"{klass.__module__}.{klass.__name__}"
422
+
423
+
424
+ def _strip_iris_prefix(value: str) -> str:
425
+ if value.startswith(_IRIS_MODULE_PREFIX):
426
+ return value[len(_IRIS_MODULE_PREFIX) :]
427
+ return value
428
+
429
+
430
+ def _looks_like_message_key(value: str) -> bool:
431
+ return not any(character in value for character in "'\"[](){} ,:")
432
+
433
+
434
+ def _unquote_string(value: str) -> str | None:
435
+ try:
436
+ literal = ast.literal_eval(value)
437
+ except (SyntaxError, ValueError):
438
+ return None
439
+ return literal if isinstance(literal, str) else None
440
+
441
+
442
+ def _lookup_annotation_name(host: Any, method: Callable, name: str) -> Any:
443
+ if not name.isidentifier():
444
+ return _UNRESOLVED
445
+
446
+ localns = _annotation_localns(host)
447
+ if name in localns:
448
+ return localns[name]
449
+
450
+ return _annotation_globalns(method).get(name, _UNRESOLVED)
451
+
452
+
453
+ def _annotation_globalns(method: Callable) -> dict[str, Any]:
454
+ function = getattr(method, "__func__", method)
455
+ return getattr(function, "__globals__", {})
456
+
457
+
458
+ def _annotation_localns(host: Any) -> dict[str, Any]:
459
+ namespace: dict[str, Any] = {}
460
+ for klass in reversed(type(host).__mro__):
461
+ namespace.update(vars(klass))
462
+ return namespace
379
463
 
380
464
 
381
465
  def _is_dispatch_message_class(klass: type) -> bool:
iop/production/model.py CHANGED
@@ -34,6 +34,7 @@ from .types import (
34
34
  PersistentMessageRegistration,
35
35
  ProductionDiff,
36
36
  ProductionGraph,
37
+ TargetSetting,
37
38
  TargetSettingRef,
38
39
  _edge_identity,
39
40
  )
@@ -1055,6 +1056,35 @@ class Production(_DeclarativeProductionMixin):
1055
1056
  )
1056
1057
  self._items.append(ref)
1057
1058
  self._items_by_name[ref.name] = ref
1059
+ self._apply_target_defaults()
1060
+
1061
+ def _apply_target_defaults(self) -> None:
1062
+ for ref in self._items:
1063
+ for setting_name, target_name in self._target_default_routes(ref):
1064
+ if setting_name not in ref.host_settings:
1065
+ if setting_name in ref.target_setting_names:
1066
+ continue
1067
+ ref.host_settings[setting_name] = target_name
1068
+ ref.target_setting_names.add(setting_name)
1069
+ if str(ref.host_settings[setting_name]) != target_name:
1070
+ continue
1071
+ if target_name not in self._items_by_name:
1072
+ continue
1073
+ self._register_connection(
1074
+ ref.name,
1075
+ setting_name,
1076
+ target_name,
1077
+ )
1078
+
1079
+ def _target_default_routes(self, ref: ComponentRef) -> tuple[tuple[str, str], ...]:
1080
+ if ref.component_class is None:
1081
+ return ()
1082
+ routes: list[tuple[str, str]] = []
1083
+ for setting_name, descriptor in _target_settings(ref.component_class):
1084
+ target_name = _target_default_name(descriptor.default)
1085
+ if target_name:
1086
+ routes.append((setting_name, target_name))
1087
+ return tuple(routes)
1058
1088
 
1059
1089
  def _diff_current(
1060
1090
  self,
@@ -1141,3 +1171,21 @@ def _connection_mode(mode: str) -> str:
1141
1171
  "Unsupported connection mode: "
1142
1172
  f"{mode!r}. Expected 'replace', 'add', or 'remove'."
1143
1173
  )
1174
+
1175
+
1176
+ def _target_settings(
1177
+ component_class: type,
1178
+ ) -> tuple[tuple[str, TargetSetting], ...]:
1179
+ descriptors: dict[str, TargetSetting] = {}
1180
+ for base in reversed(component_class.__mro__):
1181
+ for name, value in base.__dict__.items():
1182
+ if isinstance(value, TargetSetting):
1183
+ descriptors[name] = value
1184
+ return tuple(descriptors.items())
1185
+
1186
+
1187
+ def _target_default_name(value: Any) -> str:
1188
+ if value is None:
1189
+ return ""
1190
+ name = getattr(value, "name", value)
1191
+ return str(name).strip()
iop/production/types.py CHANGED
@@ -12,11 +12,11 @@ from ..components.settings import Category, Setting, controls
12
12
  class TargetSetting(Setting):
13
13
  """Production target setting descriptor created by target()."""
14
14
 
15
- def __init__(self, **kwargs: Any):
15
+ def __init__(self, default: Any = "", **kwargs: Any):
16
16
  kwargs.setdefault("iris_type", "Ens.DataType.ConfigName")
17
17
  kwargs.setdefault("category", Category.BASIC)
18
18
  kwargs.setdefault("control", controls.production_item())
19
- super().__init__("", **kwargs)
19
+ super().__init__(default, **kwargs)
20
20
 
21
21
  @overload
22
22
  def __get__(self, instance: None, owner: type | None = None) -> TargetSetting: ...
@@ -34,13 +34,16 @@ class TargetSetting(Setting):
34
34
  return super().__get__(instance, owner)
35
35
 
36
36
 
37
- def target(**kwargs: Any) -> TargetSetting:
37
+ def target(default: Any = "", **kwargs: Any) -> TargetSetting:
38
38
  """Purpose:
39
39
  Declare a configurable outbound target setting on a component class.
40
40
 
41
41
  Use when:
42
42
  A service or process sends messages to another production component.
43
43
 
44
+ Pass a target item name as the first argument when the route should have
45
+ a default target even without an explicit Production.connect(...) call.
46
+
44
47
  Lifecycle:
45
48
  The descriptor becomes an IRIS production setting. Production.connect(...)
46
49
  binds it to a concrete target component.
@@ -59,11 +62,14 @@ def target(**kwargs: Any) -> TargetSetting:
59
62
 
60
63
  prod.connect(router.Output, operation)
61
64
 
65
+ class DefaultRouter(BusinessProcess):
66
+ Output = target("DefaultOperation")
67
+
62
68
  Related:
63
69
  docs/cookbooks/production-settings-and-targets.md,
64
70
  docs/production-graph.md
65
71
  """
66
- return TargetSetting(**kwargs)
72
+ return TargetSetting(default, **kwargs)
67
73
 
68
74
 
69
75
  @dataclass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iris_pex_embedded_python
3
- Version: 4.0.1b3
3
+ Version: 4.0.1b5
4
4
  Summary: Iris Interoperability based on Embedded Python
5
5
  Author-email: grongier <guillaume.rongier@intersystems.com>
6
6
  License: MIT License
@@ -53,7 +53,7 @@ iop/components/settings.py,sha256=USAKAisdersKlAEIk8HDu_TuYtkO-Sc3an3PXjPcDRA,62
53
53
  iop/messages/__init__.py,sha256=k1AKplpjdqSxM5Gl7bpWAfw93xUgigovlI4SKhwKHRI,355
54
54
  iop/messages/base.py,sha256=ZrVXEz7JrYHnt5b3M7avxcJCB0_OZxm1uWm9gQ4_eAw,1906
55
55
  iop/messages/decorators.py,sha256=CWk08k8U2aTJv7Dx1n2jp8sjZZSCD8NJPoifKLB3W2g,2488
56
- iop/messages/dispatch.py,sha256=wfoKyaMOcJTzMEAE5hEHYEt3aiRsaUR_TYmROT2fDrg,11592
56
+ iop/messages/dispatch.py,sha256=l7FX-TAx5cLgjxoxNpL8ul3Lj4w4-taxTtyeZU3W85I,14001
57
57
  iop/messages/persistent.py,sha256=ibRhRTqzwGcAqem744KZzHiouUlc8-J3ftB4zyzQYx4,16906
58
58
  iop/messages/serialization.py,sha256=BW0o-7GaceMW7RMOzOJ8g9rKEv28NYnYFwtTLmAf7Yg,9255
59
59
  iop/messages/validation.py,sha256=X6MSWsjLZDuyX1Tfx0TP3T6OI4nj1HZccwR2Z9PSubY,1606
@@ -71,13 +71,13 @@ iop/production/declarative.py,sha256=IpHYO46mu7k9wnq3DpFreoALPoX1Vi8DrJb-85O_-bQ
71
71
  iop/production/diff.py,sha256=NYpcSVklaGpaZOyl933W8eLsDWcwjlf6Upbp4pVGSMU,9975
72
72
  iop/production/import_.py,sha256=twBra6j2peolFadv8WyDwZZOd9NznzAkPsk2RbOfZQM,10833
73
73
  iop/production/inspection.py,sha256=PPv9DiDS4OrnKROqV7JPbuY8Y0JR-m8syPhap3GgE4g,3363
74
- iop/production/model.py,sha256=CpI5OBEBBw9z40Lws1rKVL9Mx_P4fhKmwYPoqEUntKU,41705
74
+ iop/production/model.py,sha256=RCss8i6jnpP-sNv0eLCwpxU2y2uOaLKJ5qeP4mlwwuQ,43533
75
75
  iop/production/planning.py,sha256=nCYrXLKlHBlUaQZPzPfqtIERIv62F2sg4gJ0b8mtcKk,23375
76
76
  iop/production/reconstruction.py,sha256=5vVteTEM6IOiQUfpe4AUQg6Nt4oaaIDR61-1l6Vu8hk,12532
77
77
  iop/production/rendering.py,sha256=UVHLICY4zWEP3W7-99TpB2WeLBNREC2N8rYSnBa_iZM,20240
78
78
  iop/production/runtime.py,sha256=6grX1u20OhHnvhzC3yPDTFEoDs_TN4mfs5MxzFzZMls,2637
79
79
  iop/production/source_inference.py,sha256=Wl3E9-1hWb4qxD1jwrrZTLSgqN-0vGhOrJtmzDWPCao,21613
80
- iop/production/types.py,sha256=L_wyXO8deOnqRFin74SX_K8UWILFu-VzIO5pKNa6VXY,16580
80
+ iop/production/types.py,sha256=B5tSmRAwFBoFPxp7oVPmfMW9sxHaeBtonAfv6C06W1g,16889
81
81
  iop/production/validation.py,sha256=14BfjrLXW1AOuP8AdBrs-CY6zynaDc8_AUD9frsYAIU,13753
82
82
  iop/runtime/__init__.py,sha256=wcyWMRuPCYE_CLd1NmgmI1M-IbFs2Gz2yIj2-EW_2AA,70
83
83
  iop/runtime/director.py,sha256=KW8BvkATK8XuIUPNBD9rMww9r3Zg4K3H54868eOgvl0,12867
@@ -91,9 +91,9 @@ iop/runtime/remote/director.py,sha256=bkie751GrYLgGs7ApRCowHpSnJA8KupxCOrFDQmjyO
91
91
  iop/runtime/remote/migration.py,sha256=Iboz-Ye7i9xif9bn433GazCF-gtD-HJW3wiUwWS5P6w,1831
92
92
  iop/runtime/remote/settings.py,sha256=P1Hsgld5o57HpSZgPVZzobyjlUmxJj6bstiWvR0JXnc,2144
93
93
  iop/runtime/remote/setup.py,sha256=Tr80F3JJTy7oSUso2jyx0iA_KZayd4MYt9vL0BvPpMA,2600
94
- iris_pex_embedded_python-4.0.1b3.dist-info/licenses/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
95
- iris_pex_embedded_python-4.0.1b3.dist-info/METADATA,sha256=UFR3F4n2-7iQJSkQ0WLBQYtmNaBwO08z7gZLl-q-_Hg,5846
96
- iris_pex_embedded_python-4.0.1b3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
97
- iris_pex_embedded_python-4.0.1b3.dist-info/entry_points.txt,sha256=fzS8ZFsPe8cnpEx208X60DNugCPQmuSloPeg4pyyJDY,42
98
- iris_pex_embedded_python-4.0.1b3.dist-info/top_level.txt,sha256=BM54-OXQ6l2BDtmesWNXckh033s9gcjoO7bfjbwZbxU,4
99
- iris_pex_embedded_python-4.0.1b3.dist-info/RECORD,,
94
+ iris_pex_embedded_python-4.0.1b5.dist-info/licenses/LICENSE,sha256=rZSiBFId_sfbJ6RL0GjjPX-InNLkNS9ou7eQsikciI8,1089
95
+ iris_pex_embedded_python-4.0.1b5.dist-info/METADATA,sha256=Ulc1Gj783FmOQWvAUmWTZNMKyOSA8hx6lOF1anaHJg0,5846
96
+ iris_pex_embedded_python-4.0.1b5.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
97
+ iris_pex_embedded_python-4.0.1b5.dist-info/entry_points.txt,sha256=fzS8ZFsPe8cnpEx208X60DNugCPQmuSloPeg4pyyJDY,42
98
+ iris_pex_embedded_python-4.0.1b5.dist-info/top_level.txt,sha256=BM54-OXQ6l2BDtmesWNXckh033s9gcjoO7bfjbwZbxU,4
99
+ iris_pex_embedded_python-4.0.1b5.dist-info/RECORD,,