Created
June 12, 2020 14:19
-
-
Save JosephRedfern/ef52916e70497bc9631d6e018a97f84d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import importlib | |
import sys | |
import argparse | |
import subprocess | |
import logging | |
import inspect | |
try: | |
from pip._internal.cli.main import main as pipmain # sorry | |
except ImportError: | |
from pip import main as pipmain | |
logger = logging.getLogger('unrequired') | |
logger.setLevel(logging.DEBUG) | |
class RequirementBruteForce: | |
def __init__(self, module_name): | |
self.module_name = module_name | |
self.max_attempts = 10000 # let's not go mad here. | |
self.versions = {} | |
def run(self): | |
""" this is the main loop that orchestrates the brute force. | |
""" | |
logger.log(logging.DEBUG, "running the brute force") | |
complete = False | |
attempts = 0 | |
package_candidates = {} | |
package_name = None # this should go | |
while attempts < self.max_attempts: | |
try: | |
importlib.import_module(self.module_name) | |
complete = True | |
break | |
except ModuleNotFoundError as err: | |
# In this case, we're missing the module. We can get the module name from the exception (python >= 3.3?) | |
package_name = err.name | |
logger.log(logging.INFO, f"Importing the module {package_name} failed, will attempt installation.") | |
except Exception: | |
frm = inspect.trace()[-1] # get last entry in stack trace | |
context = frm.code_context # "code context" seems to help... | |
module_name = context[0].split(".")[0] | |
package_name = module_name | |
logger.log(logging.INFO, f"Error originating in {package_name} detected, will attempt to downgrade") | |
# Here, we have a generic exception, but it's probably not cause by a ModuleNotFoundError. We need to inspect | |
# the stack trace and see where the error was thrown. | |
# Get possible versions | |
if package_name is not None: | |
if package_name not in package_candidates: | |
package_versions = self.get_package_versions(package_name) | |
if len(package_versions) > 0: | |
package_candidates[package_name] = package_versions | |
else: | |
# If we can't find any version names, then the pypi package name is probably different to the module name. FFS. | |
pass | |
current_version = package_candidates[package_name].pop() # pop it -- most recent is first here. | |
logging.log(logging.INFO, f"Installing {package_name} == {current_version}\n") | |
self.install_package(package_name, current_version) # install the package... | |
if complete: | |
print("Through some miracle, this seemed to work... here's what we found.") | |
for name, version in self.versions.items(): | |
print("{name} == {version}") | |
else: | |
print("It will come as no surprise that this was a total waste of time, hasn't helped, and has absolutely ruined your python installation.") | |
def install_package(self, package_name, package_version): | |
logger.log(logging.DEBUG, f"Installing {package_name}=={package_version}\n") | |
pipmain(['install', f'{package_name}=={package_version}']) | |
self.versions[package_name] = package_version # keep track of versions, we'll want to print this later. | |
def get_package_versions(self, package_name): | |
"""Get possible versions from Pypi, from newest to oldest. | |
This is really horrible for a number of reasons: | |
* Pip doesn't have a stable/public API. Odd, but excusable. | |
* Pip doesn't have a proper way of listing possible package versions, beyond requesting a version that doesn't exist... wtf? | |
* I couldn't be bothered to figure out the regular expression. | |
""" | |
logger.log(logging.DEBUG, f"Checking available versions for `{package_name}`") | |
last_arg = f'{package_name}== ' ## specifying a blank version number triggers an error which lists possible versions. | |
sp = subprocess.run(['pip', 'install', last_arg], capture_output=True) | |
stderr_output = sp.stderr.decode('utf-8') | |
list_start = stderr_output.index(last_arg) + len(last_arg) + 16 # sorry | |
list_end = stderr_output.index(")\n") | |
versions = stderr_output[list_start:list_end].split(", ") | |
return versions | |
if __name__ == "__main__": | |
print(""" | |
_ _ | |
(_) | | | |
_ _ _ __ _ __ ___ __ _ _ _ _ _ __ ___ __| | | |
| | | | '_ \| '__/ _ \/ _` | | | | | '__/ _ \/ _` | | |
| |_| | | | | | | __/ (_| | |_| | | | | __/ (_| | | |
\__,_|_| |_|_| \___|\__, |\__,_|_|_| \___|\__,_| | |
| | | |
|_| | |
""") | |
parser = argparse.ArgumentParser(description="Do some horrendous stuff to brute force dependencies in case of missing requirements file") | |
parser.add_argument('module', help='Module name to import') | |
args = parser.parse_args() | |
bf = RequirementBruteForce(args.module) | |
print("why are you doing this to yourself? are you sure you want to do this to yourself!? y/n", end=": ") | |
sure = input().lower().strip() | |
if len(sure) == 0 or sure[0] != "y": | |
print("\nwise. ") | |
sys.exit() | |
print("are you... like... really sure?! y/n", end=": ") | |
sure = input().lower().strip() | |
if len(sure) == 0 or sure[0] != "y": | |
print("\nphew. ") | |
sys.exit() | |
else: | |
print("\nok... you were warned.") | |
logger.log(logging.DEBUG, "DOING THIS THING") | |
bf.run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment