Skip to content

Commit 952ffd9

Browse files
author
RabidSheep55
committed
restructure to v2
1 parent 66beb46 commit 952ffd9

19 files changed

Lines changed: 357 additions & 613 deletions

.github/workflows/build-base-image.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

.github/workflows/test-and-deploy.yml

Lines changed: 109 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Test & Deploy Grading Script to AWS Lambda
1+
name: Test & Deploy Evaluation Function to AWS Lambda
22

33
on:
44
push:
@@ -20,6 +20,10 @@ jobs:
2020
run:
2121
working-directory: app/
2222

23+
env:
24+
REQUEST_SCHEMA_URL: https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/master/request.json
25+
RESPONSE_SCHEMA_URL: https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/master/responsev2.json
26+
2327
steps:
2428
- name: Checkout
2529
uses: actions/checkout@v2
@@ -33,7 +37,6 @@ jobs:
3337
run: |
3438
python -m pip install --upgrade pip
3539
python -m pip install flake8 pytest
36-
python -m pip install -r tools/tools_requirements.txt
3740
python -m pip install -r requirements.txt
3841
3942
- name: Lint with flake8
@@ -43,29 +46,109 @@ jobs:
4346
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
4447
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
4548
46-
- name: Test Handler functions
49+
- name: Test Evaluation Function
4750
run: |
48-
pytest -v tests/handling.py::TestHandlerFunction
49-
- name: Test JSON schema
51+
pytest -v evaluation_tests.py::TestEvaluationFunction
52+
53+
deploy-staging:
54+
name: Deploy Staging
55+
needs: test
56+
runs-on: ubuntu-latest
57+
environment: production
58+
env:
59+
ECR_REPOSITORY: lambda-feedback-staging-functions-repository
60+
61+
steps:
62+
- name: Checkout
63+
uses: actions/checkout@v2
64+
65+
- name: Set config.json output
66+
id: set_config_var
5067
run: |
51-
pytest -v tests/validation.py::TestSchemaValidation
52-
- name: Test Grading functions
68+
content=`cat ./config.json`
69+
# the following lines are only required for multi line json
70+
content="${content//'%'/'%25'}"
71+
content="${content//$'\n'/'%0A'}"
72+
content="${content//$'\r'/'%0D'}"
73+
# end of optional handling for multi line json
74+
echo "::set-output name=configJson::$content"
75+
76+
- name: set Evaluation Function Name
77+
id: set_function_name
5378
run: |
54-
pytest -v tests/grading.py::TestGradingFunction
79+
functionName="${{fromJson(steps.set_config_var.outputs.configJson).EvaluationFunctionName}}"
80+
[[ -z "$functionName" ]] && { echo "Add EvaluationFunctionName to config.json" ; exit 1; }
81+
echo "::set-output name=function_name::$functionName"
5582
56-
deploy:
57-
name: Deploy
58-
needs: test
83+
- name: Configure AWS credentials
84+
uses: aws-actions/configure-aws-credentials@v1
85+
with:
86+
aws-access-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }}
87+
aws-secret-access-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET }}
88+
aws-region: eu-west-2
89+
90+
- name: Login to Amazon ECR
91+
id: login-ecr
92+
uses: aws-actions/amazon-ecr-login@v1
93+
94+
- name: Build, tag, and push image to Amazon ECR
95+
id: build-image
96+
env:
97+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
98+
IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }}
99+
run: |
100+
# Build docker image from algorithm, schema and requirements
101+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG app/
102+
# Push image to ECR
103+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
104+
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
105+
106+
- name: deploy evaluation function
107+
id: deploy-evaluation-function
108+
env:
109+
BACKEND_API_URL: https://hytqbudf4a.eu-west-1.awsapprunner.com
110+
API_KEY: ${{ secrets.FUNCTION_ADMIN_API_KEY }}
111+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
112+
IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }}
113+
run: |
114+
curl --location --request POST "$BACKEND_API_URL/grading-function/ensure" \
115+
--header 'content-type: application/json' \
116+
--data-raw "{
117+
\"apiKey\": \"$API_KEY\",
118+
\"dockerImageUri\": \"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG\",
119+
\"functionName\": \"$IMAGE_TAG\"
120+
}"
121+
122+
deploy-production:
123+
name: Deploy Production
124+
needs: deploy-staging
59125
runs-on: ubuntu-latest
60126
environment: production
61127
env:
62-
ECR_REPOSITORY: is-exact-equal
63-
LAMBDA_FUNCTION_NAME: isExactEqual
128+
ECR_REPOSITORY: lambda-feedback-production-functions-repository
64129

65130
steps:
66131
- name: Checkout
67132
uses: actions/checkout@v2
68133

134+
- name: Set config.json output
135+
id: set_config_var
136+
run: |
137+
content=`cat ./config.json`
138+
# the following lines are only required for multi line json
139+
content="${content//'%'/'%25'}"
140+
content="${content//$'\n'/'%0A'}"
141+
content="${content//$'\r'/'%0D'}"
142+
# end of optional handling for multi line json
143+
echo "::set-output name=configJson::$content"
144+
145+
- name: set Evaluation Function Name
146+
id: set_function_name
147+
run: |
148+
functionName="${{fromJson(steps.set_config_var.outputs.configJson).EvaluationFunctionName}}"
149+
[[ -z "$functionName" ]] && { echo "Add EvaluationFunctionName to config.json" ; exit 1; }
150+
echo "::set-output name=function_name::$functionName"
151+
69152
- name: Configure AWS credentials
70153
uses: aws-actions/configure-aws-credentials@v1
71154
with:
@@ -81,17 +164,26 @@ jobs:
81164
id: build-image
82165
env:
83166
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
84-
IMAGE_TAG: latest
167+
IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }}
85168
run: |
86169
# Build docker image from algorithm, schema and requirements
87170
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG app/
88171
# Push image to ECR
89172
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
90173
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
91174
92-
- name: Update lambda function image
175+
- name: deploy evaluation function
176+
id: deploy-evaluation-function
93177
env:
178+
BACKEND_API_URL: https://vnfapgr7gv.eu-west-1.awsapprunner.com
179+
API_KEY: ${{ secrets.FUNCTION_ADMIN_API_KEY }}
94180
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
95-
IMAGE_TAG: latest
181+
IMAGE_TAG: ${{ steps.set_function_name.outputs.function_name }}
96182
run: |
97-
aws lambda update-function-code --function-name $LAMBDA_FUNCTION_NAME --image-uri $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
183+
curl --location --request POST "$BACKEND_API_URL/grading-function/ensure" \
184+
--header 'content-type: application/json' \
185+
--data-raw "{
186+
\"apiKey\": \"$API_KEY\",
187+
\"dockerImageUri\": \"$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG\",
188+
\"functionName\": \"$IMAGE_TAG\"
189+
}"

app/Dockerfile

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
# Base image that bundles AWS Lambda Python 3.8 image with some middleware functions
2-
FROM loumstarlearjet/python-lambda-question-type:testing
2+
FROM rabidsheep55/python-base-eval-layer
33

4-
# To avoid setting up the app in the global space,
5-
# everything is package within an /app directory.
64
WORKDIR /app
75

8-
# Copy and install any packages/modules needed for your grading script.
6+
# Copy and install any packages/modules needed for your evaluation script.
97
COPY requirements.txt .
108
RUN pip3 install -r requirements.txt
119

12-
# Copy the schema needed to check the incoming data is valid
13-
COPY schema.json ./app/
10+
# Copy the evaluation and testing scripts
11+
COPY evaluation.py ./app/
12+
COPY evaluation_tests.py ./app/
1413

15-
# Copy the grading and testing scripts
16-
COPY algorithm.py ./app/
17-
COPY tests/ ./app/tests/
14+
# Copy Documentation
15+
COPY docs.md ./app/
1816

1917
# Set permissions so files and directories can be accessed on AWS
2018
RUN chmod 644 $(find . -type f)
2119
RUN chmod 755 $(find . -type d)
2220

2321
# The entrypoint for AWS is to invoke the handler function within the app package
24-
CMD ["/app/app.handler"]
22+
CMD [ "/app/app.handler" ]

app/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +0,0 @@
1-
from .algorithm import grading_function
2-
from .tools import handler, validate_request

app/docs.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# IsExactEqual
2+
Could be qualified as the simplest form of evaluation function, testing exact equality. This function will use the default python `==` test to compare answer and responses. It doesn't infer any types - meaning it requires a `params.type` to be supplied.
3+
4+
## Inputs
5+
This function requires a parameter to function properly:
6+
```json
7+
{
8+
"params": {
9+
"type": "<string>" (any of ["int", "float", "str", "dict"])
10+
}
11+
"response": <>,
12+
"answer": <>
13+
}
14+
15+
```
16+
17+
## Outputs
18+
Outputs to the `grade` command will feature:
19+
20+
```json
21+
{
22+
"command": "eval",
23+
"result": {
24+
"is_correct": "<bool>"
25+
}
26+
}
27+
28+
```
29+
30+
## Examples
31+
32+
### Simple String Comparison
33+
34+
```python
35+
{
36+
"answer": "hydrophobic",
37+
"response" "hydrophobic",
38+
"params": {
39+
"type": "str"
40+
}
41+
}
42+
```
43+
44+
```python
45+
{
46+
"example": {
47+
"Something": "something"
48+
}
49+
}
50+
```
Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
def grading_function(body: dict) -> dict:
1+
def evaluation_function(response, answer, params) -> dict:
22
"""
33
Function used to grade a student response.
44
---
5-
The handler function passes only one argument to grading_function(),
5+
The handler function passes only one argument to evaluation_function(),
66
which is a dictionary of the structure of the API request body
77
deserialised from JSON.
88
@@ -15,31 +15,32 @@ def grading_function(body: dict) -> dict:
1515
1616
The way you wish to structure you code (all in this function, or
1717
split into many) is entirely up to you. All that matters are the
18-
return types and that grading_function() is the main function used
18+
return types and that evaluation_function() is the main function used
1919
to output the grading response.
2020
"""
2121

2222
type_dict = {"float": float, "int": int, "str": str, "dict": dict}
23-
req_type = body["params"]["type"]
23+
req_type = params.get("type", False)
24+
25+
if not req_type:
26+
raise SyntaxError("params.type is a required field")
27+
28+
cast_type = type_dict.get(req_type, False)
29+
30+
if not cast_type:
31+
raise SyntaxError(f"Supplied type {req_type} not available")
2432

2533
# Try cast each of the inputs to their requested type:
2634
errors = []
2735
try:
28-
res = type_dict[req_type](body["response"])
36+
res = cast_type(response)
2937
except ValueError as e:
30-
errors += [
31-
{"description": f"Could not cast `response` parameter to {req_type}"}
32-
]
38+
raise ValueError(f"Could not cast `response` parameter to {req_type}")
3339

3440
try:
35-
ans = type_dict[req_type](body["answer"])
41+
ans = cast_type(answer)
3642
except ValueError as e:
37-
errors += [{"description": f"Could not cast `answer` parameter to {req_type}"}]
38-
39-
if errors:
40-
return {"error": errors}
41-
42-
# Are they equaL?
43-
is_exact_equal = res == ans
43+
raise ValueError(f"Could not cast `answer` parameter to {req_type}")
4444

45-
return {"is_correct": is_exact_equal}
45+
# Are they equal?
46+
return {"is_correct": res == ans}

0 commit comments

Comments
 (0)