jenkins-bot has submitted this change and it was merged.
Change subject: WbQuantity: use Decimal rather than floats
......................................................................
WbQuantity: use Decimal rather than floats
As in Ib54be2898a035a99592cba1298f354bd1637a6b5,
this patch passes strings to the wikibase api rather than
floats. In contrast to the original patch, this change
switches all WbQuantity values to use Decimal rather than
floats, which should maintain accuracy within the error
arithmetic as well.
Tests:
- split up in different functions
- added 'scientific' case
- added Decimal case
- switched from number to strings in api (by Tobias Schönberg)
The tests were contributed by Tobias Schönberg in Ib54be28.
Change-Id: Ie1b9ffbc9c1cc4b5d843469603ebfa1eaa5fd429
Co-Author: Tobias Schönberg <tobias47n9e(a)gmail.com>
---
M pywikibot/__init__.py
M tests/wikibase_tests.py
2 files changed, 85 insertions(+), 28 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 812329f..7c83b5f 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -16,6 +16,7 @@
import re
import sys
import threading
+from decimal import Decimal
if sys.version_info[0] > 2:
from queue import Queue
@@ -470,36 +471,54 @@
_items = ('amount', 'upperBound', 'lowerBound',
'unit')
+ @staticmethod
+ def _todecimal(value):
+ """Convert a string to a Decimal for use in
WbQuantity."""
+ if isinstance(value, Decimal):
+ return value
+ return Decimal(str(value))
+
+ @staticmethod
+ def _fromdecimal(value):
+ """Convert a Decimal to a string representation suitable for
WikiBase."""
+ return format(value, "+g")
+
def __init__(self, amount, unit=None, error=None):
u"""
Create a new WbQuantity object.
@param amount: number representing this quantity
- @type amount: float
+ @type amount: string or Decimal. Other types are accepted, and converted
+ via str to Decimal.
@param unit: not used (only unit-less quantities are supported)
@param error: the uncertainty of the amount (e.g. ±1)
- @type error: float, or tuple of two floats, where the first value is
+ @type error: same as amount, or tuple of two values, where the first value is
the upper error and the second is the lower error value.
"""
if amount is None:
raise ValueError('no amount given')
if unit is None:
unit = '1'
- self.amount = amount
+
+ self.amount = self._todecimal(amount)
self.unit = unit
- upperError = lowerError = 0
- if isinstance(error, tuple):
- upperError, lowerError = error
- elif error is not None:
- upperError = lowerError = error
+
+ if error is None:
+ upperError = lowerError = Decimal(0)
+ elif isinstance(error, tuple):
+ upperError = self._todecimal(error[0])
+ lowerError = self._todecimal(error[1])
+ else:
+ upperError = lowerError = self._todecimal(error)
+
self.upperBound = self.amount + upperError
self.lowerBound = self.amount - lowerError
def toWikibase(self):
"""Convert the data to a JSON object for the Wikibase
API."""
- json = {'amount': self.amount,
- 'upperBound': self.upperBound,
- 'lowerBound': self.lowerBound,
+ json = {'amount': self._fromdecimal(self.amount),
+ 'upperBound': self._fromdecimal(self.upperBound),
+ 'lowerBound': self._fromdecimal(self.lowerBound),
'unit': self.unit
}
return json
@@ -511,9 +530,9 @@
@param wb: Wikibase JSON
"""
- amount = eval(wb['amount'])
- upperBound = eval(wb['upperBound'])
- lowerBound = eval(wb['lowerBound'])
+ amount = cls._todecimal(wb['amount'])
+ upperBound = cls._todecimal(wb['upperBound'])
+ lowerBound = cls._todecimal(wb['lowerBound'])
error = (upperBound - amount, amount - lowerBound)
return cls(amount, wb['unit'], error)
diff --git a/tests/wikibase_tests.py b/tests/wikibase_tests.py
index d477591..a370f6a 100644
--- a/tests/wikibase_tests.py
+++ b/tests/wikibase_tests.py
@@ -12,6 +12,7 @@
import copy
import json
+from decimal import Decimal
import pywikibot
@@ -146,42 +147,79 @@
self.assertRaises(ValueError, pywikibot.WbTime, site=repo, precision=15)
self.assertRaises(ValueError, pywikibot.WbTime, site=repo,
precision='invalid_precision')
- def test_WbQuantity(self):
+ def test_WbQuantity_integer(self):
q = pywikibot.WbQuantity(amount=1234, error=1)
self.assertEqual(q.toWikibase(),
- {'amount': 1234, 'lowerBound': 1233,
- 'upperBound': 1235, 'unit': '1', })
+ {'amount': '+1234', 'lowerBound':
'+1233',
+ 'upperBound': '+1235', 'unit':
'1', })
q = pywikibot.WbQuantity(amount=5, error=(2, 3))
self.assertEqual(q.toWikibase(),
- {'amount': 5, 'lowerBound': 2,
'upperBound': 7,
- 'unit': '1', })
+ {'amount': '+5', 'lowerBound':
'+2',
+ 'upperBound': '+7', 'unit':
'1', })
+ q = pywikibot.WbQuantity(amount=0, error=(0, 0))
+ self.assertEqual(q.toWikibase(),
+ {'amount': '+0', 'lowerBound':
'+0',
+ 'upperBound': '+0', 'unit':
'1', })
+ q = pywikibot.WbQuantity(amount=-5, error=(2, 3))
+ self.assertEqual(q.toWikibase(),
+ {'amount': '-5', 'lowerBound':
'-8',
+ 'upperBound': '-3', 'unit':
'1', })
+
+ def test_WbQuantity_float_27(self):
q = pywikibot.WbQuantity(amount=0.044405586)
- q_dict = {'amount': 0.044405586, 'lowerBound': 0.044405586,
- 'upperBound': 0.044405586, 'unit': '1', }
+ q_dict = {'amount': '+0.044405586', 'lowerBound':
'+0.044405586',
+ 'upperBound': '+0.044405586', 'unit':
'1', }
self.assertEqual(q.toWikibase(), q_dict)
+
+ def test_WbQuantity_scientific(self):
+ q = pywikibot.WbQuantity(amount='1.3e-13', error='1e-14')
+ q_dict = {'amount': '+1.3e-13', 'lowerBound':
'+1.2e-13',
+ 'upperBound': '+1.4e-13', 'unit': '1',
}
+ self.assertEqual(q.toWikibase(), q_dict)
+
+ def test_WbQuantity_decimal(self):
+ q = pywikibot.WbQuantity(amount=Decimal('0.044405586'))
+ q_dict = {'amount': '+0.044405586', 'lowerBound':
'+0.044405586',
+ 'upperBound': '+0.044405586', 'unit':
'1', }
+ self.assertEqual(q.toWikibase(), q_dict)
+
+ def test_WbQuantity_string(self):
+ q = pywikibot.WbQuantity(amount='0.044405586')
+ q_dict = {'amount': '+0.044405586', 'lowerBound':
'+0.044405586',
+ 'upperBound': '+0.044405586', 'unit':
'1', }
+ self.assertEqual(q.toWikibase(), q_dict)
+
+ def test_WbQuantity_formatting(self):
# test other WbQuantity methods
+ q = pywikibot.WbQuantity(amount='0.044405586')
self.assertEqual("%s" % q,
'{\n'
- ' "amount": %(val)r,\n'
- ' "lowerBound": %(val)r,\n'
+ ' "amount": "+%(val)s",\n'
+ ' "lowerBound": "+%(val)s",\n'
' "unit": "1",\n'
- ' "upperBound": %(val)r\n'
- '}' % {'val': 0.044405586})
+ ' "upperBound": "+%(val)s"\n'
+ '}' % {'val': '0.044405586'})
self.assertEqual("%r" % q,
"WbQuantity(amount=%(val)s, "
"upperBound=%(val)s, lowerBound=%(val)s, "
- "unit=1)" % {'val': 0.044405586})
+ "unit=1)" % {'val': '0.044405586'})
+
+ def test_WbQuantity_equality(self):
+ q = pywikibot.WbQuantity(amount='0.044405586')
self.assertEqual(q, q)
+ def test_WbQuantity_fromWikibase(self):
# test WbQuantity.fromWikibase() instantiating
q = pywikibot.WbQuantity.fromWikibase({u'amount': u'+0.0229',
u'lowerBound': u'0',
u'upperBound': u'1',
u'unit': u'1'})
+ # note that the bounds are inputted as INT but are returned as FLOAT
self.assertEqual(q.toWikibase(),
- {'amount': 0.0229, 'lowerBound': 0,
'upperBound': 1,
- 'unit': '1', })
+ {'amount': '+0.0229', 'lowerBound':
'+0.0000',
+ 'upperBound': '+1.0000', 'unit':
'1', })
+ def test_WbQuantity_errors(self):
# test WbQuantity error handling
self.assertRaises(ValueError, pywikibot.WbQuantity, amount=None,
error=1)
--
To view, visit
https://gerrit.wikimedia.org/r/250497
To unsubscribe, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ie1b9ffbc9c1cc4b5d843469603ebfa1eaa5fd429
Gerrit-PatchSet: 10
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>