From bebe4504ed8710a2ff5fd9344bb7a702cbe11145 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Fri, 17 Nov 2023 09:11:10 +0000 Subject: [PATCH] Repackage to latest boilerplate. --- library/CHANGELOG.txt => CHANGELOG.md | 0 library/MANIFEST.in => MANIFEST.in | 0 Makefile | 92 +++++------ README.md | 17 +- check.sh | 88 ++++++++++ examples/advanced/lcd-demo.py | 7 +- examples/advanced/moisture.py | 1 - examples/monitor.py | 3 +- examples/tools/calibrate-pump.py | 11 +- examples/web_serve.py | 4 +- {library/grow => grow}/__init__.py | 3 +- {library/grow => grow}/moisture.py | 1 + {library/grow => grow}/pump.py | 3 +- install.sh | 219 ++++++++++++++++--------- library/.coveragerc | 4 - library/LICENSE.txt | 21 --- library/README.md | 68 -------- library/setup.py | 28 ---- library/tox.ini | 24 --- pyproject.toml | 124 ++++++++++++++ requirements-dev.txt | 9 + requirements-examples.txt | 3 + library/setup.cfg => setup.cfg | 0 {library/tests => tests}/conftest.py | 1 + {library/tests => tests}/test_lock.py | 0 {library/tests => tests}/test_setup.py | 2 +- tox.ini | 34 ++++ uninstall.sh | 83 ++++++++-- 28 files changed, 541 insertions(+), 309 deletions(-) rename library/CHANGELOG.txt => CHANGELOG.md (100%) rename library/MANIFEST.in => MANIFEST.in (100%) create mode 100755 check.sh rename {library/grow => grow}/__init__.py (99%) rename {library/grow => grow}/moisture.py (99%) rename {library/grow => grow}/pump.py (99%) delete mode 100644 library/.coveragerc delete mode 100644 library/LICENSE.txt delete mode 100644 library/README.md delete mode 100644 library/setup.py delete mode 100644 library/tox.ini create mode 100644 pyproject.toml create mode 100644 requirements-dev.txt create mode 100644 requirements-examples.txt rename library/setup.cfg => setup.cfg (100%) rename {library/tests => tests}/conftest.py (99%) rename {library/tests => tests}/test_lock.py (100%) rename {library/tests => tests}/test_setup.py (96%) create mode 100644 tox.ini diff --git a/library/CHANGELOG.txt b/CHANGELOG.md similarity index 100% rename from library/CHANGELOG.txt rename to CHANGELOG.md diff --git a/library/MANIFEST.in b/MANIFEST.in similarity index 100% rename from library/MANIFEST.in rename to MANIFEST.in diff --git a/Makefile b/Makefile index 23fbcd4..9e0c15c 100644 --- a/Makefile +++ b/Makefile @@ -1,70 +1,60 @@ -LIBRARY_VERSION=$(shell grep version library/setup.cfg | awk -F" = " '{print $$2}') -LIBRARY_NAME=$(shell grep name library/setup.cfg | awk -F" = " '{print $$2}') +LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null) +LIBRARY_VERSION := $(shell hatch version 2> /dev/null) -.PHONY: usage install uninstall +.PHONY: usage install uninstall check pytest qa build-deps check tag wheel sdist clean dist testdeploy deploy usage: +ifdef LIBRARY_NAME @echo "Library: ${LIBRARY_NAME}" @echo "Version: ${LIBRARY_VERSION}\n" +else + @echo "WARNING: You should 'make dev-deps'\n" +endif @echo "Usage: make , where target is one of:\n" - @echo "install: install the library locally from source" - @echo "uninstall: uninstall the local library" - @echo "check: peform basic integrity checks on the codebase" - @echo "python-readme: generate library/README.rst from README.md" - @echo "python-wheels: build python .whl files for distribution" - @echo "python-sdist: build python source distribution" - @echo "python-clean: clean python build and dist directories" - @echo "python-dist: build all python distribution files" - @echo "python-testdeploy: build all and deploy to test PyPi" - @echo "tag: tag the repository with the current version" + @echo "install: install the library locally from source" + @echo "uninstall: uninstall the local library" + @echo "dev-deps: install Python dev dependencies" + @echo "check: perform basic integrity checks on the codebase" + @echo "qa: run linting and package QA" + @echo "pytest: run Python test fixtures" + @echo "clean: clean Python build and dist directories" + @echo "build: build Python distribution files" + @echo "testdeploy: build and upload to test PyPi" + @echo "deploy: build and upload to PyPi" + @echo "tag: tag the repository with the current version\n" install: - ./install.sh + ./install.sh --unstable uninstall: ./uninstall.sh +dev-deps: + python3 -m pip install -r requirements-dev.txt + sudo apt install dos2unix + check: - @echo "Checking for trailing whitespace" - @! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO - @echo "Checking for DOS line-endings" - @! grep -lIUrn --color " " --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile - @echo "Checking library/CHANGELOG.txt" - @cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION} - @echo "Checking library/${LIBRARY_NAME}/__init__.py" - @cat library/${LIBRARY_NAME}/__init__.py | grep "^__version__ = '${LIBRARY_VERSION}'" + @bash check.sh + +qa: + tox -e qa + +pytest: + tox -e py + +nopost: + @bash check.sh --nopost tag: git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" -python-readme: library/README.md +build: check + @hatch build -python-license: library/LICENSE.txt +clean: + -rm -r dist -library/README.md: README.md library/CHANGELOG.txt - cp README.md library/README.md - printf "\n# Changelog\n" >> library/README.md - cat library/CHANGELOG.txt >> library/README.md +testdeploy: build + twine upload --repository testpypi dist/* -library/LICENSE.txt: LICENSE - cp LICENSE library/LICENSE.txt - -python-wheels: python-readme python-license - cd library; python3 setup.py bdist_wheel - cd library; python setup.py bdist_wheel - -python-sdist: python-readme python-license - cd library; python setup.py sdist - -python-clean: - -rm -r library/dist - -rm -r library/build - -rm -r library/*.egg-info - -python-dist: python-clean python-wheels python-sdist - ls library/dist - -python-testdeploy: python-dist - twine upload --repository-url https://test.pypi.org/legacy/ library/dist/* - -python-deploy: check python-dist - twine upload library/dist/* +deploy: nopost build + twine upload dist/* diff --git a/README.md b/README.md index c50ef20..d5e908a 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ Designed as a tiny valet for your plants, Grow HAT mini will monitor the soil moiture for up to 3 plants, water them with tiny pumps, and show you their health on its small but informative screen. Learn more - https://shop.pimoroni.com/products/grow -[![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/grow-python) +[![Build Status](https://img.shields.io/github/actions/workflow/status/pimoroni/grow-python/test.yml?branch=main)](https://github.com/pimoroni/grow-python/actions/workflows/test.yml) [![Coverage Status](https://coveralls.io/repos/github/pimoroni/grow-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/grow-python?branch=master) -[![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/growhat) -[![Python Versions](https://img.shields.io/pypi/pyversions/enviroplus.svg)](https://pypi.python.org/pypi/growhat) +[![PyPi Package](https://img.shields.io/pypi/v/growhat.svg)](https://pypi.python.org/pypi/growhat) +[![Python Versions](https://img.shields.io/pypi/pyversions/growhat.svg)](https://pypi.python.org/pypi/growhat) # Installing @@ -55,3 +55,14 @@ You should read the following to get up and running with our monitoring script: * GPIO Pinout - https://pinout.xyz/pinout/grow_hat_mini * Support forums - http://forums.pimoroni.com/c/support * Discord - https://discord.gg/hr93ByC + +# Changelog +0.0.2 +----- + +* Add mutually exclusive locking to pumps to avoid brownout running multiple pumps at once + +0.0.1 +----- + +* Initial Release diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..4395d89 --- /dev/null +++ b/check.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +# This script handles some basic QA checks on the source + +NOPOST=$1 +LIBRARY_NAME=`hatch project metadata name` +LIBRARY_VERSION=`hatch version | awk -F "." '{print $1"."$2"."$3}'` +POST_VERSION=`hatch version | awk -F "." '{print substr($4,0,length($4))}'` +TERM=${TERM:="xterm-256color"} + +success() { + echo -e "$(tput setaf 2)$1$(tput sgr0)" +} + +inform() { + echo -e "$(tput setaf 6)$1$(tput sgr0)" +} + +warning() { + echo -e "$(tput setaf 1)$1$(tput sgr0)" +} + +while [[ $# -gt 0 ]]; do + K="$1" + case $K in + -p|--nopost) + NOPOST=true + shift + ;; + *) + if [[ $1 == -* ]]; then + printf "Unrecognised option: $1\n"; + exit 1 + fi + POSITIONAL_ARGS+=("$1") + shift + esac +done + +inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n" + +inform "Checking for trailing whitespace..." +grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO +if [[ $? -eq 0 ]]; then + warning "Trailing whitespace found!" + exit 1 +else + success "No trailing whitespace found." +fi +printf "\n" + +inform "Checking for DOS line-endings..." +grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile +if [[ $? -eq 0 ]]; then + warning "DOS line-endings found!" + exit 1 +else + success "No DOS line-endings found." +fi +printf "\n" + +inform "Checking CHANGELOG.md..." +cat CHANGELOG.md | grep ^${LIBRARY_VERSION} > /dev/null 2>&1 +if [[ $? -eq 1 ]]; then + warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md." + exit 1 +else + success "Changes found for version ${LIBRARY_VERSION}." +fi +printf "\n" + +inform "Checking for git tag ${LIBRARY_VERSION}..." +git tag -l | grep -E "${LIBRARY_VERSION}$" +if [[ $? -eq 1 ]]; then + warning "Missing git tag for version ${LIBRARY_VERSION}" +fi +printf "\n" + +if [[ $NOPOST ]]; then + inform "Checking for .postN on library version..." + if [[ "$POST_VERSION" != "" ]]; then + warning "Found .$POST_VERSION on library version." + inform "Please only use these for testpypi releases." + exit 1 + else + success "OK" + fi +fi diff --git a/examples/advanced/lcd-demo.py b/examples/advanced/lcd-demo.py index d922491..6d22296 100644 --- a/examples/advanced/lcd-demo.py +++ b/examples/advanced/lcd-demo.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -import ST7735 -from PIL import Image, ImageDraw, ImageFont -from fonts.ttf import RobotoMedium as UserFont import logging +import ST7735 +from fonts.ttf import RobotoMedium as UserFont +from PIL import Image, ImageDraw, ImageFont + logging.basicConfig( format="%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s", level=logging.INFO, diff --git a/examples/advanced/moisture.py b/examples/advanced/moisture.py index c377eb1..744caf3 100644 --- a/examples/advanced/moisture.py +++ b/examples/advanced/moisture.py @@ -2,7 +2,6 @@ import time from grow.moisture import Moisture - print("""moisture.py - Print out sensor reading in Hz Press Ctrl+C to exit! diff --git a/examples/monitor.py b/examples/monitor.py index 9adb8dd..72b70ef 100644 --- a/examples/monitor.py +++ b/examples/monitor.py @@ -10,15 +10,14 @@ import time import ltr559 import RPi.GPIO as GPIO import ST7735 +import yaml from fonts.ttf import RobotoMedium as UserFont from PIL import Image, ImageDraw, ImageFont -import yaml from grow import Piezo from grow.moisture import Moisture from grow.pump import Pump - FPS = 10 BUTTONS = [5, 6, 16, 24] diff --git a/examples/tools/calibrate-pump.py b/examples/tools/calibrate-pump.py index f12b329..7a941b1 100644 --- a/examples/tools/calibrate-pump.py +++ b/examples/tools/calibrate-pump.py @@ -1,15 +1,14 @@ -import time import logging +import time + import RPi.GPIO as GPIO - -from PIL import Image, ImageDraw, ImageFont -from fonts.ttf import RobotoMedium as UserFont - import ST7735 +from fonts.ttf import RobotoMedium as UserFont +from PIL import Image, ImageDraw, ImageFont + from grow.moisture import Moisture from grow.pump import Pump - """ Auto water a single target with the channel/pump selected below. diff --git a/examples/web_serve.py b/examples/web_serve.py index 1b68ab6..acf80ac 100644 --- a/examples/web_serve.py +++ b/examples/web_serve.py @@ -3,10 +3,12 @@ License: MIT Description: Web API for moisture readings: http://:8080/ """ -from functools import partial import json import logging +from functools import partial + from aiohttp import web + from grow.moisture import Moisture json_response = partial(web.json_response, dumps=partial(json.dumps, default=str)) diff --git a/library/grow/__init__.py b/grow/__init__.py similarity index 99% rename from library/grow/__init__.py rename to grow/__init__.py index 74f9cf8..77ac843 100644 --- a/library/grow/__init__.py +++ b/grow/__init__.py @@ -1,8 +1,9 @@ __version__ = '0.0.2' -import time import atexit import threading +import time + import RPi.GPIO as GPIO diff --git a/library/grow/moisture.py b/grow/moisture.py similarity index 99% rename from library/grow/moisture.py rename to grow/moisture.py index 3d563e8..f426e6b 100644 --- a/library/grow/moisture.py +++ b/grow/moisture.py @@ -1,4 +1,5 @@ import time + import RPi.GPIO as GPIO MOISTURE_1_PIN = 23 diff --git a/library/grow/pump.py b/grow/pump.py similarity index 99% rename from library/grow/pump.py rename to grow/pump.py index 200f991..fe11ded 100644 --- a/library/grow/pump.py +++ b/grow/pump.py @@ -1,6 +1,7 @@ -import time import atexit import threading +import time + import RPi.GPIO as GPIO PUMP_1_PIN = 17 diff --git a/install.sh b/install.sh index 21def79..1f6ee40 100755 --- a/install.sh +++ b/install.sh @@ -1,31 +1,31 @@ #!/bin/bash - -CONFIG=/boot/config.txt +LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +MODULE_NAME="grow" +CONFIG_FILE=config.txt +CONFIG_DIR="/boot/firmware" DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"` CONFIG_BACKUP=false APT_HAS_UPDATED=false -USER_HOME=/home/$SUDO_USER -RESOURCES_TOP_DIR=$USER_HOME/Pimoroni +RESOURCES_TOP_DIR=$HOME/Pimoroni +VENV_BASH_SNIPPET=$RESOURCES_DIR/auto_venv.sh +VENV_DIR=$HOME/.virtualenvs/pimoroni WD=`pwd` -USAGE="sudo ./install.sh (--unstable)" +USAGE="./install.sh (--unstable)" POSITIONAL_ARGS=() +FORCE=false UNSTABLE=false -CODENAME=`lsb_release -sc` +PYTHON="python" -if [[ $CODENAME == "bullseye" ]]; then - bash ./install-bullseye.sh $@ - exit $? -fi user_check() { - if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./install.sh'\n" + if [ $(id -u) -eq 0 ]; then + printf "Script should not be run as root. Try './install.sh'\n" exit 1 fi } confirm() { - if [ "$FORCE" == '-y' ]; then + if $FORCE; then true else read -r -p "$1 [y/N] " response < /dev/tty @@ -58,16 +58,72 @@ warning() { echo -e "$(tput setaf 1)$1$(tput sgr0)" } +find_config() { + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then + CONFIG_DIR="/boot" + if [ ! -f "$CONFIG_DIR/$CONFIG_FILE"]; then + warning "Could not find $CONFIG_FILE!" + exit 1 + fi + else + if [ -f "/boot/$CONFIG_FILE" ] && [ ! -L "/boot/$CONFIG_FILE" ]; then + warning "Oops! It looks like /boot/$CONFIG_FILE is not a link to $CONFIG_DIR/$CONFIG_FILE" + warning "You might want to fix this!" + fi + fi + inform "Using $CONFIG_FILE in $CONFIG_DIR" +} + +venv_bash_snippet() { + if [ ! -f $VENV_BASH_SNIPPET ]; then + cat << EOF > $VENV_BASH_SNIPPET +# Add `source $RESOURCES_DIR/auto_venv.sh` to your ~/.bashrc to activate +# the Pimoroni virtual environment automagically! +VENV_DIR="$VENV_DIR" +if [ ! -f \$VENV_DIR/bin/activate ]; then + printf "Creating user Python environment in \$VENV_DIR, please wait...\n" + mkdir -p \$VENV_DIR + python3 -m venv --system-site-packages \$VENV_DIR +fi +printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n" +source \$VENV_DIR/bin/activate +EOF + fi +} + +venv_check() { + PYTHON_BIN=`which $PYTHON` + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + if confirm "Would you like us to create one for you?"; then + if [ ! -f $VENV_DIR/bin/activate ]; then + inform "Creating virtual Python environment in $VENV_DIR, please wait...\n" + mkdir -p $VENV_DIR + /usr/bin/python3 -m venv $VENV_DIR --system-site-packages + venv_bash_snippet + else + inform "Found existing virtual Python environment in $VENV_DIR\n" + fi + inform "Activating virtual Python environment in $VENV_DIR..." + inform "source $VENV_DIR/bin/activate\n" + source $VENV_DIR/bin/activate + + else + exit 1 + fi + fi +} + function do_config_backup { if [ ! $CONFIG_BACKUP == true ]; then CONFIG_BACKUP=true FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt" - inform "Backing up $CONFIG to /boot/$FILENAME\n" - cp $CONFIG /boot/$FILENAME + inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n" + sudo cp $CONFIG_DIR/$CONFIG_FILE $CONFIG_DIR/$FILENAME mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME + cp $CONFIG_DIR/$CONFIG_FILE $RESOURCES_TOP_DIR/config-backups/$FILENAME if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER + echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> $UNINSTALLER fi fi } @@ -88,16 +144,20 @@ function apt_pkg_install { if ! [ "$PACKAGES" == "" ]; then echo "Installing missing packages: $PACKAGES" if [ ! $APT_HAS_UPDATED ]; then - apt update + sudo apt update APT_HAS_UPDATED=true fi - apt install -y $PACKAGES + sudo apt install -y $PACKAGES if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" + echo "apt uninstall -y $PACKAGES" >> $UNINSTALLER fi fi } +function pip_pkg_install { + PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@" +} + while [[ $# -gt 0 ]]; do K="$1" case $K in @@ -105,6 +165,15 @@ while [[ $# -gt 0 ]]; do UNSTABLE=true shift ;; + -f|--force) + FORCE=true + shift + ;; + -p|--python) + PYTHON=$2 + shift + shift + ;; *) if [[ $1 == -* ]]; then printf "Unrecognised option: $1\n"; @@ -117,28 +186,31 @@ while [[ $# -gt 0 ]]; do done user_check +venv_check -apt_pkg_install python-configparser +if [ ! -f `which $PYTHON` ]; then + printf "Python path $PYTHON not found!\n" + exit 1 +fi -CONFIG_VARS=`python - < $UNINSTALLER @@ -161,50 +240,35 @@ printf "It's recommended you run these steps manually.\n" printf "If you want to run the full script, open it in\n" printf "an editor and remove 'exit 1' from below.\n" exit 1 +source $VIRTUAL_ENV/bin/activate EOF -printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n" - if $UNSTABLE; then warning "Installing unstable library from source.\n\n" else printf "Installing stable library from pypi.\n\n" fi -cd library - -printf "Installing for Python 2..\n" -apt_pkg_install "${PY2_DEPS[@]}" +inform "Installing for $PYTHON_VER...\n" +apt_pkg_install "${APT_PACKAGES[@]}" if $UNSTABLE; then - python setup.py install > /dev/null + pip_pkg_install . else - pip install --upgrade $LIBRARY_NAME + pip_pkg_install $LIBRARY_NAME fi if [ $? -eq 0 ]; then success "Done!\n" - echo "pip uninstall $LIBRARY_NAME" >> $UNINSTALLER -fi - -if [ -f "/usr/bin/python3" ]; then - printf "Installing for Python 3..\n" - apt_pkg_install "${PY3_DEPS[@]}" - if $UNSTABLE; then - python3 setup.py install > /dev/null - else - pip3 install --upgrade $LIBRARY_NAME - fi - if [ $? -eq 0 ]; then - success "Done!\n" - echo "pip3 uninstall $LIBRARY_NAME" >> $UNINSTALLER - fi + echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER fi cd $WD +find_config + for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do CMD="${SETUP_CMDS[$i]}" - # Attempt to catch anything that touches /boot/config.txt and trigger a backup - if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then + # Attempt to catch anything that touches config.txt and trigger a backup + if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then do_config_backup fi eval $CMD @@ -214,10 +278,10 @@ for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do CONFIG_LINE="${CONFIG_TXT[$i]}" if ! [ "$CONFIG_LINE" == "" ]; then do_config_backup - inform "Adding $CONFIG_LINE to $CONFIG\n" - sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG - if ! grep -q "^$CONFIG_LINE" $CONFIG; then - printf "$CONFIG_LINE\n" >> $CONFIG + inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE\n" + sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE + if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then + printf "$CONFIG_LINE\n" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE fi fi done @@ -231,14 +295,17 @@ if [ -d "examples" ]; then fi fi -if [ -d "service" ]; then - inform "Grow includes a service that runs on boot and monitors your plants." - if confirm "Would you like to install the service?"; then - cd service - sudo ./install.sh - echo "rm /usr/bin/grow" >> $UNINSTALLER - echo "rm -r /usr/share/grow-monitor" >> $UNINSTALLER - echo "rm /etc/default/grow" >> $UNINSTALLER +printf "\n" + +if confirm "Would you like to generate documentation?"; then + pip_pkg_install pdoc + printf "Generating documentation.\n" + $PYTHON -m pdoc $MODULE_NAME -o $RESOURCES_DIR/docs > /dev/null + if [ $? -eq 0 ]; then + inform "Documentation saved to $RESOURCES_DIR/docs" + success "Done!" + else + warning "Error: Failed to generate documentation." fi fi diff --git a/library/.coveragerc b/library/.coveragerc deleted file mode 100644 index 8de38fd..0000000 --- a/library/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -source = grow -omit = - .tox/* diff --git a/library/LICENSE.txt b/library/LICENSE.txt deleted file mode 100644 index aed751a..0000000 --- a/library/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Pimoroni Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/library/README.md b/library/README.md deleted file mode 100644 index 2cce1a7..0000000 --- a/library/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Grow HAT Mini - -Designed as a tiny valet for your plants, Grow HAT mini will monitor the soil moiture for up to 3 plants, water them with tiny pumps, and show you their health on its small but informative screen. Learn more - https://shop.pimoroni.com/products/grow - -[![Build Status](https://travis-ci.com/pimoroni/enviroplus-python.svg?branch=master)](https://travis-ci.com/pimoroni/grow-python) -[![Coverage Status](https://coveralls.io/repos/github/pimoroni/grow-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/grow-python?branch=master) -[![PyPi Package](https://img.shields.io/pypi/v/enviroplus.svg)](https://pypi.python.org/pypi/growhat) -[![Python Versions](https://img.shields.io/pypi/pyversions/enviroplus.svg)](https://pypi.python.org/pypi/growhat) - -# Installing - -You're best using the "One-line" install method. - -## One-line (Installs from GitHub) - -``` -curl -sSL https://get.pimoroni.com/grow | bash -``` - -**Note** report issues with one-line installer here: https://github.com/pimoroni/get - -## Or... Install and configure dependencies from GitHub: - -* `git clone https://github.com/pimoroni/grow-python` -* `cd grow-python` -* `sudo ./install.sh` - -**Note** Raspbian Lite users may first need to install git: `sudo apt install git` - -## Or... Install from PyPi and configure manually: - -* Install dependencies: - -``` -sudo apt install python3-setuptools python3-pip python3-yaml python3-smbus python3-pil python3-spidev python3-rpi.gpio -``` - -* Run `sudo pip3 install growhat` - -**Note** this wont perform any of the required configuration changes on your Pi, you may additionally need to: - -* Enable i2c: `sudo raspi-config nonint do_i2c 0` -* Enable SPI: `sudo raspi-config nonint do_spi 0` -* Add the following to `/boot/config.txt`: `dtoverlay=spi0-cs,cs0_pin=14` - -## Monitoring - -You should read the following to get up and running with our monitoring script: - -* [Using and configuring monitor.py](examples/README.md) -* [Setting up monitor.py as a service](service/README.md) - -## Help & Support - -* GPIO Pinout - https://pinout.xyz/pinout/grow_hat_mini -* Support forums - http://forums.pimoroni.com/c/support -* Discord - https://discord.gg/hr93ByC - -# Changelog -0.0.2 ------ - -* Add mutually exclusive locking to pumps to avoid brownout running multiple pumps at once - -0.0.1 ------ - -* Initial Release diff --git a/library/setup.py b/library/setup.py deleted file mode 100644 index 725f19e..0000000 --- a/library/setup.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python - -""" -Copyright (c) 2020 Pimoroni - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -from setuptools import setup - - -setup() diff --git a/library/tox.ini b/library/tox.ini deleted file mode 100644 index 02c283d..0000000 --- a/library/tox.ini +++ /dev/null @@ -1,24 +0,0 @@ -[tox] -envlist = py,qa -skip_missing_interpreters = True - -[testenv] -commands = - python setup.py install - coverage run -m pytest -v -r wsx - coverage report -deps = - mock - pytest>=3.1 - pytest-cov - -[testenv:qa] -commands = - check-manifest --ignore tox.ini,tests/*,.coveragerc - python setup.py sdist bdist_wheel - twine check dist/* - flake8 --ignore E501 -deps = - check-manifest - flake8 - twine diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d30edaf --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,124 @@ +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[project] +name = "growhat" +dynamic = ["version", "readme"] +description = "Grow HAT Mini. A plant valet add-on for the Raspberry Pi" +license = {file = "LICENSE"} +requires-python = ">= 3.7" +authors = [ + { name = "Philip Howard", email = "phil@pimoroni.com" }, + { name = "Paul Beech", email = "paul@pimoroni.com" }, +] +maintainers = [ + { name = "Philip Howard", email = "phil@pimoroni.com" }, +] +keywords = [ + "Pi", + "Raspberry", +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Hardware", +] +dependencies = [ + "ltr559", + "st7735>=0.0.5", + "pyyaml", + "fonts", + "font-roboto" +] + +[project.urls] +GitHub = "https://www.github.com/pimoroni/grow-python" +Homepage = "https://www.pimoroni.com" + +[tool.hatch.version] +path = "grow/__init__.py" + +[tool.hatch.build] +include = [ + "grow", + "README.md", + "CHANGELOG.md", + "LICENSE" +] + +[tool.hatch.build.targets.sdist] +include = [ + "*" +] +exclude = [ + ".*", + "dist" +] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" +fragments = [ + { path = "README.md" }, + { text = "\n" }, + { path = "CHANGELOG.md" } +] + +[tool.ruff] +exclude = [ + '.tox', + '.egg', + '.git', + '__pycache__', + 'build', + 'dist' +] +line-length = 200 + +[tool.codespell] +skip = """ +./.tox,\ +./.egg,\ +./.git,\ +./__pycache__,\ +./build,\ +./dist.\ +""" + +[tool.isort] +line_length = 200 + +[tool.black] +line-length = 200 + +[tool.check-manifest] +ignore = [ + '.stickler.yml', + 'boilerplate.md', + 'check.sh', + 'install.sh', + 'uninstall.sh', + 'Makefile', + 'tox.ini', + 'tests/*', + 'examples/*', + '.coveragerc', + 'requirements-dev.txt', + 'requirements-examples.txt' +] + +[tool.pimoroni] +apt_packages = [] +configtxt = [] +commands = [] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..525b042 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,9 @@ +check-manifest +ruff +codespell +isort +twine +hatch +hatch-fancy-pypi-readme +tox +pdoc diff --git a/requirements-examples.txt b/requirements-examples.txt new file mode 100644 index 0000000..b5aa38f --- /dev/null +++ b/requirements-examples.txt @@ -0,0 +1,3 @@ +pyyaml +pillow>=10.0.0 +numpy \ No newline at end of file diff --git a/library/setup.cfg b/setup.cfg similarity index 100% rename from library/setup.cfg rename to setup.cfg diff --git a/library/tests/conftest.py b/tests/conftest.py similarity index 99% rename from library/tests/conftest.py rename to tests/conftest.py index a6f97c9..d1ac05b 100644 --- a/library/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ These allow the mocking of various Python modules that might otherwise have runtime side-effects. """ import sys + import mock import pytest from i2cdevice import MockSMBus diff --git a/library/tests/test_lock.py b/tests/test_lock.py similarity index 100% rename from library/tests/test_lock.py rename to tests/test_lock.py diff --git a/library/tests/test_setup.py b/tests/test_setup.py similarity index 96% rename from library/tests/test_setup.py rename to tests/test_setup.py index 988ca53..6ae8523 100644 --- a/library/tests/test_setup.py +++ b/tests/test_setup.py @@ -28,7 +28,7 @@ def test_moisture_read(GPIO, smbus): def test_pump_setup(GPIO, smbus): - from grow.pump import Pump, PUMP_PWM_FREQ + from grow.pump import PUMP_PWM_FREQ, Pump ch1 = Pump(channel=1) ch2 = Pump(channel=2) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..44c8654 --- /dev/null +++ b/tox.ini @@ -0,0 +1,34 @@ +[tox] +envlist = py,qa +skip_missing_interpreters = True +isolated_build = true +minversion = 4.0.0 + +[testenv] +commands = + coverage run -m pytest -v -r wsx + coverage report +deps = + mock + pytest>=3.1 + pytest-cov + build + +[testenv:qa] +commands = + check-manifest + python -m build --no-isolation + python -m twine check dist/* + isort --check . + ruff . + codespell . +deps = + check-manifest + ruff + codespell + isort + twine + build + hatch + hatch-fancy-pypi-readme + diff --git a/uninstall.sh b/uninstall.sh index 4848039..f213fc5 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,25 +1,72 @@ #!/bin/bash -LIBRARY_VERSION=`cat library/setup.cfg | grep version | awk -F" = " '{print $2}'` -LIBRARY_NAME=`cat library/setup.cfg | grep name | awk -F" = " '{print $2}'` +FORCE=false +LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME +PYTHON="python" -printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Uninstaller\n\n" -if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./uninstall.sh'\n" - exit 1 +venv_check() { + PYTHON_BIN=`which $PYTHON` + if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then + printf "This script should be run in a virtual Python environment.\n" + exit 1 + fi +} + +user_check() { + if [ $(id -u) -eq 0 ]; then + printf "Script should not be run as root. Try './uninstall.sh'\n" + exit 1 + fi +} + +confirm() { + if $FORCE; then + true + else + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi + fi +} + +prompt() { + read -r -p "$1 [y/N] " response < /dev/tty + if [[ $response =~ ^(yes|y|Y)$ ]]; then + true + else + false + fi +} + +success() { + echo -e "$(tput setaf 2)$1$(tput sgr0)" +} + +inform() { + echo -e "$(tput setaf 6)$1$(tput sgr0)" +} + +warning() { + echo -e "$(tput setaf 1)$1$(tput sgr0)" +} + +printf "$LIBRARY_NAME Python Library: Uninstaller\n\n" + +user_check +venv_check + +printf "Uninstalling for Python 3...\n" +$PYTHON -m pip uninstall $LIBRARY_NAME + +if [ -d $RESOURCES_DIR ]; then + if confirm "Would you like to delete $RESOURCES_DIR?"; then + rm -r $RESOURCES_DIR + fi fi -cd library - -printf "Unnstalling for Python 2..\n" -pip uninstall $LIBRARY_NAME - -if [ -f "/usr/bin/pip3" ]; then - printf "Uninstalling for Python 3..\n" - pip3 uninstall $LIBRARY_NAME -fi - -cd .. - printf "Done!\n"