From c0520fcb1cef74cd38c35de160627e23077d8bf9 Mon Sep 17 00:00:00 2001 From: yumiguan <41277086+yumiguan@users.noreply.github.com> Date: Wed, 14 Nov 2018 16:14:34 +0800 Subject: [PATCH] v0.14.0 - support save interactive data into database sqlite (#51) * support save interactive data * support create table when init * change file path * remove wrong path of sqlite * fix Nonetype error * fix Nonetype error2 --- lyrebird/__init__.py | 2 +- lyrebird/application.py | 3 ++- lyrebird/event.py | 36 ++++++++++++++++---------- lyrebird/mock/context.py | 2 +- lyrebird/mock/db/__init__.py | 0 lyrebird/mock/db/database.py | 17 ++++++++++++ lyrebird/mock/db/models.py | 28 ++++++++++++++++++++ lyrebird/mock/handlers/mock_handler.py | 9 ++++--- lyrebird/mock/mock_server.py | 20 +++++++++++--- lyrebird/version.py | 2 +- requirements.txt | 1 + 11 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 lyrebird/mock/db/__init__.py create mode 100644 lyrebird/mock/db/database.py create mode 100644 lyrebird/mock/db/models.py diff --git a/lyrebird/__init__.py b/lyrebird/__init__.py index a28aae7f1..e1de84c55 100644 --- a/lyrebird/__init__.py +++ b/lyrebird/__init__.py @@ -56,7 +56,7 @@ def subscribe(channel, func, *args, **kwargs): :param sender: 信号发送者标识 """ # context.application.event_bus.subscribe(channel, func) - application.server['event'].subscribe(channel, func) + application.server['event'].subscribe(channel, func, *args, **kwargs) def publish(channel, event, *args, **kwargs): diff --git a/lyrebird/application.py b/lyrebird/application.py index f95d02c7c..7735b108b 100644 --- a/lyrebird/application.py +++ b/lyrebird/application.py @@ -60,4 +60,5 @@ def raw(self): def root_dir(): - return _cm.ROOT + if _cm: + return _cm.ROOT diff --git a/lyrebird/event.py b/lyrebird/event.py index 809f34c79..6b1636885 100644 --- a/lyrebird/event.py +++ b/lyrebird/event.py @@ -34,9 +34,12 @@ def __init__(self): self.any_channel = [] self.broadcast_executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix='event-broadcast') - def broadcast_handler(self, callback_fn, message): + def broadcast_handler(self, callback_fn, event, args, kwargs): try: - callback_fn(message) + if kwargs.get('event', False): + callback_fn(event) + else: + callback_fn(event.message) except Exception: # TODO handle exceptioins and send to event bus traceback.print_exc() @@ -47,10 +50,10 @@ def run(self): e = self.event_queue.get() callback_fn_list = self.pubsub_channels.get(e.channel) if callback_fn_list: - for callback_fn in callback_fn_list: - self.broadcast_executor.submit(self.broadcast_handler, callback_fn, e.message) - for callback_fn in self.any_channel: - self.broadcast_executor.submit(self.broadcast_handler, callback_fn, e.message) + for callback_fn, args, kwargs in callback_fn_list: + self.broadcast_executor.submit(self.broadcast_handler, callback_fn, e, args, kwargs) + for callback_fn, args, kwargs in self.any_channel: + self.broadcast_executor.submit(self.broadcast_handler, callback_fn, e, args, kwargs) except Exception: # empty event traceback.print_exc() @@ -74,24 +77,31 @@ def subscribe(self, channel, callback_fn, *args, **kwargs): """ Subscribe channel with a callback function That function will be called when a new message was published into it's channel + + kwargs: + event=False receiver gets a message dict + event=True receiver gets an Event object """ if channel == 'any': - self.any_channel.append(callback_fn) + self.any_channel.append([callback_fn, args, kwargs]) else: callback_fn_list = self.pubsub_channels.setdefault(channel, []) - callback_fn_list.append(callback_fn) + callback_fn_list.append([callback_fn, args, kwargs]) - def unsubscribe(self, channel, callback_fn, *args, **kwargs): + def unsubscribe(self, channel, target_callback_fn, *args, **kwargs): """ Unsubscribe callback function from channel """ - if channel == 'any' and callback_fn in self.any_channel: - self.any_channel.remove(callback_fn) + if channel == 'any': + for any_channel_fn, *_ in self.any_channel: + if target_callback_fn == any_channel_fn: + self.any_channel.remove([target_callback_fn, *_]) else: callback_fn_list = self.pubsub_channels.get(channel) - if callback_fn_list and callback_fn in callback_fn_list: - callback_fn_list.remove(callback_fn) + for callback_fn, *_ in callback_fn_list: + if target_callback_fn == callback_fn: + callback_fn_list.remove([target_callback_fn, *_]) class CustomEventReceiver: diff --git a/lyrebird/mock/context.py b/lyrebird/mock/context.py index 616de7929..c87c3a33b 100644 --- a/lyrebird/mock/context.py +++ b/lyrebird/mock/context.py @@ -59,7 +59,7 @@ def save(self): application = Application() - +db = None def make_ok_response(**kwargs): ok_resp = { diff --git a/lyrebird/mock/db/__init__.py b/lyrebird/mock/db/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lyrebird/mock/db/database.py b/lyrebird/mock/db/database.py new file mode 100644 index 000000000..23f0cca47 --- /dev/null +++ b/lyrebird/mock/db/database.py @@ -0,0 +1,17 @@ +from flask_sqlalchemy import SQLAlchemy + +class DataBase: + def __init__(self, app): + self._db = SQLAlchemy(app) + self._db.init_app(app) + + @property + def model(self): + return self._db.Model + + @property + def session(self): + return self._db.session + + def create_all(self): + self._db.create_all() diff --git a/lyrebird/mock/db/models.py b/lyrebird/mock/db/models.py new file mode 100644 index 000000000..ca1ee147a --- /dev/null +++ b/lyrebird/mock/db/models.py @@ -0,0 +1,28 @@ +import json +import datetime +import lyrebird +from lyrebird.mock import context +from sqlalchemy import Column, Table, String, Integer, DateTime, Text + + +class Flow(context.db.model): + + id = Column(Integer, primary_key=True, autoincrement=True) + channel = Column(String(16)) + content = Column(Text) + ts = Column(DateTime(timezone=True), default=datetime.datetime.now) + + def __init__(self, channel, content): + self.channel = channel + self.content = content + +def active_db_listener(): + lyrebird.subscribe('any', save_data_into_db, event=True) + context.db.create_all() + +def save_data_into_db(event): + message, channel = event.message, event.channel + content = json.dumps(message) + data = Flow(channel, content) + context.db.session.add(data) + context.db.session.commit() diff --git a/lyrebird/mock/handlers/mock_handler.py b/lyrebird/mock/handlers/mock_handler.py index dd84e4a8b..56373d9dd 100644 --- a/lyrebird/mock/handlers/mock_handler.py +++ b/lyrebird/mock/handlers/mock_handler.py @@ -8,7 +8,8 @@ class MockHandler: """ def handle(self, handler_context): - data_group = context.application.data_manager.current_data_group - if data_group: - handler_context.response = data_group.get_response(handler_context.get_origin_url(), - handler_context.request.data) + pass + # data_group = context.application.data_manager.current_data_group + # if data_group: + # handler_context.response = data_group.get_response(handler_context.get_origin_url(), + # handler_context.request.data) diff --git a/lyrebird/mock/mock_server.py b/lyrebird/mock/mock_server.py index 7fa9f1dce..7ce6b0dc8 100644 --- a/lyrebird/mock/mock_server.py +++ b/lyrebird/mock/mock_server.py @@ -1,9 +1,9 @@ -import json import os import sys -import socket +import json import errno import socket +import datetime import subprocess from . import plugin_manager from flask import Flask, request, redirect, url_for, Response @@ -14,10 +14,11 @@ from flask_socketio import SocketIO from .reporter import report_handler from ..version import VERSION -import datetime from lyrebird.base_server import ThreadServer from lyrebird import application from lyrebird import log +from flask_sqlalchemy import SQLAlchemy +from lyrebird.mock.db.database import DataBase """ @@ -74,6 +75,16 @@ def __init__(self): raise SyntaxError('Can not start mock server without config file.' ' Default config file path = api-mock/conf.json') + # sqlite初始化 + ROOT_DIR = application.root_dir() + DB_FILE_NAME = 'lyrebird.db' + if ROOT_DIR: + SQLALCHEMY_DATABASE_URI = ROOT_DIR/DB_FILE_NAME + # TODO: 'sqlite:///' is unfriendly to windows + self.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+str(SQLALCHEMY_DATABASE_URI) + self.app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + context.db = DataBase(self.app) + # 插件初始化 plugin_manager.load() # 加载插件界面 @@ -105,6 +116,9 @@ def run(self): server_ip = application.config.get('ip') _logger.warning(f'start on http://{server_ip}:{self.port}') report_handler.start() + # cannot import at beginning, cause db hasn't init + from lyrebird.mock.db.models import active_db_listener + active_db_listener() self.socket_io.run(self.app, host='0.0.0.0', port=self.port, debug=True, use_reloader=False) diff --git a/lyrebird/version.py b/lyrebird/version.py index 4ba83f779..46de3edc2 100644 --- a/lyrebird/version.py +++ b/lyrebird/version.py @@ -1,3 +1,3 @@ -IVERSION = (0, 13, 9) +IVERSION = (0, 14, 0) VERSION = ".".join(str(i) for i in IVERSION) LYREBIRD = "Lyrebird " + VERSION diff --git a/requirements.txt b/requirements.txt index 095343073..72c7d4eca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ requests==2.13.0 fire==0.1.0 flask-socketio flask-restful +Flask-SQLAlchemy beautifulsoup4 pytest portpicker