aboutsummaryrefslogtreecommitdiffstats
path: root/mitmproxy/types.py
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2019-11-18 02:55:51 +0100
committerMaximilian Hils <git@maximilianhils.com>2019-11-18 03:05:41 +0100
commitcb723c53fab93dae67f68b4414a35c8bec7fc144 (patch)
treea6cf5923a8e6c04d68ab634e23e42727351c9501 /mitmproxy/types.py
parent8a6370f1c2ae55fb03f606cb055de455ffe25eec (diff)
downloadmitmproxy-cb723c53fab93dae67f68b4414a35c8bec7fc144.tar.gz
mitmproxy-cb723c53fab93dae67f68b4414a35c8bec7fc144.tar.bz2
mitmproxy-cb723c53fab93dae67f68b4414a35c8bec7fc144.zip
revamp command processing
- Display the parameter name instead of the parameter type whenver users interact with commands. This makes it easy to enter commands just by their signature. We may want to expose type information in the command list, but some quick testing showed that this are rather intuitive anyways. - Add shift tab backward cycling for the command completion. - Use inspect.Signature instead of homebrew argument matching solution. This gets rid of quite a bit of cruft. - Remove some type checking hacks in mitmproxy.types
Diffstat (limited to 'mitmproxy/types.py')
-rw-r--r--mitmproxy/types.py123
1 files changed, 56 insertions, 67 deletions
diff --git a/mitmproxy/types.py b/mitmproxy/types.py
index b48aef84..1f1c503b 100644
--- a/mitmproxy/types.py
+++ b/mitmproxy/types.py
@@ -5,6 +5,9 @@ import typing
from mitmproxy import exceptions
from mitmproxy import flow
+if typing.TYPE_CHECKING:
+ from mitmproxy.command import CommandManager
+
class Path(str):
pass
@@ -14,7 +17,7 @@ class Cmd(str):
pass
-class Arg(str):
+class CmdArgs(str):
pass
@@ -22,6 +25,10 @@ class Unknown(str):
pass
+class Space(str):
+ pass
+
+
class CutSpec(typing.Sequence[str]):
pass
@@ -40,27 +47,11 @@ class Choice:
return False
-# One of the many charming things about mypy is that introducing type
-# annotations can cause circular dependencies where there were none before.
-# Rather than putting types and the CommandManger in the same file, we introduce
-# a stub type with the signature we use.
-class _CommandBase:
- commands: typing.MutableMapping[str, typing.Any] = {}
-
- def _call_strings(self, path: str, args: typing.Sequence[str]) -> typing.Any:
- raise NotImplementedError
-
- def execute(self, cmd: str) -> typing.Any:
- raise NotImplementedError
-
-
class _BaseType:
typ: typing.Type = object
display: str = ""
- def completion(
- self, manager: _CommandBase, t: typing.Any, s: str
- ) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: typing.Any, s: str) -> typing.Sequence[str]:
"""
Returns a list of completion strings for a given prefix. The strings
returned don't necessarily need to be suffixes of the prefix, since
@@ -68,9 +59,7 @@ class _BaseType:
"""
raise NotImplementedError
- def parse(
- self, manager: _CommandBase, typ: typing.Any, s: str
- ) -> typing.Any:
+ def parse(self, manager: "CommandManager", typ: typing.Any, s: str) -> typing.Any:
"""
Parse a string, given the specific type instance (to allow rich type annotations like Choice) and a string.
@@ -78,7 +67,7 @@ class _BaseType:
"""
raise NotImplementedError
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
"""
Check if data is valid for this type.
"""
@@ -89,10 +78,10 @@ class _BoolType(_BaseType):
typ = bool
display = "bool"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return ["false", "true"]
- def parse(self, manager: _CommandBase, t: type, s: str) -> bool:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> bool:
if s == "true":
return True
elif s == "false":
@@ -102,7 +91,7 @@ class _BoolType(_BaseType):
"Booleans are 'true' or 'false', got %s" % s
)
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return val in [True, False]
@@ -110,13 +99,13 @@ class _StrType(_BaseType):
typ = str
display = "str"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return []
- def parse(self, manager: _CommandBase, t: type, s: str) -> str:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> str:
return s
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, str)
@@ -124,13 +113,13 @@ class _UnknownType(_BaseType):
typ = Unknown
display = "unknown"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return []
- def parse(self, manager: _CommandBase, t: type, s: str) -> str:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> str:
return s
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return False
@@ -138,16 +127,16 @@ class _IntType(_BaseType):
typ = int
display = "int"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return []
- def parse(self, manager: _CommandBase, t: type, s: str) -> int:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> int:
try:
return int(s)
except ValueError as e:
raise exceptions.TypeError from e
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, int)
@@ -155,7 +144,7 @@ class _PathType(_BaseType):
typ = Path
display = "path"
- def completion(self, manager: _CommandBase, t: type, start: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, start: str) -> typing.Sequence[str]:
if not start:
start = "./"
path = os.path.expanduser(start)
@@ -177,10 +166,10 @@ class _PathType(_BaseType):
ret.sort()
return ret
- def parse(self, manager: _CommandBase, t: type, s: str) -> str:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> str:
return os.path.expanduser(s)
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, str)
@@ -188,43 +177,43 @@ class _CmdType(_BaseType):
typ = Cmd
display = "cmd"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return list(manager.commands.keys())
- def parse(self, manager: _CommandBase, t: type, s: str) -> str:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> str:
if s not in manager.commands:
raise exceptions.TypeError("Unknown command: %s" % s)
return s
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return val in manager.commands
class _ArgType(_BaseType):
- typ = Arg
+ typ = CmdArgs
display = "arg"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return []
- def parse(self, manager: _CommandBase, t: type, s: str) -> str:
- return s
+ def parse(self, manager: "CommandManager", t: type, s: str) -> str:
+ raise exceptions.TypeError("Arguments for unknown command.")
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, str)
class _StrSeqType(_BaseType):
typ = typing.Sequence[str]
- display = "[str]"
+ display = "str[]"
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return []
- def parse(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return [x.strip() for x in s.split(",")]
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
if isinstance(val, str) or isinstance(val, bytes):
return False
try:
@@ -238,7 +227,7 @@ class _StrSeqType(_BaseType):
class _CutSpecType(_BaseType):
typ = CutSpec
- display = "[cut]"
+ display = "cut[]"
valid_prefixes = [
"request.method",
"request.scheme",
@@ -277,7 +266,7 @@ class _CutSpecType(_BaseType):
"server_conn.tls_established",
]
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
spec = s.split(",")
opts = []
for pref in self.valid_prefixes:
@@ -285,11 +274,11 @@ class _CutSpecType(_BaseType):
opts.append(",".join(spec))
return opts
- def parse(self, manager: _CommandBase, t: type, s: str) -> CutSpec:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> CutSpec:
parts: typing.Any = s.split(",")
return parts
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
if not isinstance(val, str):
return False
parts = [x.strip() for x in val.split(",")]
@@ -327,7 +316,7 @@ class _BaseFlowType(_BaseType):
"~c",
]
- def completion(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[str]:
return self.valid_prefixes
@@ -335,7 +324,7 @@ class _FlowType(_BaseFlowType):
typ = flow.Flow
display = "flow"
- def parse(self, manager: _CommandBase, t: type, s: str) -> flow.Flow:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> flow.Flow:
try:
flows = manager.execute("view.flows.resolve %s" % (s))
except exceptions.CommandError as e:
@@ -346,21 +335,21 @@ class _FlowType(_BaseFlowType):
)
return flows[0]
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
return isinstance(val, flow.Flow)
class _FlowsType(_BaseFlowType):
typ = typing.Sequence[flow.Flow]
- display = "[flow]"
+ display = "flow[]"
- def parse(self, manager: _CommandBase, t: type, s: str) -> typing.Sequence[flow.Flow]:
+ def parse(self, manager: "CommandManager", t: type, s: str) -> typing.Sequence[flow.Flow]:
try:
return manager.execute("view.flows.resolve %s" % (s))
except exceptions.CommandError as e:
raise exceptions.TypeError from e
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
try:
for v in val:
if not isinstance(v, flow.Flow):
@@ -372,19 +361,19 @@ class _FlowsType(_BaseFlowType):
class _DataType(_BaseType):
typ = Data
- display = "[data]"
+ display = "data[][]"
def completion(
- self, manager: _CommandBase, t: type, s: str
+ self, manager: "CommandManager", t: type, s: str
) -> typing.Sequence[str]: # pragma: no cover
raise exceptions.TypeError("data cannot be passed as argument")
def parse(
- self, manager: _CommandBase, t: type, s: str
+ self, manager: "CommandManager", t: type, s: str
) -> typing.Any: # pragma: no cover
raise exceptions.TypeError("data cannot be passed as argument")
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
# FIXME: validate that all rows have equal length, and all columns have equal types
try:
for row in val:
@@ -400,16 +389,16 @@ class _ChoiceType(_BaseType):
typ = Choice
display = "choice"
- def completion(self, manager: _CommandBase, t: Choice, s: str) -> typing.Sequence[str]:
+ def completion(self, manager: "CommandManager", t: Choice, s: str) -> typing.Sequence[str]:
return manager.execute(t.options_command)
- def parse(self, manager: _CommandBase, t: Choice, s: str) -> str:
+ def parse(self, manager: "CommandManager", t: Choice, s: str) -> str:
opts = manager.execute(t.options_command)
if s not in opts:
raise exceptions.TypeError("Invalid choice.")
return s
- def is_valid(self, manager: _CommandBase, typ: typing.Any, val: typing.Any) -> bool:
+ def is_valid(self, manager: "CommandManager", typ: typing.Any, val: typing.Any) -> bool:
try:
opts = manager.execute(typ.options_command)
except exceptions.CommandError:
@@ -423,7 +412,7 @@ class TypeManager:
for t in types:
self.typemap[t.typ] = t()
- def get(self, t: typing.Optional[typing.Type], default=None) -> _BaseType:
+ def get(self, t: typing.Optional[typing.Type], default=None) -> typing.Optional[_BaseType]:
if type(t) in self.typemap:
return self.typemap[type(t)]
return self.typemap.get(t, default)