From 043a23f7712bb790986ac3486c51bc9136839cff Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 17:54:38 +0530 Subject: [PATCH 01/15] Added Readme --- README.md | 1263 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1263 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3bf6cd --- /dev/null +++ b/README.md @@ -0,0 +1,1263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + clocks/README.md at master · Salad-King/clocks + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Skip to content + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + + + +
+
+
+ + + + + + + + + + + + + + +
+ +
+ +
+

+ + + + + / + + clocks + + +

+ + + forked from mbeken/clocks + + +
+ +
    + + + + +
  • + +
    + +
    + + + Watch + + +
    + Notifications +
    +
    + + + + + + + +
    +
    +
    + +
    +
  • + +
  • +
    +
    + + +
    +
    + + +
    + +
  • + +
  • +
    +
    + + + Fork + + + +
    + +

    Fork clocks

    +
    +
    + +
    + +
    +

    If this dialog fails to load, you can visit the fork page directly.

    +
    +
    + +
    +
    + + +
    +
    + + +
  • +
+ +
+ + + + + +
+ + + + + + +
+
+ + + + + + + + Permalink + + + + +
+ + +
+ + Branch: + master + + + + +
+ + + +
+
+
+ +
+ + Find file + + + Copy path + +
+
+ + +
+ + Find file + + + Copy path + +
+
+ + + + +
+ Fetching contributors… +
+ +
+ + Cannot retrieve contributors at this time +
+
+ + + + + +
+ +
+
+ + 58 lines (33 sloc) + + 4.26 KB +
+ +
+ +
+ Raw + Blame + History +
+ + +
+ + + + +
+ +
+
+ +
+
+
+ + + + +
+

Clock Exercise

+

We are interested in running code of course, but even more in your development process and understanding of Software Development Lifecycle Management.

+

Fork this repo, then get to work. Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. When you’re ready push your changes back to Github and put in a pull request back to the base repo.

+

This exercise is not meant to take an excessive amount of time. It is an opportunity for you to demonstrate your skills without the stress of an interview. If you start to run out of time, it’s ok to leave an imaginary team member a TODO list that details all the things you didn’t quite have time to do in order for your solution to go to prod.

+

If you need clarification, or would like to request additional information, pease reach out to the interviewer by email.

+

Scenario

+

You have just joined a DevOps team. This team lives by DevOps principles and you want to let them know you mean business! This particular team is developing a product that is deployed in a Google Cloud Project.

+

This sprint, the team has been asked to work on a new feature that depends on being able to calculate the angle between the hands on a clock face. They’ve asked you to write some code to help out with that. This is an IOT project, and they have sensors emitting times at a pretty low frequency (about 10 a minute), and for some reason they need to be processed and stored as angles.

+

You may need to make some assupmtions, that's OK, just document what they are and move on.

+

The team loves innovation, so you can use whatever languages and technologies you like to complete this. Approach this problem as if your code will go to production. Whilst we don’t expect the code to be perfect, we are not looking for a hacked together script.

+

Your solution should offer the rest of the team a way to submit a time and receive an angle in return or store it somewhere. They are little fuzzy on the best way to get this low frequency data to your service, so if you can offer them any hints on that, they’d be really happy.

+

How to proceed

+

Fork this repo, then get to work. Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. When you’re ready push your changes back to Github and put in a pull request back to the base repo.

+

Be sure to add in instructions for how to deploy your solution, and document things in a way that the rest of the team can pick this up and run with it. Remember you have all the tools in the GCP arsenal at your disposal.

+

We are looking for you to demonstrate your abilities in software practices and DevOps, including reusability, portability, reliability, ease of maintenance etc.

+

Think about how this will actually be deployed and maintained in the future as you build on it and expand it. You don’t have to implement deployment practices if you don’t have the time or resources, its ok to just document those.

+
+

Product Backlog Item (Sprint Story)

+

Here is the story that is in the backlog.

+

As with all stories, the team may have been optimistic with how much can be done in the time permitted. It's ok to meet some of the acceptance criteria by documenting what you would do in the next sprint! Prioritize your time and make sure you have some technical content to deliver.

+

Description:-

+

As a team
+We need a serivce that we can send a time value to and have it return or store an angle value
+So that we can use it in downstream processing

+

Detail:-

+

We need to calculate the angle between the hands on a clock face. For example input 03:00 would yield 90 degrees.

+

Acceptance Criteria:-

+
    +
  1. Code to perform the calculation
  2. +
  3. How will you deploy this solution (in code or as a todo list if time is limited). i.e. how and where will this run?
  4. +
  5. How will you manage any infrastructure needed?
  6. +
  7. Delivered as a feature branch in the repo fork
  8. +
  9. Bonus points for a working deployed solution in GCP that you can demo at the "sprint review" (ie interview)
  10. +
  11. Any DevOps/Cicd components that would support this feature in a production setting
  12. +
+
+
+ +
+ + + +
+ + +
+ + +
+
+ + + +
+
+ +
+
+ + +
+ + + + + + +
+ + + You can’t perform that action at this time. +
+ + + + + + + + + + + + + + + + + + + + From f6d93b0f6b0ba734c5ba0baa5f43177a718f4ec6 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 17:57:45 +0530 Subject: [PATCH 02/15] Updated readme --- README.md | 1269 ++--------------------------------------------------- 1 file changed, 32 insertions(+), 1237 deletions(-) diff --git a/README.md b/README.md index c3bf6cd..4a77c09 100644 --- a/README.md +++ b/README.md @@ -1,1263 +1,58 @@ +# Clock Exercise +We are interested in running code of course, but even more in your development process and understanding of Software Development Lifecycle Management. +**Fork this repo, then get to work.** Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. **When you’re ready push your changes back to Github and put in a pull request back to the base repo.** +This exercise is not meant to take an excessive amount of time. It is an opportunity for you to demonstrate your skills without the stress of an interview. If you start to run out of time, it’s ok to leave an imaginary team member a TODO list that details all the things you didn’t quite have time to do in order for your solution to go to prod. +If you need clarification, or would like to request additional information, pease reach out to the interviewer by email. +## Scenario - - - - - - - - - - - +You have just joined a DevOps team. This team lives by DevOps principles and you want to let them know you mean business! This particular team is developing a product that is deployed in a Google Cloud Project. +This sprint, the team has been asked to work on a new feature that depends on being able to calculate the angle between the hands on a clock face. They’ve asked you to write some code to help out with that. This is an IOT project, and they have sensors emitting times at a pretty low frequency (about 10 a minute), and for some reason they need to be processed and stored as angles. +You may need to make some assupmtions, that's OK, just document what they are and move on. - - - - - - - +The team loves innovation, so you can use whatever languages and technologies you like to complete this. Approach this problem as if your code will go to production. Whilst we don’t expect the code to be perfect, we are not looking for a hacked together script. +Your solution should offer the rest of the team a way to submit a time and receive an angle in return or store it somewhere. They are little fuzzy on the best way to get this low frequency data to your service, so if you can offer them any hints on that, they’d be really happy. - - - clocks/README.md at master · Salad-King/clocks - - - - +## How to proceed - - +**Fork this repo, then get to work.** Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. **When you’re ready push your changes back to Github and put in a pull request back to the base repo.** - - - +Be sure to add in instructions for how to deploy your solution, and document things in a way that the rest of the team can pick this up and run with it. Remember you have all the tools in the GCP arsenal at your disposal. - +We are looking for you to demonstrate your abilities in software practices and DevOps, including reusability, portability, reliability, ease of maintenance etc. +Think about how this will actually be deployed and maintained in the future as you build on it and expand it. You don’t have to implement deployment practices if you don’t have the time or resources, its ok to just document those. +--- - +## Product Backlog Item (Sprint Story) - +Here is the story that is in the backlog. - +As with all stories, the team may have been optimistic with how much can be done in the time permitted. It's ok to meet some of the acceptance criteria by documenting what you would do in the next sprint! Prioritize your time and make sure you have some technical content to deliver. - - - +### Description:- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Skip to content - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- - - - - - - - - -
-
-
- - - - - - - - - - - - - - -
- -
- -
-

- - - - - / - - clocks - - -

- - - forked from mbeken/clocks - - -
- -
    - - - - -
  • - -
    - -
    - - - Watch - - -
    - Notifications -
    -
    - - - - - - - -
    -
    -
    - -
    -
  • - -
  • -
    -
    - - -
    -
    - - -
    - -
  • - -
  • -
    -
    - - - Fork - - - -
    - -

    Fork clocks

    -
    -
    - -
    - -
    -

    If this dialog fails to load, you can visit the fork page directly.

    -
    -
    - -
    -
    - - -
    -
    - - -
  • -
- -
- - - - - -
- - - - - - -
-
- - - - - - - - Permalink - - - - -
- - -
- - Branch: - master - - - - -
- - - -
-
-
- -
- - Find file - - - Copy path - -
-
- - -
- - Find file - - - Copy path - -
-
- - - - -
- Fetching contributors… -
- -
- - Cannot retrieve contributors at this time -
-
- - - - - -
- -
-
- - 58 lines (33 sloc) - - 4.26 KB -
- -
- -
- Raw - Blame - History -
- - -
- - - - -
- -
-
- -
-
-
- - - - -
-

Clock Exercise

-

We are interested in running code of course, but even more in your development process and understanding of Software Development Lifecycle Management.

-

Fork this repo, then get to work. Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. When you’re ready push your changes back to Github and put in a pull request back to the base repo.

-

This exercise is not meant to take an excessive amount of time. It is an opportunity for you to demonstrate your skills without the stress of an interview. If you start to run out of time, it’s ok to leave an imaginary team member a TODO list that details all the things you didn’t quite have time to do in order for your solution to go to prod.

-

If you need clarification, or would like to request additional information, pease reach out to the interviewer by email.

-

Scenario

-

You have just joined a DevOps team. This team lives by DevOps principles and you want to let them know you mean business! This particular team is developing a product that is deployed in a Google Cloud Project.

-

This sprint, the team has been asked to work on a new feature that depends on being able to calculate the angle between the hands on a clock face. They’ve asked you to write some code to help out with that. This is an IOT project, and they have sensors emitting times at a pretty low frequency (about 10 a minute), and for some reason they need to be processed and stored as angles.

-

You may need to make some assupmtions, that's OK, just document what they are and move on.

-

The team loves innovation, so you can use whatever languages and technologies you like to complete this. Approach this problem as if your code will go to production. Whilst we don’t expect the code to be perfect, we are not looking for a hacked together script.

-

Your solution should offer the rest of the team a way to submit a time and receive an angle in return or store it somewhere. They are little fuzzy on the best way to get this low frequency data to your service, so if you can offer them any hints on that, they’d be really happy.

-

How to proceed

-

Fork this repo, then get to work. Remember that this is a DevOps team, so make sure your repo reflects that. Spend however much time you feel is reasonable. It doesn’t matter if the project is ‘done’, nothing ever is. When you’re ready push your changes back to Github and put in a pull request back to the base repo.

-

Be sure to add in instructions for how to deploy your solution, and document things in a way that the rest of the team can pick this up and run with it. Remember you have all the tools in the GCP arsenal at your disposal.

-

We are looking for you to demonstrate your abilities in software practices and DevOps, including reusability, portability, reliability, ease of maintenance etc.

-

Think about how this will actually be deployed and maintained in the future as you build on it and expand it. You don’t have to implement deployment practices if you don’t have the time or resources, its ok to just document those.

-
-

Product Backlog Item (Sprint Story)

-

Here is the story that is in the backlog.

-

As with all stories, the team may have been optimistic with how much can be done in the time permitted. It's ok to meet some of the acceptance criteria by documenting what you would do in the next sprint! Prioritize your time and make sure you have some technical content to deliver.

-

Description:-

-

As a team
+As a team
We need a serivce that we can send a time value to and have it return or store an angle value
-So that we can use it in downstream processing

-

Detail:-

-

We need to calculate the angle between the hands on a clock face. For example input 03:00 would yield 90 degrees.

-

Acceptance Criteria:-

-
    -
  1. Code to perform the calculation
  2. -
  3. How will you deploy this solution (in code or as a todo list if time is limited). i.e. how and where will this run?
  4. -
  5. How will you manage any infrastructure needed?
  6. -
  7. Delivered as a feature branch in the repo fork
  8. -
  9. Bonus points for a working deployed solution in GCP that you can demo at the "sprint review" (ie interview)
  10. -
  11. Any DevOps/Cicd components that would support this feature in a production setting
  12. -
-
-
- -
- - - -
- - -
- - -
-
- - - -
-
- -
-
- - -
- - - - - - -
- - - You can’t perform that action at this time. -
- - - - - - - - - - - - - +So that we can use it in downstream processing - +### Detail:- +We need to calculate the angle between the hands on a clock face. For example input 03:00 would yield 90 degrees. - - +### Acceptance Criteria:- +1) Code to perform the calculation +1) How will you deploy this solution (in code or as a todo list if time is limited). i.e. how and where will this run? +1) How will you manage any infrastructure needed? +1) Delivered as a feature branch in the repo fork +1) Bonus points for a working deployed solution in GCP that you can demo at the "sprint review" (ie interview) +1) Any DevOps/Cicd components that would support this feature in a production setting From cddfb5763fee67a2c0f7038751013c94b1620c84 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 18:39:19 +0530 Subject: [PATCH 03/15] Initial commit --- clock_angles/.gcloudignore | 19 ++++++++ clock_angles/__init__.py | 0 clock_angles/app.yaml | 1 + clock_angles/main.py | 86 +++++++++++++++++++++++++++++++++++ clock_angles/requirements.txt | 2 + clock_angles/test/__init__.py | 0 clock_angles/test/test.py | 25 ++++++++++ 7 files changed, 133 insertions(+) create mode 100644 clock_angles/.gcloudignore create mode 100644 clock_angles/__init__.py create mode 100644 clock_angles/app.yaml create mode 100644 clock_angles/main.py create mode 100644 clock_angles/requirements.txt create mode 100644 clock_angles/test/__init__.py create mode 100644 clock_angles/test/test.py diff --git a/clock_angles/.gcloudignore b/clock_angles/.gcloudignore new file mode 100644 index 0000000..a987f11 --- /dev/null +++ b/clock_angles/.gcloudignore @@ -0,0 +1,19 @@ +# This file specifies files that are *not* uploaded to Google Cloud Platform +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +# Python pycache: +__pycache__/ +# Ignored by the build system +/setup.cfg \ No newline at end of file diff --git a/clock_angles/__init__.py b/clock_angles/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clock_angles/app.yaml b/clock_angles/app.yaml new file mode 100644 index 0000000..6ae7e5a --- /dev/null +++ b/clock_angles/app.yaml @@ -0,0 +1 @@ +runtime: python37 \ No newline at end of file diff --git a/clock_angles/main.py b/clock_angles/main.py new file mode 100644 index 0000000..4e81bcb --- /dev/null +++ b/clock_angles/main.py @@ -0,0 +1,86 @@ +import json + +from flask import Flask, redirect, request, Response + +app = Flask(__name__) + + +class Helpers: + STATUS_OK = 200 + STATUS_BAD_REQUEST = 400 + STATUS_SERVER_ERROR = 500 + + @staticmethod + def standard_response(status, payload): + json_data = json.dumps({ + 'response': payload + }, sort_keys=True, indent=4, separators=(',', ': ')) + resp = Response(json_data, status=status, mimetype='application/json') + return resp + + @staticmethod + def success(payload): + return Helpers.standard_response(Helpers.STATUS_OK, payload) + + @staticmethod + def error(status, error_info): + return Helpers.standard_response(status, { + 'error': error_info + }) + + @staticmethod + def bad_request(error_info): + return Helpers.error(Helpers.STATUS_BAD_REQUEST, error_info) + + @staticmethod + def validate_input(hour: int, minute: int): + """ + Validates user input + """ + if type(hour) != int or type(minute) != int: + return False + + if 0 <= hour <= 24 and 0 <= minute <= 60: + hour = hour % 12 + minute = minute % 60 + return hour, minute + else: + return False + + +@app.route('/') +def home(): + return redirect('/clock_angles') + + +@app.route('/clock_angles', methods=['GET', 'POST']) +def calculate_angles(): + """ + returns the angle between the hour hand and the minute hand + + Request to be sent should be POST and should have a json payload containing + ifo on location of hour hand and minute hand + """ + if request.method == 'GET': + return Helpers.bad_request('GET request received. Expected POST with application/json body') + + else: + request_data = request.get_json() + result = Helpers.validate_input(request_data['hour_hand'], request_data['minute_hand']) + if result: + hour, minute = result + + hour_angle = 0.5 * (hour * 60 + minute) + minute_angle = 6 * minute + + angle = abs(hour_angle - minute_angle) + + return Helpers.success(min(360 - angle, angle)) + else: + return Helpers.bad_request("Invalid arguments sent. " + "Expected integer values between 0 and 24 for hour and " + "between 0 and 60 for minute...") + + +if __name__ == '__main__': + app.run(host='127.0.0.1', port=8080, debug=True) diff --git a/clock_angles/requirements.txt b/clock_angles/requirements.txt new file mode 100644 index 0000000..7b59451 --- /dev/null +++ b/clock_angles/requirements.txt @@ -0,0 +1,2 @@ +Flask==1.1.2 +pytest==5.3.2 \ No newline at end of file diff --git a/clock_angles/test/__init__.py b/clock_angles/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clock_angles/test/test.py b/clock_angles/test/test.py new file mode 100644 index 0000000..ef808c9 --- /dev/null +++ b/clock_angles/test/test.py @@ -0,0 +1,25 @@ +from clock_angles import main +import json + + +def test_index(): + main.app.testing = True + client = main.app.test_client() + + r = client.post("/clock_angles", json={'hour_hand': 13, 'minute_hand': 0}) + assert r.status_code == 200 + response_json = json.loads(r.data.decode('utf-8')) + assert {'response': 30.0} == response_json + + r = client.post("/clock_angles", json={'hour_hand': '1', 'minute_hand': 0}) + assert r.status_code == 400 + response_json = json.loads(r.data.decode('utf-8')) + assert_json = { + 'response': + { + 'error': 'Invalid arguments sent. Expected integer values between 0 and 24 for ' + 'hour and between 0 and 60 for minute...' + } + } + + assert assert_json == response_json From 08fe9da1572fb9388c01eeeba5f3d63546de9de9 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 19:07:25 +0530 Subject: [PATCH 04/15] added pytests --- .gitignore | 4 ++++ clock_angles/__init__.py | 1 + clock_angles/test/__init__.py | 0 clock_angles/test/test.py | 13 ++++++++++++- cloudbuild.yaml | 12 ++++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .gitignore delete mode 100644 clock_angles/test/__init__.py create mode 100644 cloudbuild.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af8fae3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +venv/ +venv_clocks/ +.idea/ +__pycache__/ diff --git a/clock_angles/__init__.py b/clock_angles/__init__.py index e69de29..8b13789 100644 --- a/clock_angles/__init__.py +++ b/clock_angles/__init__.py @@ -0,0 +1 @@ + diff --git a/clock_angles/test/__init__.py b/clock_angles/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/clock_angles/test/test.py b/clock_angles/test/test.py index ef808c9..24ce495 100644 --- a/clock_angles/test/test.py +++ b/clock_angles/test/test.py @@ -2,7 +2,7 @@ import json -def test_index(): +def test_positive(): main.app.testing = True client = main.app.test_client() @@ -11,6 +11,10 @@ def test_index(): response_json = json.loads(r.data.decode('utf-8')) assert {'response': 30.0} == response_json + +def test_negative(): + main.app.testing = True + client = main.app.test_client() r = client.post("/clock_angles", json={'hour_hand': '1', 'minute_hand': 0}) assert r.status_code == 400 response_json = json.loads(r.data.decode('utf-8')) @@ -23,3 +27,10 @@ def test_index(): } assert assert_json == response_json + + +if __name__ == "__main__": + import sys + import os + + sys.path.append(os.path.dirname(os.getcwd())) \ No newline at end of file diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..c4f9cf6 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,12 @@ +steps: +- name: 'docker.io/library/python:3.7' + id: Test + entrypoint: /bin/sh + args: + - -c + - 'export PYTHON_PATH=`/` pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' +- name: "gcr.io/cloud-builders/gcloud" + args: ["config", "list"] +- name: "gcr.io/cloud-builders/gcloud" + args: ["app", "deploy", "clock_angles/app.yaml"] +timeout: "1600s" From 67721514f6441621cfba10aadd1b892675e4b395 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 19:08:30 +0530 Subject: [PATCH 05/15] added pytests --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index c4f9cf6..a78be2e 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,7 @@ steps: entrypoint: /bin/sh args: - -c - - 'export PYTHON_PATH=`/` pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' + - 'export PYTHON_PATH=`/` && pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' - name: "gcr.io/cloud-builders/gcloud" args: ["config", "list"] - name: "gcr.io/cloud-builders/gcloud" From 10dc5a4b2904261711e5b918dacf038d526b276a Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 19:13:24 +0530 Subject: [PATCH 06/15] check 1 --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a78be2e..ba6d603 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,7 @@ steps: entrypoint: /bin/sh args: - -c - - 'export PYTHON_PATH=`/` && pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' + - 'echo `pwd` && ls -l && pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' - name: "gcr.io/cloud-builders/gcloud" args: ["config", "list"] - name: "gcr.io/cloud-builders/gcloud" From c4c5f827c172f2fb4a493b9e42945da01753c073 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 19:14:51 +0530 Subject: [PATCH 07/15] check 1 --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index ba6d603..7af3884 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,7 @@ steps: entrypoint: /bin/sh args: - -c - - 'echo `pwd` && ls -l && pip install -t /workspace/lib -r clock_angeles/requirements.txt && pytest clock_angles/test/test.py' + - 'pip3 install -r clock_angles/requirements.txt' - name: "gcr.io/cloud-builders/gcloud" args: ["config", "list"] - name: "gcr.io/cloud-builders/gcloud" From cf248eb60b50db791772b96021fc62a7f8af8cf7 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 19:17:32 +0530 Subject: [PATCH 08/15] check 3 --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 7af3884..bbb9d09 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,7 @@ steps: entrypoint: /bin/sh args: - -c - - 'pip3 install -r clock_angles/requirements.txt' + - 'pip3 install -r clock_angles/requirements.txt && export PYTHONPATH=`pwd` && pytest -v clock_angles/test/test.py' - name: "gcr.io/cloud-builders/gcloud" args: ["config", "list"] - name: "gcr.io/cloud-builders/gcloud" From 70ef7c6b7adb2ce577b9b01071fd8212f3abd0e5 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 20:51:15 +0530 Subject: [PATCH 09/15] Changed request type to GET --- clock_angles/lib/__init__.py | 0 clock_angles/lib/helpers.py | 53 +++++++++++++++++++++++++ clock_angles/main.py | 75 +++++++----------------------------- clock_angles/test/test.py | 6 --- 4 files changed, 67 insertions(+), 67 deletions(-) create mode 100644 clock_angles/lib/__init__.py create mode 100644 clock_angles/lib/helpers.py diff --git a/clock_angles/lib/__init__.py b/clock_angles/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/clock_angles/lib/helpers.py b/clock_angles/lib/helpers.py new file mode 100644 index 0000000..5e4486e --- /dev/null +++ b/clock_angles/lib/helpers.py @@ -0,0 +1,53 @@ +import json +import re + +from flask import Response + + +class Helpers: + STATUS_OK = 200 + STATUS_BAD_REQUEST = 400 + STATUS_SERVER_ERROR = 500 + + @staticmethod + def standard_response(status, payload): + json_data = json.dumps({ + 'response': payload + }, sort_keys=True, indent=4, separators=(',', ': ')) + resp = Response(json_data, status=status, mimetype='application/json') + return resp + + @staticmethod + def success(payload): + return Helpers.standard_response(Helpers.STATUS_OK, payload) + + @staticmethod + def error(status, error_info): + return Helpers.standard_response(status, { + 'error': error_info + }) + + @staticmethod + def bad_request(error_info): + return Helpers.error(Helpers.STATUS_BAD_REQUEST, error_info) + + @staticmethod + def validate_and_parse_input(time: str): + """ + Validates user input. + + For an input to be valid, string should contain at least 3 characters + and the all characters except ':' should be digits + """ + if time is None or not re.match(r'^\d{1,2}:\d{1,2}$', time): + return False + hour, minute = map(int, time.split(r':')) + if type(hour) != int or type(minute) != int: + return False + + if 0 <= hour < 24 and 0 <= minute < 60: + hour = hour % 12 + minute = minute + return hour, minute + else: + return False diff --git a/clock_angles/main.py b/clock_angles/main.py index 4e81bcb..29cafaa 100644 --- a/clock_angles/main.py +++ b/clock_angles/main.py @@ -1,59 +1,16 @@ -import json +from flask import Flask, redirect, request +from lib.helpers import Helpers -from flask import Flask, redirect, request, Response app = Flask(__name__) -class Helpers: - STATUS_OK = 200 - STATUS_BAD_REQUEST = 400 - STATUS_SERVER_ERROR = 500 - - @staticmethod - def standard_response(status, payload): - json_data = json.dumps({ - 'response': payload - }, sort_keys=True, indent=4, separators=(',', ': ')) - resp = Response(json_data, status=status, mimetype='application/json') - return resp - - @staticmethod - def success(payload): - return Helpers.standard_response(Helpers.STATUS_OK, payload) - - @staticmethod - def error(status, error_info): - return Helpers.standard_response(status, { - 'error': error_info - }) - - @staticmethod - def bad_request(error_info): - return Helpers.error(Helpers.STATUS_BAD_REQUEST, error_info) - - @staticmethod - def validate_input(hour: int, minute: int): - """ - Validates user input - """ - if type(hour) != int or type(minute) != int: - return False - - if 0 <= hour <= 24 and 0 <= minute <= 60: - hour = hour % 12 - minute = minute % 60 - return hour, minute - else: - return False - - @app.route('/') def home(): return redirect('/clock_angles') -@app.route('/clock_angles', methods=['GET', 'POST']) +@app.route('/clock_angles', methods=['GET']) def calculate_angles(): """ returns the angle between the hour hand and the minute hand @@ -61,25 +18,21 @@ def calculate_angles(): Request to be sent should be POST and should have a json payload containing ifo on location of hour hand and minute hand """ - if request.method == 'GET': - return Helpers.bad_request('GET request received. Expected POST with application/json body') + time = request.args.get('time') - else: - request_data = request.get_json() - result = Helpers.validate_input(request_data['hour_hand'], request_data['minute_hand']) - if result: - hour, minute = result + result = Helpers.validate_and_parse_input(time) + if result: + hour, minute = result - hour_angle = 0.5 * (hour * 60 + minute) - minute_angle = 6 * minute + hour_angle = 0.5 * (hour * 60 + minute) + minute_angle = 6 * minute - angle = abs(hour_angle - minute_angle) + angle = abs(hour_angle - minute_angle) - return Helpers.success(min(360 - angle, angle)) - else: - return Helpers.bad_request("Invalid arguments sent. " - "Expected integer values between 0 and 24 for hour and " - "between 0 and 60 for minute...") + return Helpers.success(min(360 - angle, angle)) + else: + return Helpers.bad_request(r"query parameter time should follow regex ^\d{1,2}:\d{1,2}$ and value should be " + r"between 00:00 and 23:59") if __name__ == '__main__': diff --git a/clock_angles/test/test.py b/clock_angles/test/test.py index 24ce495..6507d09 100644 --- a/clock_angles/test/test.py +++ b/clock_angles/test/test.py @@ -28,9 +28,3 @@ def test_negative(): assert assert_json == response_json - -if __name__ == "__main__": - import sys - import os - - sys.path.append(os.path.dirname(os.getcwd())) \ No newline at end of file From 566192efc1b74c913c06e0a6260c48fe9db2db18 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 20:55:43 +0530 Subject: [PATCH 10/15] Added pythonpath to run pytests --- cloudbuild.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudbuild.yaml b/cloudbuild.yaml index bbb9d09..6f2d7b1 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -4,7 +4,7 @@ steps: entrypoint: /bin/sh args: - -c - - 'pip3 install -r clock_angles/requirements.txt && export PYTHONPATH=`pwd` && pytest -v clock_angles/test/test.py' + - 'pip3 install -r clock_angles/requirements.txt && export PYTHONPATH=`pwd`:`pwd`/clock_angles && pytest -v clock_angles/test/test.py' - name: "gcr.io/cloud-builders/gcloud" args: ["config", "list"] - name: "gcr.io/cloud-builders/gcloud" From 706d34d0787ce269334cbcd469aaf41c6cffd5dc Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 21:04:26 +0530 Subject: [PATCH 11/15] Updated to use GET request --- clock_angles/test/test.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/clock_angles/test/test.py b/clock_angles/test/test.py index 6507d09..0d766f8 100644 --- a/clock_angles/test/test.py +++ b/clock_angles/test/test.py @@ -6,25 +6,38 @@ def test_positive(): main.app.testing = True client = main.app.test_client() - r = client.post("/clock_angles", json={'hour_hand': 13, 'minute_hand': 0}) + r = client.get("/clock_angles?time=13:0") assert r.status_code == 200 response_json = json.loads(r.data.decode('utf-8')) assert {'response': 30.0} == response_json -def test_negative(): +def test_negative_out_of_bounds(): main.app.testing = True client = main.app.test_client() - r = client.post("/clock_angles", json={'hour_hand': '1', 'minute_hand': 0}) + r = client.get("/clock_angles?time=24:40") assert r.status_code == 400 response_json = json.loads(r.data.decode('utf-8')) assert_json = { - 'response': - { - 'error': 'Invalid arguments sent. Expected integer values between 0 and 24 for ' - 'hour and between 0 and 60 for minute...' - } + 'response': { + 'error': r'query parameter time should follow regex ' + r'^\d{1,2}:\d{1,2}$ and value should be between 00:00 and 23:59' + } } + assert assert_json == response_json + +def test_negetive_invalid_input(): + main.app.testing = True + client = main.app.test_client() + r = client.get("/clock_angles?time=2A:40") + assert r.status_code == 400 + response_json = json.loads(r.data.decode('utf-8')) + assert_json = { + 'response': { + 'error': r'query parameter time should follow regex ' + r'^\d{1,2}:\d{1,2}$ and value should be between 00:00 and 23:59' + } + } assert assert_json == response_json From d46184d1a7c259c04081dde873d3ba79665cf995 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 21:45:43 +0530 Subject: [PATCH 12/15] Added datastore logging --- clock_angles/lib/helpers.py | 20 ++++++++++++++++++++ clock_angles/main.py | 6 ++++-- clock_angles/requirements.txt | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/clock_angles/lib/helpers.py b/clock_angles/lib/helpers.py index 5e4486e..62c934b 100644 --- a/clock_angles/lib/helpers.py +++ b/clock_angles/lib/helpers.py @@ -2,6 +2,9 @@ import re from flask import Response +import uuid + +from google.cloud import datastore class Helpers: @@ -51,3 +54,20 @@ def validate_and_parse_input(time: str): return hour, minute else: return False + + +class DatastoreClient: + def __init__(self, kind): + self.client = datastore.Client(project='ci-cd-clock-angles', namespace='default') + self.kind = kind + + def log_to_datastore(self, request_time, response_angle): + name = str(uuid.uuid4()) + log_key = self.client.key(self.kind, name) + + log_entry = datastore.Entity(key=log_key) + log_entry['time'] = request_time + log_entry['response'] = response_angle + + # Saves the entity + self.client.put(log_entry) diff --git a/clock_angles/main.py b/clock_angles/main.py index 29cafaa..768de7e 100644 --- a/clock_angles/main.py +++ b/clock_angles/main.py @@ -1,5 +1,5 @@ from flask import Flask, redirect, request -from lib.helpers import Helpers +from lib.helpers import Helpers, DatastoreClient app = Flask(__name__) @@ -28,8 +28,10 @@ def calculate_angles(): minute_angle = 6 * minute angle = abs(hour_angle - minute_angle) + angle = min(360 - angle, angle) + DatastoreClient(kind='clock_angle_logs').log_to_datastore(time, angle) - return Helpers.success(min(360 - angle, angle)) + return Helpers.success(angle) else: return Helpers.bad_request(r"query parameter time should follow regex ^\d{1,2}:\d{1,2}$ and value should be " r"between 00:00 and 23:59") diff --git a/clock_angles/requirements.txt b/clock_angles/requirements.txt index 7b59451..09dc3b0 100644 --- a/clock_angles/requirements.txt +++ b/clock_angles/requirements.txt @@ -1,2 +1,3 @@ Flask==1.1.2 -pytest==5.3.2 \ No newline at end of file +pytest==5.3.2 +google-cloud-datastore==1.12.0 \ No newline at end of file From 2325957b3d9de18dcf0ee8ca3ef1b513773011b0 Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Wed, 13 May 2020 21:51:59 +0530 Subject: [PATCH 13/15] added logging in negative scenarios --- clock_angles/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/clock_angles/main.py b/clock_angles/main.py index 768de7e..5ee7f82 100644 --- a/clock_angles/main.py +++ b/clock_angles/main.py @@ -33,6 +33,7 @@ def calculate_angles(): return Helpers.success(angle) else: + DatastoreClient(kind='clock_angle_logs').log_to_datastore(time, 'bad_request') return Helpers.bad_request(r"query parameter time should follow regex ^\d{1,2}:\d{1,2}$ and value should be " r"between 00:00 and 23:59") From 12f6b45ba8616eb68e743659c8878bf21bf9f50f Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Thu, 14 May 2020 18:16:01 +0530 Subject: [PATCH 14/15] Updated docstring for the clock_angles method --- clock_angles/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clock_angles/main.py b/clock_angles/main.py index 5ee7f82..2098f31 100644 --- a/clock_angles/main.py +++ b/clock_angles/main.py @@ -15,8 +15,8 @@ def calculate_angles(): """ returns the angle between the hour hand and the minute hand - Request to be sent should be POST and should have a json payload containing - ifo on location of hour hand and minute hand + Request to be sent should be GET and should have a query string with key time containing + the HH:MM information """ time = request.args.get('time') From af5e25ff4603adea259f957de8c0795c07c99cff Mon Sep 17 00:00:00 2001 From: Mohammad Tameem Date: Thu, 14 May 2020 19:47:39 +0530 Subject: [PATCH 15/15] build test --- clock_angles/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clock_angles/main.py b/clock_angles/main.py index 2098f31..620a78e 100644 --- a/clock_angles/main.py +++ b/clock_angles/main.py @@ -17,6 +17,8 @@ def calculate_angles(): Request to be sent should be GET and should have a query string with key time containing the HH:MM information + + Build demo """ time = request.args.get('time')