-
Notifications
You must be signed in to change notification settings - Fork 88
/
abc.py
127 lines (100 loc) · 4.4 KB
/
abc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""This module defines the :class:`Codec` base class, a common interface for
all codec classes.
Codec classes must implement :func:`Codec.encode` and :func:`Codec.decode`
methods. Inputs to and outputs from these methods may be any Python object
exporting a contiguous buffer via the new-style Python protocol.
Codec classes must implement a :func:`Codec.get_config` method,
which must return a dictionary holding all configuration parameters
required to enable encoding and decoding of data. The expectation is that
these configuration parameters will be stored or communicated separately
from encoded data, and thus the codecs do not need to store all encoding
parameters within the encoded data. For broad compatibility,
the configuration object must contain only JSON-serializable values. The
configuration object must also contain an 'id' field storing the codec
identifier (see below).
Codec classes must implement a :func:`Codec.from_config` class method,
which will return an instance of the class initialized from a configuration
object.
Finally, codec classes must set a `codec_id` class-level attribute. This
must be a string. Two different codec classes may set the same value for the
`codec_id` attribute if and only if they are fully compatible, meaning that
(1) configuration parameters are the same, and (2) given the same
configuration, one class could correctly decode data encoded by the
other and vice versa.
"""
from abc import ABC, abstractmethod
from typing import Optional
class Codec(ABC):
"""Codec abstract base class."""
# override in sub-class
codec_id: Optional[str] = None
"""Codec identifier."""
@abstractmethod
def encode(self, buf): # pragma: no cover
"""Encode data in `buf`.
Parameters
----------
buf : buffer-like
Data to be encoded. May be any object supporting the new-style
buffer protocol.
Returns
-------
enc : buffer-like
Encoded data. May be any object supporting the new-style buffer
protocol.
"""
@abstractmethod
def decode(self, buf, out=None): # pragma: no cover
"""Decode data in `buf`.
Parameters
----------
buf : buffer-like
Encoded data. May be any object supporting the new-style buffer
protocol.
out : buffer-like, optional
Writeable buffer to store decoded data. N.B. if provided, this buffer must
be exactly the right size to store the decoded data.
Returns
-------
dec : buffer-like
Decoded data. May be any object supporting the new-style
buffer protocol.
"""
def get_config(self):
"""Return a dictionary holding configuration parameters for this
codec. Must include an 'id' field with the codec identifier. All
values must be compatible with JSON encoding."""
# override in sub-class if need special encoding of config values
# setup config object
config = {'id': self.codec_id}
# by default, assume all non-private members are configuration
# parameters - override this in sub-class if not the case
for k in self.__dict__:
if not k.startswith('_'):
config[k] = getattr(self, k)
return config
@classmethod
def from_config(cls, config):
"""Instantiate codec from a configuration object."""
# N.B., assume at this point the 'id' field has been removed from
# the config object
# override in sub-class if need special decoding of config values
# by default, assume constructor accepts configuration parameters as
# keyword arguments without any special decoding
return cls(**config)
def __eq__(self, other):
# override in sub-class if need special equality comparison
try:
return self.get_config() == other.get_config()
except AttributeError:
return False
def __repr__(self):
# override in sub-class if need special representation
# by default, assume all non-private members are configuration
# parameters and valid keyword arguments to constructor function
r = f'{type(self).__name__}('
params = [
f'{k}={getattr(self, k)!r}' for k in sorted(self.__dict__) if not k.startswith('_')
]
r += ', '.join(params) + ')'
return r