Skip to content

Commit d809f17

Browse files
fix(platform): preserve interrupt model compatibility
1 parent f56b3bf commit d809f17

3 files changed

Lines changed: 74 additions & 42 deletions

File tree

packages/uipath-platform/src/uipath/platform/common/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def __getattr__(name: str) -> Any:
194194

195195
from . import interrupt_models
196196

197-
value = getattr(interrupt_models, name)
197+
value = interrupt_models._resolve(name)
198198
globals()[name] = value
199199
return value
200200

packages/uipath-platform/src/uipath/platform/common/interrupt_models.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,31 +63,34 @@
6363
"WaitUntil",
6464
]
6565

66-
_warning_emitted = False
6766

67+
def _resolve(name: str) -> Any:
68+
"""Resolve a deprecated alias from the canonical module and warn.
6869
69-
def __getattr__(name: str) -> Any:
70-
"""Resolve deprecated interrupt model aliases on demand."""
71-
if name not in __all__:
72-
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
73-
70+
Called only from a module ``__getattr__`` (here or in ``common``), so the
71+
frame three levels up from ``warnings.warn`` is the user's import site.
72+
"""
7473
import uipath.platform.resume_triggers.interrupt_models as canonical_models
7574

7675
value = getattr(canonical_models, name)
77-
global _warning_emitted
78-
if not _warning_emitted:
79-
warnings.warn(
80-
"Interrupt models from uipath.platform.common are deprecated and will "
81-
"be removed in a future release; import from "
82-
"uipath.platform.resume_triggers instead.",
83-
FutureWarning,
84-
stacklevel=2,
85-
)
86-
_warning_emitted = True
76+
warnings.warn(
77+
"Importing interrupt models from uipath.platform.common is deprecated "
78+
"and will be removed in a future release; import from "
79+
"uipath.platform.resume_triggers instead.",
80+
DeprecationWarning,
81+
stacklevel=3,
82+
)
8783
globals()[name] = value
8884
return value
8985

9086

87+
def __getattr__(name: str) -> Any:
88+
"""Resolve deprecated interrupt model aliases on demand."""
89+
if name not in __all__:
90+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
91+
return _resolve(name)
92+
93+
9194
def __dir__() -> list[str]:
9295
"""List compatibility exports."""
9396
return sorted(set(globals()) | set(__all__))

packages/uipath-platform/tests/common/test_interrupt_models_compatibility.py

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,15 @@ def test_compatibility_exports_are_canonical_objects():
5454
common = importlib.import_module("uipath.platform.common")
5555
legacy = importlib.import_module("uipath.platform.common.interrupt_models")
5656

57-
with warnings.catch_warnings(record=True) as caught:
58-
warnings.simplefilter("always")
57+
with warnings.catch_warnings():
58+
warnings.simplefilter("ignore", DeprecationWarning)
5959
for name in INTERRUPT_MODEL_NAMES:
60-
canonical_model = getattr(canonical, name)
61-
assert getattr(legacy, name) is canonical_model
62-
assert getattr(common, name) is canonical_model
63-
64-
compatibility_warnings = [
65-
warning
66-
for warning in caught
67-
if issubclass(warning.category, FutureWarning)
68-
and "uipath.platform.resume_triggers" in str(warning.message)
69-
]
70-
assert len(compatibility_warnings) == 1
60+
assert getattr(legacy, name) is getattr(canonical, name)
61+
assert getattr(common, name) is getattr(canonical, name)
62+
7163
assert set(INTERRUPT_MODEL_NAMES) == set(legacy.__all__)
7264
assert set(INTERRUPT_MODEL_NAMES) <= set(common.__all__)
73-
assert set(INTERRUPT_MODEL_NAMES) <= set(dir(legacy))
65+
assert set(legacy.__all__) <= set(dir(legacy))
7466
assert set(INTERRUPT_MODEL_NAMES) <= set(dir(common))
7567

7668

@@ -86,7 +78,7 @@ def test_importing_common_does_not_load_resume_triggers_or_warn():
8678
8779
assert "uipath.platform.resume_triggers" not in sys.modules
8880
assert "uipath.platform.resume_triggers.interrupt_models" not in sys.modules
89-
assert not [item for item in caught if issubclass(item.category, FutureWarning)]
81+
assert not [item for item in caught if issubclass(item.category, DeprecationWarning)]
9082
"""
9183
)
9284

@@ -120,35 +112,72 @@ def test_import_orders_are_cycle_safe(imports: str):
120112
compatibility_warnings = [
121113
item
122114
for item in caught
123-
if issubclass(item.category, FutureWarning)
115+
if issubclass(item.category, DeprecationWarning)
124116
and "uipath.platform.resume_triggers" in str(item.message)
125117
]
126-
assert len(compatibility_warnings) == 1
118+
assert compatibility_warnings
127119
"""
128120
)
129121

130122

131-
def test_only_first_legacy_model_resolution_warns():
123+
def test_legacy_imports_warn_at_caller_site():
132124
_run_python(
133125
"""
134-
import importlib
135126
import warnings
136127
137-
common = importlib.import_module("uipath.platform.common")
138-
legacy = importlib.import_module("uipath.platform.common.interrupt_models")
128+
with warnings.catch_warnings(record=True) as caught:
129+
warnings.simplefilter("always")
130+
from uipath.platform.common import WaitUntil
131+
from uipath.platform.common.interrupt_models import WaitTask
132+
133+
compatibility_warnings = [
134+
item
135+
for item in caught
136+
if issubclass(item.category, DeprecationWarning)
137+
and "uipath.platform.resume_triggers" in str(item.message)
138+
]
139+
assert len(compatibility_warnings) == 2
140+
assert all(item.filename == "<string>" for item in compatibility_warnings), [
141+
item.filename for item in compatibility_warnings
142+
]
143+
"""
144+
)
145+
146+
147+
def test_cached_resolution_warns_once_per_name():
148+
_run_python(
149+
"""
150+
import warnings
151+
152+
import uipath.platform.common as common
139153
140154
with warnings.catch_warnings(record=True) as caught:
141155
warnings.simplefilter("always")
142-
legacy.WaitUntil
143-
legacy.WaitTask
156+
common.WaitUntil
157+
common.WaitUntil
144158
common.WaitJob
145159
146160
compatibility_warnings = [
147161
item
148162
for item in caught
149-
if issubclass(item.category, FutureWarning)
163+
if issubclass(item.category, DeprecationWarning)
150164
and "uipath.platform.resume_triggers" in str(item.message)
151165
]
152-
assert len(compatibility_warnings) == 1
166+
assert len(compatibility_warnings) == 2
167+
"""
168+
)
169+
170+
171+
def test_star_import_exports_all_interrupt_models():
172+
_run_python(
173+
"""
174+
import warnings
175+
176+
warnings.simplefilter("ignore", DeprecationWarning)
177+
178+
from uipath.platform.common.interrupt_models import * # noqa: F403
179+
180+
for name in ("InvokeProcess", "WaitUntil", "CreateTask", "WaitIntegrationEvent"):
181+
assert name in globals(), name
153182
"""
154183
)

0 commit comments

Comments
 (0)