diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..8e9f916 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,2 @@ +blank_issues_enabled: false +contact_links: [] \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/release-request.yml b/.github/ISSUE_TEMPLATE/release-request.yml new file mode 100644 index 0000000..2ed9717 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-request.yml @@ -0,0 +1,53 @@ +name: "Release Request" +description: "Request a deployment by specifying the evaluation function, changes, target commit/branch, and test confirmation." +title: "Release Request" +labels: + - release + - deployment +assignees: [] +body: + - type: textarea + id: description_of_changes + attributes: + label: Description of changes + description: "Summarize what is changing and why. Include links to PRs, issues, or changelogs." + placeholder: | + - What changed: + - Why: + - Related PRs/Issues: #123, #456 + render: markdown + validations: + required: true + + - type: input + id: branch_to_deploy + attributes: + label: Branch to deploy + description: | + Specify Branch name to deploy. + placeholder: "e.g., release/2025-09-29" + validations: + required: true + + - type: dropdown + id: version-bump + attributes: + label: "🚀 What kind of update is this?" + description: "Tell us how significant this change is. This helps us set the correct new version number." + options: + - "Patch: A small fix for a bug. It won't break anything for existing users. (e.g., 1.2.3 ➔ 1.2.4)" + - "Minor: Adds a new feature, but doesn't change how existing ones work. A safe update. (e.g., 1.2.3 ➔ 1.3.0)" + - "Major: A big change that alters existing features. Users may need to update their work to adapt. (e.g., 1.2.3 ➔ 2.0.0)" + default: 0 + validations: + required: true + + - type: markdown + attributes: + value: | + --- + ### ⚡ Click the Link Below to Run the Workflow + + Clicking the link will take you to the Actions page. You will need to click the **"Run workflow"** button there to start the process. + + ## [➡️ Go to Workflow Run Page](../actions/workflows/production-deploy.yml) \ No newline at end of file diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml new file mode 100644 index 0000000..4e0b374 --- /dev/null +++ b/.github/workflows/production-deploy.yml @@ -0,0 +1,44 @@ +name: Deploy to Production + +on: + workflow_dispatch: + inputs: + version-bump: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + default: patch + branch: + description: 'Branch to release from' + required: true + type: string + default: 'main' +jobs: + deploy: + uses: lambda-feedback/evaluation-function-workflows/.github/workflows/deploy.yml@deploy-request + with: + template-repository-name: 'lambda-feedback/evaluation-function-boilerplate-python' + build-file: "app/Dockerfile" + build-context: "./app" + environment: "production" + version-bump: ${{ inputs.version-bump }} + branch: ${{ inputs.branch }} + run-tests: true + + secrets: + aws-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }} + aws-secret-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET}} + function-admin-api-key: ${{ secrets.FUNCTION_ADMIN_API_KEY}} + gcp_credentials: ${{ secrets.GCP_DEPLOY }} + TEST_API_ENDPOINT: ${{ secrets.TEST_API_ENDPOINT }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_HOST: ${{ secrets.DB_HOST }} + DB_PORT: ${{ secrets.DB_PORT }} + DB_NAME: ${{ secrets.DB_NAME }} + GCP_DB_CREDS: ${{ secrets.GCP_DB_CREDS }} + GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} \ No newline at end of file diff --git a/.github/workflows/staging-deploy.yml b/.github/workflows/staging-deploy.yml new file mode 100644 index 0000000..eef61cc --- /dev/null +++ b/.github/workflows/staging-deploy.yml @@ -0,0 +1,76 @@ +name: Deploy to Staging + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + permissions: + contents: read + actions: read + checks: write + pull-requests: write + strategy: + fail-fast: false + matrix: + python-version: ["3.8"] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + id: python-setup + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m venv .venv + .venv/bin/pip install pytest + .venv/bin/pip install -r app/requirements.txt + + # TODO: add linting / black / flake8 + # - name: Lint with flake8 + # run: | + # source .venv/bin/activate + # # stop the build if there are Python syntax errors or undefined names + # flake8 ./evaluation_function --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 ./evaluation_function --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Run tests + if: always() + run: | + source .venv/bin/activate + pytest --junit-xml=./reports/pytest.xml --tb=auto -v + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-${{ matrix.python-version }} + path: ./reports/pytest.xml + if-no-files-found: warn + deploy: + needs: test + uses: lambda-feedback/evaluation-function-workflows/.github/workflows/deploy.yml@deploy-request + with: + template-repository-name: "lambda-feedback/evaluation-function-boilerplate-python" + build-file: "app/Dockerfile" + build-context: "./app" + build-platforms: "aws" + environment: "staging" + lfs: false + secrets: + aws-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }} + aws-secret-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET}} + function-admin-api-key: ${{ secrets.FUNCTION_ADMIN_API_KEY}} + gcp_credentials: ${{ secrets.GCP_DEPLOY }} diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml deleted file mode 100644 index 8329f93..0000000 --- a/.github/workflows/test-and-deploy.yml +++ /dev/null @@ -1,194 +0,0 @@ -name: Test & Deploy Evaluation Function to AWS Lambda - -on: - push: - branches: - - master - - main - workflow_dispatch: - -jobs: - test: - name: Test - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - python-version: [3.8] - - defaults: - run: - working-directory: app/ - - env: - REQUEST_SCHEMA_URL: https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/master/request.json - RESPONSE_SCHEMA_URL: https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/master/responsev2.json - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 pytest - python -m pip install -r requirements.txt - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Test Evaluation Function - run: | - pytest -v evaluation_tests.py::TestEvaluationFunction - - - name: Test Preview Function - run: | - pytest -v preview_tests.py::TestPreviewFunction - - deploy-staging: - name: Deploy Staging - needs: test - runs-on: ubuntu-latest - environment: production - env: - ECR_REPOSITORY: lambda-feedback-staging-functions-repository - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Set config.json output - id: set_config_var - run: | - content=`cat ./config.json` - # the following lines are only required for multi line json - content="${content//'%'/'%25'}" - content="${content//$'\n'/'%0A'}" - content="${content//$'\r'/'%0D'}" - # end of optional handling for multi line json - echo "::set-output name=configJson::$content" - - - name: set Evaluation Function Name - id: set_function_name - run: | - functionName="${{fromJson(steps.set_config_var.outputs.configJson).EvaluationFunctionName}}" - [[ -z "$functionName" ]] && { echo "Add EvaluationFunctionName to config.json" ; exit 1; } - echo "::set-output name=function_name::$functionName" - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }} - aws-secret-access-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET }} - aws-region: eu-west-2 - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - - name: Build, tag, and push image to Amazon ECR - id: build-image - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }} - run: | - # Build docker image from algorithm, schema and requirements - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG app/ - # Push image to ECR - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - - - name: deploy evaluation function - id: deploy-evaluation-function - env: - BACKEND_API_URL: https://staging-api.lambdafeedback.com - API_KEY: ${{ secrets.FUNCTION_ADMIN_API_KEY }} - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }} - run: | - curl --location --request POST "$BACKEND_API_URL/grading-function/ensure" \ - --header 'content-type: application/json' \ - --data-raw "{ - \"apiKey\": \"$API_KEY\", - \"dockerImageUri\": \"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG\", - \"functionName\": \"$IMAGE_TAG\" - }" - - deploy-production: - name: Deploy Production - needs: deploy-staging - runs-on: ubuntu-latest - environment: production - env: - ECR_REPOSITORY: lambda-feedback-production-functions-repository - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Set config.json output - id: set_config_var - run: | - content=`cat ./config.json` - # the following lines are only required for multi line json - content="${content//'%'/'%25'}" - content="${content//$'\n'/'%0A'}" - content="${content//$'\r'/'%0D'}" - # end of optional handling for multi line json - echo "::set-output name=configJson::$content" - - - name: set Evaluation Function Name - id: set_function_name - run: | - functionName="${{fromJson(steps.set_config_var.outputs.configJson).EvaluationFunctionName}}" - [[ -z "$functionName" ]] && { echo "Add EvaluationFunctionName to config.json" ; exit 1; } - echo "::set-output name=function_name::$functionName" - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }} - aws-secret-access-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET }} - aws-region: eu-west-2 - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - - name: Build, tag, and push image to Amazon ECR - id: build-image - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }} - run: | - # Build docker image from algorithm, schema and requirements - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG app/ - # Push image to ECR - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - - - name: deploy evaluation function - id: deploy-evaluation-function - env: - BACKEND_API_URL: https://prod-api.lambdafeedback.com - API_KEY: ${{ secrets.FUNCTION_ADMIN_API_KEY }} - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }} - run: | - curl --location --request POST "$BACKEND_API_URL/grading-function/ensure" \ - --header 'content-type: application/json' \ - --data-raw "{ - \"apiKey\": \"$API_KEY\", - \"dockerImageUri\": \"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG\", - \"functionName\": \"$IMAGE_TAG\" - }" diff --git a/README.md b/README.md index 0de5731..c9040e9 100755 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ For more information, look at the docs [here](https://lambda-feedback.github.io/ This version is specifically for python, however the ultimate goal is to make similar boilerplate repositories in any language, allowing tutors the freedom to code in what they feel most comfortable with. +## Deployment +[![Create Release Request](https://img.shields.io/badge/Create%20Release%20Request-blue?style=for-the-badge)](https://github.com/lambda-feedback/compareExpressions/issues/new?template=release-request.yml) + + ### Getting Started 1. Clone this repository diff --git a/app/Dockerfile b/app/Dockerfile index f032540..9f5ca25 100755 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -13,9 +13,9 @@ RUN pip3 install -r requirements.txt # Copy main scripts COPY evaluation.py ./app/ -COPY evaluation_tests.py ./app/ +COPY evaluation_test.py ./app/ COPY preview.py ./app/ -COPY preview_tests.py ./app/ +COPY preview_test.py ./app/ # Copy contexts COPY context/physical_quantity.py ./app/context/ @@ -30,12 +30,12 @@ COPY preview_implementations/physical_quantity_preview.py ./app/preview_implemen COPY preview_implementations/symbolic_preview.py ./app/preview_implementations/ # Copy tests -COPY tests/example_tests.py ./app/tests/ -COPY tests/physical_quantity_evaluation_tests.py ./app/tests/ -COPY tests/physical_quantity_preview_tests.py ./app/tests/ -COPY tests/slr_quantity_tests.py ./app/tests/ -COPY tests/symbolic_evaluation_tests.py ./app/tests/ -COPY tests/symbolic_preview_tests.py ./app/tests/ +COPY tests/example_test.py ./app/tests/ +COPY tests/physical_quantity_evaluation_test.py ./app/tests/ +COPY tests/physical_quantity_preview_test.py ./app/tests/ +COPY tests/slr_quantity_test.py ./app/tests/ +COPY tests/symbolic_evaluation_test.py ./app/tests/ +COPY tests/symbolic_preview_test.py ./app/tests/ # Copy utility code COPY utility/criteria_graph_utilities.py ./app/utility/ diff --git a/app/evaluation_tests.py b/app/evaluation_test.py similarity index 96% rename from app/evaluation_tests.py rename to app/evaluation_test.py index a183ea7..bd05396 100755 --- a/app/evaluation_tests.py +++ b/app/evaluation_test.py @@ -25,13 +25,13 @@ class TestEvaluationFunction(): """ # Import tests that makes sure that mathematical expression comparison works as expected - from .tests.symbolic_evaluation_tests import TestEvaluationFunction as TestSymbolicComparison + from .tests.symbolic_evaluation_test import TestEvaluationFunction as TestSymbolicComparison # Import tests that makes sure that physical quantities are handled as expected - from .tests.physical_quantity_evaluation_tests import TestEvaluationFunction as TestQuantities + from .tests.physical_quantity_evaluation_test import TestEvaluationFunction as TestQuantities # Import tests that corresponds to examples in documentation and examples module - from .tests.example_tests import TestEvaluationFunction as TestExamples + from .tests.example_test import TestEvaluationFunction as TestExamples def test_eval_function_can_handle_latex_input(self): response = r"\sin x + x^{7}" diff --git a/app/preview_tests.py b/app/preview_test.py similarity index 98% rename from app/preview_tests.py rename to app/preview_test.py index bdacdad..8ae19c6 100644 --- a/app/preview_tests.py +++ b/app/preview_test.py @@ -25,10 +25,10 @@ class TestPreviewFunction(): """ # Import tests that makes sure that mathematical expression comparison works as expected - from .tests.symbolic_preview_tests import TestPreviewFunction as TestSymbolicComparison + from .tests.symbolic_preview_test import TestPreviewFunction as TestSymbolicComparison # Import tests that makes sure that physical quantities are handled as expected - from .tests.physical_quantity_preview_tests import TestPreviewFunction as TestQuantityComparison + from .tests.physical_quantity_preview_test import TestPreviewFunction as TestQuantityComparison def test_empty_latex_expression(self): response = "" diff --git a/app/tests/example_tests.py b/app/tests/example_test.py similarity index 100% rename from app/tests/example_tests.py rename to app/tests/example_test.py diff --git a/app/tests/physical_quantity_evaluation_tests.py b/app/tests/physical_quantity_evaluation_test.py similarity index 99% rename from app/tests/physical_quantity_evaluation_tests.py rename to app/tests/physical_quantity_evaluation_test.py index ba1bed5..d7e1fef 100644 --- a/app/tests/physical_quantity_evaluation_tests.py +++ b/app/tests/physical_quantity_evaluation_test.py @@ -2,7 +2,7 @@ import os # Import necessary data and reference cases for tests -from .slr_quantity_tests import slr_strict_si_syntax_test_cases, slr_natural_si_syntax_test_cases +from .slr_quantity_test import slr_strict_si_syntax_test_cases, slr_natural_si_syntax_test_cases from ..evaluation import evaluation_function from ..utility.unit_system_conversions import ( set_of_SI_prefixes, @@ -32,7 +32,7 @@ class TestEvaluationFunction(): """ # Import tests that makes sure that physical quantity parsing works as expected - from .slr_quantity_tests import TestEvaluationFunction as TestStrictSLRSyntax + from .slr_quantity_test import TestEvaluationFunction as TestStrictSLRSyntax log_details = True diff --git a/app/tests/physical_quantity_preview_tests.py b/app/tests/physical_quantity_preview_test.py similarity index 97% rename from app/tests/physical_quantity_preview_tests.py rename to app/tests/physical_quantity_preview_test.py index d343e39..f44f7f4 100644 --- a/app/tests/physical_quantity_preview_tests.py +++ b/app/tests/physical_quantity_preview_test.py @@ -2,7 +2,7 @@ import pytest from ..preview_implementations.physical_quantity_preview import preview_function -from .slr_quantity_tests import slr_strict_si_syntax_test_cases, slr_natural_si_syntax_test_cases +from .slr_quantity_test import slr_strict_si_syntax_test_cases, slr_natural_si_syntax_test_cases class TestPreviewFunction(): diff --git a/app/tests/slr_quantity_tests.py b/app/tests/slr_quantity_test.py similarity index 100% rename from app/tests/slr_quantity_tests.py rename to app/tests/slr_quantity_test.py diff --git a/app/tests/symbolic_evaluation_tests.py b/app/tests/symbolic_evaluation_test.py similarity index 100% rename from app/tests/symbolic_evaluation_tests.py rename to app/tests/symbolic_evaluation_test.py diff --git a/app/tests/symbolic_preview_tests.py b/app/tests/symbolic_preview_test.py similarity index 99% rename from app/tests/symbolic_preview_tests.py rename to app/tests/symbolic_preview_test.py index c6e6fcb..cb86d62 100644 --- a/app/tests/symbolic_preview_tests.py +++ b/app/tests/symbolic_preview_test.py @@ -3,7 +3,7 @@ from ..utility.preview_utilities import Params, extract_latex from ..preview_implementations.symbolic_preview import preview_function -from .symbolic_evaluation_tests import elementary_function_test_cases +from .symbolic_evaluation_test import elementary_function_test_cases class TestPreviewFunction():