1
0
Fork 0
Browse Source

port code over from test proj.

pull/1/head
Craig Oates 4 years ago
parent
commit
9f0c71ada2
  1. 41
      app/api.py
  2. 16
      app/app.py
  3. 36
      app/build_database.py
  4. 24
      app/config.py
  5. 50
      app/models/meters.py
  6. BIN
      app/readings.db
  7. 84
      app/services/get_services.py
  8. 42
      app/services/post_services.py
  9. 237
      app/swagger.yml
  10. 12
      app/templates/home.html
  11. 10
      proj-env/bin/chardetect
  12. 10
      proj-env/bin/connexion
  13. 10
      proj-env/bin/flask
  14. 10
      proj-env/bin/jsonschema
  15. 10
      proj-env/bin/openapi-spec-validator
  16. 10
      proj-env/bin/virtualenv
  17. 28
      requirements.txt

41
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()

16
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)

36
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()

24
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)

50
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

BIN
app/readings.db

Binary file not shown.

84
app/services/get_services.py

@ -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

42
app/services/post_services.py

@ -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)

237
app/swagger.yml

@ -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).

12
app/templates/home.html

@ -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>

10
proj-env/bin/chardetect vendored

@ -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())

10
proj-env/bin/connexion vendored

@ -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())

10
proj-env/bin/flask vendored

@ -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())

10
proj-env/bin/jsonschema vendored

@ -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())

10
proj-env/bin/openapi-spec-validator vendored

@ -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())

10
proj-env/bin/virtualenv vendored

@ -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())

28
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