modal 1.1.5.dev83__py3-none-any.whl → 1.3.1.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of modal might be problematic. Click here for more details.
- modal/__init__.py +4 -4
- modal/__main__.py +4 -29
- modal/_billing.py +84 -0
- modal/_clustered_functions.py +1 -3
- modal/_container_entrypoint.py +33 -208
- modal/_functions.py +146 -121
- modal/_grpc_client.py +191 -0
- modal/_ipython.py +16 -6
- modal/_load_context.py +106 -0
- modal/_object.py +72 -21
- modal/_output.py +12 -14
- modal/_partial_function.py +31 -4
- modal/_resolver.py +44 -57
- modal/_runtime/container_io_manager.py +26 -28
- modal/_runtime/container_io_manager.pyi +42 -44
- modal/_runtime/gpu_memory_snapshot.py +9 -7
- modal/_runtime/user_code_event_loop.py +80 -0
- modal/_runtime/user_code_imports.py +236 -10
- modal/_serialization.py +2 -1
- modal/_traceback.py +4 -13
- modal/_tunnel.py +16 -11
- modal/_tunnel.pyi +25 -3
- modal/_utils/async_utils.py +337 -10
- modal/_utils/auth_token_manager.py +1 -4
- modal/_utils/blob_utils.py +29 -22
- modal/_utils/function_utils.py +20 -21
- modal/_utils/grpc_testing.py +6 -3
- modal/_utils/grpc_utils.py +223 -64
- modal/_utils/mount_utils.py +26 -1
- modal/_utils/package_utils.py +0 -1
- modal/_utils/rand_pb_testing.py +8 -1
- modal/_utils/task_command_router_client.py +524 -0
- modal/_vendor/cloudpickle.py +144 -48
- modal/app.py +215 -96
- modal/app.pyi +78 -37
- modal/billing.py +5 -0
- modal/builder/2025.06.txt +6 -3
- modal/builder/PREVIEW.txt +2 -1
- modal/builder/base-images.json +4 -2
- modal/cli/_download.py +19 -3
- modal/cli/cluster.py +4 -2
- modal/cli/config.py +3 -1
- modal/cli/container.py +5 -4
- modal/cli/dict.py +5 -2
- modal/cli/entry_point.py +26 -2
- modal/cli/environment.py +2 -16
- modal/cli/launch.py +1 -76
- modal/cli/network_file_system.py +5 -20
- modal/cli/queues.py +5 -4
- modal/cli/run.py +24 -204
- modal/cli/secret.py +1 -2
- modal/cli/shell.py +375 -0
- modal/cli/utils.py +1 -13
- modal/cli/volume.py +11 -17
- modal/client.py +16 -125
- modal/client.pyi +94 -144
- modal/cloud_bucket_mount.py +3 -1
- modal/cloud_bucket_mount.pyi +4 -0
- modal/cls.py +101 -64
- modal/cls.pyi +9 -8
- modal/config.py +21 -1
- modal/container_process.py +288 -12
- modal/container_process.pyi +99 -38
- modal/dict.py +72 -33
- modal/dict.pyi +88 -57
- modal/environments.py +16 -8
- modal/environments.pyi +6 -2
- modal/exception.py +154 -16
- modal/experimental/__init__.py +23 -5
- modal/experimental/flash.py +161 -74
- modal/experimental/flash.pyi +97 -49
- modal/file_io.py +50 -92
- modal/file_io.pyi +117 -89
- modal/functions.pyi +70 -87
- modal/image.py +73 -47
- modal/image.pyi +33 -30
- modal/io_streams.py +500 -149
- modal/io_streams.pyi +279 -189
- modal/mount.py +60 -45
- modal/mount.pyi +41 -17
- modal/network_file_system.py +19 -11
- modal/network_file_system.pyi +72 -39
- modal/object.pyi +114 -22
- modal/parallel_map.py +42 -44
- modal/parallel_map.pyi +9 -17
- modal/partial_function.pyi +4 -2
- modal/proxy.py +14 -6
- modal/proxy.pyi +10 -2
- modal/queue.py +45 -38
- modal/queue.pyi +88 -52
- modal/runner.py +96 -96
- modal/runner.pyi +44 -27
- modal/sandbox.py +225 -108
- modal/sandbox.pyi +226 -63
- modal/secret.py +58 -56
- modal/secret.pyi +28 -13
- modal/serving.py +7 -11
- modal/serving.pyi +7 -8
- modal/snapshot.py +29 -15
- modal/snapshot.pyi +18 -10
- modal/token_flow.py +1 -1
- modal/token_flow.pyi +4 -6
- modal/volume.py +102 -55
- modal/volume.pyi +125 -66
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
- modal-1.3.1.dev8.dist-info/RECORD +189 -0
- modal_proto/api.proto +86 -30
- modal_proto/api_grpc.py +10 -25
- modal_proto/api_pb2.py +1080 -1047
- modal_proto/api_pb2.pyi +253 -79
- modal_proto/api_pb2_grpc.py +14 -48
- modal_proto/api_pb2_grpc.pyi +6 -18
- modal_proto/modal_api_grpc.py +175 -176
- modal_proto/{sandbox_router.proto → task_command_router.proto} +62 -45
- modal_proto/task_command_router_grpc.py +138 -0
- modal_proto/task_command_router_pb2.py +180 -0
- modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +110 -63
- modal_proto/task_command_router_pb2_grpc.py +272 -0
- modal_proto/task_command_router_pb2_grpc.pyi +100 -0
- modal_version/__init__.py +1 -1
- modal_version/__main__.py +1 -1
- modal/cli/programs/launch_instance_ssh.py +0 -94
- modal/cli/programs/run_marimo.py +0 -95
- modal-1.1.5.dev83.dist-info/RECORD +0 -191
- modal_proto/modal_options_grpc.py +0 -3
- modal_proto/options.proto +0 -19
- modal_proto/options_grpc.py +0 -3
- modal_proto/options_pb2.py +0 -35
- modal_proto/options_pb2.pyi +0 -20
- modal_proto/options_pb2_grpc.py +0 -4
- modal_proto/options_pb2_grpc.pyi +0 -7
- modal_proto/sandbox_router_grpc.py +0 -105
- modal_proto/sandbox_router_pb2.py +0 -148
- modal_proto/sandbox_router_pb2_grpc.py +0 -203
- modal_proto/sandbox_router_pb2_grpc.pyi +0 -75
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
- {modal-1.1.5.dev83.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
modal/_vendor/cloudpickle.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Vendored version of cloudpickle v3.
|
|
2
|
+
Vendored version of cloudpickle v3.1.2.
|
|
3
3
|
|
|
4
4
|
This is vendored because cloudpickle needs to be the same version on both sides
|
|
5
5
|
of a send/receive operation, and users may require a distinct version for their
|
|
@@ -72,7 +72,7 @@ import itertools
|
|
|
72
72
|
import logging
|
|
73
73
|
import opcode
|
|
74
74
|
import pickle
|
|
75
|
-
from pickle import _getattribute
|
|
75
|
+
from pickle import _getattribute as _pickle_getattribute
|
|
76
76
|
import platform
|
|
77
77
|
import struct
|
|
78
78
|
import sys
|
|
@@ -127,13 +127,15 @@ def _get_or_create_tracker_id(class_def):
|
|
|
127
127
|
def _lookup_class_or_track(class_tracker_id, class_def):
|
|
128
128
|
if class_tracker_id is not None:
|
|
129
129
|
with _DYNAMIC_CLASS_TRACKER_LOCK:
|
|
130
|
-
class_def = _DYNAMIC_CLASS_TRACKER_BY_ID.setdefault(
|
|
130
|
+
class_def = _DYNAMIC_CLASS_TRACKER_BY_ID.setdefault(
|
|
131
|
+
class_tracker_id, class_def
|
|
132
|
+
)
|
|
131
133
|
_DYNAMIC_CLASS_TRACKER_BY_CLASS[class_def] = class_tracker_id
|
|
132
134
|
return class_def
|
|
133
135
|
|
|
134
136
|
|
|
135
137
|
def register_pickle_by_value(module):
|
|
136
|
-
"""Register a module to make
|
|
138
|
+
"""Register a module to make its functions and classes picklable by value.
|
|
137
139
|
|
|
138
140
|
By default, functions and classes that are attributes of an importable
|
|
139
141
|
module are to be pickled by reference, that is relying on re-importing
|
|
@@ -164,7 +166,10 @@ def register_pickle_by_value(module):
|
|
|
164
166
|
# this introspection yet, in order to avoid a possible breaking change
|
|
165
167
|
# later, we still enforce the presence of module inside sys.modules.
|
|
166
168
|
if module.__name__ not in sys.modules:
|
|
167
|
-
raise ValueError(
|
|
169
|
+
raise ValueError(
|
|
170
|
+
f"{module} was not imported correctly, have you used an "
|
|
171
|
+
"`import` statement to access it?"
|
|
172
|
+
)
|
|
168
173
|
_PICKLE_BY_VALUE_MODULES.add(module.__name__)
|
|
169
174
|
|
|
170
175
|
|
|
@@ -196,6 +201,14 @@ def _is_registered_pickle_by_value(module):
|
|
|
196
201
|
return False
|
|
197
202
|
|
|
198
203
|
|
|
204
|
+
if sys.version_info >= (3, 14):
|
|
205
|
+
def _getattribute(obj, name):
|
|
206
|
+
return _pickle_getattribute(obj, name.split('.'))
|
|
207
|
+
else:
|
|
208
|
+
def _getattribute(obj, name):
|
|
209
|
+
return _pickle_getattribute(obj, name)[0]
|
|
210
|
+
|
|
211
|
+
|
|
199
212
|
def _whichmodule(obj, name):
|
|
200
213
|
"""Find the module an object belongs to.
|
|
201
214
|
|
|
@@ -215,10 +228,15 @@ def _whichmodule(obj, name):
|
|
|
215
228
|
for module_name, module in sys.modules.copy().items():
|
|
216
229
|
# Some modules such as coverage can inject non-module objects inside
|
|
217
230
|
# sys.modules
|
|
218
|
-
if
|
|
231
|
+
if (
|
|
232
|
+
module_name == "__main__"
|
|
233
|
+
or module_name == "__mp_main__"
|
|
234
|
+
or module is None
|
|
235
|
+
or not isinstance(module, types.ModuleType)
|
|
236
|
+
):
|
|
219
237
|
continue
|
|
220
238
|
try:
|
|
221
|
-
if _getattribute(module, name)
|
|
239
|
+
if _getattribute(module, name) is obj:
|
|
222
240
|
return module_name
|
|
223
241
|
except Exception:
|
|
224
242
|
pass
|
|
@@ -256,7 +274,9 @@ def _should_pickle_by_reference(obj, name=None):
|
|
|
256
274
|
return False
|
|
257
275
|
return obj.__name__ in sys.modules
|
|
258
276
|
else:
|
|
259
|
-
raise TypeError(
|
|
277
|
+
raise TypeError(
|
|
278
|
+
"cannot check importability of {} instances".format(type(obj).__name__)
|
|
279
|
+
)
|
|
260
280
|
|
|
261
281
|
|
|
262
282
|
def _lookup_module_and_qualname(obj, name=None):
|
|
@@ -290,7 +310,7 @@ def _lookup_module_and_qualname(obj, name=None):
|
|
|
290
310
|
return None
|
|
291
311
|
|
|
292
312
|
try:
|
|
293
|
-
obj2
|
|
313
|
+
obj2 = _getattribute(module, name)
|
|
294
314
|
except AttributeError:
|
|
295
315
|
# obj was not found inside the module it points to
|
|
296
316
|
return None
|
|
@@ -353,7 +373,11 @@ def _find_imported_submodules(code, top_level_dependencies):
|
|
|
353
373
|
subimports = []
|
|
354
374
|
# check if any known dependency is an imported package
|
|
355
375
|
for x in top_level_dependencies:
|
|
356
|
-
if
|
|
376
|
+
if (
|
|
377
|
+
isinstance(x, types.ModuleType)
|
|
378
|
+
and hasattr(x, "__package__")
|
|
379
|
+
and x.__package__
|
|
380
|
+
):
|
|
357
381
|
# check if the package has any currently loaded sub-imports
|
|
358
382
|
prefix = x.__name__ + "."
|
|
359
383
|
# A concurrent thread could mutate sys.modules,
|
|
@@ -403,7 +427,10 @@ def _walk_global_ops(code):
|
|
|
403
427
|
|
|
404
428
|
def _extract_class_dict(cls):
|
|
405
429
|
"""Retrieve a copy of the dict of a class without the inherited method."""
|
|
406
|
-
|
|
430
|
+
# Hack to circumvent non-predictable memoization caused by string interning.
|
|
431
|
+
# See the inline comment in _class_setstate for details.
|
|
432
|
+
clsdict = {"".join(k): cls.__dict__[k] for k in sorted(cls.__dict__)}
|
|
433
|
+
|
|
407
434
|
if len(cls.__bases__) == 1:
|
|
408
435
|
inherited_dict = cls.__bases__[0].__dict__
|
|
409
436
|
else:
|
|
@@ -514,7 +541,9 @@ def _make_cell(value=_empty_cell_value):
|
|
|
514
541
|
return cell
|
|
515
542
|
|
|
516
543
|
|
|
517
|
-
def _make_skeleton_class(
|
|
544
|
+
def _make_skeleton_class(
|
|
545
|
+
type_constructor, name, bases, type_kwargs, class_tracker_id, extra
|
|
546
|
+
):
|
|
518
547
|
"""Build dynamic class with an empty __dict__ to be filled once memoized
|
|
519
548
|
|
|
520
549
|
If class_tracker_id is not None, try to lookup an existing class definition
|
|
@@ -525,11 +554,21 @@ def _make_skeleton_class(type_constructor, name, bases, type_kwargs, class_track
|
|
|
525
554
|
The "extra" variable is meant to be a dict (or None) that can be used for
|
|
526
555
|
forward compatibility shall the need arise.
|
|
527
556
|
"""
|
|
528
|
-
|
|
557
|
+
# We need to intern the keys of the type_kwargs dict to avoid having
|
|
558
|
+
# different pickles for the same dynamic class depending on whether it was
|
|
559
|
+
# dynamically created or reconstructed from a pickled stream.
|
|
560
|
+
type_kwargs = {sys.intern(k): v for k, v in type_kwargs.items()}
|
|
561
|
+
|
|
562
|
+
skeleton_class = types.new_class(
|
|
563
|
+
name, bases, {"metaclass": type_constructor}, lambda ns: ns.update(type_kwargs)
|
|
564
|
+
)
|
|
565
|
+
|
|
529
566
|
return _lookup_class_or_track(class_tracker_id, skeleton_class)
|
|
530
567
|
|
|
531
568
|
|
|
532
|
-
def _make_skeleton_enum(
|
|
569
|
+
def _make_skeleton_enum(
|
|
570
|
+
bases, name, qualname, members, module, class_tracker_id, extra
|
|
571
|
+
):
|
|
533
572
|
"""Build dynamic enum with an empty __dict__ to be filled once memoized
|
|
534
573
|
|
|
535
574
|
The creation of the enum class is inspired by the code of
|
|
@@ -597,7 +636,7 @@ def _get_bases(typ):
|
|
|
597
636
|
if "__orig_bases__" in getattr(typ, "__dict__", {}):
|
|
598
637
|
# For generic types (see PEP 560)
|
|
599
638
|
# Note that simply checking `hasattr(typ, '__orig_bases__')` is not
|
|
600
|
-
# correct. Subclasses of a fully-
|
|
639
|
+
# correct. Subclasses of a fully-parameterized generic class does not
|
|
601
640
|
# have `__orig_bases__` defined, but `hasattr(typ, '__orig_bases__')`
|
|
602
641
|
# will return True because it's defined in the base class.
|
|
603
642
|
bases_attr = "__orig_bases__"
|
|
@@ -682,8 +721,10 @@ def _function_getstate(func):
|
|
|
682
721
|
# unpickling time by iterating over slotstate and calling setattr(func,
|
|
683
722
|
# slotname, slotvalue)
|
|
684
723
|
slotstate = {
|
|
685
|
-
|
|
686
|
-
|
|
724
|
+
# Hack to circumvent non-predictable memoization caused by string interning.
|
|
725
|
+
# See the inline comment in _class_setstate for details.
|
|
726
|
+
"__name__": "".join(func.__name__),
|
|
727
|
+
"__qualname__": "".join(func.__qualname__),
|
|
687
728
|
"__annotations__": func.__annotations__,
|
|
688
729
|
"__kwdefaults__": func.__kwdefaults__,
|
|
689
730
|
"__defaults__": func.__defaults__,
|
|
@@ -709,7 +750,9 @@ def _function_getstate(func):
|
|
|
709
750
|
)
|
|
710
751
|
slotstate["__globals__"] = f_globals
|
|
711
752
|
|
|
712
|
-
|
|
753
|
+
# Hack to circumvent non-predictable memoization caused by string interning.
|
|
754
|
+
# See the inline comment in _class_setstate for details.
|
|
755
|
+
state = {"".join(k): v for k, v in func.__dict__.items()}
|
|
713
756
|
return state, slotstate
|
|
714
757
|
|
|
715
758
|
|
|
@@ -749,6 +792,12 @@ def _class_getstate(obj):
|
|
|
749
792
|
|
|
750
793
|
clsdict.pop("__dict__", None) # unpicklable property object
|
|
751
794
|
|
|
795
|
+
if sys.version_info >= (3, 14):
|
|
796
|
+
# PEP-649/749: __annotate_func__ contains a closure that references the class
|
|
797
|
+
# dict. We need to exclude it from pickling. Python will recreate it when
|
|
798
|
+
# __annotations__ is accessed at unpickling time.
|
|
799
|
+
clsdict.pop("__annotate_func__", None)
|
|
800
|
+
|
|
752
801
|
return (clsdict, {})
|
|
753
802
|
|
|
754
803
|
|
|
@@ -790,6 +839,19 @@ def _code_reduce(obj):
|
|
|
790
839
|
# of the specific type from types, for example:
|
|
791
840
|
# >>> from types import CodeType
|
|
792
841
|
# >>> help(CodeType)
|
|
842
|
+
|
|
843
|
+
# Hack to circumvent non-predictable memoization caused by string interning.
|
|
844
|
+
# See the inline comment in _class_setstate for details.
|
|
845
|
+
co_name = "".join(obj.co_name)
|
|
846
|
+
|
|
847
|
+
# Create shallow copies of these tuple to make cloudpickle payload deterministic.
|
|
848
|
+
# When creating a code object during load, copies of these four tuples are
|
|
849
|
+
# created, while in the main process, these tuples can be shared.
|
|
850
|
+
# By always creating copies, we make sure the resulting payload is deterministic.
|
|
851
|
+
co_names = tuple(name for name in obj.co_names)
|
|
852
|
+
co_varnames = tuple(name for name in obj.co_varnames)
|
|
853
|
+
co_freevars = tuple(name for name in obj.co_freevars)
|
|
854
|
+
co_cellvars = tuple(name for name in obj.co_cellvars)
|
|
793
855
|
if hasattr(obj, "co_exceptiontable"):
|
|
794
856
|
# Python 3.11 and later: there are some new attributes
|
|
795
857
|
# related to the enhanced exceptions.
|
|
@@ -802,16 +864,16 @@ def _code_reduce(obj):
|
|
|
802
864
|
obj.co_flags,
|
|
803
865
|
obj.co_code,
|
|
804
866
|
obj.co_consts,
|
|
805
|
-
|
|
806
|
-
|
|
867
|
+
co_names,
|
|
868
|
+
co_varnames,
|
|
807
869
|
obj.co_filename,
|
|
808
|
-
|
|
870
|
+
co_name,
|
|
809
871
|
obj.co_qualname,
|
|
810
872
|
obj.co_firstlineno,
|
|
811
873
|
obj.co_linetable,
|
|
812
874
|
obj.co_exceptiontable,
|
|
813
|
-
|
|
814
|
-
|
|
875
|
+
co_freevars,
|
|
876
|
+
co_cellvars,
|
|
815
877
|
)
|
|
816
878
|
elif hasattr(obj, "co_linetable"):
|
|
817
879
|
# Python 3.10 and later: obj.co_lnotab is deprecated and constructor
|
|
@@ -825,14 +887,14 @@ def _code_reduce(obj):
|
|
|
825
887
|
obj.co_flags,
|
|
826
888
|
obj.co_code,
|
|
827
889
|
obj.co_consts,
|
|
828
|
-
|
|
829
|
-
|
|
890
|
+
co_names,
|
|
891
|
+
co_varnames,
|
|
830
892
|
obj.co_filename,
|
|
831
|
-
|
|
893
|
+
co_name,
|
|
832
894
|
obj.co_firstlineno,
|
|
833
895
|
obj.co_linetable,
|
|
834
|
-
|
|
835
|
-
|
|
896
|
+
co_freevars,
|
|
897
|
+
co_cellvars,
|
|
836
898
|
)
|
|
837
899
|
elif hasattr(obj, "co_nmeta"): # pragma: no cover
|
|
838
900
|
# "nogil" Python: modified attributes from 3.9
|
|
@@ -847,15 +909,15 @@ def _code_reduce(obj):
|
|
|
847
909
|
obj.co_flags,
|
|
848
910
|
obj.co_code,
|
|
849
911
|
obj.co_consts,
|
|
850
|
-
|
|
912
|
+
co_varnames,
|
|
851
913
|
obj.co_filename,
|
|
852
|
-
|
|
914
|
+
co_name,
|
|
853
915
|
obj.co_firstlineno,
|
|
854
916
|
obj.co_lnotab,
|
|
855
917
|
obj.co_exc_handlers,
|
|
856
918
|
obj.co_jump_table,
|
|
857
|
-
|
|
858
|
-
|
|
919
|
+
co_freevars,
|
|
920
|
+
co_cellvars,
|
|
859
921
|
obj.co_free2reg,
|
|
860
922
|
obj.co_cell2reg,
|
|
861
923
|
)
|
|
@@ -870,14 +932,14 @@ def _code_reduce(obj):
|
|
|
870
932
|
obj.co_flags,
|
|
871
933
|
obj.co_code,
|
|
872
934
|
obj.co_consts,
|
|
873
|
-
|
|
874
|
-
|
|
935
|
+
co_names,
|
|
936
|
+
co_varnames,
|
|
875
937
|
obj.co_filename,
|
|
876
|
-
|
|
938
|
+
co_name,
|
|
877
939
|
obj.co_firstlineno,
|
|
878
940
|
obj.co_lnotab,
|
|
879
|
-
|
|
880
|
-
|
|
941
|
+
co_freevars,
|
|
942
|
+
co_cellvars,
|
|
881
943
|
)
|
|
882
944
|
return types.CodeType, args
|
|
883
945
|
|
|
@@ -902,7 +964,9 @@ def _file_reduce(obj):
|
|
|
902
964
|
import io
|
|
903
965
|
|
|
904
966
|
if not hasattr(obj, "name") or not hasattr(obj, "mode"):
|
|
905
|
-
raise pickle.PicklingError(
|
|
967
|
+
raise pickle.PicklingError(
|
|
968
|
+
"Cannot pickle files that do not map to an actual file"
|
|
969
|
+
)
|
|
906
970
|
if obj is sys.stdout:
|
|
907
971
|
return getattr, (sys, "stdout")
|
|
908
972
|
if obj is sys.stderr:
|
|
@@ -914,7 +978,9 @@ def _file_reduce(obj):
|
|
|
914
978
|
if hasattr(obj, "isatty") and obj.isatty():
|
|
915
979
|
raise pickle.PicklingError("Cannot pickle files that map to tty objects")
|
|
916
980
|
if "r" not in obj.mode and "+" not in obj.mode:
|
|
917
|
-
raise pickle.PicklingError(
|
|
981
|
+
raise pickle.PicklingError(
|
|
982
|
+
"Cannot pickle files that are not opened for reading: %s" % obj.mode
|
|
983
|
+
)
|
|
918
984
|
|
|
919
985
|
name = obj.name
|
|
920
986
|
|
|
@@ -927,7 +993,9 @@ def _file_reduce(obj):
|
|
|
927
993
|
contents = obj.read()
|
|
928
994
|
obj.seek(curloc)
|
|
929
995
|
except OSError as e:
|
|
930
|
-
raise pickle.PicklingError(
|
|
996
|
+
raise pickle.PicklingError(
|
|
997
|
+
"Cannot pickle file %s as it cannot be read" % name
|
|
998
|
+
) from e
|
|
931
999
|
retval.write(contents)
|
|
932
1000
|
retval.seek(curloc)
|
|
933
1001
|
|
|
@@ -1109,11 +1177,38 @@ def _class_setstate(obj, state):
|
|
|
1109
1177
|
if attrname == "_abc_impl":
|
|
1110
1178
|
registry = attr
|
|
1111
1179
|
else:
|
|
1180
|
+
# Note: setting attribute names on a class automatically triggers their
|
|
1181
|
+
# interning in CPython:
|
|
1182
|
+
# https://github.com/python/cpython/blob/v3.12.0/Objects/object.c#L957
|
|
1183
|
+
#
|
|
1184
|
+
# This means that to get deterministic pickling for a dynamic class that
|
|
1185
|
+
# was initially defined in a different Python process, the pickler
|
|
1186
|
+
# needs to ensure that dynamic class and function attribute names are
|
|
1187
|
+
# systematically copied into a non-interned version to avoid
|
|
1188
|
+
# unpredictable pickle payloads.
|
|
1189
|
+
#
|
|
1190
|
+
# Indeed the Pickler's memoizer relies on physical object identity to break
|
|
1191
|
+
# cycles in the reference graph of the object being serialized.
|
|
1112
1192
|
setattr(obj, attrname, attr)
|
|
1193
|
+
|
|
1194
|
+
if sys.version_info >= (3, 13) and "__firstlineno__" in state:
|
|
1195
|
+
# Set the Python 3.13+ only __firstlineno__ attribute one more time, as it
|
|
1196
|
+
# will be automatically deleted by the `setattr(obj, attrname, attr)` call
|
|
1197
|
+
# above when `attrname` is "__firstlineno__". We assume that preserving this
|
|
1198
|
+
# information might be important for some users and that it not stale in the
|
|
1199
|
+
# context of cloudpickle usage, hence legitimate to propagate. Furthermore it
|
|
1200
|
+
# is necessary to do so to keep deterministic chained pickling as tested in
|
|
1201
|
+
# test_deterministic_str_interning_for_chained_dynamic_class_pickling.
|
|
1202
|
+
obj.__firstlineno__ = state["__firstlineno__"]
|
|
1203
|
+
|
|
1113
1204
|
if registry is not None:
|
|
1114
1205
|
for subclass in registry:
|
|
1115
1206
|
obj.register(subclass)
|
|
1116
1207
|
|
|
1208
|
+
# PEP-649/749: During pickling, we excluded the __annotate_func__ attribute but it
|
|
1209
|
+
# will be created by Python. Subsequently, annotations will be recreated when
|
|
1210
|
+
# __annotations__ is accessed.
|
|
1211
|
+
|
|
1117
1212
|
return obj
|
|
1118
1213
|
|
|
1119
1214
|
|
|
@@ -1225,12 +1320,9 @@ class Pickler(pickle.Pickler):
|
|
|
1225
1320
|
def dump(self, obj):
|
|
1226
1321
|
try:
|
|
1227
1322
|
return super().dump(obj)
|
|
1228
|
-
except
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
raise pickle.PicklingError(msg) from e
|
|
1232
|
-
else:
|
|
1233
|
-
raise
|
|
1323
|
+
except RecursionError as e:
|
|
1324
|
+
msg = "Could not pickle object as excessively deep recursion required."
|
|
1325
|
+
raise pickle.PicklingError(msg) from e
|
|
1234
1326
|
|
|
1235
1327
|
def __init__(self, file, protocol=None, buffer_callback=None):
|
|
1236
1328
|
if protocol is None:
|
|
@@ -1368,7 +1460,9 @@ class Pickler(pickle.Pickler):
|
|
|
1368
1460
|
elif obj is type(NotImplemented):
|
|
1369
1461
|
return self.save_reduce(type, (NotImplemented,), obj=obj)
|
|
1370
1462
|
elif obj in _BUILTIN_TYPE_NAMES:
|
|
1371
|
-
return self.save_reduce(
|
|
1463
|
+
return self.save_reduce(
|
|
1464
|
+
_builtin_type, (_BUILTIN_TYPE_NAMES[obj],), obj=obj
|
|
1465
|
+
)
|
|
1372
1466
|
|
|
1373
1467
|
if name is not None:
|
|
1374
1468
|
super().save_global(obj, name=name)
|
|
@@ -1390,7 +1484,9 @@ class Pickler(pickle.Pickler):
|
|
|
1390
1484
|
elif PYPY and isinstance(obj.__code__, builtin_code_type):
|
|
1391
1485
|
return self.save_pypy_builtin_func(obj)
|
|
1392
1486
|
else:
|
|
1393
|
-
return self._save_reduce_pickle5(
|
|
1487
|
+
return self._save_reduce_pickle5(
|
|
1488
|
+
*self._dynamic_function_reduce(obj), obj=obj
|
|
1489
|
+
)
|
|
1394
1490
|
|
|
1395
1491
|
def save_pypy_builtin_func(self, obj):
|
|
1396
1492
|
"""Save pypy equivalent of builtin functions.
|