Skip to content

Commit

Permalink
Fixed #31529 -- Added support for serialization of pathlib.Path/PureP…
Browse files Browse the repository at this point in the history
…ath and os.PathLike in migrations.
  • Loading branch information
ngnpope authored and felixxm committed Jun 24, 2020
1 parent 162765d commit 074844e
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 0 deletions.
17 changes: 17 additions & 0 deletions django/db/migrations/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import enum
import functools
import math
import os
import pathlib
import re
import types
import uuid
Expand Down Expand Up @@ -217,6 +219,19 @@ def serialize(self):
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()
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/releases/3.2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~

Expand Down
10 changes: 10 additions & 0 deletions docs/topics/migrations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
41 changes: 41 additions & 0 deletions tests/migrations/test_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import functools
import math
import os
import pathlib
import re
import sys
import uuid
from unittest import mock

Expand Down Expand Up @@ -429,6 +431,45 @@ def test_serialize_uuid(self):
"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)
Expand Down

0 comments on commit 074844e

Please sign in to comment.