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__/ diff --git a/calculator.py b/calculator.py index a46affd..da13b9c 100644 --- a/calculator.py +++ b/calculator.py @@ -1,84 +1,126 @@ +#!/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 +# ------------------------------------------------------------------------ # """ -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. +import re +import functools -You'll need to support: +DEFAULT = "No Value Set" - * 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`. +def index(): + funcs = ['add', 'subtract', 'multiply', 'divide'] -Consider the following URL/Response body pairs as tests: + 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

', '
'] -``` - 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... -``` + item_template = '') + return '\n'.join(body) -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 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 -""" +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 -def add(*args): +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 - # TODO: Fill sum with the correct value, based on the - # args provided. - sum = "0" - return sum +def divide(*args): + """ Returns a STRING with the sum of the arguments """ + try: + 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): + """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)) + 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

" + 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')] -# TODO: Add functions for handling more arithmetic operations. 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} - # 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'] + 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 - # 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. - pass 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() 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):