4343import base64
4444import datetime as dt
4545import decimal
46- import functools
4746import importlib
4847import importlib .util
49- import inspect
5048import json
5149import math
5250import sys
@@ -65,10 +63,6 @@ class ProtocolError(Exception):
6563 """Raised for malformed requests (bad protocol/id/method/params)."""
6664
6765
68- class InstanceHandleError (ValueError ):
69- """Raised when an instance handle is unknown or no longer valid."""
70-
71-
7266class ImportNotAllowedError (PermissionError ):
7367 """Raised when a requested module import is not on the active allowlist."""
7468
@@ -97,8 +91,8 @@ def __init__(self, attr_name):
9791# IMPORT / ATTRIBUTE ALLOWLIST (trust boundary enforcement)
9892# =============================================================================
9993#
100- # The bridge dispatches call/instantiate/call_method by importing the requested
101- # module and getattr-ing the requested function/class/ method. That is an
94+ # The bridge dispatches call requests by importing the requested module and
95+ # getattr-ing the requested function/class method. That is an
10296# arbitrary import+getattr+call surface, so two complementary guards live here.
10397# Both are PURE (no env reads) so the rules behave identically under the
10498# subprocess server and the in-WASM Pyodide server; the subprocess server derives
@@ -205,22 +199,6 @@ def resolve_allowed_attr_path(root, dotted_name, *, allow_private_attrs):
205199 return obj
206200
207201
208- def is_accessor_attr (obj , attr_name ):
209- """
210- True when attr_name resolves to a @property or functools.cached_property on
211- obj's type — i.e. it is read by attribute access, not called.
212-
213- Inspects type(obj)'s MRO via getattr_static (which never triggers the
214- descriptor protocol), NOT the instance dict. That matters for
215- cached_property: after the first read it stores its value in the instance
216- __dict__, so an instance-level static lookup would return the cached value
217- rather than the descriptor and misclassify it as a method on the next read.
218- Reading from the type keeps the classification stable across repeated reads.
219- """
220- descriptor = inspect .getattr_static (type (obj ), attr_name , None )
221- return isinstance (descriptor , (property , functools .cached_property ))
222-
223-
224202class CodecError (Exception ):
225203 """Raised when value encoding fails (e.g. NaN/Infinity not allowed)."""
226204
@@ -990,64 +968,7 @@ def handle_call(
990968 return serialize (res , force_json_markers = force_json_markers , torch_allow_copy = torch_allow_copy )
991969
992970
993- def handle_instantiate (
994- params , instances , * , allowed_modules , allow_private_attrs , has_envelope_markers
995- ):
996- module_name = require_str (params , 'module' )
997- class_name = require_str (params , 'className' )
998- args = deserialize (coerce_list (params .get ('args' ), 'args' ), has_envelope_markers = has_envelope_markers )
999- kwargs = deserialize (coerce_dict (params .get ('kwargs' ), 'kwargs' ), has_envelope_markers = has_envelope_markers )
1000- mod = import_allowed_module (module_name , allowed_modules )
1001- cls = get_allowed_attr (mod , class_name , allow_private_attrs = allow_private_attrs )
1002- obj = cls (* args , ** kwargs )
1003- handle_id = str (id (obj ))
1004- instances [handle_id ] = obj
1005- return handle_id
1006-
1007-
1008- def handle_call_method (
1009- params ,
1010- instances ,
1011- * ,
1012- force_json_markers ,
1013- torch_allow_copy ,
1014- allow_private_attrs ,
1015- has_envelope_markers ,
1016- ):
1017- handle_id = require_str (params , 'handle' )
1018- method_name = require_str (params , 'methodName' )
1019- args = deserialize (coerce_list (params .get ('args' ), 'args' ), has_envelope_markers = has_envelope_markers )
1020- kwargs = deserialize (coerce_dict (params .get ('kwargs' ), 'kwargs' ), has_envelope_markers = has_envelope_markers )
1021- if handle_id not in instances :
1022- raise InstanceHandleError (f'Unknown instance handle: { handle_id } ' )
1023- obj = instances [handle_id ]
1024- # A @property / functools.cached_property is read, not called: the generated
1025- # `get prop()` accessor emits callMethod(handle, name, []). Classify before
1026- # touching the value (so cached_property is detected on its first read) and
1027- # return the attribute directly; everything else is a bound method to call.
1028- if is_accessor_attr (obj , method_name ):
1029- # An accessor is read, never called: a generated `get prop()` always
1030- # sends empty args. Reject a malformed request that supplies any so it
1031- # fails loudly instead of silently dropping the arguments.
1032- if args or kwargs :
1033- raise ProtocolError (f'Accessor { method_name !r} does not accept arguments' )
1034- res = get_allowed_attr (obj , method_name , allow_private_attrs = allow_private_attrs )
1035- else :
1036- func = get_allowed_attr (obj , method_name , allow_private_attrs = allow_private_attrs )
1037- res = func (* args , ** kwargs )
1038- return serialize (res , force_json_markers = force_json_markers , torch_allow_copy = torch_allow_copy )
1039-
1040-
1041- def handle_dispose_instance (params , instances ):
1042- handle_id = require_str (params , 'handle' )
1043- if handle_id not in instances :
1044- return False
1045- del instances [handle_id ]
1046- return True
1047-
1048-
1049971def build_meta (
1050- instances ,
1051972 * ,
1052973 bridge ,
1053974 pid ,
@@ -1088,7 +1009,7 @@ def build_meta(
10881009 'scipyAvailable' : module_available ('scipy' ),
10891010 'torchAvailable' : module_available ('torch' ),
10901011 'sklearnAvailable' : module_available ('sklearn' ),
1091- 'instances' : len ( instances ) ,
1012+ 'instances' : 0 ,
10921013 }
10931014 if transport_info is not None :
10941015 meta ['transport' ] = transport_info
@@ -1097,7 +1018,6 @@ def build_meta(
10971018
10981019def dispatch_request (
10991020 msg ,
1100- instances ,
11011021 * ,
11021022 bridge ,
11031023 pid ,
@@ -1121,7 +1041,7 @@ def dispatch_request(
11211041 the final encode_value() call, which the caller performs.
11221042
11231043 allowed_modules: None (default) disables the import allowlist so existing
1124- behavior is preserved. Supplying a set restricts call/instantiate imports to
1044+ behavior is preserved. Supplying a set restricts call imports to
11251045 those modules (plus the stdlib the bridge itself needs) and raises
11261046 ImportNotAllowedError otherwise. allow_private_attrs=False (default) blocks
11271047 getattr of underscore-prefixed names; True restores unrestricted access. See
@@ -1141,32 +1061,12 @@ def dispatch_request(
11411061 allow_private_attrs = allow_private_attrs ,
11421062 has_envelope_markers = has_envelope_markers ,
11431063 )
1144- elif method == 'instantiate' :
1145- result = handle_instantiate (
1146- params ,
1147- instances ,
1148- allowed_modules = allowed_modules ,
1149- allow_private_attrs = allow_private_attrs ,
1150- has_envelope_markers = has_envelope_markers ,
1151- )
1152- elif method == 'call_method' :
1153- result = handle_call_method (
1154- params ,
1155- instances ,
1156- force_json_markers = force_json_markers ,
1157- torch_allow_copy = torch_allow_copy ,
1158- allow_private_attrs = allow_private_attrs ,
1159- has_envelope_markers = has_envelope_markers ,
1160- )
1161- elif method == 'dispose_instance' :
1162- result = handle_dispose_instance (params , instances )
11631064 elif method == 'meta' :
11641065 if python_version is None :
11651066 import sys
11661067 python_version = sys .version .split ()[0 ]
11671068 codec_fallback = 'json' if force_json_markers else 'none'
11681069 result = build_meta (
1169- instances ,
11701070 bridge = bridge ,
11711071 pid = pid ,
11721072 python_version = python_version ,
0 commit comments