diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..ddee38e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +name: Build + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + name: Python ${{ matrix.python }} + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.9', '3.10', '3.11'] + + env: + RELEASE_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name || github.sha }}-py${{ matrix.python }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + + - name: Install Dependencies + run: | + make dev-deps + + - name: Build Packages + run: | + make dist + + - name: Upload Packages + uses: actions/upload-artifact@v3 + with: + name: ${{ env.RELEASE_FILE }} + path: dist/ diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml new file mode 100644 index 0000000..18305d2 --- /dev/null +++ b/.github/workflows/qa.yml @@ -0,0 +1,32 @@ +name: QA + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + name: linting & spelling + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Set up Python '3,11' + uses: actions/setup-python@v3 + with: + python-version: '3.11' + + - name: Install Dependencies + run: | + make dev-deps + + - name: Run Quality Assurance + run: | + make qa + + - name: Run Code Checks + run: | + make check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 725c8ac..016a678 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: Python Tests +name: Tests on: pull_request: @@ -8,30 +8,34 @@ on: jobs: test: + name: Python ${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: - python: [2.7, 3.5, 3.7, 3.9] + python: ['3.9', '3.10', '3.11'] steps: - - uses: actions/checkout@v2 + - name: Checkout Code + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python }} + - name: Install Dependencies run: | - python -m pip install --upgrade setuptools tox + make dev-deps + - name: Run Tests - working-directory: library run: | - tox -e py + make pytest + - name: Coverage + if: ${{ matrix.python == '3.9' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - working-directory: library run: | python -m pip install coveralls coveralls --service=github - if: ${{ matrix.python == '3.9' }} diff --git a/library/CHANGELOG.txt b/CHANGELOG.md similarity index 70% rename from library/CHANGELOG.txt rename to CHANGELOG.md index 0f98d12..a9e0187 100644 --- a/library/CHANGELOG.txt +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +# Changelog + 0.0.1 ----- diff --git a/LICENSE b/LICENSE index aed751a..edd3445 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Pimoroni Ltd. +Copyright (c) 2023 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 diff --git a/Makefile b/Makefile index 0813324..760b4b8 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.md from README.md + library/CHANGELOG.txt" - @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 -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}'" - -tag: - git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" +dev-deps: + python3 -m pip install -r requirements-dev.txt + sudo apt install dos2unix -python-readme: library/README.md - -python-license: library/LICENSE.txt +check: + @bash check.sh -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 +qa: + tox -e qa -library/LICENSE.txt: LICENSE - cp LICENSE library/LICENSE.txt +pytest: + tox -e py -python-wheels: python-readme python-license - cd library; python3 setup.py bdist_wheel - cd library; python setup.py bdist_wheel +nopost: + @bash check.sh --nopost -python-sdist: python-readme python-license - cd library; python setup.py sdist +tag: + git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}" -python-clean: - -rm -r library/dist - -rm -r library/build - -rm -r library/*.egg-info +build: check + @hatch build -python-dist: python-clean python-wheels python-sdist - ls library/dist +clean: + -rm -r dist -python-testdeploy: python-dist - twine upload --repository-url https://test.pypi.org/legacy/ library/dist/* +testdeploy: build + twine upload --repository-url https://test.pypi.org/legacy/ dist/* -python-deploy: check python-dist - twine upload library/dist/* +deploy: nopost build + twine upload dist/* diff --git a/PROJECT_NAME/__init__.py b/PROJECT_NAME/__init__.py new file mode 100644 index 0000000..b41daff --- /dev/null +++ b/PROJECT_NAME/__init__.py @@ -0,0 +1,5 @@ +__version__ = '0.0.1' + + +def test(): + return "Hello World" diff --git a/README.md b/README.md index 5afaa35..1cf33f1 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ -# {{TITLE}} +# __TITLE__ -[![Build Status](https://shields.io/github/workflow/status/pimoroni/{{LIBNAME}}-python/Python%20Tests.svg)](https://github.com/pimoroni/{{LIBNAME}}-python/actions/workflows/test.yml) -[![Coverage Status](https://coveralls.io/repos/github/pimoroni/{{LIBNAME}}-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/{{LIBNAME}}-python?branch=master) -[![PyPi Package](https://img.shields.io/pypi/v/{{LIBNAME}}.svg)](https://pypi.python.org/pypi/{{LIBNAME}}) -[![Python Versions](https://img.shields.io/pypi/pyversions/{{LIBNAME}}.svg)](https://pypi.python.org/pypi/{{LIBNAME}}) +[![Build Status](https://img.shields.io/github/actions/workflow/status/pimoroni/PROJECT_NAME-python/test.yml?branch=main)](https://github.com/pimoroni/PROJECT_NAME-python/actions/workflows/test.yml) +[![Coverage Status](https://coveralls.io/repos/github/pimoroni/PROJECT_NAME-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/PROJECT_NAME-python?branch=master) +[![PyPi Package](https://img.shields.io/pypi/v/PROJECT_NAME.svg)](https://pypi.python.org/pypi/PROJECT_NAME) +[![Python Versions](https://img.shields.io/pypi/pyversions/PROJECT_NAME.svg)](https://pypi.python.org/pypi/PROJECT_NAME) + +Generated from [the Pimoroni Python Boilerplate](https://github.com/pimoroni/boilerplate-python). # Pre-requisites @@ -18,13 +20,13 @@ You can optionally run `sudo raspi-config` or the graphical Raspberry Pi Configu Stable library from PyPi: -* Just run `pip3 install {{LIBNAME}}` +* Just run `pip3 install PROJECT_NAME` In some cases you may need to use `sudo` or install pip with: `sudo apt install python3-pip` Latest/development library from GitHub: -* `git clone https://github.com/pimoroni/{{LIBNAME}}-python` -* `cd {{LIBNAME}}-python` +* `git clone https://github.com/pimoroni/PROJECT_NAME-python` +* `cd PROJECT_NAME-python` * `sudo ./install.sh` diff --git a/_bootstrap.md b/_bootstrap.md deleted file mode 100644 index 3fea21e..0000000 --- a/_bootstrap.md +++ /dev/null @@ -1,77 +0,0 @@ -* Python Library - library/name/__init__.py -* At least *one* test - library/tests/test_setup.py -* Examples - -# In Detail - -To get started, copy the contents of this repository (excluding the .git directory) and run `_bootstrap.sh` to highlight substitutions you need to make. - -Be careful to copy *all* files, including those starting with a . such as `.travis.yml`. - -A Makefile is provided to automate some tests, checks and package building. You should use it! - -## Library - -### Structure - -Libraries should be singleton if they pertain to a HAT or pHAT and class-based if they are for breakouts with selectable addresses. - -A singleton library would work like this: - -``` -import library - -library.do_something() -``` - -Whereas a class-based library requires an instance of its class: - -``` -import library - -device = library.Library() - -device.do_something() -``` - -### Linting - -You should ensure you either run `flake8` while writing the library, or use an IDE/Editor with linting. - -All libraries (and example code) should stick to PEP8 style guides, although you can ignore long line warnings (E501) if you feel they're unwarranted. - -### Testing - -At least one test script should be used to prevent obvious errors and omissions from creeping into the library. - -You should use `tox` to run the test scripts: - -``` -sudo pip install tox -cd library/ -tox -``` - -Make sure your main branch is named "main" or GitHub actions wont run! - -## Examples - -Examples should use hyphens and short, descriptive names, ie: `rainbow-larson.py` - -Examples should include a `print()` front-matter introducing them, ie: - -``` -print("""rainbow-larson.py - Display a larson scanning rainbow - -Press Ctrl+C to exit. - -""") -``` - -# Deployment - -Before deploying you should `make python-testdeploy` and verify that the package looks good and is installable from test PyPi. - -You should also `make check` to catch common errors, including mismatched version numbers, trailing whitespace and DOS line-endings. - -Subsequent to deployment you should `git tag -a "vX.X.X" -m "Version X.X.X"` and `git push origin master --follow-tags` to tag a release on GitHub that matches the deployed code. diff --git a/_bootstrap.sh b/_bootstrap.sh deleted file mode 100755 index 8b88e7d..0000000 --- a/_bootstrap.sh +++ /dev/null @@ -1,5 +0,0 @@ -printf "\nOutstanding substitutions:\n" -grep -Irn --color "{{[A-Z:]*}}" - -printf "\nOutstanding directory renames:\n" -find . -regex ".*{{[A-Z]*}}" diff --git a/_bootstrap_remotes.sh b/_bootstrap_remotes.sh deleted file mode 100755 index c29ba5f..0000000 --- a/_bootstrap_remotes.sh +++ /dev/null @@ -1,3 +0,0 @@ -git remote rename origin boilerplate -git remote set-url --push boilerplate no_push -git remote add origin $1 diff --git a/boilerplate.md b/boilerplate.md new file mode 100644 index 0000000..254b724 --- /dev/null +++ b/boilerplate.md @@ -0,0 +1,130 @@ +# Python Library Boilerplate + +Source: https://github.com/pimoroni/boilerplate-python + +This Python Library boilerplate uses `pyproject.toml`, with a `hatchling` back-end. + +`tox` is used to manage testing and QA, with `ruff` and `pytest`. + +The tooling uses `build` as the front-end, but this is not essential. `python3 -m pip install .` will work just as well. + +- [Get Started](#get-started) +- [Prepare Your Project](#prepare-your-project) +- [Linting](#linting) +- [Testing](#testing) +- [Examples](#examples) +- [Install / Uninstall Scripts](#install--uninstall-scripts) +- [Licensing](#licensing) +- [Git Remotes](#git-remotes) +- [Deployment](#deployment) + +## Get Started + +To get started, clone the contents of this repository. + +You can also use it as a template, by starting here: https://github.com/pimoroni/boilerplate-python/generate + +You should replace all instances of `PROJECT_NAME` with your project name. This should be a valid, Python package name. See: https://peps.python.org/pep-0008/#package-and-module-names + +You can find outstanding replacements in your local copy with: + +``` +grep -Irn --color "__TITLE__" +grep -Irn --color "__DESCRIPTION__" +grep -Irn --color "PROJECT_NAME" +``` + +For the most part you will need to edit: + +* `pyproject.toml` - pay attention to `authors` and `maintainers` and `keywords` +* `README.md` - title and introduce your project! Replace `pimoroni/PROJECT_NAME-python` with your own GitHub URL. +* `LICENSE` - update the license accordingly. You may relicense as you see fit. + +Make sure you rename the `PROJECT_NAME` directory to match your chosen name. + +A Makefile is provided to automate some tests, checks and package building. You should use it! + +You should delete this file (`boilerplate.md`) from your project once you've set everything up. + +## Prepare Your Project + +* Create your Python library. +* Write at least *one* test. +* Write some examples! +* Fill out `CHANGELOG.md` + +## Linting + +You should ensure you either run `ruff` while writing the library, or use an IDE/Editor with linting. + +All libraries (and example code) should stick to PEP8 style guides, although you can ignore long line warnings (E501) if you feel they're unwarranted. + +You can run linting with `tox` by running: + +``` +make test-deps +make qa +``` + +`tox` will run `ruff`, in addition to `check-manifest` to ensure your GitHub repository and Python package are in sync, and `twine check` to ensure valid packages are being built. It will also run `codespell` to spell-check your code. Both `ruff` and `codespell` can be run from the project root, and will pick up configuration from `pyproject.toml` if you need faster checks while you work. + +## Testing + +At least one test script should be used to prevent obvious errors and omissions from creeping into the library. + +You should use `tox` to run the test scripts: + +``` +make test-deps +make pytest +``` + +Make sure your main branch is named "main" or GitHub actions won't run! + +## Examples + +Examples should use hyphens and short, descriptive names, ie: `rainbow_larson.py` + +Examples should include a `print()` front-matter introducing them, ie: + +```python +print("""rainbow-larson.py - Display a larson scanning rainbow + +Press Ctrl+C to exit. + +""") +``` + +## Install / Uninstall Scripts + +If your package directory (`PROJECT_NAME/`) differs from your library name, you should update `install.sh` and `uninstall.sh` and hard-code the correct library name. + +## Licensing + +This boilerplate should be considered CC-BY 4.0 - https://creativecommons.org/licenses/by/4.0/legalcode + +The included MIT License is provided as an example (and because it's what we use, so it makes it easier to bootstrap our projects). You do not need to release projects based upon this boilerplate or modifications to the tools/scripts herein under the MIT License. + +*However* you must provide attribution by means of a link to https://github.com/pimoroni/boilerplate-python in your `README.md` + +## Git Remotes + +If you have cloned `boilerplate-python` and want to point your local copy at a new git remote: + +``` +git remote rename origin boilerplate +git remote set-url --push boilerplate no_push +git remote add origin https://github.com/you_name/your_project +``` + +## Deployment + +Before deploying you should `make testdeploy` and verify that the package looks good and is installable from test PyPi. + +You can use `hatch version post` to bump the `.postN` version number if you need to make changes and preview them on test PyPi. + +Make sure to *remove* the `.postN` version before release, the Makefile will try to check this for you. + +You should also `make check` to catch common errors, including mismatched version numbers, trailing whitespace and DOS line-endings. + +Before deployment you should `git tag -a "vX.X.X" -m "Version X.X.X"` (or `make tag`). Once you're happy with the release you can `git push origin master --follow-tags` to tag a release on GitHub that matches the deployed code. If you're using branch protection, we'll assume you know how to amend these commands! diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..cbb1565 --- /dev/null +++ b/check.sh @@ -0,0 +1,87 @@ +#!/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))}'` + +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/README.md b/examples/README.md new file mode 100644 index 0000000..df635b4 --- /dev/null +++ b/examples/README.md @@ -0,0 +1 @@ +# Examples diff --git a/install-legacy.sh b/install-legacy.sh deleted file mode 100755 index 3572e50..0000000 --- a/install-legacy.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/bin/bash - -CONFIG=/boot/config.txt -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 -WD=`pwd` -USAGE="sudo ./install.sh (--unstable)" -POSITIONAL_ARGS=() -FORCE=false -UNSTABLE=false -CODENAME=`lsb_release -sc` - -if [[ $CODENAME == "bullseye" ]]; then - bash ./install.sh $@ - exit $? -fi - -user_check() { - if [ $(id -u) -ne 0 ]; then - printf "Script must be run as root. Try 'sudo ./install.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)" -} - -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 - mkdir -p $RESOURCES_TOP_DIR/config-backups/ - cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME - if [ -f "$UNINSTALLER" ]; then - echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER - fi - fi -} - -function apt_pkg_install { - PACKAGES=() - PACKAGES_IN=("$@") - for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do - PACKAGE="${PACKAGES_IN[$i]}" - if [ "$PACKAGE" == "" ]; then continue; fi - printf "Checking for $PACKAGE\n" - dpkg -L $PACKAGE > /dev/null 2>&1 - if [ "$?" == "1" ]; then - PACKAGES+=("$PACKAGE") - fi - done - PACKAGES="${PACKAGES[@]}" - if ! [ "$PACKAGES" == "" ]; then - echo "Installing missing packages: $PACKAGES" - if [ ! $APT_HAS_UPDATED ]; then - apt update - APT_HAS_UPDATED=true - fi - apt install -y $PACKAGES - if [ -f "$UNINSTALLER" ]; then - echo "apt uninstall -y $PACKAGES" - fi - fi -} - -while [[ $# -gt 0 ]]; do - K="$1" - case $K in - -u|--unstable) - UNSTABLE=true - shift - ;; - -f|--force) - FORCE=true - shift - ;; - *) - if [[ $1 == -* ]]; then - printf "Unrecognised option: $1\n"; - printf "Usage: $USAGE\n"; - exit 1 - fi - POSITIONAL_ARGS+=("$1") - shift - esac -done - -user_check - -apt_pkg_install python-configparser - -CONFIG_VARS=`python - < $UNINSTALLER -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 -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[@]}" -if $UNSTABLE; then - python setup.py install > /dev/null -else - pip install --upgrade $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 -fi - -cd $WD - -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 - do_config_backup - fi - eval $CMD -done - -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 - fi - fi -done - -if [ -d "examples" ]; then - if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then - inform "Copying examples to $RESOURCES_DIR" - cp -r examples/ $RESOURCES_DIR - echo "rm -r $RESOURCES_DIR" >> $UNINSTALLER - success "Done!" - fi -fi - -printf "\n" - -if [ -f "/usr/bin/pydoc" ]; then - printf "Generating documentation.\n" - pydoc -w $LIBRARY_NAME > /dev/null - if [ -f "$LIBRARY_NAME.html" ]; then - cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html - rm -f $LIBRARY_NAME.html - inform "Documentation saved to $RESOURCES_DIR/docs.html" - success "Done!" - else - warning "Error: Failed to generate documentation." - fi -fi - -success "\nAll done!" -inform "If this is your first time installing you should reboot for hardware changes to take effect.\n" -inform "Find uninstall steps in $UNINSTALLER\n" diff --git a/install.sh b/install.sh index 28d3d46..6071e5f 100755 --- a/install.sh +++ b/install.sh @@ -1,10 +1,11 @@ #!/bin/bash +LIBRARY_NAME=`grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}'` +LIBRARY_VERSION=`grep __version__ $LIBRARY_NAME/__init__.py | awk -F" = " '{print substr($2,2,length($2)-2)}'` CONFIG=/boot/config.txt 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 WD=`pwd` USAGE="sudo ./install.sh (--unstable)" POSITIONAL_ARGS=() @@ -14,8 +15,8 @@ PYTHON="/usr/bin/python3" 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 } @@ -84,10 +85,10 @@ 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" fi @@ -130,29 +131,22 @@ fi PYTHON_VER=`$PYTHON --version` -inform "Installing. Please wait..." +printf "$LIBRARY_NAME ($LIBRARY_VERSION) Python Library: Installer\n\n" -$PYTHON -m pip install --upgrade configparser +inform "Checking Dependencies. Please wait..." + +$PYTHON -m pip install --upgrade toml CONFIG_VARS=`$PYTHON - < /dev/null + $PYTHON -m pip install . else $PYTHON -m pip install --upgrade $LIBRARY_NAME fi diff --git a/library/.coveragerc b/library/.coveragerc deleted file mode 100644 index dfa463a..0000000 --- a/library/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -source = {{LIBNAME}} -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/MANIFEST.in b/library/MANIFEST.in deleted file mode 100644 index 1071cd8..0000000 --- a/library/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include CHANGELOG.txt -include LICENSE.txt -include README.md -include setup.py -recursive-include {{LIBNAME}} *.py diff --git a/library/pyproject.toml b/library/pyproject.toml deleted file mode 100644 index 2f21011..0000000 --- a/library/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools>=40.8.0", "wheel"] -build-backend = "setuptools.build_meta" diff --git a/library/setup.cfg b/library/setup.cfg deleted file mode 100644 index 2cd4422..0000000 --- a/library/setup.cfg +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -[metadata] -name = {{LIBNAME}} -version = 0.0.1 -author = Philip Howard -author_email = phil@pimoroni.com -description = {{DESCRIPTION}} -long_description = file: README.md -long_description_content_type = text/markdown -keywords = Raspberry Pi -url = https://www.pimoroni.com -project_urls = - GitHub=https://www.github.com/pimoroni/{{LIBNAME}}-python -license = MIT -# This includes the license file(s) in the wheel. -# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file -license_files = LICENSE.txt -classifiers = - Development Status :: 4 - Beta - Operating System :: POSIX :: Linux - License :: OSI Approved :: MIT License - Intended Audience :: Developers - Programming Language :: Python :: 2.7 - Programming Language :: Python :: 3 - Topic :: Software Development - Topic :: Software Development :: Libraries - Topic :: System :: Hardware - -[options] -python_requires = >= 2.7 -packages = {{LIBNAME}} -install_requires = - -[flake8] -exclude = - .tox, - .eggs, - .git, - __pycache__, - build, - dist -ignore = - E501 - -[pimoroni] -py2deps = -py3deps = -configtxt = -commands = diff --git a/library/setup.py b/library/setup.py deleted file mode 100755 index afb1ee1..0000000 --- a/library/setup.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Copyright (c) 2016 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, __version__ -from pkg_resources import parse_version - -minimum_version = parse_version('30.4.0') - -if parse_version(__version__) < minimum_version: - raise RuntimeError("Package setuptools must be at least version {}".format(minimum_version)) - -setup() diff --git a/library/tests/test_setup.py b/library/tests/test_setup.py deleted file mode 100644 index e69de29..0000000 diff --git a/library/tox.ini b/library/tox.ini deleted file mode 100644 index 687f305..0000000 --- a/library/tox.ini +++ /dev/null @@ -1,25 +0,0 @@ -[tox] -envlist = py{27,35,37,39},qa -skip_missing_interpreters = True - -[testenv] -commands = - python setup.py install - coverage run -m py.test -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/library/{{LIBNAME}}/__init__.py b/library/{{LIBNAME}}/__init__.py deleted file mode 100644 index 2192205..0000000 --- a/library/{{LIBNAME}}/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -__version__ = '0.0.1' diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d80e486 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,113 @@ +[build-system] +requires = ["hatchling", "hatch-fancy-pypi-readme"] +build-backend = "hatchling.build" + +[project] +name = "PROJECT_NAME" +dynamic = ["version", "readme"] +description = "__DESCRIPTION__" +license = {file = "LICENSE"} +requires-python = ">= 3.7" +authors = [ + { name = "Philip Howard", email = "phil@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 = [] + +[project.urls] +GitHub = "https://www.github.com/pimoroni/PROJECT_NAME-python" +Homepage = "https://www.pimoroni.com" + +[tool.hatch.version] +path = "PROJECT_NAME/__init__.py" + +[tool.hatch.build] +include = [ + "PROJECT_NAME", + "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.check-manifest] +ignore = [ + '.stickler.yml', + 'boilerplate.md', + 'check.sh', + 'install.sh', + 'uninstall.sh', + 'Makefile', + 'tox.ini', + 'tests/*', + 'examples/*', + '.coveragerc', + 'requirements-dev.txt' +] + +[pimoroni] +apt_packages = ["python3-rpi.gpio", "python3-smbus"] +configtxt = [] +commands = [] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..08e9dec --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,8 @@ +check-manifest +ruff +codespell +isort +twine +hatch +hatch-fancy-pypi-readme +tox diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e1a0810 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(scope='function', autouse=False) +def test(): + yield None diff --git a/tests/test_setup.py b/tests/test_setup.py new file mode 100644 index 0000000..85e84bd --- /dev/null +++ b/tests/test_setup.py @@ -0,0 +1,7 @@ +def test_setup(test): + assert test is None + + +def test_version(): + import PROJECT_NAME + assert PROJECT_NAME.__version__ == '0.0.1' diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..090fdb0 --- /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 --format=github . + codespell . +deps = + check-manifest + ruff + codespell + isort + twine + build + hatch + hatch-fancy-pypi-readme + diff --git a/uninstall.sh b/uninstall.sh index 4848039..d82fc39 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -1,25 +1,63 @@ #!/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)}'` +LIBRARY_VERSION=`grep __version__ $LIBRARY_NAME/__init__.py | awk -F" = " '{print substr($2,2,length($2)-2)}'` +RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME +PYTHON="/usr/bin/python3" + +user_check() { + if [ $(id -u) -eq 0 ]; then + printf "Script should not be run as root. Try './install.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 $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 -fi - -cd library +user_check -printf "Unnstalling for Python 2..\n" -pip uninstall $LIBRARY_NAME +printf "Uninstalling for Python 3...\n" +$PYTHON -m pip uninstall $LIBRARY_NAME -if [ -f "/usr/bin/pip3" ]; then - printf "Uninstalling for Python 3..\n" - pip3 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 .. - printf "Done!\n"