First commit
Made a first proof of concept with the ability to override things
This commit is contained in:
commit
90e3d2335b
|
@ -0,0 +1,50 @@
|
|||
This allows to override things.
|
||||
|
||||
Some examples:
|
||||
```python
|
||||
$ cat target.py
|
||||
import sys
|
||||
|
||||
print(sys.executable)
|
||||
$ python target.py
|
||||
/usr/bin/python
|
||||
$ cat patch.py
|
||||
from hijack import BinLaden
|
||||
|
||||
patcher = BinLaden()
|
||||
patcher.override("sys.executable", "nope")
|
||||
patcher.run("target.py")
|
||||
$ python patch.py
|
||||
nope
|
||||
$
|
||||
```
|
||||
|
||||
This could be useful to add some functionality or act as a decorator:
|
||||
```python
|
||||
$ cat target.py
|
||||
import requests
|
||||
|
||||
try:
|
||||
r = requests.get("fsf.org")
|
||||
print(r.status_code, r.reason)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
$ python target.py
|
||||
Invalid URL 'fsf.org': No schema supplied. Perhaps you meant http://fsf.org?
|
||||
$ cat patch.py
|
||||
from hijack import BinLaden
|
||||
|
||||
patcher = BinLaden()
|
||||
old_get = patcher.import_("requests").get
|
||||
|
||||
@patcher.override("requests.get")
|
||||
def get(url, *a, **kw):
|
||||
if not url.startswith("http"):
|
||||
url = "https://" + url
|
||||
return old_get(url, *a, **kw)
|
||||
|
||||
patcher.run("target.py")
|
||||
$ python patch.py
|
||||
200 OK
|
||||
$
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
import sys
|
||||
import runpy
|
||||
import types
|
||||
# TIL: module.__builtins__ is just an implementation detail
|
||||
import builtins
|
||||
|
||||
# If you don't know what the qualified name is:
|
||||
# https://docs.python.org/3/glossary.html#term-qualified-name
|
||||
|
||||
class BinLaden:
|
||||
def __init__(self):
|
||||
# "Things" yet to override
|
||||
# Once they have been overriden thre is no need to keep em around
|
||||
# since sys.modules is a thing.
|
||||
# If you want to have "selective" override keeping the original module
|
||||
# might be necessary to avoid reimporting every time
|
||||
# This dict maps qualified name -> "thing"
|
||||
self.to_override = {}
|
||||
self.hijack()
|
||||
|
||||
def __import__(self, name, globals_=None, locals_=None, fromlist=(), level=0):
|
||||
module = self.import_(name, globals_, locals_, fromlist, level)
|
||||
|
||||
for qualname, obj in self.to_override.copy().items():
|
||||
if not qualname[0] == module.__name__:
|
||||
continue
|
||||
|
||||
foo = module
|
||||
for name in qualname[1:-1]:
|
||||
if not hasattr(foo, name):
|
||||
break
|
||||
foo = getattr(module, name)
|
||||
else:
|
||||
if hasattr(foo, qualname[-1]):
|
||||
setattr(foo, qualname[-1], obj)
|
||||
self.to_override.pop(qualname)
|
||||
|
||||
return module
|
||||
|
||||
def hijack(self):
|
||||
self.import_ = builtins.__import__
|
||||
builtins.__import__ = self.__import__
|
||||
|
||||
def restore(self):
|
||||
builtins.__import__ = self.import_
|
||||
|
||||
def run(self, file):
|
||||
runpy.run_path(file, run_name="__main__")
|
||||
|
||||
def runmodule(self, path):
|
||||
sys.modules.pop("__main__")
|
||||
runpy.run_module(path, run_name="__main__")
|
||||
|
||||
def override(self, qualname, obj=None):
|
||||
if obj:
|
||||
self.to_override[tuple(qualname.split("."))] = obj
|
||||
return
|
||||
|
||||
def _(fun):
|
||||
self.to_override[tuple(qualname.split("."))] = fun
|
||||
return fun
|
||||
|
||||
return _
|
Loading…
Reference in New Issue