Skip to content

Commit 56bbe7b

Browse files
committed
Add option to pickle relative filepaths in cloudpickle.
1 parent bbf3613 commit 56bbe7b

1 file changed

Lines changed: 46 additions & 6 deletions

File tree

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

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
import itertools
6767
import logging
6868
import opcode
69+
import os
6970
import pickle
7071
from pickle import _getattribute as _pickle_getattribute
7172
import platform
@@ -111,6 +112,7 @@ class CloudPickleConfig:
111112
"""Configuration for cloudpickle behavior."""
112113
id_generator: typing.Optional[callable] = uuid_generator
113114
skip_reset_dynamic_type_state: bool = False
115+
use_relative_filepaths: bool = False
114116

115117

116118
DEFAULT_CONFIG = CloudPickleConfig()
@@ -396,6 +398,27 @@ def func():
396398
return subimports
397399

398400

401+
def _get_relative_path(path):
402+
"""Returns the path of a filename relative to the longest matching directory
403+
in sys.path.
404+
Args:
405+
path: The path to the file.
406+
"""
407+
abs_path = os.path.abspath(path)
408+
longest_match = ""
409+
410+
for dir_path in sys.path:
411+
if not dir_path.endswith(os.path.sep):
412+
dir_path += os.path.sep
413+
414+
if abs_path.startswith(dir_path) and len(dir_path) > len(longest_match):
415+
longest_match = dir_path
416+
417+
if not longest_match:
418+
return path
419+
return os.path.relpath(abs_path, longest_match)
420+
421+
399422
# relevant opcodes
400423
STORE_GLOBAL = opcode.opmap["STORE_GLOBAL"]
401424
DELETE_GLOBAL = opcode.opmap["DELETE_GLOBAL"]
@@ -831,7 +854,7 @@ def _enum_getstate(obj):
831854
# these holes".
832855

833856

834-
def _code_reduce(obj):
857+
def _code_reduce(obj, config):
835858
"""code object reducer."""
836859
# If you are not sure about the order of arguments, take a look at help
837860
# of the specific type from types, for example:
@@ -850,6 +873,11 @@ def _code_reduce(obj):
850873
co_varnames = tuple(name for name in obj.co_varnames)
851874
co_freevars = tuple(name for name in obj.co_freevars)
852875
co_cellvars = tuple(name for name in obj.co_cellvars)
876+
877+
co_filename = obj.co_filename
878+
if (config and config.use_relative_filepaths):
879+
co_filename = _get_relative_path(co_filename)
880+
853881
if hasattr(obj, "co_exceptiontable"):
854882
# Python 3.11 and later: there are some new attributes
855883
# related to the enhanced exceptions.
@@ -864,7 +892,7 @@ def _code_reduce(obj):
864892
obj.co_consts,
865893
co_names,
866894
co_varnames,
867-
obj.co_filename,
895+
co_filename,
868896
co_name,
869897
obj.co_qualname,
870898
obj.co_firstlineno,
@@ -887,7 +915,7 @@ def _code_reduce(obj):
887915
obj.co_consts,
888916
co_names,
889917
co_varnames,
890-
obj.co_filename,
918+
co_filename,
891919
co_name,
892920
obj.co_firstlineno,
893921
obj.co_linetable,
@@ -908,7 +936,7 @@ def _code_reduce(obj):
908936
obj.co_code,
909937
obj.co_consts,
910938
co_varnames,
911-
obj.co_filename,
939+
co_filename,
912940
co_name,
913941
obj.co_firstlineno,
914942
obj.co_lnotab,
@@ -932,7 +960,7 @@ def _code_reduce(obj):
932960
obj.co_consts,
933961
co_names,
934962
co_varnames,
935-
obj.co_filename,
963+
co_filename,
936964
co_name,
937965
obj.co_firstlineno,
938966
obj.co_lnotab,
@@ -1240,7 +1268,6 @@ class Pickler(pickle.Pickler):
12401268
_dispatch_table[property] = _property_reduce
12411269
_dispatch_table[staticmethod] = _classmethod_reduce
12421270
_dispatch_table[CellType] = _cell_reduce
1243-
_dispatch_table[types.CodeType] = _code_reduce
12441271
_dispatch_table[types.GetSetDescriptorType] = _getset_descriptor_reduce
12451272
_dispatch_table[types.ModuleType] = _module_reduce
12461273
_dispatch_table[types.MethodType] = _method_reduce
@@ -1300,6 +1327,12 @@ def _function_getnewargs(self, func):
13001327
base_globals = self.globals_ref.setdefault(id(func.__globals__), {})
13011328

13021329
if base_globals == {}:
1330+
if "__file__" in func.__globals__:
1331+
# Apply normalization ONLY to the __file__ attribute
1332+
file_path = func.__globals__["__file__"]
1333+
if self.config.use_relative_filepaths:
1334+
file_path = _get_relative_path(file_path)
1335+
base_globals["__file__"] = file_path
13031336
# Add module attributes used to resolve relative imports
13041337
# instructions inside func.
13051338
for k in ["__package__", "__name__", "__path__", "__file__"]:
@@ -1405,6 +1438,8 @@ def reducer_override(self, obj):
14051438
return _class_reduce(obj, self.config)
14061439
elif isinstance(obj, typing.TypeVar): # Add this check
14071440
return _typevar_reduce(obj, self.config)
1441+
elif isinstance(obj, types.CodeType):
1442+
return _code_reduce(obj, self.config)
14081443
elif isinstance(obj, types.FunctionType):
14091444
return self._function_reduce(obj)
14101445
else:
@@ -1487,6 +1522,11 @@ def save_typevar(self, obj, name=None):
14871522

14881523
dispatch[typing.TypeVar] = save_typevar
14891524

1525+
def save_code(self, obj, name=None):
1526+
return self.save_reduce(*_code_reduce(obj, self.config), obj=obj)
1527+
1528+
dispatch[types.CodeType] = save_code
1529+
14901530
def save_function(self, obj, name=None):
14911531
"""Registered with the dispatch to handle all function types.
14921532

0 commit comments

Comments
 (0)