Skip to content

Commit f4dc5a6

Browse files
committed
Do some renaming, apply linter.
1 parent 5087483 commit f4dc5a6

File tree

6 files changed

+146
-54
lines changed

6 files changed

+146
-54
lines changed

temporalio/worker/workflow_sandbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@
5555
# * https://github.com/GrahamDumpleton/wrapt/issues/130
5656

5757
from ._restrictions import (
58+
DisallowedUnintentionalPassthroughError,
5859
RestrictedWorkflowAccessError,
5960
SandboxMatcher,
6061
SandboxRestrictions,
61-
DisallowedUnintentionalPassthroughError,
6262
)
6363
from ._runner import SandboxedWorkflowRunner
6464

temporalio/worker/workflow_sandbox/_importer.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
import temporalio.workflow
3939

4040
from ._restrictions import (
41+
DisallowedUnintentionalPassthroughError,
4142
RestrictedModule,
4243
RestrictedWorkflowAccessError,
43-
DisallowedUnintentionalPassthroughError,
4444
RestrictionContext,
4545
SandboxRestrictions,
4646
)
@@ -205,7 +205,7 @@ def _import(
205205
if (
206206
self.restriction_context.in_activation
207207
and self._is_import_policy_applied(
208-
temporalio.workflow.SandboxImportPolicy.WARN_ON_DYNAMIC_IMPORT
208+
temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT
209209
)
210210
):
211211
warnings.warn(
@@ -295,13 +295,15 @@ def module_configured_passthrough(self, name: str) -> bool:
295295
return True
296296

297297
def _is_import_policy_applied(
298-
self, policy: temporalio.workflow.SandboxImportPolicy
298+
self, policy: temporalio.workflow.SandboxImportNotificationPolicy
299299
) -> bool:
300-
applied_policy = temporalio.workflow.unsafe.current_sandbox_import_policy()
301-
if applied_policy != temporalio.workflow.SandboxImportPolicy.UNSET:
300+
applied_policy = (
301+
temporalio.workflow.unsafe.current_sandbox_import_notification_policy()
302+
)
303+
if applied_policy:
302304
return policy in applied_policy
303305

304-
return policy in self.restrictions.import_policy
306+
return policy in self.restrictions.import_notification_policy
305307

306308
def _maybe_passthrough_module(self, name: str) -> Optional[types.ModuleType]:
307309
# If imports not passed through and all modules are not passed through
@@ -311,13 +313,12 @@ def _maybe_passthrough_module(self, name: str) -> Optional[types.ModuleType]:
311313
and not self.module_configured_passthrough(name)
312314
):
313315
if self._is_import_policy_applied(
314-
temporalio.workflow.SandboxImportPolicy.RAISE_ON_NON_PASSTHROUGH
316+
temporalio.workflow.SandboxImportNotificationPolicy.RAISE_ON_UNINTENTIONAL_PASSTHROUGH
315317
):
316-
# TODO(amazzeo): this is not an appropriate error type
317318
raise DisallowedUnintentionalPassthroughError(name)
318319

319320
if self._is_import_policy_applied(
320-
temporalio.workflow.SandboxImportPolicy.WARN_ON_NON_PASSTHROUGH
321+
temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_UNINTENTIONAL_PASSTHROUGH
321322
):
322323
warnings.warn(
323324
f"Module {name} was not intentionally passed through to the sandbox."

temporalio/worker/workflow_sandbox/_restrictions.py

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,17 @@ def default_message(qualified_name: str) -> str:
8484

8585
# TODO(amazzeo): is NondeterminisimError appropriate as a subclass?
8686
class DisallowedUnintentionalPassthroughError(temporalio.workflow.NondeterminismError):
87+
"""Error that occurs when a workflow unintentionally passes an import to the sandbox when
88+
the import notification policy includes :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.RAISE_ON_NON_PASSTHROUGH`.
89+
90+
Attributes:
91+
qualified_name: Fully qualified name of what was passed through to the sandbox.
92+
"""
93+
8794
def __init__(
8895
self, qualified_name: str, *, override_message: Optional[str] = None
8996
) -> None:
97+
"""Create a disallowed unintentional passthrough error."""
9098
super().__init__(
9199
override_message
92100
or DisallowedUnintentionalPassthroughError.default_message(qualified_name)
@@ -95,7 +103,7 @@ def __init__(
95103

96104
@staticmethod
97105
def default_message(qualified_name: str) -> str:
98-
"""Get default message for restricted access."""
106+
"""Get default message for disallowed unintential passthrough."""
99107
return f"Module {qualified_name} was not intentionally passed through to the sandbox."
100108

101109

@@ -127,14 +135,43 @@ class methods (including __init__, etc). The check compares the against the
127135
fully qualified path to the item.
128136
"""
129137

130-
import_policy: temporalio.workflow.SandboxImportPolicy = (
131-
temporalio.workflow.SandboxImportPolicy.WARN_ON_DYNAMIC_IMPORT
132-
)
138+
import_notification_policy: temporalio.workflow.SandboxImportNotificationPolicy
139+
"""
140+
The import notification policy to use when an import is triggered during workflow loading or execution. See :py:class:`temporalio.workflow.SandboxImportNotificationPolicy` for options.
141+
"""
142+
143+
import_notification_policy_default: ClassVar[
144+
temporalio.workflow.SandboxImportNotificationPolicy
145+
]
146+
"""
147+
The default import notification policy. Equivalent to :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT`.
148+
"""
149+
150+
import_notification_policy_all_warnings: ClassVar[
151+
temporalio.workflow.SandboxImportNotificationPolicy
152+
]
153+
"""
154+
An import notification policy that enables warnings on dynamic imports during a
155+
workflow and imports that are unintentionally passed through to the sandbox.
156+
Equivalent to :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT` | :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_UNINTENTIONAL_PASSTHROUGH`
157+
"""
133158

134-
import_policy_all_warnings: ClassVar[temporalio.workflow.SandboxImportPolicy]
135-
import_policy_disallow_unintentional_passthrough: ClassVar[
136-
temporalio.workflow.SandboxImportPolicy
159+
import_notification_policy_disallow_unintentional_passthrough: ClassVar[
160+
temporalio.workflow.SandboxImportNotificationPolicy
137161
]
162+
"""
163+
An import notification policy that enables warnings on dynamic imports during a
164+
workflow and raises an error when imports are unintentionally passed through to the sandbox.
165+
Equivalent to :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT` | :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.RAISE_ON_UNINTENTIONAL_PASSTHROUGH`
166+
"""
167+
168+
import_notification_policy_silent: ClassVar[
169+
temporalio.workflow.SandboxImportNotificationPolicy
170+
]
171+
"""
172+
An import notification policy that disables all warnings and errors.
173+
Equivalent to :py:attr:`temporalio.workflow.SandboxImportNotificationPolicy.SILENT`.
174+
"""
138175

139176
passthrough_all_modules: bool = False
140177
"""
@@ -196,6 +233,12 @@ def with_passthrough_all_modules(self) -> SandboxRestrictions:
196233
"""
197234
return dataclasses.replace(self, passthrough_all_modules=True)
198235

236+
def with_import_policy(
237+
self, policy: temporalio.workflow.SandboxImportNotificationPolicy
238+
) -> SandboxRestrictions:
239+
"""Create a new restriction set with the given import notification policy as the :py:attr:`import_policy`."""
240+
return dataclasses.replace(self, import_notification_policy=policy)
241+
199242

200243
# We intentionally use specific fields instead of generic "matcher" callbacks
201244
# for optimization reasons.
@@ -311,7 +354,6 @@ def access_matcher(
311354
Returns:
312355
The matcher if matched.
313356
"""
314-
315357
# We prefer to avoid recursion
316358
matcher = self
317359
for v in child_path:
@@ -525,13 +567,19 @@ def with_child_unrestricted(self, *child_path: str) -> SandboxMatcher:
525567
}
526568
)
527569

528-
SandboxRestrictions.import_policy_all_warnings = (
529-
temporalio.workflow.SandboxImportPolicy.WARN_ON_DYNAMIC_IMPORT
530-
| temporalio.workflow.SandboxImportPolicy.WARN_ON_NON_PASSTHROUGH
570+
SandboxRestrictions.import_notification_policy_default = (
571+
temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT
572+
)
573+
SandboxRestrictions.import_notification_policy_all_warnings = (
574+
temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT
575+
| temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_UNINTENTIONAL_PASSTHROUGH
576+
)
577+
SandboxRestrictions.import_notification_policy_disallow_unintentional_passthrough = (
578+
temporalio.workflow.SandboxImportNotificationPolicy.WARN_ON_DYNAMIC_IMPORT
579+
| temporalio.workflow.SandboxImportNotificationPolicy.RAISE_ON_UNINTENTIONAL_PASSTHROUGH
531580
)
532-
SandboxRestrictions.import_policy_disallow_unintentional_passthrough = (
533-
temporalio.workflow.SandboxImportPolicy.WARN_ON_DYNAMIC_IMPORT
534-
| temporalio.workflow.SandboxImportPolicy.RAISE_ON_NON_PASSTHROUGH
581+
SandboxRestrictions.import_notification_policy_silent = (
582+
temporalio.workflow.SandboxImportNotificationPolicy.SILENT
535583
)
536584

537585
# sys.stdlib_module_names is only available on 3.10+, so we hardcode here. A
@@ -836,6 +884,7 @@ def _public_callables(parent: Any, *, exclude: Set[str] = set()) -> Set[str]:
836884
passthrough_modules=SandboxRestrictions.passthrough_modules_default,
837885
invalid_modules=SandboxMatcher.none,
838886
invalid_module_members=SandboxRestrictions.invalid_module_members_default,
887+
import_notification_policy=SandboxRestrictions.import_notification_policy_default,
839888
)
840889

841890

temporalio/workflow.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,24 +1410,20 @@ async def wait_condition(
14101410
_sandbox_unrestricted = threading.local()
14111411
_in_sandbox = threading.local()
14121412
_imports_passed_through = threading.local()
1413+
_sandbox_import_notification_policy_override = threading.local()
14131414

14141415

1415-
# TODO(amazzeo): this name is confusing with existing import passthrough phrasing.
1416-
class SandboxImportPolicy(Flag):
1417-
# TODO(amazzeo): Need to establish consistent phrasing about the time this is applied
1418-
"""Defines the behavior taken when modules are imported into the sandbox after the workflow is initially loaded."""
1416+
class SandboxImportNotificationPolicy(Flag):
1417+
"""Defines the behavior taken when modules are imported into the sandbox after the workflow is initially loaded or unintentionally missing from the passthrough list."""
14191418

1420-
UNSET = auto()
1421-
"""All imports that do not violate sandbox restrictions are allowed and no warning is generated."""
1419+
SILENT = auto()
1420+
"""Allow imports that do not violate sandbox restrictions and no warnings are generated."""
14221421
WARN_ON_DYNAMIC_IMPORT = auto()
1423-
"""Issue a warning when an import is triggered in the sandbox after initial workflow load."""
1424-
WARN_ON_NON_PASSTHROUGH = auto()
1425-
"""Issue a warning when an import is triggered in the sandbox that was not passed through."""
1426-
RAISE_ON_NON_PASSTHROUGH = auto()
1427-
"""Raise an error when an import is triggered in the sandbox that was not passed through."""
1428-
1429-
1430-
_sandbox_import_policy = threading.local()
1422+
"""Allows dynamic imports that do not violate sandbox restrictions but issues a warning when an import is triggered in the sandbox after initial workflow load."""
1423+
WARN_ON_UNINTENTIONAL_PASSTHROUGH = auto()
1424+
"""Allows imports that do not violate sandbox restrictions but issues a warning when an import is triggered in the sandbox that was unintentionally passed through."""
1425+
RAISE_ON_UNINTENTIONAL_PASSTHROUGH = auto()
1426+
"""Raise an error when an import is triggered in the sandbox that was unintentionally passed through."""
14311427

14321428

14331429
class unsafe:
@@ -1517,24 +1513,33 @@ def imports_passed_through() -> Iterator[None]:
15171513
_imports_passed_through.value = False
15181514

15191515
@staticmethod
1520-
def current_sandbox_import_policy() -> SandboxImportPolicy:
1516+
def current_sandbox_import_notification_policy() -> (
1517+
Optional[SandboxImportNotificationPolicy]
1518+
):
1519+
"""Gets the current import notification policy override if one is set."""
15211520
applied_policy = getattr(
1522-
_sandbox_import_policy, "value", SandboxImportPolicy.UNSET
1521+
_sandbox_import_notification_policy_override,
1522+
"value",
1523+
None,
15231524
)
15241525
return applied_policy
15251526

15261527
@staticmethod
15271528
@contextmanager
1528-
def sandbox_import_policy(policy: SandboxImportPolicy) -> Iterator[None]:
1529-
# TODO(amazzeo): the default behavior here seems inappropriate
1530-
original_policy = _sandbox_import_policy.value = getattr(
1531-
_sandbox_import_policy, "value", SandboxImportPolicy.UNSET
1529+
def sandbox_import_notification_policy(
1530+
policy: SandboxImportNotificationPolicy,
1531+
) -> Iterator[None]:
1532+
"""Context manager to apply the given import notification policy."""
1533+
original_policy = _sandbox_import_notification_policy_override.value = getattr(
1534+
_sandbox_import_notification_policy_override,
1535+
"value",
1536+
None,
15321537
)
1533-
_sandbox_import_policy.value = policy
1538+
_sandbox_import_notification_policy_override.value = policy
15341539
try:
15351540
yield None
15361541
finally:
1537-
_sandbox_import_policy.value = original_policy
1542+
_sandbox_import_notification_policy_override.value = original_policy
15381543

15391544

15401545
class LoggerAdapter(logging.LoggerAdapter):

tests/worker/workflow_sandbox/test_runner.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
WorkflowInterceptorClassInput,
2626
)
2727
from temporalio.worker.workflow_sandbox import (
28+
DisallowedUnintentionalPassthroughError,
2829
RestrictedWorkflowAccessError,
2930
SandboxedWorkflowRunner,
3031
SandboxMatcher,
3132
SandboxRestrictions,
32-
DisallowedUnintentionalPassthroughError,
3333
)
3434
from tests.helpers import assert_eq_eventually
3535
from tests.worker.workflow_sandbox.testmodules import stateful_module
@@ -41,7 +41,7 @@
4141
_ = os.name
4242

4343
# This used to fail because our __init__ couldn't handle metaclass init
44-
import zipfile
44+
import zipfile # noqa: E402
4545

4646

4747
class MyZipFile(zipfile.ZipFile):
@@ -495,10 +495,10 @@ class _TestWorkflowInboundInterceptor(WorkflowInboundInterceptor):
495495
async def execute_workflow(self, input: ExecuteWorkflowInput) -> Any:
496496
# import in the interceptor to show it will be captured
497497
# applying this policy should squelch the "after initial workload" warning
498-
with workflow.unsafe.sandbox_import_policy(
499-
workflow.SandboxImportPolicy.WARN_ON_NON_PASSTHROUGH
498+
with workflow.unsafe.sandbox_import_notification_policy(
499+
workflow.SandboxImportNotificationPolicy.WARN_ON_UNINTENTIONAL_PASSTHROUGH
500500
):
501-
import tests.worker.workflow_sandbox.testmodules.lazy_module_interceptor
501+
import tests.worker.workflow_sandbox.testmodules.lazy_module_interceptor # noqa: F401
502502

503503
return await super().execute_workflow(input)
504504

@@ -515,7 +515,7 @@ class LazyImportWorkflow:
515515
@workflow.run
516516
async def run(self) -> None:
517517
try:
518-
import tests.worker.workflow_sandbox.testmodules.lazy_module
518+
import tests.worker.workflow_sandbox.testmodules.lazy_module # noqa: F401
519519
except DisallowedUnintentionalPassthroughError as err:
520520
raise ApplicationError(
521521
str(err), type="DisallowedUnintentionalPassthroughError"
@@ -554,7 +554,7 @@ async def test_workflow_sandbox_import_default_warnings(client: Client):
554554
async def test_workflow_sandbox_import_all_warnings(client: Client):
555555
restrictions = dataclasses.replace(
556556
SandboxRestrictions.default,
557-
import_policy=SandboxRestrictions.import_policy_all_warnings,
557+
import_notification_policy=SandboxRestrictions.import_notification_policy_all_warnings,
558558
# passthrough this test module to avoid a ton of noisy warnings
559559
passthrough_modules=SandboxRestrictions.passthrough_modules_default
560560
| {"tests.worker.workflow_sandbox.test_runner"},
@@ -587,7 +587,7 @@ async def test_workflow_sandbox_import_all_warnings(client: Client):
587587
async def test_workflow_sandbox_import_errors(client: Client):
588588
restrictions = dataclasses.replace(
589589
SandboxRestrictions.default,
590-
import_policy=SandboxRestrictions.import_policy_disallow_unintentional_passthrough,
590+
import_notification_policy=SandboxRestrictions.import_notification_policy_disallow_unintentional_passthrough,
591591
# passthrough this test module to avoid a ton of noisy warnings
592592
passthrough_modules=SandboxRestrictions.passthrough_modules_default
593593
| {"tests.worker.workflow_sandbox.test_runner"},
@@ -622,6 +622,42 @@ async def test_workflow_sandbox_import_errors(client: Client):
622622
)
623623

624624

625+
@workflow.defn
626+
class SupressWarningsLazyImportWorkflow:
627+
@workflow.run
628+
async def run(self) -> None:
629+
with workflow.unsafe.sandbox_import_notification_policy(
630+
SandboxRestrictions.import_notification_policy_silent
631+
):
632+
try:
633+
import tests.worker.workflow_sandbox.testmodules.lazy_module # noqa: F401
634+
except UserWarning:
635+
raise ApplicationError("No warnings were expected")
636+
637+
638+
async def test_workflow_sandbox_import_suppress_warnings(client: Client):
639+
restrictions = dataclasses.replace(
640+
SandboxRestrictions.default,
641+
# passthrough this test module to avoid a ton of noisy warnings
642+
passthrough_modules=SandboxRestrictions.passthrough_modules_default
643+
| {"tests.worker.workflow_sandbox.test_runner"},
644+
)
645+
646+
async with Worker(
647+
client,
648+
task_queue=str(uuid.uuid4()),
649+
workflows=[SupressWarningsLazyImportWorkflow],
650+
workflow_runner=SandboxedWorkflowRunner(restrictions),
651+
) as worker:
652+
with pytest.warns(None) as recorder: # type:ignore
653+
await client.execute_workflow(
654+
SupressWarningsLazyImportWorkflow.run,
655+
id=f"workflow-{uuid.uuid4()}",
656+
task_queue=worker.task_queue,
657+
)
658+
assert len(recorder) == 0, "Expected no warnings to be issued"
659+
660+
625661
def _assert_expected_warnings(
626662
recorder: pytest.WarningsRecorder, expected_warnings: Set[str]
627663
):

tests/worker/workflow_sandbox/testmodules/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
"tests.worker.workflow_sandbox.testmodules.invalid_module_members".split("."),
1515
SandboxMatcher(use={"invalid_function"}),
1616
),
17+
import_notification_policy=SandboxRestrictions.import_notification_policy_default,
1718
)

0 commit comments

Comments
 (0)