17 changed files with 630 additions and 0 deletions
@ -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() |
@ -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) |
@ -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() |
@ -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) |
@ -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 |
Binary file not shown.
@ -0,0 +1,84 @@
|
||||
from flask import make_response, abort |
||||
from config import db |
||||
from models.meters import (Meter1, Meter1Schema, Meter2, Meter2Schema, |
||||
Meter3, Meter3Schema) |
||||
|
||||
''' |
||||
Get Services Note |
||||
====================================================================== |
||||
The functions in this file are for retrieving data stored at the (this) |
||||
server. If you want to store any readings taken with the light meters |
||||
at Ritherdon, you will need to head to the /post_services.py/ file. |
||||
It should be in the same directory at this: /services/. |
||||
''' |
||||
|
||||
def get_in_mem_reading(meter): |
||||
# Need to refactor in use an in-mem database. |
||||
# Currently grabbing from database. |
||||
if meter == 1: |
||||
return get_m1_latest() |
||||
elif meter == 2: |
||||
return get_m2_latest() |
||||
elif meter == 3: |
||||
return get_m3_latest() |
||||
|
||||
def get_all_readings_from_table(name): |
||||
if name == 1: |
||||
return get_all_readings_for_meter1() |
||||
elif name == 2: |
||||
return get_all_readings_for_meter2() |
||||
elif name == 3: |
||||
return get_all_readings_for_meter3() |
||||
|
||||
def get_all_readings_from_database(): |
||||
return get_all_readings() |
||||
|
||||
''' |
||||
The Nitty-Gritty Functions |
||||
====================================================================== |
||||
The functions below are the main functions within this file. The files |
||||
above act as "header functions" for the methods in /api.py/. I find it |
||||
easier to see what the method names are when this file and /api.py/ |
||||
are open side-by-side. At the very least it reduces the amount I need |
||||
to scroll up and down the file to find what I am after. |
||||
''' |
||||
|
||||
def get_m1_latest(): |
||||
reading = Meter1.query.order_by(Meter1.id.desc()).first() |
||||
meter_schema = Meter1Schema() |
||||
return meter_schema.dump(reading) |
||||
|
||||
def get_m2_latest(): |
||||
reading = Meter2.query.order_by(Meter2.id.desc()).first() |
||||
meter_schema = Meter2Schema() |
||||
return meter_schema.dump(reading) |
||||
|
||||
def get_m3_latest(): |
||||
reading = Meter3.query.order_by(Meter3.id.desc()).first() |
||||
meter_schema = Meter3Schema() |
||||
return meter_schema.dump(reading) |
||||
|
||||
def get_all_readings_for_meter1(): |
||||
readings = Meter1.query.order_by(Meter1.id.desc()).all() |
||||
schema = Meter1Schema(many=True) |
||||
data = schema.dump(readings) |
||||
return data |
||||
|
||||
def get_all_readings_for_meter2(): |
||||
readings = Meter2.query.order_by(Meter2.id.desc()).all() |
||||
schema = Meter2Schema(many=True) |
||||
data = schema.dump(readings) |
||||
return data |
||||
|
||||
def get_all_readings_for_meter3(): |
||||
readings = Meter3.query.order_by(Meter3.id.desc()).all() |
||||
schema = Meter3Schema(many=True) |
||||
data = schema.dump(readings) |
||||
return data |
||||
|
||||
def get_all_readings(): |
||||
m1 = get_all_readings_for_meter1() |
||||
m2 = get_all_readings_for_meter2() |
||||
m3 = get_all_readings_for_meter3() |
||||
readings = {"meter1": m1, "meter2": m2, "meter3": m3} |
||||
return readings |
@ -0,0 +1,42 @@
|
||||
from flask import make_response, abort |
||||
from config import db |
||||
from models.meters import (Meter1, Meter1Schema, Meter2, Meter2Schema, |
||||
Meter3, Meter3Schema) |
||||
|
||||
''' |
||||
Post Services Note |
||||
====================================================================== |
||||
The functions in this file are for storing the readings taken from the |
||||
light meters in Ritherdon. It you are wanting to retrieve data from |
||||
the (this) server, you will need to head to the /get_services.py/ file. |
||||
It should be in the same directory as this: /services/. |
||||
''' |
||||
|
||||
def add_latest_reading(meter,reading): |
||||
if meter == 1: |
||||
return add_reading_to_meter1(reading) |
||||
elif meter == 2: |
||||
return add_reading_to_meter2(reading) |
||||
elif meter == 3: |
||||
return add_reading_to_meter3(reading) |
||||
|
||||
def add_reading_to_meter1(the_reading): |
||||
schema = Meter1Schema() |
||||
new_reading = schema.load(the_reading, session=db.session) |
||||
db.session.add(new_reading) |
||||
db.session.commit() |
||||
return make_response("Reading successfully stored in database.", 201) |
||||
|
||||
def add_reading_to_meter2(the_reading): |
||||
schema = Meter2Schema() |
||||
new_reading = schema.load(the_reading, session=db.session) |
||||
db.session.add(new_reading) |
||||
db.session.commit() |
||||
return make_response("Reading successfully stored database.", 201) |
||||
|
||||
def add_reading_to_meter3(the_reading): |
||||
schema = Meter3Schema() |
||||
new_reading = schema.load(the_reading, session=db.session) |
||||
db.session.add(new_reading) |
||||
db.session.commit() |
||||
return make_response("Reading successfully stored database.", 201) |
@ -0,0 +1,237 @@
|
||||
swagger: "2.0" |
||||
info: |
||||
description: >- |
||||
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). |
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>Application Home Page</title> |
||||
</head> |
||||
<body> |
||||
<h2> |
||||
Hello World! |
||||
</h2> |
||||
</body> |
||||
</html> |
@ -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()) |
@ -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()) |
@ -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()) |
@ -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()) |
@ -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()) |
@ -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()) |
@ -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 |
Reference in new issue