diff --git a/bryl/__init__.py b/bryl/__init__.py index 3c408ad..20f7322 100644 --- a/bryl/__init__.py +++ b/bryl/__init__.py @@ -45,45 +45,41 @@ class MyRecord(bryl.Record): """ __all__ = [ - 'ctx', - 'Field', - 'Numeric', - 'Alphanumeric', - 'Date', - 'Datetime', - 'Time', - 'Record', - 'Reader', + "ctx", + "Field", + "Numeric", + "Alphanumeric", + "Date", + "Datetime", + "Time", + "Record", + "Reader", ] -__version__ = '0.1.0' +__version__ = "0.2.0" import collections import copy import datetime import inspect +import io import itertools import os import re import string -import StringIO import threading class Context(threading.local): - class _Frame(dict): - def __getattr__(self, key): if key in self: return self[key] raise AttributeError( - '"{0}" object has no attribute "{1}"' - .format(type(self).__name__, key) + '"{0}" object has no attribute "{1}"'.format(type(self).__name__, key) ) class _Close(object): - def __init__(self, func): self.func = func @@ -94,12 +90,11 @@ def __exit__(self, type, value, traceback): self.func() def __init__(self, **defaults): - self.stack = [self._Frame( - alpha_filter=False, - alpha_truncate=False, - alpha_upper=False, - **defaults - )] + self.stack = [ + self._Frame( + alpha_filter=False, alpha_truncate=False, alpha_upper=False, **defaults + ) + ] def push(self, **kwargs): self.stack.append(self._Frame(**kwargs)) @@ -117,8 +112,7 @@ def __getattr__(self, key): if key in frame: return frame[key] raise AttributeError( - '"{0}" object has no attribute "{1}"' - .format(type(self).__name__, key) + '"{0}" object has no attribute "{1}"'.format(type(self).__name__, key) ) @@ -126,52 +120,47 @@ def __getattr__(self, key): class Field(object): - - LEFT = 'left' - RIGHT = 'right' + LEFT = "left" + RIGHT = "right" _order = itertools.count() - pad = '' - + pad = "" align = None - offset = None - default = None - pattern = None ctx = ctx - error_type = ValueError copy = [ - 'length', - 'required', - 'pad', - 'align', - ('order', '_order'), - 'name', - ('constant', '_constant'), - ('enum', '_enum'), - 'offset', - 'default', + "length", + "required", + "pad", + "align", + ("order", "_order"), + "name", + ("constant", "_constant"), + ("enum", "_enum"), + "offset", + "default", ] - def __init__(self, - length, - required=True, - pad=None, - align=None, - order=None, - name=None, - constant=None, - enum=None, - default=None, - offset=None, - ): - self._order = self._order.next() if order is None else order + def __init__( + self, + length, + required=True, + pad=None, + align=None, + order=None, + name=None, + constant=None, + enum=None, + default=None, + offset=None, + ): + self._order = next(self._order) if order is None else order self.name = name self.length = length self.required = required and (default is None) @@ -192,7 +181,7 @@ def __init__(self, self._enum = enum self.enum = None if self._enum: - for k, v in self._enum.iteritems(): + for k, v in self._enum.items(): setattr(self, k, v) self.enum = self._enum.values() self.offset = offset @@ -200,8 +189,7 @@ def __init__(self, def reserved(self): if type(self).default is None: raise TypeError( - '{0} does not have a default and so cannot be reserved' - .format(self) + "{0} does not have a default and so cannot be reserved".format(self) ) other = copy.copy(self) other._constant = type(self).default @@ -213,8 +201,8 @@ def constant(self, value): error = self.validate(value) if error: raise self.error_type( - 'Invalid {0}.constant({1}) - {2}'.format(self, value, error) - ) + "Invalid {0}.constant({1}) - {2}".format(self, value, error) + ) other._constant = value other.default = other._constant return other @@ -228,8 +216,7 @@ def __get__(self, record, record_type=None): if self.default is not None: return self.default raise LookupError( - '{0}.{1} value is missing' - .format(type(record).__name__, self.name) + "{0}.{1} value is missing".format(type(record).__name__, self.name) ) value = record[self.name] if value is None: @@ -240,32 +227,32 @@ def __set__(self, record, value): new_value = self.map(record, value) if self._constant is not None: if self._constant != value: - raise TypeError( - '{0} is constant and cannot be modified'.format(self) - ) + raise TypeError("{0} is constant and cannot be modified".format(self)) return record[self.name] = new_value def __copy__(self): kwargs = {} for k in self.copy: - if isinstance(k, basestring): + if isinstance(k, str): k = (k, k) dst, src = k kwargs[dst] = getattr(self, src) return type(self)(**kwargs) def __repr__(self, *args, **kwargs): - attrs = ', '.join([ - '{0}={1}'.format(k, repr(getattr(self, k))) - for k in ['name', 'length', 'required', 'default', 'pad', 'align'] - ]) - return '{0}({1})'.format(type(self).__name__, attrs) + attrs = ", ".join( + [ + "{0}={1}".format(k, repr(getattr(self, k))) + for k in ["name", "length", "required", "default", "pad", "align"] + ] + ) + return "{0}({1})".format(type(self).__name__, attrs) @property def value(self): if self._constant is None: - raise TypeError('Non-constant fields do not have a value') + raise TypeError("Non-constant fields do not have a value") return self._constant def fill(self, record, value): @@ -274,7 +261,7 @@ def fill(self, record, value): return record[self.name] = new_value - def map(self, record, value): + def map(self, record, value): # noqa: C901 if value is not None: try: value = self.sanitize(value) @@ -295,8 +282,9 @@ def map(self, record, value): pass if error: raise self.error_type( - 'Invalid {0}.{1} value {2} for - {3}' - .format(type(record).__name__, self.name, value, error) + "Invalid {0}.{1} value {2} for - {3}".format( + type(record).__name__, self.name, value, error + ) ) return value @@ -312,20 +300,17 @@ def error(self, value, description): def pack(self, value): error = self.validate(value) if error: - if isinstance(value, str): - value = value.decode('utf-8') raise self.error_type( - u'Invalid {0} value {1} for - {2}'.format(self, value, error) + "Invalid {0} value {1} for - {2}".format(self, value, error) ) value = self.dump(value) + value = str(value) if self.align == self.LEFT: - value = value + self.pad * (self.length - len(value)) + value = value + str(self.pad) * (self.length - len(value)) elif self.align == self.RIGHT: - value = (self.pad * (self.length - len(value))) + value + value = (str(self.pad) * (self.length - len(value))) + value else: value = self.pad * (self.length - len(value)) + value - if isinstance(value, unicode): - value = value.encode('ascii') return value def dump(self, value): @@ -333,8 +318,8 @@ def dump(self, value): def unpack(self, raw): if len(raw) < self.length: - raise self.error_type('Length must be >= {0}'.format(self.length)) - raw = raw[:self.length] + raise self.error_type("Length must be >= {0}".format(self.length)) + raw = raw[: self.length] if self.align == self.LEFT: value = raw.rstrip(self.pad) elif self.align == self.RIGHT: @@ -344,16 +329,16 @@ def unpack(self, raw): if self.pattern and not re.match(self.pattern, value): raise self.error_type( '"{0}" does not match pattern "{1}"'.format(value, self.pattern) - ) + ) try: value = self.load(value) - except self.error_type, ex: + except self.error_type as ex: value = ex if isinstance(value, Exception): - raise self.error_type('{0} - {1}'.format(self, self.error(raw, value))) + raise self.error_type("{0} - {1}".format(self, self.error(raw, value))) error = self.validate(value) if error: - raise self.error_type('{0} {1} - {2}'.format(self, value, error)) + raise self.error_type("{0} {1} - {2}".format(self, value, error)) return value def load(self, value): @@ -361,7 +346,7 @@ def load(self, value): def probe(self, io): if self.offset is None: - raise TypeError('{0}.offset is None'.format(self)) + raise TypeError("{0}.offset is None".format(self)) restore = io.tell() try: io.seek(self.offset, os.SEEK_CUR) @@ -375,76 +360,77 @@ def probe(self, io): class Numeric(Field): - pad = '0' + pad = "0" align = Field.RIGHT default = 0 min_value = 0 max_value = None copy = Field.copy + [ - 'min_value', - 'min_value', - ] + "min_value", + "min_value", + ] - def __init__(self, *args, **kwargs): - self.min_value = kwargs.pop('min_value', self.min_value) - self.max_value = kwargs.pop('max_value', self.max_value) + def __init__(self, *args, **kwargs): + self.min_value = kwargs.pop("min_value", self.min_value) + self.max_value = kwargs.pop("max_value", self.max_value) super(Numeric, self).__init__(*args, **kwargs) def load(self, raw): - if not raw or raw.strip() is '': - raw = '0' + if not raw or raw.strip() == "": + raw = "0" return int(raw) def dump(self, value): return str(value) def validate(self, value): - if isinstance(value, basestring) and re.match('\d+', value): + if isinstance(value, str) and re.match(r"\d+", value): value = int(value) - if not isinstance(value, (int, long)): - return self.error(value, 'must be a whole number') + if not isinstance(value, int): + return self.error(value, "must be a whole number") if self.enum and value not in self.enum: - return self.error(value, 'must be one of {0}, got "{1}"'.format( - self.enum, value)) + return self.error( + value, 'must be one of {0}, got "{1}"'.format(self.enum, value) + ) if len(str(value)) > self.length: - return self.error(value, 'must have length <= {0}'.format(self.length)) + return self.error(value, "must have length <= {0}".format(self.length)) if self.min_value is not None and self.min_value > value: - return self.error(value, 'must be >= {0}'.format(self.min_value)) + return self.error(value, "must be >= {0}".format(self.min_value)) if self.max_value is not None and self.max_value < value: - return self.error(value, 'must be <= {0}'.format(self.min_value)) + return self.error(value, "must be <= {0}".format(self.min_value)) if self._constant is not None and value != self._constant: - return self.error(value, 'must be constant {0}'.format(repr(self._constant))) + return self.error( + value, "must be constant {0}".format(repr(self._constant)) + ) class Alphanumeric(Field): - pad = ' ' + pad = " " align = Field.LEFT alphabet = string.printable - default = '' + default = "" def sanitize(self, value): v = value if self.ctx.alpha_filter: - v = ''.join(c for c in value if c in self.alphabet) + v = "".join(c for c in value if c in self.alphabet) if self.ctx.alpha_truncate: if len(v) > self.length: - v = v[:self.length] + v = v[: self.length] if self.ctx.alpha_upper: v = v.upper() return v def validate(self, value): - if not isinstance(value, basestring): - return self.error(value, 'must be a string') + if not isinstance(value, str): + return self.error(value, "must be a string") if self.enum and value not in self.enum: return self.error( value, 'must be one of {0}, got "{1}"'.format(self.enum, value) ) if len(value) > self.length: - return self.error( - value, 'must have length <= {0}'.format(self.length) - ) + return self.error(value, "must have length <= {0}".format(self.length)) for i, c in enumerate(value): if c not in self.alphabet: if c not in string.printable: @@ -459,34 +445,31 @@ class Datetime(Field): default = None format_re = re.compile( - 'Y{4}|Y{2}|D{3}|D{2}|M{2}|' # day - 'h{2}|H{2}|m{2}|s{2}|X{2}|Z{3}|p{2}' # time + "Y{4}|Y{2}|D{3}|D{2}|M{2}|" "h{2}|H{2}|m{2}|s{2}|X{2}|Z{3}|p{2}" # day # time ) format_spec = { # day - 'YYYY': '%Y', - 'YY': '%y', - 'DDD': '%j', - 'DD': '%d', - 'MM': '%m', - 'JJJ': '%j', - + "YYYY": "%Y", + "YY": "%y", + "DDD": "%j", + "DD": "%d", + "MM": "%m", + "JJJ": "%j", # time - 'hh': '%H', # 24 hr - 'HH': '%I', # 12 hr - 'mm': '%M', - 'ss': '%S', - 'pp': '%p', # http://stackoverflow.com/a/1759485 - 'ZZZ': '%Z', # http://stackoverflow.com/a/14763274 - } + "hh": "%H", # 24 hr + "HH": "%I", # 12 hr + "mm": "%M", + "ss": "%S", + "pp": "%p", # http://stackoverflow.com/a/1759485 + "ZZZ": "%Z", # http://stackoverflow.com/a/14763274 + } - copy = [k for k in Field.copy if k != 'length'] + ['format'] + copy = [k for k in Field.copy if k != "length"] + ["format"] - time_zones = { - } + time_zones = {} - def __init__(self, format, *args, **kwargs): + def __init__(self, format, *args, **kwargs): self.format = format super(Datetime, self).__init__(len(self.format), *args, **kwargs) self._str_format, self._tz = self._to_str_format(format) @@ -498,38 +481,36 @@ def _to_str_format(cls, format): prev = 0 for m in cls.format_re.finditer(format): if prev != m.start(): - parts.append(format[prev:m.start()]) + parts.append(format[prev : m.start()]) prev = m.end() value = m.group() - if value == 'ZZZ': + if value == "ZZZ": tz = m.start(), m.end() - m.start() continue spec = cls.format_spec[value] parts.append(spec) if prev != len(format): parts.append(format[prev:]) - return ''.join(parts), tz + return "".join(parts), tz @classmethod def _extract_tz(cls, raw, spec): offset, length = spec - tz = raw[offset:offset + length] - raw = raw[:offset] + raw[offset + length:] + tz = raw[offset : offset + length] + raw = raw[:offset] + raw[offset + length :] return raw, tz @classmethod def _insert_tz(cls, raw, tz, spec): offset, length = spec if len(tz) != length: - raise ValueError( - 'Timezone "{0}" length != {1}'.format(tz, length) - ) + raise ValueError('Timezone "{0}" length != {1}'.format(tz, length)) raw = raw[:offset] + tz + raw[offset:] return raw def validate(self, value): if not isinstance(value, datetime.datetime): - return self.error(value, 'must be a datetime') + return self.error(value, "must be a datetime") def load(self, raw): tz = None @@ -537,8 +518,9 @@ def load(self, raw): raw, tz = self._extract_tz(raw, self._tz) if tz not in self.time_zones: raise ValueError( - 'Unsupported time-zone "{0}", expected one of {1}' - .format(tz, self.time_zones.keys()) + 'Unsupported time-zone "{0}", expected one of {1}'.format( + tz, self.time_zones.keys() + ) ) tz = self.time_zones[tz] value = datetime.datetime.strptime(raw, self._str_format) @@ -555,13 +537,11 @@ def dump(self, value): class Date(Datetime): - format_re = re.compile( - 'Y{4}|Y{2}|D{3}|D{2}|M{2}|J{3}' # day - ) + format_re = re.compile("Y{4}|Y{2}|D{3}|D{2}|M{2}|J{3}") # day def validate(self, value): if not isinstance(value, datetime.date): - return self.error(value, 'must be a date') + return self.error(value, "must be a date") def load(self, raw): return datetime.datetime.strptime(raw, self._str_format).date() @@ -572,20 +552,17 @@ def dump(self, value): class Time(Datetime): - format_re = re.compile( - 'h{2}|H{2}|m{2}|s{2}|X{2}|Z{3}|p{2}' # time - ) + format_re = re.compile("h{2}|H{2}|m{2}|s{2}|X{2}|Z{3}|p{2}") # time def validate(self, value): if not isinstance(value, datetime.time): - return self.error(value, 'must be a time') + return self.error(value, "must be a time") def load(self, raw): return super(Time, self).load(raw).time() class RecordMeta(type): - def __new__(mcs, name, bases, dikt): cls = type.__new__(mcs, name, bases, dikt) @@ -595,7 +572,7 @@ def __new__(mcs, name, bases, dikt): attr.name = name # cache fields - is_field = lambda x: ( + is_field = lambda x: ( # noqa: E731 inspect.isdatadescriptor(x) and isinstance(x, Field) ) cls.fields = [ @@ -604,8 +581,9 @@ def __new__(mcs, name, bases, dikt): ] for field in cls.fields: for base in bases: - if (not hasattr(base, field.name) or - not isinstance(getattr(base, field.name), Field)): + if not hasattr(base, field.name) or not isinstance( + getattr(base, field.name), Field + ): continue field._order = getattr(base, field.name)._order cls.fields = sorted(cls.fields, key=lambda x: x._order) @@ -620,35 +598,35 @@ def __new__(mcs, name, bases, dikt): cls.length = sum(field.length for field in cls.fields) # cache default field values - cls._defaults = dict([ - (field.name, field.default) - for field in cls.fields if field.default is not None - ]) + cls._defaults = dict( + [ + (field.name, field.default) + for field in cls.fields + if field.default is not None + ] + ) return cls -class Record(dict): - - __metaclass__ = RecordMeta - +class Record(dict, metaclass=RecordMeta): field_type = Field def __init__(self, **kwargs): values = copy.copy(self._defaults) values.update(kwargs) - for k, v in values.iteritems(): + for k, v in values.items(): field = getattr(type(self), k, None) if not field or not isinstance(field, self.field_type): raise ValueError( - '{0} does not have field {1}'.format(type(self).__name__, k) + "{0} does not have field {1}".format(type(self).__name__, k) ) field.fill(self, v) @classmethod def probe(cls, io): - if isinstance(io, basestring): - io = StringIO.StringIO(io) + if isinstance(io, str): + io = io.StringIO(io) restore = io.tell() try: try: @@ -664,15 +642,14 @@ def load(cls, raw): for f in cls.fields: value = f.unpack(raw) values[f.name] = value - raw = raw[f.length:] + raw = raw[f.length :] return cls(**values) def dump(self): - return ''.join([f.pack(f.__get__(self)) for f in self.fields]) + return "".join([f.pack(f.__get__(self)) for f in self.fields]) class Malformed(ValueError): - def __init__(self, file_name, offset, reason): super(Malformed, self).__init__( "{0} @ {1} - {2}".format(file_name, offset, reason) @@ -706,13 +683,13 @@ def as_record_type(reader, data, offset): """ self.fo = fo - self.name = getattr(self.fo, 'name', '') + self.name = getattr(self.fo, "name", "") self.as_record_type = as_record_type or self.as_record_type if self.as_record_type is None: - raise TypeError('Must define as_record_type=') + raise TypeError("Must define as_record_type=") self.retry = None - def next_record(self, expected_type=None, default='raise'): + def next_record(self, expected_type=None, default="raise"): raise NotImplementedError def malformed(self, offset, reason): @@ -742,12 +719,13 @@ def as_record_type(reader, data, offset): """ - def __init__(self, - fo, - as_record_type=None, - include_terminal=False, - expected_terminal=None, - ): + def __init__( + self, + fo, + as_record_type=None, + include_terminal=False, + expected_terminal=None, + ): super(LineReader, self).__init__(fo, as_record_type) self.line_no = 1 self.include_terminal = include_terminal @@ -755,25 +733,25 @@ def __init__(self, # Reader - def next_record(self, expected_type=None, default='raise'): + def next_record(self, expected_type=None, default="raise"): line, line_no = self.next_line() if line is None: - if default == 'raise': - self.malformed(line_no, 'unexpected EOF') + if default == "raise": + self.malformed(line_no, "unexpected EOF") return default try: record = self.as_record(line, line_no) - except Malformed, ex: + except Malformed: self.retry = line, line_no raise - except self.record_type.field_type.error_type, ex: + except self.record_type.field_type.error_type as ex: self.retry = line, line_no self.malformed(line_no, str(ex)) if not isinstance(record, (expected_type or self.record_type)): self.retry = line, line_no - if default == 'raise': + if default == "raise": self.malformed( - line_no, 'unexpected record type {0}'.format(type(record)) + line_no, "unexpected record type {0}".format(type(record)) ) return return record @@ -786,16 +764,16 @@ def next(self): raise StopIteration() try: record = self.as_record(line, line_no) - except self.record_type.field_type.error_type, ex: + except self.record_type.field_type.error_type as ex: raise self.malformed(line_no, str(ex)) if not self.include_terminal: return record - record_terminal = line[type(record).length:] - if (self.expected_terminal is not None and - record_terminal != self.expected_terminal): - self.malformed( - line_no, 'unexpected EOL "{0}"'.format(record_terminal) - ) + record_terminal = line[type(record).length :] + if ( + self.expected_terminal is not None + and record_terminal != self.expected_terminal + ): + self.malformed(line_no, 'unexpected EOL "{0}"'.format(record_terminal)) return record, record_terminal # internals @@ -851,26 +829,26 @@ def __init__(self, fo, as_record_type=None, record_size=None): # Reader - def next_record(self, expected_type=None, default='raise'): + def next_record(self, expected_type=None, default="raise"): block, block_offset = self.next_block() if block is None: - if default == 'raise': - self.malformed(block_offset, 'unexpected EOF') + if default == "raise": + self.malformed(block_offset, "unexpected EOF") return default try: record = self.as_record(block, block_offset) - except Malformed, ex: + except Malformed: self.retry = block, block_offset raise - except self.record_type.field_type.error_type, ex: + except self.record_type.field_type.error_type as ex: self.retry = block, block_offset self.malformed(block_offset, str(ex)) if not isinstance(record, (expected_type or self.record_type)): self.retry = block, block_offset - if default == 'raise': + if default == "raise": self.malformed( block_offset, - 'unexpected record type {0}'.format(type(record)), + "unexpected record type {0}".format(type(record)), ) return return record @@ -883,7 +861,7 @@ def next(self): raise StopIteration() try: record = self.as_record(block, block_offset) - except self.record_type.field_type.error_type, ex: + except self.record_type.field_type.error_type as ex: raise self.malformed(block_offset, str(ex)) return record diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9bda358 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +ignore = D203,E203,W503 +exclude = .git,__pycache__,build,dist,venv +max-complexity = 10 +max-line-length = 120 diff --git a/setup.py b/setup.py index ef6cdea..fccc120 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,9 @@ class PyTest(setuptools.command.test.test): - def finalize_options(self): setuptools.command.test.test.finalize_options(self) - self.test_args = ['tests.py'] + self.test_args = ["tests.py"] self.test_suite = True def run_tests(self): @@ -15,52 +14,47 @@ def run_tests(self): pytest.main(self.test_args) -install_requires = [ -] +install_requires = [] extras_require = { - 'tests': [ - 'pytest >=2.5.2,<3', - 'pytest-cov >=1.7,<2', + "tests": [ + "pytest >=2.5.2,<3", + "pytest-cov >=1.7,<2", ], } -packages = setuptools.find_packages( - '.', exclude=('tests', 'tests.*') -) +packages = setuptools.find_packages(".", exclude=("tests", "tests.*")) setuptools.setup( - name='bryl', + name="bryl", version=( - re - .compile(r".*__version__ = '(.*?)'", re.S) - .match(open('bryl/__init__.py').read()) + re.compile(r".*__version__ = '(.*?)'", re.S) + .match(open("bryl/__init__.py").read()) .group(1) ), - url='https://github.com/balanced/bryl/', - license=open('LICENSE').read(), - author='Balanced', - author_email='dev+bryl@balancedpayments.com', - description='.', - long_description=open('README.rst').read(), + url="https://github.com/balanced/bryl/", + license=open("LICENSE").read(), + author="Balanced", + author_email="dev+bryl@balancedpayments.com", + description=".", + long_description=open("README.rst").read(), packages=packages, - package_data={'': ['LICENSE']}, + package_data={"": ["LICENSE"]}, include_package_data=True, install_requires=install_requires, extras_require=extras_require, - tests_require=extras_require['tests'], + tests_require=extras_require["tests"], cmdclass={ - 'test': PyTest, + "test": PyTest, }, classifiers=[ - 'Intended Audience :: Developers', - 'Development Status :: 4 - Beta', - 'Natural Language :: English', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'License :: OSI Approved :: ISC License (ISCL)', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', + "Intended Audience :: Developers", + "Development Status :: 4 - Beta", + "Natural Language :: English", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: ISC License (ISCL)", + "Programming Language :: Python", + "Programming Language :: Python :: 3", ], ) diff --git a/tests.py b/tests.py index b7b3966..0438898 100644 --- a/tests.py +++ b/tests.py @@ -4,28 +4,22 @@ def test_reserved(): - class Record(bryl.Record): a = bryl.Alphanumeric(length=10) b = bryl.Alphanumeric(length=15).reserved() - c = bryl.Alphanumeric(length=10, default='abc123') - + c = bryl.Alphanumeric(length=10, default="abc123") # cant't set - r1 = Record(a='hiya') + r1 = Record(a="hiya") with pytest.raises(TypeError): - r1.b = 'nonono' + r1.b = "nonono" # but will read raw = r1.dump() - mangle = ( - raw[:Record.b.offset] + - 'fuzzy'.ljust(Record.b.length) + - raw[Record.c.offset:] - ) + mangle = raw[: Record.b.offset] + "fuzzy".ljust(Record.b.length) + raw[Record.c.offset :] r2 = Record.load(mangle) # and are the same