From 79b56abc7c00358d68ae97b7001c862e89979ad6 Mon Sep 17 00:00:00 2001 From: "Dr. Marina Souza, PhD" <1130416+bt3gl@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:38:31 -0700 Subject: [PATCH] add my custom boilerplate (#50) --- boilerplates/project_boilerplate/.env.example | 2 + boilerplates/project_boilerplate/.gitignore | 160 ++++++++++++++++++ boilerplates/project_boilerplate/Makefile | 30 ++++ boilerplates/project_boilerplate/README.md | 6 + .../project_boilerplate/requirements.txt | 2 + .../project_boilerplate/requirements_dev.txt | 2 + boilerplates/project_boilerplate/setup.py | 13 ++ .../project_boilerplate/src/__init__.py | 1 + boilerplates/project_boilerplate/src/main.py | 33 ++++ .../project_boilerplate/src/utils/__init__.py | 1 + .../project_boilerplate/src/utils/os.py | 130 ++++++++++++++ .../project_boilerplate/src/utils/strings.py | 14 ++ .../tests/test_utils_aes_cipher.py | 16 ++ boilerplates/project_boilerplate/tox.ini | 8 + 14 files changed, 418 insertions(+) create mode 100644 boilerplates/project_boilerplate/.env.example create mode 100644 boilerplates/project_boilerplate/.gitignore create mode 100644 boilerplates/project_boilerplate/Makefile create mode 100644 boilerplates/project_boilerplate/README.md create mode 100644 boilerplates/project_boilerplate/requirements.txt create mode 100644 boilerplates/project_boilerplate/requirements_dev.txt create mode 100644 boilerplates/project_boilerplate/setup.py create mode 100644 boilerplates/project_boilerplate/src/__init__.py create mode 100644 boilerplates/project_boilerplate/src/main.py create mode 100644 boilerplates/project_boilerplate/src/utils/__init__.py create mode 100644 boilerplates/project_boilerplate/src/utils/os.py create mode 100644 boilerplates/project_boilerplate/src/utils/strings.py create mode 100644 boilerplates/project_boilerplate/tests/test_utils_aes_cipher.py create mode 100644 boilerplates/project_boilerplate/tox.ini diff --git a/boilerplates/project_boilerplate/.env.example b/boilerplates/project_boilerplate/.env.example new file mode 100644 index 0000000..d0df79a --- /dev/null +++ b/boilerplates/project_boilerplate/.env.example @@ -0,0 +1,2 @@ +# Options are: 'error' < 'info' < 'debug' +LOG_LEVEL=info \ No newline at end of file diff --git a/boilerplates/project_boilerplate/.gitignore b/boilerplates/project_boilerplate/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/boilerplates/project_boilerplate/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +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 + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +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.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +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/ diff --git a/boilerplates/project_boilerplate/Makefile b/boilerplates/project_boilerplate/Makefile new file mode 100644 index 0000000..655d196 --- /dev/null +++ b/boilerplates/project_boilerplate/Makefile @@ -0,0 +1,30 @@ +.PHONY: clean +clean: + @find . -iname '*.py[co]' -delete + @find . -iname '__pycache__' -delete + @rm -rf '.pytest_cache' + @rm -rf dist/ + @rm -rf build/ + @rm -rf *.egg-info + @rm -rf .tox + @rm -rf venv/lib/python*/site-packages/*.egg + +.PHONY: install +install: + @PYTHONPATH=$(pwd) python3 setup.py install + +.PHONY: install_deps +install_deps: + @PYTHONPATH=$(pwd) pip3 install -r requirements.txt + +.PHONY: install_dev +install_deps: + @PYTHONPATH=$(pwd) pip3 install -r requirements_dev.txt + +.PHONY: lint +lint: + @PYTHONPATH=$(pwd) tox -e lint + +.PHONY: test +test: + @PYTHONPATH=$(pwd) tox \ No newline at end of file diff --git a/boilerplates/project_boilerplate/README.md b/boilerplates/project_boilerplate/README.md new file mode 100644 index 0000000..2584f0c --- /dev/null +++ b/boilerplates/project_boilerplate/README.md @@ -0,0 +1,6 @@ +## python boilerplate + +
+ +#### a customized boilerplate to make it quicker starting new projects. + diff --git a/boilerplates/project_boilerplate/requirements.txt b/boilerplates/project_boilerplate/requirements.txt new file mode 100644 index 0000000..e8c0169 --- /dev/null +++ b/boilerplates/project_boilerplate/requirements.txt @@ -0,0 +1,2 @@ +python-dotenv==1.0.0 + diff --git a/boilerplates/project_boilerplate/requirements_dev.txt b/boilerplates/project_boilerplate/requirements_dev.txt new file mode 100644 index 0000000..80f4bc9 --- /dev/null +++ b/boilerplates/project_boilerplate/requirements_dev.txt @@ -0,0 +1,2 @@ +pytest==7.4.0 +tox==4.6.4 \ No newline at end of file diff --git a/boilerplates/project_boilerplate/setup.py b/boilerplates/project_boilerplate/setup.py new file mode 100644 index 0000000..7c655f2 --- /dev/null +++ b/boilerplates/project_boilerplate/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup, find_packages + +setup( + name="my_package", + version='0.1', + packages=find_packages(include=['src', \ + 'src.utils']), + author="bt3gl", + install_requires=['python-dotenv'], + entry_points={ + 'console_scripts': ['my_package=src.main:run'] + }, +) \ No newline at end of file diff --git a/boilerplates/project_boilerplate/src/__init__.py b/boilerplates/project_boilerplate/src/__init__.py new file mode 100644 index 0000000..dae354a --- /dev/null +++ b/boilerplates/project_boilerplate/src/__init__.py @@ -0,0 +1 @@ +# -*- encoding: utf-8 -*- diff --git a/boilerplates/project_boilerplate/src/main.py b/boilerplates/project_boilerplate/src/main.py new file mode 100644 index 0000000..7bd34b6 --- /dev/null +++ b/boilerplates/project_boilerplate/src/main.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- +# src/main.py + +import argparse + + +def run_menu() -> argparse.ArgumentParser: + + parser = argparse.ArgumentParser(description='Run my project') + + parser.add_argument('-t', dest='test', nargs=1, + help="Run test method. \ + Example: -t ") + + return parser + + +def run() -> None: + """Entry point for this module.""" + + parser = run_menu() + args = parser.parse_args() + + if args.test: + pass + + else: + parser.print_help() + + +if __name__ == "__main__": + run() diff --git a/boilerplates/project_boilerplate/src/utils/__init__.py b/boilerplates/project_boilerplate/src/utils/__init__.py new file mode 100644 index 0000000..dae354a --- /dev/null +++ b/boilerplates/project_boilerplate/src/utils/__init__.py @@ -0,0 +1 @@ +# -*- encoding: utf-8 -*- diff --git a/boilerplates/project_boilerplate/src/utils/os.py b/boilerplates/project_boilerplate/src/utils/os.py new file mode 100644 index 0000000..9ca3659 --- /dev/null +++ b/boilerplates/project_boilerplate/src/utils/os.py @@ -0,0 +1,130 @@ +# -*- encoding: utf-8 -*- +# utils/os.py +# This class implements OS/file system util methods used by the other classes. + +import os +import sys +import json +import copy +import logging +from pathlib import Path +from dotenv import load_dotenv + + +def set_logging(log_level) -> None: + """Set logging level according to .env config.""" + + if log_level == 'info': + logging.basicConfig(level=logging.INFO, format='%(message)s') + + elif log_level == 'error': + logging.basicConfig(level=logging.ERROR, format='%(message)s') + + elif log_level == 'debug': + logging.basicConfig(level=logging.DEBUG, format='%(message)s') + + else: + print(f'Logging level {log_level} is not available. Setting to ERROR') + logging.basicConfig(level=logging.ERROR, format='%(message)s') + + +def load_config() -> dict: + """Load and set environment variables.""" + + env_file = Path('.') / '.env' + if not os.path.isfile(env_file): + exit_with_error('Please create an .env file') + + env_vars = {} + load_dotenv(env_file) + + try: + env_vars['OUTPUT_DIR'] = os.getenv("OUTPUT_DIR") + set_logging(os.getenv("LOG_LEVEL")) + + return env_vars + + except KeyError as e: + exit_with_error(f'Cannot extract env variables: {e}. Exiting.') + + +def log_error(string) -> None: + """Print STDOUT error using the logging library.""" + + logging.error('🚨 %s', string) + + +def log_info(string) -> None: + """Print STDOUT info using the logging library.""" + + logging.info('🙌🏼 %s', string) + + +def log_debug(string) -> None: + """Print STDOUT debug using the logging library.""" + + logging.debug('🟨 %s', string) + + +def open_json(filepath) -> dict: + """Load and parse a file.""" + + try: + with open(filepath, 'r', encoding='utf-8') as infile: + return json.load(infile) + + except (IOError, FileNotFoundError, TypeError) as e: + exit_with_error(f'Failed to parse: "{filepath}": {e}') + + +def format_path(dir_path, filename) -> str: + """Format a OS full filepath.""" + + return os.path.join(dir_path, filename) + + +def format_output_file(name) -> str: + """Format the name for the result file.""" + + return f'{name}.json' + + +def save_output(destination, data) -> None: + """Save data from memory to a destination in disk.""" + + try: + with open(destination, 'w', encoding='utf-8') as outfile: + json.dump(data, outfile, indent=4) + + except (IOError, TypeError) as e: + log_error(f'Could not save {destination}: {e}') + + +def create_dir(result_dir) -> None: + """Check whether a directory exists and create it if needed.""" + + try: + if not os.path.isdir(result_dir): + os.mkdir(result_dir) + + except OSError as e: + log_error(f'Could not create {result_dir}: {e}') + + +def deep_copy(dict_to_clone) -> dict: + """Deep copy (not reference copy) to a dict.""" + + return copy.deepcopy(dict_to_clone) + + +def exit_with_error(message) -> None: + """Log an error message and halt the program.""" + log_error(message) + sys.exit(1) + + +def print_pid() -> None: + """Print the PID of the current process.""" + + log_info(f'Starting process main os.getpid() = {os.getpid()}') + diff --git a/boilerplates/project_boilerplate/src/utils/strings.py b/boilerplates/project_boilerplate/src/utils/strings.py new file mode 100644 index 0000000..057fdff --- /dev/null +++ b/boilerplates/project_boilerplate/src/utils/strings.py @@ -0,0 +1,14 @@ +# -*- encoding: utf-8 -*- +# utils/strings.py +# This class implements string methods used by the other classes. + +from pprint import PrettyPrinter + +def pprint(data, indent=None) -> None: + """Print dicts and data in a suitable format""" + + print() + indent = indent or 4 + pp = PrettyPrinter(indent=indent) + pp.pprint(data) + print() diff --git a/boilerplates/project_boilerplate/tests/test_utils_aes_cipher.py b/boilerplates/project_boilerplate/tests/test_utils_aes_cipher.py new file mode 100644 index 0000000..9c4f4b1 --- /dev/null +++ b/boilerplates/project_boilerplate/tests/test_utils_aes_cipher.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +import unittest + + +class TestMyFunction(unittest.TestCase): + + def setUp(self): + pass + + def test_one(self): + pass + + +if __name__ == '__main__': + unittest.main() diff --git a/boilerplates/project_boilerplate/tox.ini b/boilerplates/project_boilerplate/tox.ini new file mode 100644 index 0000000..d86ade9 --- /dev/null +++ b/boilerplates/project_boilerplate/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py36, py37, py38, py39, py10 +skip_missing_interpreters = true + +[testenv:lint] +skip_install = true +deps = flake8 +commands = flake8 src/ \ No newline at end of file