Skip to content

Commit

Permalink
Merge pull request #54 from mmagr/overrides
Browse files Browse the repository at this point in the history
Implements device overrides
  • Loading branch information
mmagr committed Mar 29, 2018
2 parents 63ac5ba + 7423bf8 commit a53c31e
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 20 deletions.
21 changes: 19 additions & 2 deletions DeviceManager/DatabaseModels.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

class DeviceOverride(db.Model):
__tablename__ = 'overrides'

id = db.Column(db.Integer, db.Sequence('override_id'), primary_key=True)

did = db.Column(db.String(8), db.ForeignKey('devices.id'))
aid = db.Column(db.Integer, db.ForeignKey('attrs.id'))

device = db.relationship('Device', back_populates='overrides')
attr = db.relationship('DeviceAttr', back_populates='overrides')

static_value = db.Column(db.String(128))

class DeviceAttr(db.Model):
__tablename__ = 'attrs'

Expand All @@ -30,6 +43,9 @@ class DeviceAttr(db.Model):
parent = db.relationship("DeviceAttr", remote_side=[id], back_populates="children")
children = db.relationship("DeviceAttr", back_populates="parent", cascade="delete")

# remove known overrides if this attribute is removed
overrides = db.relationship('DeviceOverride', cascade="delete")

# Any given template must not possess two attributes with the same type, label
__table_args__ = (
sqlalchemy.UniqueConstraint('template_id', 'type', 'label'),
Expand Down Expand Up @@ -69,13 +85,14 @@ def __repr__(self):
class Device(db.Model):
__tablename__ = 'devices'

id = db.Column(db.String(4), unique=True, nullable=False, primary_key=True)
id = db.Column(db.String(8), unique=True, nullable=False, primary_key=True)
label = db.Column(db.String(128), nullable=False)
created = db.Column(db.DateTime, default=datetime.now)
updated = db.Column(db.DateTime, onupdate=datetime.now)

# template_id = db.Column(db.Integer, db.ForeignKey('templates.id'), nullable=False)
templates = db.relationship("DeviceTemplate", secondary='device_template', back_populates="devices")
overrides = db.relationship("DeviceOverride", back_populates="device", cascade="delete")

persistence = db.Column(db.String(128))

Expand All @@ -85,7 +102,7 @@ def __repr__(self):

class DeviceTemplateMap(db.Model):
__tablename__ = 'device_template'
device_id = db.Column(db.String(4), db.ForeignKey('devices.id'),
device_id = db.Column(db.String(8), db.ForeignKey('devices.id'),
primary_key=True, index=True)
template_id = db.Column(db.Integer, db.ForeignKey('templates.id'),
primary_key=True, index=True, nullable=False)
Expand Down
54 changes: 42 additions & 12 deletions DeviceManager/DeviceHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import logging
import re
from datetime import datetime
from flask import request
from flask import Blueprint
from sqlalchemy.exc import IntegrityError
Expand All @@ -16,25 +17,34 @@
from DeviceManager.DatabaseModels import db, assert_device_exists, assert_template_exists
from DeviceManager.DatabaseModels import handle_consistency_exception, assert_device_relation_exists
from DeviceManager.DatabaseModels import DeviceTemplate, DeviceAttr, Device, DeviceTemplateMap
from DeviceManager.DatabaseModels import DeviceOverride
from DeviceManager.SerializationModels import *
from DeviceManager.TenancyManager import init_tenant_context
from DeviceManager.app import app


device = Blueprint('device', __name__)

LOGGER = logging.getLogger('device-manager.' + __name__)
LOGGER.addHandler(logging.StreamHandler())
LOGGER.setLevel(logging.INFO)


def serialize_full_device(orm_device):
data = device_schema.dump(orm_device).data
data['attrs'] = {}
for template in orm_device.templates:
data['attrs'][template.id] = attr_list_schema.dump(template.attrs).data

for override in orm_device.overrides:
for attr in data['attrs'][override.attr.template_id]:
if attr['id'] == override.aid:
attr['static_value'] = override.static_value

return data

def find_template(template_list, id):
for template in template_list:
if template.id == int(id):
return template

def auto_create_template(json_payload, new_device):
if ('attrs' in json_payload) and (new_device.templates is None):
Expand All @@ -44,13 +54,31 @@ def auto_create_template(json_payload, new_device):
new_device.templates = [device_template]
load_attrs(json_payload['attrs'], device_template, DeviceAttr, db)

# TODO: perhaps it'd be best if all ids were random hex strings?
if ('attrs' in json_payload) and (new_device.templates is not None):
for attr in json_payload['attrs']:
orm_template = find_template(new_device.templates, attr['template_id'])
if orm_template is None:
raise HTTPRequestError(400, 'Unknown template "{}" in attr list'.format(template))

target = int(attr['id'])
found = False
for orm_attr in orm_template.attrs:
if target == orm_attr.id:
found = True
orm_override = DeviceOverride(
device=new_device,
attr=orm_attr,
static_value=attr['static_value']
)
db.session.add(orm_override)
if not found:
raise HTTPRequestError(400, "Unkown attribute \"{}\" in override list".format(target))

def parse_template_list(template_list, new_device):
new_device.templates = []
for template_id in template_list:
new_device.templates.append(
assert_template_exists(template_id, db.session))

new_device.templates.append(assert_template_exists(template_id, db.session))

def find_attribute(orm_device, attr_name, attr_type):
"""
Expand All @@ -63,7 +91,6 @@ def find_attribute(orm_device, attr_name, attr_type):
return attr
return None


class DeviceHandler(object):

def __init__(self):
Expand Down Expand Up @@ -337,21 +364,24 @@ def update_device(req, device_id):
device_data, json_payload = parse_payload(req, device_schema)

# update sanity check
if 'attrs' in json_payload:
LOGGER.warn('Got request with "attrs" field set. Ignoring.')
json_payload.pop('attrs')
# if 'attrs' in json_payload:
# LOGGER.warn('Got request with "attrs" field set. Ignoring.')
# json_payload.pop('attrs')

tenant = init_tenant_context(req, db)
old_orm_device = assert_device_exists(device_id)
db.session.delete(old_orm_device)
db.session.flush()

# handled separately by parse_template_list
device_data.pop('templates')
updated_orm_device = Device(**device_data)
parse_template_list(json_payload.get(
'templates', []), updated_orm_device)
parse_template_list(json_payload.get('templates', []), updated_orm_device)
auto_create_template(json_payload, updated_orm_device)
updated_orm_device.id = device_id
updated_orm_device.updated = datetime.now()
updated_orm_device.created = old_orm_device.created

db.session.delete(old_orm_device)
db.session.add(updated_orm_device)

full_device = serialize_full_device(updated_orm_device)
Expand Down
5 changes: 4 additions & 1 deletion DeviceManager/SerializationModels.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class AttrSchema(Schema):

@post_dump
def remove_null_values(self, data):
return {key: value for key, value in data.items() if value is not None}
return {
key: value for key, value in data.items() \
if (value is not None) and ((isinstance(value, list) and len(value)) or not isinstance(value, list))
}

attr_schema = AttrSchema()
attr_list_schema = AttrSchema(many=True)
Expand Down
1 change: 1 addition & 0 deletions DeviceManager/TemplateHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def create_template(req):
"""
init_tenant_context(req, db)
tpl, json_payload = parse_payload(req, template_schema)
LOGGER.info(tpl)
loaded_template = DeviceTemplate(**tpl)
load_attrs(json_payload['attrs'], loaded_template, DeviceAttr, db)
db.session.add(loaded_template)
Expand Down
6 changes: 1 addition & 5 deletions DeviceManager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,9 @@ def format_response(status, message=None):

return make_response(payload, status)


def create_id():
""" Generates a random hex id for managed entities """
# TODO this is far too small for any practical deployment, but helps keep
# the demo process simple
return '%04x' % random.randrange(16**4)

return '%05x' % random.randrange(16**6)

# from auth service
class HTTPRequestError(Exception):
Expand Down

0 comments on commit a53c31e

Please sign in to comment.