Pickle Marshaller
*****************

  >>> from cromlech.marshallers import PickleMarshaller
  >>> marshaller = PickleMarshaller()

The `pickle` marshaller handles binary data:

  >>> marshaller.binary
  True


Straightforward dump/load
=========================

Pickle can handle most of the python native structures.

  >>> data = marshaller.dumps({'test': 'this is a test'})
  >>> data
  b'\x80\x04\x95\x1c\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04test\x94\x8c\x0ethis is a test\x94s.'

  >>> marshaller.loads(data)
  {'test': 'this is a test'}


Non-basic types
---------------

Our usual date/datetime test is trivial for Pickle :

  >>> from datetime import datetime
  >>> mydatetime = datetime(2017, 12, 19, 11, 30, 25)

  >>> structure = {
  ...    'datetime': mydatetime,
  ... }

  >>> data = marshaller.dumps(structure)
  >>> data
  b'\x80\x04\x95/\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x08datetime\x94h\x01\x8c\x08datetime\x94\x93\x94C\n\x07\xe1\x0c\x13\x0b\x1e\x19\x00\x00\x00\x94\x85\x94R\x94s.'

  >>> marshaller.loads(data)
  {'datetime': datetime.datetime(2017, 12, 19, 11, 30, 25)}


Decorator
---------

We can use the marshaller as a decorator :

  >>> @marshaller.wraps
  ... def my_renderer():
  ...     return {"three": 12.145}

  >>> my_renderer
  <function my_renderer at ...>

  >>> my_renderer()
  b'\x80\x04\x95\x15\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x05three\x94G@(J=p\xa3\xd7\ns.'

  >>> marshaller.loads(my_renderer())
  {'three': 12.145}


Files
-----

  >>> import os
  >>> path = str(getfixture('tmpdir'))
  >>> filepath = os.path.join(path, 'data.bson')

  >>> struct = {"almost_three": 3.00001}
  >>> marshaller.dump_to(struct, filepath)

The `dump_to` method is in charge of writing the data in the file.
We can load the content of this file using the `load_from` method :

  >>> marshaller.load_from(filepath)
  {'almost_three': 3.00001}

We can check manually the content of the file :

  >>> with open(filepath, 'rb') as fd:
  ...     fd.read()
  b'\x80\x04\x95\x1c\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x0calmost_three\x94G@\x08\x00\x05>-b9s.'


Streams
--------

The `dump` method is in charge writing the data in the stream.
We can interpret the content of this stream using the `load` method.
We use a bytes stream since our marshaller handles binary data.

  >>> from io import BytesIO
  >>> stream = BytesIO()

  >>> marshaller.dump(struct, stream)
  >>> stream.getvalue()
  b'\x80\x04\x95\x1c\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x0calmost_three\x94G@\x08\x00\x05>-b9s.'

We now return to the offset 0 of the file and we can load it:

  >>> assert stream.seek(0) == 0
  >>> marshaller.load(stream)
  {'almost_three': 3.00001}


Concurrency and timeout
-----------------------

Concurrency is handled via a file lock. We use the cross-os `portalocker`
package.

For our test, let's simulate a lock in place :

  >>> import portalocker
  >>> lock = portalocker.Lock(filepath)
  >>> assert lock.acquire()


When we try to write to this file, we get an `OSError` raised by our
marshaller in response of the lock timeout :

  >>> import pytest
  >>> with pytest.raises(IOError) as timeout:
  ...     marshaller.dump_to(struct, filepath, timeout=0)
  >>> timeout.value
  OSError('Resource is busy and could not be freed.')


Once we release the problematic timeout, we can write :

  >>> lock.release()
  >>> marshaller.dump_to(struct, filepath, timeout=0)


The same happens with the read :

  >>> assert lock.acquire()
  >>> with pytest.raises(IOError) as timeout:
  ...     marshaller.load_from(filepath, timeout=0)
  >>> timeout.value
  OSError('Resource is busy and could not be freed.')

  >>> lock.release()
  >>> assert marshaller.load_from(filepath, timeout=0) == struct

Please note that the read/write are tied by the same lock. You can't read
and write at the same time.
