diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index c4bec3b..1848591 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -41,9 +41,7 @@ jobs: - name: uv sync run: uv sync - - name: Debug CLI + - name: Get CLI version run: | source .venv/bin/activate - github-rest-cli -h - env: - GITHUB_AUTH_TOKEN: f@k3-t0k3n + github-rest-cli --version diff --git a/README.md b/README.md index 2f35182..5cddf0d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ # GitHub REST API -### Usage +## Installation + +Install using `pip`: +```shell +pip install github-rest-cli +``` + +Install using `uv`: +```shell +uv pip install github-rest-cli +``` + +## Usage Set up python package dependencies in `pyproject.toml`: ```shell @@ -24,7 +36,7 @@ export GITHUB_AUTH_TOKEN="" Run cli: ```shell -github-rest-cli -h +github-rest-cli -v ``` ### Dynaconf diff --git a/pyproject.toml b/pyproject.toml index bb4c369..6edc882 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "github-rest-cli" -version = "1.0.0" +version = "1.0.1" description = "GitHub REST API cli" authors = [ { name = "lbrealdev", email = "lbrealdeveloper@gmail.com" } diff --git a/src/github_rest_cli/__init__.py b/src/github_rest_cli/__init__.py index 3dc1f76..e69de29 100644 --- a/src/github_rest_cli/__init__.py +++ b/src/github_rest_cli/__init__.py @@ -1 +0,0 @@ -__version__ = "0.1.0" diff --git a/src/github_rest_cli/api.py b/src/github_rest_cli/api.py index 50a289f..e17515d 100644 --- a/src/github_rest_cli/api.py +++ b/src/github_rest_cli/api.py @@ -1,5 +1,5 @@ import requests -from github_rest_cli.globals import GITHUB_URL, HEADERS +from github_rest_cli.globals import GITHUB_URL, get_headers from github_rest_cli.utils import rich_output, rprint @@ -40,21 +40,34 @@ def build_url(*segments: str) -> str: return f"{base}/{path}" -def fetch_user(): +def fetch_user() -> str: + headers = get_headers() url = build_url("user") - response = request_with_handling("GET", url, headers=HEADERS) + response = request_with_handling("GET", url, headers=headers) if response: data = response.json() return data.get("login") return None -def get_repository(owner: str, name: str, org: str = None): +def get_repository(name: str, org: str = None): + owner = fetch_user() + headers = get_headers() url = build_url("repos", org or owner, name) - response = request_with_handling("GET", url, headers=HEADERS) + response = request_with_handling( + "GET", + url, + headers=headers, + error_msg={ + 401: "Unauthorized access. Please check your token or credentials.", + 404: "The requested repository does not exist.", + }, + ) + if response: data = response.json() rprint(data) + return None def create_repository(owner: str, name: str, visibility: str, org: str = None): @@ -67,12 +80,13 @@ def create_repository(owner: str, name: str, visibility: str, org: str = None): if visibility == "private": data["private"] = True + headers = get_headers() url = build_url("orgs", org, "repos") if org else build_url("user", "repos") return request_with_handling( "POST", url, - headers=HEADERS, + headers=headers, json=data, success_msg=f"Repository successfully created in {owner or org }/{name}", error_msg={ @@ -83,21 +97,23 @@ def create_repository(owner: str, name: str, visibility: str, org: str = None): def delete_repository(owner: str, name: str, org: str = None): + headers = get_headers() url = build_url("repos", org, name) if org else build_url("repos", owner, name) return request_with_handling( "DELETE", url, - headers=HEADERS, + headers=headers, success_msg=f"Repository sucessfully deleted in {owner or org}/{name}", error_msg={ 403: "The authenticated user does not have sufficient permissions to delete this repository.", - 404: "The requested repository was not found.", + 404: "The requested repository does not exist.", }, ) def list_repositories(page: int, property: str, role: str): + headers = get_headers() url = build_url("user", "repos") params = {"per_page": page, "sort": property, "type": role} @@ -106,7 +122,7 @@ def list_repositories(page: int, property: str, role: str): "GET", url, params=params, - headers=HEADERS, + headers=headers, error_msg={401: "Unauthorized access. Please check your token or credentials."}, ) @@ -121,6 +137,7 @@ def list_repositories(page: int, property: str, role: str): def dependabot_security(owner: str, name: str, enabled: bool, org: str = None): is_enabled = bool(enabled) + headers = get_headers() url = build_url("repos", org, name) if org else build_url("repos", owner, name) security_urls = ["vulnerability-alerts", "automated-security-fixes"] @@ -130,7 +147,7 @@ def dependabot_security(owner: str, name: str, enabled: bool, org: str = None): request_with_handling( "PUT", url=full_url, - headers=HEADERS, + headers=headers, success_msg=f"Enabled {endpoint}", error_msg={ 401: "Unauthorized. Please check your credentials.", @@ -141,13 +158,14 @@ def dependabot_security(owner: str, name: str, enabled: bool, org: str = None): request_with_handling( "DELETE", url=full_url, - headers=HEADERS, + headers=headers, success_msg=f"Dependabot has been disabled on repository {owner or org}/{name}.", error_msg={401: "Unauthorized. Please check your credentials."}, ) def deployment_environment(owner: str, name: str, env: str, org: str = None): + headers = get_headers() url = ( build_url("repos", org, name, "environments", env) if org @@ -157,7 +175,7 @@ def deployment_environment(owner: str, name: str, env: str, org: str = None): return request_with_handling( "PUT", url, - headers=HEADERS, + headers=headers, success_msg=f"Environment {env} has been created successfully in {owner or org}/{name}.", error_msg={ 422: f"Failed to create repository enviroment {owner or org}/{name}" diff --git a/src/github_rest_cli/config.py b/src/github_rest_cli/config.py index 6ba58f1..77d0b6d 100644 --- a/src/github_rest_cli/config.py +++ b/src/github_rest_cli/config.py @@ -1,23 +1,20 @@ from dynaconf import Dynaconf, Validator settings = Dynaconf( - # Default environment variables prefix. envvar_prefix="GITHUB", - # Source configuration files. settings_files=["../../settings.toml", "../../.secrets.toml"], environments=["development", "testing", "production"], env_switcher="SET_ENV", - # The script will not work if the variables - # defined in the Validator class are not defined. - validators=[ - Validator( - "AUTH_TOKEN", - must_exist=True, - messages={ - "must_exist_true": "Environment variable GITHUB_AUTH_TOKEN is not set. Please set it and try again." - }, - ), - ], +) + +# The CLI will not work if the variable +# defined in the Validator class are not defined. +AUTH_TOKEN_VALIDATOR = Validator( + "AUTH_TOKEN", + must_exist=True, + messages={ + "must_exist_true": "Environment variable GITHUB_AUTH_TOKEN is not set. Please set it and try again." + }, ) # `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. diff --git a/src/github_rest_cli/globals.py b/src/github_rest_cli/globals.py index bcda475..798ba79 100644 --- a/src/github_rest_cli/globals.py +++ b/src/github_rest_cli/globals.py @@ -1,10 +1,21 @@ -from github_rest_cli.config import settings +from github_rest_cli.config import settings, AUTH_TOKEN_VALIDATOR +from dynaconf.base import ValidationError +import logging + GITHUB_URL = "https://api.github.com" -GITHUB_TOKEN = f"{settings.AUTH_TOKEN}" +logger = logging.getLogger(__name__) + + +def get_headers(): + try: + AUTH_TOKEN_VALIDATOR.validate(settings) + except ValidationError as e: + logger.error(str(e)) + raise SystemExit(1) -HEADERS = { - "Accept": "application/vnd.github+json", - "X-GitHub-Api-Version": "2022-11-28", - "Authorization": f"token {GITHUB_TOKEN}", -} + return { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "Authorization": f"token {settings.AUTH_TOKEN}", + } diff --git a/src/github_rest_cli/main.py b/src/github_rest_cli/main.py index f9da400..8feea5b 100644 --- a/src/github_rest_cli/main.py +++ b/src/github_rest_cli/main.py @@ -1,6 +1,5 @@ import argparse from github_rest_cli.api import ( - fetch_user, get_repository, create_repository, delete_repository, @@ -8,22 +7,31 @@ dependabot_security, deployment_environment, ) +from importlib.metadata import version +import logging + + +__version__ = version("github-rest-cli") +logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") def cli(): """ Create parsers and subparsers for CLI arguments """ - global_parser = argparse.ArgumentParser( - description="Python CLI to GitHub REST API", + parser = argparse.ArgumentParser( + description="Python CLI for GitHub REST API", ) - subparsers = global_parser.add_subparsers( - help="Python GitHub REST API commands", dest="command" + + parser.add_argument( + "-v", "--version", action="version", version=f"%(prog)s {__version__}" ) + subparsers = parser.add_subparsers(help="GitHub REST API commands", dest="command") + # Subparser for "get-repository" function get_repo_parser = subparsers.add_parser( - "get-repo", help="Get repository information" + "get-repo", help="Get a repository's details" ) get_repo_parser.add_argument( "-n", @@ -35,11 +43,12 @@ def cli(): get_repo_parser.add_argument( "-o", "--org", help="The organization name", required=False, dest="org" ) + get_repo_parser.set_defaults(func=get_repository) # Subparser for "list-repository" function list_repo_parser = subparsers.add_parser( "list-repo", - help="List repositories for authenticated user", + help="List your repositories", ) list_repo_parser.add_argument( "-r", @@ -65,6 +74,7 @@ def cli(): dest="sort", help="List repositories sorted by", ) + list_repo_parser.set_defaults(func=list_repositories) # Subparser for "create-repository" function create_repo_parser = subparsers.add_parser( @@ -93,11 +103,12 @@ def cli(): dest="org", help="The organization name", ) + create_repo_parser.set_defaults(func=create_repository) # Subparser for "delete-repository" function delete_repo_parser = subparsers.add_parser( "delete-repo", - help="Delete a repository", + help="Delete an existing repository", ) delete_repo_parser.add_argument( "-n", @@ -113,11 +124,12 @@ def cli(): dest="org", help="The organization name", ) + delete_repo_parser.set_defaults(func=delete_repository) # Subparser for "dependabot" function dependabot_parser = subparsers.add_parser( "dependabot", - help="Github Dependabot security updates", + help="Manage Dependabot settings", ) dependabot_parser.add_argument( "-n", @@ -146,11 +158,12 @@ def cli(): dest="control", help="Disable dependabot security updates", ) + dependabot_parser.set_defaults(func=dependabot_security) # Subparser for "deployment-environments" function deploy_env_parser = subparsers.add_parser( "environment", - help="Github Deployment environments", + help="Manage deployment environments", ) deploy_env_parser.add_argument( "-n", @@ -173,26 +186,28 @@ def cli(): dest="org", help="The organization name", ) + deploy_env_parser.set_defaults(func=deployment_environment) - # guard clause pattern - args = global_parser.parse_args() + args = parser.parse_args() command = args.command - owner = fetch_user() - - if command == "get-repo": - return get_repository(owner, args.name, args.org) - if command == "list-repo": - return list_repositories(args.page, args.sort, args.role) - if command == "create-repo": - return create_repository(owner, args.name, args.visibility, args.org) - if command == "delete-repo": - return delete_repository(owner, args.name, args.org) - if command == "dependabot": - return dependabot_security(owner, args.name, args.control, args.org) - if command == "environment": - return deployment_environment(owner, args.name, args.env, args.org) - return False + if hasattr(args, "func"): + if command == "get-repo": + args.func(args.name, args.org) + elif command == "list-repo": + args.func(args.page, args.sort, args.role) + elif command == "create-repo": + args.func(args.name, args.visibility, args.org) + elif command == "delete-repo": + args.func(args.name, args.org) + elif command == "dependabot": + args.func(args.name, args.control, args.org) + elif command == "environment": + args.func(args.name, args.env, args.org) + else: + return False + else: + parser.print_help() if __name__ == "__main__": diff --git a/uv.lock b/uv.lock index 8f5ffae..cdcd1cf 100644 --- a/uv.lock +++ b/uv.lock @@ -61,7 +61,7 @@ wheels = [ [[package]] name = "github-rest-cli" -version = "0.1.0" +version = "1.0.1" source = { editable = "." } dependencies = [ { name = "dynaconf" },