Repository: joblib/joblib
Pull request #820 (2018-12-11 13:08:25)

Add check_call_in_cache method to check cache without calling function (as per note in the code). Add some tests.

codecov[bot]: # [Codecov](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=h1) Report
> Merging [#820](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=desc) into [master](https://codecov.io/gh/joblib/joblib/commit/7be116a5a6bd4b304c1b0be66bc44fc84cfb0267?src=pr&el=desc) will **decrease** coverage by `0.06%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/joblib/joblib/pull/820/graphs/tree.svg?width=650&token=gA6LF5DGTW&height=150&src=pr)](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #820      +/-   ##
==========================================
- Coverage   95.35%   95.28%   -0.07%     
==========================================
  Files          44       44              
  Lines        6307     6320      +13     
==========================================
+ Hits         6014     6022       +8     
- Misses        293      298       +5
```


| [Impacted Files](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [joblib/test/test\_memory.py](https://codecov.io/gh/joblib/joblib/pull/820/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9tZW1vcnkucHk=) | `98.14% <100%> (+0.02%)` | :arrow_up: |
| [joblib/memory.py](https://codecov.io/gh/joblib/joblib/pull/820/diff?src=pr&el=tree#diff-am9ibGliL21lbW9yeS5weQ==) | `95.92% <100%> (+0.3%)` | :arrow_up: |
| [joblib/\_parallel\_backends.py](https://codecov.io/gh/joblib/joblib/pull/820/diff?src=pr&el=tree#diff-am9ibGliL19wYXJhbGxlbF9iYWNrZW5kcy5weQ==) | `93.6% <0%> (-2.41%)` | :arrow_down: |
| [joblib/test/test\_parallel.py](https://codecov.io/gh/joblib/joblib/pull/820/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9wYXJhbGxlbC5weQ==) | `96.71% <0%> (-0.15%)` | :arrow_down: |
| [joblib/logger.py](https://codecov.io/gh/joblib/joblib/pull/820/diff?src=pr&el=tree#diff-am9ibGliL2xvZ2dlci5weQ==) | `88.15% <0%> (+1.31%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=footer). Last update [7be116a...2f4e6ff](https://codecov.io/gh/joblib/joblib/pull/820?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

cottrell: Code coverage is showing changes in parallel_backends and logger for some reason. No changes there.
cottrell: Just pushed the wrong branch in case someone was about to do something here ... fixing in one sec.

cottrell: > Just pushed the wrong branch in case someone was about to do something here ... fixing in one sec.

Squashed again to one commit. And import removal had crept in and the test had not picked it up. I think the risks are now small as it is only one screen of new lines to view in the single commit.
aabadie: It seems to me that this PR is addressing the same problem as #730. Did you have a look at it first ?
cottrell: > It seems to me that this PR is addressing the same problem as #730. Did you have a look at it first ?

nope. 20 line diff. can't even remember this. Whoever is managing this repo just chuck whatever is easiest. Nobody getting paid here for this so no hard feelings :)  as long and the functionality gets in. 
tomMoral: This PR seems to have stalled. I think this would be useful. 
It seems that the only missing parts are an entry in the changelog and a mention in the doc. @cottrell are you available to help getting this merged?
cottrell: @tomMoral just saw this ... I've added something to the doc and the changelog. I'm assuming the pipeline does the auto gen part of the doc stuff.

Rebased on master.
cottrell: Not sure about those test failures, likely the master I rebased on. I don't think those failures happened before the rebase.

danlkv: What happened? Why this is not merged?
tomMoral: I did not notice the update from @cottrell , sorry!
Indeed, we should merge this one. I will rebase on master and try to get this merge asap.

tomMoral: @aabadie @ogrisel I think this feature is helpful and the proposed solution is nice.
Not so sure about the naming.





Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
deleted file mode 100644
index 7668179..0000000
--- a/.circleci/config.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-version: 2
-
-jobs:
-  build:
-    docker:
-      - image: circleci/python:3.6.2
-    steps:
-      - checkout
-      - run:
-          command: ./continuous_integration/build_doc.sh
-          environment:
-            MINICONDA_PATH: ~/miniconda
-            CONDA_ENV_NAME: testenv
-      - store_artifacts:
-          path: doc/_build/html
-          destination: doc
diff --git a/CHANGES.rst b/CHANGES.rst
index 752c57f..c1e8c52 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,12 +1,29 @@
 Latest changes
 ==============
 
-In development
---------------
+Development version
+-------------------
+
+- Fix byte order inconsistency issue during deserialization using joblib.load
+  in cross-endian environment: the numpy arrays are now always loaded to
+  use the system byte order, independently of the byte order of the system
+  that serialized the pickle.
+  https://github.com/joblib/joblib/pull/1181
+
+- Fix joblib.Memory bug with the ``ignore`` parameter when the cached function
+  is a decorated function.
+  https://github.com/joblib/joblib/pull/1165
+
+1.0.1
+-----
 
+- Add check_call_in_cache method to check cache without calling function.
+  https://github.com/joblib/joblib/pull/820
+ 
 - dask: avoid redundant scattering of large arguments to make a more
-  efficient use of the network resources and avoid
-  "OSError: [Errno 55] No buffer space available" errors on macOS.
+  efficient use of the network resources and avoid crashing dask with
+  "OSError: [Errno 55] No buffer space available"
+  or "ConnectionResetError: [Errno 104] connection reset by peer".
   https://github.com/joblib/joblib/pull/1133
 
 1.0.0
@@ -328,8 +345,8 @@ Thomas Moreau
 
     Implement the ``'loky'`` backend with @ogrisel. This backend relies on
     a robust implementation of ``concurrent.futures.ProcessPoolExecutor``
-    with spawned processes that can be reused accross the ``Parallel``
-    calls. This fixes the bad interation with third paty libraries relying on
+    with spawned processes that can be reused across the ``Parallel``
+    calls. This fixes the bad integration with third paty libraries relying on
     thread pools, described in https://pythonhosted.org/joblib/parallel.html#bad-interaction-of-multiprocessing-and-third-party-libraries
 
     Limit the number of threads used in worker processes by C-libraries that
@@ -389,7 +406,7 @@ Alexandre Abadie
 
     Add ``register_compressor`` function for extending available compressors.
 
-    Allow passing a string to ``compress`` parameter in ``dump`` funtion. This
+    Allow passing a string to ``compress`` parameter in ``dump`` function. This
     string should correspond to the compressor used (e.g. zlib, gzip, lz4,
     etc). The default compression level is used in this case.
 
@@ -439,7 +456,7 @@ Loïc Estève
 Loïc Estève
 
     Fix handling of memmap objects with offsets greater than
-    mmap.ALLOCATIONGRANULARITY in ``joblib.Parrallel``. See
+    mmap.ALLOCATIONGRANULARITY in ``joblib.Parallel``. See
     https://github.com/joblib/joblib/issues/451 for more details.
 
 Loïc Estève
@@ -1038,7 +1055,7 @@ Release 0.5.3
 2011-06-25
 Gael varoquaux
 
-   API: All the usefull symbols in the __init__
+   API: All the useful symbols in the __init__
 
 
 Release 0.5.2
diff --git a/README.rst b/README.rst
index f1aac98..d659dbd 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,4 @@
-|PyPi| |Azure| |Codecov|
+|PyPi| |Azure| |ReadTheDocs| |Codecov| 
 
 .. |PyPi| image:: https://badge.fury.io/py/joblib.svg
    :target: https://badge.fury.io/py/joblib
@@ -6,7 +6,11 @@
 
 .. |Azure| image:: https://dev.azure.com/joblib/joblib/_apis/build/status/joblib.joblib?branchName=master
    :target: https://dev.azure.com/joblib/joblib/_build?definitionId=3&_a=summary&branchFilter=40
-   :alt: Codecov coverage
+   :alt: Azure CI status
+
+.. |ReadTheDocs| image:: https://readthedocs.org/projects/pip/badge/?version=latest
+   :target: https://joblib.readthedocs.io/en/latest/
+   :alt: Doc status
 
 .. |Codecov| image:: https://codecov.io/gh/joblib/joblib/branch/master/graph/badge.svg
    :target: https://codecov.io/gh/joblib/joblib
diff --git a/continuous_integration/build_doc.sh b/continuous_integration/build_doc.sh
deleted file mode 100755
index 0c1e6e2..0000000
--- a/continuous_integration/build_doc.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env bash
-set -x
-set -e
-
-# deactivate circleci virtualenv and setup a miniconda env instead
-if [[ `type -t deactivate` ]]; then
-    deactivate
-fi
-
-wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh \
-     -O miniconda.sh
-chmod +x miniconda.sh && ./miniconda.sh -b -p $MINICONDA_PATH
-export PATH="$MINICONDA_PATH/bin:$PATH"
-conda update --yes --quiet conda
-
-conda create -n $CONDA_ENV_NAME --yes --quiet python=3
-source activate $CONDA_ENV_NAME
-
-conda install --yes --quiet pip numpy sphinx matplotlib pillow dask distributed
-pip install sphinx-gallery numpydoc lz4
-
-python setup.py develop
-
-make doc 2>&1
diff --git a/doc/conf.py b/doc/conf.py
index fa9e928..05e3aa1 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -253,7 +253,7 @@ try:
     shutil.copyfile('../README.rst', 'README.rst')
 except IOError:
     pass
-    # This fails during the tesing, as the code is ran in a different
+    # This fails during the testing, as the code is ran in a different
     # directory
 
 numpydoc_show_class_members = False
diff --git a/doc/memory.rst b/doc/memory.rst
index 0b9fbae..0cc701a 100644
--- a/doc/memory.rst
+++ b/doc/memory.rst
@@ -427,7 +427,7 @@ objects that, in addition of behaving like normal functions, expose
 methods useful for cache exploration and management.
 
 .. autoclass:: MemorizedFunc
-    :members: __init__, call, clear
+    :members: __init__, call, clear, check_call_in_cache
 
 
 ..
diff --git a/examples/serialization_and_wrappers.py b/examples/serialization_and_wrappers.py
index 8e7ccf9..4e9967d 100644
--- a/examples/serialization_and_wrappers.py
+++ b/examples/serialization_and_wrappers.py
@@ -78,7 +78,7 @@ if sys.platform != 'win32':
 # POSIX specification and can have bad interaction with compiled extensions
 # that use ``openmp``. Also, it is not possible to start processes with
 # ``fork`` on windows where only ``spawn`` is available. The ``loky`` backend
-# has been developped to mitigate these issues.
+# has been developed to mitigate these issues.
 #
 # To have fast pickling with ``loky``, it is possible to rely on ``pickle`` to
 # serialize all communications between the main process and the workers with
diff --git a/joblib/_dask.py b/joblib/_dask.py
index 009ddc6..e759b7f 100644
--- a/joblib/_dask.py
+++ b/joblib/_dask.py
@@ -51,7 +51,7 @@ class _WeakKeyDictionary:
     such as large numpy arrays or pandas dataframes that are not hashable and
     therefore cannot be used as keys of traditional python dicts.
 
-    Futhermore using a dict with id(array) as key is not safe because the
+    Furthermore using a dict with id(array) as key is not safe because the
     Python is likely to reuse id of recently collected arrays.
     """
 
diff --git a/joblib/externals/loky/backend/queues.py b/joblib/externals/loky/backend/queues.py
index 62735db..5671464 100644
--- a/joblib/externals/loky/backend/queues.py
+++ b/joblib/externals/loky/backend/queues.py
@@ -209,7 +209,7 @@ class SimpleQueue(mp_SimpleQueue):
             else:
                 self._wlock = ctx.Lock()
 
-        # Add possiblity to use custom reducers
+        # Add possibility to use custom reducers
         self._reducers = reducers
 
     def close(self):
diff --git a/joblib/externals/loky/backend/resource_tracker.py b/joblib/externals/loky/backend/resource_tracker.py
index 95dff35..48e49be 100644
--- a/joblib/externals/loky/backend/resource_tracker.py
+++ b/joblib/externals/loky/backend/resource_tracker.py
@@ -36,7 +36,7 @@
 # Note that this behavior differs from CPython's resource_tracker, which only
 # implements list of shared resources, and not a proper refcounting scheme.
 # Also, CPython's resource tracker will only attempt to cleanup those shared
-# resources once all procsses connected to the resouce tracker have exited.
+# resources once all procsses connected to the resource tracker have exited.
 
 
 import os
diff --git a/joblib/externals/loky/backend/spawn.py b/joblib/externals/loky/backend/spawn.py
index 2a16c84..d4a6a8c 100644
--- a/joblib/externals/loky/backend/spawn.py
+++ b/joblib/externals/loky/backend/spawn.py
@@ -105,7 +105,7 @@ def get_preparation_data(name, init_main_module=True):
             _resource_tracker as mp_resource_tracker
         )
         # multiprocessing's resource_tracker must be running before loky
-        # process is created (othewise the child won't be able to use it if it
+        # process is created (otherwise the child won't be able to use it if it
         # is created later on)
         mp_resource_tracker.ensure_running()
         d["mp_tracker_args"] = {
diff --git a/joblib/externals/loky/backend/utils.py b/joblib/externals/loky/backend/utils.py
index dc1b82a..5342204 100644
--- a/joblib/externals/loky/backend/utils.py
+++ b/joblib/externals/loky/backend/utils.py
@@ -117,7 +117,7 @@ def _recursive_terminate(pid):
 
 
 def get_exitcodes_terminated_worker(processes):
-    """Return a formated string with the exitcodes of terminated workers.
+    """Return a formatted string with the exitcodes of terminated workers.
 
     If necessary, wait (up to .25s) for the system to correctly set the
     exitcode of one terminated worker.
diff --git a/joblib/func_inspect.py b/joblib/func_inspect.py
index ec6bb4a..3751347 100644
--- a/joblib/func_inspect.py
+++ b/joblib/func_inspect.py
@@ -171,10 +171,9 @@ def get_func_name(func, resolv_alias=True, win_characters=True):
     return module, name
 
 
-def _signature_str(function_name, arg_spec):
+def _signature_str(function_name, arg_sig):
     """Helper function to output a function signature"""
-    arg_spec_str = inspect.formatargspec(*arg_spec)
-    return '{}{}'.format(function_name, arg_spec_str)
+    return '{}{}'.format(function_name, arg_sig)
 
 
 def _function_called_str(function_name, args, kwargs):
@@ -221,20 +220,34 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
             warnings.warn('Cannot inspect object %s, ignore list will '
                           'not work.' % func, stacklevel=2)
         return {'*': args, '**': kwargs}
-    arg_spec = inspect.getfullargspec(func)
-    arg_names = arg_spec.args + arg_spec.kwonlyargs
-    arg_defaults = arg_spec.defaults or ()
-    if arg_spec.kwonlydefaults:
-        arg_defaults = arg_defaults + tuple(arg_spec.kwonlydefaults[k]
-                                            for k in arg_spec.kwonlyargs
-                                            if k in arg_spec.kwonlydefaults)
-    arg_varargs = arg_spec.varargs
-    arg_varkw = arg_spec.varkw
-
+    arg_sig = inspect.signature(func)
+    arg_names = []
+    arg_defaults = []
+    arg_kwonlyargs = []
+    arg_varargs = None
+    arg_varkw = None
+    for param in arg_sig.parameters.values():
+        if param.kind is param.POSITIONAL_OR_KEYWORD:
+            arg_names.append(param.name)
+        elif param.kind is param.KEYWORD_ONLY:
+            arg_names.append(param.name)
+            arg_kwonlyargs.append(param.name)
+        elif param.kind is param.VAR_POSITIONAL:
+            arg_varargs = param.name
+        elif param.kind is param.VAR_KEYWORD:
+            arg_varkw = param.name
+        if param.default is not param.empty:
+            arg_defaults.append(param.default)
     if inspect.ismethod(func):
         # First argument is 'self', it has been removed by Python
         # we need to add it back:
         args = [func.__self__, ] + args
+        # func is an instance method, inspect.signature(func) does not
+        # include self, we need to fetch it from the class method, i.e
+        # func.__func__
+        class_method_sig = inspect.signature(func.__func__)
+        self_name = next(iter(class_method_sig.parameters))
+        arg_names = [self_name] + arg_names
     # XXX: Maybe I need an inspect.isbuiltin to detect C-level methods, such
     # as on ndarrays.
 
@@ -244,7 +257,7 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
     for arg_position, arg_name in enumerate(arg_names):
         if arg_position < len(args):
             # Positional argument or keyword argument given as positional
-            if arg_name not in arg_spec.kwonlyargs:
+            if arg_name not in arg_kwonlyargs:
                 arg_dict[arg_name] = args[arg_position]
             else:
                 raise ValueError(
@@ -252,7 +265,7 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
                     'positional parameter for %s:\n'
                     '     %s was called.'
                     % (arg_name,
-                       _signature_str(name, arg_spec),
+                       _signature_str(name, arg_sig),
                        _function_called_str(name, args, kwargs))
                 )
 
@@ -268,7 +281,7 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
                     raise ValueError(
                         'Wrong number of arguments for %s:\n'
                         '     %s was called.'
-                        % (_signature_str(name, arg_spec),
+                        % (_signature_str(name, arg_sig),
                            _function_called_str(name, args, kwargs))
                     ) from e
 
@@ -296,7 +309,7 @@ def filter_args(func, ignore_lst, args=(), kwargs=dict()):
             raise ValueError("Ignore list: argument '%s' is not defined for "
                              "function %s"
                              % (item,
-                                _signature_str(name, arg_spec))
+                                _signature_str(name, arg_sig))
                              )
     # XXX: Return a sorted list of pairs?
     return arg_dict
diff --git a/joblib/memory.py b/joblib/memory.py
index 424d9fe..b660f14 100644
--- a/joblib/memory.py
+++ b/joblib/memory.py
@@ -19,7 +19,6 @@ import functools
 import traceback
 import warnings
 import inspect
-import sys
 import weakref
 
 from tokenize import open as open_py_source
@@ -33,7 +32,6 @@ from .logger import Logger, format_time, pformat
 from ._store_backends import StoreBackendBase, FileSystemStoreBackend
 
 
-
 FIRST_LINE_TEXT = "# first line:"
 
 # TODO: The following object should have a data store object as a sub
@@ -136,7 +134,6 @@ def _store_backend_factory(backend, location, verbose=0, backend_options=None):
             "supported by joblib. Returning None instead.".format(
                 location.__class__.__name__), UserWarning)
 
-
     return None
 
 
@@ -361,6 +358,12 @@ class NotMemorizedFunc(object):
         # Argument "warn" is for compatibility with MemorizedFunc.clear
         pass
 
+    def call(self, *args, **kwargs):
+        return self.func(*args, **kwargs)
+
+    def check_call_in_cache(self, *args, **kwargs):
+        return False
+
 
 ###############################################################################
 # class `MemorizedFunc`
@@ -606,6 +609,21 @@ class MemorizedFunc(Logger):
 
         return state
 
+    def check_call_in_cache(self, *args, **kwargs):
+        """Check if function call is in the memory cache.
+
+        Does not call the function or do any work besides func inspection
+        and arg hashing.
+
+        Returns
+        -------
+        is_call_in_cache: bool
+            Whether or not the result of the function has been cached
+            for the input arguments that have been passed.
+        """
+        func_id, args_id = self._get_output_identifiers(*args, **kwargs)
+        return self.store_backend.contains_item((func_id, args_id))
+
     # ------------------------------------------------------------------------
     # Private interface
     # ------------------------------------------------------------------------
@@ -683,8 +701,8 @@ class MemorizedFunc(Logger):
                 extract_first_line(
                     self.store_backend.get_cached_func_code([func_id]))
         except (IOError, OSError):  # some backend can also raise OSError
-                self._write_func_code(func_code, first_line)
-                return False
+            self._write_func_code(func_code, first_line)
+            return False
         if old_func_code == func_code:
             return True
 
@@ -821,8 +839,6 @@ class MemorizedFunc(Logger):
                           % this_duration, stacklevel=5)
         return metadata
 
-    # XXX: Need a method to check if results are available.
-
     # ------------------------------------------------------------------------
     # Private `object` interface
     # ------------------------------------------------------------------------
@@ -848,7 +864,7 @@ class Memory(Logger):
 
         Parameters
         ----------
-        location: str or None
+        location: str, pathlib.Path or None
             The path of the base directory to use as a data store
             or None. If None is given, no caching is done and
             the Memory object is completely transparent. This option
@@ -882,7 +898,12 @@ class Memory(Logger):
             as functions are evaluated.
 
         bytes_limit: int, optional
-            Limit in bytes of the size of the cache.
+            Limit in bytes of the size of the cache. By default, the size of
+            the cache is unlimited. When reducing the size of the cache,
+            ``joblib`` keeps the most recently accessed items first.
+
+            **Note:** You need to call :meth:`joblib.Memory.reduce_size` to
+            actually reduce the cache size to be less than ``bytes_limit``.
 
         backend_options: dict, optional
             Contains a dictionnary of named parameters used to configure
diff --git a/joblib/numpy_pickle.py b/joblib/numpy_pickle.py
index 93e5537..cc593af 100644
--- a/joblib/numpy_pickle.py
+++ b/joblib/numpy_pickle.py
@@ -20,6 +20,7 @@ from .compressor import (ZlibCompressorWrapper, GzipCompressorWrapper,
 from .numpy_pickle_utils import Unpickler, Pickler
 from .numpy_pickle_utils import _read_fileobject, _write_fileobject
 from .numpy_pickle_utils import _read_bytes, BUFFER_SIZE
+from .numpy_pickle_utils import _ensure_native_byte_order
 from .numpy_pickle_compat import load_compatibility
 from .numpy_pickle_compat import NDArrayWrapper
 # For compatibility with old versions of joblib, we need ZNDArrayWrapper
@@ -147,7 +148,8 @@ class NumpyArrayWrapper(object):
             else:
                 array.shape = self.shape
 
-        return array
+        # Detect byte order mis-match and swap as needed.
+        return _ensure_native_byte_order(array)
 
     def read_mmap(self, unpickler):
         """Read an array using numpy memmap."""
diff --git a/joblib/numpy_pickle_compat.py b/joblib/numpy_pickle_compat.py
index 6541a06..096acbc 100644
--- a/joblib/numpy_pickle_compat.py
+++ b/joblib/numpy_pickle_compat.py
@@ -9,7 +9,7 @@ from io import BytesIO
 
 from .numpy_pickle_utils import _ZFILE_PREFIX
 from .numpy_pickle_utils import Unpickler
-
+from .numpy_pickle_utils import _ensure_native_byte_order
 
 def hex_str(an_int):
     """Convert an int to an hexadecimal string."""
@@ -105,6 +105,9 @@ class NDArrayWrapper(object):
             kwargs["allow_pickle"] = True
         array = unpickler.np.load(filename, **kwargs)
 
+        # Detect byte order mis-match and swap as needed.
+        array = _ensure_native_byte_order(array)
+
         # Reconstruct subclasses. This does not work with old
         # versions of numpy
         if (hasattr(array, '__array_prepare__') and
diff --git a/joblib/numpy_pickle_utils.py b/joblib/numpy_pickle_utils.py
index a501055..02a88ff 100644
--- a/joblib/numpy_pickle_utils.py
+++ b/joblib/numpy_pickle_utils.py
@@ -6,6 +6,7 @@
 
 import pickle
 import io
+import sys
 import warnings
 import contextlib
 
@@ -48,6 +49,30 @@ def _get_prefixes_max_len():
     return max(prefixes)
 
 
+def _is_numpy_array_byte_order_mismatch(array):
+    """Check if numpy array is having byte order mis-match"""
+    return ((sys.byteorder == 'big' and
+             (array.dtype.byteorder == '<' or
+              (array.dtype.byteorder == '|' and array.dtype.fields and
+               all(e[0].byteorder == '<'
+                   for e in array.dtype.fields.values())))) or
+            (sys.byteorder == 'little' and
+             (array.dtype.byteorder == '>' or
+              (array.dtype.byteorder == '|' and array.dtype.fields and
+               all(e[0].byteorder == '>'
+                   for e in array.dtype.fields.values())))))
+
+
+def _ensure_native_byte_order(array):
+    """Use the byte order of the host while preserving values
+
+    Does nothing if array already uses the system byte order.
+    """
+    if _is_numpy_array_byte_order_mismatch(array):
+        array = array.byteswap().newbyteorder('=')
+    return array
+
+
 ###############################################################################
 # Cache file utilities
 def _detect_compressor(fileobj):


Test patch:
diff --git a/joblib/test/test_hashing.py b/joblib/test/test_hashing.py
index 37d9480..5682d8f 100644
--- a/joblib/test/test_hashing.py
+++ b/joblib/test/test_hashing.py
@@ -260,8 +260,8 @@ def test_numpy_scalar():
 
 
 def test_dict_hash(tmpdir):
-    # Check that dictionaries hash consistently, eventhough the ordering
-    # of the keys is not garanteed
+    # Check that dictionaries hash consistently, even though the ordering
+    # of the keys is not guaranteed
     k = KlassWithCachedMethod(tmpdir.strpath)
 
     d = {'#s12069__c_maps.nii.gz': [33],
diff --git a/joblib/test/test_memory.py b/joblib/test/test_memory.py
index ad0ddf4..6f74966 100644
--- a/joblib/test/test_memory.py
+++ b/joblib/test/test_memory.py
@@ -6,6 +6,7 @@ Test the memory module.
 # Copyright (c) 2009 Gael Varoquaux
 # License: BSD Style, 3 clauses.
 
+import functools
 import gc
 import shutil
 import os
@@ -364,7 +365,7 @@ def test_memory_eval(tmpdir):
 def count_and_append(x=[]):
     """ A function with a side effect in its arguments.
 
-        Return the lenght of its argument and append one element.
+        Return the length of its argument and append one element.
     """
     len_x = len(x)
     x.append(None)
@@ -488,6 +489,32 @@ def test_memory_ignore(tmpdir):
     assert len(accumulator) == 1
 
 
+def test_memory_ignore_decorated(tmpdir):
+    " Test the ignore feature of memory on a decorated function "
+    memory = Memory(location=tmpdir.strpath, verbose=0)
+    accumulator = list()
+
+    def decorate(f):
+        @functools.wraps(f)
+        def wrapped(*args, **kwargs):
+            return f(*args, **kwargs)
+        return wrapped
+
+    @memory.cache(ignore=['y'])
+    @decorate
+    def z(x, y=1):
+        accumulator.append(1)
+
+    assert z.ignore == ['y']
+
+    z(0, y=1)
+    assert len(accumulator) == 1
+    z(0, y=1)
+    assert len(accumulator) == 1
+    z(0, y=2)
+    assert len(accumulator) == 1
+
+
 def test_memory_args_as_kwargs(tmpdir):
     """Non-regression test against 0.12.0 changes.
 
@@ -582,6 +609,19 @@ def test_persistence(tmpdir):
     gp(1)
 
 
+def test_check_call_in_cache(tmpdir):
+    for func in (MemorizedFunc(f, tmpdir.strpath),
+                 Memory(location=tmpdir.strpath, verbose=0).cache(f)):
+        result = func.check_call_in_cache(2)
+        assert not result
+        assert isinstance(result, bool)
+        assert func(2) == 5
+        result = func.check_call_in_cache(2)
+        assert result
+        assert isinstance(result, bool)
+        func.clear()
+
+
 def test_call_and_shelve(tmpdir):
     # Test MemorizedFunc outputting a reference to cache.
 
@@ -1130,7 +1170,7 @@ def test_register_invalid_store_backends_object():
 
 
 def test_memory_default_store_backend():
-    # test an unknow backend falls back into a FileSystemStoreBackend
+    # test an unknown backend falls back into a FileSystemStoreBackend
     with raises(TypeError) as excinfo:
         Memory(location='/tmp/joblib', backend='unknown')
     excinfo.match(r"Unknown location*")
diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py
index db130b1..7cdd90f 100644
--- a/joblib/test/test_numpy_pickle.py
+++ b/joblib/test/test_numpy_pickle.py
@@ -5,6 +5,7 @@ import os
 import random
 import re
 import io
+import sys
 import warnings
 import gzip
 import zlib
@@ -30,6 +31,8 @@ from joblib.test import data
 
 from joblib.numpy_pickle_utils import _IO_BUFFER_SIZE
 from joblib.numpy_pickle_utils import _detect_compressor
+from joblib.numpy_pickle_utils import _is_numpy_array_byte_order_mismatch
+from joblib.numpy_pickle_utils import _ensure_native_byte_order
 from joblib.compressor import (_COMPRESSORS, _LZ4_PREFIX, CompressorWrapper,
                                LZ4_NOT_INSTALLED_ERROR, BinaryZlibFile)
 
@@ -355,6 +358,7 @@ def test_compressed_pickle_dump_and_load(tmpdir):
     result_list = numpy_pickle.load(fname)
     for result, expected in zip(result_list, expected_list):
         if isinstance(expected, np.ndarray):
+            expected = _ensure_native_byte_order(expected)
             assert result.dtype == expected.dtype
             np.testing.assert_equal(result, expected)
         else:
@@ -394,6 +398,7 @@ def _check_pickle(filename, expected_list):
                         "pickle file.".format(filename))
             for result, expected in zip(result_list, expected_list):
                 if isinstance(expected, np.ndarray):
+                    expected = _ensure_native_byte_order(expected)
                     assert result.dtype == expected.dtype
                     np.testing.assert_equal(result, expected)
                 else:
@@ -457,6 +462,47 @@ def test_joblib_pickle_across_python_versions():
         _check_pickle(fname, expected_list)
 
 
+@with_numpy
+def test_numpy_array_byte_order_mismatch_detection():
+    # List of numpy arrays with big endian byteorder.
+    be_arrays = [np.array([(1, 2.0), (3, 4.0)],
+                          dtype=[('', '>i8'), ('', '>f8')]),
+                 np.arange(3, dtype=np.dtype('>i8')),
+                 np.arange(3, dtype=np.dtype('>f8'))]
+
+    # Verify the byteorder mismatch is correctly detected.
+    for array in be_arrays:
+        if sys.byteorder == 'big':
+            assert not _is_numpy_array_byte_order_mismatch(array)
+        else:
+            assert _is_numpy_array_byte_order_mismatch(array)
+        converted = _ensure_native_byte_order(array)
+        if converted.dtype.fields:
+            for f in converted.dtype.fields.values():
+                f[0].byteorder == '='
+        else:
+            assert converted.dtype.byteorder == "="
+
+    # List of numpy arrays with little endian byteorder.
+    le_arrays = [np.array([(1, 2.0), (3, 4.0)],
+                          dtype=[('', '<i8'), ('', '<f8')]),
+                 np.arange(3, dtype=np.dtype('<i8')),
+                 np.arange(3, dtype=np.dtype('<f8'))]
+
+    # Verify the byteorder mismatch is correctly detected.
+    for array in le_arrays:
+        if sys.byteorder == 'little':
+            assert not _is_numpy_array_byte_order_mismatch(array)
+        else:
+            assert _is_numpy_array_byte_order_mismatch(array)
+        converted = _ensure_native_byte_order(array)
+        if converted.dtype.fields:
+            for f in converted.dtype.fields.values():
+                f[0].byteorder == '='
+        else:
+            assert converted.dtype.byteorder == "="
+
+
 @parametrize('compress_tuple', [('zlib', 3), ('gzip', 3)])
 def test_compress_tuple_argument(tmpdir, compress_tuple):
     # Verify the tuple is correctly taken into account.
diff --git a/joblib/test/test_parallel.py b/joblib/test/test_parallel.py
index 2458ed9..7edeb85 100644
--- a/joblib/test/test_parallel.py
+++ b/joblib/test/test_parallel.py
@@ -1347,7 +1347,7 @@ def test_invalid_backend_hinting_and_constraints():
         # requiring shared memory semantics.
         Parallel(prefer='processes', require='sharedmem')
 
-    # It is inconsistent to ask explictly for a process-based parallelism
+    # It is inconsistent to ask explicitly for a process-based parallelism
     # while requiring shared memory semantics.
     with raises(ValueError):
         Parallel(backend='loky', require='sharedmem')
@@ -1563,7 +1563,7 @@ def _parent_max_num_threads_for(child_module, parent_info):
 
 def check_child_num_threads(workers_info, parent_info, num_threads):
     # Check that the number of threads reported in workers_info is consistent
-    # with the expectation. We need to be carefull to handle the cases where
+    # with the expectation. We need to be careful to handle the cases where
     # the requested number of threads is below max_num_thread for the library.
     for child_threadpool_info in workers_info:
         for child_module in child_threadpool_info: