"""Picklers for working with serialized data."""

from __future__ import unicode_literals

import pickle


class DjangoCompatUnpickler(pickle.Unpickler):
    """Unpickler compatible with changes to Django class/module paths.

    This provides compatibility across Django versions for various field types,
    updating referenced module paths for fields to a standard location so
    that the fields can be located on all Django versions.
    """

    def find_class(self, module, name):
        """Return the class for a given module and class name.

        If looking up a class from ``django.db.models.fields``, the class will
        instead be looked up from ``django.db.models``, fixing lookups on
        some Django versions.

        Args:
            module (unicode):
                The module path.

            name (unicode):
                The class name.

        Returns:
            type:
            The resulting class.

        Raises:
            AttributeError:
                The class could not be found in the module.
        """
        if module == 'django.db.models.fields':
            module = 'django.db.models'

        return super(DjangoCompatUnpickler, self).find_class(module, name)

"""Picklers for working with serialized data."""

from __future__ import unicode_literals

import pickle

from django_evolution.compat.datastructures import OrderedDict


class SortedDict(dict):
    """Compatibility for unpickling a SortedDict.

    Old signatures may use an old Django ``SortedDict`` structure, which does
    not exist in modern versions. This changes any construction of this
    data structure into a :py:class:`collections.OrderedDict`.
    """

    def __new__(cls, *args, **kwargs):
        """Construct an instance of the class.

        Args:
            *args (tuple):
                Positional arguments to pass to the constructor.

            **kwargs (dict):
                Keyword arguments to pass to the constructor.

        Returns:
            collections.OrderedDict:
            The new instance.
        """
        return OrderedDict.__new__(cls, *args, **kwargs)


class DjangoCompatUnpickler(pickle._Unpickler):
    """Unpickler compatible with changes to Django class/module paths.

    This provides compatibility across Django versions for various field types,
    updating referenced module paths for fields to a standard location so
    that the fields can be located on all Django versions.
    """

    def find_class(self, module, name):
        """Return the class for a given module and class name.

        If looking up a class from ``django.db.models.fields``, the class will
        instead be looked up from ``django.db.models``, fixing lookups on
        some Django versions.

        Args:
            module (unicode):
                The module path.

            name (unicode):
                The class name.

        Returns:
            type:
            The resulting class.

        Raises:
            AttributeError:
                The class could not be found in the module.
        """
        if module == 'django.utils.datastructures' and name == 'SortedDict':
            return SortedDict
        elif module == 'django.db.models.fields':
            module = 'django.db.models'

        return super(DjangoCompatUnpickler, self).find_class(module, name)

Support loading pickled data referencing SortedDict.
Support loading pickled data referencing SortedDict.

Django used to provide a class called `SortedDict`, which has long been
deprecated in favor of Python's own `OrderedDict`. However, due to the
way that pickling works, older signatures would still attempt to loading
a `SortedDict` class.

This change adds a compatibility mechanism for this. Upon finding an
attempt to load a `SortedDict`, we instead give it a forwarding object
that's compatible with the unpickle code that constructs a new
`OrderedDict`. It's a bit hacky, in that we need to have this class that
subclasses `dict`, overrides `__new__`, and then returns an entirely
different object, but it's a necessity for the compatibility.

Testing Done:
Successfully loaded an older signature on Python 3.7/Django 1.11.

Reviewed at https://reviews.reviewboard.org/r/10557/

  """Picklers for working with serialized data."""
  
  from __future__ import unicode_literals
  
  import pickle
  
+ from django_evolution.compat.datastructures import OrderedDict
  
+ 
+ class SortedDict(dict):
+     """Compatibility for unpickling a SortedDict.
+ 
+     Old signatures may use an old Django ``SortedDict`` structure, which does
+     not exist in modern versions. This changes any construction of this
+     data structure into a :py:class:`collections.OrderedDict`.
+     """
+ 
+     def __new__(cls, *args, **kwargs):
+         """Construct an instance of the class.
+ 
+         Args:
+             *args (tuple):
+                 Positional arguments to pass to the constructor.
+ 
+             **kwargs (dict):
+                 Keyword arguments to pass to the constructor.
+ 
+         Returns:
+             collections.OrderedDict:
+             The new instance.
+         """
+         return OrderedDict.__new__(cls, *args, **kwargs)
+ 
+ 
- class DjangoCompatUnpickler(pickle.Unpickler):
+ class DjangoCompatUnpickler(pickle._Unpickler):
      """Unpickler compatible with changes to Django class/module paths.
  
      This provides compatibility across Django versions for various field types,
      updating referenced module paths for fields to a standard location so
      that the fields can be located on all Django versions.
      """
  
      def find_class(self, module, name):
          """Return the class for a given module and class name.
  
          If looking up a class from ``django.db.models.fields``, the class will
          instead be looked up from ``django.db.models``, fixing lookups on
          some Django versions.
  
          Args:
              module (unicode):
                  The module path.
  
              name (unicode):
                  The class name.
  
          Returns:
              type:
              The resulting class.
  
          Raises:
              AttributeError:
                  The class could not be found in the module.
          """
+         if module == 'django.utils.datastructures' and name == 'SortedDict':
+             return SortedDict
-         if module == 'django.db.models.fields':
+         elif module == 'django.db.models.fields':
              module = 'django.db.models'
  
          return super(DjangoCompatUnpickler, self).find_class(module, name)
  
Support loading pickled data referencing SortedDict.
## Code Before:
"""Picklers for working with serialized data."""

from __future__ import unicode_literals

import pickle


class DjangoCompatUnpickler(pickle.Unpickler):
    """Unpickler compatible with changes to Django class/module paths.

    This provides compatibility across Django versions for various field types,
    updating referenced module paths for fields to a standard location so
    that the fields can be located on all Django versions.
    """

    def find_class(self, module, name):
        """Return the class for a given module and class name.

        If looking up a class from ``django.db.models.fields``, the class will
        instead be looked up from ``django.db.models``, fixing lookups on
        some Django versions.

        Args:
            module (unicode):
                The module path.

            name (unicode):
                The class name.

        Returns:
            type:
            The resulting class.

        Raises:
            AttributeError:
                The class could not be found in the module.
        """
        if module == 'django.db.models.fields':
            module = 'django.db.models'

        return super(DjangoCompatUnpickler, self).find_class(module, name)

## Instruction:
Support loading pickled data referencing SortedDict.
## Code After:
"""Picklers for working with serialized data."""

from __future__ import unicode_literals

import pickle

from django_evolution.compat.datastructures import OrderedDict


class SortedDict(dict):
    """Compatibility for unpickling a SortedDict.

    Old signatures may use an old Django ``SortedDict`` structure, which does
    not exist in modern versions. This changes any construction of this
    data structure into a :py:class:`collections.OrderedDict`.
    """

    def __new__(cls, *args, **kwargs):
        """Construct an instance of the class.

        Args:
            *args (tuple):
                Positional arguments to pass to the constructor.

            **kwargs (dict):
                Keyword arguments to pass to the constructor.

        Returns:
            collections.OrderedDict:
            The new instance.
        """
        return OrderedDict.__new__(cls, *args, **kwargs)


class DjangoCompatUnpickler(pickle._Unpickler):
    """Unpickler compatible with changes to Django class/module paths.

    This provides compatibility across Django versions for various field types,
    updating referenced module paths for fields to a standard location so
    that the fields can be located on all Django versions.
    """

    def find_class(self, module, name):
        """Return the class for a given module and class name.

        If looking up a class from ``django.db.models.fields``, the class will
        instead be looked up from ``django.db.models``, fixing lookups on
        some Django versions.

        Args:
            module (unicode):
                The module path.

            name (unicode):
                The class name.

        Returns:
            type:
            The resulting class.

        Raises:
            AttributeError:
                The class could not be found in the module.
        """
        if module == 'django.utils.datastructures' and name == 'SortedDict':
            return SortedDict
        elif module == 'django.db.models.fields':
            module = 'django.db.models'

        return super(DjangoCompatUnpickler, self).find_class(module, name)
