saxbophone / basest-python

Arbitrary base binary-to-text encoder (any base to any base), in Python.
https://pypi.org/project/basest/
Mozilla Public License 2.0
6 stars 0 forks source link

Revise the class-based interface #27

Open saxbophone opened 6 years ago

saxbophone commented 6 years ago

This needs tidying up, there's two or three routes I can go down:

saxbophone commented 6 years ago

Illustrations of how certain approaches might look to the user:

class Base64EncoderBase(BaseEnccoder):
    input_base = 256
    output_base = 64
    input_ratio = 3
    output_ratio = 4

class Base64MappingMixin(MappingEncoderMixin, Base64EncoderBase):
    input_mapping = [chr(x) for x in range(256)]
    output_mapping = [char(x) for x in range(0x21, 0x21 + 64)]
    padding_character = '='

class Base64StreamingEncoder(Base64MappingMixin, StreamEncoder):
    pass

class Base64Encoder(Base64MappingMixin, Encoder):
    pass
saxbophone commented 6 years ago

Other things to consider:

saxbophone commented 5 years ago

I've been doing some more thinking about this and here are some of my thoughts:

saxbophone commented 5 years ago

I am wanton to deprecate the functional interface of the library if, once I begin implementing my new ideas it becomes easier to simply integrate the functionality into the class-based one.

However, if the functions are left in, they will not be deprecated. Any deprecation, if needed, should be done by v1.0.0 (breaking change).

saxbophone commented 5 years ago

Another thought: make all class methods @classmethod —encoder classes don't need to be instantiated really, as they don't need any state. Inheritance is useful for layering up their behaviour.

saxbophone commented 5 years ago

Base classes needed:

Overrideable, customisable and hookable attributes and methods required in these classes:

saxbophone commented 5 years ago

Example:

class RawStreamingEncoder(object):
    """
    Base class for generator-based encoders which operate on raw ints
    """
    @classmethod
    def encode(cls, data):
        # NOTE: should be implemented in real life!
        # this method will be a generator which implements most of the
        # encoding work, yielding raw ints in the output base
        raise NotImplementedError()

    @classmethod
    def decode(cls, data):
        # NOTE: should be implemented in real life!
        # this method will be a generator which implements most of the
        # decoding work, yielding raw ints in the input base
        raise NotImplementedError()

class MappedStreamingEncoder(RawStreamingEncoder):
    """
    Base class for generator-based encoders which operate on mapped symbols
    """
    @classmethod
    def encode(cls, data):
        # wrap generator with another generator, one which maps the symbols
        for symbol in super(MappedStreamingEncoder, cls).encode(data):
            yield super(MappedStreamingEncoder, cls).encoding_mapping_operation(symbol)

    @classmethod
    def decode(cls, data):
        # wrap generator with another generator, one which maps the symbols
        for symbol in super(MappedStreamingEncoder, cls).decode(data):
            yield super(MappedStreamingEncoder, cls).decoding_mapping_operation(symbol)

class RawEncoder(RawStreamingEncoder):
    """
    Base class for encoders which return a list of raw ints
    """
    @classmethod
    def encode(cls, data):
        # convert generator into list
        return list(super(Encoder, cls).encode(data))

    @classmethod
    def decode(cls, data):
        # convert generator into list
        return list(super(Encoder, cls).decode(data))

class Encoder(MappedStreamingEncoder, RawEncoder):
    """
    Base class for encoders which return a list of symbols
    """
    pass

class TypedEncoder(Encoder):
    """
    Base classs for encoders which return symbols coerced to a custom type
    (for example, outputting a string rather than a list of bytes)
    """
    @classmethod
    def encode(cls, data):
        return super(TypedEncoder, cls).coerce_input(data)

    @classmethod
    def decode(cls, data):
        return super(TypedEncoder, cls).coerce_output(data)

class Base64RawStreamingEncoder(RawStreamingEncoder):
    input_base = 256
    output_base = 64
    encoding_ratio = (3, 4,)
    pass

class Base64StreamingEncoder(Base64RawStreamingEncoder, MappedStreamingEncoder):
    input_alphabet = list(range(256))
    output_alphabet = list(
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    )
    padding = '='

class Base64Encoder(Base64StreamingEncoder, Encoder):
    pass
saxbophone commented 5 years ago

Maybe we need EncoderTemplate classes too, as the above can be a bit unwieldy if one wants to instantiate multiple different combinations of encoder types from one template (e.g. both mapped and un-mapped, generator and non-generator base64).