Skip to content

Commit

Permalink
Feature/CLI (#32)
Browse files Browse the repository at this point in the history
* reserver custom basic error

* add basic implementation of package parameters customization

* add customizable package parameter + validation regex for `email` & `url`

* add `user_parameters` field to upload function

* docstring added

* fulfill docstring

* updated

* add CLI Handler for reserver

* add `reserver_help`

* add OVERVIEW constant

* `autopep8.sh` applied

* remove blank line after docstring

* update overview

* update `--test` help

* add art to the requirements

* add `remove_dir` function

* use `remove_dir` instead of `shutil.rmtree`

* add CLI usage

* autopep8.sh applied

* `entry_points` added

* Smoke test added to `test.yml`

* enhance CLI

* refactor hardcoded params into `reserver_param.py`

* prioritize -v command

---------

Co-authored-by: AHReccese <[email protected]>
  • Loading branch information
AHReccese and AHReccese authored Jun 12, 2024
1 parent 0ed0c08 commit 8b77f0e
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 22 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
art==6.2
requests==2.32.3
setuptools==70.0.0
wheel==0.43.0
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
art>=5.3
requests>=1.0.0
setuptools>=40.8.0
wheel>=0.35.0
Expand Down
51 changes: 51 additions & 0 deletions reserver/__main__.py
Original file line number Diff line number Diff line change
@@ -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()
8 changes: 8 additions & 0 deletions reserver/reserver_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""Reserver errors."""


class ReserverBaseError(Exception):
"""Reserver base error class."""

pass
63 changes: 51 additions & 12 deletions reserver/reserver_func.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -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.
Expand All @@ -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='[email protected]',
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',
Expand All @@ -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) + "\"" + """,
)
"""
Expand All @@ -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")
19 changes: 10 additions & 9 deletions reserver/reserver_obj.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 18 additions & 1 deletion reserver/reserver_param.py
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"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}"
15 changes: 15 additions & 0 deletions reserver/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
"""utility module."""
from inspect import signature
import os
import shutil


def has_named_parameter(func, param_name):
Expand All @@ -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)
5 changes: 5 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,9 @@ def read_description():

],
license='MIT',
entry_points={
'console_scripts': [
'reserver = reserver.__main__:main',
]
}
)

0 comments on commit 8b77f0e

Please sign in to comment.