diff --git a/architecture/2019_01_linear_systems_APIs_and_authentication/examples/Server usage examples.ipynb b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/Server usage examples.ipynb new file mode 100644 index 0000000..c79f885 --- /dev/null +++ b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/Server usage examples.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "from requests.auth import HTTPBasicAuth\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "request_content = {\n", + " 'index_one': 1,\n", + " 'index_two': 2,\n", + " 'array': [[1, 2, 3], [4, 5, 6], [7, 8, 9]]\n", + "}\n", + "\n", + "resp = requests.get(\n", + " 'http://localhost:5000/row_interchange',\n", + " auth=HTTPBasicAuth('example_user', 'example_password'),\n", + " json=request_content\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[1., 2., 3.],\n", + " [7., 8., 9.],\n", + " [4., 5., 6.]])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array(resp.json())" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "request_content = {\n", + " 'mult_index': 0,\n", + " 'mult_by': 2.5,\n", + " 'array': [[1, 2, 3], [4, 5, 6], [7, 8, 9]],\n", + " 'api_token': 'thisshouldbeahashedstringwithhighentropy'\n", + "}\n", + "\n", + "resp = requests.get(\n", + " 'http://localhost:5000/scalar_multiplication',\n", + " json=request_content\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2.5, 5. , 7.5],\n", + " [4. , 5. , 6. ],\n", + " [7. , 8. , 9. ]])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "np.array(resp.json())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/architecture/2019_01_linear_systems_APIs_and_authentication/examples/num_method_server.py b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/num_method_server.py new file mode 100644 index 0000000..35e914f --- /dev/null +++ b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/num_method_server.py @@ -0,0 +1,73 @@ +from functools import wraps +import numpy as np +from flask import Flask, request, Response, jsonify +from numerical_methods import row_interchange, scalar_multiplication, add_scalar_mult +app = Flask(__name__) + +creds = {'example_user': 'example_password'} +acceptable_tokens = {'thisshouldbeahashedstringwithhighentropy', } + + +def check_http_basic_auth(username, password): + # Hopefully it goes without saying that we would never + # store these credentials in plain-text in the real world. + if username is None or password is None: + return False + + return creds.get(username) == password + + +def http_login_required(f): + @wraps(f) + def required_login_wrapper(*args, **kwargs): + auth = request.authorization + if check_http_basic_auth(auth.username, auth.password): + return f(*args, **kwargs) + else: + return Response('Failed auth', 403) + + return required_login_wrapper + + +def check_api_token(api_token): + return api_token in acceptable_tokens + + +def api_token_required(f): + @wraps(f) + def required_login_wrapper(*args, **kwargs): + if check_api_token(request.json['api_token']): + return f(*args, **kwargs) + else: + return Response('Failed token check', 403) + return required_login_wrapper + + +def make_array_from_json(js): + array_as_json = js.get('array') + try: + return np.array(array_as_json, dtype=np.float64) + except Exception: # Should be catching a more specific error + return Response('Could not create a NumPy array from request', 400) + + +@app.route('/row_interchange') +@http_login_required +def basic_auth_row_interchange(): + req_content = request.json + np_array = make_array_from_json(req_content) + res = row_interchange(np_array, req_content['index_one'], req_content['index_two']) + return jsonify(res.tolist()) + + +@app.route('/scalar_multiplication') +@api_token_required +def api_token_scalar_multiplication(): + req_content = request.json + np_array = make_array_from_json(req_content) + res = scalar_multiplication(np_array, req_content['mult_index'], req_content['mult_by']) + return jsonify(res.tolist()) + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/architecture/2019_01_linear_systems_APIs_and_authentication/examples/numerical_methods.py b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/numerical_methods.py new file mode 100644 index 0000000..df27958 --- /dev/null +++ b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/numerical_methods.py @@ -0,0 +1,28 @@ +import numpy as np + + +def row_interchange(arr: np.ndarray, index_one: int, index_two: int): + res = arr.copy() + res[[index_one, index_two]] = res[[index_two, index_one]] + return res + + +def scalar_multiplication( + arr: np.ndarray, + mult_index: int, + multiply_by: np.float64 +): + res = arr.copy() + res[mult_index] *= multiply_by + return res + + +def add_scalar_mult( + arr: np.ndarray, + mult_index: int, + multiply_by: np.float64, + add_index: int +): + res = arr.copy() + res[add_index] += res[mult_index] * multiply_by + return res diff --git a/architecture/2019_01_linear_systems_APIs_and_authentication/examples/test_numerical_methods.py b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/test_numerical_methods.py new file mode 100644 index 0000000..f9bf920 --- /dev/null +++ b/architecture/2019_01_linear_systems_APIs_and_authentication/examples/test_numerical_methods.py @@ -0,0 +1,67 @@ +import numpy as np +import pytest +from hypothesis import given +from hypothesis.strategies import integers, tuples, floats +from hypothesis.extra.numpy import arrays +from numerical_methods import row_interchange, scalar_multiplication, add_scalar_mult + + +# For the sake of simplicity, not bothering with nans or infs. +reasonable_floats = floats(min_value=0.001, max_value=10 * 7, allow_nan=False, allow_infinity=False) +reasonable_f_array = arrays( + dtype=np.float64, + shape=tuples(integers(min_value=2, max_value=250), integers(min_value=2, max_value=250)), + elements=reasonable_floats +) + + +@given(reasonable_f_array) +def test_row_interchange(arr): + num_rows = arr.shape[0] + index_one = np.random.randint(0, num_rows) + index_two = np.random.randint(0, num_rows) + + res = row_interchange(arr, index_one, index_two) + + # Confirm the arrays have been changed + np.testing.assert_array_equal(arr[index_one], res[index_two]) + np.testing.assert_array_equal(res[index_one], arr[index_two]) + + # If we swap the rows back, arrays should be perfectly equal + res[[index_one, index_two]] = res[[index_two, index_one]] + np.testing.assert_array_equal(res, arr) + + +@given(reasonable_f_array, reasonable_floats) +def test_scalar_multiplication(arr, multiplier): + row_to_mult = np.random.randint(0, arr.shape[0]) + + res = scalar_multiplication(arr, row_to_mult, multiplier) + + # We would expect that row_to_mult would be arr[row_to_mult] * multiplier + np.testing.assert_array_equal(arr[row_to_mult] * multiplier, res[row_to_mult]) + + # And if we scale it back down, we'd expect the arrays to be the same. + res[row_to_mult] /= multiplier + # almost_equal needed due to potential imprecision on the way back + np.testing.assert_array_almost_equal(arr, res) + + +@given(reasonable_f_array, reasonable_floats) +def test_add_scalar_mult(arr, multiplier): + row_to_mult = np.random.randint(0, arr.shape[0]) + row_to_add = np.random.randint(0, arr.shape[0]) + + res = add_scalar_mult(arr, row_to_mult, multiplier, row_to_add) + + # We would expect that row_to_add would be row_to_add + (row_to_mult * multiplier) + np.testing.assert_array_equal(arr[row_to_add] + (arr[row_to_mult] * multiplier), res[row_to_add]) + + # And if we scale it back down, we'd expect the arrays to be the same. + res[row_to_add] -= arr[row_to_mult] * multiplier + # almost_equal needed due to potential imprecision on the way back + np.testing.assert_array_almost_equal(arr, res) + + +if __name__ == '__main__': + pytest.main([__file__, '-s'])