From 8a5b67189317c3dbd089a42f35008e2d5e00402d Mon Sep 17 00:00:00 2001 From: Serhii Koropets <33310880+koropets@users.noreply.github.com> Date: Tue, 11 Apr 2023 13:32:54 +0300 Subject: [PATCH] Update python up to 3.10 and github action versions (#1310) * Update python up to 3.10 and github actions * Fix python-version * Fix issue with Mapping in python 3.10 * Fix parse_version function for 0.1.dev1_g8efdbf5 case * Trying to fix borken github CI * Trying 3.9 for main.yml CI workflow * Revert "Trying 3.9 for main.yml CI workflow" This reverts commit 386c67fb9083ac147e3d3bce6b0a0dc6e4ebf205. * Revert "Trying to fix borken github CI" This reverts commit 0c05fd944ccd615096de078667b843570c3613be. * Update pytest * Revert "Revert "Trying to fix borken github CI"" This reverts commit d2326b93c5a734d7348c195cf8e6760c62f145b1. * debug_call.sh * Debug issue with CI * Debug issue with CI * pstree * ps aux * -n 0 for pytest * Update checkout and setup-python actions * Update cache action * Trying to run unit-test wihout postgreesql.kill() * only run builder tests * Revert "only run builder tests" This reverts commit 41a98c2e6f8f268617f700afb4986a949f55b84e. * strace in debug_call.sh * sudo * Refactor debug_call.sh * Run pytests * Install gordo * scripts/tests.sh * Run on xdist * formatting tests * Fix formatting unit-tests * Fix docker tests * Do not use xdist for CI tests * dockertest mark right condition * Trying to fix unit-tests * Fix allelse test * Debug tests.sh * PYTHONPATH * not dockertest * Try to fix not dockertest condition * Trying to fix issue with not dockertest * Try to fix tests * python 3.10 downgrade github actions * Remove debug code * rm debug_call.sh * Run 3.9 and 3.10 * Do not test for 3.9 * Fix syntax error in main.yml GitHub action * Remove -x argument from bash in main.yml GitHub action * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md * Update README.md. Update condition for main GitHub actions * Attempt to use xdist for slow tests * xdist and PYTHONPATH optional * Fix for usages --------- Co-authored-by: Roman Ivaniuk --- .github/workflows/main.yml | 16 +++-- .github/workflows/release.yml | 5 +- Dockerfile | 4 +- README.md | 67 ++++++++++--------- docker_push.sh | 80 ---------------------- gordo/machine/validators.py | 5 +- gordo/util/version.py | 4 +- requirements/test_requirements.in | 10 +-- requirements/test_requirements.txt | 46 ++++++------- scripts/tests.sh | 104 +++++++++++++++++++++++++++++ setup.cfg | 42 ------------ tests/gordo/util/test_version.py | 1 + 12 files changed, 185 insertions(+), 199 deletions(-) delete mode 100755 docker_push.sh create mode 100644 scripts/tests.sh delete mode 100644 setup.cfg diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3aea4f20..96bf1a269 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,7 +1,14 @@ name: CI on: + pull_request: + paths-ignore: + - '**.md' push: + branches: + - 'master' + paths-ignore: + - '**.md' jobs: test: @@ -9,15 +16,15 @@ jobs: strategy: matrix: os: [ubuntu-latest] - component: [builder, cli, client, machine, reporters, serializer, server, util, workflow, formatting, allelse] - python-version: [3.9] + component: [builder, cli, client, machine, reporters, serializer, server, util, workflow, formatting, allelse, docker] + python-version: ["3.10"] steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - architecture: 'x64' + architecture: "x64" - uses: actions/cache@v1 if: startsWith(runner.os, 'Linux') @@ -29,9 +36,8 @@ jobs: - name: Install run: | - pip install --upgrade "pip>=22.2.2,<23.0.0" pip install -r requirements/full_requirements.txt pip install -r requirements/test_requirements.txt - name: Test ${{ matrix.component }} - run: python setup.py test${{ matrix.component }} + run: bash scripts/tests.sh -n -p ${{ matrix.component }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fce0eb7e6..2be5e3696 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,18 +12,17 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.9] + python-version: ["3.10"] steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - architecture: 'x64' + architecture: "x64" - name: Install deps run: | - pip install --upgrade "pip>=22.2.2,<23.0.0" pip install -r requirements/full_requirements.txt - name: Build diff --git a/Dockerfile b/Dockerfile index 38e59bf5e..60880183a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Gordo base image -FROM python:3.9-bullseye as builder +FROM python:3.10-bullseye as builder # Copy source code COPY . /code @@ -17,7 +17,7 @@ RUN cat /code/requirements/full_requirements.txt | grep tensorflow== > /code/pre && cat /code/requirements/full_requirements.txt | grep scipy== >> /code/prereq.txt \ && cat /code/requirements/full_requirements.txt | grep catboost== >> /code/prereq.txt -FROM python:3.9-slim-bullseye +FROM python:3.10-slim-bullseye # Nonroot user for running CMD RUN groupadd -g 999 gordo && \ diff --git a/README.md b/README.md index 67810ac38..d71b48c7d 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,6 @@ --- - -## Table of content -* [About](#About) -* [Examples](#Examples) -* [Install](#Install) -* [Uninstall](#Uninstall) -* [Developer manual](#Developer-manual) - * [How to prepare working environment](#How-to-prepare-working-environment) - * [How to update packages](#How-to-update-packages) - * [How to run tests locally](#How-to-run-tests-locally) - * [Tests system requirements](#Tests-system-requirements) - * [Run tests](#Run-tests) - ## About Gordo fulfills the role of inhaling config files and supplying components to the pipeline of: @@ -40,15 +27,19 @@ Gordo fulfills the role of inhaling config files and supplying components to the 2. Training model 3. Serving model ---- - -## Examples +## Components -See our [example](./examples) notebooks for how to develop with `gordo` locally. +* [gordo-controller](https://github.com/equinor/gordo-controller/) - Kubernetes controller for the Gordo CRD. +* [gordo-core](https://github.com/equinor/gordo-core/) - Gordo core library. +* [gordo-client](https://github.com/equinor/gordo-client/) - Gordo server's client. It is able to make predictions from deployed models. --- +## Install + +[gordo-helm](https://github.com/equinor/gordo-helm) - you can use [gordo](https://github.com/equinor/gordo-helm/tree/main/charts/gordo) helm chart from this repository to deploy gordo infrastructure to your Kubernetes cluster. + +### Python package -## Install `pip install --upgrade gordo` With additional extras: @@ -57,26 +48,34 @@ With additional extras: Bleeding edge: `pip install git+https://github.com/equinor/gordo.git` -## Uninstall -`pip uninstall gordo` ## Developer manual + This section will explain how to start development of Gordo. -### How to prepare working environment -- Install pip-tools +### Setup + +Create and activate a virtual environment first. As a default option, it can be [venv](https://docs.python.org/3/library/venv.html) module. + +Install pip-tools ``` pip install --upgrade pip pip install --upgrade pip-tools ``` -- Install requirements +Install requirements ``` pip install -r requirements/full_requirements.txt pip install -r requirements/test_requirements.txt ``` +Install package: +``` +python3 setup.py install +``` + #### How to update packages + Note: you have to install `pip-tools` version higher then `6` for requirements to have same multi-line output format. To update some package in `full_requirements.txt`: @@ -86,22 +85,26 @@ To update some package in `full_requirements.txt`: pip-compile --upgrade --output-file=full_requirements.txt mlflow_requirements.in postgres_requirements.in requirements.in ``` -### How to run tests locally +### Examples + +See our [example](./examples) notebooks for how to develop with `gordo` locally. -#### Tests system requirements -To run tests it's required for your system to has (note: commands might differ from your OS): -- Running docker process; -- Available 5432 port for postgres container -(`postgresql` container is used, so better to stop your local instance for tests running). +### How to run tests locally -#### Run tests List of commands to run tests can be found [here](/setup.cfg). Running of tests takes some time, so it's faster to run tests in parallel: ``` -python3 setup.py test +pytest -n auto -m 'not dockertest' --ignore benchmarks +``` +Run docker-related tests: ``` +pytest -m 'dockertest' +``` + +> **_NOTE:_** To run tests it's required for your system to has (note: commands might differ from your OS): +> - Running docker daemon. +> - Available 5432 port for `postgres` container. > **_NOTE:_** this example is for Pycharm IDE to use `breakpoints` in the code of the tests. > On the configuration setup for test running add to `Additional arguments:` in `pytest` > section following string: `--ignore benchmarks --cov-report= --no-cov ` -> or TEMPORARY remove `--cov-report=xml` and `--cov=gordo` from `pytest.ini` file. diff --git a/docker_push.sh b/docker_push.sh deleted file mode 100755 index 30691f85f..000000000 --- a/docker_push.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -# Script to push an docker image to a docker registry. The docker image can -# either exist and be provided in the env variable DOCKER_IMAGE, or it will be -# built if DOCKER_IMAGE is empty and DOCKER_FILE is provided -# -# If $DOCKER_USERNAME is set then it will attempt to log in to the -# $DOCKER_REGISTRY using $DOCKER_USERNAME and DOCKER_PASSWORD, otherwise it -# assumes that you are already logged in. -# -# Pushes a docker image with name DOCKER_NAME, and tag tag with the 8 first -# chars of the git commit, and if the current commit is tagged it also pushes -# with those tags. So e.g. if git HEAD is commit "3f6f963" and with tag "0.0.2" -# then two tags are pushed, both "gordo-infrastructure/gordo-deploy:3f6f963" and -# "gordo-infrastructure/gordo-deploy:0.0.2" if DOCKER_NAME is -# "gordo-infrastructure/gordo-deploy". -# -# Expects the following environment variables to be set: -# DOCKER_NAME: Required. Docker name to push to. -# DOCKER_FILE: Semi-Required. Dockerfile to build. Either DOCKER_IMAGE or -# DOCKER_FILE must be set. -# DOCKER_IMAGE: Semi-Required. The local docker image to push. Either -# DOCKER_IMAGE or DOCKER_FILE must be set. -# DOCKER_USERNAME: If set then it uses it an the password to log in to the -# registry -# DOCKER_PASSWORD: If set then it uses it an the username to log in to the -# registry -# DOCKER_REGISTRY: Docker registry to push to. Defaults to -# auroradevacr.azurecr.io -# GORDO_PROD_MODE: If false then pushed tags will include a -dev suffix. -# Defaults to false -# DOCKER_REPO: The docker repository of concern - -if [[ -z "${DOCKER_REGISTRY}" ]]; then - echo "DOCKER_REGISTRY must be set, exiting" - exit 1 -fi - -if [[ -z "${DOCKER_NAME}" ]]; then - echo "DOCKER_NAME must be set, exiting" - exit 1 -fi - -if [[ -z "${DOCKER_USERNAME}" ]]; then - echo "DOCKER_USERNAME not set: we assume that you are already logged in to the docker registry." -else - # Logging in to the docker registry, exiting script if it fails - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin $DOCKER_REGISTRY || exit 1 -fi - -export tmp_tag=$(date +%Y-%m-%d) - -if [[ -z "${DOCKER_IMAGE}" ]]; then - if [[ -z "${DOCKER_FILE}" ]]; then - echo "DOCKER_IMAGE or DOCKER_FILE must be provided, exiting" - exit 1 - fi - docker build -t $tmp_tag -f $DOCKER_FILE . - export DOCKER_IMAGE=$tmp_tag -fi - -# Ensure we're getting the latest version, including any dirty state of the repo -# replacing any '+' development identifier with an underscore for docker compatibility -export version=$(docker run --rm $DOCKER_IMAGE gordo --version | tr + _) - - -if [[ -z "${GORDO_PROD_MODE}" ]]; then - echo "Skipping pushing of 'latest' image" -else - # if we're in prod mode, we'll push the latest image. - docker tag $DOCKER_IMAGE $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:latest - docker push $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:latest -fi - -docker tag $DOCKER_IMAGE $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:$version -docker push $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:$version - -git tag --points-at HEAD | while read -r tag ; do - docker tag $DOCKER_IMAGE $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:$tag - docker push $DOCKER_REGISTRY/$DOCKER_REPO/$DOCKER_NAME:$tag -done diff --git a/gordo/machine/validators.py b/gordo/machine/validators.py index 61a5f3783..d3cd51d04 100644 --- a/gordo/machine/validators.py +++ b/gordo/machine/validators.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import collections import copy import re import datetime @@ -8,6 +7,8 @@ import dateutil.parser import logging +from collections.abc import Mapping + from gordo.serializer import from_definition from gordo_core.sensor_tag import SensorTag @@ -162,7 +163,7 @@ def fix_runtime(runtime_dict): # We must also limit/request errors for key, val in runtime_dict.items(): - if isinstance(val, collections.Mapping): + if isinstance(val, Mapping): resource = val.get("resources") if resource: runtime_dict[key]["resources"] = fix_resource_limits(resource) diff --git a/gordo/util/version.py b/gordo/util/version.py index 936242aed..737bc5b4e 100644 --- a/gordo/util/version.py +++ b/gordo/util/version.py @@ -32,7 +32,7 @@ def get_version(self): return self.special.value -release_re = re.compile(r"^(\d{1,5})(\.(\d+)(\.(\d+)(.*?)?)?)?$") +release_re = re.compile(r"^(\d{1,5})(\.(\d+)((\.(\d+))?(.*?)?)?)?$") @dataclass(frozen=True) @@ -119,7 +119,7 @@ def parse_version( return GordoPR(number) m = release_re.match(gordo_version) if m: - (major, _, minor, _, patch, suffix) = m.groups() + (major, _, minor, _, _, patch, suffix) = m.groups() return GordoRelease( int(major), int(minor) if minor else None, diff --git a/requirements/test_requirements.in b/requirements/test_requirements.in index 3cc5e0181..8d0be0d61 100644 --- a/requirements/test_requirements.in +++ b/requirements/test_requirements.in @@ -1,12 +1,12 @@ -c full_requirements.txt docker>=4.0,<7.0 -pytest~=6.2 -pytest-xdist~=2.3 +pytest~=7.2 +pytest-xdist~=3.2 pytest-mock~=3.6 pytest-mypy~=0.9 -pytest-timeout~=1.4 -pytest-cov~=2.12 -pytest-benchmark~=3.4 +pytest-timeout~=2.1 +pytest-cov~=4.0 +pytest-benchmark~=4.0 mock~=4.0 responses~=0.13 # Due to packaging>22.0 in black 23.0, azureml-core~=1.49 requires packaging<22.0 diff --git a/requirements/test_requirements.txt b/requirements/test_requirements.txt index ba6176c75..a2d8c66c3 100644 --- a/requirements/test_requirements.txt +++ b/requirements/test_requirements.txt @@ -36,7 +36,7 @@ cffi==1.15.1 # via # -c full_requirements.txt # argon2-cffi-bindings -charset-normalizer==2.1.1 +charset-normalizer==3.1.0 # via # -c full_requirements.txt # requests @@ -44,7 +44,7 @@ click==8.1.3 # via # -c full_requirements.txt # black -coverage==6.5.0 +coverage[toml]==6.5.0 # via pytest-cov debugpy==1.6.3 # via ipykernel @@ -61,6 +61,8 @@ entrypoints==0.4 # -c full_requirements.txt # jupyter-client # nbconvert +exceptiongroup==1.1.1 + # via pytest execnet==1.9.0 # via pytest-xdist executing==1.2.0 @@ -133,7 +135,7 @@ mock==4.0.3 # via -r test_requirements.in mypy==0.982 # via pytest-mypy -mypy-extensions==0.4.3 +mypy-extensions==1.0.0 # via # -c full_requirements.txt # black @@ -177,7 +179,7 @@ pandocfilters==1.5.0 # via nbconvert parso==0.8.3 # via jedi -pathspec==0.10.2 +pathspec==0.11.1 # via # -c full_requirements.txt # black @@ -189,7 +191,7 @@ platformdirs==2.5.2 # via black pluggy==1.0.0 # via pytest -prometheus-client==0.15.0 +prometheus-client==0.16.0 # via # -c full_requirements.txt # jupyter-server @@ -205,10 +207,6 @@ ptyprocess==0.7.0 # terminado pure-eval==0.2.2 # via stack-data -py==1.11.0 - # via - # pytest - # pytest-forked py-cpuinfo==9.0.0 # via pytest-benchmark pycparser==2.21 @@ -217,7 +215,7 @@ pycparser==2.21 # cffi pyflakes==2.5.0 # via pytest-flakes -pygments==2.13.0 +pygments==2.14.0 # via # -c full_requirements.txt # ipython @@ -232,32 +230,29 @@ pysocks==1.7.1 # via # -c full_requirements.txt # requests -pytest==6.2.5 +pytest==7.2.2 # via # -r test_requirements.in # pytest-benchmark # pytest-cov # pytest-flakes - # pytest-forked # pytest-mock # pytest-mypy # pytest-timeout # pytest-xdist -pytest-benchmark==3.4.1 +pytest-benchmark==4.0.0 # via -r test_requirements.in -pytest-cov==2.12.1 +pytest-cov==4.0.0 # via -r test_requirements.in pytest-flakes==4.0.5 # via -r test_requirements.in -pytest-forked==1.4.0 - # via pytest-xdist pytest-mock==3.10.0 # via -r test_requirements.in pytest-mypy==0.10.0 # via -r test_requirements.in -pytest-timeout==1.4.2 +pytest-timeout==2.1.0 # via -r test_requirements.in -pytest-xdist==2.5.0 +pytest-xdist==3.2.1 # via -r test_requirements.in python-dateutil==2.8.2 # via @@ -270,7 +265,7 @@ pyzmq==24.0.1 # jupyter-server # nbclassic # notebook -requests[socks]==2.28.1 +requests[socks]==2.28.2 # via # -c full_requirements.txt # docker @@ -301,14 +296,13 @@ terminado==0.17.0 testpath==0.6.0 # via nbconvert toml==0.10.2 - # via - # pytest - # pytest-cov - # responses + # via responses tomli==1.2.3 # via # black + # coverage # mypy + # pytest tornado==6.2 # via # ipykernel @@ -348,12 +342,12 @@ types-toml==0.10.8 # via responses types-urllib3==1.26.25.1 # via types-requests -typing-extensions==4.4.0 +typing-extensions==4.5.0 # via # -c full_requirements.txt # black # mypy -urllib3==1.26.13 +urllib3==1.26.15 # via # -c full_requirements.txt # docker @@ -363,7 +357,7 @@ wcwidth==0.2.5 # via prompt-toolkit webencodings==0.5.1 # via bleach -websocket-client==1.4.2 +websocket-client==1.5.1 # via # -c full_requirements.txt # docker diff --git a/scripts/tests.sh b/scripts/tests.sh new file mode 100644 index 000000000..bd856f318 --- /dev/null +++ b/scripts/tests.sh @@ -0,0 +1,104 @@ +#/usr/bin/env bash + +set -e + +function show_help() { + echo "Usage: $0 [-h] ACTION" + echo + echo "Runs CI pytest action." + echo + echo "-n uses xdist to speedup slow-running tests" + echo "-p export PYTHONPATH=. environment variable. Helpful when gordo is not being installed in the system" + echo "-h display this help and exit" + exit $1 +} + + +while getopts "nph" opt; do + case "$opt" in + n) + use_xdist="true" + ;; + p) + export_pythonpath="true" + ;; + h) + show_help 0 + ;; + esac +done + +shift $((OPTIND-1)) + +action=$1 + +if [ -n "$export_pythonpath" ]; then + export PYTHONPATH=. +fi + +if [ -n "$use_xdist" ]; then + slow_args="-n auto" +else + slow_args="-n 0" +fi + +case "$action" in + all) + pytest $slow_args -m "not dockertest" --ignore benchmarks + ;; + builder) + pytest $slow_args -m "not dockertest" tests/gordo/builder + ;; + cli) + pytest $slow_args -m "not dockertest" tests/gordo/cli + ;; + machine) + pytest $slow_args -m "not dockertest" tests/gordo/machine + ;; + server) + pytest $slow_args -m "not dockertest" tests/gordo/server + ;; + reporters) + pytest -m "not dockertest" tests/gordo/reporters + ;; + serializer) + pytest -m "not dockertest" tests/gordo/serializer + ;; + client) + pytest -m "not dockertest" tests/gordo/client + ;; + util) + pytest -m "not dockertest" tests/gordo/util + ;; + workflow) + pytest -m "not dockertest" tests/gordo/workflow + ;; + formatting) + pytest -m "not dockertest" tests/test_formatting.py + ;; + allelse) + pytest -m "not dockertest" --ignore tests/gordo/builder \ + --ignore tests/gordo/cli \ + --ignore tests/gordo/client \ + --ignore tests/gordo/machine \ + --ignore tests/gordo/reporters \ + --ignore tests/gordo/serializer \ + --ignore tests/gordo/server \ + --ignore tests/gordo/util \ + --ignore tests/gordo/watchman \ + --ignore tests/gordo/workflow \ + --ignore tests/test_formatting.py \ + --ignore benchmarks \ + . + ;; + docker) + pytest -m "dockertest" -m dockertest + ;; + benchmarks) + pytest --benchmark-only benchmarks/ + ;; + *) + echo "Wrong action '$action'." 1>&2 + show_help 2 + ;; +esac diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 14a460488..000000000 --- a/setup.cfg +++ /dev/null @@ -1,42 +0,0 @@ -[aliases] - -# All tests in parallel, minus any requiring docker -test = pytest --addopts "-n auto -m 'not dockertest' --ignore benchmarks" - -# Test _everything_ -testall = pytest --addopts "--ignore benchmarks" - -# Only run tests which use docker -testdocker = pytest --addopts "-m 'dockertest'" - -# Component level filtering of tests -testbuilder = pytest --addopts "tests/gordo/builder" -testcli = pytest --addopts "tests/gordo/cli" -testclient = pytest --addopts "tests/gordo/client" -testmachine = pytest --addopts "tests/gordo/machine" -testmodel = pytest --addopts "tests/gordo/machine/model" -testreporters = pytest --addopts "tests/gordo/reporters" -testserializer = pytest --addopts "tests/gordo/serializer" -testserver = pytest --addopts "tests/gordo/server" -testutil = pytest --addopts "tests/gordo/util" -testworkflow = pytest --addopts "tests/gordo/workflow" - -# Black formatting -testformatting = pytest --addopts "tests/test_formatting.py" - -# all else, ie mypy, flakes, examples, etc. -testallelse = pytest --addopts - "--ignore tests/gordo/builder - --ignore tests/gordo/cli - --ignore tests/gordo/client - --ignore tests/gordo/machine - --ignore tests/gordo/reporters - --ignore tests/gordo/serializer - --ignore tests/gordo/server - --ignore tests/gordo/util - --ignore tests/gordo/watchman - --ignore tests/gordo/workflow - --ignore tests/test_formatting.py - --ignore benchmarks" - -testbenchmarks = pytest --addopts "--benchmark-only benchmarks/" diff --git a/tests/gordo/util/test_version.py b/tests/gordo/util/test_version.py index c07b071a9..48fb68624 100644 --- a/tests/gordo/util/test_version.py +++ b/tests/gordo/util/test_version.py @@ -30,6 +30,7 @@ def test_release(): [ ("1.2.3", GordoRelease(1, 2, 3)), ("3.4.5dev2", GordoRelease(3, 4, 5, "dev2")), + ("0.1.dev0", GordoRelease(0, 1, None, ".dev0")), ("5.7", GordoRelease(5, 7)), ("latest", GordoSpecial(Special.LATEST)), ("pr-43", GordoPR(43)),