aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/optmanager.py
diff options
context:
space:
mode:
authorAldo Cortesi <aldo@corte.si>2018-05-08 10:56:00 +1200
committerAldo Cortesi <aldo@corte.si>2018-05-08 10:56:00 +1200
commitf7d7e31f06dadcf0f7801ab1db055f8609c88a71 (patch)
tree56aa748d814c00a594c8c46338d6e4abb1c9f629 /mitmproxy/optmanager.py
parent7ec9c5524f64bbceaf52b58696c8c9342a4887dd (diff)
downloadmitmproxy-f7d7e31f06dadcf0f7801ab1db055f8609c88a71.tar.gz
mitmproxy-f7d7e31f06dadcf0f7801ab1db055f8609c88a71.tar.bz2
mitmproxy-f7d7e31f06dadcf0f7801ab1db055f8609c88a71.zip
options: add the concept of deferred settings
We've had a perpetual sequencing problem with addon startup. Users need to be able to specify options to addons on the command-line, before addons are actually loaded. This is only exacerbated with the new async core, where load order can't be relied on. This patch introduces deferred options. Options passed with "--set" on the command line are deferred if they are unknown, and are automatically applied by the addon manager once matching addons are registered and their options are defined.
Diffstat (limited to 'mitmproxy/optmanager.py')
-rw-r--r--mitmproxy/optmanager.py64
1 files changed, 48 insertions, 16 deletions
diff --git a/mitmproxy/optmanager.py b/mitmproxy/optmanager.py
index bb9e3030..7dc4bf6c 100644
--- a/mitmproxy/optmanager.py
+++ b/mitmproxy/optmanager.py
@@ -91,9 +91,12 @@ class OptManager:
mutation doesn't change the option state inadvertently.
"""
def __init__(self):
- self.__dict__["_options"] = {}
- self.__dict__["changed"] = blinker.Signal()
- self.__dict__["errored"] = blinker.Signal()
+ self._deferred: typing.Dict[str, str] = {}
+ self.changed = blinker.Signal()
+ self.errored = blinker.Signal()
+ # Options must be the last attribute here - after that, we raise an
+ # error for attribute assigment to unknown options.
+ self._options: typing.Dict[str, typing.Any] = {}
def add_option(
self,
@@ -168,7 +171,14 @@ class OptManager:
raise AttributeError("No such option: %s" % attr)
def __setattr__(self, attr, value):
- self.update(**{attr: value})
+ # This is slightly tricky. We allow attributes to be set on the instance
+ # until we have an _options attribute. After that, assignment is sent to
+ # the update function, and will raise an error for unknown options.
+ opts = self.__dict__.get("_options")
+ if not opts:
+ super().__setattr__(attr, value)
+ else:
+ self.update(**{attr: value})
def keys(self):
return set(self._options.keys())
@@ -272,12 +282,44 @@ class OptManager:
options=options
)
- def set(self, *spec):
+ def set(self, *spec, defer=False):
+ """
+ Takes a list of set specification in standard form (option=value).
+ Options that are known are updated immediately. If defer is true,
+ options that are not known are deferred, and will be set once they
+ are added.
+ """
vals = {}
+ unknown = {}
for i in spec:
- vals.update(self._setspec(i))
+ parts = i.split("=", maxsplit=1)
+ if len(parts) == 1:
+ optname, optval = parts[0], None
+ else:
+ optname, optval = parts[0], parts[1]
+ if optname in self._options:
+ vals[optname] = self.parse_setval(optname, optval)
+ else:
+ unknown[optname] = optval
+ if defer:
+ self._deferred.update(unknown)
+ elif unknown:
+ raise exceptions.OptionsError("Unknown options: %s" % ", ".join(unknown.keys()))
self.update(**vals)
+ def process_deferred(self):
+ """
+ Processes options that were deferred in previous calls to set, and
+ have since been added.
+ """
+ update = {}
+ for optname, optval in self._deferred.items():
+ if optname in self._options:
+ update[optname] = self.parse_setval(optname, optval)
+ self.update(**update)
+ for k in update.keys():
+ del self._deferred[k]
+
def parse_setval(self, optname: str, optstr: typing.Optional[str]) -> typing.Any:
"""
Convert a string to a value appropriate for the option type.
@@ -316,16 +358,6 @@ class OptManager:
return getattr(self, optname) + [optstr]
raise NotImplementedError("Unsupported option type: %s", o.typespec)
- def _setspec(self, spec):
- d = {}
- parts = spec.split("=", maxsplit=1)
- if len(parts) == 1:
- optname, optval = parts[0], None
- else:
- optname, optval = parts[0], parts[1]
- d[optname] = self.parse_setval(optname, optval)
- return d
-
def make_parser(self, parser, optname, metavar=None, short=None):
"""
Auto-Create a command-line parser entry for a named option. If the