Kaynağa Gözat

Fixed #31529 -- Added support for serialization of pathlib.Path/PurePath and os.PathLike in migrations.

Nick Pope 4 yıl önce
ebeveyn
işleme
074844e947

+ 17 - 0
django/db/migrations/serializer.py

@@ -5,6 +5,8 @@ import decimal
 import enum
 import functools
 import math
+import os
+import pathlib
 import re
 import types
 import uuid
@@ -217,6 +219,19 @@ class OperationSerializer(BaseSerializer):
         return string.rstrip(','), imports
 
 
+class PathLikeSerializer(BaseSerializer):
+    def serialize(self):
+        return repr(os.fspath(self.value)), {}
+
+
+class PathSerializer(BaseSerializer):
+    def serialize(self):
+        # Convert concrete paths to pure paths to avoid issues with migrations
+        # generated on one platform being used on a different platform.
+        prefix = 'Pure' if isinstance(self.value, pathlib.Path) else ''
+        return 'pathlib.%s%r' % (prefix, self.value), {'import pathlib'}
+
+
 class RegexSerializer(BaseSerializer):
     def serialize(self):
         regex_pattern, pattern_imports = serializer_factory(self.value.pattern).serialize()
@@ -298,6 +313,8 @@ class Serializer:
         collections.abc.Iterable: IterableSerializer,
         (COMPILED_REGEX_TYPE, RegexObject): RegexSerializer,
         uuid.UUID: UUIDSerializer,
+        pathlib.PurePath: PathSerializer,
+        os.PathLike: PathLikeSerializer,
     }
 
     @classmethod

+ 3 - 0
docs/releases/3.2.txt

@@ -200,6 +200,9 @@ Migrations
   filename fragment that will be used to name a migration containing only that
   operation.
 
+* Migrations now support serialization of pure and concrete path objects from
+  :mod:`pathlib`, and :class:`os.PathLike` instances.
+
 Models
 ~~~~~~
 

+ 10 - 0
docs/topics/migrations.txt

@@ -720,6 +720,11 @@ Django can serialize the following:
 - ``uuid.UUID`` instances
 - :func:`functools.partial` and :class:`functools.partialmethod` instances
   which have serializable ``func``, ``args``, and ``keywords`` values.
+- Pure and concrete path objects from :mod:`pathlib`. Concrete paths are
+  converted to their pure path equivalent, e.g. :class:`pathlib.PosixPath` to
+  :class:`pathlib.PurePosixPath`.
+- :class:`os.PathLike` instances, e.g. :class:`os.DirEntry`, which are
+  converted to ``str`` or ``bytes`` using :func:`os.fspath`.
 - ``LazyObject`` instances which wrap a serializable value.
 - Enumeration types (e.g. ``TextChoices`` or ``IntegerChoices``) instances.
 - Any Django field
@@ -728,6 +733,11 @@ Django can serialize the following:
 - Any class reference (must be in module's top-level scope)
 - Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
 
+.. versionchanged:: 3.2
+
+    Serialization support for pure and concrete path objects from
+    :mod:`pathlib`, and :class:`os.PathLike` instances was added.
+
 Django cannot serialize:
 
 - Nested classes

+ 41 - 0
tests/migrations/test_writer.py

@@ -4,7 +4,9 @@ import enum
 import functools
 import math
 import os
+import pathlib
 import re
+import sys
 import uuid
 from unittest import mock
 
@@ -429,6 +431,45 @@ class WriterTests(SimpleTestCase):
             "default=uuid.UUID('5c859437-d061-4847-b3f7-e6b78852f8c8'))"
         )
 
+    def test_serialize_pathlib(self):
+        # Pure path objects work in all platforms.
+        self.assertSerializedEqual(pathlib.PurePosixPath())
+        self.assertSerializedEqual(pathlib.PureWindowsPath())
+        path = pathlib.PurePosixPath('/path/file.txt')
+        expected = ("pathlib.PurePosixPath('/path/file.txt')", {'import pathlib'})
+        self.assertSerializedResultEqual(path, expected)
+        path = pathlib.PureWindowsPath('A:\\File.txt')
+        expected = ("pathlib.PureWindowsPath('A:/File.txt')", {'import pathlib'})
+        self.assertSerializedResultEqual(path, expected)
+        # Concrete path objects work on supported platforms.
+        if sys.platform == 'win32':
+            self.assertSerializedEqual(pathlib.WindowsPath.cwd())
+            path = pathlib.WindowsPath('A:\\File.txt')
+            expected = ("pathlib.PureWindowsPath('A:/File.txt')", {'import pathlib'})
+            self.assertSerializedResultEqual(path, expected)
+        else:
+            self.assertSerializedEqual(pathlib.PosixPath.cwd())
+            path = pathlib.PosixPath('/path/file.txt')
+            expected = ("pathlib.PurePosixPath('/path/file.txt')", {'import pathlib'})
+            self.assertSerializedResultEqual(path, expected)
+
+        field = models.FilePathField(path=pathlib.PurePosixPath('/home/user'))
+        string, imports = MigrationWriter.serialize(field)
+        self.assertEqual(
+            string,
+            "models.FilePathField(path=pathlib.PurePosixPath('/home/user'))",
+        )
+        self.assertIn('import pathlib', imports)
+
+    def test_serialize_path_like(self):
+        path_like = list(os.scandir(os.path.dirname(__file__)))[0]
+        expected = (repr(path_like.path), {})
+        self.assertSerializedResultEqual(path_like, expected)
+
+        field = models.FilePathField(path=path_like)
+        string = MigrationWriter.serialize(field)[0]
+        self.assertEqual(string, 'models.FilePathField(path=%r)' % path_like.path)
+
     def test_serialize_functions(self):
         with self.assertRaisesMessage(ValueError, 'Cannot serialize function: lambda'):
             self.assertSerializedEqual(lambda x: 42)