Skip to content

Instantly share code, notes, and snippets.

@cnicodeme
Last active September 21, 2022 13:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cnicodeme/622c12d9d40a8ef9df63dcc5692a2561 to your computer and use it in GitHub Desktop.
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.
#!/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