Как отправить поток данных RxPy в веб-интерфейс JavaScript - PullRequest
0 голосов
/ 21 октября 2019

Я пытаюсь получить поток Python ReactiveX (используя библиотеку RxPy) для отправки в javascript на компонент веб-интерфейса, но, похоже, не могу найти способ сделать это. Кроме того, мне может понадобиться получить поток данных, поступающий в Javascript, в своего рода RxJS Observable для дальнейшей обработки. Не могли бы вы помочь мне понять, как этого добиться? Я до сих пор овладеваю ReactiveX, поэтому, может быть, мне не хватает некоторых фундаментальных концепций, но я изо всех сил пытаюсь найти что-то подобное в сети.

Эта проблема возникла, когда я работаю над настольным приложением, которое берет данные из конечной точки csv или zeromq и передает их в пользовательский интерфейс, где данные будут отображаться динамически (график обновляется как новыйданные поступают). Я использую Electron для создания своего приложения, используя Python в качестве внутреннего кода. Python является обязательным условием, так как я буду расширять приложение некоторыми моделями TensorFlow.

После действительно хорошо сделанного примера в качестве исходной структуры, я написал некоторый пример кода для игры, но яне могу заставить его работать. Мне удается добраться от кнопки пользовательского интерфейса до сценариев Python, но я застрял в возвращении метода PricesApi.get_stream (...).

index.html

Внешний интерфейс - прямой.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Electron Application</title>  
    </head>
    <body>
        <button id="super-button">Trigger Python Code</button>
        <div id="py-output">
        </div>
    </body>
    <script src="renderer.js" ></script>
</html>

api.py:

Файл сервера ZeroRPC похож на файл ввышеупомянутая ссылка.

import gevent
import json
import signal
import zerorpc
from core_operator import stream


class PricesApi(object):

    def get_stream(self, filename):
        return stream(filename)

    def stop(self):
        print('Stopping strategy.')

    def echo(self, text):
        """echo any text"""
        return text


def load_settings():
    with open('settings.json') as json_settings:
        settings_dictionary = json.load(json_settings)
    return settings_dictionary


def main():
    settings = load_settings()
    s = zerorpc.Server(PricesApi())
    s.bind(settings['address'])
    print(f"Initialising server on {settings['address']}")
    s.run()


if __name__ == '__main__':
    main()

core_operator.py

Это файл, в котором основная логика будет сидеть для получения цен по подписке zeroMQ, но в настоящее время просто создает Observable из CSV.

import sys
import rx
from csv import DictReader


def prepare_csv_timeseries_stream(filename):
    return rx.from_(DictReader(open(filename, 'r')))


def stream(filename):
    price_observable = prepare_csv_timeseries_stream(filename)
    return price_observable

rendered.js

наконец, JavaScript, который должен получать поток:

const zerorpc = require('zerorpc');
const fs = require('fs')

const settings_block = JSON.parse(fs.readFileSync('./settings.json').toString());
let client = new zerorpc.Client();
client.connect(settings_block['address']);

let button = document.querySelector('#super-button');
let pyOutput = document.querySelector('#py-output');
let filename = '%path-to-file%'
button.addEventListener('click', () => {
    let line_to_write = '1'
    console.log('button click received.')
    client.invoke('get_stream', filename, (error, result) => {
        var messages = pyOutput;
        message = document.createElement('li'),
        content = document.createTextNode(error.data);
        message.appendChild(content);
        messages.appendChild(message);

        if(error) {
            console.error(error);
        } else {
           var messages = pyOutput;
           message = document.createElement('li'),
           content = document.createTextNode(result.data);
           message.appendChild(content);
           messages.appendChild(message);    
        }
    })
})

Я изучал использование WebSockets, но не смог понятькак это реализовать. Я нашел несколько примеров использования сервера Tornado, однако я стараюсь сохранить его как можно более чистым, и, кроме того, мне кажется странным, что, имея уже структуру клиент / сервер от Electron, я не могу использовать это напрямую. Также я пытаюсь сохранить всю систему в виде структуры PUSH, поскольку требования к данным не позволяют использовать шаблон типа PULL с регулярными опросами и т. Д.

Большое спасибо заранее за любое время, когда вы можетевыделите это, и, пожалуйста, дайте мне знать, если вам потребуются какие-либо дополнительные детали или объяснения.

1 Ответ

0 голосов
/ 01 ноября 2019

Я нашел решение, используя удивительную библиотеку под названием Eel (описанную как «Маленькая библиотека Python для создания простых Electron-подобных приложений HTML / JS GUI»). Его абсолютная простота и интуитивность позволили мне добиться того, чего я хотел, несколькими простыми строчками.

  1. Следуйте введению, чтобы понять макет.
  2. Затем ваш основной файл python (который я обычно назвал main.py), вы выставляете функцию потока для eel, так что она может бытьвызывается из файла JS и направляет поток в функцию JavaScript «receive_price», которая предоставляется из файла JS!
import sys
import rx
from csv import DictReader


def prepare_csv_timeseries_stream(filename):
    return rx.from_(DictReader(open(filename, 'r')))


def process_logic():
    return pipe(
        ops.map(lambda p: print(p)),  # just to view what's flowing through
        ops.map(lambda p: eel.receive_price(p)),  # KEY FUNCTION in JS file, exposed via eel, is called for each price. 
    )


@eel.expose  # Decorator so this function can get triggered from JavaScript
def stream(filename):
    price_observable = prepare_csv_timeseries_stream(filename)
    price_observable.pipe(process_logic()).subscribe()  # apply the pipe and subscribe to trigger stream

eel.init('web')
eel.start('main.html')  # look at how beautiful and elegant this is! 
Теперь мы создадим файл price_processing.js (помещенный в папку «web» в соответствии с инструкциями Eel), чтобы включить открытые функции
let button   = document.querySelector('#super-button');
let pyOutput = document.querySelector('#py-output'   );
let filename = '%path-to-file%'

console.log("ready to receive data!")

eel.expose(receive_price);  // Exposing the function to Python, to process each price
function receive_price(result) {
    var messages = pyOutput;
    message = document.createElement('li');
    content = document.createTextNode(result);
    message.appendChild(content);
    messages.appendChild(message);
    // in here you can add more functions to process data, e.g. logging, charting and so on..
};

button.addEventListener('click', () => {
    console.log('Button clicked magnificently! Bloody good job')
    eel.stream(filename); // calling the Python function exposed through Eel to start stream.
})
HTML остается почти таким же, за исключением изменения скрипта refs: /eel.js, согласно документации Eel и нашему файлу price_processing.js.
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Let's try Eel</title>
    </head>
    <body>
        <h1>Eel-saved-my-life: the App!</h1>
        <button id="super-button">Trigger Python Code</button>
        <div id="py-output">

        </div>
    </body>
    <script type="text/javascript" src="/eel.js"></script>
    <script type="text/javascript" src="price_processing.js"></script>
</html>

Надеюсь, это может помочь любому, кто борется с той же проблемой.

...