Returning large data from RPC (Crossbar + Autobahn|Python) - crossbar

I am trying to transmit large data through websockets using crossbar/autobahn's RPC. My setup is as follow:
Python 2.7
A crossbar router (version 17.8.1.post1)
A back-end that will try to send a large pandas DataFrame as a json string
A front-end that will want to receive this string
In essence my front-end is trying to call a function that will return a large string.
class MyComponent(ApplicationSession):
#inlineCallbacks
def onJoin(self, details):
print("session ready")
try:
res = yield self.call(u'data.get')
And I get this error:
2017-08-09T16:38:10+0200 session closed with reason wamp.close.transport_lost [WAMP transport was lost without closing the session before]
2017-08-09T16:38:10+0200 Cancelling 1 outstanding requests
2017-08-09T16:38:10+0200 call error: ApplicationError(error=<wamp.close.transport_lost>, args=[u'WAMP transport was lost without closing the session before'], kwargs={}, enc_algo=None)
It seems crossbar is kicking me out because my client session looks dead to him, but I thought that autobahn would chunk my data and that the call would not block the client reactor.
I enabled a few things in my crossbar configuration to improve websocket treatment; thanks to that I was able to transmit larger amount of data but eventually I would hit a limit (config file largely copied and pasted from sam & max).
"options": {
"enable_webstatus": false,
"max_frame_size": 16777216,
"auto_fragment_size": 65536,
"fail_by_drop": true,
"open_handshake_timeout": 2500,
"close_handshake_timeout": 1000,
"auto_ping_interval": 10000,
"auto_ping_timeout": 5000,
"auto_ping_size": 4,
"compression": {
"deflate": {
"request_no_context_takeover": false,
"request_max_window_bits": 11,
"no_context_takeover": false,
"max_window_bits": 11,
"memory_level": 4
}
}
}
Any ideas, takes, things that I am doing wrong?
Thank you,
Client code:
from __future__ import print_function
import pandas as pd
from autobahn.twisted.wamp import ApplicationSession
from twisted.internet.defer import inlineCallbacks
class MyComponent(ApplicationSession):
#inlineCallbacks
def onJoin(self, details):
print("session ready")
try:
res = yield self.call(u'data.get')
print('Got the data')
data = pd.read_json(res)
print("call result: {}".format(data.head()))
print("call result shape: {0}, {1}".format(*data.shape))
except Exception as e:
print("call error: {0}".format(e))
if __name__ == "__main__":
from autobahn.twisted.wamp import ApplicationRunner
runner = ApplicationRunner(url=u"ws://127.0.0.1:8080/ws", realm=u"realm1")
runner.run(MyComponent)
Backend code
from __future__ import absolute_import, division, print_function
from twisted.internet.defer import inlineCallbacks
from autobahn.twisted.wamp import ApplicationSession
from twisted.internet import reactor, defer, threads
# Imports
import pandas as pd
def get_data():
"""Returns a DataFrame of stuff as a JSON
:return: str, data as a JSON string
"""
data = pd.DataFrame({
'col1': pd.np.arange(1000000),
'col2': "I'm big",
'col3': 'Like really big',
})
print("call result shape: {0}, {1}".format(*data.shape))
print(data.memory_usage().sum())
print(data.head())
return data.to_json()
class MyBackend(ApplicationSession):
def __init__(self, config):
ApplicationSession.__init__(self, config)
#inlineCallbacks
def onJoin(self, details):
# Register a procedure for remote calling
#inlineCallbacks
def async_daily_price(eqt_list):
res = yield threads.deferToThread(get_data)
defer.returnValue(res)
yield self.register(async_daily_price, u'data.get')
if __name__ == "__main__":
from autobahn.twisted.wamp import ApplicationRunner
runner = ApplicationRunner(url=u"ws://127.0.0.1:8080/ws", realm=u"realm1")
runner.run(MyBackend)
Configuration
{
"version": 2,
"controller": {},
"workers": [
{
"type": "router",
"realms": [
{
"name": "realm1",
"roles": [
{
"name": "anonymous",
"permissions": [
{
"uri": "",
"match": "prefix",
"allow": {
"call": true,
"register": true,
"publish": true,
"subscribe": true
},
"disclose": {
"caller": false,
"publisher": false
},
"cache": true
}
]
}
]
}
],
"transports": [
{
"type": "universal",
"endpoint": {
"type": "tcp",
"port": 8080
},
"rawsocket": {
},
"websocket": {
"ws": {
"type": "websocket",
"options": {
"enable_webstatus": false,
"max_frame_size": 16777216,
"auto_fragment_size": 65536,
"fail_by_drop": true,
"open_handshake_timeout": 2500,
"close_handshake_timeout": 1000,
"auto_ping_interval": 10000,
"auto_ping_timeout": 5000,
"auto_ping_size": 4,
"compression": {
"deflate": {
"request_no_context_takeover": false,
"request_max_window_bits": 11,
"no_context_takeover": false,
"max_window_bits": 11,
"memory_level": 4
}
}
}
}
},
"web": {
"paths": {
"/": {
"type": "static",
}
}
}
}
]
}
]
}

The solution suggested by the crossbar.io group was to use the progressive result option of the RPC.
A full working example is located at https://github.com/crossbario/autobahn-python/tree/master/examples/twisted/wamp/rpc/progress
In my code I had to add a the chunking of the result in the backend
step = 10000
if details.progress and len(res) > step:
for i in xrange(0, len(res), step):
details.progress(res[i:i+step])
else:
defer.returnValue(res)
And to the caller
res = yield self.call(
u'data.get'
options=CallOptions(
on_progress=partial(on_progress, res=res_list)
)
)
Where my function on_progress adds the chunks to a result list
def on_progress(x, res):
res.append(x)
Picking the right chunk size will do the trick.

Related

search a list in api/ format url seach for api

I want to generate 6 random numbers for pokemon api ID.
Put in list.
Then use the 6 numbers in url search.
The url doesn't recognise the list.
I need to convert the list to numbers. I'm not sure how to format them into the url.
import random
import requests
pokemon_ID = []
# pokemon_ID_add = str(pokemon_ID)[1:-1]
# pokemon_ID2 = str(pokemon_ID)[1:-1]
for i in range(0,6):
number = random.randint(1 ,151)
while i in pokemon_ID:
number = random.randint(1, 151)
pokemon_ID.append(number)
url = 'https://pokeapi.co/api/v2/pokemon/{}/'.format(pokemon_ID)
response = requests.get(url)
pokemon = response.json()
print(pokemon)
You can use loop to iterate over random IDs and store the result to a list:
import json
import random
import requests
url = "https://pokeapi.co/api/v2/pokemon/{}/"
random_pokemon_ids = [random.randint(1, 151) for i in range(6)]
result = []
for id_ in random_pokemon_ids:
pokemon = requests.get(url.format(id_)).json()
result.append(pokemon)
# pretty print the result:
print(json.dumps(result, indent=4))
Prints:
[
{
"abilities": [
{
"ability": {
"name": "rock-head",
"url": "https://pokeapi.co/api/v2/ability/69/"
},
"is_hidden": false,
"slot": 1
},
{
"ability": {
"name": "lightning-rod",
"url": "https://pokeapi.co/api/v2/ability/31/"
},
"is_hidden": false,
"slot": 2
},
{
"ability": {
"name": "battle-armor",
"url": "https://pokeapi.co/api/v2/ability/4/"
},
"is_hidden": true,
"slot": 3
}
],
"base_experience": 64,
"forms": [
{
"name": "cubone",
"url": "https://pokeapi.co/api/v2/pokemon-form/104/"
}
],
...

Flask API - create nested json response group by field single table

I have a basic API setup to do a basic Post and Get from a single table. I want to create a nested array though grouping by force_element_type
model.py
from db import db
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy import text as sa_text
class ForceElementModel(db.Model):
__tablename__ = 'force_element'
__table_args__ = {'schema': 'force_element'}
force_element_id = db.Column(UUID(as_uuid=True), primary_key=True, server_default=sa_text("uuid_generate_v4()"))
name = db.Column(db.String(100), nullable=False)
force_element_type = db.Column(db.String(20), nullable=False)
def __init__(self, name, force_element_type):
self.name = name
self.force_element_type = force_element_type
def json(self):
return {'name': self.name, 'force_element_type': self.force_element_type}
#classmethod
def find_by_name(cls, name):
return cls.query.filter_by(name=name).first() # simple TOP 1 select
def save_to_db(self): # Upserting data
db.session.add(self)
db.session.commit() # Balla
def delete_from_db(self):
db.session.delete(self)
db.session.commit()
resource.py
from flask_restful import Resource, reqparse
#from flask_jwt import jwt_required
from models.force_element import ForceElementModel
class ForceElement(Resource):
parser = reqparse.RequestParser() # only allow price changes, no name changes allowed
parser.add_argument('force_element_type', type=str, required=True, help='This field cannot be left blank')
##jwt_required()
def post(self, name):
if ForceElementModel.find_by_name(name):
return {'message': "An Force Element with name '{}' already exists.".format(name)}, 400
data = ForceElement.parser.parse_args()
force_element = ForceElementModel(name, data['force_element_type'])
try:
force_element.save_to_db()
except:
return {"message": "An error occurred inserting the item."}, 500
return force_element.json(), 201
class ForceElementList(Resource):
##jwt_required()
def get(self):
return {'force_elements': [force_element.json() for force_element in ForceElementModel.query.all()]}
class ForceElementType(Resource):
##jwt_required()
def get(self):
The GET endpoint using ForceElementList returns
{
"force_elements": [
{
"name": "San Antonio",
"force_element_type": "ship"
},
{
"name": "Nimitz",
"force_element_type": "ship"
},
{
"name": "Nimitz- Starboard",
"force_element_type": "Crew"
},
{
"name": "Nimitz- Port",
"force_element_type": "Crew"
}
]
}
I don't know how to group by force_element_type and return
[
"ship": [
{
"name": "San Antonio",
"force_element_id": "xxx1"
},
{
"name": "Nimitz",
"force_element_id": "xxx2"
}],
"crew": [
{
"name": "Nimitz- Starboard",
"force_element_id": "yyy1"
},
{
"name": "Nimitz- Port",
"force_element_id": "yyy2"
}
]
]
How do I create this separate andpoint?
OK I got there, here is how I did it. Is there a better way?
Lesson one use an online parser to check the json format this is what I was actually aiming for and the square braket at then start had me scratching my head for a while
{
"ship": [
{
"name": "San Antonio",
"force_element_id": "xxx1"
},
{
"name": "Nimitz",
"force_element_id": "xxx2"
}],
"crew": [
{
"name": "Nimitz- Starboard",
"force_element_id": "yyy1"
},
{
"name": "Nimitz- Port",
"force_element_id": "yyy2"
}]
}
This code creates the correct format for the output
class ForceElementType(Resource):
##jwt_required()
def get(self):
types = {}
force_elements = ForceElementModel.query.order_by(ForceElementModel.force_element_type.desc()).all()
for force_element in force_elements:
nested = {'name': force_element.name, 'force_element_id': str(force_element.force_element_id)}
print(nested)
if not force_element.force_element_type in types:
types[force_element.force_element_type] = []
types[force_element.force_element_type].append(nested)
response = types

how to replace null to 'null' in a python dict

This might be a silly question. I would like to replace from null to 'null' in a dict in python.
mydict = {"headers": {"ai5": "8fa683e59c02c04cb781ac689686db07", "debug": null, "random": **null**, "sdkv": "7.6"}, "post": {"event": "ggstart", "ts": "1462759195259"}, "params": {}, "bottle": {"timestamp": "2016-05-09 02:00:00.004906", "game_id": "55107008"}}
I can't do any string operation in python as it throws error:
NameError: name 'null' is not defined
I have a huge file of 18000 this type of data, and I can't do it manually.
Please help.
You dont need to replace anything. Just load the file and convert the data to dict.
import json
import pprint
with open('x.json') as f:
data = json.load(f)
pprint.pprint(data)
Input (x.json)
{
"headers": {
"ai5": "8fa683e59c02c04cb781ac689686db07",
"debug": null,
"random": null,
"sdkv": "7.6"
},
"post": {
"event": "ggstart",
"ts": "1462759195259"
},
"params": {},
"bottle": {
"timestamp": "2016-05-09 02:00:00.004906",
"game_id": "55107008"
}
}
Output
{'bottle': {'game_id': '55107008', 'timestamp': '2016-05-09 02:00:00.004906'},
'headers': {'ai5': '8fa683e59c02c04cb781ac689686db07',
'debug': None,
'random': None,
'sdkv': '7.6'},
'params': {},
'post': {'event': 'ggstart', 'ts': '1462759195259'}}
You can try something like this. It replaces every null with 'null' and stores it into new file.
Storing it into same file or writing it to new file and then replacing it with original one is upto you.
import re
f_handle = open("test.txt","r+")
f_2 = open("result.txt","w+")
for f_string in f_handle.readlines():
print(f_string)
f_result = re.sub(r'(?:^|\W)null(?:$|\W)',"'null'",f_string)
print(f_result)
f_2.write(f_result)
f_handle.close()
f_2.close()
import json
x = json.loads(mydict)
and then null in your dict will change to be "".

How to create a personalized response with dialoglow?

I am trying to build a chatbot with dialogflow which is able to advice books for users. But I really don't find how to build the responses in a python file. I mean, I want that if the intent is "search-book", then it will send few books depending on the gender the user said. Actually, my python file is there :
# -*- coding:utf-8 -*-
# !/usr/bin/env python
# Copyright 2017 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import os
import sys
import json
import yaml
try:
import apiai
except ImportError:
sys.path.append(
os.path.join(
os.path.dirname(os.path.realpath(__file__)),
os.pardir,
os.pardir
)
)
import apiai
CLIENT_ACCESS_TOKEN = '197ef97149d449a6962ba5bd5e488607'
def yaml_loader(filepath):
"""Loads a yaml file"""
with open(filepath, 'r') as file:
data = yaml.load(file)
return data
def yaml_dump(filepath, data):
"""Dumps data to a yaml file"""
with open(filepath, "w") as file:
yaml.dump(data, file)
def main():
ai = apiai.ApiAI(CLIENT_ACCESS_TOKEN)
filepath = "proxy.yaml"
data = yaml_loader(filepath)
proxy = data.get('proxy')
for proxy_protocol, proxy_host in proxy.items():
os.environ["" + proxy_protocol] = "" + proxy_host
while True:
print(u"> ", end=u"")
user_message = input()
if user_message == u"exit":
break
request = ai.text_request()
request.query = user_message
response = json.loads(request.getresponse().read())
result = response['result']
action = result.get('action')
actionIncomplete = result.get('actionIncomplete', False)
print(u"< %s" % response['result']['fulfillment']['speech'])
if action is not None:
if action == "search-book":
parameters = result['parameters']
text = parameters.get('text')
Gender = parameters.get('Gender')
print (
'text: %s, Gender: %s' %
(
text if text else "null",
Gender if Gender else "null",
)
)
if __name__ == '__main__':
main()
For Google books API I found this, and it is working:
https://github.com/hoffmann/googlebooks
I already have created an Entity called "gender" and an intent named "search-book"
what you have to do is you need to implement a webhook (a web service) for your intent.
set the url to your webhook here
then go to your intent and enable the webhook for the intent
so when some one query for your intent your webhook will get a post request with bellow josn body
{
"responseId": "ea3d77e8-ae27-41a4-9e1d-174bd461b68c",
"session": "projects/your-agents-project-id/agent/sessions/88d13aa8-2999-4f71-b233-39cbf3a824a0",
"queryResult": {
"queryText": "user's original query to your agent",
"parameters": {
"param": "param value"
},
"allRequiredParamsPresent": true,
"fulfillmentText": "Text defined in Dialogflow's console for the intent that was matched",
"fulfillmentMessages": [
{
"text": {
"text": [
"Text defined in Dialogflow's console for the intent that was matched"
]
}
}
],
"outputContexts": [
{
"name": "projects/your-agents-project-id/agent/sessions/88d13aa8-2999-4f71-b233-39cbf3a824a0/contexts/generic",
"lifespanCount": 5,
"parameters": {
"param": "param value"
}
}
],
"intent": {
"name": "projects/your-agents-project-id/agent/intents/29bcd7f8-f717-4261-a8fd-2d3e451b8af8",
"displayName": "Matched Intent Name"
},
"intentDetectionConfidence": 1,
"diagnosticInfo": {},
"languageCode": "en"
},
"originalDetectIntentRequest": {}
}
you can get the intent name
body.queryResult.intent.displayName
also you can get the parameters
body.queryResult.parameters
since now you have the parameters you need, you can call to your googlebooks api and send the result back to the google dialogflow
the responce json should be something like this
{
"fulfillmentText": "This is a text response",
"fulfillmentMessages": [
{
"card": {
"title": "card title",
"subtitle": "card text",
"imageUri": "https://assistant.google.com/static/images/molecule/Molecule-Formation-stop.png",
"buttons": [
{
"text": "button text",
"postback": "https://assistant.google.com/"
}
]
}
}
],
"source": "example.com",
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "this is a simple response"
}
}
]
}
},
"facebook": {
"text": "Hello, Facebook!"
},
"slack": {
"text": "This is a text response for Slack."
}
},
"outputContexts": [
{
"name": "projects/${PROJECT_ID}/agent/sessions/${SESSION_ID}/contexts/context name",
"lifespanCount": 5,
"parameters": {
"param": "param value"
}
}
],
"followupEventInput": {
"name": "event name",
"languageCode": "en-US",
"parameters": {
"param": "param value"
}
}
}
some thing i have done with node js
'use strict';
const http = require('http');
exports.bookWebhook = (req, res) => {
if (req.body.queryResult.intent.displayName == "search-book") {
res.json({
'fulfillmentText': getbookDetails(req.body.queryResult.parameters.gender)
});
}
};
function getbookDetails(gender) {
//do you api search for book here
return "heard this book is gooooood";
}
in getbookDetails function you can call to you api get the book and format the string and return the string.. same should be applied to python as we. only the syntax will be different.

Logging RotatingFileHandler not rotating

I have implemented a custom RotatingFileHandler:
class FreezeAwareFileHandler(RotatingFileHandler):
def emit(self, record):
try:
msg = self.format(record)
stream = self.stream
stream.write(msg)
stream.write('\n')
self.flush()
except (KeyboardInterrupt, SystemExit): #pragma: no cover
raise
except:
self.handleError(record)
And I have this configuration json file (I have tried even with yaml and specifying the configuration through the class methods):
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
}
},
"handlers": {
"freeze_aware_file_handler": {
"class": "Logging.MyHandler",
"formatter": "standard",
"level": "INFO",
"filename": "logs\\MyLog.log",
"maxBytes": 1024,
"backupCount": 10,
"encoding": "utf8"
}
},
"loggers": {
"my_module": {
"level": "INFO",
"handlers": ["my_handler"],
"propagate": "no"
}
},
"root": {
"level": "INFO",
"handlers": ["my_handler"]
}
}
And this is the code I use for initializing:
if os.path.exists(path):
with open(path, 'rt') as f:
config_json = json.load(f)
logging.config.dictConfig(config_json)
logger = logging.getLogger("my_handler")
I can normally log to the specified file but it is never rotating.
Someone know why I'm having this behavior?
I'm using Python 3.5
It turned out the problem was really simple.
For the rotating file handler it is needed to explicitly call the shouldRollver and doRollover methods in the emit of the base class.
def emit(self, record):
try:
if self.shouldRollover(record):
self.doRollover()
...
do any custom actions here
...
except:
self.handleError(record)

Resources