diff --git a/.gitignore b/.gitignore index 7bac8768..e0a583ec 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +*-testresults.xml +test-output.xml # Translations *.mo diff --git a/.pipelines/azdo-base-pipeline.yml b/.pipelines/azdo-base-pipeline.yml index 926b404f..665a25f0 100644 --- a/.pipelines/azdo-base-pipeline.yml +++ b/.pipelines/azdo-base-pipeline.yml @@ -1,26 +1,65 @@ -# this pipeline should be ignored for now -parameters: - pipelineType: 'training' - -steps: -- script: | - flake8 --output-file=$(Build.BinariesDirectory)/lint-testresults.xml --format junit-xml - workingDirectory: '$(Build.SourcesDirectory)' - displayName: 'Run code quality tests' - enabled: 'true' - -- script: | - pytest --junitxml=$(Build.BinariesDirectory)/unit-testresults.xml $(Build.SourcesDirectory)/tests/unit - displayName: 'Run unit tests' - enabled: 'true' - env: - SP_APP_SECRET: '$(SP_APP_SECRET)' - -- task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '$(Build.BinariesDirectory)/*-testresults.xml' - testRunTitle: 'Linting & Unit tests' - failTaskOnFailedTests: true - displayName: 'Publish linting and unit test results' - enabled: 'true' +jobs: +- job: BuildEnvironment + displayName: "Set up CI environment" + steps: + - task: AzureCLI@1 + displayName: Generate build container + name: BuildContainer + inputs: + azureSubscription: AzureResourceConnection + scriptLocation: inlineScript + inlineScript: | + set -euxo pipefail # fail on error + + # Get name and server of the container registry linked to the Azure ML workspace. + acrId=$(az resource show -g "$RESOURCE_GROUP" -n "$WORKSPACE_NAME" --resource-type Microsoft.MachineLearningServices/workspaces --query properties.containerRegistry -o tsv) + read -r acrName acrServer <<< $(az resource show --id "$acrId" --query '[[name,properties.loginServer]]' -o tsv) + + dockerDir=environment_setup/build-image + # Generate an tag with a reproducible checksum of all files in $dockerDir by doing a checksum of all files + # in alphabetical order, then another checksum of their names and checksums. + imageTag=$(find "$dockerDir" -type f -exec md5sum {} \; | sort -k 2 | md5sum | cut -f1 -d ' ') + + # If the image with the generated tag doesn't already exist, build it. + repo="modelbuild/$(BUILDCONTAINER_NAME)" + if ! az acr repository show -n $acrName --image "$repo:$imageTag" -o table; then + az acr build \ + -r "$acrName" \ + -t "$repo:$imageTag" \ + -t "$repo:latest" \ + "$dockerDir" + fi + + # Make image name available to subsequent jobs. + echo "##vso[task.setvariable variable=BUILD_IMAGE;isOutput=true]$acrServer/$repo:$imageTag" + +- job: "Model_CI" + displayName: "Model CI" + dependsOn: BuildEnvironment + variables: + BUILD_IMAGE: $[ dependencies.BuildEnvironment.outputs['BuildContainer.BUILD_IMAGE'] ] + container: + image: $(BUILD_IMAGE) + endpoint: ContainerRegistry + timeoutInMinutes: 0 + steps: + + - script: | + tox + displayName: 'Linting & unit tests' + + - task: PublishTestResults@2 + condition: succeededOrFailed() + inputs: + testResultsFiles: '*-testresults.xml' + testRunTitle: 'Linting & Unit tests' + failTaskOnFailedTests: true + displayName: 'Publish test results' + + - task: PublishCodeCoverageResults@1 + displayName: 'Publish coverage report' + condition: succeededOrFailed() + inputs: + codeCoverageTool: Cobertura + summaryFileLocation: 'coverage.xml' + failIfCoverageEmpty: true diff --git a/.pipelines/azdo-ci-build-train.yml b/.pipelines/azdo-ci-build-train.yml index e1ed0dc6..9ca472d5 100644 --- a/.pipelines/azdo-ci-build-train.yml +++ b/.pipelines/azdo-ci-build-train.yml @@ -7,26 +7,26 @@ trigger: exclude: - docs/ - environment_setup/ - - ml_service/util/create_scoring_image.* + - ml_service/util/create_scoring_image.py - ml_service/util/smoke_test_scoring_service.py variables: - template: azdo-variables.yml - group: devopsforai-aml-vg +pool: + vmImage: ubuntu-latest stages: - stage: 'Model_CI' displayName: 'Model CI' jobs: - - job: "Model_CI_Pipeline" - displayName: "Model CI Pipeline" - pool: - vmImage: 'ubuntu-latest' + - template: azdo-base-pipeline.yml + - job: "Publish_Pipeline" + displayName: "Publish AML Pipeline" container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: - - template: azdo-base-pipeline.yml - task: AzureCLI@1 inputs: azureSubscription: '$(WORKSPACE_SVC_CONNECTION)' @@ -35,7 +35,7 @@ stages: set -e # fail on error export SUBSCRIPTION_ID=$(az account show --query id -o tsv) # Invoke the Python building and publishing a training pipeline - python $(Build.SourcesDirectory)/ml_service/pipelines/${{ variables.BUILD_TRAIN_SCRIPT }} + python ml_service/pipelines/${{ variables.BUILD_TRAIN_SCRIPT }} displayName: 'Publish Azure Machine Learning Pipeline' - stage: 'Trigger_AML_Pipeline' @@ -44,8 +44,6 @@ stages: - job: "Get_Pipeline_ID" condition: and(succeeded(), eq(coalesce(variables['auto-trigger-training'], 'true'), 'true')) displayName: "Get Pipeline ID for execution" - pool: - vmImage: 'ubuntu-latest' container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: @@ -56,7 +54,7 @@ stages: inlineScript: | set -e # fail on error export SUBSCRIPTION_ID=$(az account show --query id -o tsv) - python $(Build.SourcesDirectory)/ml_service/pipelines/run_train_pipeline.py --output_pipeline_id_file "pipeline_id.txt" --skip_train_execution + python ml_service/pipelines/run_train_pipeline.py --output_pipeline_id_file "pipeline_id.txt" --skip_train_execution # Set AMLPIPELINEID variable for next AML Pipeline task in next job AMLPIPELINEID="$(cat pipeline_id.txt)" echo "##vso[task.setvariable variable=AMLPIPELINEID;isOutput=true]$AMLPIPELINEID" @@ -89,8 +87,6 @@ stages: - job: "Training_Run_Report" dependsOn: "Run_ML_Pipeline" displayName: "Determine if evaluation succeeded and new model is registered" - pool: - vmImage: 'ubuntu-latest' container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: @@ -102,8 +98,6 @@ stages: jobs: - job: "Deploy_ACI" displayName: "Deploy to ACI" - pool: - vmImage: 'ubuntu-latest' container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: @@ -137,8 +131,6 @@ stages: jobs: - job: "Deploy_AKS" displayName: "Deploy to AKS" - pool: - vmImage: 'ubuntu-latest' container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: @@ -173,8 +165,6 @@ stages: jobs: - job: "Deploy_Webapp" displayName: "Deploy to Webapp" - pool: - vmImage: 'ubuntu-latest' container: mcr.microsoft.com/mlops/python:latest timeoutInMinutes: 0 steps: diff --git a/.pipelines/azdo-pr-build-train.yml b/.pipelines/azdo-pr-build-train.yml index 24231b2a..b1be1958 100644 --- a/.pipelines/azdo-pr-build-train.yml +++ b/.pipelines/azdo-pr-build-train.yml @@ -4,16 +4,12 @@ pr: include: - master -pool: +pool: vmImage: 'ubuntu-latest' -container: mcr.microsoft.com/mlops/python:latest - - variables: - template: azdo-variables.yml - group: devopsforai-aml-vg - -steps: -- template: azdo-base-pipeline.yml \ No newline at end of file +jobs: +- template: azdo-base-pipeline.yml diff --git a/.pipelines/azdo-template-get-model-version.yml b/.pipelines/azdo-template-get-model-version.yml index f69f3366..4a7dd39d 100644 --- a/.pipelines/azdo-template-get-model-version.yml +++ b/.pipelines/azdo-template-get-model-version.yml @@ -6,7 +6,7 @@ steps: inlineScript: | set -e # fail on error export SUBSCRIPTION_ID=$(az account show --query id -o tsv) - python $(Build.SourcesDirectory)/ml_service/pipelines/verify_train_pipeline.py --build_id $(Build.BuildId) --output_model_version_file "model_version.txt" + python ml_service/pipelines/verify_train_pipeline.py --build_id $(Build.BuildId) --output_model_version_file "model_version.txt" # Output model version to Azure DevOps job MODEL_VERSION="$(cat model_version.txt)" echo "##vso[task.setvariable variable=MODEL_VERSION]$MODEL_VERSION" diff --git a/.pipelines/azdo-variables.yml b/.pipelines/azdo-variables.yml index 0691e673..745c8a76 100644 --- a/.pipelines/azdo-variables.yml +++ b/.pipelines/azdo-variables.yml @@ -2,6 +2,9 @@ variables: # Azure ML Workspace Variables - name: EXPERIMENT_NAME value: mlopspython + # Azure DevOps build container +- name: BUILDCONTAINER_NAME + value: diabetes # AML Compute Cluster Config - name: AML_COMPUTE_CLUSTER_CPU_SKU value: STANDARD_DS2_V2 diff --git a/README.md b/README.md index e622ba75..3ce71c3f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ This reference architecture shows how to implement continuous integration (CI), Once you have registered your ML model, you can use Azure ML + Azure DevOps to deploy it. -[Azure DevOps release pipeline](https://docs.microsoft.com/en-us/azure/devops/pipelines/release/?view=azure-devops) packages the new model along with the scoring file and its python dependencies into a [docker image](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-azure-machine-learning-architecture#image) and pushes it to [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-intro). This image is used to deploy the model as [web service](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-azure-machine-learning-architecture#web-service) across QA and Prod environments. The QA environment is running on top of [Azure Container Instances (ACI)](https://azure.microsoft.com/en-us/services/container-instances/) and the Prod environment is built with [Azure Kubernetes Service (AKS)](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes). +The [Azure DevOps multi-stage pipeline](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/stages?view=azure-devops&tabs=yaml) packages the new model along with the scoring file and its python dependencies into a [docker image](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-azure-machine-learning-architecture#image) and pushes it to [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-intro). This image is used to deploy the model as [web service](https://docs.microsoft.com/en-us/azure/machine-learning/service/concept-azure-machine-learning-architecture#web-service) across QA and Prod environments. The QA environment is running on top of [Azure Container Instances (ACI)](https://azure.microsoft.com/en-us/services/container-instances/) and the Prod environment is built with [Azure Kubernetes Service (AKS)](https://docs.microsoft.com/en-us/azure/aks/intro-kubernetes). ### Repo Details diff --git a/code/scoring/score.py b/code/scoring/score.py index b78a435c..4a1c6152 100644 --- a/code/scoring/score.py +++ b/code/scoring/score.py @@ -57,7 +57,7 @@ def run(raw_data, request_headers): request_headers.get("X-Ms-Request-Id", ""), request_headers.get("Traceparent", ""), len(result) - )) + )) return {"result": result.tolist()} diff --git a/code/training/R/train_with_r_on_databricks.py b/code/training/R/train_with_r_on_databricks.py index 1a120bd0..c571d609 100644 --- a/code/training/R/train_with_r_on_databricks.py +++ b/code/training/R/train_with_r_on_databricks.py @@ -11,5 +11,5 @@ args, unknown = parser.parse_known_args() folder = args.AZUREML_SCRIPT_DIRECTORY_NAME -os.system("cd " + "/dbfs/" + folder + - " && Rscript r_train.r && ls -ltr model.rds") +os.system("cd " + "/dbfs/" + folder + + " && Rscript r_train.r && ls -ltr model.rds") diff --git a/docs/code_description.md b/docs/code_description.md index 472e781b..75049d5c 100644 --- a/docs/code_description.md +++ b/docs/code_description.md @@ -2,15 +2,15 @@ ### Environment Setup -- `environment_setup/requirements.txt` : It consists of a list of python packages which are needed by the train.py to run successfully on host agent (locally). +- `environment_setup/build-image/Dockerfile` : Dockerfile of a build agent containing Python 3.6 and all required packages. + +- `environment_setup/build-image/requirements.txt` : List of python packages which are needed by the train.py and associated unit tests to run successfully on host agent (locally). - `environment_setup/install_requirements.sh` : This script prepares the python environment i.e. install the Azure ML SDK and the packages specified in requirements.txt - `environment_setup/iac-*.yml, arm-templates` : Infrastructure as Code piplines to create and delete required resources along with corresponding arm-templates. -- `environment_setup/Dockerfile` : Dockerfile of a build agent containing Python 3.6 and all required packages. - -- `environment_setup/docker-image-pipeline.yml` : An AzDo pipeline for building and pushing [microsoft/mlopspython](https://hub.docker.com/_/microsoft-mlops-python) image. +- `environment_setup/mlops-image/docker-image-pipeline.yml` : An AzDo pipeline for building and pushing [microsoft/mlopspython](https://hub.docker.com/_/microsoft-mlops-python) image. ### Pipelines diff --git a/docs/development_setup.md b/docs/development_setup.md new file mode 100644 index 00000000..e625e7d8 --- /dev/null +++ b/docs/development_setup.md @@ -0,0 +1,44 @@ +## Development environment setup + +### Setup + +Please be aware that the local environment also needs access to the Azure subscription so you have to have Contributor access on the Azure ML Workspace. + +In order to configure the project locally, create a copy of `.env.example` in the root directory and name it `.env`. Fill out all missing values and adjust the existing ones to suit your requirements. + +### Installation + +[Install the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). The Azure CLI will be used to log you in interactively. + +Create a virtual environment using [venv](https://docs.python.org/3/library/venv.html), [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv). + +Here is an example for setting up and activating a `venv` environment with Python 3: + +``` +python3 -mvenv .venv +source .venv/bin/activate +``` + +Install the required Python modules in your virtual environment. + +``` +pip install -r environment_setup/build-image/requirements.txt +``` + +### Running local code + +To run your local ML pipeline code on Azure ML, run a command such as the following (in bash, all on one line): + +``` +export BUILD_BUILDID=$(uuidgen); python ml_service/pipelines/build_train_pipeline.py && python ml_service/pipelines/run_train_pipeline.py +``` + +BUILD_BUILDID is a variable used to uniquely identify the ML pipeline between the +`build_train_pipeline.py` and `run_train_pipeline.py` scripts. In Azure DevOps it is +set to the current build number. In a local environment, we can use a command such as +`uuidgen` so set a different random identifier on each run, ensuring there are +no collisions. + +### Local testing + +Before committing, run `tox` to execute linter and unit test checks. diff --git a/docs/getting_started.md b/docs/getting_started.md index a46d5304..4f4da5a0 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -80,10 +80,7 @@ There are more variables used in the project. They're defined in two places, one ### Local configuration -In order to configure the project locally, create a copy of `.env.example` in the root directory and name it `.env`. Fill out all missing values and adjust the existing ones to suit your requirements. - -For local development, you will also need to [install the Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli). The Azure CLI will be used to log you in interactively. -Please be aware that the local environment also needs access to the Azure subscription so you have to have Contributor access on the Azure ML Workspace. +For instructions on how to set up a local development environment, refer to the [Development environment setup instructions](development_setup.md). ### Azure DevOps configuration @@ -139,6 +136,18 @@ your Azure AD tenant, or receive the ID and secret of a service principal from your Azure AD Administrator. That principal must have Contributor permissions on the Azure ML Workspace. +## Create a Docker Service Connection to manage your build container + +The pipeline requires a **Docker Registry** +[service connection](https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#sep-docreg). +As **Registry type**, choose **Azure Container Registry**. +Point to the Azure Container Registry deployed by your IaC pipeline. + +Use **``ContainerRegistry``** as the connection name, since it is used +in the Azure ML pipeline definition. + +![configure container registry service connection](images/create-acr-service-connection.png) + ## Set up Build, Release Trigger, and Release Multi-Stage Pipeline Now that you have all the required resources created from the IaC pipeline, @@ -172,9 +181,24 @@ Great, you now have the build pipeline set up which automatically triggers every * The first stage of the pipeline, **Model CI**, performs linting, unit testing, build and publishes an **ML Training Pipeline** in an **ML Workspace**. + * The **Generate build container** job creates a Docker container with + your ML dependencies, such as sklearn, or reuses the container + from a previous run if your container definition has not changed. + * The **Model CI** job runs on the Docker container generated in the + previous job and runs linting and unit tests. Test results and + code coverage reports can be found on your job output page. + * The **Publish AML Pipeline** job publishes the Azure ML pipeline + in your workspace. To save time, this job runs in parallel with + the **Model CI** job. If CI fails (for instance with unit test failures), + the AML Pipeline might have been published but will not be used + for training. The next run will publish a new AML Pipeline with + the same name and updated tags. + **Note:** The build pipeline also supports building and publishing ML pipelines using R to train a model. This is enabled -by changing the `build-train-script` pipeline variable to either `build_train_pipeline_with_r.py`, or `build_train_pipeline_with_r_on_dbricks.py`. For pipeline training a model with R on Databricks you'll need +by changing the `build-train-script` pipeline variable to either `build_train_pipeline_with_r.py`, or `build_train_pipeline_with_r_on_dbricks.py` +and uncommenting the R installation step in the `Dockerfile`. +For a pipeline training a model with R on Databricks you'll need to manually create a Databricks cluster and attach it to the ML Workspace as a compute (Values DB_CLUSTER_ID and DATABRICKS_COMPUTE_NAME variables should be specified). diff --git a/docs/images/create-acr-service-connection.png b/docs/images/create-acr-service-connection.png new file mode 100644 index 00000000..ed9d0c25 Binary files /dev/null and b/docs/images/create-acr-service-connection.png differ diff --git a/environment_setup/build-image/Dockerfile b/environment_setup/build-image/Dockerfile new file mode 100644 index 00000000..20ef7365 --- /dev/null +++ b/environment_setup/build-image/Dockerfile @@ -0,0 +1,18 @@ +FROM conda/miniconda3 + +RUN apt-get update && apt-get install -y \ + curl \ + apt-transport-https \ + gcc + +# Install dotnet runtime used to generate code coverage report +RUN curl -O https://packages.microsoft.com/config/ubuntu/19.04/packages-microsoft-prod.deb +RUN dpkg -i packages-microsoft-prod.deb +RUN apt-get update && apt-get install -y dotnet-runtime-3.1 + +# Uncomment to run R +# RUN conda install -c r r-essentials + +# Install Python modules +COPY requirements.txt /setup/ +RUN pip install --upgrade -r /setup/requirements.txt diff --git a/environment_setup/build-image/requirements.txt b/environment_setup/build-image/requirements.txt new file mode 100644 index 00000000..babb1ddc --- /dev/null +++ b/environment_setup/build-image/requirements.txt @@ -0,0 +1,12 @@ +pytest>=5.3 +pytest-cov>=2.8.1 +requests>=2.22 +numpy>=1.17 +pandas>=0.25 +scikit-learn>=0.21.3 +azureml-sdk>=1.0 +python-dotenv>=0.10.3 +flake8>=3.7 +flake8_formatter_junit_xml>=0.0.6 +tox>=3.14.3 +azure-cli==2.0.76 diff --git a/environment_setup/install_requirements.sh b/environment_setup/install_requirements.sh index 1bdd081d..6cb76a00 100644 --- a/environment_setup/install_requirements.sh +++ b/environment_setup/install_requirements.sh @@ -26,6 +26,4 @@ python --version -pip install azure-cli==2.0.46 -pip install --upgrade azureml-sdk[cli] -pip install -r requirements.txt \ No newline at end of file +pip install -r files/requirements.txt diff --git a/environment_setup/Dockerfile b/environment_setup/mlops-image/Dockerfile similarity index 51% rename from environment_setup/Dockerfile rename to environment_setup/mlops-image/Dockerfile index 5e7b7581..cdd23048 100644 --- a/environment_setup/Dockerfile +++ b/environment_setup/mlops-image/Dockerfile @@ -4,11 +4,10 @@ LABEL org.label-schema.vendor = "Microsoft" \ org.label-schema.url = "https://hub.docker.com/r/microsoft/mlopspython" \ org.label-schema.vcs-url = "https://github.com/microsoft/MLOpsPython" - +RUN apt-get update && apt-get install -y \ + gcc -COPY environment_setup/requirements.txt /setup/ - -RUN apt-get update && apt-get install gcc -y && pip install --upgrade -r /setup/requirements.txt && \ - conda install -c r r-essentials -CMD ["python"] \ No newline at end of file +# Install Python modules +COPY requirements.txt /setup/ +RUN pip install --upgrade -r /setup/requirements.txt diff --git a/environment_setup/docker-image-pipeline.yml b/environment_setup/mlops-image/docker-image-pipeline.yml similarity index 77% rename from environment_setup/docker-image-pipeline.yml rename to environment_setup/mlops-image/docker-image-pipeline.yml index 7678d96d..99392e8f 100644 --- a/environment_setup/docker-image-pipeline.yml +++ b/environment_setup/mlops-image/docker-image-pipeline.yml @@ -11,7 +11,7 @@ trigger: paths: include: - - environment_setup/Dockerfile + - environment_setup/mlops-image/* variables: containerRegistry: $[coalesce(variables['acrServiceConnection'], 'acrconnection')] @@ -27,5 +27,4 @@ steps: tags: | $(Build.BuildNumber) latest - buildContext: '$(Build.SourcesDirectory)' - dockerFile: '$(Build.SourcesDirectory)/environment_setup/Dockerfile' + buildContext: '$(Build.SourcesDirectory)/environment_setup/mlops-image' diff --git a/environment_setup/requirements.txt b/environment_setup/mlops-image/requirements.txt similarity index 72% rename from environment_setup/requirements.txt rename to environment_setup/mlops-image/requirements.txt index f99e7f4b..446b359f 100644 --- a/environment_setup/requirements.txt +++ b/environment_setup/mlops-image/requirements.txt @@ -1,8 +1,5 @@ pytest>=5.3 requests>=2.22 -numpy>=1.17 -pandas>=0.25 -scikit-learn>=0.21.3 azureml-sdk>=1.0 python-dotenv>=0.10.3 flake8>=3.7 diff --git a/ml_service/util/smoke_test_scoring_service.py b/ml_service/util/smoke_test_scoring_service.py index 753ef23e..136656f6 100644 --- a/ml_service/util/smoke_test_scoring_service.py +++ b/ml_service/util/smoke_test_scoring_service.py @@ -52,7 +52,7 @@ def call_web_app(url, headers): response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: - if i == retries-1: + if i == retries - 1: raise e print(e) print("Retrying...") diff --git a/tests/unit/code_test.py b/tests/unit/code_test.py index 06654b2f..1703d54b 100644 --- a/tests/unit/code_test.py +++ b/tests/unit/code_test.py @@ -8,9 +8,9 @@ def test_train_model(): - X_train = np.array([1, 2, 3, 4, 5, 6]).reshape(-1, 1) + X_train = np.array([1, 2, 3, 4, 5, 6]).reshape(-1, 1) y_train = np.array([10, 9, 8, 8, 6, 5]) - X_test = np.array([3, 4]).reshape(-1, 1) + X_test = np.array([3, 4]).reshape(-1, 1) y_test = np.array([8, 7]) data = {"train": {"X": X_train, "y": y_train}, "test": {"X": X_test, "y": y_test}} diff --git a/tests/unit/data_test.py b/tests/unit/data_test.py index 8b40b8bc..4148f029 100644 --- a/tests/unit/data_test.py +++ b/tests/unit/data_test.py @@ -120,8 +120,8 @@ def test_check_distribution(): mean = np.mean(dataset.values, axis=0) std = np.mean(dataset.values, axis=0) assert ( - np.sum(abs(mean - historical_mean) > - shift_tolerance * abs(historical_mean)) - or np.sum(abs(std - historical_std) > - shift_tolerance * abs(historical_std)) > 0 + np.sum(abs(mean - historical_mean) + > shift_tolerance * abs(historical_mean)) + or np.sum(abs(std - historical_std) + > shift_tolerance * abs(historical_std)) > 0 ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..17faeda5 --- /dev/null +++ b/tox.ini @@ -0,0 +1,20 @@ +[tox] +minversion = 3.14.3 +skipsdist = True + +[flake8] +# ignore obsolete warning +ignore = W503 +exclude = .git,__pycache__,.venv,.tox,**/site-packages/**/*.py,**/lib/**.py,**/bin/**.py + +[pytest] +junit_family = legacy + +[testenv] +whitelist_externals = + flake8 + pytest +passenv = HOME +commands = + flake8 --output-file=lint-testresults.xml --format junit-xml + pytest tests/unit --cov=code --cov-report=xml --junitxml=unit-testresults.xml