Move serialization methods out of Options

Attributes on options share a namespace with options themselves. It's getting
too crowded on our Options object, so let's shift some obvious stuff into the
module.
This commit is contained in:
Aldo Cortesi 2017-03-13 11:06:18 +13:00
parent c24f7d8e12
commit 2832e790fd
4 changed files with 100 additions and 94 deletions

View File

@ -222,83 +222,6 @@ class OptManager:
"""
return self._options[option].has_changed()
def save(self, path, defaults=False):
"""
Save to path. If the destination file exists, modify it in-place.
"""
if os.path.exists(path) and os.path.isfile(path):
with open(path, "r") as f:
data = f.read()
else:
data = ""
data = self.serialize(data, defaults)
with open(path, "w") as f:
f.write(data)
def serialize(self, text, defaults=False):
"""
Performs a round-trip serialization. If text is not None, it is
treated as a previous serialization that should be modified
in-place.
- If "defaults" is False, only options with non-default values are
serialized. Default values in text are preserved.
- Unknown options in text are removed.
- Raises OptionsError if text is invalid.
"""
data = self._load(text)
for k in self.keys():
if defaults or self.has_changed(k):
data[k] = getattr(self, k)
for k in list(data.keys()):
if k not in self._options:
del data[k]
return ruamel.yaml.round_trip_dump(data)
def _load(self, text):
if not text:
return {}
try:
data = ruamel.yaml.load(text, ruamel.yaml.RoundTripLoader)
except ruamel.yaml.error.YAMLError as v:
snip = v.problem_mark.get_snippet()
raise exceptions.OptionsError(
"Config error at line %s:\n%s\n%s" %
(v.problem_mark.line + 1, snip, v.problem)
)
if isinstance(data, str):
raise exceptions.OptionsError("Config error - no keys found.")
return data
def load(self, text):
"""
Load configuration from text, over-writing options already set in
this object. May raise OptionsError if the config file is invalid.
"""
data = self._load(text)
try:
self.update(**data)
except KeyError as v:
raise exceptions.OptionsError(v)
def load_paths(self, *paths):
"""
Load paths in order. Each path takes precedence over the previous
path. Paths that don't exist are ignored, errors raise an
OptionsError.
"""
for p in paths:
p = os.path.expanduser(p)
if os.path.exists(p) and os.path.isfile(p):
with open(p, "r") as f:
txt = f.read()
try:
self.load(txt)
except exceptions.OptionsError as e:
raise exceptions.OptionsError(
"Error reading %s: %s" % (p, e)
)
def merge(self, opts):
"""
Merge a dict of options into this object. Options that have None
@ -468,3 +391,85 @@ def dump_defaults(opts):
)
s.yaml_set_comment_before_after_key(k, before = "\n" + txt)
return ruamel.yaml.round_trip_dump(s)
def parse(text):
if not text:
return {}
try:
data = ruamel.yaml.load(text, ruamel.yaml.RoundTripLoader)
except ruamel.yaml.error.YAMLError as v:
snip = v.problem_mark.get_snippet()
raise exceptions.OptionsError(
"Config error at line %s:\n%s\n%s" %
(v.problem_mark.line + 1, snip, v.problem)
)
if isinstance(data, str):
raise exceptions.OptionsError("Config error - no keys found.")
return data
def load(opts, text):
"""
Load configuration from text, over-writing options already set in
this object. May raise OptionsError if the config file is invalid.
"""
data = parse(text)
try:
opts.update(**data)
except KeyError as v:
raise exceptions.OptionsError(v)
def load_paths(opts, *paths):
"""
Load paths in order. Each path takes precedence over the previous
path. Paths that don't exist are ignored, errors raise an
OptionsError.
"""
for p in paths:
p = os.path.expanduser(p)
if os.path.exists(p) and os.path.isfile(p):
with open(p, "r") as f:
txt = f.read()
try:
load(opts, txt)
except exceptions.OptionsError as e:
raise exceptions.OptionsError(
"Error reading %s: %s" % (p, e)
)
def serialize(opts, text, defaults=False):
"""
Performs a round-trip serialization. If text is not None, it is
treated as a previous serialization that should be modified
in-place.
- If "defaults" is False, only options with non-default values are
serialized. Default values in text are preserved.
- Unknown options in text are removed.
- Raises OptionsError if text is invalid.
"""
data = parse(text)
for k in opts.keys():
if defaults or opts.has_changed(k):
data[k] = getattr(opts, k)
for k in list(data.keys()):
if k not in opts._options:
del data[k]
return ruamel.yaml.round_trip_dump(data)
def save(opts, path, defaults=False):
"""
Save to path. If the destination file exists, modify it in-place.
"""
if os.path.exists(path) and os.path.isfile(path):
with open(path, "r") as f:
data = f.read()
else:
data = ""
data = serialize(opts, data, defaults)
with open(path, "w") as f:
f.write(data)

View File

@ -1,6 +1,7 @@
import urwid
from mitmproxy import contentviews
from mitmproxy import optmanager
from mitmproxy.tools.console import common
from mitmproxy.tools.console import grideditor
from mitmproxy.tools.console import select
@ -173,7 +174,7 @@ class Options(urwid.WidgetWrap):
return super().keypress(size, key)
def do_save(self, path):
self.master.options.save(path)
optmanager.save(self.master.options, path)
return "Saved"
def save(self):

View File

@ -69,7 +69,7 @@ def run(MasterKlass, args): # pragma: no cover
args = parser.parse_args(args)
master = None
try:
opts.load_paths(args.conf)
optmanager.load_paths(opts, args.conf)
server = process_options(parser, opts, args)
master = MasterKlass(opts, server)
master.addons.configure_all(opts, opts.keys())

View File

@ -199,61 +199,61 @@ def test_simple():
def test_serialize():
o = TD2()
o.three = "set"
assert "dfour" in o.serialize(None, defaults=True)
assert "dfour" in optmanager.serialize(o, None, defaults=True)
data = o.serialize(None)
data = optmanager.serialize(o, None)
assert "dfour" not in data
o2 = TD2()
o2.load(data)
optmanager.load(o2, data)
assert o2 == o
t = """
unknown: foo
"""
data = o.serialize(t)
data = optmanager.serialize(o, t)
o2 = TD2()
o2.load(data)
optmanager.load(o2, data)
assert o2 == o
t = "invalid: foo\ninvalid"
with pytest.raises(Exception, match="Config error"):
o2.load(t)
optmanager.load(o2, t)
t = "invalid"
with pytest.raises(Exception, match="Config error"):
o2.load(t)
optmanager.load(o2, t)
t = ""
o2.load(t)
optmanager.load(o2, t)
with pytest.raises(exceptions.OptionsError, matches='No such option: foobar'):
o2.load("foobar: '123'")
optmanager.load(o2, "foobar: '123'")
def test_serialize_defaults():
o = options.Options()
assert o.serialize(None, defaults=True)
assert optmanager.serialize(o, None, defaults=True)
def test_saving(tmpdir):
o = TD2()
o.three = "set"
dst = str(tmpdir.join("conf"))
o.save(dst, defaults=True)
optmanager.save(o, dst, defaults=True)
o2 = TD2()
o2.load_paths(dst)
optmanager.load_paths(o2, dst)
o2.three = "foo"
o2.save(dst, defaults=True)
optmanager.save(o2, dst, defaults=True)
o.load_paths(dst)
optmanager.load_paths(o, dst)
assert o.three == "foo"
with open(dst, 'a') as f:
f.write("foobar: '123'")
with pytest.raises(exceptions.OptionsError, matches=''):
o.load_paths(dst)
optmanager.load_paths(o, dst)
def test_merge():