loading extension from external python packages

This commit is contained in:
manuel83 2018-12-31 00:22:00 +01:00
parent 28f87c6c3f
commit ce2d942771
23 changed files with 633 additions and 992 deletions

File diff suppressed because it is too large Load diff

3
config/plugin_list.txt Normal file
View file

@ -0,0 +1,3 @@
cbpi-actor
cbpi-actor
cbpi-actor

View file

@ -9,10 +9,11 @@ from core.database.model import ActorModel
from core.http_endpoints.http_api import HttpAPI
from utils.encoder import ComplexEncoder
auth = True
class ActorHttp(HttpAPI):
@request_mapping(path="/{id:\d+}/on", auth_required=False)
@request_mapping(path="/{id:\d+}/on", auth_required=auth)
async def http_on(self, request) -> web.Response:
"""
:param request:
@ -27,7 +28,7 @@ class ActorHttp(HttpAPI):
return web.Response(status=204)
@request_mapping(path="/{id:\d+}/off", auth_required=False)
@request_mapping(path="/{id:\d+}/off", auth_required=auth)
async def http_off(self, request) -> web.Response:
"""
:param request:
@ -37,7 +38,7 @@ class ActorHttp(HttpAPI):
await self.cbpi.bus.fire(topic="actor/%s/off" % id, id=id)
return web.Response(status=204)
@request_mapping(path="/{id:\d+}/toggle", auth_required=False)
@request_mapping(path="/{id:\d+}/toggle", auth_required=auth)
async def http_toggle(self, request) -> web.Response:
"""
:param request:
@ -78,6 +79,7 @@ class ActorController(ActorHttp, CRUDController):
await super(ActorController, self).init()
for id, value in self.cache.items():
await self._init_actor(value)
async def _init_actor(self, actor):
@ -85,10 +87,13 @@ class ActorController(ActorHttp, CRUDController):
cfg = actor.config.copy()
cfg.update(dict(cbpi=self.cbpi, id=id, name=actor.name))
clazz = self.types[actor.type]["class"];
self.cache[actor.id].instance = clazz(**cfg)
self.cache[actor.id].instance.init()
await self.cbpi.bus.fire(topic="actor/%s/initialized" % actor.id, id=actor.id)
else:
self.logger.error("Actor type '%s' not found (Available Actor Types: %s)" % (actor.type, ', '.join(self.types.keys())))
async def _stop_actor(self, actor):

View file

@ -51,7 +51,7 @@ class ConfigController(ConfigHTTPController):
self.cache[value.name] = value
async def get(self, name, default=None):
def get(self, name, default=None):
self.logger.info("GET CONFIG VALUE %s (default %s)" % (name,default))
if name in self.cache and self.cache[name].value is not None:
return self.cache[name].value

View file

@ -1,16 +1,12 @@
import imp
import importlib
import logging
import os
import sys
from importlib import import_module
from pprint import pprint
import aiohttp
import yaml
from aiohttp import web
from cbpi_api import *
from core.utils.utils import load_config, json_dumps
logger = logging.getLogger(__name__)
@ -42,20 +38,17 @@ class PluginController():
if os.path.isdir("./core/extension/" + filename) is False or filename == "__pycache__":
continue
try:
logger.info("Trying to load plugin %s" % filename)
data = load_config("./core/extension/%s/config.yaml" % filename)
if(data.get("version") == 4):
self.modules[filename] = import_module("core.extension.%s" % (filename))
self.modules[filename].setup(self.cbpi)
logger.info("Plugin %s loaded successful" % filename)
else:
logger.warning("Plguin %s is not supporting version 4" % filename)
logger.warning("Plugin %s is not supporting version 4" % filename)
except Exception as e:
@ -63,14 +56,25 @@ class PluginController():
def load_plugins_from_evn(self):
plugins = []
plugins = ["cbpi-actor"]
with open('./config/plugin_list.txt') as f:
plugins = f.read().splitlines()
plugins = list(set(plugins))
for p in plugins:
logger.debug("Load Plugin %s" % p)
try:
logger.info("Try to load plugin: %s " % p)
self.modules[p] = import_module(p)
self.modules[p].setup(self.cbpi)
self.modules[p] = import_module(p)
self.modules[p].setup(self.cbpi)
logger.info("Plugin %s loaded successfully" % p)
except Exception as e:
logger.error("FAILED to load plugin %s " % p)
logger.error(e)
@request_mapping(path="/plugins", method="GET", auth_required=False)
@ -99,11 +103,10 @@ class PluginController():
:param clazz: actor class
:return: None
'''
logger.info("Register %s Class %s" % (name, clazz.__name__))
if issubclass(clazz, CBPiActor):
self.cbpi.actor.types[name] = {"class": clazz, "config": self._parse_props(clazz)}
if issubclass(clazz, CBPiSensor):
self.cbpi.sensor.types[name] = {"class": clazz, "config": self._parse_props(clazz)}

View file

@ -48,5 +48,4 @@ class SensorController(CRUDController, HttpAPI):
async def get_value(self, id):
return self.cache[id].instance.value

View file

@ -22,7 +22,7 @@ from core.controller.plugin_controller import PluginController
from core.controller.sensor_controller import SensorController
from core.controller.system_controller import SystemController
from core.database.model import DBModel
from core.eventbus import EventBus
from core.eventbus import CBPiEventBus
from core.http_endpoints.http_login import Login
from core.utils import *
from core.websocket import WebSocket
@ -50,7 +50,7 @@ class CraftBeerPi():
logger.info("Init CraftBeerPI")
policy = auth.SessionTktAuthentication(urandom(32), 60, include_ip=True)
middlewares = [session_middleware(EncryptedCookieStorage(urandom(32))), auth.auth_middleware(policy)]
middlewares = [web.normalize_path_middleware(), session_middleware(EncryptedCookieStorage(urandom(32))), auth.auth_middleware(policy)]
self.app = web.Application(middlewares=middlewares)
self.initializer = []
self.shutdown = False
@ -65,7 +65,7 @@ class CraftBeerPi():
setup(self.app, self)
self.bus = EventBus(self.app.loop, self)
self.bus = CBPiEventBus(self.app.loop, self)
self.ws = WebSocket(self)
self.actor = ActorController(self)
self.sensor = SensorController(self)
@ -75,14 +75,7 @@ class CraftBeerPi():
self.kettle = KettleController(self)
self.step = StepController(self)
self.notification = NotificationController(self)
self.login = Login(self)
self.plugin.load_plugins()
self.plugin.load_plugins_from_evn()
self.register_events(self.ws)
@ -202,9 +195,11 @@ class CraftBeerPi():
sub = web.Application()
sub.add_routes(routes)
if static is not None:
sub.add_routes([web.static('/static', static, show_index=True)])
sub.add_routes([web.static('/static', static, show_index=False)])
self.app.add_subapp(url_prefix, sub)
else:
self.app.add_routes(routes)
@ -249,53 +244,43 @@ class CraftBeerPi():
#async def init_database(app):
#self.app.on_startup.append(call_initializer)
async def call_initializer(self, app):
self.initializer = sorted(self.initializer, key=lambda k: k['order'])
for i in self.initializer:
logger.info("CALL INITIALIZER %s - %s " % (i["name"], i["method"].__name__))
await i["method"]()
def _print_logo(self):
from pyfiglet import Figlet
f = Figlet(font='big')
print("")
print(f.renderText("%s %s" % (self.config.get("name"), self.config.get("version"))))
print("")
async def init_database(app):
await DBModel.test_connection()
logger.info("\n%s" % f.renderText("%s %s" % (self.config.get("name"), self.config.get("version"))))
async def init_controller(app):
await self.sensor.init()
await self.step.init()
await self.actor.init()
await self.kettle.init()
await self.config2.init()
async def init_serivces(self):
self._print_logo()
await DBModel.test_connection()
await self.config2.init()
self.plugin.load_plugins()
self.plugin.load_plugins_from_evn()
await self.sensor.init()
await self.step.init()
await self.actor.init()
await self.kettle.init()
await self.call_initializer(self.app)
print(self.sensor.info())
print(self.actor.info())
import pprint
#pprint.pprint(self.bus.dump())
async def load_plugins(app):
#await PluginController.load_plugin_list()
#self.plugin.load_plugins()
pass
async def call_initializer(app):
self.initializer = sorted(self.initializer, key=lambda k: k['order'])
for i in self.initializer:
logger.info("CALL INITIALIZER %s - %s " % (i["name"], i["method"].__name__))
await i["method"]()
self.app.on_startup.append(init_database)
self.app.on_startup.append(call_initializer)
self.app.on_startup.append(load_plugins)
self.app.on_startup.append(init_controller)
logger.info(self.sensor.info())
logger.info(self.actor.info())
self._swagger_setup()
return self.app
def start(self):
web.run_app(self.app, port=self.config.get("port", 8080))
web.run_app(self.init_serivces(), port=self.config.get("port", 8080))

View file

@ -1,9 +1,7 @@
import json
import aiosqlite
from core.database.orm_framework import DBModel
TEST_DB = "./craftbeerpi.db"
DATABASE_FILE = "./craftbeerpi.db"
class ActorModel(DBModel):
__fields__ = ["name", "type", "config"]
@ -11,8 +9,6 @@ class ActorModel(DBModel):
__json_fields__ = ["config"]
class SensorModel(DBModel):
__fields__ = ["name", "type", "config"]
__table_name__ = "sensor"
@ -37,7 +33,7 @@ class StepModel(DBModel):
@classmethod
async def update_step_state(cls, step_id, state):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
cursor = await db.execute("UPDATE %s SET stepstate = ? WHERE id = ?" % cls.__table_name__, (json.dumps(state), step_id))
await db.commit()
@ -45,7 +41,7 @@ class StepModel(DBModel):
async def get_by_state(cls, state, order=True):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = aiosqlite.Row
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__, cls.__table_name__,), state) as cursor:
@ -57,48 +53,7 @@ class StepModel(DBModel):
@classmethod
async def reset_all_steps(cls):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
cursor = await db.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
await db.commit()
'''
@classmethod
def sort(cls, new_order):
cur = get_db().cursor()
for key, value in new_order.items():
cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (value, key))
get_db().commit()
@classmethod
def get_max_order(cls):
cur = get_db().cursor()
cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__)
r = cur.fetchone()
return r.get("order")
@classmethod
def delete_all(cls):
cur = get_db().cursor()
cur.execute("DELETE FROM %s" % cls.__table_name__)
get_db().commit()
@classmethod
def get_by_state(cls, state, order=True):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__, cls.__table_name__,), state)
r = cur.fetchone()
if r is not None:
return cls(r)
else:
return None
@classmethod
def reset_all_steps(cls):
cur = get_db().cursor()
cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
get_db().commit()
'''

View file

@ -1,10 +1,8 @@
import json
import aiosqlite
import os
# Thats the name of the database file
TEST_DB = "./craftbeerpi.db"
DATABASE_FILE = "./craftbeerpi.db"
class DBModel(object):
@ -36,7 +34,7 @@ class DBModel(object):
@classmethod
async def test_connection(self):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
assert isinstance(db, aiosqlite.Connection)
this_directory = os.path.dirname(__file__)
qry = open(os.path.join(this_directory, '../sql/create_table_user.sql'), 'r').read()
@ -49,7 +47,7 @@ class DBModel(object):
result = []
else:
result = {}
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
if cls.__order_by__ is not None:
sql = "SELECT * FROM %s ORDER BY %s.'%s'" % (cls.__table_name__, cls.__table_name__, cls.__order_by__)
@ -71,7 +69,7 @@ class DBModel(object):
@classmethod
async def get_one(cls, id):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
db.row_factory = aiosqlite.Row
db.row_factory = DBModel.dict_factory
async with db.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,)) as cursor:
@ -83,14 +81,14 @@ class DBModel(object):
@classmethod
async def delete(cls, id):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
await db.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,))
await db.commit()
@classmethod
async def insert(cls, **kwargs):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
if cls.__priamry_key__ is not None and cls.__priamry_key__ in kwargs:
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__table_name__,
@ -129,7 +127,7 @@ class DBModel(object):
@classmethod
async def update(cls, **kwargs):
async with aiosqlite.connect(TEST_DB) as db:
async with aiosqlite.connect(DATABASE_FILE) as db:
query = 'UPDATE %s SET %s WHERE %s = ?' % (cls.__table_name__, ', '.join("'%s' = ?" % str(x) for x in cls.__fields__), cls.__priamry_key__)
data = ()
@ -150,4 +148,3 @@ class DBModel(object):
for idx, col in enumerate(cursor.description):
d[col[0]] = row[idx]
return d

View file

@ -3,7 +3,7 @@ import inspect
import logging
class EventBus(object):
class CBPiEventBus(object):
class Node(object):
__slots__ = '_children', '_content'
@ -33,9 +33,9 @@ class EventBus(object):
self.timeout = timeout
for key, value in results.items():
if value.done() is True:
self.results[key] = EventBus.Result(value.result(), True)
self.results[key] = CBPiEventBus.Result(value.result(), True)
else:
self.results[key] = EventBus.Result(None, False)
self.results[key] = CBPiEventBus.Result(None, False)
@ -104,7 +104,7 @@ class EventBus(object):
self.loop.create_task(self.fire(topic=topic, timeout=timeout, **kwargs))
async def fire(self, topic: str, timeout=1, **kwargs):
print("FIRE")
futures = {}
async def wait(futures):

View file

@ -1,13 +1,8 @@
import logging
from aiohttp import web
from cbpi_api import *
from core.utils.utils import json_dumps
class HttpAPI():
def __init__(self, cbpi):
self.logger = logging.getLogger(__name__)

View file

@ -20,12 +20,16 @@ class Login():
@request_mapping(path="/login",name="Login", method="POST", auth_required=False)
async def login_view(self, request):
print("TRY LOGIN")
params = await request.post()
user = params.get('username', None)
password = params.get('password', None)
print("UUSEr", user, password, str(self.db[user]))
if (user in self.db and
params.get('password', None) == self.db[user]):
params.get('password', None) == str(self.db[user])):
# User is in our database, remember their login details
await auth.remember(request, user)

View file

@ -77,31 +77,4 @@ class WebSocket:
self.logger.info("Web Socket Close")
return ws
'''
async def websocket_handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
_ws.append(ws)
c = len(_ws) - 1
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
if msg.data == 'close':
await ws.close()
else:
await ws.send_str(msg.data)
elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' %
ws.exception())
del _ws[c]
print('websocket connection closed')
return ws
'''
return ws

Binary file not shown.

View file

@ -9,10 +9,10 @@ As a main framework CraftBeerPi is based on `aiohttp`
* aioHTTP https://aiohttp.readthedocs.io/en/stable/
EventBus
CBPiEventBus
--------
One core concept of CraftBeerPi 4.x is the EventBus.
One core concept of CraftBeerPi 4.x is the CBPiEventBus.
It should be avoided to call method on a controller directly. Events should be fired and listener methods should be used.
This makes sure that all components are loosely coupled. New plugins can listen on events and extend or change the functionality easily.

View file

@ -187,7 +187,7 @@
</li>
<li class="toctree-l1"><a class="reference internal" href="standards.html">Standard &amp; Guidelines</a><ul>
<li class="toctree-l2"><a class="reference internal" href="standards.html#python">Python</a><ul>
<li class="toctree-l3"><a class="reference internal" href="standards.html#eventbus">EventBus</a></li>
<li class="toctree-l3"><a class="reference internal" href="standards.html#eventbus">CBPiEventBus</a></li>
<li class="toctree-l3"><a class="reference internal" href="standards.html#http-endpoints">HTTP Endpoints</a></li>
<li class="toctree-l3"><a class="reference internal" href="standards.html#websocket">WebSocket</a></li>
</ul>

View file

@ -87,7 +87,7 @@
<li class="toctree-l1"><a class="reference internal" href="properties.html">Properties</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Standard &amp; Guidelines</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#python">Python</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#eventbus">EventBus</a></li>
<li class="toctree-l3"><a class="reference internal" href="#eventbus">CBPiEventBus</a></li>
<li class="toctree-l3"><a class="reference internal" href="#http-endpoints">HTTP Endpoints</a></li>
<li class="toctree-l3"><a class="reference internal" href="#websocket">WebSocket</a></li>
</ul>
@ -170,8 +170,8 @@ As a main framework CraftBeerPi is based on <cite>aiohttp</cite></p>
<li>aioHTTP <a class="reference external" href="https://aiohttp.readthedocs.io/en/stable/">https://aiohttp.readthedocs.io/en/stable/</a></li>
</ul>
<div class="section" id="eventbus">
<h3>EventBus<a class="headerlink" href="#eventbus" title="Permalink to this headline"></a></h3>
<p>One core concept of CraftBeerPi 4.x is the EventBus.
<h3>CBPiEventBus<a class="headerlink" href="#eventbus" title="Permalink to this headline"></a></h3>
<p>One core concept of CraftBeerPi 4.x is the CBPiEventBus.
It should be avoided to call method on a controller directly. Events should be fired and listener methods should be used.
This makes sure that all components are loosely coupled. New plugins can listen on events and extend or change the functionality easily.</p>
<p>Here an example how to fire an event</p>

View file

@ -9,10 +9,10 @@ As a main framework CraftBeerPi is based on `aiohttp`
* aioHTTP https://aiohttp.readthedocs.io/en/stable/
EventBus
CBPiEventBus
--------
One core concept of CraftBeerPi 4.x is the EventBus.
One core concept of CraftBeerPi 4.x is the CBPiEventBus.
It should be avoided to call method on a controller directly. Events should be fired and listener methods should be used.
This makes sure that all components are loosely coupled. New plugins can listen on events and extend or change the functionality easily.

View file

@ -1,90 +1,7 @@
import asyncio
import re
from core.eventbus3 import EventBus
async def waiter(event):
print('waiting for it ...')
await asyncio.sleep(4)
print('... got it!')
event.set()
with open('./config/plugin_list.txt') as f:
required = f.read().splitlines()
async def main(loop):
# Create an Event object.
event = asyncio.Event()
# Spawn a Task to wait until 'event' is set.
#waiter_task = asyncio.create_task(waiter(event))
waiter_task = loop.create_task(waiter(event))
print("WAIT FOR EVENT")
await event.wait()
# Wait until the waiter task is finished.
await waiter_task
#loop = asyncio.get_event_loop()
#loop.run_until_complete(main(loop))
#loop.close()
bus = EventBus(None)
def test(**kwargs):
print("")
print("------------> HALLO WILD CARD")
print("")
print(hex(id(test)))
print("BUS",bus)
def test2(**kwargs):
print("------------> HALLO NAME")
class Name():
def test(self, **kwargs):
print("---->OK")
bus.unregister(self.test)
print("#################### ID2", hex(id(n.test)))
n = Name()
print("the ID", hex(id(n.test)))
id1 = bus.register("test/#", n.test)
print("the ID2", hex(id(n.test)))
if n.test == n.test:
print("SAME")
id1 = bus.register("test/#", test)
id2 = bus.register("test/name", test2)
print(id1, id2)
from gtts import gTTS
print(hex(id(test2)))
print(bus.get_callbacks("test/name"))
bus.fire("test/name")
bus.fire("test/name")
bus.unregister(test2)
bus.fire("test/name")
m0 = re.match("kettle_logic_(\d+)", "kettle_1logic_22")
print(m0)
#print(m0.group(1))
print(required)

1
run.py
View file

@ -4,5 +4,4 @@ from core.craftbeerpi import CraftBeerPi
cbpi = CraftBeerPi()
cbpi.setup()
cbpi.start()

View file

View file

@ -1,3 +1,4 @@
import aiohttp
from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
from core.craftbeerpi import CraftBeerPi
@ -11,14 +12,20 @@ class MyAppTestCase(AioHTTPTestCase):
async def get_application(self):
self.cbpi = CraftBeerPi()
self.cbpi.setup()
await self.cbpi.init_serivces()
return self.cbpi.app
@unittest_run_loop
async def test_example(self):
resp = await self.client.request("GET", "/actor/1/on")
resp = await self.client.post(path="/login", data={"username": "cbpi", "password": "123"})
print("resp.status",resp.status)
assert resp.status == 200
resp = await self.client.request("GET", "/actor/1/on")
print("resp.status", resp.status)
assert resp.status == 204
i = await self.cbpi.actor.get_one(1)
assert i.instance.state is True