Open saxbophone opened 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
Other things to consider:
I've been doing some more thinking about this and here are some of my thoughts:
EncoderTemplate
class, which all custom encoders should inherit from to produce a generic encoder template for that class, for instance:
Base64EncoderTemplate
which inherits from EncoderTemplate
and sets some class variables defining how base64 is encodedBase64EncoderTemplate
as required for their needs (perhaps these need to be created manually or could be done via class methods provided in EncoderTemplate
), for example we might then from this class, have subclasses Base64StreamEncoder
, Base64Encoder
or Base64RawEncoder
.EncoderTemplate().encoder_pre_process()
, EncoderTemplate().encoder_post_process()
, EncoderTemplate().decoder_pre_process()
, etc...)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).
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.
Base classes needed:
Overrideable, customisable and hookable attributes and methods required in these classes:
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
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).
This needs tidying up, there's two or three routes I can go down:
@classmethod
StreamEncoder
base class and aMappingEncoder
mixin class. This would probably require deprecating the functional interface entirely.