jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/339389 )
Change subject: Allow retrieval of unit as ItemPage for WbQuantity ......................................................................
Allow retrieval of unit as ItemPage for WbQuantity
Introduces ItemPage.from_entity_uri() which attempts to retrieve the ItemPage corresponding to an entity uri (e.g. http://www.wikidata.org/entity/Q123)
This function is accessed from WbQuantity.get_unit_item() to retrieve the ItemPage correspoding to the unit.
In future it could also be used for e.g. Coordinate globes or to resolve sparql results.
The site parameter was added to TestWbQuantity tests so as to obey rules for Dry tests.
Bug: T143594 Change-Id: I9746881ada22cb7dd47a76c0597737f063cd31f6 --- M pywikibot/__init__.py M pywikibot/page.py M tests/wikibase_tests.py 3 files changed, 168 insertions(+), 22 deletions(-)
Approvals: Dalba: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py index b980330..5b87ea5 100644 --- a/pywikibot/__init__.py +++ b/pywikibot/__init__.py @@ -662,7 +662,7 @@ @param amount: number representing this quantity @type amount: string or Decimal. Other types are accepted, and converted via str to Decimal. - @param unit: the Wikibase item for the unit or the URL entity of this + @param unit: the Wikibase item for the unit or the entity URI of this Wikibase item. @type unit: pywikibot.ItemPage, str or None @param error: the uncertainty of the amount (e.g. ±1) @@ -676,11 +676,12 @@
self.amount = self._todecimal(amount) self._unit = unit + self.site = site or Site().data_repository()
- # also allow entity urls to be provided via unit parameter + # also allow entity URIs to be provided via unit parameter if isinstance(unit, basestring) and \ unit.partition('://')[0] not in ('http', 'https'): - raise ValueError("'unit' must be an ItemPage or entity url.") + raise ValueError("'unit' must be an ItemPage or entity uri.")
if error is None and not self._require_errors(site): self.upperBound = self.lowerBound = None @@ -698,11 +699,35 @@
@property def unit(self): - """Return _unit's entity url or '1' if _unit is None.""" + """Return _unit's entity uri or '1' if _unit is None.""" if isinstance(self._unit, ItemPage): - return self._unit.concept_url() + return self._unit.concept_uri() return self._unit or '1'
+ def get_unit_item(self, repo=None, lazy_load=False): + """ + Return the ItemPage corresponding to the unit. + + Note that the unit need not be in the same data repository as the + WbQuantity itself. + + A successful lookup is stored as an internal value to avoid the need + for repeated lookups. + + @param repo: the Wikibase site for the unit, if different from that + provided with the WbQuantity. + @type repo: pywikibot.site.DataSite + @param lazy_load: Do not raise NoPage if ItemPage does not exist. + @type lazy_load: bool + @return: pywikibot.ItemPage + """ + if not isinstance(self._unit, basestring): + return self._unit + + repo = repo or self.site + self._unit = ItemPage.from_entity_uri(repo, self._unit, lazy_load) + return self._unit + def toWikibase(self): """ Convert the data to a JSON object for the Wikibase API. diff --git a/pywikibot/page.py b/pywikibot/page.py index a268b1e..93c8b67 100644 --- a/pywikibot/page.py +++ b/pywikibot/page.py @@ -65,13 +65,13 @@ ) from pywikibot.data.api import APIError from pywikibot.family import Family -from pywikibot.site import Namespace, need_version +from pywikibot.site import DataSite, Namespace, need_version from pywikibot.tools import ( PYTHON_VERSION, MediaWikiVersion, UnicodeMixin, ComparableMixin, DotReadableDict, deprecated, deprecate_arg, deprecated_args, issue_deprecation_warning, ModuleDeprecationWrapper as _ModuleDeprecationWrapper, - first_upper, remove_last_args, _NotImplementedWarning, + first_upper, redirect_func, remove_last_args, _NotImplementedWarning, OrderedDict, Counter, ) from pywikibot.tools.ip import ip_regexp @@ -3973,6 +3973,7 @@ @rtype: ItemPage
@raise NoPage: There is no corresponding ItemPage for the page + @raise WikiBaseError: The site of the page has no data repository. """ if not page.site.has_data_repository: raise pywikibot.WikiBaseError('{0} has no data repository' @@ -3994,6 +3995,40 @@ if not lazy_load and not i.exists(): raise pywikibot.NoPage(i) return i + + @classmethod + def from_entity_uri(cls, site, uri, lazy_load=False): + """ + Get the ItemPage from its entity uri. + + @param site: The Wikibase site for the item. + @type site: pywikibot.site.DataSite + @param uri: Entity uri for the Wikibase item. + @type uri: basestring + @param lazy_load: Do not raise NoPage if ItemPage does not exist. + @type lazy_load: bool + @rtype: ItemPage + + @raise TypeError: Site is not a valid DataSite. + @raise ValueError: Site does not match the base of the provided uri. + @raise NoPage: Uri points to non-existent item. + """ + if not isinstance(site, DataSite): + raise TypeError('{0} is not a data repository.'.format(site)) + + base_uri, _, qid = uri.rpartition('/') + if base_uri != site.concept_base_uri.rstrip('/'): + raise ValueError( + 'The supplied data repository ({repo}) does not correspond to ' + 'that of the item ({item})'.format( + repo=site.concept_base_uri.rstrip('/'), + item=base_uri)) + + item = cls(site, qid) + if not lazy_load and not item.exists(): + raise pywikibot.NoPage(item) + + return item
def get(self, force=False, get_redirect=False, *args, **kwargs): """ @@ -4022,8 +4057,8 @@ return data
@need_version('1.28-wmf.23') - def concept_url(self): - """Return the full concept URL.""" + def concept_uri(self): + """Return the full concept URI.""" return '{0}{1}'.format(self.site.concept_base_uri, self.id)
def getRedirectTarget(self): @@ -4215,6 +4250,10 @@ return self._isredir return super(ItemPage, self).isRedirectPage()
+ # alias for backwards compatibility + concept_url = redirect_func(concept_uri, old_name='concept_url', + class_name='ItemPage') +
class Property(object):
diff --git a/tests/wikibase_tests.py b/tests/wikibase_tests.py index d652467..ef4f68b 100644 --- a/tests/wikibase_tests.py +++ b/tests/wikibase_tests.py @@ -219,55 +219,61 @@
def test_WbQuantity_integer(self): """Test WbQuantity for integer value.""" - q = pywikibot.WbQuantity(amount=1234, error=1) + repo = self.get_repo() + q = pywikibot.WbQuantity(amount=1234, error=1, site=repo) self.assertEqual(q.toWikibase(), {'amount': '+1234', 'lowerBound': '+1233', 'upperBound': '+1235', 'unit': '1', }) - q = pywikibot.WbQuantity(amount=5, error=(2, 3)) + q = pywikibot.WbQuantity(amount=5, error=(2, 3), site=repo) self.assertEqual(q.toWikibase(), {'amount': '+5', 'lowerBound': '+2', 'upperBound': '+7', 'unit': '1', }) - q = pywikibot.WbQuantity(amount=0, error=(0, 0)) + q = pywikibot.WbQuantity(amount=0, error=(0, 0), site=repo) self.assertEqual(q.toWikibase(), {'amount': '+0', 'lowerBound': '+0', 'upperBound': '+0', 'unit': '1', }) - q = pywikibot.WbQuantity(amount=-5, error=(2, 3)) + q = pywikibot.WbQuantity(amount=-5, error=(2, 3), site=repo) self.assertEqual(q.toWikibase(), {'amount': '-5', 'lowerBound': '-8', 'upperBound': '-3', 'unit': '1', })
def test_WbQuantity_float_27(self): """Test WbQuantity for float value.""" - q = pywikibot.WbQuantity(amount=0.044405586, error=0.0) + repo = self.get_repo() + q = pywikibot.WbQuantity(amount=0.044405586, error=0.0, site=repo) q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586', 'upperBound': '+0.044405586', 'unit': '1', } self.assertEqual(q.toWikibase(), q_dict)
def test_WbQuantity_scientific(self): """Test WbQuantity for scientific notation.""" - q = pywikibot.WbQuantity(amount='1.3e-13', error='1e-14') + repo = self.get_repo() + q = pywikibot.WbQuantity(amount='1.3e-13', error='1e-14', site=repo) 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): """Test WbQuantity for decimal value.""" + repo = self.get_repo() q = pywikibot.WbQuantity(amount=Decimal('0.044405586'), - error=Decimal('0.0')) + error=Decimal('0.0'), site=repo) q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586', 'upperBound': '+0.044405586', 'unit': '1', } self.assertEqual(q.toWikibase(), q_dict)
def test_WbQuantity_string(self): """Test WbQuantity for decimal notation.""" - q = pywikibot.WbQuantity(amount='0.044405586', error='0') + repo = self.get_repo() + q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo) q_dict = {'amount': '+0.044405586', 'lowerBound': '+0.044405586', 'upperBound': '+0.044405586', 'unit': '1', } self.assertEqual(q.toWikibase(), q_dict)
def test_WbQuantity_formatting_bound(self): """Test WbQuantity formatting with bounds.""" - q = pywikibot.WbQuantity(amount='0.044405586', error='0') + repo = self.get_repo() + q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo) self.assertEqual("%s" % q, '{\n' ' "amount": "+%(val)s",\n' @@ -282,7 +288,8 @@
def test_WbQuantity_equality(self): """Test WbQuantity equality.""" - q = pywikibot.WbQuantity(amount='0.044405586', error='0') + repo = self.get_repo() + q = pywikibot.WbQuantity(amount='0.044405586', error='0', site=repo) self.assertEqual(q, q)
def test_WbQuantity_fromWikibase(self): @@ -304,8 +311,9 @@ error=1)
def test_WbQuantity_entity_unit(self): - """Test WbQuantity with entity url unit.""" - q = pywikibot.WbQuantity(amount=1234, error=1, + """Test WbQuantity with entity uri unit.""" + repo = self.get_repo() + q = pywikibot.WbQuantity(amount=1234, error=1, site=repo, unit='http://www.wikidata.org/entity/Q712226') self.assertEqual(q.toWikibase(), {'amount': '+1234', 'lowerBound': '+1233', @@ -314,9 +322,11 @@
def test_WbQuantity_unit_fromWikibase(self): """Test WbQuantity recognising unit from Wikibase output.""" + repo = self.get_repo() q = pywikibot.WbQuantity.fromWikibase({ 'amount': '+1234', 'lowerBound': '+1233', 'upperBound': '+1235', - 'unit': 'http://www.wikidata.org/entity/Q712226', }) + 'unit': 'http://www.wikidata.org/entity/Q712226', }, + site=repo) self.assertEqual(q.toWikibase(), {'amount': '+1234', 'lowerBound': '+1233', 'upperBound': '+1235', @@ -414,6 +424,31 @@ self.assertNotEqual(b, c) self.assertNotEqual(b, d)
+ def test_WbQuantity_get_unit_item(self): + """Test getting unit item from WbQuantity.""" + repo = self.get_repo() + q = pywikibot.WbQuantity(amount=1234, error=1, site=repo, + unit='http://www.wikidata.org/entity/Q123') + self.assertEqual(q.get_unit_item(), + ItemPage(repo, 'Q123')) + + def test_WbQuantity_get_unit_item_provide_repo(self): + """Test getting unit item from WbQuantity, providing repo.""" + repo = self.get_repo() + q = pywikibot.WbQuantity(amount=1234, error=1, + unit='http://www.wikidata.org/entity/Q123') + self.assertEqual(q.get_unit_item(repo), + ItemPage(repo, 'Q123')) + + def test_WbQuantity_get_unit_item_different_repo(self): + """Test getting unit item in different repo from WbQuantity.""" + repo = self.get_repo() + test_repo = pywikibot.Site('test', 'wikidata') + q = pywikibot.WbQuantity(amount=1234, error=1, site=repo, + unit='http://test.wikidata.org/entity/Q123') + self.assertEqual(q.get_unit_item(test_repo), + ItemPage(test_repo, 'Q123')) +
class TestWbMonolingualText(WikidataTestCase):
@@ -485,6 +520,7 @@ 1. by Q id 2. ItemPage.fromPage(page) 3. ItemPage.fromPage(page_with_props_loaded) + 4. ItemPage.from_entity_uri(site, uri)
Test various invalid scenarios: 1. invalid Q ids @@ -809,6 +845,52 @@ # as that is what the bot operator needs to see in the log output. self.assertRaisesRegex(pywikibot.NoPage, 'Test page', item.get)
+ def test_from_entity_uri(self): + """Test ItemPage.from_entity_uri.""" + repo = self.get_repo() + entity_uri = 'http://www.wikidata.org/entity/Q124' + self.assertEqual(ItemPage.from_entity_uri(repo, entity_uri), + ItemPage(repo, 'Q124')) + + def test_from_entity_uri_not_a_data_repo(self): + """Test ItemPage.from_entity_uri with a non-Wikibase site.""" + repo = self.site + entity_uri = 'http://www.wikidata.org/entity/Q124' + self.assertRaises(TypeError, + ItemPage.from_entity_uri, repo, entity_uri) + + def test_from_entity_uri_wrong_repo(self): + """Test ItemPage.from_entity_uri with unexpected item repo.""" + repo = self.get_repo() + entity_uri = 'http://test.wikidata.org/entity/Q124' + self.assertRaises(ValueError, + ItemPage.from_entity_uri, repo, entity_uri) + + def test_from_entity_uri_invalid_title(self): + """Test ItemPage.from_entity_uri with an invalid item title format.""" + repo = self.get_repo() + entity_uri = 'http://www.wikidata.org/entity/Nonsense' + self.assertRaises(pywikibot.InvalidTitle, + ItemPage.from_entity_uri, repo, entity_uri) + + def test_from_entity_uri_no_item(self): + """Test ItemPage.from_entity_uri with non-exitent item.""" + repo = self.get_repo() + entity_uri = 'http://www.wikidata.org/entity/Q999999999999999999' + self.assertRaises(pywikibot.NoPage, + ItemPage.from_entity_uri, repo, entity_uri) + + def test_from_entity_uri_no_item_lazy(self): + """Test ItemPage.from_entity_uri with lazy loaded non-exitent item.""" + repo = self.get_repo() + entity_uri = 'http://www.wikidata.org/entity/Q999999999999999999' + expected_item = ItemPage(repo, 'Q999999999999999999') + self.assertEqual( + ItemPage.from_entity_uri(repo, entity_uri, lazy_load=True), + expected_item) + + self.assertFalse(expected_item.exists()) # ensure actually missing +
class TestRedirects(WikidataTestCase):
pywikibot-commits@lists.wikimedia.org