Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encoder abstract class and pickle/dill encoders #91

Open
oakaigh opened this issue Oct 8, 2023 · 0 comments
Open

Encoder abstract class and pickle/dill encoders #91

oakaigh opened this issue Oct 8, 2023 · 0 comments

Comments

@oakaigh
Copy link

oakaigh commented Oct 8, 2023

@willingc @MSeal
It would be helpful to provide a base class (interface) for encoders so that people don't get confused when trying to implement new encoders. Also I would like to have a "pickler" as one of the builtin encoders since oftentimes I need to embed open matplotlib figures for rework later; the use cases aren't limited to matplotlib figures either - its the picklable objects: users should have the freedom to save and restore any variable of their choice in interactive notebooks.

Implementation details follow

  • scrapbook_ext.py
import scrapbook as sb

import scrapbook.encoders
import scrapbook.scraps

import abc

# encoder class interface
class BaseEncoder(abc.ABC):
    def name(self):
        ...

    def encodable(self, data):
        ...

    def encode(self, scrap: sb.scraps.Scrap, **kwargs):
        ...

    def decode(self, scrap: sb.scraps.Scrap, **kwargs):
        ...

# pickle encoder
import base64
import pickle

import functools
# TODO ref https://stackoverflow.com/a/38755760
def pipeline(*funcs):
    return lambda x: functools.reduce(lambda f, g: g(f), list(funcs), x)


class PickleEncoder(BaseEncoder):
    ENCODER_NAME = 'pickle'

    def name(self):
        return self.ENCODER_NAME

    def encodable(self, data):
        # TODO
        return True

    def encode(self, scrap: sb.scraps.Scrap, **kwargs):
        _impl = pipeline(
            functools.partial(pickle.dumps, **kwargs),
            # NOTE .decode() makes sure its a UTF-8 string instead of bytes
            lambda x: base64.b64encode(x).decode()
        )
        return scrap._replace(
            data=_impl(scrap.data)
        )

    def decode(self, scrap: sb.scraps.Scrap, **kwargs):
        _impl = pipeline(
            base64.b64decode,
            functools.partial(pickle.loads, **kwargs)
        )
        return scrap._replace(data=_impl(scrap.data))


# TODO dill encoder
# NOTE dill has a function `.pickles` to check if an object is encodable. so `encodable` does not have to return `True` regardless of data like `PickleEncoder` does

def register():
    sb.encoders.registry.register(PickleEncoder())

Usage examples

  • notebook.ipynb
import scrapbook as sb
import scrapbook_ext as sb_ext
# register the encoder(s); currently required as the above implementation is a separate module
sb_ext.register()

import matplotlib.pyplot as plt

import numpy as np
import io
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)

fig, ax = plt.subplots(figsize=(5, 3.5))
ax.plot(t, s)

ax.set(xlabel='time (s)', ylabel='voltage (mV)')
ax.grid()

# glue this figure to the notebook
sb.glue("figure:test", fig)
# sb.glue("figure:test", fig, encoder='pickle')
  • another_notebook.ipynb
import matplotlib
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'

import scrapbook as sb
import scrapbook_ext as sb_ext
# register the encoder(s); currently required as the above implementation is a separate module
sb_ext.register()

nb = sb.read_notebook('notebook.ipynb')
# display the figure
nb.scraps['figure:test'].data

See also: Example project
https://github.com/oakaigh/scrapbook-ext

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant