Merge pull request #513 from a01200356/wolframalpha

WolframAlpha infobox
This commit is contained in:
Adam Tauber 2016-03-01 10:41:56 +01:00
commit 308613e586
11 changed files with 450 additions and 315 deletions

View File

@ -1,43 +1,60 @@
# Wolfram Alpha (Maths) # Wolfram Alpha (Science)
# #
# @website http://www.wolframalpha.com # @website https://www.wolframalpha.com
# @provide-api yes (http://api.wolframalpha.com/v2/) # @provide-api yes (https://api.wolframalpha.com/v2/)
# #
# @using-api yes # @using-api yes
# @results XML # @results XML
# @stable yes # @stable yes
# @parse result # @parse url, infobox
from urllib import urlencode from urllib import urlencode
from lxml import etree from lxml import etree
from re import search
# search-url # search-url
base_url = 'http://api.wolframalpha.com/v2/query' search_url = 'https://api.wolframalpha.com/v2/query?appid={api_key}&{query}'
search_url = base_url + '?appid={api_key}&{query}&format=plaintext' site_url = 'https://www.wolframalpha.com/input/?{query}'
site_url = 'http://www.wolframalpha.com/input/?{query}'
api_key = '' # defined in settings.yml api_key = '' # defined in settings.yml
# xpath variables # xpath variables
failure_xpath = '/queryresult[attribute::success="false"]' failure_xpath = '/queryresult[attribute::success="false"]'
answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext' answer_xpath = '//pod[attribute::primary="true"]/subpod/plaintext'
input_xpath = '//pod[starts-with(attribute::title, "Input")]/subpod/plaintext' input_xpath = '//pod[starts-with(attribute::id, "Input")]/subpod/plaintext'
pods_xpath = '//pod'
subpods_xpath = './subpod'
pod_id_xpath = './@id'
pod_title_xpath = './@title'
plaintext_xpath = './plaintext'
image_xpath = './img'
img_src_xpath = './@src'
img_alt_xpath = './@alt'
# pods to display as image in infobox
# this pods do return a plaintext, but they look better and are more useful as images
image_pods = {'VisualRepresentation',
'Illustration'}
# do search-request # do search-request
def request(query, params): def request(query, params):
params['url'] = search_url.format(query=urlencode({'input': query}), params['url'] = search_url.format(query=urlencode({'input': query}),
api_key=api_key) api_key=api_key)
params['headers']['Referer'] = site_url.format(query=urlencode({'i': query}))
return params return params
# replace private user area characters to make text legible # replace private user area characters to make text legible
def replace_pua_chars(text): def replace_pua_chars(text):
pua_chars = {u'\uf74c': 'd', pua_chars = {u'\uf522': u'\u2192', # rigth arrow
u'\uf74d': u'\u212f', u'\uf7b1': u'\u2115', # set of natural numbers
u'\uf74e': 'i', u'\uf7b4': u'\u211a', # set of rational numbers
u'\uf7d9': '='} u'\uf7b5': u'\u211d', # set of real numbers
u'\uf7bd': u'\u2124', # set of integer numbers
u'\uf74c': 'd', # differential
u'\uf74d': u'\u212f', # euler's number
u'\uf74e': 'i', # imaginary number
u'\uf7d9': '='} # equals sign
for k, v in pua_chars.iteritems(): for k, v in pua_chars.iteritems():
text = text.replace(k, v) text = text.replace(k, v)
@ -55,23 +72,51 @@ def response(resp):
if search_results.xpath(failure_xpath): if search_results.xpath(failure_xpath):
return [] return []
# parse answers
answers = search_results.xpath(answer_xpath)
if answers:
for answer in answers:
answer = replace_pua_chars(answer.text)
results.append({'answer': answer})
# if there's no input section in search_results, check if answer has the input embedded (before their "=" sign)
try: try:
query_input = search_results.xpath(input_xpath)[0].text infobox_title = search_results.xpath(input_xpath)[0].text
except IndexError: except:
query_input = search(u'([^\uf7d9]+)', answers[0].text).group(1) infobox_title = None
pods = search_results.xpath(pods_xpath)
result_chunks = []
for pod in pods:
pod_id = pod.xpath(pod_id_xpath)[0]
pod_title = pod.xpath(pod_title_xpath)[0]
subpods = pod.xpath(subpods_xpath)
if not subpods:
continue
# Appends either a text or an image, depending on which one is more suitable
for subpod in subpods:
content = subpod.xpath(plaintext_xpath)[0].text
image = subpod.xpath(image_xpath)
if content and pod_id not in image_pods:
# if no input pod was found, title is first plaintext pod
if not infobox_title:
infobox_title = content
content = replace_pua_chars(content)
result_chunks.append({'label': pod_title, 'value': content})
elif image:
result_chunks.append({'label': pod_title,
'image': {'src': image[0].xpath(img_src_xpath)[0],
'alt': image[0].xpath(img_alt_xpath)[0]}})
if not result_chunks:
return []
# append infobox
results.append({'infobox': infobox_title,
'attributes': result_chunks,
'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
# append link to site # append link to site
result_url = site_url.format(query=urlencode({'i': query_input.encode('utf-8')})) results.append({'url': resp.request.headers['Referer'].decode('utf8'),
results.append({'url': result_url, 'title': 'Wolfram|Alpha',
'title': query_input + " - Wolfram|Alpha"}) 'content': infobox_title})
return results return results

View File

@ -1,26 +1,26 @@
# WolframAlpha (Maths) # Wolfram|Alpha (Science)
# #
# @website http://www.wolframalpha.com/ # @website https://www.wolframalpha.com/
# @provide-api yes (http://api.wolframalpha.com/v2/) # @provide-api yes (https://api.wolframalpha.com/v2/)
# #
# @using-api no # @using-api no
# @results HTML # @results JSON
# @stable no # @stable no
# @parse answer # @parse url, infobox
from cgi import escape from cgi import escape
from json import loads from json import loads
from time import time from time import time
from urllib import urlencode from urllib import urlencode
from lxml.etree import XML
from searx.poolrequests import get as http_get from searx.poolrequests import get as http_get
# search-url # search-url
url = 'https://www.wolframalpha.com/' url = 'https://www.wolframalpha.com/'
search_url = url + 'input/?{query}'
search_url = url + 'input/json.jsp'\ search_url = url + 'input/json.jsp'\
'?async=true'\ '?async=false'\
'&banners=raw'\ '&banners=raw'\
'&debuggingdata=false'\ '&debuggingdata=false'\
'&format=image,plaintext,imagemap,minput,moutput'\ '&format=image,plaintext,imagemap,minput,moutput'\
@ -33,13 +33,17 @@ search_url = url + 'input/json.jsp'\
'&sponsorcategories=true'\ '&sponsorcategories=true'\
'&statemethod=deploybutton' '&statemethod=deploybutton'
# xpath variables referer_url = url + 'input/?{query}'
scripts_xpath = '//script'
title_xpath = '//title'
failure_xpath = '//p[attribute::class="pfail"]'
token = {'value': '', token = {'value': '',
'last_updated': None} 'last_updated': None}
# pods to display as image in infobox
# this pods do return a plaintext, but they look better and are more useful as images
image_pods = {'VisualRepresentation',
'Illustration',
'Symbol'}
# seems, wolframalpha resets its token in every hour # seems, wolframalpha resets its token in every hour
def obtain_token(): def obtain_token():
@ -62,13 +66,15 @@ def request(query, params):
if time() - token['last_updated'] > 3600: if time() - token['last_updated'] > 3600:
obtain_token() obtain_token()
params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value']) params['url'] = search_url.format(query=urlencode({'input': query}), token=token['value'])
params['headers']['Referer'] = 'https://www.wolframalpha.com/input/?i=' + query params['headers']['Referer'] = referer_url.format(query=urlencode({'i': query}))
return params return params
# get response from search-request # get response from search-request
def response(resp): def response(resp):
results = []
resp_json = loads(resp.text) resp_json = loads(resp.text)
if not resp_json['queryresult']['success']: if not resp_json['queryresult']['success']:
@ -76,20 +82,35 @@ def response(resp):
# TODO handle resp_json['queryresult']['assumptions'] # TODO handle resp_json['queryresult']['assumptions']
result_chunks = [] result_chunks = []
infobox_title = None
for pod in resp_json['queryresult']['pods']: for pod in resp_json['queryresult']['pods']:
pod_id = pod.get('id', '')
pod_title = pod.get('title', '') pod_title = pod.get('title', '')
if 'subpods' not in pod: if 'subpods' not in pod:
continue continue
if pod_id == 'Input' or not infobox_title:
infobox_title = pod['subpods'][0]['plaintext']
for subpod in pod['subpods']: for subpod in pod['subpods']:
if 'img' in subpod: if subpod['plaintext'] != '' and pod_id not in image_pods:
result_chunks.append(u'<p>{0}<br /><img src="{1}" alt="{2}" /></p>' # append unless it's not an actual answer
.format(escape(pod_title or subpod['img']['alt']), if subpod['plaintext'] != '(requires interactivity)':
escape(subpod['img']['src']), result_chunks.append({'label': pod_title, 'value': subpod['plaintext']})
escape(subpod['img']['alt'])))
elif 'img' in subpod:
result_chunks.append({'label': pod_title, 'image': subpod['img']})
if not result_chunks: if not result_chunks:
return [] return []
return [{'url': resp.request.headers['Referer'].decode('utf-8'), results.append({'infobox': infobox_title,
'title': 'Wolframalpha', 'attributes': result_chunks,
'content': ''.join(result_chunks)}] 'urls': [{'title': 'Wolfram|Alpha', 'url': resp.request.headers['Referer'].decode('utf8')}]})
results.append({'url': resp.request.headers['Referer'].decode('utf8'),
'title': 'Wolfram|Alpha',
'content': infobox_title})
return results

View File

@ -310,8 +310,8 @@ engines:
shortcut : wa shortcut : wa
# You can use the engine using the official stable API, but you need an API key # You can use the engine using the official stable API, but you need an API key
# See : http://products.wolframalpha.com/api/ # See : http://products.wolframalpha.com/api/
# engine : wolframalpha_api # engine : wolframalpha_api
# api_key: 'apikey' # required! # api_key: '' # required!
engine : wolframalpha_noapi engine : wolframalpha_noapi
timeout: 6.0 timeout: 6.0
categories : science categories : science

File diff suppressed because one or more lines are too long

View File

@ -476,6 +476,7 @@ color: @color-font-light;
margin: 0px 2px 5px 5px; margin: 0px 2px 5px 5px;
padding: 0px 2px 2px; padding: 0px 2px 2px;
max-width: 21em; max-width: 21em;
word-wrap: break-word;
.infobox { .infobox {
margin: 10px 0 10px; margin: 10px 0 10px;
@ -485,7 +486,7 @@ color: @color-font-light;
/* box-shadow: 0px 0px 5px #CCC; */ /* box-shadow: 0px 0px 5px #CCC; */
img { img {
max-width: 20em; max-width: 90%;
max-heigt: 12em; max-heigt: 12em;
display: block; display: block;
margin: 5px; margin: 5px;
@ -497,7 +498,7 @@ color: @color-font-light;
} }
table { table {
width: auto; table-layout: fixed;
td { td {
vertical-align: top; vertical-align: top;

View File

@ -17,7 +17,7 @@ input[type=checkbox]:not(:checked)+.label_hide_if_not_checked,input[type=checkbo
.result_download{margin-right:5px} .result_download{margin-right:5px}
#pagination{margin-top:30px;padding-bottom:50px} #pagination{margin-top:30px;padding-bottom:50px}
.label-default{color:#aaa;background:#fff} .label-default{color:#aaa;background:#fff}
.infobox .infobox_part{margin-bottom:20px;word-wrap:break-word} .infobox .infobox_part{margin-bottom:20px;word-wrap:break-word;table-layout:fixed}
.infobox .infobox_part:last-child{margin-bottom:0} .infobox .infobox_part:last-child{margin-bottom:0}
.search_categories{margin:10px 0;text-transform:capitalize} .search_categories{margin:10px 0;text-transform:capitalize}
.cursor-text{cursor:text !important} .cursor-text{cursor:text !important}

View File

@ -1,7 +1,8 @@
.infobox { .infobox {
.infobox_part { .infobox_part {
margin-bottom: 20px; margin-bottom: 20px;
word-wrap: break-word; word-wrap: break-word;
table-layout: fixed;
} }
.infobox_part:last-child { .infobox_part:last-child {

View File

@ -7,7 +7,14 @@
<div class="attributes"> <div class="attributes">
<table> <table>
{% for attribute in infobox.attributes %} {% for attribute in infobox.attributes %}
<tr><td>{{ attribute.label }}</td><td>{{ attribute.value }}</td></tr> <tr>
<td>{{ attribute.label }}</td>
{% if attribute.image %}
<td><img src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
{% else %}
<td>{{ attribute.value }}</td>
{% endif %}
</tr>
{% endfor %} {% endfor %}
</table> </table>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="panel panel-default infobox"> <div class="panel panel-default infobox">
<div class="panel-heading"> <div class="panel-heading">
<h4 class="panel-title">{{ infobox.infobox }}</h4> <h4 class="panel-title infobox_part">{{ infobox.infobox }}</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %} {% if infobox.img_src %}<img class="img-responsive center-block infobox_part" src="{{ image_proxify(infobox.img_src) }}" alt="{{ infobox.infobox }}" />{% endif %}
@ -11,7 +11,11 @@
{% for attribute in infobox.attributes %} {% for attribute in infobox.attributes %}
<tr> <tr>
<td>{{ attribute.label }}</td> <td>{{ attribute.label }}</td>
{% if attribute.image %}
<td><img class="img-responsive" src="{{ image_proxify(attribute.image.src) }}" alt="{{ attribute.image.alt }}" /></td>
{% else %}
<td>{{ attribute.value }}</td> <td>{{ attribute.value }}</td>
{% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from collections import defaultdict from collections import defaultdict
import mock import mock
from requests import Request
from searx.engines import wolframalpha_api from searx.engines import wolframalpha_api
from searx.testing import SearxTestCase from searx.testing import SearxTestCase
@ -9,17 +10,17 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
def test_request(self): def test_request(self):
query = 'test_query' query = 'test_query'
api_key = 'XXXXXX-XXXXXXXXXX'
dicto = defaultdict(dict) dicto = defaultdict(dict)
dicto['api_key'] = api_key
params = wolframalpha_api.request(query, dicto) params = wolframalpha_api.request(query, dicto)
# TODO: test api_key
self.assertIn('url', params) self.assertIn('url', params)
self.assertIn('https://api.wolframalpha.com/v2/query?', params['url'])
self.assertIn(query, params['url']) self.assertIn(query, params['url'])
self.assertIn('wolframalpha.com', params['url']) self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
self.assertIn('api_key', params) def test_replace_pua_chars(self):
self.assertIn(api_key, params['api_key']) self.assertEqual('i', wolframalpha_api.replace_pua_chars(u'\uf74e'))
def test_response(self): def test_response(self):
self.assertRaises(AttributeError, wolframalpha_api.response, None) self.assertRaises(AttributeError, wolframalpha_api.response, None)
@ -27,281 +28,137 @@ class TestWolframAlphaAPIEngine(SearxTestCase):
self.assertRaises(AttributeError, wolframalpha_api.response, '') self.assertRaises(AttributeError, wolframalpha_api.response, '')
self.assertRaises(AttributeError, wolframalpha_api.response, '[]') self.assertRaises(AttributeError, wolframalpha_api.response, '[]')
referer_url = 'referer_url'
request = Request(headers={'Referer': referer_url})
# test failure
xml = '''<?xml version='1.0' encoding='UTF-8'?> xml = '''<?xml version='1.0' encoding='UTF-8'?>
<queryresult success='false' error='false' /> <queryresult success='false' error='false' />
''' '''
# test failure
response = mock.Mock(content=xml) response = mock.Mock(content=xml)
self.assertEqual(wolframalpha_api.response(response), []) self.assertEqual(wolframalpha_api.response(response), [])
# test basic case
xml = """<?xml version='1.0' encoding='UTF-8'?> xml = """<?xml version='1.0' encoding='UTF-8'?>
<queryresult success='true' <queryresult success='true'
error='false' error='false'
numpods='6' numpods='3'
datatypes='' datatypes='Math'
timedout='' id='queryresult_id'
timedoutpods='' host='http://www4c.wolframalpha.com'
timing='0.684' related='related_url'
parsetiming='0.138'
parsetimedout='false'
recalculate=''
id='MSPa416020a7966dachc463600000f9c66cc21444cfg'
host='http://www3.wolframalpha.com'
server='6'
related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?...'
version='2.6'> version='2.6'>
<pod title='Input' <pod title='Input'
scanner='Identity' scanner='Identity'
id='Input' id='Input'
position='100' numsubpods='1'>
error='false' <subpod title=''>
numsubpods='1'> <img src='input_img_src.gif'
<subpod title=''> alt='input_img_alt'
<plaintext>sqrt(-1)</plaintext> title='input_img_title' />
</subpod> <plaintext>input_plaintext</plaintext>
</pod> </subpod>
<pod title='Result' </pod>
scanner='Simplification' <pod title='Result'
id='Result' scanner='Simplification'
position='200' id='Result'
error='false'
numsubpods='1'
primary='true'>
<subpod title=''>
<plaintext></plaintext>
</subpod>
<states count='1'>
<state name='Step-by-step solution'
input='Result__Step-by-step solution' />
</states>
</pod>
<pod title='Polar coordinates'
scanner='Numeric'
id='PolarCoordinates'
position='300'
error='false'
numsubpods='1'>
<subpod title=''>
<plaintext>r1 (radius), θ90° (angle)</plaintext>
</subpod>
</pod>
<pod title='Position in the complex plane'
scanner='Numeric'
id='PositionInTheComplexPlane'
position='400'
error='false'
numsubpods='1'>
<subpod title=''>
<plaintext></plaintext>
</subpod>
</pod>
<pod title='All 2nd roots of -1'
scanner='RootsOfUnity'
id=''
position='500'
error='false'
numsubpods='2'>
<subpod title=''>
<plaintext> (principal root)</plaintext>
</subpod>
<subpod title=''>
<plaintext>-</plaintext>
</subpod>
</pod>
<pod title='Plot of all roots in the complex plane'
scanner='RootsOfUnity'
id='PlotOfAllRootsInTheComplexPlane'
position='600'
error='false'
numsubpods='1'>
<subpod title=''>
<plaintext></plaintext>
</subpod>
</pod>
</queryresult>
"""
# test private user area char in response
response = mock.Mock(content=xml)
results = wolframalpha_api.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 2)
self.assertIn('i', results[0]['answer'])
self.assertIn('sqrt(-1) - Wolfram|Alpha', results[1]['title'])
self.assertEquals('http://www.wolframalpha.com/input/?i=sqrt%28-1%29', results[1]['url'])
xml = """<?xml version='1.0' encoding='UTF-8'?>
<queryresult success='true'
error='false'
numpods='2'
datatypes=''
timedout=''
timedoutpods=''
timing='1.286'
parsetiming='0.255'
parsetimedout='false'
recalculate=''
id='MSPa195222ad740ede5214h30000480ca61h003d3gd6'
host='http://www3.wolframalpha.com'
server='20'
related='http://www3.wolframalpha.com/api/v2/relatedQueries.jsp?id=...'
version='2.6'>
<pod title='Indefinite integral'
scanner='Integral'
id='IndefiniteIntegral'
position='100'
error='false'
numsubpods='1' numsubpods='1'
primary='true'> primary='true'>
<subpod title=''> <subpod title=''>
<plaintext>1/xxlog(x)+constant</plaintext> <img src='result_img_src.gif'
</subpod> alt='result_img_alt'
<states count='1'> title='result_img_title' />
<state name='Step-by-step solution' <plaintext>result_plaintext</plaintext>
input='IndefiniteIntegral__Step-by-step solution' /> </subpod>
</states>
<infos count='1'>
<info text='log(x) is the natural logarithm'>
<link url='http://reference.wolfram.com/mathematica/ref/Log.html'
text='Documentation'
title='Mathematica' />
<link url='http://functions.wolfram.com/ElementaryFunctions/Log'
text='Properties'
title='Wolfram Functions Site' />
<link url='http://mathworld.wolfram.com/NaturalLogarithm.html'
text='Definition'
title='MathWorld' />
</info>
</infos>
</pod> </pod>
<pod title='Plots of the integral' <pod title='Manipulatives illustration'
scanner='Integral' scanner='Arithmetic'
id='Plot' id='Illustration'
position='200' numsubpods='1'>
error='false' <subpod title=''>
numsubpods='2'> <img src='illustration_img_src.gif'
<subpod title=''> alt='illustration_img_alt' />
<plaintext></plaintext> <plaintext>illustration_plaintext</plaintext>
<states count='1'> </subpod>
<statelist count='2'
value='Complex-valued plot'
delimiters=''>
<state name='Complex-valued plot'
input='Plot__1_Complex-valued plot' />
<state name='Real-valued plot'
input='Plot__1_Real-valued plot' />
</statelist>
</states>
</subpod>
<subpod title=''>
<plaintext></plaintext>
<states count='1'>
<statelist count='2'
value='Complex-valued plot'
delimiters=''>
<state name='Complex-valued plot'
input='Plot__2_Complex-valued plot' />
<state name='Real-valued plot'
input='Plot__2_Real-valued plot' />
</statelist>
</states>
</subpod>
</pod> </pod>
<assumptions count='1'> </queryresult>
<assumption type='Clash'
word='integral'
template='Assuming &quot;${word}&quot; is ${desc1}. Use as ${desc2} instead'
count='2'>
<value name='IntegralsWord'
desc='an integral'
input='*C.integral-_*IntegralsWord-' />
<value name='MathematicalFunctionIdentityPropertyClass'
desc='a function property'
input='*C.integral-_*MathematicalFunctionIdentityPropertyClass-' />
</assumption>
</assumptions>
</queryresult>
""" """
# test integral response = mock.Mock(content=xml, request=request)
response = mock.Mock(content=xml)
results = wolframalpha_api.response(response) results = wolframalpha_api.response(response)
self.assertEqual(type(results), list) self.assertEqual(type(results), list)
self.assertEqual(len(results), 2) self.assertEqual(len(results), 2)
self.assertIn('log(x)+c', results[0]['answer']) self.assertEqual('input_plaintext', results[0]['infobox'])
self.assertIn('∫1/xx - Wolfram|Alpha'.decode('utf-8'), results[1]['title'])
self.assertEquals('http://www.wolframalpha.com/input/?i=%E2%88%AB1%2Fx%EF%9D%8Cx', results[1]['url'])
self.assertEqual(len(results[0]['attributes']), 3)
self.assertEqual('Input', results[0]['attributes'][0]['label'])
self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
self.assertEqual('Result', results[0]['attributes'][1]['label'])
self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
self.assertEqual(len(results[0]['urls']), 1)
self.assertEqual(referer_url, results[0]['urls'][0]['url'])
self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
self.assertEqual(referer_url, results[1]['url'])
self.assertEqual('Wolfram|Alpha', results[1]['title'])
# test calc
xml = """<?xml version='1.0' encoding='UTF-8'?> xml = """<?xml version='1.0' encoding='UTF-8'?>
<queryresult success='true' <queryresult success='true'
error='false' error='false'
numpods='4' numpods='2'
datatypes='Solve' datatypes=''
timedout=''
timedoutpods=''
timing='0.79'
parsetiming='0.338'
parsetimedout='false' parsetimedout='false'
recalculate='' id='queryresult_id'
id='MSPa7481f7i06d25h3deh2900004810i3a78d9b4fdc'
host='http://www5b.wolframalpha.com' host='http://www5b.wolframalpha.com'
server='23' related='related_url'
related='http://www5b.wolframalpha.com/api/v2/relatedQueries.jsp?id=...' version='2.6' >
version='2.6'> <pod title='Indefinite integral'
<pod title='Input interpretation' scanner='Integral'
scanner='Identity' id='IndefiniteIntegral'
id='Input' error='false'
position='100' numsubpods='1'
error='false' primary='true'>
numsubpods='1'> <subpod title=''>
<subpod title=''> <img src='integral_image.gif'
<plaintext>solve x^2+x0</plaintext> alt='integral_img_alt'
</subpod> title='integral_img_title' />
</pod> <plaintext>integral_plaintext</plaintext>
<pod title='Results' </subpod>
scanner='Solve' </pod>
id='Result' <pod title='Plot of the integral'
position='200' scanner='Integral'
error='false' id='Plot'
numsubpods='2' error='false'
primary='true'> numsubpods='1'>
<subpod title=''> <subpod title=''>
<plaintext>x-1</plaintext> <img src='plot.gif'
</subpod> alt='plot_alt'
<subpod title=''> title='' />
<plaintext>x0</plaintext> <plaintext></plaintext>
</subpod> </subpod>
<states count='1'> </pod>
<state name='Step-by-step solution'
input='Result__Step-by-step solution' />
</states>
</pod>
<pod title='Root plot'
scanner='Solve'
id='RootPlot'
position='300'
error='false'
numsubpods='1'>
<subpod title=''>
<plaintext></plaintext>
</subpod>
</pod>
<pod title='Number line'
scanner='Solve'
id='NumberLine'
position='400'
error='false'
numsubpods='1'>
<subpod title=''>
<plaintext></plaintext>
</subpod>
</pod>
</queryresult> </queryresult>
""" """
# test ecuation with multiple answers response = mock.Mock(content=xml, request=request)
response = mock.Mock(content=xml)
results = wolframalpha_api.response(response) results = wolframalpha_api.response(response)
self.assertEqual(type(results), list) self.assertEqual(type(results), list)
self.assertEqual(len(results), 3) self.assertEqual(len(results), 2)
self.assertIn('x=-1', results[0]['answer']) self.assertEqual('integral_plaintext', results[0]['infobox'])
self.assertIn('x=0', results[1]['answer'])
self.assertIn('solve x^2+x0 - Wolfram|Alpha'.decode('utf-8'), results[2]['title']) self.assertEqual(len(results[0]['attributes']), 2)
self.assertEquals('http://www.wolframalpha.com/input/?i=solve+x%5E2%2Bx%EF%9F%990', results[2]['url']) self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
self.assertEqual(len(results[0]['urls']), 1)
self.assertEqual(referer_url, results[0]['urls'][0]['url'])
self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
self.assertEqual(referer_url, results[1]['url'])
self.assertEqual('Wolfram|Alpha', results[1]['title'])

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from collections import defaultdict from collections import defaultdict
import mock
from requests import Request
from searx.engines import wolframalpha_noapi from searx.engines import wolframalpha_noapi
from searx.testing import SearxTestCase from searx.testing import SearxTestCase
@ -9,15 +11,212 @@ class TestWolframAlphaNoAPIEngine(SearxTestCase):
def test_request(self): def test_request(self):
query = 'test_query' query = 'test_query'
dicto = defaultdict(dict) dicto = defaultdict(dict)
dicto['pageno'] = 1
params = wolframalpha_noapi.request(query, dicto) params = wolframalpha_noapi.request(query, dicto)
self.assertIn('url', params) self.assertIn('url', params)
self.assertIn('https://www.wolframalpha.com/input/json.jsp', params['url'])
self.assertIn(query, params['url']) self.assertIn(query, params['url'])
self.assertIn('wolframalpha.com', params['url']) self.assertEqual('https://www.wolframalpha.com/input/?i=test_query', params['headers']['Referer'])
def test_response(self): def test_response(self):
self.assertRaises(AttributeError, wolframalpha_noapi.response, None) self.assertRaises(AttributeError, wolframalpha_noapi.response, None)
self.assertRaises(AttributeError, wolframalpha_noapi.response, []) self.assertRaises(AttributeError, wolframalpha_noapi.response, [])
self.assertRaises(AttributeError, wolframalpha_noapi.response, '') self.assertRaises(AttributeError, wolframalpha_noapi.response, '')
self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]') self.assertRaises(AttributeError, wolframalpha_noapi.response, '[]')
# TODO
referer_url = 'referer_url'
request = Request(headers={'Referer': referer_url})
# test failure
json = '''
{"queryresult" : {
"success" : false,
"error" : false,
"numpods" : 0,
"id" : "",
"host" : "https:\/\/www5a.wolframalpha.com",
"didyoumeans" : {}
}}
'''
response = mock.Mock(text=json, request=request)
self.assertEqual(wolframalpha_noapi.response(response), [])
# test basic case
json = '''
{"queryresult" : {
"success" : true,
"error" : false,
"numpods" : 6,
"datatypes" : "Math",
"id" : "queryresult_id",
"host" : "https:\/\/www5b.wolframalpha.com",
"related" : "related_url",
"version" : "2.6",
"pods" : [
{
"title" : "Input",
"scanners" : [
"Identity"
],
"id" : "Input",
"error" : false,
"numsubpods" : 1,
"subpods" : [
{
"title" : "",
"img" : {
"src" : "input_img_src.gif",
"alt" : "input_img_alt",
"title" : "input_img_title"
},
"plaintext" : "input_plaintext",
"minput" : "input_minput"
}
]
},
{
"title" : "Result",
"scanners" : [
"Simplification"
],
"id" : "Result",
"error" : false,
"numsubpods" : 1,
"primary" : true,
"subpods" : [
{
"title" : "",
"img" : {
"src" : "result_img_src.gif",
"alt" : "result_img_alt",
"title" : "result_img_title"
},
"plaintext" : "result_plaintext",
"moutput" : "result_moutput"
}
]
},
{
"title" : "Manipulatives illustration",
"scanners" : [
"Arithmetic"
],
"id" : "Illustration",
"error" : false,
"numsubpods" : 1,
"subpods" : [
{
"title" : "",
"CDFcontent" : "Resizeable",
"img" : {
"src" : "illustration_img_src.gif",
"alt" : "illustration_img_alt",
"title" : "illustration_img_title"
},
"plaintext" : "illustration_img_plaintext"
}
]
}
]
}}
'''
response = mock.Mock(text=json, request=request)
results = wolframalpha_noapi.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 2)
self.assertEqual('input_plaintext', results[0]['infobox'])
self.assertEqual(len(results[0]['attributes']), 3)
self.assertEqual('Input', results[0]['attributes'][0]['label'])
self.assertEqual('input_plaintext', results[0]['attributes'][0]['value'])
self.assertEqual('Result', results[0]['attributes'][1]['label'])
self.assertEqual('result_plaintext', results[0]['attributes'][1]['value'])
self.assertEqual('Manipulatives illustration', results[0]['attributes'][2]['label'])
self.assertEqual('illustration_img_src.gif', results[0]['attributes'][2]['image']['src'])
self.assertEqual('illustration_img_alt', results[0]['attributes'][2]['image']['alt'])
self.assertEqual(len(results[0]['urls']), 1)
self.assertEqual(referer_url, results[0]['urls'][0]['url'])
self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
self.assertEqual(referer_url, results[1]['url'])
self.assertEqual('Wolfram|Alpha', results[1]['title'])
# test calc
json = """
{"queryresult" : {
"success" : true,
"error" : false,
"numpods" : 2,
"datatypes" : "",
"id" : "queryresult_id",
"host" : "https:\/\/www4b.wolframalpha.com",
"related" : "related_url",
"version" : "2.6",
"pods" : [
{
"title" : "Indefinite integral",
"scanners" : [
"Integral"
],
"id" : "IndefiniteIntegral",
"error" : false,
"numsubpods" : 1,
"primary" : true,
"subpods" : [
{
"title" : "",
"img" : {
"src" : "integral_img_src.gif",
"alt" : "integral_img_alt",
"title" : "integral_img_title"
},
"plaintext" : "integral_plaintext",
"minput" : "integral_minput",
"moutput" : "integral_moutput"
}
]
},
{
"title" : "Plot of the integral",
"scanners" : [
"Integral"
],
"id" : "Plot",
"error" : false,
"numsubpods" : 1,
"subpods" : [
{
"title" : "",
"img" : {
"src" : "plot.gif",
"alt" : "plot_alt",
"title" : "plot_title"
},
"plaintext" : "",
"minput" : "plot_minput"
}
]
}
]
}}
"""
response = mock.Mock(text=json, request=request)
results = wolframalpha_noapi.response(response)
self.assertEqual(type(results), list)
self.assertEqual(len(results), 2)
self.assertEqual('integral_plaintext', results[0]['infobox'])
self.assertEqual(len(results[0]['attributes']), 2)
self.assertEqual('Indefinite integral', results[0]['attributes'][0]['label'])
self.assertEqual('integral_plaintext', results[0]['attributes'][0]['value'])
self.assertEqual('Plot of the integral', results[0]['attributes'][1]['label'])
self.assertEqual('plot.gif', results[0]['attributes'][1]['image']['src'])
self.assertEqual('plot_alt', results[0]['attributes'][1]['image']['alt'])
self.assertEqual(len(results[0]['urls']), 1)
self.assertEqual(referer_url, results[0]['urls'][0]['url'])
self.assertEqual('Wolfram|Alpha', results[0]['urls'][0]['title'])
self.assertEqual(referer_url, results[1]['url'])
self.assertEqual('Wolfram|Alpha', results[1]['title'])