Skip to content

Commit

Permalink
Merge pull request #1491 from pelson/deepcopy_fix
Browse files Browse the repository at this point in the history
Fix pickle/deepcopy for non-default argument CRSs
  • Loading branch information
QuLogic authored Mar 26, 2020
2 parents e7fab0a + 1696405 commit 136a047
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 57 deletions.
42 changes: 23 additions & 19 deletions lib/cartopy/_crs.pyx
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
# (C) British Crown Copyright 2011 - 2019, Met Office
# Copyright Cartopy Contributors
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see <https://www.gnu.org/licenses/>.
# This file is part of Cartopy and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
#
# cython: embedsignature=True

Expand Down Expand Up @@ -269,23 +258,38 @@ cdef class CRS:
instance of this class (e.g. an empty tuple). The state will then be
added via __getstate__ and __setstate__.
We are forced to this approach because a CRS does not store
the constructor keyword arguments in its state.
"""
return self.__class__, tuple()
return self.__class__, (), self.__getstate__()

def __getstate__(self):
"""Return the full state of this instance for reconstruction
in ``__setstate__``.
"""
return {'proj4_params': self.proj4_params}
state = self.__dict__.copy()
# Remove the proj4 instance and the proj4_init string, which can
# be re-created (in __setstate__) from the other arguments.
state.pop('proj4', None)
state.pop('proj4_init', None)
state['proj4_params'] = self.proj4_params
return state

def __setstate__(self, state):
"""
Take the dictionary created by ``__getstate__`` and passes it
through to the class's __init__ method.
through to this implementation's __init__ method.
"""
self.__init__(self, **state)
# Strip out the key state items for a CRS instance.
CRS_state = {key: state.pop(key) for key in ['proj4_params', 'globe']}
# Put everything else directly into the dict of the instance.
self.__dict__.update(state)
# Call the init of this class to ensure that the projection is
# properly initialised with proj4.
CRS.__init__(self, **CRS_state)

# TODO
#def __str__
Expand Down
47 changes: 28 additions & 19 deletions lib/cartopy/tests/test_crs.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
# (C) British Crown Copyright 2011 - 2019, Met Office
# Copyright Cartopy Contributors
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see <https://www.gnu.org/licenses/>.
# This file is part of Cartopy and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.

from __future__ import (absolute_import, division, print_function)

import copy
from io import BytesIO
import pickle

Expand Down Expand Up @@ -236,13 +226,32 @@ def test_utm(self):
decimal=1)


def test_pickle():
@pytest.fixture(params=[
[ccrs.PlateCarree, {}],
[ccrs.PlateCarree, dict(
central_longitude=1.23)],
[ccrs.NorthPolarStereo, dict(
central_longitude=42.5,
globe=ccrs.Globe(ellipse="helmert"))],
])
def proj_to_copy(request):
cls, kwargs = request.param
return cls(**kwargs)


def test_pickle(proj_to_copy):
# check that we can pickle a simple CRS
fh = BytesIO()
pickle.dump(ccrs.PlateCarree(), fh)
pickle.dump(proj_to_copy, fh)
fh.seek(0)
pc = pickle.load(fh)
assert pc == ccrs.PlateCarree()
pickled_prj = pickle.load(fh)
assert proj_to_copy == pickled_prj


def test_deepcopy(proj_to_copy):
prj_cp = copy.deepcopy(proj_to_copy)
assert proj_to_copy.proj4_params == prj_cp.proj4_params
assert proj_to_copy == prj_cp


def test_PlateCarree_shortcut():
Expand Down
25 changes: 6 additions & 19 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
# (C) British Crown Copyright 2011 - 2020, Met Office
# Copyright Cartopy Contributors
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see <https://www.gnu.org/licenses/>.
# This file is part of Cartopy and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.

from __future__ import print_function

import fnmatch
Expand All @@ -34,9 +24,6 @@
"""




# The existence of a PKG-INFO directory is enough to tell us whether this is a
# source installation or not (sdist).
HERE = os.path.dirname(__file__)
Expand Down Expand Up @@ -328,7 +315,7 @@ def get_proj_libraries():
else:
extras_require[section].append(line.strip())
install_requires = extras_require.pop('default')
tests_require = extras_require.pop('tests', [])
tests_require = extras_require.get('tests', [])

# General extension paths
if sys.platform.startswith('win'):
Expand Down

0 comments on commit 136a047

Please sign in to comment.