modal 1.1.5.dev66__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.

Files changed (143) hide show
  1. modal/__init__.py +4 -4
  2. modal/__main__.py +4 -29
  3. modal/_billing.py +84 -0
  4. modal/_clustered_functions.py +1 -3
  5. modal/_container_entrypoint.py +33 -208
  6. modal/_functions.py +171 -138
  7. modal/_grpc_client.py +191 -0
  8. modal/_ipython.py +16 -6
  9. modal/_load_context.py +106 -0
  10. modal/_object.py +72 -21
  11. modal/_output.py +12 -14
  12. modal/_partial_function.py +31 -4
  13. modal/_resolver.py +44 -57
  14. modal/_runtime/container_io_manager.py +30 -28
  15. modal/_runtime/container_io_manager.pyi +42 -44
  16. modal/_runtime/gpu_memory_snapshot.py +9 -7
  17. modal/_runtime/user_code_event_loop.py +80 -0
  18. modal/_runtime/user_code_imports.py +236 -10
  19. modal/_serialization.py +2 -1
  20. modal/_traceback.py +4 -13
  21. modal/_tunnel.py +16 -11
  22. modal/_tunnel.pyi +25 -3
  23. modal/_utils/async_utils.py +337 -10
  24. modal/_utils/auth_token_manager.py +1 -4
  25. modal/_utils/blob_utils.py +29 -22
  26. modal/_utils/function_utils.py +20 -21
  27. modal/_utils/grpc_testing.py +6 -3
  28. modal/_utils/grpc_utils.py +223 -64
  29. modal/_utils/mount_utils.py +26 -1
  30. modal/_utils/name_utils.py +2 -3
  31. modal/_utils/package_utils.py +0 -1
  32. modal/_utils/rand_pb_testing.py +8 -1
  33. modal/_utils/task_command_router_client.py +524 -0
  34. modal/_vendor/cloudpickle.py +144 -48
  35. modal/app.py +285 -105
  36. modal/app.pyi +216 -53
  37. modal/billing.py +5 -0
  38. modal/builder/2025.06.txt +6 -3
  39. modal/builder/PREVIEW.txt +2 -1
  40. modal/builder/base-images.json +4 -2
  41. modal/cli/_download.py +19 -3
  42. modal/cli/cluster.py +4 -2
  43. modal/cli/config.py +3 -1
  44. modal/cli/container.py +5 -4
  45. modal/cli/dict.py +5 -2
  46. modal/cli/entry_point.py +26 -2
  47. modal/cli/environment.py +2 -16
  48. modal/cli/launch.py +1 -76
  49. modal/cli/network_file_system.py +5 -20
  50. modal/cli/programs/run_jupyter.py +1 -1
  51. modal/cli/programs/vscode.py +1 -1
  52. modal/cli/queues.py +5 -4
  53. modal/cli/run.py +24 -204
  54. modal/cli/secret.py +1 -2
  55. modal/cli/shell.py +375 -0
  56. modal/cli/utils.py +1 -13
  57. modal/cli/volume.py +11 -17
  58. modal/client.py +16 -125
  59. modal/client.pyi +94 -144
  60. modal/cloud_bucket_mount.py +3 -1
  61. modal/cloud_bucket_mount.pyi +4 -0
  62. modal/cls.py +101 -64
  63. modal/cls.pyi +9 -8
  64. modal/config.py +21 -1
  65. modal/container_process.py +288 -12
  66. modal/container_process.pyi +99 -38
  67. modal/dict.py +72 -33
  68. modal/dict.pyi +88 -57
  69. modal/environments.py +16 -8
  70. modal/environments.pyi +6 -2
  71. modal/exception.py +154 -16
  72. modal/experimental/__init__.py +24 -53
  73. modal/experimental/flash.py +161 -74
  74. modal/experimental/flash.pyi +97 -49
  75. modal/file_io.py +50 -92
  76. modal/file_io.pyi +117 -89
  77. modal/functions.pyi +70 -87
  78. modal/image.py +82 -47
  79. modal/image.pyi +51 -30
  80. modal/io_streams.py +500 -149
  81. modal/io_streams.pyi +279 -189
  82. modal/mount.py +60 -46
  83. modal/mount.pyi +41 -17
  84. modal/network_file_system.py +19 -11
  85. modal/network_file_system.pyi +72 -39
  86. modal/object.pyi +114 -22
  87. modal/parallel_map.py +42 -44
  88. modal/parallel_map.pyi +9 -17
  89. modal/partial_function.pyi +4 -2
  90. modal/proxy.py +14 -6
  91. modal/proxy.pyi +10 -2
  92. modal/queue.py +45 -38
  93. modal/queue.pyi +88 -52
  94. modal/runner.py +96 -96
  95. modal/runner.pyi +44 -27
  96. modal/sandbox.py +225 -107
  97. modal/sandbox.pyi +226 -60
  98. modal/secret.py +58 -56
  99. modal/secret.pyi +28 -13
  100. modal/serving.py +7 -11
  101. modal/serving.pyi +7 -8
  102. modal/snapshot.py +29 -15
  103. modal/snapshot.pyi +18 -10
  104. modal/token_flow.py +1 -1
  105. modal/token_flow.pyi +4 -6
  106. modal/volume.py +102 -55
  107. modal/volume.pyi +125 -66
  108. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/METADATA +10 -9
  109. modal-1.3.1.dev8.dist-info/RECORD +189 -0
  110. modal_proto/api.proto +141 -70
  111. modal_proto/api_grpc.py +42 -26
  112. modal_proto/api_pb2.py +1123 -1103
  113. modal_proto/api_pb2.pyi +331 -83
  114. modal_proto/api_pb2_grpc.py +80 -48
  115. modal_proto/api_pb2_grpc.pyi +26 -18
  116. modal_proto/modal_api_grpc.py +175 -174
  117. modal_proto/task_command_router.proto +164 -0
  118. modal_proto/task_command_router_grpc.py +138 -0
  119. modal_proto/task_command_router_pb2.py +180 -0
  120. modal_proto/{sandbox_router_pb2.pyi → task_command_router_pb2.pyi} +148 -57
  121. modal_proto/task_command_router_pb2_grpc.py +272 -0
  122. modal_proto/task_command_router_pb2_grpc.pyi +100 -0
  123. modal_version/__init__.py +1 -1
  124. modal_version/__main__.py +1 -1
  125. modal/cli/programs/launch_instance_ssh.py +0 -94
  126. modal/cli/programs/run_marimo.py +0 -95
  127. modal-1.1.5.dev66.dist-info/RECORD +0 -191
  128. modal_proto/modal_options_grpc.py +0 -3
  129. modal_proto/options.proto +0 -19
  130. modal_proto/options_grpc.py +0 -3
  131. modal_proto/options_pb2.py +0 -35
  132. modal_proto/options_pb2.pyi +0 -20
  133. modal_proto/options_pb2_grpc.py +0 -4
  134. modal_proto/options_pb2_grpc.pyi +0 -7
  135. modal_proto/sandbox_router.proto +0 -125
  136. modal_proto/sandbox_router_grpc.py +0 -89
  137. modal_proto/sandbox_router_pb2.py +0 -128
  138. modal_proto/sandbox_router_pb2_grpc.py +0 -169
  139. modal_proto/sandbox_router_pb2_grpc.pyi +0 -63
  140. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/WHEEL +0 -0
  141. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/entry_points.txt +0 -0
  142. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/licenses/LICENSE +0 -0
  143. {modal-1.1.5.dev66.dist-info → modal-1.3.1.dev8.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  """
2
- Vendored version of cloudpickle v3.0.0.
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(class_tracker_id, class_def)
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 it functions and classes picklable by value.
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(f"{module} was not imported correctly, have you used an " "`import` statement to access it?")
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 module_name == "__main__" or module is None or not isinstance(module, types.ModuleType):
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)[0] is obj:
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(f"cannot check importability of {type(obj).__name__} instances")
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, parent = _getattribute(module, name)
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 isinstance(x, types.ModuleType) and hasattr(x, "__package__") and x.__package__:
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
- clsdict = dict(cls.__dict__) # copy dict proxy to a dict
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(type_constructor, name, bases, type_kwargs, class_tracker_id, extra):
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
- skeleton_class = types.new_class(name, bases, {"metaclass": type_constructor}, lambda ns: ns.update(type_kwargs))
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(bases, name, qualname, members, module, class_tracker_id, extra):
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-parametrized generic class does not
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
- "__name__": func.__name__,
686
- "__qualname__": func.__qualname__,
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
- state = func.__dict__
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
- obj.co_names,
806
- obj.co_varnames,
867
+ co_names,
868
+ co_varnames,
807
869
  obj.co_filename,
808
- obj.co_name,
870
+ co_name,
809
871
  obj.co_qualname,
810
872
  obj.co_firstlineno,
811
873
  obj.co_linetable,
812
874
  obj.co_exceptiontable,
813
- obj.co_freevars,
814
- obj.co_cellvars,
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
- obj.co_names,
829
- obj.co_varnames,
890
+ co_names,
891
+ co_varnames,
830
892
  obj.co_filename,
831
- obj.co_name,
893
+ co_name,
832
894
  obj.co_firstlineno,
833
895
  obj.co_linetable,
834
- obj.co_freevars,
835
- obj.co_cellvars,
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
- obj.co_varnames,
912
+ co_varnames,
851
913
  obj.co_filename,
852
- obj.co_name,
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
- obj.co_freevars,
858
- obj.co_cellvars,
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
- obj.co_names,
874
- obj.co_varnames,
935
+ co_names,
936
+ co_varnames,
875
937
  obj.co_filename,
876
- obj.co_name,
938
+ co_name,
877
939
  obj.co_firstlineno,
878
940
  obj.co_lnotab,
879
- obj.co_freevars,
880
- obj.co_cellvars,
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("Cannot pickle files that do not map to an actual file")
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("Cannot pickle files that are not opened for reading: %s" % obj.mode)
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("Cannot pickle file %s as it cannot be read" % name) from e
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 RuntimeError as e:
1229
- if len(e.args) > 0 and "recursion" in e.args[0]:
1230
- msg = "Could not pickle object as excessively deep recursion required."
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(_builtin_type, (_BUILTIN_TYPE_NAMES[obj],), obj=obj)
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(*self._dynamic_function_reduce(obj), obj=obj)
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.