Skip to content

Instantly share code, notes, and snippets.

@JosephRedfern
Created June 12, 2020 14:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JosephRedfern/ef52916e70497bc9631d6e018a97f84d to your computer and use it in GitHub Desktop.
Save JosephRedfern/ef52916e70497bc9631d6e018a97f84d to your computer and use it in GitHub Desktop.
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