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 <[email protected]> --- 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 <[email protected]> Gerrit-Reviewer: John Vandenberg <[email protected]> Gerrit-Reviewer: Ladsgroup <[email protected]> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
