diff --git a/${REPO_NAME_SNAKE}/__init__.py b/${REPO_NAME_SNAKE}/__init__.py new file mode 100644 index 0000000..7b381cd --- /dev/null +++ b/${REPO_NAME_SNAKE}/__init__.py @@ -0,0 +1,46 @@ +import argparse +import asyncio +import sys + +import uvloop + +__version__ = "0.0.1" + + +async def main(args: argparse.Namespace) -> None: + """ + Main function of the program (stub). + + This function serves as ${REPO_NAME_SNAKE}'s critical path for primary logic. + It receives an `argparse.Namespace` object as input from invoke_maiun, which includes + the command-line arguments that were defined and parsed in the `run` function. + + Args: + args (argparse.Namespace): The command-line arguments object. + + Returns: + None + """ + pass + + +def invoke_main(args: argparse.Namespace) -> None: + """ + Function to invoke the main function using asyncio and uvloop (stub). + + This function serves as a bridge to run the async main function in + a uvloop event loop. It handles the differences in asyncio API across + different Python versions. + + Args: + args (argparse.Namespace): The command-line arguments object. + + Returns: + None + """ + if sys.version_info >= (3, 11): + with asyncio.Runner(loop_factory=uvloop.new_event_loop) as runner: + runner.run(main(args)) + else: + uvloop.install() + asyncio.run(main(args)) diff --git a/${REPO_NAME_SNAKE}/cli.py b/${REPO_NAME_SNAKE}/cli.py new file mode 100644 index 0000000..feec62f --- /dev/null +++ b/${REPO_NAME_SNAKE}/cli.py @@ -0,0 +1,44 @@ +from argparse import ArgumentParser +from pathlib import Path + +from . import __version__ +from .logger import logger +from .validation import validate_file_folder + + +def run() -> None: + """ + Command-line interface for ${REPO_NAME_SNAKE}. + + This function defines and handles command-line arguments for the program. + The available options are: + + --version: Show the version of the program and exit. + --verbose: Enable verbose logging. This will produce detailed output messages. + --output/-o: Specify the output file. This must be a valid file path. + paths: Specify any number of existing files or directories to be processed. + These paths must point to existing files or directories. + + Upon parsing the command-line arguments, the function configures the logger + and invokes the `main` function with the parsed arguments. + + Returns: + None + """ + # your code here + parser = ArgumentParser(description="${REPO_DESCRIPTION}") + parser.add_argument("--version", action="version", version=__version__) + parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") + parser.add_argument("--output", "-o", type=Path, help="Specify output file") + parser.add_argument( + "paths", + nargs="+", + type=validate_file_folder, + help="Specify any number of existing files or directories to be processed.", + ) + args = parser.parse_args() + + if args.verbose: + logger.debug("Verbose logging enabled") + + run(args) diff --git a/${REPO_NAME_SNAKE}/logger.py b/${REPO_NAME_SNAKE}/logger.py new file mode 100644 index 0000000..3fe05e6 --- /dev/null +++ b/${REPO_NAME_SNAKE}/logger.py @@ -0,0 +1,4 @@ +import logging + +logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S") +logger = logging.getLogger(__name__) diff --git a/${REPO_NAME_SNAKE}/validation.py b/${REPO_NAME_SNAKE}/validation.py new file mode 100644 index 0000000..ad9581e --- /dev/null +++ b/${REPO_NAME_SNAKE}/validation.py @@ -0,0 +1,12 @@ +from pathlib import Path + +from aiopath import AsyncPath + + +def validate_file_folder(value: str) -> Path: + file_folder_path = Path(value) + if not file_folder_path.exists(): + raise FileNotFoundError(f"No such file or folder: {value}") + if not file_folder_path.is_file() and not file_folder_path.is_dir(): + raise TypeError(f"Not a file or directory: {value}") + return AsyncPath(value) diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f3d0278 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 160 +exclude = docs/*, build/*, .git, __pycache__, build diff --git a/.gitignore b/.gitignore index 34207bd..fa764ad 100644 --- a/.gitignore +++ b/.gitignore @@ -28,8 +28,6 @@ share/python-wheels/ MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -83,34 +81,10 @@ target/ profile_default/ ipython_config.py -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide +# PDM .pdm.toml -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +# PEP 582 __pypackages__/ # Celery stuff @@ -154,11 +128,7 @@ dmypy.json cython_debug/ # PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ # ---> macOS # General @@ -167,7 +137,8 @@ cython_debug/ .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -228,4 +199,3 @@ $RECYCLE.BIN/ # .nfs files are created when an open file is removed but is still being accessed .nfs* - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..230afda --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/asottile/reorder_python_imports + rev: v3.9.0 + hooks: + - id: reorder-python-imports + args: [--application-directories, '.:${REPO_NAME_SNAKE}', --py39-plus] +- repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + language_version: python3.11 +- repo: https://github.com/PyCQA/bandit + rev: 1.7.5 + hooks: + - id: bandit + args: ["-c", "pyproject.toml"] + additional_dependencies: ["bandit[toml]"] +- repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 +- repo: local + hooks: + - id: version + name: Update version + entry: ./pre-commit.sh + language: script + files: 'pyproject.toml' diff --git a/README.md b/README.md index 6f502f0..30ecbe4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ -# python-module +
+# ${REPO_NAME_SNAKE} -Template for a new Python project. \ No newline at end of file +**${REPO_NAME_SNAKE}** is a... + +Python project!
+ +[Installation](#installation) • +[Examples](#examples) • +[Contributing](#contributing) • +[License](#license) +
+ +## Installation + +Install and run ${REPO_NAME_SNAKE} using the below commands or their equivalents for your environment. + +```bash +pip3 install -U ${REPO_HTTPS_URL} +${REPO_NAME_SNAKE} +``` + +## Examples + +TBD + +## Contributing + +If you would like to contribute to this project, feel free to submit a pull request or open an issue on GitHub. + +## License + +This project is licensed under the current AGPL 3.0+ License. See the `LICENSE` file for details. diff --git a/pre-commit.sh b/pre-commit.sh new file mode 100755 index 0000000..4fbf7fd --- /dev/null +++ b/pre-commit.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +PROJECT_ROOT=$(git rev-parse --show-toplevel) +VERSION=$(grep 'version =' $PROJECT_ROOT/pyproject.toml | head -1 | cut -d '"' -f 2) +INIT_FILE="$PROJECT_ROOT/${REPO_NAME_SNAKE}/${REPO_NAME_SNAKE}/__init__.py" +PYPROJECT_FILE="$PROJECT_ROOT/pyproject.toml" + +# Modify the CLI line to remove quotes in pyproject.toml +sed -i.bak "s/\"${REPO_NAME_SNAKE}\" = \"${REPO_NAME_SNAKE}\.cli:run\"/${REPO_NAME_SNAKE} = \"${REPO_NAME_SNAKE}\.cli:run\"/" $PYPROJECT_FILE && \ + rm "$PYPROJECT_FILE.bak" + +# Check if __init__.py file exists for template purposes +# This can be removed now that the template is cloned +if [ ! -f "$INIT_FILE" ]; then + echo "__init__.py does not exist yet, exiting." + exit 0 +fi + +# Portable workaround for macOS/Linux sed differences +sed -i.bak "s/__version__ = .*/__version__ = \"$VERSION\"/" $INIT_FILE && \ + rm "$INIT_FILE.bak" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..52e1c06 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[build-system] +requires = ["setuptools>=68.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "${REPO_NAME_SNAKE}" +version = "0.0.1" +authors = [{ name = "${REPO_OWNER_SNAKE}", email = "git@nixon.mozmail.com" }] +description = "${REPO_DESCRIPTION}" +readme = "README.md" +requires-python = ">=3.9" +license = { text = "MIT" } +dependencies = [ + "loguru>=0.7.0", + "uvloop>=0.17.0", + "aiopath>=0.6.11", + "asyncstdlib>=3.10.8", +] + +[project.scripts] +"${REPO_NAME_SNAKE}" = "${REPO_NAME_SNAKE}.cli:run" + +[project.urls] +homepage = "${REPO_LINK}" +repository = "${REPO_LINK}" + +[tool.setuptools] +py-modules = ["${REPO_NAME_SNAKE}"] + +[tool.bandit] +exclude_dirs = ["/docs", "/build"] + +[tool.black] +line-length = 120 diff --git a/template b/template new file mode 100644 index 0000000..70dc522 --- /dev/null +++ b/template @@ -0,0 +1,5 @@ +**.py +.pre-commit-config.yaml +README.md +pyproject.toml +pre-commit.sh