First commit

Made a first proof of concept with the ability to override things
This commit is contained in:
GodSaveTheDoge 2021-08-28 00:18:06 +02:00
commit 90e3d2335b
2 changed files with 113 additions and 0 deletions

50
README.md Normal file
View File

@ -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
$
```

63
hijack.py Normal file
View File

@ -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 _