diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e7d7eee..1dac879 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -127,7 +127,7 @@ repos: hooks: - id: mypy verbose: true - args: [--show-error-codes] + args: ["--show-error-codes", "--install-types", "--non-interactive"] additional_dependencies: ["pytest", "types-requests"] - repo: https://github.com/astral-sh/ruff-pre-commit diff --git a/LICENSE b/LICENSE index 579da11..6267c66 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ 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. \ No newline at end of file +SOFTWARE. diff --git a/README.md b/README.md index 2fdd306..6377318 100644 --- a/README.md +++ b/README.md @@ -23,46 +23,52 @@ Python must be available, preferably in a virtual environment (venv). Some environment variables are used by various source code and scripts. Setup your environment as follows (note that "source" is used) -~~~~ + +```console source ./bin/environment.sh -~~~~ +``` It is recommended that a Python virtual environment be created. We have provided several convenience scripts to create and activate a virtual environment. To create a new virtual environment using these convenience scripts, execute the following (this will create a directory called "venv" in your current working directory): -~~~~ + +```console $PROJECT_DIR/bin/venv.sh -~~~~ +``` Once your virtual enviornment has been created, it can be activated -as follows (note: you *must* activate the virtual environment +as follows (note: you _must_ activate the virtual environment for it to be used, and the command requires "source" to ensure environment variables to support venv are established correctly): -~~~~ + +```console source $PROJECT_DIR/bin/vactivate.sh -~~~~ +``` Install the required libraries as follows: -~~~~ + +```console pip install -r requirements.txt -~~~~ +``` Note that if you wish to run test cases then you will need to also install "pytest" (it is not installed by default as it is a development rather than product dependency). -~~~~ + +```console pip install pytest -~~~~ +``` ## Creating a Docker Image -A Dockefile is provided for this service. A docker image for this +A Dockefile is provided for this service. A docker image for this service can be creating using the following script: -~~~~ + +```console $PROJECT_DIR/bin/dockerize.sh -~~~~ +``` ## Starting the Service @@ -73,7 +79,7 @@ can be found [here](https://github.com/brodagroupsoftware/osc-dm-mesh-srv) A standalone server can be started for testing purposes using the following command: -~~~~ -$PROJECT_DIR/app/start.sh -~~~~ +```console +$PROJECT_DIR/app/start.sh +``` diff --git a/app/start.sh b/app/start.sh index de8e886..c40494e 100755 --- a/app/start.sh +++ b/app/start.sh @@ -27,4 +27,4 @@ export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python HOST="0.0.0.0" ; PORT=8000 ; CONFIGURATION="./config/config.yaml" ; -python ./src/server.py --host $HOST --port $PORT --configuration $CONFIGURATION \ No newline at end of file +python ./src/server.py --host $HOST --port $PORT --configuration $CONFIGURATION diff --git a/app/startd.sh b/app/startd.sh index 4a51a8e..33845e1 100755 --- a/app/startd.sh +++ b/app/startd.sh @@ -17,7 +17,7 @@ if [ -z ${ROOT_DIR+x} ] ; then fi # Show the environment -$PROJECT_DIR/bin/show.sh +"$PROJECT_DIR"/bin/show.sh usage() { echo " " @@ -39,15 +39,15 @@ echo "HOSTNAME: $HOSTNAME <--- This is the hostname running in docker" echo " " NETWORK_NAME="localnet" -docker network create $NETWORK_NAME +docker network create "$NETWORK_NAME" compose() { - docker-compose -f $PROJECT_DIR/docker/docker-compose.yml up + docker-compose -f "$PROJECT_DIR"/docker/docker-compose.yml up } decompose() { - docker-compose -f $PROJECT_DIR/docker/docker-compose.yml down + docker-compose -f "$PROJECT_DIR"/docker/docker-compose.yml down } compose; -decompose; \ No newline at end of file +decompose; diff --git a/bin/dockerize.sh b/bin/dockerize.sh index 0a98521..05bc952 100755 --- a/bin/dockerize.sh +++ b/bin/dockerize.sh @@ -18,16 +18,16 @@ POSITIONAL_ARGS=() PUBLISH="false" # default value while [[ $# -gt 0 ]]; do - POSITIONAL_ARGS+=("$1") - case $1 in + POSITIONAL_ARGS+=("$1") + case $1 in -p|--publish) - PUBLISH="true" - shift #move past arg - ;; + PUBLISH="true" + shift #move past arg + ;; *) # ignore other args - shift - ;; - esac + shift + ;; + esac done function showHelp { @@ -75,9 +75,9 @@ echo " " prepare() { echo "Preparing..." - cp $PROJECT_DIR/requirements.txt $WORKING_DIR - cp $PROJECT_DIR/docker/Dockerfile $WORKING_DIR - cp $PROJECT_DIR/src/*.py $WORKING_DIR + cp "$PROJECT_DIR/requirements.txt" "$WORKING_DIR" + cp "$PROJECT_DIR/docker/Dockerfile" "$WORKING_DIR" + cp "$PROJECT_DIR/src/*.py" "$WORKING_DIR" } cleanup() { @@ -93,7 +93,7 @@ build() { echo "Building Docker image $IMAGE_NAME:$VERSION" echo "Images built using version $VERSION and latest" - docker build --build-arg GITHUB_TOKEN=$GITHUB_TOKEN \ + docker build --build-arg GITHUB_TOKEN="$GITHUB_TOKEN" \ -t "$IMAGE_NAME:$VERSION" \ -t "$IMAGE_NAME:latest" \ -f Dockerfile . \ @@ -101,61 +101,61 @@ build() { } dockerhub_login() { - echo "logging in to docker hub with username $DOCKERHUB_USERNAME" - docker login -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_TOKEN" - LOGIN_RETURN=$? - - if [[ $LOGIN_RETURN -eq 0 ]]; then - echo "Successfully logged into dockerhub as $DOCKERHUB_USERNAME" - return 0 - else - echo "Could not log into dockerhub as $DOCKERHUB_USERNAME" - return 1 - fi + echo "logging in to docker hub with username $DOCKERHUB_USERNAME" + docker login -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_TOKEN" + LOGIN_RETURN=$? + + if [[ "$LOGIN_RETURN" -eq 0 ]]; then + echo "Successfully logged into dockerhub as $DOCKERHUB_USERNAME" + return 0 + else + echo "Could not log into dockerhub as $DOCKERHUB_USERNAME" + return 1 + fi } dockerhub_logout() { - echo "logging out from dockerhub" - docker logout - LOGOUT_RETURN=$? - if [[ $LOGOUT_RETURN -eq 0 ]]; then - echo "Successfully logged out of dockerhub" - else - echo "problem occurred logging out of dockerhub!" - fi + echo "logging out from dockerhub" + docker logout + LOGOUT_RETURN=$? + if [[ "$LOGOUT_RETURN" -eq 0 ]]; then + echo "Successfully logged out of dockerhub" + else + echo "problem occurred logging out of dockerhub!" + fi } publish() { - echo "Pushing image to DockerHub" - docker push "$IMAGE_NAME" - DOCKER_RETURN=$? - - if [[ $DOCKER_RETURN -eq 0 ]]; then - echo "Image successfully published!" - else - echo "Attempt to publish docker image failed!" - fi + echo "Pushing image to DockerHub" + docker push "$IMAGE_NAME" + DOCKER_RETURN=$? + + if [[ "$DOCKER_RETURN" -eq 0 ]]; then + echo "Image successfully published!" + else + echo "Attempt to publish docker image failed!" + fi } echo "Changing directory to sandbox directory ($WORKING_DIR)" mkdir -p "$WORKING_DIR" -cd "$WORKING_DIR" +cd "$WORKING_DIR" || exit prepare; cleanup; build; if [[ "$PUBLISH" == "true" ]]; then - dockerhub_login; - PUBLISH_LOGIN_SUCCESS=$? - if [[ $PUBLISH_LOGIN_SUCCESS -eq 0 ]]; then - publish; - else - echo "Not publishing due to login failure" - dockerhub_logout; - exit 1 - fi + dockerhub_login; + PUBLISH_LOGIN_SUCCESS=$? + if [[ "$PUBLISH_LOGIN_SUCCESS" -eq 0 ]]; then + publish; + else + echo "Not publishing due to login failure" + dockerhub_logout; + exit 1 + fi - dockerhub_logout; - exit 0 -fi \ No newline at end of file + dockerhub_logout; + exit 0 +fi diff --git a/bin/environment.sh b/bin/environment.sh index 428acab..077c5f0 100755 --- a/bin/environment.sh +++ b/bin/environment.sh @@ -20,4 +20,4 @@ export ROOT_DIR="$HOME_DIR" export PROJECT="osc-dm-monitor-srv" export PROJECT_DIR="$ROOT_DIR/$PROJECT" -$PROJECT_DIR/bin/show.sh \ No newline at end of file +"$PROJECT_DIR"/bin/show.sh diff --git a/bin/vactivate.sh b/bin/vactivate.sh index e571215..f904654 100755 --- a/bin/vactivate.sh +++ b/bin/vactivate.sh @@ -34,10 +34,11 @@ fi VENV_NAME="venv" if [[ "$OSTYPE" == "msys" ]]; then # For Ms/Windows - source $PROJECT_DIR/$VENV_NAME/Scripts/activate + # shellcheck disable=SC1090 + source "$PROJECT_DIR/$VENV_NAME/Scripts/activate" else # For Linux/MacOS - source $PROJECT_DIR/$VENV_NAME/bin/activate + # shellcheck disable=SC1090 + source "$PROJECT_DIR/$VENV_NAME/bin/activate" fi echo "Activated virtual environment: $VENV_NAME" - diff --git a/bin/venv.sh b/bin/venv.sh index d8f0e77..9b996cf 100755 --- a/bin/venv.sh +++ b/bin/venv.sh @@ -24,6 +24,6 @@ function showHelp { } # Create the virtual environment in a directory called -cd $PROJECT_DIR +cd "$PROJECT_DIR" || exit NAME="venv" python3 -m venv $NAME diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7d58190..f8cf391 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -14,4 +14,4 @@ services: networks: localnet: - external: true \ No newline at end of file + external: true diff --git a/pyproject.toml b/pyproject.toml index afc4f9c..afd7f81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "osc-dm-monitor-srv" -version = "v0.1.1" +dynamic = ["version"] description = "Ecosystem Platform Monitor." authors = [ { name = "Eric Broda", email = "93649628+ModeSevenIndustrialSolutions@users.noreply.github.com" }, @@ -71,7 +71,7 @@ dependencies = [ [tool.setuptools.package-data] itr = [ - "py.typed" + "py.typed", "types-PyYAML" ] [project.urls] @@ -83,7 +83,7 @@ Documentation = "https://github.com/brodagroupsoftware/osc-dm-mesh-doc" "Source Code" = "https://github.com/os-climate/osc-dm-monitor-srv" [build-system] -requires = ["pdm-backend"] +requires = ["pdm-backend", "types-PyYAML"] build-backend = "pdm.backend" [tool.pdm.scripts] diff --git a/src/bgsexception.py b/src/bgsexception.py index 72c17ec..8ac9fee 100644 --- a/src/bgsexception.py +++ b/src/bgsexception.py @@ -16,4 +16,4 @@ class BgsNotFoundException(BgsException): """ def __init__(self, message, original_exception=None): super().__init__(message) - self.original_exception = original_exception \ No newline at end of file + self.original_exception = original_exception diff --git a/src/server.py b/src/server.py index 742700c..a464b0d 100644 --- a/src/server.py +++ b/src/server.py @@ -6,17 +6,19 @@ # # Created: 2024-04-15 by eric.broda@brodagroupsoftware.com +import asyncio + # Library imports import logging import uvicorn as uvicorn -from fastapi import FastAPI, HTTPException import yaml -import asyncio +from fastapi import FastAPI, HTTPException + +import state # Project imports import utilities -import state # Set up logging LOGGING_FORMAT = \ @@ -173,4 +175,4 @@ async def _repeat_every(interval_sec, func, *args): except Exception as e: logger.info(f"STOPPING service, exception:{e}") finally: - logger.info(f"TERMINATING service on host:{args.host} port:{args.port}") \ No newline at end of file + logger.info(f"TERMINATING service on host:{args.host} port:{args.port}") diff --git a/src/utilities.py b/src/utilities.py index 13bf250..04d5e49 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -6,9 +6,10 @@ # # Created: 2024-04-15 by eric.broda@brodagroupsoftware.com -from typing import List, Optional, Dict, Any -import httpx import logging +from typing import Any, Dict, List, Optional + +import httpx from bgsexception import BgsException, BgsNotFoundException @@ -133,4 +134,4 @@ def shttprequest(host: str, port: int, service: str, method: str, except Exception as e: msg = f"Unexpected error for {url}: {e}" logger.error(msg) - raise BgsException(msg) \ No newline at end of file + raise BgsException(msg) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..63ee0d2 --- /dev/null +++ b/tox.ini @@ -0,0 +1,125 @@ +# Tox configuration file +# Read more under https://tox.wiki/ + +[gh-actions] +python = + 3.11: py311 + +[tox] +minversion = 3.24 +env_list = py{39,310,311,312},lint +isolated_build = True + +[testenv] +description = Invoke pytest to run automated tests +deps = + pdm + pydantic>=2.3.0 + pytest +setenv = + TOXINIDIR = {toxinidir} + PDM_IGNORE_SAVED_PYTHON="1" +passenv = + HOME +commands = + pdm install --dev + pytest test + +[testenv:tests] +allowlist_externals = + pdm + pytest +commands = + pdm install --dev + pytest test + +[testenv:lint] +description = Perform static analysis and style checks +passenv = + HOMEPATH + PROGRAMDATA +deps = + pdm + pre-commit +allowlist_externals = + pdm + pre-commit +commands = + pdm install -G lint + pre-commit run --all-files {posargs:--show-diff-on-failure} + +[testenv:{build,clean}] +description = + build: Build the package in isolation according to PEP517, see https://github.com/pypa/build + clean: Remove old distribution files and temporary build artifacts (./build and ./dist) +skip_install = True +changedir = {toxinidir} +deps = + build: pdm + build: tox-pdm + build: build[virtualenv] +allowlist_externals = + python + pre-commit + pdm + tox +commands = + clean: python -c 'import shutil; [shutil.rmtree(p, True) for p in ("build", "dist", "docs/_build")]' + clean: python -c 'import pathlib, shutil; [shutil.rmtree(p, True) for p in pathlib.Path("src").glob("*.egg-info")]' + build: pdm install + build: pdm build {posargs} + +[testenv:cov] +usedevelop = True +deps = + pytest-cov +commands = pytest --cov-report=html {posargs} + +[testenv:{docs,doctests,linkcheck}] +description = + docs: Invoke sphinx-build to build the docs + doctests: Invoke sphinx-build to run doctests + linkcheck: Check for broken links in the documentation +setenv = + DOCSDIR = {toxinidir}/docs + BUILDDIR = {toxinidir}/docs/_build + docs: BUILD = html + doctests: BUILD = doctest + linkcheck: BUILD = linkcheck +allowlist_externals = + sphinx + sphinx-build +commands = + sphinx-build --color -b {env:BUILD} -d "{env:BUILDDIR}/doctrees" "{env:DOCSDIR}" "{env:BUILDDIR}/{env:BUILD}" {posargs} + +# [testenv:publish] +# description = +# Publish the package you have been developing to a package index server. +# By default, it uses testpypi. If you really want to publish your package +# to be publicly accessible in PyPI, use the `-- --repository pypi` option. +# skip_install = True +# changedir = {toxinidir} +# passenv = +# # See: https://twine.readthedocs.io/en/latest/ +# TWINE_USERNAME +# TWINE_PASSWORD +# TWINE_REPOSITORY +# TWINE_REPOSITORY_URL +# deps = twine +# allowlist_externals = +# pdm +# twine +# commands = +# pdm publish --no-build + +[tool.flake8] +exclude = tests/* +count = True +max-line-length = 160 +max-complexity = 10 +# Allow __init__ files to have unused imports. +per-file-ignores = __init__.py:F401 +extend-ignore = + # Allow spacing before colon (to favor Black). + E203 + E501