diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d67b8ee..9f4a5f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,6 +27,10 @@ jobs: run: | python -m pip install --upgrade pip pip install . + - name: Smoke test + run: | + python -m reserver --version + reserver --version - name: Test requirements Installation run: | python otherfiles/requirements-splitter.py diff --git a/README.md b/README.md index 4985d75..d08dcbf 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,27 @@ Reserver is an open source Python package that offers the ability to quickly ## Usage +### Programmatically ```python from reserver import PyPIUploader uploader = PyPIUploader(PYPI_API_TOKEN, test_pypi= False) uploader.upload("CONSIDERED_NAME_FOR_YOUR_PACKAGE") ``` +### CLI +⚠️ You can use `reserver` or `python -m reserver` to run this program +#### Version +```console +reserver -v +reserver --version +``` +#### Reserve in test PyPI (test.pypi.org) +```console +reserver --name sample_name1 sample_name2 --token=PYPI_TOKEN --test +``` +#### Reserve in main PyPI (pypi.org) +```console +reserver --name sample_name1 sample_name2 --token=PYPI_TOKEN +``` ## Issues & bug reports diff --git a/dev-requirements.txt b/dev-requirements.txt index 9978eef..905a844 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ +art==6.2 requests==2.32.3 setuptools==70.0.0 wheel==0.43.0 diff --git a/requirements.txt b/requirements.txt index d8fcee2..4d21919 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +art>=5.3 requests>=1.0.0 setuptools>=40.8.0 wheel>=0.35.0 diff --git a/reserver/__main__.py b/reserver/__main__.py new file mode 100644 index 0000000..2f095a9 --- /dev/null +++ b/reserver/__main__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +"""Reserver main.""" +import argparse +from art import tprint +from .reserver_param import RESERVER_VERSION +from .reserver_func import reserver_help +from .reserver_obj import PyPIUploader + + +def main(): + """ + CLI main function. + + :return: None + """ + parser = argparse.ArgumentParser() + parser.add_argument( + '--name', + nargs='?', + type=str, + metavar="PACKAGE_NAME", + help='Name(s) to get reserved', + ) + parser.add_argument( + '--token', + nargs='?', + type=str, + metavar="(TEST.PYPI|PYPI)_TOKEN", + help='The token for (main|test) PyPI account', + ) + parser.add_argument('--test', action='store_true', help='test PyPI (test.pypi.org)') + parser.add_argument('--version', help="version", action='store_true', default=False) + parser.add_argument('-v', help="version", action='store_true', default=False) + args = parser.parse_known_args()[0] + if args.version or args.v: + print(RESERVER_VERSION) + return + names = args.name + test_pypi = args.test + pypi_token = args.token + if names and pypi_token: + PyPIUploader(pypi_token, test_pypi).batch_upload(names) + else: + tprint("Reserver") + tprint("V:" + RESERVER_VERSION) + reserver_help() + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/reserver/reserver_errors.py b/reserver/reserver_errors.py new file mode 100644 index 0000000..8ef5549 --- /dev/null +++ b/reserver/reserver_errors.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +"""Reserver errors.""" + + +class ReserverBaseError(Exception): + """Reserver base error class.""" + + pass diff --git a/reserver/reserver_func.py b/reserver/reserver_func.py index bbfcdc6..1141a4a 100644 --- a/reserver/reserver_func.py +++ b/reserver/reserver_func.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- """Reserver functions.""" +import re import requests -import requests.adapters -from .reserver_param import PYPI_TEST_URL, PYPI_MAIN_URL +import requests.adapters +from .reserver_param import PYPI_TEST_URL, PYPI_MAIN_URL, PACKAGE_PARAMETERS, VALIDATIONS, OVERVIEW +from .reserver_param import INVALID_PACKAGE_PARAMETER_NAME_ERROR, INVALID_PACKAGE_PARAMETER_VALUE_ERROR +from .reserver_errors import ReserverBaseError from hashlib import sha256 from time import time from os import mkdir, rmdir @@ -38,8 +41,8 @@ def does_package_exist(suggested_name, test_pypi): retries = requests.adapters.Retry( total=5, backoff_factor=0.1, - status_forcelist=[ 500, 502, 503, 504 ] - ) + status_forcelist=[500, 502, 503, 504] + ) s.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries)) s.mount('https://', requests.adapters.HTTPAdapter(max_retries=retries)) @@ -48,7 +51,32 @@ def does_package_exist(suggested_name, test_pypi): return not response.status_code == 404 -def generate_template_setup_py(package_name): +def get_package_parameter(parameter, user_parameters, regex=None): + """ + Get the value for the associated package parameter. + + :param parameter: one of the customizable package parameters + :type parameter: str + :param user_parameters: user-customized package parameters + :type user_parameters: dict + :param regex: name of the regex to get applied + :type regex: str + :return: value of the associated parameter + """ + if not user_parameters or parameter not in user_parameters: + if parameter in PACKAGE_PARAMETERS: + return PACKAGE_PARAMETERS[parameter] + else: + raise ReserverBaseError(INVALID_PACKAGE_PARAMETER_NAME_ERROR) + if regex: + if re.match(VALIDATIONS[regex], user_parameters[parameter]): + return user_parameters[parameter] + else: + raise ReserverBaseError(INVALID_PACKAGE_PARAMETER_VALUE_ERROR.format(parameter=parameter, regex=regex)) + return user_parameters[parameter] + + +def generate_template_setup_py(package_name, user_parameters): """ Generate a template `setup.py` file for given package name. @@ -73,18 +101,18 @@ def generate_template_setup_py(package_name): name =""" + "\"" + package_name + "\"" + """, packages=[""" + "\"" + package_name + "\"" + "," + """], version='0.0.0', - description='This name has been reserved using Reserver', + description=""" + "\"" + get_package_parameter("description", user_parameters) + "\"" + """, long_description=\"\"\" This name has been reserved using [Reserver](https://github.com/openscilab/reserver). \"\"\", long_description_content_type='text/markdown', - author='Development Team', - author_email='test@test.com', - url='https://url.com', - download_url='https://download_url.com', + author=""" + "\"" + get_package_parameter("author", user_parameters) + "\"" + """, + author_email=""" + "\"" + get_package_parameter("author_email", user_parameters, "email") + "\"" + """, + url=""" + "\"" + get_package_parameter("url", user_parameters, "url") + "\"" + """, + download_url=""" + "\"" + get_package_parameter("download_url", user_parameters, "url") + "\"" + """, keywords="python3 python reserve reserver reserved", project_urls={ - 'Source': 'https://github.com/source', + 'Source':""" + "\"" + get_package_parameter("source", user_parameters, "url") + "\"" + """, }, install_requires="", python_requires='>=3.6', @@ -98,7 +126,7 @@ def generate_template_setup_py(package_name): \'Programming Language :: Python :: 3.11\', \'Programming Language :: Python :: 3.12\', ], - license='MIT', + license=""" + "\"" + get_package_parameter("license", user_parameters) + "\"" + """, ) """ @@ -113,3 +141,14 @@ def generate_template_setup_py(package_name): with open(package_name + "/__init__.py", "w") as f: f.write("# -*- coding: utf-8 -*-\n") f.write("\"\"\"" + package_name + " modules." + "\"\"\"") + + +def reserver_help(): + """ + Print Reserver details. + + :return: None + """ + print(OVERVIEW) + print("Repo : https://github.com/openscilab/reserver") + print("Webpage : https://openscilab.com/\n") diff --git a/reserver/reserver_obj.py b/reserver/reserver_obj.py index fa771cc..1d355d7 100644 --- a/reserver/reserver_obj.py +++ b/reserver/reserver_obj.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- """Reserver modules.""" from .reserver_func import does_package_exist, generate_template_setup_py -from .util import has_named_parameter +from .util import has_named_parameter, remove_dir from os import environ, path, getcwd, remove -from shutil import rmtree from sys import executable from subprocess import check_output, CalledProcessError from re import sub @@ -49,19 +48,21 @@ def batch_upload(self, *names): reserved_successfully += 1 return reserved_successfully - def upload(self, package_name): + def upload(self, package_name, user_parameters=None): """ Upload a template package to pypi or test_pypi. :param package_name: package name :type package_name: str + :param user_parameters: user-customized package parameters + :type user_parameters: dict :return: True if the package is successfully reserved, False otherwise """ if does_package_exist(package_name, self.test_pypi): print("This package already exists in PyPI.") return False - generate_template_setup_py(package_name) + generate_template_setup_py(package_name, user_parameters) environ["TWINE_USERNAME"] = self.username environ["TWINE_PASSWORD"] = self.password @@ -76,7 +77,7 @@ def upload(self, package_name): generated_wheel_file = path.join(generated_dist_folder, "*.whl") # prevent from uploading any other previously build library in this path. if path.exists(generated_dist_folder): - rmtree(generated_dist_folder) + remove_dir(generated_dist_folder) commands = [executable + " " + generated_setup_file_path + " sdist bdist_wheel "] if self.test_pypi: @@ -115,10 +116,10 @@ def upload(self, package_name): environ.pop("TWINE_PASSWORD") remove(generated_setup_file_path) - rmtree(generated_package_folder) - rmtree(generated_egginfo_file_path) - rmtree(generated_built_folder) - rmtree(generated_dist_folder) + remove_dir(generated_package_folder) + remove_dir(generated_egginfo_file_path) + remove_dir(generated_built_folder) + remove_dir(generated_dist_folder) if publish_failed: print(f"Publish to PyPI failed because of: ", error) diff --git a/reserver/reserver_param.py b/reserver/reserver_param.py index 3e9603c..c85541a 100644 --- a/reserver/reserver_param.py +++ b/reserver/reserver_param.py @@ -1,7 +1,24 @@ # -*- coding: utf-8 -*- """Parameters and constants.""" +OVERVIEW = """ +Reserver is an open source Python package that offers the ability to quickly reserve a PyPI package name. Got a notion? Before it's taken, immediately reserve the product name! +""" RESERVER_VERSION = "0.1" RESERVER_NAME = "reserver" - PYPI_TEST_URL = "https://test.pypi.org/project" PYPI_MAIN_URL = "https://pypi.org/project" +PACKAGE_PARAMETERS = { + "description": "This name has been reserved using Reserver", + "author": "Development Team", + "author_email": "test@test.com", + "url": "https://url.com", + "download_url": "https://download_url.com", + "source": "https://github.com/source", + "license": "MIT", +} +VALIDATIONS = { + "email": r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', + "url": r'^(http|https)://[a-zA-Z0-9.-_]+\.[a-zA-Z]{2,}(/\S*)?$', +} +INVALID_PACKAGE_PARAMETER_NAME_ERROR = "Given parameter doesn't exist among the supported user allowed parameters." +INVALID_PACKAGE_PARAMETER_VALUE_ERROR = "Invalid value for {parameter} that should be a valid {regex}" \ No newline at end of file diff --git a/reserver/util.py b/reserver/util.py index a02da86..f84d4ff 100644 --- a/reserver/util.py +++ b/reserver/util.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- """utility module.""" from inspect import signature +import os +import shutil def has_named_parameter(func, param_name): @@ -17,3 +19,16 @@ def has_named_parameter(func, param_name): _signature = signature(func) parameter_names = [p.name for p in _signature.parameters.values()] return param_name in parameter_names + + +def remove_dir(dirpath): + """ + Check whether the given path exists or not and remove it if it does. + + :param dirpath: path to the directory + :type dirpath: str + + :return: None + """ + if os.path.exists(dirpath) and os.path.isdir(dirpath): + shutil.rmtree(dirpath) diff --git a/setup.py b/setup.py index 88739fa..d727e0b 100644 --- a/setup.py +++ b/setup.py @@ -64,4 +64,9 @@ def read_description(): ], license='MIT', + entry_points={ + 'console_scripts': [ + 'reserver = reserver.__main__:main', + ] + } )