Skip to content

Commit 88f7bb7

Browse files
committed
Make filepath interceptor, add docstrings to CloudPickleConfig, revert coder changes (they need to be guarded by update compat flag).
1 parent d3628da commit 88f7bb7

3 files changed

Lines changed: 56 additions & 30 deletions

File tree

sdks/python/apache_beam/coders/coder_impl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ def encode_type(self, t, stream):
561561

562562
if t not in _pickled_types:
563563
_pickled_types[t] = cloudpickle_pickler.dumps(
564-
t, config=cloudpickle_pickler.DETERMINISTIC_CODER_CONFIG)
564+
t, config=cloudpickle_pickler.NO_DYNAMIC_CLASS_TRACKING_CONFIG)
565565
stream.write(_pickled_types[t], True)
566566

567567
def decode_type(self, stream):

sdks/python/apache_beam/internal/cloudpickle/cloudpickle.py

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,28 @@ def uuid_generator(_):
109109

110110
@dataclasses.dataclass
111111
class CloudPickleConfig:
112-
"""Configuration for cloudpickle behavior."""
112+
"""Configuration for cloudpickle behavior.
113+
114+
This class controls various aspects of how cloudpickle serializes objects.
115+
116+
Attributes:
117+
id_generator: Callable that generates unique identifiers for dynamic
118+
types. Controls isinstance semantics preservation. If None,
119+
disables type tracking and isinstance relationships are not
120+
preserved across pickle/unpickle cycles. If callable, generates
121+
unique IDs to maintain object identity.
122+
Default: uuid_generator (generates UUID hex strings).
123+
124+
skip_reset_dynamic_type_state: Whether to skip resetting state when
125+
reconstructing dynamic types. If True, skips state reset for
126+
already-reconstructed types.
127+
128+
filepath_interceptor: Used to modify filepaths in `co_filename` and
129+
function.__globals__['__file__'].
130+
"""
113131
id_generator: typing.Optional[callable] = uuid_generator
114132
skip_reset_dynamic_type_state: bool = False
115-
use_relative_filepaths: bool = False
133+
filepath_interceptor: typing.Optional[callable] = None
116134

117135

118136
DEFAULT_CONFIG = CloudPickleConfig()
@@ -398,7 +416,7 @@ def func():
398416
return subimports
399417

400418

401-
def _get_relative_path(path):
419+
def get_relative_path(path):
402420
"""Returns the path of a filename relative to the longest matching directory
403421
in sys.path.
404422
Args:
@@ -631,7 +649,7 @@ def _make_typevar(
631649
return _lookup_class_or_track(class_tracker_id, tv)
632650

633651

634-
def _decompose_typevar(obj, config):
652+
def _decompose_typevar(obj, config: CloudPickleConfig):
635653
return (
636654
obj.__name__,
637655
obj.__bound__,
@@ -642,7 +660,7 @@ def _decompose_typevar(obj, config):
642660
)
643661

644662

645-
def _typevar_reduce(obj, config):
663+
def _typevar_reduce(obj, config: CloudPickleConfig):
646664
# TypeVar instances require the module information hence why we
647665
# are not using the _should_pickle_by_reference directly
648666
module_and_name = _lookup_module_and_qualname(obj, name=obj.__name__)
@@ -694,7 +712,7 @@ def _make_dict_items(obj, is_ordered=False):
694712
# -------------------------------------------------
695713

696714

697-
def _class_getnewargs(obj, config):
715+
def _class_getnewargs(obj, config: CloudPickleConfig):
698716
type_kwargs = {}
699717
if "__module__" in obj.__dict__:
700718
type_kwargs["__module__"] = obj.__module__
@@ -713,7 +731,7 @@ def _class_getnewargs(obj, config):
713731
)
714732

715733

716-
def _enum_getnewargs(obj, config):
734+
def _enum_getnewargs(obj, config: CloudPickleConfig):
717735
members = {e.name: e.value for e in obj}
718736
return (
719737
obj.__bases__,
@@ -854,7 +872,7 @@ def _enum_getstate(obj):
854872
# these holes".
855873

856874

857-
def _code_reduce(obj, config):
875+
def _code_reduce(obj, config: CloudPickleConfig):
858876
"""code object reducer."""
859877
# If you are not sure about the order of arguments, take a look at help
860878
# of the specific type from types, for example:
@@ -875,8 +893,8 @@ def _code_reduce(obj, config):
875893
co_cellvars = tuple(name for name in obj.co_cellvars)
876894

877895
co_filename = obj.co_filename
878-
if (config and config.use_relative_filepaths):
879-
co_filename = _get_relative_path(co_filename)
896+
if (config and config.filepath_interceptor):
897+
co_filename = config.filepath_interceptor(co_filename)
880898

881899
if hasattr(obj, "co_exceptiontable"):
882900
# Python 3.11 and later: there are some new attributes
@@ -1071,7 +1089,7 @@ def _weakset_reduce(obj):
10711089
return weakref.WeakSet, (list(obj), )
10721090

10731091

1074-
def _dynamic_class_reduce(obj, config):
1092+
def _dynamic_class_reduce(obj, config: CloudPickleConfig):
10751093
"""Save a class that can't be referenced as a module attribute.
10761094
10771095
This method is used to serialize classes that are defined inside
@@ -1102,7 +1120,7 @@ def _dynamic_class_reduce(obj, config):
11021120
)
11031121

11041122

1105-
def _class_reduce(obj, config):
1123+
def _class_reduce(obj, config: CloudPickleConfig):
11061124
"""Select the reducer depending on the dynamic nature of the class obj."""
11071125
if obj is type(None): # noqa
11081126
return type, (None, )
@@ -1197,7 +1215,7 @@ def _function_setstate(obj, state):
11971215
setattr(obj, k, v)
11981216

11991217

1200-
def _class_setstate(obj, state, skip_reset_dynamic_type_state):
1218+
def _class_setstate(obj, state, skip_reset_dynamic_type_state=False):
12011219
# Lock while potentially modifying class state.
12021220
with _DYNAMIC_CLASS_TRACKER_LOCK:
12031221
if skip_reset_dynamic_type_state and obj in _DYNAMIC_CLASS_STATE_TRACKER_BY_CLASS:
@@ -1330,12 +1348,12 @@ def _function_getnewargs(self, func):
13301348
if "__file__" in func.__globals__:
13311349
# Apply normalization ONLY to the __file__ attribute
13321350
file_path = func.__globals__["__file__"]
1333-
if self.config.use_relative_filepaths:
1334-
file_path = _get_relative_path(file_path)
1351+
if self.config.filepath_interceptor:
1352+
file_path = self.config.filepath_interceptor(file_path)
13351353
base_globals["__file__"] = file_path
13361354
# Add module attributes used to resolve relative imports
13371355
# instructions inside func.
1338-
for k in ["__package__", "__name__", "__path__", "__file__"]:
1356+
for k in ["__package__", "__name__", "__path__"]:
13391357
if k in func.__globals__:
13401358
base_globals[k] = func.__globals__[k]
13411359

@@ -1351,15 +1369,16 @@ def _function_getnewargs(self, func):
13511369
def dump(self, obj):
13521370
try:
13531371
return super().dump(obj)
1354-
except RuntimeError as e:
1355-
if len(e.args) > 0 and "recursion" in e.args[0]:
1356-
msg = "Could not pickle object as excessively deep recursion required."
1357-
raise pickle.PicklingError(msg) from e
1358-
else:
1359-
raise
1372+
except RecursionError as e:
1373+
msg = "Could not pickle object as excessively deep recursion required."
1374+
raise pickle.PicklingError(msg) from e
13601375

13611376
def __init__(
1362-
self, file, protocol=None, buffer_callback=None, config=DEFAULT_CONFIG):
1377+
self,
1378+
file,
1379+
protocol=None,
1380+
buffer_callback=None,
1381+
config: CloudPickleConfig = DEFAULT_CONFIG):
13631382
if protocol is None:
13641383
protocol = DEFAULT_PROTOCOL
13651384
super().__init__(file, protocol=protocol, buffer_callback=buffer_callback)
@@ -1572,7 +1591,12 @@ def save_pypy_builtin_func(self, obj):
15721591
# Shorthands similar to pickle.dump/pickle.dumps
15731592

15741593

1575-
def dump(obj, file, protocol=None, buffer_callback=None, config=DEFAULT_CONFIG):
1594+
def dump(
1595+
obj,
1596+
file,
1597+
protocol=None,
1598+
buffer_callback=None,
1599+
config: CloudPickleConfig = DEFAULT_CONFIG):
15761600
"""Serialize obj as bytes streamed into file
15771601
15781602
protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to
@@ -1590,7 +1614,11 @@ def dump(obj, file, protocol=None, buffer_callback=None, config=DEFAULT_CONFIG):
15901614
config=config).dump(obj)
15911615

15921616

1593-
def dumps(obj, protocol=None, buffer_callback=None, config=DEFAULT_CONFIG):
1617+
def dumps(
1618+
obj,
1619+
protocol=None,
1620+
buffer_callback=None,
1621+
config: CloudPickleConfig = DEFAULT_CONFIG):
15941622
"""Serialize obj as a string of bytes allocated in memory
15951623
15961624
protocol defaults to cloudpickle.DEFAULT_PROTOCOL which is an alias to

sdks/python/apache_beam/internal/cloudpickle_pickler.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,8 @@
3939

4040
DEFAULT_CONFIG = cloudpickle.CloudPickleConfig(
4141
skip_reset_dynamic_type_state=True)
42-
DETERMINISTIC_CODER_CONFIG = cloudpickle.CloudPickleConfig(
43-
id_generator=None,
44-
skip_reset_dynamic_type_state=True,
45-
use_relative_filepaths=True)
42+
NO_DYNAMIC_CLASS_TRACKING_CONFIG = cloudpickle.CloudPickleConfig(
43+
id_generator=None, skip_reset_dynamic_type_state=True)
4644

4745
try:
4846
from absl import flags

0 commit comments

Comments
 (0)