Last active
September 21, 2022 13:27
-
-
Save cnicodeme/622c12d9d40a8ef9df63dcc5692a2561 to your computer and use it in GitHub Desktop.
Will parse the local requirements.txt and prompt for updates when there is.
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
#!/usr/bin/python | |
# -*- coding:utf-8 -*- | |
# Parse the given requirements.txt file (defaults to the local one) and find updates from Pypi. | |
# Optional parameter "dry" will only show the changes without applying them. | |
from importlib.metadata import version | |
from importlib.metadata import PackageNotFoundError | |
from pkg_resources import Requirement | |
import requests, datetime, sys, subprocess, argparse | |
def parse_date(value): | |
try: | |
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ") | |
except ValueError: | |
return datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ") | |
def get_latest_version(package, current): | |
""" | |
Returns a tuple Version, Distance from the current, Release date | |
""" | |
version = None | |
response = requests.get('https://pypi.org/pypi/{}/json'.format(package)) | |
body = response.json() | |
version = body.get('info').get('version') | |
if not current or version == current: | |
return ( | |
version, | |
0 if current else -1, | |
parse_date(body['releases'][version][0]['upload_time_iso_8601']) | |
) | |
distance = 0 | |
current_release_date = parse_date(body['releases'][current][0]['upload_time_iso_8601']) | |
for release in body['releases']: | |
if not body['releases'][release]: | |
continue | |
active_release_date = parse_date(body['releases'][release][0]['upload_time_iso_8601']) | |
if active_release_date > current_release_date: | |
distance += 1 | |
return ( | |
version, | |
distance, | |
parse_date(body['releases'][version][0]['upload_time_iso_8601']) | |
) | |
def confirm(message, accepted='y', options=('y', 'n')): | |
while True: | |
result = input(message) | |
if result == '': | |
result = accepted | |
if result not in options: | |
continue | |
return result.lower() == accepted | |
def run(requirements_path, dry=False): | |
results = [] | |
changes = False | |
with open(requirements_path, 'r') as f: | |
for line in f: | |
line = line.strip() | |
if line[0:1] == '#': | |
results.append(line) | |
continue | |
req = Requirement.parse(line) | |
assert len(req.specs) <= 1 | |
try: | |
current = version(req.name) | |
except PackageNotFoundError: | |
current = None | |
package = get_latest_version(req.name, current) | |
new_line = req.name | |
if req.extras: | |
new_line += '[{}]'.format(','.join(req.extras)) | |
new_package_version = current | |
if current != package[0]: | |
changes = True | |
install = False | |
if current is not None: | |
print('"{}" is {} versions old. New package version is {} (Released on {}).'.format( | |
req.name, | |
package[1], | |
package[0], | |
package[2].strftime('%c') | |
)) | |
if dry: | |
continue | |
if confirm('Do you want to upgrade it? ([Y/n])'): | |
install = True | |
else: | |
if dry: | |
print('New package "{}" with version {} (Released on {}).'.format( | |
req.name, | |
package[0], | |
package[2].strftime('%c') | |
)) | |
continue | |
install = True | |
if install: | |
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', '{}=={}'.format(new_line, package[0])]) | |
new_package_version = package[0] | |
if len(req.specs) == 1: | |
new_line += req.specs[0][0] | |
new_line += new_package_version | |
else: | |
changes = True | |
new_line += '=={}'.format(new_package_version) | |
if line.find('#') > -1: | |
new_line += ' ' + line[line.find('#'):] | |
results.append(new_line) | |
if changes: | |
with open(requirements_path, 'w') as f: | |
f.write('\n'.join(results)) | |
print('Updates found.\nDry mode enabled so no changes were applied.') if dry else print('Requirements.txt updated.') | |
else: | |
print('No updates found.') if dry else print('No updates found. requirements.txt has not been changed.') | |
if __name__ == '__main__': | |
parser = argparse.ArgumentParser(description="Reads a requirements.txt file and update the packages if needed.\nAdds the package version too to ensure proper package management.") | |
parser.add_argument('--requirements', '-r', type=argparse.FileType('r'), default='./requirements.txt', required=False) | |
parser.add_argument('--dry', action=argparse.BooleanOptionalAction) | |
parser.set_defaults(dry=False) | |
args = parser.parse_args() | |
run(args.requirements.name, args.dry) | |
exit(0) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment