From 994eab43c8b0f560071d7f22cff3dcf5912e9aa8 Mon Sep 17 00:00:00 2001 From: Jan-Frederik Schmidt Date: Sun, 9 Jun 2024 13:54:28 +0200 Subject: [PATCH] ci: Add GitHub actions pipeline --- .github/workflows/pipeline.yml | 100 ++++++++++++++++++ Manager.py | 3 +- backup.py | 2 +- pyproject.toml | 5 +- setup.cfg | 4 +- setupdb.py | 3 +- shopdb_entry.py | 2 +- src/shop_db2/api.py | 3 +- src/shop_db2/helpers/uploads.py | 3 +- src/shop_db2/routes/maintenance.py | 3 +- tests/base.py | 3 +- ...test_api_get_stocktaking_print_template.py | 1 + tests/unit/api/test_api_toggle_maintenance.py | 3 +- tox.ini | 21 ++-- wsgi.py | 3 +- 15 files changed, 139 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/pipeline.yml diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..d25457e --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,100 @@ +name: CI pipeline + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest] + python-version: ["3.8", "3.9"] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install wkhtmltopdf + run: | + if [ "$RUNNER_OS" == "Linux" ]; then + sudo apt-get install xvfb libfontconfig wkhtmltopdf + elif [ "$RUNNER_OS" == "macOS" ]; then + brew install wkhtmltopdf + fi + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install tox + run: python -m pip install --upgrade pip tox tox-gh-actions + + - name: Write example configuration file + run: cp configuration.example.py configuration.py + + - name: Run tests + run: tox + + - name: Upload coverage artifact + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: pytest-${{ matrix.python-version }} + path: reports/* + + coverage: + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: test + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Download all coverage artifacts + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: reports + + - uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Install tox + run: python -m pip install --upgrade pip tox + + - name: Combine coverage results + run: tox run -e combine-test-reports + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: reports/coverage.xml + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: 3.8 + + - name: Install tox + run: python -m pip install --upgrade tox + + - name: Run static checks + run: tox -e lint diff --git a/Manager.py b/Manager.py index 731fb89..a512574 100755 --- a/Manager.py +++ b/Manager.py @@ -5,9 +5,10 @@ from flask_migrate import Migrate, MigrateCommand from flask_script import Manager -import configuration as config from shop_db2.api import app, db, set_app +import configuration as config # isort: skip + set_app(config.ProductiveConfig) migrate = Migrate(app, db) manager = Manager(app) diff --git a/backup.py b/backup.py index f8a3966..0ede9ea 100755 --- a/backup.py +++ b/backup.py @@ -6,7 +6,7 @@ import sqlite3 import sys -from configuration import ProductiveConfig +from configuration import ProductiveConfig # isort: skip if __name__ == "__main__": _currentDate = datetime.datetime.now() diff --git a/pyproject.toml b/pyproject.toml index 395aa15..015e010 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,7 +95,7 @@ addopts = """ --ignore="migrations" --ignore="configuration.example.py" --ignore="docs/_scripts" - --cov=shop-db2 + --cov=shop_db2 --cov-config=pyproject.toml --cov-report= """ @@ -122,5 +122,8 @@ module = [ # Ignore packages that do not provide type hints here # For example, add "dash.*" to ignore all imports from Dash "sphinxawesome_theme.postprocess", + "sqlalchemy.*", + "flask_script.*", + "flask_migrate.*", ] ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg index 3c9dfa5..089fd0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,7 +10,7 @@ url = https://github.com/g3n35i5/shop-db2 [options] -python_requires = ~=3.8 +python_requires = >=3.8,<3.10 packages = find: package_dir = =src @@ -30,7 +30,7 @@ install_requires = MarkupSafe==1.1.1 nose==1.3.7 pdfkit==0.6.1 - Pillow==8.3.1 + Pillow==10.3.0 pycparser==2.19 PyJWT==1.7.1 python-dateutil==2.8.0 diff --git a/setupdb.py b/setupdb.py index 5be5881..7792599 100644 --- a/setupdb.py +++ b/setupdb.py @@ -9,12 +9,13 @@ from sqlalchemy.exc import IntegrityError -import configuration as config import shop_db2.exceptions as exc from shop_db2.api import app, db, set_app from shop_db2.helpers.users import insert_user from shop_db2.models import Rank, User +import configuration as config # isort: skip + def _get_password(): """Ask the user for a password and repeat it until both passwords match and diff --git a/shopdb_entry.py b/shopdb_entry.py index 9aa443f..be7ca39 100755 --- a/shopdb_entry.py +++ b/shopdb_entry.py @@ -5,7 +5,7 @@ import sys try: - import configuration as config + import configuration as config # isort: skip except ModuleNotFoundError: sys.exit( "No configuration file was found. Please make sure, " diff --git a/src/shop_db2/api.py b/src/shop_db2/api.py index af2b3e7..4de750f 100644 --- a/src/shop_db2/api.py +++ b/src/shop_db2/api.py @@ -5,9 +5,10 @@ from flask import Flask, jsonify from flask_bcrypt import Bcrypt -import configuration as config from shop_db2.shared import db +import configuration as config # isort: skip + app = Flask(__name__) # Default app settings (to suppress unittest warnings) will be overwritten. diff --git a/src/shop_db2/helpers/uploads.py b/src/shop_db2/helpers/uploads.py index a724c18..7b36f14 100644 --- a/src/shop_db2/helpers/uploads.py +++ b/src/shop_db2/helpers/uploads.py @@ -11,9 +11,10 @@ from PIL import Image -import configuration as config import shop_db2.exceptions as exc +import configuration as config # isort: skip + def insert_image(file: dict) -> str: if not file: diff --git a/src/shop_db2/routes/maintenance.py b/src/shop_db2/routes/maintenance.py index 70115e5..4924838 100644 --- a/src/shop_db2/routes/maintenance.py +++ b/src/shop_db2/routes/maintenance.py @@ -8,12 +8,13 @@ from flask import jsonify import shop_db2.exceptions as exc -from configuration import PATH from shop_db2.api import app from shop_db2.helpers.decorators import adminRequired from shop_db2.helpers.utils import json_body from shop_db2.helpers.validators import check_fields_and_types +from configuration import PATH # isort: skip + @app.route("/maintenance", methods=["GET"]) def get_maintenance_mode(): diff --git a/tests/base.py b/tests/base.py index a051209..8cf63ae 100644 --- a/tests/base.py +++ b/tests/base.py @@ -4,7 +4,6 @@ from flask_testing import TestCase -import configuration as config from shop_db2.api import app, bcrypt, db, set_app from shop_db2.models import ( AdminUpdate, @@ -20,6 +19,8 @@ User, ) +import configuration as config # isort: skip + # Global password storage. Hashing the passwords for each unit test # would take too long. For this reason, the passwords are created once # and then stored in this array. diff --git a/tests/unit/api/test_api_get_stocktaking_print_template.py b/tests/unit/api/test_api_get_stocktaking_print_template.py index 2b4aa24..1a0abaa 100644 --- a/tests/unit/api/test_api_get_stocktaking_print_template.py +++ b/tests/unit/api/test_api_get_stocktaking_print_template.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- __author__ = "g3n35i5" + import shop_db2.exceptions as exc from shop_db2.api import db from shop_db2.models import Product diff --git a/tests/unit/api/test_api_toggle_maintenance.py b/tests/unit/api/test_api_toggle_maintenance.py index f2e0414..0c2eb24 100644 --- a/tests/unit/api/test_api_toggle_maintenance.py +++ b/tests/unit/api/test_api_toggle_maintenance.py @@ -9,10 +9,11 @@ from flask import json import shop_db2.exceptions as exc -from configuration import PATH from shop_db2.api import app from tests.base_api import BaseAPITestCase +from configuration import PATH # isort: skip + class ToggleMaintenanceAPITestCase(BaseAPITestCase): @staticmethod diff --git a/tox.ini b/tox.ini index fa52f29..8a971db 100644 --- a/tox.ini +++ b/tox.ini @@ -1,19 +1,24 @@ [tox] envlist = lint - py38-test + {py38,py39}-test combine-test-reports isolated_build = True +[gh-actions] +python = + 3.8: py38-test + 3.9: py39-test [testenv:lint] description = Run static checkers. basepython = py38 extras = lint -passenv = CODEMETER_HOST +passenv = + RUNNER_OS commands = # Check import ordering - isort . --check + isort . --check --diff # Check formatting black . --check # Check type hinting @@ -25,14 +30,15 @@ commands = # pylint --load-plugins pylint.extensions.docparams src {posargs} -[testenv:py38-test] +[testenv:{py38,py39}-test] description = Run doc tests and unit tests. package = wheel extras = test setenv = PY_IGNORE_IMPORTMISMATCH=1 COVERAGE_FILE = reports{/}.coverage.{envname} -passenv = CODEMETER_HOST +passenv = + RUNNER_OS commands = # Run tests and doctests from .py files pytest --junitxml=reports/pytest.xml.{envname} {posargs} src/ tests/ @@ -43,7 +49,7 @@ description = Combine test and coverage data from multiple test runs. skip_install = true setenv = COVERAGE_FILE = reports/.coverage -depends = py38-test +depends = {py38,py39}-test deps = junitparser coverage[toml] @@ -56,7 +62,8 @@ commands = [testenv:build] description = Build the package. extras = build -passenv = CODEMETER_HOST +passenv = + RUNNER_OS commands = # Clean up build directories python -c 'from shutil import rmtree; rmtree("build", True); rmtree("dist", True)' diff --git a/wsgi.py b/wsgi.py index 53097b4..318a280 100755 --- a/wsgi.py +++ b/wsgi.py @@ -11,9 +11,10 @@ import gunicorn.app.base from gunicorn.six import iteritems -import configuration as config from shop_db2.api import app, set_app +import configuration as config # isort: skip + def number_of_workers(): return (multiprocessing.cpu_count() * 2) + 1