From 03dd1a485d7dc8993ab79edff5da3456d60f08ba Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Tue, 18 Jan 2022 18:08:24 +0100 Subject: [PATCH] Add continuous integration pipeline Added basic continuous integration tests to run on any push: On main branch, the python program is built. On tagged commit, a gitea release is created. Fixed first detected build pipeline issues: Fixing mypy library stubs missing for some imported libraries. Fixed two small typing errors for Repetitions. The steps run on basic python containers, onto which the ci steps simply install poetry. This takes a little more processing time during pipeline running (~16s per step), but also gives a lot of flexibility in container usage. Added script which assists in creating an automatic release by extracting the current version and newest changes from the semantic changelog. This is then used in the gitea release preparation as title and content of the release message. The files built in the dist directory by poetry will be attached. --- .woodpecker.yml | 75 ++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 2 +- pyproject.toml | 10 +++++ src/habitmove/__init__.py | 2 +- src/habitmove/repetitions.py | 7 +++- tools/extract-changelog.py | 56 +++++++++++++++++++++++++++ 6 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 .woodpecker.yml create mode 100644 tools/extract-changelog.py diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..e50afec --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,75 @@ +branches: main + +pipeline: + code_lint: + image: python + commands: + - pip install poetry + - poetry install + - pip install black + - echo "----------------- running lint ------------------" + - python --version && poetry --version && black --version + - poetry run black . + + unit_tests: + image: thekevjames/nox + commands: + - pip install poetry + - poetry install + - echo "----------------- running tests ------------------" + - python --version && poetry --version && nox --version + - poetry run nox + + static_analysis: + image: python + commands: + - pip install poetry + - poetry install + - pip install mypy + - echo "----------------- running analysis ------------------" + - python --version && poetry --version && mypy --version + - poetry run mypy . + + build_dist: + image: python + commands: + - pip install poetry + - poetry install + - echo "----------------- running analysis ------------------" + - python --version && poetry --version + - poetry build + when: + branch: main + + release_prep: + image: python + commands: + - echo "----------------- preparing release ------------------" + - python tools/extract-changelog.py + + gitea_release: + image: plugins/gitea-release + settings: + api_key: + from_secret: gitea_release_token + base_url: https://git.martyoeh.me + files: dist/* + title: NEWEST_VERSION.md + note: NEWEST_CHANGES.md + when: + event: tag + tag: v* + + notify_matrix: + image: plugins/matrix + settings: + homeserver: https://matrix.org + roomid: + from_secret: matrix_roomid + userid: + from_secret: matrix_userid + accesstoken: + from_secret: matrix_token + when: + status: [ success, failure ] + diff --git a/CHANGELOG.md b/CHANGELOG.md index a03a334..723aa00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project tries to adhere to [Semantic Versioning](https://semver.org/spe * Compatible with Python stretching back to version 3.7 -## [0.4] - 2021-12-06 +## [0.4.0] - 2021-12-06 ### Added diff --git a/pyproject.toml b/pyproject.toml index c0716f4..468c0db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,3 +38,13 @@ show_missing = true [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[[tool.mypy.overrides]] +module = [ + "click", + "click.testing", + "pytest", + "nox", + "importlib-metadata" +] +ignore_missing_imports = true diff --git a/src/habitmove/__init__.py b/src/habitmove/__init__.py index fcbc880..6c60766 100644 --- a/src/habitmove/__init__.py +++ b/src/habitmove/__init__.py @@ -4,6 +4,6 @@ import sys try: from importlib.metadata import version as metadata_version except ImportError: - from importlib_metadata import version as metadata_version + from importlib_metadata import version as metadata_version # type: ignore __version__ = str(metadata_version(__name__)) diff --git a/src/habitmove/repetitions.py b/src/habitmove/repetitions.py index 0d1ffa9..63ce416 100644 --- a/src/habitmove/repetitions.py +++ b/src/habitmove/repetitions.py @@ -55,10 +55,11 @@ def habit_list_add_ids(c: sqlite3.Cursor, habitlist: list[Habit]) -> dict[int, H :return habit_id_dict: The habit collection as a dict with the keys consisting of the habit's sqlite database ID. """ - with_id = {} + with_id: dict[int, Habit] = {} for h in habitlist: sql_id = fetch_habit_id(c, h.uuid or "") - with_id[sql_id] = h + if sql_id is not None: + with_id[sql_id] = h return with_id @@ -74,6 +75,8 @@ def fetch_habit_id(cursor: sqlite3.Cursor, uuid: str) -> Optional[int]: if id is not None: return id[0] + return None + def add_to_database( cursor: sqlite3.Cursor, habits: dict[int, Habit], repetition: Repetition diff --git a/tools/extract-changelog.py b/tools/extract-changelog.py new file mode 100644 index 0000000..e14de09 --- /dev/null +++ b/tools/extract-changelog.py @@ -0,0 +1,56 @@ +import re + +## Extracts the version and newest changes from a semantic changelog. +# +# Important, it only works with three-parted version numbers +# a-la 1.2.3 or 313.01.1888 -- needs \d.\d.\d to work. +# +# The version number and changeset will be put in `NEWEST_VERSION.md` +# and `NEWEST_CHANGES.md` respectively, for further use in releases. +OUTPUT_FILE_VERSION = "NEWEST_VERSION.md" +OUTPUT_FILE_CHANGES = "NEWEST_CHANGES.md" + + +def getVersion(file): + for line in file: + m = re.match(r"^## \[(\d+\.\d+\.\d+)\]", line) + if m and m.group(1): + return m.group(1) + + +def getSection(file): + inRecordingMode = False + for line in file: + if not inRecordingMode: + if re.match(r"^## \[\d+\.\d+\.\d+\]", line): + inRecordingMode = True + elif re.match(r"^## \[\d+\.\d+\.\d+\]", line): + inRecordingMode = False + break + elif re.match(r"^$", line): + pass + else: + yield line + + +def toFile(fname, content): + file = open(fname, "w") + file.write(content) + file.close() + + +with open("CHANGELOG.md") as file: + title = getVersion(file) + print(title) + toFile(OUTPUT_FILE_VERSION, title) + +with open("CHANGELOG.md") as file: + newest_changes_gen = getSection(file) + newest_changes = "" + for line in newest_changes_gen: + newest_changes += line + print("[Extracted Changelog]") + print(newest_changes) + toFile(OUTPUT_FILE_CHANGES, newest_changes) + +file.close()