From 7eb81874ecbf429f9e69c9219420974fd5853070 Mon Sep 17 00:00:00 2001 From: Andy Simpson Date: Tue, 20 Oct 2020 16:45:47 -0700 Subject: [PATCH 1/4] intial setup of the calculator.py script - AS --- calculator.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/calculator.py b/calculator.py index a46affd..e8c72c0 100644 --- a/calculator.py +++ b/calculator.py @@ -40,7 +40,14 @@ """ - +def homepage(): + all_books = DB.titles() + body = ['

Calculator

', '') + return '\n'.join(body) def add(*args): """ Returns a STRING with the sum of the arguments """ @@ -48,6 +55,39 @@ def add(*args): # TODO: Fill sum with the correct value, based on the # args provided. sum = "0" + for item in args: + sum += item + return sum + +def subtract(*args): + """ Returns a STRING with the sum of the arguments """ + + # TODO: Fill sum with the correct value, based on the + # args provided. + sum = "0" + for item in args: + sum -= item + return sum + +def multiply(*args): + """ Returns a STRING with the sum of the arguments """ + + # TODO: Fill sum with the correct value, based on the + # args provided. + sum = "0" + for item in args: + sum *= item + + return sum + +def divide(*args): + """ Returns a STRING with the sum of the arguments """ + + # TODO: Fill sum with the correct value, based on the + # args provided. + sum = "0" + for item in args: + sum /= item return sum @@ -63,11 +103,31 @@ def resolve_path(path): # examples provide the correct *syntax*, but you should # determine the actual values of func and args using the # path. - func = add - args = ['25', '32'] + #func = add + #args = ['25', '32'] + + + funcs = { + '': home, + 'add': add, + 'subtract': subrtact, + 'multiply': multiply, + 'divide': divide, + } + + path = path.strip('/').split('/') + + func_name = path[0] + args = path[1:] + + try: + func = funcs[func_name] + except KeyError: + raise NameError return func, args + def application(environ, start_response): # TODO: Your application code from the book database # work here as well! Remember that your application must @@ -76,9 +136,33 @@ def application(environ, start_response): # # TODO (bonus): Add error handling for a user attempting # to divide by zero. - pass + headers = [("Content-type", "text/html")] + try: + path = environ.get('PATH_INFO', None) + if path is None: + raise NameError + func, args = resolve_path(path) + body = func(*args) + status = "200 OK" + except NameError: + status = "404 Not Found" + body = "

Not Found

" + except Exception: + status = "500 Internal Server Error" + body = "

Internal Server Error

" + print(traceback.format_exc()) + except ZeroDivisionError: + status = "Can not divide by zero" + body = "

Zero Division Error. Please Try again!!

" + finally: + headers.append(('Content-length', str(len(body)))) + start_response(status, headers) + return [body.encode('utf8')] if __name__ == '__main__': # TODO: Insert the same boilerplate wsgiref simple # server creation that you used in the book database. - pass + + from wsgiref.simple_server import make_server + srv = make_server('localhost', 8080, application) + srv.serve_forever() From 06748f90be9c87b8786df21c2e6968921594d58b Mon Sep 17 00:00:00 2001 From: Andy Simpson Date: Tue, 20 Oct 2020 16:45:56 -0700 Subject: [PATCH 2/4] intial setup of the calculator.py script - AS --- .gitignore | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1f0145 --- /dev/null +++ b/.gitignore @@ -0,0 +1,139 @@ +*pycache* +*solution* +*.idea* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# USER SPECIFIC/IDE FILES +.idea/ + +# GENERAL PYTHON FILES +__pycache__/ From 5422332d1a20c0b1003ab937561a39c348b7c5f2 Mon Sep 17 00:00:00 2001 From: Andy Simpson Date: Wed, 21 Oct 2020 22:10:32 -0700 Subject: [PATCH 3/4] Updated the calculator.py and tests.py scripts. Now testing final results. - AS --- calculator.py | 206 +++++++++++++++++++------------------------------- tests.py | 18 ++--- 2 files changed, 82 insertions(+), 142 deletions(-) diff --git a/calculator.py b/calculator.py index e8c72c0..88ec2ea 100644 --- a/calculator.py +++ b/calculator.py @@ -1,147 +1,78 @@ -""" -For your homework this week, you'll be creating a wsgi application of -your own. - -You'll create an online calculator that can perform several operations. - -You'll need to support: - - * Addition - * Subtractions - * Multiplication - * Division - -Your users should be able to send appropriate requests and get back -proper responses. For example, if I open a browser to your wsgi -application at `http://localhost:8080/multiple/3/5' then the response -body in my browser should be `15`. - -Consider the following URL/Response body pairs as tests: - -``` - http://localhost:8080/multiply/3/5 => 15 - http://localhost:8080/add/23/42 => 65 - http://localhost:8080/subtract/23/42 => -19 - http://localhost:8080/divide/22/11 => 2 - http://localhost:8080/ => Here's how to use this page... -``` - -To submit your homework: - - * Fork this repository (Session03). - * Edit this file to meet the homework requirements. - * Your script should be runnable using `$ python calculator.py` - * When the script is running, I should be able to view your - application in my browser. - * I should also be able to see a home page (http://localhost:8080/) - that explains how to perform calculations. - * Commit and push your changes to your fork. - * Submit a link to your Session03 fork repository! - - -""" -def homepage(): - all_books = DB.titles() - body = ['

Calculator

', '
    '] - item_template = '
  • {title}
  • ' - for book in all_books: - body.append(item_template.format(**book)) - body.append('
') - return '\n'.join(body) +#!/usr/bin/env python -def add(*args): - """ Returns a STRING with the sum of the arguments """ - # TODO: Fill sum with the correct value, based on the - # args provided. - sum = "0" - for item in args: - sum += item - return sum -def subtract(*args): - """ Returns a STRING with the sum of the arguments """ - # TODO: Fill sum with the correct value, based on the - # args provided. - sum = "0" - for item in args: - sum -= item - return sum +import re +import functools -def multiply(*args): - """ Returns a STRING with the sum of the arguments """ +DEFAULT = "No Value Set" - # TODO: Fill sum with the correct value, based on the - # args provided. - sum = "0" - for item in args: - sum *= item - return sum +def index(): + funcs = ['add', 'subtract', 'multiply', 'divide'] -def divide(*args): - """ Returns a STRING with the sum of the arguments """ - - # TODO: Fill sum with the correct value, based on the - # args provided. - sum = "0" - for item in args: - sum /= item - - return sum - -# TODO: Add functions for handling more arithmetic operations. - -def resolve_path(path): - """ - Should return two values: a callable and an iterable of - arguments. - """ - - # TODO: Provide correct values for func and args. The - # examples provide the correct *syntax*, but you should - # determine the actual values of func and args using the - # path. - #func = add - #args = ['25', '32'] + body = ['

Calculator!!

', '

To use this calculator, select the
' + ' operation below and add a "/" between each
' + ' number in the URL address bar.

Ex: /multiply/3/5

', '
',] + item_template = '
  • {0} numbers
  • ' + for func in funcs: + body.append(item_template.format(func)) + body.append('
') + return '\n'.join(body) - funcs = { - '': home, - 'add': add, - 'subtract': subrtact, - 'multiply': multiply, - 'divide': divide, - } +def add(*args): + """ Returns a STRING with the sum of the arguments """ + response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum(map(int, args)))) + return response_body - path = path.strip('/').split('/') +def subtract(*args): + """ Returns a STRING with the sum of the arguments """ + if len(args) == 0: + sum = 0 + else: + sum = functools.reduce(lambda a, b: a - b, map(int, args)) + response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum)) + return response_body - func_name = path[0] - args = path[1:] +def multiply(*args): + """ Returns a STRING with the sum of the arguments """ + if len(args) == 0: + sum = 0 + else: + sum = functools.reduce(lambda a, b: a * b, map(int, args)) + response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum)) + return response_body +def divide(*args): + """ Returns a STRING with the sum of the arguments """ try: - func = funcs[func_name] - except KeyError: - raise NameError - - return func, args + sum = 0 + if "0" in args: + raise ZeroDivisionError + if 0 in args: + raise ZeroDivisionError + if len(args) == 0: + raise ValueError + sum = functools.reduce(lambda a, b: a / b, map(int, args)) + response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum)) + return response_body + except ZeroDivisionError: + response_body = '

Cannot divide by zero. Try Again!


Back to the list of operations' + return response_body + except ValueError: + response_body = '

No values were provided for Division Operation.
Please provide values and Try Again!' \ + '


Back to the list of operations' + return response_body def application(environ, start_response): - # TODO: Your application code from the book database - # work here as well! Remember that your application must - # invoke start_response(status, headers) and also return - # the body of the response in BYTE encoding. - # - # TODO (bonus): Add error handling for a user attempting - # to divide by zero. - headers = [("Content-type", "text/html")] + """Application function used to create a response in bytes bak to the client""" + try: - path = environ.get('PATH_INFO', None) - if path is None: - raise NameError - func, args = resolve_path(path) + headers = [("Content-type", "text/html")] + func, args = resolve_path(environ.get('PATH_INFO', DEFAULT)) body = func(*args) status = "200 OK" except NameError: @@ -150,7 +81,6 @@ def application(environ, start_response): except Exception: status = "500 Internal Server Error" body = "

Internal Server Error

" - print(traceback.format_exc()) except ZeroDivisionError: status = "Can not divide by zero" body = "

Zero Division Error. Please Try again!!

" @@ -159,10 +89,26 @@ def application(environ, start_response): start_response(status, headers) return [body.encode('utf8')] -if __name__ == '__main__': - # TODO: Insert the same boilerplate wsgiref simple - # server creation that you used in the book database. +def resolve_path(path): + """ + Should return two values: a callable and an iterable of + arguments. + """ + funcs = {"": index, "add": add, "subtract": subtract, "multiply": multiply, "divide": divide,} + + path = path.strip('/').split('/') + func_name = path[0] + args = path[1:] + + try: + func = funcs[func_name] + except KeyError: + raise NameError + return func, args + + +if __name__ == '__main__': from wsgiref.simple_server import make_server srv = make_server('localhost', 8080, application) srv.serve_forever() diff --git a/tests.py b/tests.py index c2a8dcb..9733b6c 100644 --- a/tests.py +++ b/tests.py @@ -2,6 +2,7 @@ import subprocess import http.client import random +import time class WebTestCase(unittest.TestCase): @@ -16,6 +17,9 @@ def setUp(self): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) + # Had to pause the code here for a second to let the sun processes finish + # otherwise if we don't all the tests will fail. + time.sleep(1) def tearDown(self): self.server_process.kill() @@ -25,9 +29,10 @@ def get_response(self, url): """ Helper function to get a response from a given url, using http.client """ - conn = http.client.HTTPConnection('localhost:8080') + conn.request('GET', url) + #time.sleep(1) response = conn.getresponse() self.assertEqual(200, response.getcode()) @@ -40,7 +45,6 @@ def test_add(self): """ A call to /add/a/b yields a + b """ - a = random.randint(100, 10000) b = random.randint(100, 10000) @@ -48,14 +52,12 @@ def test_add(self): response = self.get_response(path) self.assertEqual(200, response.getcode()) - self.assertIn(str(a + b).encode(), response.read()) def test_multiply(self): """ A call to /multiply/a/b yields a*b """ - a = random.randint(100, 10000) b = random.randint(100, 10000) @@ -63,14 +65,12 @@ def test_multiply(self): response = self.get_response(path) self.assertEqual(200, response.getcode()) - self.assertIn(str(a*b).encode(), response.read()) def test_subtract_positive_result(self): """ A call to /subtract/a/b yields a - b, for a > b """ - a = random.randint(10000, 100000) b = random.randint(100, 1000) @@ -78,14 +78,12 @@ def test_subtract_positive_result(self): response = self.get_response(path) self.assertEqual(200, response.getcode()) - self.assertIn(str(a - b).encode(), response.read()) def test_subtract_negative_result(self): """ A call to /subtract/a/b yields a - b, for a < b """ - a = random.randint(100, 1000) b = random.randint(10000, 100000) @@ -93,16 +91,13 @@ def test_subtract_negative_result(self): response = self.get_response(path) self.assertEqual(200, response.getcode()) - self.assertIn(str(a - b).encode(), response.read()) def test_divide(self): """ A call to /divide/a/b yields a/b, for a % b = 0 """ - result = random.randint(2, 10) - b = random.randint(100, 1000) a = result * b @@ -110,7 +105,6 @@ def test_divide(self): response = self.get_response(path) self.assertEqual(200, response.getcode()) - self.assertIn(str(result).encode(), response.read()) def test_index_instructions(self): From 1c04d0155f610bc399d4ef3f16fcaf98abe8a2cd Mon Sep 17 00:00:00 2001 From: Andy Simpson Date: Wed, 21 Oct 2020 22:22:16 -0700 Subject: [PATCH 4/4] De-linting and more testing - AS --- calculator.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/calculator.py b/calculator.py index 88ec2ea..da13b9c 100644 --- a/calculator.py +++ b/calculator.py @@ -1,7 +1,14 @@ #!/usr/bin/env python - - - +""" +# ------------------------------------------------------------------------ # +# Title: Lesson 4 - WSGI (The Web Server Gateway Interface) example. This script +# is a set of simple calculator web pages allowing the user to pass values in and get +# calculated values back. +# Description: This is the main "calculator.py" script. +# ChangeLog (Who,When,What): +# ASimpson, 10/21/2020, Modified code to complete lesson4 - Assignment04 +# ------------------------------------------------------------------------ # +""" import re import functools @@ -14,7 +21,7 @@ def index(): body = ['

Calculator!!

', '

To use this calculator, select the
' ' operation below and add a "/" between each
' - ' number in the URL address bar.

Ex: /multiply/3/5

', '
',] + ' number in the URL address bar.

Ex: /multiply/3/5', '
'] item_template = '
  • {0} numbers
  • ' for func in funcs: @@ -22,11 +29,14 @@ def index(): body.append('
') return '\n'.join(body) + def add(*args): """ Returns a STRING with the sum of the arguments """ - response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum(map(int, args)))) + response_body = '

Your total is: {0}


Back to the ' \ + 'list of operations'.format(str(sum(map(int, args)))) return response_body + def subtract(*args): """ Returns a STRING with the sum of the arguments """ if len(args) == 0: @@ -36,6 +46,7 @@ def subtract(*args): response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum)) return response_body + def multiply(*args): """ Returns a STRING with the sum of the arguments """ if len(args) == 0: @@ -45,6 +56,7 @@ def multiply(*args): response_body = '

Your total is: {0}


Back to the list of operations'.format(str(sum)) return response_body + def divide(*args): """ Returns a STRING with the sum of the arguments """ try: @@ -69,7 +81,6 @@ def divide(*args): def application(environ, start_response): """Application function used to create a response in bytes bak to the client""" - try: headers = [("Content-type", "text/html")] func, args = resolve_path(environ.get('PATH_INFO', DEFAULT)) @@ -95,7 +106,8 @@ def resolve_path(path): Should return two values: a callable and an iterable of arguments. """ - funcs = {"": index, "add": add, "subtract": subtract, "multiply": multiply, "divide": divide,} + funcs = {"": index, "add": add, "subtract": subtract, + "multiply": multiply, "divide": divide} path = path.strip('/').split('/') func_name = path[0]