From 9f0c71ada2c39538f3c2c96bb9391cf44bef928e Mon Sep 17 00:00:00 2001 From: Craig Oates Date: Sun, 5 Jan 2020 23:51:37 +0000 Subject: [PATCH] port code over from test proj. --- app/api.py | 41 +++++ app/app.py | 16 ++ app/build_database.py | 36 +++++ app/config.py | 24 +++ app/models/meters.py | 50 ++++++ app/readings.db | Bin 0 -> 16384 bytes app/services/get_services.py | 84 ++++++++++ app/services/post_services.py | 42 +++++ app/swagger.yml | 237 ++++++++++++++++++++++++++++ app/templates/home.html | 12 ++ proj-env/bin/chardetect | 10 ++ proj-env/bin/connexion | 10 ++ proj-env/bin/flask | 10 ++ proj-env/bin/jsonschema | 10 ++ proj-env/bin/openapi-spec-validator | 10 ++ proj-env/bin/virtualenv | 10 ++ requirements.txt | 28 ++++ 17 files changed, 630 insertions(+) create mode 100644 app/api.py create mode 100644 app/app.py create mode 100644 app/build_database.py create mode 100644 app/config.py create mode 100644 app/models/meters.py create mode 100644 app/readings.db create mode 100644 app/services/get_services.py create mode 100644 app/services/post_services.py create mode 100644 app/swagger.yml create mode 100644 app/templates/home.html create mode 100755 proj-env/bin/chardetect create mode 100755 proj-env/bin/connexion create mode 100755 proj-env/bin/flask create mode 100755 proj-env/bin/jsonschema create mode 100755 proj-env/bin/openapi-spec-validator create mode 100755 proj-env/bin/virtualenv create mode 100644 requirements.txt diff --git a/app/api.py b/app/api.py new file mode 100644 index 0000000..ab261ab --- /dev/null +++ b/app/api.py @@ -0,0 +1,41 @@ +from services import post_services, get_services + +''' +API Functions +====================================================================== +These functions are what are exposed/referenced in the swagger.yml +file -- they are essentially wrapper functions. The main work is done +in the files in the /services/ folder. +These functions are acting as very light controllers essentially. +''' + +# The each value represents a light meter. +READINGS = [0, 0, 0] + +def post_a_reading(light_meter, the_reading): + if light_meter == 1: + READINGS[0] = the_reading.get("reading") + elif light_meter == 2: + READINGS[1] = the_reading.get("reading") + elif light_meter == 3: + READINGS[2] = the_reading.get("reading") + return post_services.add_latest_reading(light_meter,the_reading) + +def get_latest(light_meter): + # YOU ARE UP TO HERE. NEED TO REFACTOR BACK INTO DATABASE FUNCTION + # BELOW THIS FUNCTION. YAML FILE NEEDS CLEANING UP TOO. + if light_meter == 1: + return READINGS[0] + elif light_meter == 2: + return READINGS[1] + elif light_meter == 3: + return READINGS[2] + +def get_latest_reading(light_meter): + return get_services.get_in_mem_reading(light_meter) + +def get_all_readings(light_meter): + return get_services.get_all_readings_from_table(light_meter) + +def get_all_readings_for_every_meter(): + return get_services.get_all_readings_from_database() diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..2d3e6f6 --- /dev/null +++ b/app/app.py @@ -0,0 +1,16 @@ +from flask import Flask, render_template +import connexion +import config + +# The application instance. +app = config.connex_app + +# The yml file configures the app's endpoints. +app.add_api("swagger.yml") + +@app.route("/") +def home (): + return render_template("home.html") + +if __name__ == "__main__": + app.run(host="0.0.0.0", debug=True) diff --git a/app/build_database.py b/app/build_database.py new file mode 100644 index 0000000..059a07d --- /dev/null +++ b/app/build_database.py @@ -0,0 +1,36 @@ +import os +from datetime import datetime +from config import db +from models.meters import Meter1, Meter2, Meter3 + +def get_timestamp(): + return datetime.now().strftime(("%Y-%m-%d %H:%M:%S")) + +# The initialisation data for the database +READINGS1 =[ {"time":datetime.now(), "reading": 0} ] +READINGS2 =[ {"time":datetime.now(), "reading": 0} ] +READINGS3 =[ {"time":datetime.now(), "reading": 0} ] + +# Deletes the database if it already exists +if os.path.exists("readings.db"): + os.remove("readings.db") + +# Creates the database +db.create_all() + +# Iterates over the READINGS1 structure and populates the database +for info in READINGS1: + r = Meter1(time=info.get("time"), reading=info.get("reading")) + db.session.add(r) + +# Iterates over the READINGS2 structure and populates the datebase +for info in READINGS2: + r = Meter2(time=info.get("time"), reading=info.get("reading")) + db.session.add(r) + +# Iterates over the READINGS3 structure and populates the datebase +for info in READINGS3: + r = Meter3(time=info.get("time"), reading=info.get("reading")) + db.session.add(r) + +db.session.commit() diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..b55e515 --- /dev/null +++ b/app/config.py @@ -0,0 +1,24 @@ +import os +import connexion +from flask_sqlalchemy import SQLAlchemy +from flask_marshmallow import Marshmallow + +basedir = os.path.abspath(os.path.dirname(__file__)) + +# Creates the Connexion application instance +connex_app = connexion.App(__name__, specification_dir=basedir) + +# Gets the underlying Flask app instance +app = connex_app.app +database_uri = "sqlite:////" + os.path.join(basedir, "readings.db") + +# Configures the SQLAlchemy part of the app instance +app.config["SQLALCHEMY_ECHO"] = True # Set to false in prod. +app.config["SQLALCHEMY_DATABASE_URI"] = database_uri +app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False + +# Creates the SQLAlchemy db instance +db = SQLAlchemy(app) + +# Initialises Marshmallow +ma = Marshmallow(app) diff --git a/app/models/meters.py b/app/models/meters.py new file mode 100644 index 0000000..7d0382d --- /dev/null +++ b/app/models/meters.py @@ -0,0 +1,50 @@ +from datetime import datetime +from config import db, ma + +''' +Note on Duplication Levels +====================================================================== +While the code in this file seems very duplicated, it is just the +result of using SQL-Alchemy (ORM) and the repetitive nature of the +project as a whole. At the time of writing, the expected amount of +light meters is three and each one must take their own readings. +But, they must keep their readings separate from each other. This +means a table in the database for each meter. This is the main cause +for the repetitive/duplicated code. +Because this project has fixed requirements, the hard-coded nature is +a trade-off because of this. If the project increases the amount of +light meters it uses, this will probably need to be refactored. +''' + +class Meter1(db.Model): + __tablename__ = "meter1" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, default=datetime.utcnow) + reading = db.Column(db.Integer) + +class Meter1Schema(ma.ModelSchema): + class Meta: + model = Meter1 + sqla_session = db.session + +class Meter2(db.Model): + __tablename__ = "meter2" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime) + reading = db.Column(db.Integer) + +class Meter2Schema(ma.ModelSchema): + class Meta: + model = Meter2 + sqla_session = db.session + +class Meter3(db.Model): + __tablename__ = "meter3" + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime) + reading = db.Column(db.Integer) + +class Meter3Schema(ma.ModelSchema): + class Meta: + model = Meter3 + sqla_session = db.session diff --git a/app/readings.db b/app/readings.db new file mode 100644 index 0000000000000000000000000000000000000000..4cb822be5e6b16b6b29108111feb506e5badf0d1 GIT binary patch literal 16384 zcmeI#&r8B^7{~GN+@KW7Zs8@Kqe9AlDA`V}8X--qH4h#QR}8X1cJ(0rqx~sunjj4F z;zfEtY~SyA_>Rwb?esi91xXsq>*s7bPQ_6tO4HPph*HYY`f8HVOd*5I_I{1Q0*~0R#|0;C~9t^@Gz|OIL-r9M5KAUURZO&^B$eX- + This A.P.I. is part of the, at time of writing, unnamed project by + Nicola Ellis. The project's code-name is Roaming Light A.P.I. + version: "0.0.1 - Alpha" + title: Return to Ritherdon Project A.P.I +consumes: + - application/json +produces: + - application/json + +basePath: /api + +paths: + /readings/add/{light_meter}: + post: + operationId: api.post_a_reading + tags: + - Store Readings + summary: >- + Posts a reading from one of the project's light meters and store + it in the database. + description: >- + Use this U.R.L. to post a new reading from of the light meters + used in the project. At the time of writing, there are three + light meters taking readings in the Ritherdon factory. + It is important to note which light meter is posting the + reading. Each light meter has its own table in the database + and it will corrupt the data integrity if you post a reading + from a meter into the wrong table. At the time of writing, + there are three meters taking light reading and posting them + to here. + parameters: + - name: light_meter + in: path + description: >- + The Id. of the light meter which has taken the reading. At + the time of writing, it should be a number between 1-3. + type: integer + required: True + - name: the_reading + in: body + description: >- + The data object which represents a single light reading + from light meter 1. It conveys the time the reading was + taken and the light value it recorded. Remember, each light + has their own table in the database so make sure you do not + post the light meter reading to the wrong URL. It will + corrupt the data integrity. + required: True + schema: + type: object + properties: + time: + type: string + format: date-time + example: 2019-10-19 17:04:57.880010 + description: >- + The date and time the reading was taken. Make sure + you use the UTC time format. This is because the + A.P.I. has made a design decision to standardise on + it. + reading: + type: integer + example: 23 + description: >- + This represents the amount of light the light meter + recorded. This is the most important piece of data + you will post in this data-object. + responses: + 201: + description: >- + The reading was successfully added to the database. + + /readings/latest/{light_meter}: + get: + operationId: api.get_latest_reading + tags: + - Request Readings + summary: >- + Returns the latest reading from the specified light meter. + description: >- + Use this U.R.L. to retrieve the latest reading from the + light meter you specified (in the U.R.L.). At the time of + writing, the project has only three light meters and are + labelled 1-3. + parameters: + - name: light_meter + in: path + description: >- + This is the Id. of the light meter which you are retrieving + the reading for. The Id. consists of a number between 1-3 + at the time of writing. + type: integer + required: True + responses: + 200: + description: >- + If the server can successfully retrieve the latest reading + for the specified light meter, you should receive a JASON + object like the one below. It should include the Id. of + the light meter it was taken with, the time the reading + was taken and the reading itself. + schema: + type: object + properties: + id: + type: integer + example: 2 + description: >- + This is the Id. of the light meter which took the + reading. It should be between 1-3 at the time of + writing. + time: + type: string + example: 2019-10-19 17:04:57 + description: >- + The time and date the reading was taken. The A.P.I. + has standardised on the U.T.C. format. + reading: + type: integer + example: 34 + description: >- + This is the actual reading taken from the specified + light meter, at the time specified in this response + body (I.E. JSON object). + + /readings/all/{light_meter}: + get: + operationId: api.get_all_readings + tags: + - Request Readings + summary: >- + Returns every reading taken by the specified light meter. + description: >- + Use this U.R.L. to retrieve the all the reading from the + light meter you specified (in the U.R.L.). At the time of + writing, the project has only three light meters and are + labelled 1-3. + parameters: + - name: light_meter + in: path + description: >- + This is the Id. of the light meter which you are retrieving + the readings for. The Id. consists of a number between 1-3 + at the time of writing. + type: integer + required: True + responses: + 200: + description: >- + If the server can successfully retrieve all the readings + for the specified light meter, you should receive an array + of JSON objects like the one below. It should include the + database Id. of the reading, the time the reading was + taken and the reading itself. + schema: + type: object + properties: + id: + type: integer + example: 2 + description: >- + This is the database Id. of the reading. + time: + type: string + example: 2019-10-19 17:04:57 + description: >- + This is the time and date the reading was taken. + The A.P.I. has standardised on the U.T.C. format. + reading: + type: integer + example: 34 + description: >- + This is the actual reading taken from the specified + light meter, at the time specified in this response + body (I.E. JSON object). + + /readings/all: + get: + operationId: api.get_all_readings_for_every_meter + tags: + - Request Readings + summary: >- + Returns every reading taken by every light meter in the + project. + description: >- + Use this U.R.L. to retrieve all the readings from every light + meter in the project. There is no example of the data returned + because I can't seem to replicate it as a YAML schema which + is understood by Swagger. Because of this, it is registered as + a free-form object. To see what the data looks like when it is + returned, I recommend you use the "Try it out!" button to see a + working example. + responses: + 200: + description: >- + All the readings were successfully retrieved from the + database and delivered. + schema: + type: object + additionalProperties: True + +# This is the in-mem. database URL. + /readings/late/{light_meter}: + get: + operationId: api.get_latest + tags: + - Request Readings + summary: >- + Returns latest from in-mem database. + description: >- + latest in-mem db reading + parameters: + - name: light_meter + in: path + description: >- + This is the Id. of the light meter which you are retrieving + the readings for. The Id. consists of a number between 1-3 + at the time of writing. + type: integer + required: True + responses: + 200: + description: >- + In-mem db latest value + schema: + type: object + properties: + reading: + type: integer + example: 34 + description: >- + This is the actual reading taken from the specified + light meter, at the time specified in this response + body (I.E. JSON object). diff --git a/app/templates/home.html b/app/templates/home.html new file mode 100644 index 0000000..2db6bf0 --- /dev/null +++ b/app/templates/home.html @@ -0,0 +1,12 @@ + + + + + Application Home Page + + +

+ Hello World! +

+ + diff --git a/proj-env/bin/chardetect b/proj-env/bin/chardetect new file mode 100755 index 0000000..3477a13 --- /dev/null +++ b/proj-env/bin/chardetect @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from chardet.cli.chardetect import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/proj-env/bin/connexion b/proj-env/bin/connexion new file mode 100755 index 0000000..1525334 --- /dev/null +++ b/proj-env/bin/connexion @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from connexion.cli import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/proj-env/bin/flask b/proj-env/bin/flask new file mode 100755 index 0000000..b397699 --- /dev/null +++ b/proj-env/bin/flask @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from flask.cli import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/proj-env/bin/jsonschema b/proj-env/bin/jsonschema new file mode 100755 index 0000000..de8e820 --- /dev/null +++ b/proj-env/bin/jsonschema @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from jsonschema.cli import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/proj-env/bin/openapi-spec-validator b/proj-env/bin/openapi-spec-validator new file mode 100755 index 0000000..ed12244 --- /dev/null +++ b/proj-env/bin/openapi-spec-validator @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from openapi_spec_validator.__main__ import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/proj-env/bin/virtualenv b/proj-env/bin/virtualenv new file mode 100755 index 0000000..a337242 --- /dev/null +++ b/proj-env/bin/virtualenv @@ -0,0 +1,10 @@ +#!/mnt/dev-shed/midpoint/proj-env/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys + +from virtualenv import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d1f49f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,28 @@ +certifi==2019.9.11 +chardet==3.0.4 +Click==7.0 +clickclick==1.2.2 +connexion==2.3.0 +Flask==1.1.1 +flask-marshmallow==0.10.1 +Flask-SQLAlchemy==2.4.0 +Flask-WTF==0.14.2 +idna==2.8 +inflection==0.3.1 +itsdangerous==1.1.0 +Jinja2==2.10.1 +jsonschema==2.6.0 +MarkupSafe==1.1.1 +marshmallow==3.2.1 +marshmallow-sqlalchemy==0.19.0 +openapi-spec-validator==0.2.8 +pkg-resources==0.0.0 +PyYAML==5.1.2 +requests==2.22.0 +six==1.12.0 +SQLAlchemy==1.3.8 +swagger-ui-bundle==0.0.5 +urllib3==1.25.5 +virtualenv==16.7.9 +Werkzeug==0.16.0 +WTForms==2.2.1