diff --git a/flask-connexion-rest/README.md b/flask-connexion-rest/README.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/flask-connexion-rest/README.md @@ -0,0 +1 @@ + diff --git a/version_1/server.py b/version_1/server.py new file mode 100644 index 0000000000..07c68e33a6 --- /dev/null +++ b/version_1/server.py @@ -0,0 +1,26 @@ + +# Import Flask, the Python micro web framework +from flask import ( + Flask, + render_template +) + + +# Create the application instance +app = Flask(__name__, template_folder="templates") + + +# Create a URL route in our application for "/" +@app.route('/') +def home(): + """ + This function just responds to the browser URL + localhost:5000/ + + :return: the rendered template "home.html" + """ + return render_template("home.html") + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/version_1/templates/home.html b/version_1/templates/home.html new file mode 100644 index 0000000000..3148aea1c9 --- /dev/null +++ b/version_1/templates/home.html @@ -0,0 +1,12 @@ + + + + + Application Home Page + + +

+ Hello World! +

+ + \ No newline at end of file diff --git a/version_2/__init__.py b/version_2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/version_2/people.py b/version_2/people.py new file mode 100644 index 0000000000..c39f6a2f8e --- /dev/null +++ b/version_2/people.py @@ -0,0 +1,42 @@ +""" +This is the people module and supports all the ReST actions for the PEOPLE collection + +""" + +# System modules +from datetime import datetime + + +def get_timestamp(): + return datetime.now().strftime(("%Y-%m-%d %H:%M:%S")) + + +# Data to serve with our API +PEOPLE = { + "Farrell": { + "fname": "Doug", + "lname": "Farrell", + "timestamp": get_timestamp() + }, + "Brockman": { + "fname": "Kent", + "lname": "Brockman", + "timestamp": get_timestamp() + }, + "Easter": { + "fname": "Bunny", + "lname": "Easter", + "timestamp": get_timestamp() + } +} + + +def read(): + """ + This function responds to a request for /api/people + with the complete lists of people + + :return: sorted list of people + """ + # create the list of people from our data + return [PEOPLE[key] for key in sorted(PEOPLE.keys())] diff --git a/version_2/server.py b/version_2/server.py new file mode 100644 index 0000000000..fa991cb7de --- /dev/null +++ b/version_2/server.py @@ -0,0 +1,30 @@ +""" +Main module of the server file +""" + +# 3rd party moudles +from flask import render_template +import connexion + + +# Create the application instance +app = connexion.App(__name__, specification_dir='./') + +# read the swagger.yml file to configure the endpoints +app.add_api('swagger.yml') + + +# Create a URL route in our application for "/" +@app.route('/') +def home(): + """ + This function just responds to the browser URL + localhost:5000/ + + :return: the rendered template "home.html" + """ + return render_template("home.html") + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/version_2/swagger.yml b/version_2/swagger.yml new file mode 100644 index 0000000000..2e5a27a519 --- /dev/null +++ b/version_2/swagger.yml @@ -0,0 +1,34 @@ +swagger: "2.0" +info: + description: This is the swagger file that goes with our server code + version: "1.0.0" + title: Swagger ReST Article +consumes: + - application/json +produces: + - application/json + +basePath: /api + +# Paths supported by the server application +paths: + /people: + get: + operationId: people.read + tags: + - People + summary: The people data structure supported by the server application + description: Read the list of people + responses: + 200: + description: Successful read people list operation + schema: + type: array + items: + properties: + fname: + type: string + lname: + type: string + timestamp: + type: string diff --git a/version_2/templates/home.html b/version_2/templates/home.html new file mode 100644 index 0000000000..3148aea1c9 --- /dev/null +++ b/version_2/templates/home.html @@ -0,0 +1,12 @@ + + + + + Application Home Page + + +

+ Hello World! +

+ + \ No newline at end of file diff --git a/version_3/__init__.py b/version_3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/version_3/people.py b/version_3/people.py new file mode 100644 index 0000000000..9fb890c060 --- /dev/null +++ b/version_3/people.py @@ -0,0 +1,130 @@ +""" +This is the people module and supports all the ReST actions for the PEOPLE collection +""" + +# System modules +from datetime import datetime + +# 3rd party modules +from flask import ( + request, + make_response, + abort +) + + +def get_timestamp(): + return datetime.now().strftime(("%Y-%m-%d %H:%M:%S")) + + +# Data to serve with our API +PEOPLE = { + "Farrell": { + "fname": "Doug", + "lname": "Farrell", + "timestamp": get_timestamp() + }, + "Brockman": { + "fname": "Kent", + "lname": "Brockman", + "timestamp": get_timestamp() + }, + "Easter": { + "fname": "Bunny", + "lname": "Easter", + "timestamp": get_timestamp() + } +} + + +def read_all(): + """ + This function responds to a request for /api/people + with the complete lists of people + + :return: json string of list of people + """ + # Create the list of people from our data + return [PEOPLE[key] for key in sorted(PEOPLE.keys())] + + + +def read_one(lname): + """ + This function responds to a request for /api/people/{lname} + with one matching person from people + + :param lname: last name of person to find + :return: person matching last name + """ + # Does the person exist in people? + if lname in PEOPLE: + person = PEOPLE.get(lname) + + # otherwise, nope, not found + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) + + return person + + +def create(person): + """ + This function creates a new person in the people structure + based on the passed in person data + + :param person: person to create in people structure + :return: 201 on success, 406 on person exists + """ + lname = person.get('lname', None) + fname = person.get('fname', None) + + # Does the person exist already? + if lname not in PEOPLE and lname is not None: + PEOPLE[lname] = { + 'lname': lname, + 'fname': fname, + "timestamp": get_timestamp() + } + return make_response('{lname} successfully created'.format(lname=lname), 201) + + # Otherwise, they exist, that's an error + else: + abort(406, 'Peron with last name {lname} already exists'.format(lname=lname)) + + +def update(lname, person): + """ + This function updates an existing person in the people structure + + :param lname: last name of person to update in the people structure + :param person: person to update + :return: updated person structure + """ + # Does the person exist in people? + if lname in PEOPLE: + PEOPLE[lname]['fname'] = person.get('fname') + PEOPLE[lname]['timestamp'] = get_timestamp() + + return PEOPLE[lname] + + # otherwise, nope, that's an error + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) + + +def delete(lname): + """ + This function deletes a person from the people structure + + :param lname: last name of person to delete + :return: 200 on successful delete, 404 if not found + """ + # Does the person to delete exist? + if lname in PEOPLE: + del PEOPLE[lname] + return make_response('{lname} successfully deleted'.format(lname=lname), 200) + + # Otherwise, nope, person to delete not found + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) diff --git a/version_3/server.py b/version_3/server.py new file mode 100644 index 0000000000..b94aaad802 --- /dev/null +++ b/version_3/server.py @@ -0,0 +1,30 @@ +""" +Main module of the server file +""" + +# 3rd party moudles +from flask import render_template +import connexion + + +# create the application instance +app = connexion.App(__name__, specification_dir='./') + +# Cead the swagger.yml file to configure the endpoints +app.add_api('swagger.yml') + + +# Create a URL route in our application for "/" +@app.route('/') +def home(): + """ + This function just responds to the browser URL + localhost:5000/ + + :return: the rendered template "home.html" + """ + return render_template("home.html") + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/version_3/swagger.yml b/version_3/swagger.yml new file mode 100644 index 0000000000..a5c22207ec --- /dev/null +++ b/version_3/swagger.yml @@ -0,0 +1,134 @@ +swagger: "2.0" +info: + description: This is the swagger file that goes with our server code + version: "1.0.0" + title: Swagger ReST Article +consumes: + - application/json +produces: + - application/json + +basePath: /api + +# Pßaths supported by the server application +paths: + /people: + get: + operationId: people.read_all + tags: + - People + summary: Read the entire list of people + description: Read the list of people + parameters: + - name: length + in: query + type: integer + description: Number of people to get from people + required: false + - name: offset + in: query + type: integer + description: Offset from beginning of list where to start gathering people + required: false + responses: + 200: + description: Successfully read people list operation + schema: + type: array + items: + properties: + fname: + type: string + lname: + type: string + timestamp: + type: string + + post: + operationId: people.create + tags: + - people + summary: Create a person and add it to the people list + description: Create a new person in the people list + parameters: + - name: person + in: body + description: Person to create + required: True + schema: + type: object + properties: + fname: + type: string + description: First name of person to create + lname: + type: string + description: Last name of person to create + responses: + 201: + description: Successfully created person in list + + /people/{lname}: + get: + operationId: people.read_one + tags: + - people + summary: Read one person from the people list + description: Read one person from the people list + parameters: + - name: lname + in: path + description: Last name of the person to get from the list + type: string + required: True + responses: + 200: + description: Successfully read person from people list operation + schema: + properties: + fname: + type: string + lname: + type: string + timestamp: + type: string + + put: + operationId: people.update + tags: + - people + summary: Update a person in the people list + description: Update a person in the people list + parameters: + - name: lname + in: path + description: Last name of the person to update in the list + type: string + required: True + - name: person + in: body + schema: + type: object + properties: + fname: + type: string + lname: + type: string + responses: + 200: + description: Successfully updated person in people list + + delete: + operationId: people.delete + tags: + - people + summary: Delete a person from the people list + description: Delete a person + parameters: + - name: lname + in: path + type: string + required: True + responses: + 200: + description: Successfully deleted a person from people list diff --git a/version_3/templates/home.html b/version_3/templates/home.html new file mode 100644 index 0000000000..3148aea1c9 --- /dev/null +++ b/version_3/templates/home.html @@ -0,0 +1,12 @@ + + + + + Application Home Page + + +

+ Hello World! +

+ + \ No newline at end of file diff --git a/version_4/__init__.py b/version_4/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/version_4/people.py b/version_4/people.py new file mode 100644 index 0000000000..f5ea510993 --- /dev/null +++ b/version_4/people.py @@ -0,0 +1,130 @@ +""" +This is the people module and supports all the ReST actions for the PEOPLE collection +""" + +# System modules +from datetime import datetime + +# 3rd party modules +from flask import ( + request, + make_response, + abort +) + + +def get_timestamp(): + return datetime.now().strftime(("%Y-%m-%d %H:%M:%S")) + + +# Data to serve with our API +PEOPLE = { + "Farrell": { + "fname": "Doug", + "lname": "Farrell", + "timestamp": get_timestamp() + }, + "Brockman": { + "fname": "Kent", + "lname": "Brockman", + "timestamp": get_timestamp() + }, + "Easter": { + "fname": "Bunny", + "lname": "Easter", + "timestamp": get_timestamp() + } +} + + +def read_all(): + """ + This function responds to a request for /api/people + with the complete lists of people + + :return: json string of list of people + """ + # Create the list of people from our data + return [PEOPLE[key] for key in sorted(PEOPLE.keys())] + + + +def read_one(lname): + """ + This function responds to a request for /api/people/{lname} + with one matching person from people + + :param lname: last name of person to find + :return: person matching last name + """ + # Does the person exist in people? + if lname in PEOPLE: + person = PEOPLE.get(lname) + + # otherwise, nope, not found + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) + + return person + + +def create(person): + """ + This function creates a new person in the people structure + based on the passed in person data + + :param person: person to create in people structure + :return: 201 on success, 406 on person exists + """ + lname = person.get('lname', None) + fname = person.get('fname', None) + + # Does the person exist already? + if lname not in PEOPLE and lname is not None: + PEOPLE[lname] = { + 'lname': lname, + 'fname': fname, + "timestamp": get_timestamp() + } + return PEOPLE[lname], 201 + + # Otherwise, they exist, that's an error + else: + abort(406, 'Peron with last name {lname} already exists'.format(lname=lname)) + + +def update(lname, person): + """ + This function updates an existing person in the people structure + + :param lname: last name of person to update in the people structure + :param person: person to update + :return: updated person structure + """ + # Does the person exist in people? + if lname in PEOPLE: + PEOPLE[lname]['fname'] = person.get('fname') + PEOPLE[lname]['timestamp'] = get_timestamp() + + return PEOPLE[lname] + + # otherwise, nope, that's an error + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) + + +def delete(lname): + """ + This function deletes a person from the people structure + + :param lname: last name of person to delete + :return: 200 on successful delete, 404 if not found + """ + # Does the person to delete exist? + if lname in PEOPLE: + del PEOPLE[lname] + return make_response('{lname} successfully deleted'.format(lname=lname), 200) + + # Otherwise, nope, person to delete not found + else: + abort(404, 'Person with last name {lname} not found'.format(lname=lname)) diff --git a/version_4/server.py b/version_4/server.py new file mode 100644 index 0000000000..debbc544fe --- /dev/null +++ b/version_4/server.py @@ -0,0 +1,30 @@ +""" +Main module of the server file +""" + +# 3rd party moudles +from flask import render_template +import connexion + + +# Create the application instance +app = connexion.App(__name__, specification_dir='./') + +# Read the swagger.yml file to configure the endpoints +app.add_api('swagger.yml') + + +# create a URL route in our application for "/" +@app.route('/') +def home(): + """ + This function just responds to the browser URL + localhost:5000/ + + :return: the rendered template "home.html" + """ + return render_template("home.html") + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/version_4/static/css/home.css b/version_4/static/css/home.css new file mode 100644 index 0000000000..2851274484 --- /dev/null +++ b/version_4/static/css/home.css @@ -0,0 +1,85 @@ +/* + * This is the CSS stylesheet for the demo people application + */ + +@import url(http://fonts.googleapis.com/css?family=Roboto:400,300,500,700); + +body, .ui-btn { + font-family: Roboto; +} + +.container { + padding: 10px; +} + +.banner { + text-align: center; +} + +.editor { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 1px solid lightgrey; + border-radius: 3px; + margin-bottom: 5px; +} + +label { + display: inline-block; + margin-bottom: 5px; +} + +button { + padding: 5px; + margin-right: 5px; + border-radius: 3px; + background-color: #eee; +} + +.people { + width: 50%; + margin-left: auto; + margin-right: auto; + margin-bottom: 5px; +} + +table { + width: 100%; + border-collapse: collapse; +} + +table, caption, th, td { + border: 1px solid lightgrey; +} + +table caption { + height: 33px; + font-weight: bold; + padding-top: 5px; + border-bottom: none; +} + +tr { + height: 33px; +} + +tr:nth-child(even) { + background-color: #f0f0f0 +} + +td { + text-align: center; +} + +.error { + width: 50%; + margin-left: auto; + margin-right: auto; + padding: 5px; + border: 1px solid lightgrey; + border-radius: 3px; + background-color: #fbb; + visibility: hidden; +} \ No newline at end of file diff --git a/version_4/static/js/home.js b/version_4/static/js/home.js new file mode 100644 index 0000000000..d6c4fd6d26 --- /dev/null +++ b/version_4/static/js/home.js @@ -0,0 +1,240 @@ +/* + * JavaScript file for the application to demonstrate + * using the API + */ + +// Create the namespace instance +let ns = {}; + +// Create the model instance +ns.model = (function() { + 'use strict'; + + let $event_pump = $('body'); + + // Return the API + return { + 'read': function() { + let ajax_options = { + type: 'GET', + url: 'api/people', + accepts: 'application/json', + dataType: 'json' + }; + $.ajax(ajax_options) + .done(function(data) { + $event_pump.trigger('model_read_success', [data]); + }) + .fail(function(xhr, textStatus, errorThrown) { + $event_pump.trigger('model_error', [xhr, textStatus, errorThrown]); + }) + }, + create: function(fname, lname) { + let ajax_options = { + type: 'POST', + url: 'api/people', + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify({ + 'fname': fname, + 'lname': lname + }) + }; + $.ajax(ajax_options) + .done(function(data) { + $event_pump.trigger('model_create_success', [data]); + }) + .fail(function(xhr, textStatus, errorThrown) { + $event_pump.trigger('model_error', [xhr, textStatus, errorThrown]); + }) + }, + update: function(fname, lname) { + let ajax_options = { + type: 'PUT', + url: 'api/people/' + lname, + accepts: 'application/json', + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify({ + 'fname': fname, + 'lname': lname + }) + }; + $.ajax(ajax_options) + .done(function(data) { + $event_pump.trigger('model_update_success', [data]); + }) + .fail(function(xhr, textStatus, errorThrown) { + $event_pump.trigger('model_error', [xhr, textStatus, errorThrown]); + }) + }, + 'delete': function(lname) { + let ajax_options = { + type: 'DELETE', + url: 'api/people/' + lname, + accepts: 'application/json', + contentType: 'plain/text' + }; + $.ajax(ajax_options) + .done(function(data) { + $event_pump.trigger('model_delete_success', [data]); + }) + .fail(function(xhr, textStatus, errorThrown) { + $event_pump.trigger('model_error', [xhr, textStatus, errorThrown]); + }) + } + }; +}()); + +// Create the view instance +ns.view = (function() { + 'use strict'; + + let $fname = $('#fname'), + $lname = $('#lname'); + + // return the API + return { + reset: function() { + $lname.val(''); + $fname.val('').focus(); + }, + update_editor: function(fname, lname) { + $lname.val(lname); + $fname.val(fname).focus(); + }, + build_table: function(people) { + let rows = '' + + // clear the table + $('.people table > tbody').empty(); + + // did we get a people array? + if (people) { + for (let i=0, l=people.length; i < l; i++) { + rows += `${people[i].fname}${people[i].lname}${people[i].timestamp}`; + } + $('table > tbody').append(rows); + } + }, + error: function(error_msg) { + $('.error') + .text(error_msg) + .css('visibility', 'visible'); + setTimeout(function() { + $('.error').css('visibility', 'hidden'); + }, 3000) + } + }; +}()); + +// Create the controller +ns.controller = (function(m, v) { + 'use strict'; + + let model = m, + view = v, + $event_pump = $('body'), + $fname = $('#fname'), + $lname = $('#lname'); + + // Get the data from the model after the controller is done initializing + setTimeout(function() { + model.read(); + }, 100) + + // Validate input + function validate(fname, lname) { + return fname !== "" && lname !== ""; + } + + // Create our event handlers + $('#create').click(function(e) { + let fname = $fname.val(), + lname = $lname.val(); + + e.preventDefault(); + + if (validate(fname, lname)) { + model.create(fname, lname) + } else { + alert('Problem with first or last name input'); + } + }); + + $('#update').click(function(e) { + let fname = $fname.val(), + lname = $lname.val(); + + e.preventDefault(); + + if (validate(fname, lname)) { + model.update(fname, lname) + } else { + alert('Problem with first or last name input'); + } + e.preventDefault(); + }); + + $('#delete').click(function(e) { + let lname = $lname.val(); + + e.preventDefault(); + + if (validate('placeholder', lname)) { + model.delete(lname) + } else { + alert('Problem with first or last name input'); + } + e.preventDefault(); + }); + + $('#reset').click(function() { + view.reset(); + }) + + $('table > tbody').on('dblclick', 'tr', function(e) { + let $target = $(e.target), + fname, + lname; + + fname = $target + .parent() + .find('td.fname') + .text(); + + lname = $target + .parent() + .find('td.lname') + .text(); + + view.update_editor(fname, lname); + }); + + // Handle the model events + $event_pump.on('model_read_success', function(e, data) { + view.build_table(data); + view.reset(); + }); + + $event_pump.on('model_create_success', function(e, data) { + model.read(); + }); + + $event_pump.on('model_update_success', function(e, data) { + model.read(); + }); + + $event_pump.on('model_delete_success', function(e, data) { + model.read(); + }); + + $event_pump.on('model_error', function(e, xhr, textStatus, errorThrown) { + let error_msg = textStatus + ': ' + errorThrown + ' - ' + xhr.responseJSON.detail; + view.error(error_msg); + console.log(error_msg); + }) +}(ns.model, ns.view)); + + diff --git a/version_4/swagger.yml b/version_4/swagger.yml new file mode 100644 index 0000000000..6ff70b3bf1 --- /dev/null +++ b/version_4/swagger.yml @@ -0,0 +1,134 @@ +swagger: "2.0" +info: + description: This is the swagger file that goes with our server code + version: "1.0.0" + title: Swagger Rest Article +consumes: + - application/json +produces: + - application/json + +basePath: /api + +# Pßaths supported by the server application +paths: + /people: + get: + operationId: people.read_all + tags: + - People + summary: Read the entire list of people + description: Read the list of people + parameters: + - name: length + in: query + type: integer + description: Number of people to get from people + required: false + - name: offset + in: query + type: integer + description: Offset from beginning of list where to start gathering people + required: false + responses: + 200: + description: Successfully read people list operation + schema: + type: array + items: + properties: + fname: + type: string + lname: + type: string + timestamp: + type: string + + post: + operationId: people.create + tags: + - People + summary: Create a person and add it to the people list + description: Create a new person in the people list + parameters: + - name: person + in: body + description: Person to create + required: True + schema: + type: object + properties: + fname: + type: string + description: First name of person to create + lname: + type: string + description: Last name of person to create + responses: + 201: + description: Successfully created person in list + + /people/{lname}: + get: + operationId: people.read_one + tags: + - People + summary: Read one person from the people list + description: Read one person from the people list + parameters: + - name: lname + in: path + description: Last name of the person to get from the list + type: string + required: True + responses: + 200: + description: Successfully read person from people list operation + schema: + properties: + fname: + type: string + lname: + type: string + timestamp: + type: string + + put: + operationId: people.update + tags: + - People + summary: Update a person in the people list + description: Update a person in the people list + parameters: + - name: lname + in: path + description: Last name of the person to update in the list + type: string + required: True + - name: person + in: body + schema: + type: object + properties: + fname: + type: string + lname: + type: string + responses: + 200: + description: Successfully updated person in people list + + delete: + operationId: people.delete + tags: + - People + summary: Delete a person from the people list + description: Delete a person + parameters: + - name: lname + in: path + type: string + required: True + responses: + 200: + description: Successfully deleted a person from people list diff --git a/version_4/templates/home.html b/version_4/templates/home.html new file mode 100644 index 0000000000..7eb6c8f341 --- /dev/null +++ b/version_4/templates/home.html @@ -0,0 +1,50 @@ + + + + + Application Home Page + + + + + +
+

People Demo Application

+
+ +
+ +
+ + + + +
+
+ + + + + + + + + + + +
People
First NameLast NameUpdate Time
+
+
+
+
+ + + \ No newline at end of file