Created
June 29, 2020 16:07
-
-
Save justengel/1de720bf0dcaf1e5e4957ee64306ce57 to your computer and use it in GitHub Desktop.
Change a library version and upload it to pypi.
This file contains 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
""" | |
Change a library version and upload it to pypi. | |
If you are updating the version, this script assumes a __meta__.py file exists in the package you want to upload. | |
Warning: | |
This has not been tested on all platforms! | |
Requirements: | |
* shell_proc>=1.1.1 | |
* wheel | |
* twine | |
""" | |
import argparse | |
import contextlib | |
import os | |
from shell_proc import Shell | |
__all__ = ['is_git', 'is_hg', 'PackageUpdater'] | |
def is_git(directory=None): | |
"""Return if the given directory is a git repository.""" | |
if directory is None: | |
directory = '.git' | |
else: | |
directory = os.path.join(directory, '.git') | |
return os.path.isdir(directory) | |
def is_hg(directory=None): | |
"""Return if the given directory is a git repository.""" | |
if directory is None: | |
directory = '.hg' | |
else: | |
directory = os.path.join(directory, '.hg') | |
return os.path.isdir(directory) | |
class PackageUpdater(Shell): | |
is_git = staticmethod(is_git) | |
is_hg = staticmethod(is_hg) | |
@contextlib.contextmanager | |
def change_dir(self, directory): | |
"""Temporarily change the directory""" | |
# Change directory if needed | |
current_dir = None | |
dir_changed = False | |
if directory is not None: | |
directory = os.path.abspath(str(directory)).replace('\\', '/') | |
if self.is_windows(): | |
current_dir = self('echo %CD%').stdout.strip() | |
else: | |
current_dir = self('echo $PWD').stdout.strip() | |
current_dir = os.path.abspath(current_dir).replace('\\', '/') | |
dir_changed = current_dir != directory | |
if dir_changed: | |
self('cd "{}"'.format(directory)) | |
os.chdir(directory) | |
yield | |
if dir_changed: | |
self('cd "{}"'.format(current_dir)) | |
os.chdir(current_dir) | |
@staticmethod | |
def get_meta(filename): | |
"""Return the metadata dictionary from the given filename.""" | |
with open(filename, 'r') as f: | |
meta = {} | |
exec(compile(f.read(), filename, 'exec'), meta) | |
return meta | |
@classmethod | |
def get_version(cls, name): | |
"""Return the version number in the __meta__.py file for the given package name.""" | |
filename = os.path.join(name, '__meta__.py') | |
meta = cls.get_meta(filename) | |
return meta['version'] | |
@staticmethod | |
def change_version(name, version): | |
"""Change the version number in the __meta__.py file for the given package name.""" | |
filename = os.path.join(name, '__meta__.py') | |
# Read file | |
with open(filename, 'r') as f: | |
lines = f.readlines() | |
# Change version | |
for i in range(len(lines)): | |
line = lines[i] | |
if line.startswith('version'): | |
lines[i] = "version = '{}'".format(version) | |
# Write file | |
with open(filename, 'w') as f: | |
f.writelines(lines) | |
def tag_exists(self, tag): | |
"""Return if the tag exists in git or hg.""" | |
# Change directory if needed | |
if is_git(): | |
result = self('git tag -l "{}"'.format(tag)).stdout.strip() | |
return result == tag # The stdout will be the matching tag or blank if not found | |
elif is_hg(): | |
result = self('hg log -r "{}"'.format(tag)) | |
return result.exit_code == 0 # If 0 True. Will give exit code of 255 if not found | |
return False | |
def commit_tag(self, tag, push=True): | |
"""Commit the tag.""" | |
if self.tag_exists(tag): | |
raise ValueError('The tag "{}" already exists!'.format(tag)) | |
if is_git(): | |
result = self('git tag "{}"'.format(tag)).stdout.strip() | |
if push: | |
result = self('git push --tags') | |
return result | |
elif is_hg(): | |
result = self('hg tag "{}"'.format(tag)) | |
if push: | |
result = self('hg push') | |
return result | |
def update_version(self, name, major=False, minor=False, micro=False): | |
"""Update the version number in the __meta__.py file.""" | |
# Read and change the version. | |
version = self.get_version(name) | |
vsplit = version.split('.') | |
major_changed = minor_changed = micro_changed = True | |
# Update by adding 1 | |
if isinstance(major, bool): | |
if major: | |
major = int(vsplit[0]) + 1 | |
else: | |
major = int(vsplit[0]) | |
major_changed = False | |
if isinstance(minor, bool): | |
if minor: | |
minor = int(vsplit[1] + 1) | |
else: | |
minor = int(vsplit[1]) | |
minor_changed = False | |
if isinstance(micro, bool): | |
if micro: | |
micro = int(vsplit[2] + 1) | |
else: | |
micro = int(vsplit[2]) | |
micro_changed = False | |
version = '.'.join([str(major), str(minor), str(micro)] + vsplit[3:]) | |
if major_changed or minor_changed or micro_changed: | |
self.change_version(name, version) | |
return version | |
else: | |
return False | |
def upload(self, name=None, major=False, minor=False, micro=False, tag=True, push=True, | |
activate_venv=True, directory=None): | |
"""Upload a package to pypi. | |
Args: | |
name (str)[None]: Name of the package to upload. Must have a __meta__.py file. | |
This is only used if update_version is True | |
major (bool/int)[False]: If True add 1 to the version. If an integer is given set the version. | |
minor (bool/int)[False]: If True add 1 to the version. If an integer is given set the version. | |
micro (bool/int)[False]: If True add 1 to the version. If an integer is given set the version. | |
tag (bool)[True]: Create a tag after changing the version. | |
push (bool)[True]: Push the tag after changing the version. | |
activate_venv (bool)[True]: If True try activating a virtual environment before creating the distribution. | |
directory (str)[None]: top level directory where the library's setup.py file exists. | |
""" | |
sh = self | |
with sh.change_dir(directory): | |
version = sh.update_version(name, major=major, minor=minor, micro=micro) | |
if version is not False and tag: | |
# Create and Push a new Tag | |
tag = 'v' + version | |
sh.commit_tag(tag, push=push) | |
# Activate virtual environment. | |
if activate_venv: | |
if sh.is_windows(): | |
sh('call .\\venv\\Scripts\\activate.bat') | |
else: | |
sh('source ./venv/bin/activate') | |
# Create distribution | |
sh('python setup.py sdist') | |
sh('python setup.py sdist bdist_wheel') | |
return sh('twine upload ./dist/*') | |
def as_version(value): | |
if isinstance(value, str): | |
if value == 'True' or value == 'true' or value == 'TRUE': | |
return True | |
elif value == 'False' or value == 'false' or value == 'FALSE': | |
return False | |
else: | |
try: | |
return int(value) | |
except (ValueError, TypeError, Exception): | |
return False | |
return value | |
def main(name=None, major=False, minor=False, micro=False, tag=True, push=True, activate_venv=True, directory=None): | |
P = argparse.ArgumentParser(description='Upload the package to pypi') | |
P.add_argument('--name', '-n', type=str, default=name, help='Name of the package if you are updating the version.') | |
P.add_argument('--major', default=major, help='Major version to change to. If True increment by 1.') | |
P.add_argument('--minor', default=minor, help='Minor version to change to. If True increment by 1.') | |
P.add_argument('--micro', default=micro, help='Micro version to change to. If True increment by 1.') | |
P.add_argument('--tag', type=bool, default=tag, | |
help='Create a tag if the version changes (Tag will be vMajor.Minor.Micro).') | |
P.add_argument('--push', type=bool, default=push, help='Push the tag if the version changes.') | |
P.add_argument('--activate_venv', '-a', type=bool, default=activate_venv, | |
help='Try activating a virtual environment before creating the distribution.') | |
P.add_argument('--directory', '-d', type=str, default=directory, | |
help='Directory to change to where the library\'s setup.py file exists.') | |
ARGS = P.parse_args() | |
name = ARGS.name | |
major = as_version(ARGS.major) | |
minor = as_version(ARGS.minor) | |
micro = as_version(ARGS.micro) | |
tag = ARGS.tag | |
push = ARGS.push | |
activate_venv = ARGS.activate_venv | |
directory = ARGS.directory | |
with PackageUpdater() as sh: | |
sh.upload(name=name, major=major, minor=minor, micro=micro, tag=tag, push=push, activate_venv=activate_venv, | |
directory=directory) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment