Merged in improvement/packages-setup (pull request #1)

Improvement/packages setup

Approved-by: Velid Aljic <velid.aljic@gmail.com>
master
Ensar Sarajcic 2018-10-06 12:23:40 +00:00
commit 27fbe9d886
23 changed files with 492 additions and 467 deletions

View File

@ -1,117 +0,0 @@
from app.core import bcrypt
from flask import Blueprint
from .models import Account, Role
accounts_bp = Blueprint('accounts', __name__)
def create_account(username, email, password):
"""
Tries to create account with given parameters. Raises error on failure
:param username: Desired username for Account
:param email: Desired email for Account
:param password: Desired password for Account
:type username: string
:type email: string
:type password: string
:returns: True if account is successfully created
:rtype: Boolean
:raises: ValueError if account already exists
"""
if not Account.exists_with_any_of(username=username, email=email):
pw_hash = bcrypt.generate_password_hash(password).decode('utf-8')
account = Account(username, pw_hash, email)
account.save()
return True
raise ValueError("Account with given parameters already exists")
def update_account_role(account_id, role_id):
"""
Tries to update account role
:param account_id: Target account id
:param role_id: New role role_id
:type account_id: int
:type role_id: int
:returns: True if role is updated successfully
:rtype: Boolean
"""
acc = Account.get(id=account_id)
acc.role_id = role_id
acc.save()
def create_role(display_name, permissions):
"""
Tries to create role
:param display_name: Name of role - display only
:param permissions: List of strings - permissions that this role has
:type display_name: String
:type permissions: List of String
:returns: True if role is successfully created
:rtype: Boolean
:raises: ValueError if role already exists
"""
role = Role(display_name, permissions)
role.save()
def get_role(role_id):
"""
Tries to get role
:param role_id: Id of role
:type role_id: int
:returns: Role if found
:rtype: Role
"""
return Role.get(role_id)
def get_all_roles():
"""
Gets all roles
:returns: Role list if found
:rtype: List of Roles
"""
return Role.get_all()
def create_token(username, password):
"""
Tries to create token for account with given parameters.
Raises error on failure
:param username: username of Account
:param password: password of Account
:type username: string
:type password: string
:returns: created token
:rtype: string
:raises: ValueError if credentials are invalid or account does not exist
"""
if not Account.exists(username=username):
raise ValueError("Invalid credentials")
account = Account.get(username=username)
if not bcrypt.check_password_hash(account.password, password):
raise ValueError("Invalid credentials")
return account.create_auth_token()
def validate_token(token):
"""
Validates token and returns associated account
:param token: auth token to validate
:type token: string
:returns: created token
:rtype: Account
"""
return Account.validate_token(token)

114
app/accounts/api.py 100644
View File

@ -0,0 +1,114 @@
from app.core import bcrypt
from .models import Account, Role
def create_account(username, email, password):
"""
Tries to create account with given parameters. Raises error on failure
:param username: Desired username for Account
:param email: Desired email for Account
:param password: Desired password for Account
:type username: string
:type email: string
:type password: string
:returns: True if account is successfully created
:rtype: Boolean
:raises: ValueError if account already exists
"""
if not Account.exists_with_any_of(username=username, email=email):
pw_hash = bcrypt.generate_password_hash(password).decode('utf-8')
account = Account(username, pw_hash, email)
account.save()
return True
raise ValueError("Account with given parameters already exists")
def update_account_role(account_id, role_id):
"""
Tries to update account role
:param account_id: Target account id
:param role_id: New role role_id
:type account_id: int
:type role_id: int
:returns: True if role is updated successfully
:rtype: Boolean
"""
acc = Account.get(id=account_id)
acc.role_id = role_id
acc.save()
def create_role(display_name, permissions):
"""
Tries to create role
:param display_name: Name of role - display only
:param permissions: List of strings - permissions that this role has
:type display_name: String
:type permissions: List of String
:returns: True if role is successfully created
:rtype: Boolean
:raises: ValueError if role already exists
"""
role = Role(display_name, permissions)
role.save()
def get_role(role_id):
"""
Tries to get role
:param role_id: Id of role
:type role_id: int
:returns: Role if found
:rtype: Role
"""
return Role.get(role_id)
def get_all_roles():
"""
Gets all roles
:returns: Role list if found
:rtype: List of Roles
"""
return Role.get_all()
def create_token(username, password):
"""
Tries to create token for account with given parameters.
Raises error on failure
:param username: username of Account
:param password: password of Account
:type username: string
:type password: string
:returns: created token
:rtype: string
:raises: ValueError if credentials are invalid or account does not exist
"""
if not Account.exists(username=username):
raise ValueError("Invalid credentials")
account = Account.get(username=username)
if not bcrypt.check_password_hash(account.password, password):
raise ValueError("Invalid credentials")
return account.create_auth_token()
def validate_token(token):
"""
Validates token and returns associated account
:param token: auth token to validate
:type token: string
:returns: created token
:rtype: Account
"""
return Account.validate_token(token)

View File

@ -0,0 +1,3 @@
from flask import Blueprint
accounts_bp = Blueprint('accounts', __name__)

View File

@ -1,106 +0,0 @@
import sys
from flask import Blueprint, request, g
from flask_restful import Api, Resource, abort
from functools import wraps
from marshmallow import ValidationError
from app.accounts import validate_token
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
def protected(func):
@wraps(func)
def protected_function(*args, **kwargs):
try:
token = request.headers['Authorization'] or None
if not token:
abort(401, message='Unauthorized', status='error')
g.current_account = validate_token(token.replace("Bearer ", ""))
if not g.current_account:
abort(401, message='Unauthorized', status='error')
except Exception:
error_type, error_instance, traceback = sys.exc_info()
print(str(error_type))
print(str(error_instance))
abort(401, message='Unauthorized', status='error')
return func(*args, **kwargs)
return protected_function
def requires_permission(permission, action_name='Action'):
def requires_permission_decorator(func):
@wraps(func)
def permission_protected_function(*args, **kwargs):
if permission not in g.current_account.role.permissions:
abort(403,
message=(action_name+' is not allowed'),
status='error')
return func(*args, **kwargs)
return permission_protected_function
return requires_permission_decorator
class ProtectedResource(Resource):
method_decorators = [protected]
def add_resources():
from .resources.account import (AccountResource,
AccountListResource,
AccountRoleResource,
RoleResource,
RolesResource)
from .resources.token import TokenResource, ValidateTokenResource
from .resources.device import (DeviceResource,
DeviceRecordingResource,
DeviceListResource,
DeviceTypeResource,
DeviceTypeListResource,
DeviceConfigurationResource)
from .resources.dashboard import DashboardResource, DashboardListResource
api.add_resource(AccountResource, '/v1/accounts/<int:account_id>')
api.add_resource(AccountListResource, '/v1/accounts')
api.add_resource(AccountRoleResource, '/v1/accounts/<int:account_id>/role')
api.add_resource(RoleResource, '/v1/roles/<int:role_id>')
api.add_resource(RolesResource, '/v1/roles')
api.add_resource(TokenResource, '/v1/token')
api.add_resource(ValidateTokenResource, '/v1/token/validate')
api.add_resource(DeviceResource, '/v1/devices/<int:device_id>')
api.add_resource(DeviceRecordingResource,
'/v1/devices/<int:device_id>/recordings')
api.add_resource(DeviceListResource, '/v1/devices')
api.add_resource(DeviceTypeResource,
'/v1/devices/types/<int:device_type_id>')
api.add_resource(DeviceTypeListResource, '/v1/devices/types')
api.add_resource(DeviceConfigurationResource,
'/v1/devices/<int:device_id>/configuration')
api.add_resource(DashboardListResource, '/v1/dashboards')
api.add_resource(DashboardResource,
'/v1/dashboards/<int:dashboard_id>')
add_resources()
@api_bp.errorhandler(ValidationError)
@api_bp.errorhandler(422)
def handle_validation_error(e):
return {'status': 'error', 'message': str(e)}, 422
@api_bp.errorhandler(Exception)
def handle_unknown_errors(e):
return ({
'status': 'failed',
'message': 'Unknown error has occurred! ({0})'.format(str(e))
}, 500)

View File

@ -0,0 +1,32 @@
import sys
from functools import wraps
from flask import request, g
from flask_restful import Resource, abort
from app.accounts.api import validate_token
def protected(func):
@wraps(func)
def protected_function(*args, **kwargs):
try:
token = request.headers['Authorization'] or None
if not token:
abort(401, message='Unauthorized', status='error')
g.current_account = validate_token(token.replace("Bearer ", ""))
if not g.current_account:
abort(401, message='Unauthorized', status='error')
except Exception:
error_type, error_instance, traceback = sys.exc_info()
print(str(error_type))
print(str(error_instance))
abort(401, message='Unauthorized', status='error')
return func(*args, **kwargs)
return protected_function
class ProtectedResource(Resource):
method_decorators = [protected]

View File

@ -0,0 +1,60 @@
from flask_restful import Api
from marshmallow import ValidationError
from flask import Blueprint
api_bp = Blueprint('api', __name__)
api = Api(api_bp)
def add_resources():
from .resources.account import (AccountResource,
AccountListResource,
AccountRoleResource,
RoleResource,
RolesResource)
from .resources.token import TokenResource, ValidateTokenResource
from .resources.device import (DeviceResource,
DeviceRecordingResource,
DeviceListResource,
DeviceTypeResource,
DeviceTypeListResource,
DeviceConfigurationResource)
from .resources.dashboard import DashboardResource, DashboardListResource
api.add_resource(AccountResource, '/v1/accounts/<int:account_id>')
api.add_resource(AccountListResource, '/v1/accounts')
api.add_resource(AccountRoleResource, '/v1/accounts/<int:account_id>/role')
api.add_resource(RoleResource, '/v1/roles/<int:role_id>')
api.add_resource(RolesResource, '/v1/roles')
api.add_resource(TokenResource, '/v1/token')
api.add_resource(ValidateTokenResource, '/v1/token/validate')
api.add_resource(DeviceResource, '/v1/devices/<int:device_id>')
api.add_resource(DeviceRecordingResource,
'/v1/devices/<int:device_id>/recordings')
api.add_resource(DeviceListResource, '/v1/devices')
api.add_resource(DeviceTypeResource,
'/v1/devices/types/<int:device_type_id>')
api.add_resource(DeviceTypeListResource, '/v1/devices/types')
api.add_resource(DeviceConfigurationResource,
'/v1/devices/<int:device_id>/configuration')
api.add_resource(DashboardListResource, '/v1/dashboards')
api.add_resource(DashboardResource,
'/v1/dashboards/<int:dashboard_id>')
add_resources()
@api_bp.errorhandler(ValidationError)
@api_bp.errorhandler(422)
def handle_validation_error(e):
return {'status': 'error', 'message': str(e)}, 422
@api_bp.errorhandler(Exception)
def handle_unknown_errors(e):
return ({
'status': 'failed',
'message': 'Unknown error has occurred! ({0})'.format(str(e))
}, 500)

View File

@ -0,0 +1,32 @@
from flask import g
from flask_restful import abort
from functools import wraps
valid_permissions = [
'CREATE_DEVICE_TYPE',
'CREATE_ROLE',
'ASSIGN_ROLE',
'CREATE_DEVICE',
'CREATE_DASHBOARD',
'READ_DEVICE_TYPES',
'READ_ROLES']
def requires_permission(permission, action_name='Action'):
if permission not in valid_permissions:
raise ValueError('Permission ' + str(permission) + ' does not exist!')
def requires_permission_decorator(func):
@wraps(func)
def permission_protected_function(*args, **kwargs):
if permission not in g.current_account.role.permissions:
abort(403,
message=(action_name+' is not allowed'),
status='error')
return func(*args, **kwargs)
return permission_protected_function
return requires_permission_decorator

View File

@ -3,8 +3,10 @@ from flask import g
from marshmallow import Schema, fields
from webargs.flaskparser import use_args
from flasgger import swag_from
import app.accounts as accounts
from app.api import ProtectedResource, requires_permission
import app.accounts.api as accounts
from app.api.auth_protection import ProtectedResource
from app.api.permission_protection import (requires_permission,
valid_permissions)
class UserSchema(Schema):
@ -17,11 +19,16 @@ class RoleUpdateSchema(Schema):
role_id = fields.Integer(required=True, load_only=True, location='json')
def validate_role_permissions(permissions_list):
return set(permissions_list).issubset(valid_permissions)
class RoleSchema(Schema):
id = fields.Integer(required=True, location='json')
display_name = fields.String(required=True, location='json')
permissions = fields.List(fields.String, required=True,
location='json', many=True)
location='json', many=True,
validate=validate_role_permissions)
class RoleWrapperSchema(Schema):

View File

@ -3,8 +3,8 @@ from flask_restful import abort
from marshmallow import Schema, fields
from webargs.flaskparser import use_args
from flasgger import swag_from
import app.dashboard as dashboard
from app.api import ProtectedResource
import app.dashboards.api as dashboard
from app.api.auth_protection import ProtectedResource
class DashboardSchema(Schema):

View File

@ -3,8 +3,8 @@ from marshmallow import Schema, fields
from webargs.flaskparser import use_args
from flasgger import swag_from
from flask import g, request
import app.devices as devices
from app.api import ProtectedResource
import app.devices.api as devices
from app.api.auth_protection import ProtectedResource
class DeviceTypeSchema(Schema):

View File

@ -2,8 +2,8 @@ from flask_restful import Resource, abort
from webargs import fields
from webargs.flaskparser import use_args
from flasgger import swag_from
from app.api import ProtectedResource
import app.accounts as accounts
from app.api.auth_protection import ProtectedResource
import app.accounts.api as accounts
class TokenResource(Resource):

View File

@ -4,7 +4,7 @@ from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flasgger import Swagger
from flask_cors import CORS
from .tasks import celery as celery_configurator
from .tasks import celery_configurator
app = FlaskAPI(__name__, instance_relative_config=True)
app.config.from_object('config')
@ -27,11 +27,11 @@ def setup_blueprints(app):
They are exposed as blueprints just for consistency, otherwise
they are just simple python packages/modules
"""
from .accounts import accounts_bp
from .devices import devices_bp
from .dashboard import dashboard_bp
from .api import api_bp
from .mqtt import mqtt_bp
from .accounts.blueprint import accounts_bp
from .devices.blueprint import devices_bp
from .dashboards.blueprint import dashboard_bp
from .api.blueprint import api_bp
from .mqtt.blueprint import mqtt_bp
app.register_blueprint(accounts_bp)
app.register_blueprint(devices_bp)

View File

View File

@ -1,8 +1,5 @@
from flask import Blueprint
from .models import Dashboard
dashboard_bp = Blueprint('dashboard', __name__)
# Public interface
def create_dashboard(dashboard_data, account_id):

View File

@ -0,0 +1,3 @@
from flask import Blueprint
dashboard_bp = Blueprint('dashboard', __name__)

View File

@ -1,206 +0,0 @@
import sys
from flask import Blueprint
from .models import Device, Recording, DeviceAssociation, DeviceType
from app.core import app
devices_bp = Blueprint('devices', __name__)
# Public interface
def create_device(name, account_id, device_type=1):
"""
Tries to create device with given parameters
:param name: Desired device name
:param device_type: Id of desired device type.
By default it is 1 (STANDARD)
:type name: string
:type device_type: int
:returns: True if device is successfully created
:rtype: Boolean
"""
device = Device(name, None, device_type)
device.save()
device_association = DeviceAssociation(device.id, account_id)
device_association.save()
def create_device_type(name):
"""
Tries to create device type with given parameters
:param name: Desired device type name
:type name: string
:returns: True if device type is successfully created
:rtype: Boolean
"""
device_type = DeviceType(name)
device_type.save()
def set_device_configuration(device_id, configuration_json):
"""
Tries to update configuration of device with given id
:param device_id: Id of device to change configuration
:param configuration_json: New configuration
:type device_id: int
:type configuration_json: JSON
:rtype: Boolean
"""
from app.celery_builder import send_config
device = Device.get(id=device_id)
device.configuration = configuration_json
device.save()
send_config.delay(device_id, str(configuration_json))
def get_device_configuration(device_id):
"""
Tries to get configuration for device with given parameters.
:param device_id: Id of device
:type device_id: int
:returns: Configuration of given device
:rtype: JSON Configuration
"""
return Device.get(id=device_id).configuration
def get_device_recordings(device_id):
"""
Tries to get device recording for device with given parameters. Raises
error on failure
:param device_id: Id of device
:type device_id: int
:returns: List of Recordings for given device
:rtype: List of Recording
:raises: ValueError if device does not exist
"""
if not Device.exists(id=device_id):
raise ValueError("Device with id %s does not exist" % device_id)
return Recording.get_many(device_id=device_id)
def get_device_recordings_filtered(device_id, record_type=None,
start_date=None, end_date=None):
"""
Tries to get device recording for device with given parameters. Raises
error on failure
:param device_id: Id of device
:param record_type: Type of recording
:param start_date: Lower date limit
:param end_date: Upper date limit
:type device_id: int
:type record_type: int
:type start_date: Date (string: %d-%m-%Y)
:type end_date: Date (string: %d-%m-%Y)
:returns: List of Recordings for given filters
:rtype: List of Recording
:raises: ValueError if device does not exist
"""
if not Device.exists(id=device_id):
raise ValueError("Device with id %s does not exist" % device_id)
return Recording.get_many_filtered(device_id, record_type,
start_date, end_date)
def get_device(device_id):
"""
Tries to get device with given parameters. Raises error on failure
:param device_id: Id of device
:type device_id: int
:returns: Requested device
:rtype: Device
"""
return Device.get(id=device_id)
def can_user_access_device(account_id, device_id):
"""
Checks if user with given account_id can access device with given device_id
:param account_id: Id of account
:param device_id: Id of device
:type account_id: int
:type device_id: int
:returns: true if device is accessible by this account, false otherwise
:rtype: Boolean
"""
return len(DeviceAssociation.get_many(account_id=account_id,
device_id=device_id)) > 0
def get_device_type(device_type_id):
"""
Tries to get device type with given parameters. Raises error on failure
:param device_type_id: Id of device type
:type device_type_id: int
:returns: Requested device type
:rtype: DeviceType
"""
return DeviceType.get(id=device_type_id)
def delete_device(device_id):
"""
Tries to delete device with given parameters. Does not raise errors
"""
Device.get(id=device_id).delete()
def get_devices(account_id):
"""
Tries to get all devices associated to account. Raises error on
failure
:returns: List of Devices associated to this account
:rtype: List of Devices
"""
return Device.get_many_for_user(account_id)
def get_device_types():
"""
Tries to get all device types. Raises error on failure
:returns: List of device types
:rtype: List of DeviceTypes
"""
return DeviceType.get_many()
def create_recording(device_id, raw_json):
"""
Tries to create recording with given parameters. Raises error on failure
:param device_id: Id of device
:type device_id: int
:param raw_json: Raw json received
:type raw_json: json
:raises: ValueError if parsing fails or device does not exist
"""
def parse_raw_json_recording(device_id, json_msg):
try:
return Recording(device_id=device_id,
record_type=json_msg["record_type"],
record_value=json_msg["record_value"],
recorded_at=json_msg["recorded_at"],
raw_json=json_msg)
except KeyError:
error_type, error_instance, traceback = sys.exc_info()
raise ValueError("JSON parsing failed! Key error: "
+ str(error_instance))
if not Device.exists(id=device_id):
raise ValueError("Device does not exist!")
recording = parse_raw_json_recording(device_id, raw_json)
with app.app_context():
recording.save()

203
app/devices/api.py 100644
View File

@ -0,0 +1,203 @@
import sys
from .models import Device, Recording, DeviceAssociation, DeviceType
from app.core import app
# Public interface
def create_device(name, account_id, device_type=1):
"""
Tries to create device with given parameters
:param name: Desired device name
:param device_type: Id of desired device type.
By default it is 1 (STANDARD)
:type name: string
:type device_type: int
:returns: True if device is successfully created
:rtype: Boolean
"""
device = Device(name, None, device_type)
device.save()
device_association = DeviceAssociation(device.id, account_id)
device_association.save()
def create_device_type(name):
"""
Tries to create device type with given parameters
:param name: Desired device type name
:type name: string
:returns: True if device type is successfully created
:rtype: Boolean
"""
device_type = DeviceType(name)
device_type.save()
def set_device_configuration(device_id, configuration_json):
"""
Tries to update configuration of device with given id
:param device_id: Id of device to change configuration
:param configuration_json: New configuration
:type device_id: int
:type configuration_json: JSON
:rtype: Boolean
"""
from app.celery_builder import send_config
device = Device.get(id=device_id)
device.configuration = configuration_json
device.save()
send_config.delay(device_id, str(configuration_json))
def get_device_configuration(device_id):
"""
Tries to get configuration for device with given parameters.
:param device_id: Id of device
:type device_id: int
:returns: Configuration of given device
:rtype: JSON Configuration
"""
return Device.get(id=device_id).configuration
def get_device_recordings(device_id):
"""
Tries to get device recording for device with given parameters. Raises
error on failure
:param device_id: Id of device
:type device_id: int
:returns: List of Recordings for given device
:rtype: List of Recording
:raises: ValueError if device does not exist
"""
if not Device.exists(id=device_id):
raise ValueError("Device with id %s does not exist" % device_id)
return Recording.get_many(device_id=device_id)
def get_device_recordings_filtered(device_id, record_type=None,
start_date=None, end_date=None):
"""
Tries to get device recording for device with given parameters. Raises
error on failure
:param device_id: Id of device
:param record_type: Type of recording
:param start_date: Lower date limit
:param end_date: Upper date limit
:type device_id: int
:type record_type: int
:type start_date: Date (string: %d-%m-%Y)
:type end_date: Date (string: %d-%m-%Y)
:returns: List of Recordings for given filters
:rtype: List of Recording
:raises: ValueError if device does not exist
"""
if not Device.exists(id=device_id):
raise ValueError("Device with id %s does not exist" % device_id)
return Recording.get_many_filtered(device_id, record_type,
start_date, end_date)
def get_device(device_id):
"""
Tries to get device with given parameters. Raises error on failure
:param device_id: Id of device
:type device_id: int
:returns: Requested device
:rtype: Device
"""
return Device.get(id=device_id)
def can_user_access_device(account_id, device_id):
"""
Checks if user with given account_id can access device with given device_id
:param account_id: Id of account
:param device_id: Id of device
:type account_id: int
:type device_id: int
:returns: true if device is accessible by this account, false otherwise
:rtype: Boolean
"""
return len(DeviceAssociation.get_many(account_id=account_id,
device_id=device_id)) > 0
def get_device_type(device_type_id):
"""
Tries to get device type with given parameters. Raises error on failure
:param device_type_id: Id of device type
:type device_type_id: int
:returns: Requested device type
:rtype: DeviceType
"""
return DeviceType.get(id=device_type_id)
def delete_device(device_id):
"""
Tries to delete device with given parameters. Does not raise errors
"""
Device.get(id=device_id).delete()
def get_devices(account_id):
"""
Tries to get all devices associated to account. Raises error on
failure
:returns: List of Devices associated to this account
:rtype: List of Devices
"""
return Device.get_many_for_user(account_id)
def get_device_types():
"""
Tries to get all device types. Raises error on failure
:returns: List of device types
:rtype: List of DeviceTypes
"""
return DeviceType.get_many()
def create_recording(device_id, raw_json):
"""
Tries to create recording with given parameters. Raises error on failure
:param device_id: Id of device
:type device_id: int
:param raw_json: Raw json received
:type raw_json: json
:raises: ValueError if parsing fails or device does not exist
"""
def parse_raw_json_recording(device_id, json_msg):
try:
return Recording(device_id=device_id,
record_type=json_msg["record_type"],
record_value=json_msg["record_value"],
recorded_at=json_msg["recorded_at"],
raw_json=json_msg)
except KeyError:
error_type, error_instance, traceback = sys.exc_info()
raise ValueError("JSON parsing failed! Key error: "
+ str(error_instance))
if not Device.exists(id=device_id):
raise ValueError("Device does not exist!")
recording = parse_raw_json_recording(device_id, raw_json)
with app.app_context():
recording.save()

View File

@ -0,0 +1,3 @@
from flask import Blueprint
devices_bp = Blueprint('devices', __name__)

View File

@ -1,19 +0,0 @@
import atexit
from flask import Blueprint
from .mqtt_client import MqttClient
mqtt_bp = Blueprint('mqtt', __name__)
# Setup
@mqtt_bp.record
def __on_blueprint_setup(setup_state):
MqttClient.setup(setup_state.app)
# When app dies, stop mqtt connection
def on_stop():
MqttClient.tear_down()
atexit.register(on_stop)

View File

@ -0,0 +1,19 @@
import atexit
from flask import Blueprint
from .mqtt_client import MqttClient
mqtt_bp = Blueprint('mqtt', __name__)
# Setup
@mqtt_bp.record
def __on_blueprint_setup(setup_state):
MqttClient.setup(setup_state.app)
# When app dies, stop mqtt connection
def on_stop():
MqttClient.tear_down()
atexit.register(on_stop)

View File

@ -1,7 +1,7 @@
import sys
import json
from flask_mqtt import Mqtt
import app.devices as devices
import app.devices.api as devices
class MqttClient: