jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871226 )
Change subject: [FEAT] Support federated Wikibase
......................................................................
[FEAT] Support federated Wikibase
Entity sources are hardcoded in family files.
Complete backward compatibility, no deprecation.
Bug: T173195
Change-Id: Idc9890d099a9d7cf973af94fb468fe2d0da84f85
---
M pywikibot/families/commons_family.py
M pywikibot/site/_datasite.py
M pywikibot/page/_wikibase.py
M tests/file_tests.py
M pywikibot/families/wikidata_family.py
5 files changed, 154 insertions(+), 20 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/families/commons_family.py b/pywikibot/families/commons_family.py
index 718f3e8..41b3303 100644
--- a/pywikibot/families/commons_family.py
+++ b/pywikibot/families/commons_family.py
@@ -45,3 +45,66 @@
.. versionadded:: 6.5
"""
return 'DataSite'
+
+ def calendarmodel(self, code) -> str:
+ """Default calendar model for WbTime datatype."""
+ return 'http://www.wikidata.org/entity/Q1985727'
+
+ def default_globe(self, code) -> str:
+ """Default globe for Coordinate datatype."""
+ return 'earth'
+
+ def globes(self, code):
+ """Supported globes for Coordinate datatype."""
+ return {
+ 'ariel': 'http://www.wikidata.org/entity/Q3343',
+ 'bennu': 'http://www.wikidata.org/entity/Q11558',
+ 'callisto': 'http://www.wikidata.org/entity/Q3134',
+ 'ceres': 'http://www.wikidata.org/entity/Q596',
+ 'deimos': 'http://www.wikidata.org/entity/Q7548',
+ 'dione': 'http://www.wikidata.org/entity/Q15040',
+ 'earth': 'http://www.wikidata.org/entity/Q2',
+ 'enceladus': 'http://www.wikidata.org/entity/Q3303',
+ 'eros': 'http://www.wikidata.org/entity/Q16711',
+ 'europa': 'http://www.wikidata.org/entity/Q3143',
+ 'ganymede': 'http://www.wikidata.org/entity/Q3169',
+ 'gaspra': 'http://www.wikidata.org/entity/Q158244',
+ 'hyperion': 'http://www.wikidata.org/entity/Q15037',
+ 'iapetus': 'http://www.wikidata.org/entity/Q17958',
+ 'io': 'http://www.wikidata.org/entity/Q3123',
+ 'jupiter': 'http://www.wikidata.org/entity/Q319',
+ 'lutetia': 'http://www.wikidata.org/entity/Q107556',
+ 'mars': 'http://www.wikidata.org/entity/Q111',
+ 'mercury': 'http://www.wikidata.org/entity/Q308',
+ 'mimas': 'http://www.wikidata.org/entity/Q15034',
+ 'miranda': 'http://www.wikidata.org/entity/Q3352',
+ 'moon': 'http://www.wikidata.org/entity/Q405',
+ 'oberon': 'http://www.wikidata.org/entity/Q3332',
+ 'phobos': 'http://www.wikidata.org/entity/Q7547',
+ 'phoebe': 'http://www.wikidata.org/entity/Q17975',
+ 'pluto': 'http://www.wikidata.org/entity/Q339',
+ 'rhea': 'http://www.wikidata.org/entity/Q15050',
+ 'ryugu': 'http://www.wikidata.org/entity/Q1385178',
+ 'steins': 'http://www.wikidata.org/entity/Q150249',
+ 'tethys': 'http://www.wikidata.org/entity/Q15047',
+ 'titan': 'http://www.wikidata.org/entity/Q2565',
+ 'titania': 'http://www.wikidata.org/entity/Q3322',
+ 'triton': 'http://www.wikidata.org/entity/Q3359',
+ 'umbriel': 'http://www.wikidata.org/entity/Q3338',
+ 'venus': 'http://www.wikidata.org/entity/Q313',
+ 'vesta': 'http://www.wikidata.org/entity/Q3030',
+ }
+
+ def entity_sources(self, code):
+ if code == 'commons':
+ return {
+ 'item': ('wikidata', 'wikidata'),
+ 'property': ('wikidata', 'wikidata'),
+ }
+ if code in ('test', 'beta'):
+ return {
+ 'item': (code, 'wikidata'),
+ 'property': (code, 'wikidata'),
+ }
+
+ return {} # default
diff --git a/pywikibot/families/wikidata_family.py
b/pywikibot/families/wikidata_family.py
index 335ad86..9030a5c 100644
--- a/pywikibot/families/wikidata_family.py
+++ b/pywikibot/families/wikidata_family.py
@@ -93,3 +93,6 @@
'venus': 'http://www.wikidata.org/entity/Q313',
'vesta': 'http://www.wikidata.org/entity/Q3030',
}
+
+ def entity_sources(self, code):
+ return {}
diff --git a/pywikibot/page/_wikibase.py b/pywikibot/page/_wikibase.py
index d2af3ac..080c265 100644
--- a/pywikibot/page/_wikibase.py
+++ b/pywikibot/page/_wikibase.py
@@ -276,6 +276,10 @@
value = cls.fromJSON(self._content.get(key, {}), self.repo)
setattr(self, key, value)
data[key] = value
+ # xxx: need better handling for this
+ if key in ['claims', 'statements']:
+ value.set_on_item(self)
+
return data
def editEntity(
@@ -340,9 +344,17 @@
title_pattern = r'M[1-9]\d*'
DATA_ATTRIBUTES = {
'labels': LanguageDict,
- # TODO: 'statements': ClaimCollection,
+ 'statements': ClaimCollection,
}
+ def __getattr__(self, name):
+ if name == 'claims': # T149410
+ name = 'statements'
+ if hasattr(self, name):
+ return getattr(self, name)
+
+ return super().__getattr__(name)
+
@property
def file(self) -> FilePage:
"""Get the file associated with the mediainfo."""
@@ -577,10 +589,6 @@
if 'pageid' in self._content:
self._pageid = self._content['pageid']
- # xxx: this is ugly
- if 'claims' in data:
- self.claims.set_on_item(self)
-
return data
@property
@@ -1357,12 +1365,17 @@
TARGET_CONVERTER = {
'wikibase-item': lambda value, site:
- ItemPage(site, 'Q' + str(value['numeric-id'])),
+ ItemPage(site.get_repo_for_entity_type('item'),
+ 'Q' + str(value['numeric-id'])),
'wikibase-property': lambda value, site:
- PropertyPage(site, 'P' + str(value['numeric-id'])),
- 'wikibase-lexeme': lambda value, site: LexemePage(site,
value['id']),
- 'wikibase-form': lambda value, site: LexemeForm(site,
value['id']),
- 'wikibase-sense': lambda value, site: LexemeSense(site,
value['id']),
+ PropertyPage(site.get_repo_for_entity_type('property'),
+ 'P' + str(value['numeric-id'])),
+ 'wikibase-lexeme': lambda value, site:
+ LexemePage(site.get_repo_for_entity_type('lexeme'),
value['id']),
+ 'wikibase-form': lambda value, site:
+ LexemeForm(site.get_repo_for_entity_type('lexeme'),
value['id']),
+ 'wikibase-sense': lambda value, site:
+ LexemeSense(site.get_repo_for_entity_type('lexeme'),
value['id']),
'commonsMedia': lambda value, site:
FilePage(pywikibot.Site('commons'), value), # T90492
'globe-coordinate': pywikibot.Coordinate.fromWikibase,
@@ -1392,7 +1405,9 @@
Defined by the "snak" value, supplemented by site + pid
- :param site: repository the claim is on
+ :param site: Repository where the property of the claim is defined.
+ Note that this does not have to correspond to the repository
+ where the claim has been stored.
:type site: pywikibot.site.DataSite
:param pid: property id, with "P" prefix
:param snak: snak identifier for claim
@@ -1528,7 +1543,8 @@
:rtype: pywikibot.page.Claim
"""
- claim = cls(site, data['mainsnak']['property'],
+ claim_repo = site.get_repo_for_entity_type('property')
+ claim = cls(claim_repo, data['mainsnak']['property'],
datatype=data['mainsnak'].get('datatype', None))
if 'id' in data:
claim.snak = data['id']
@@ -1538,7 +1554,7 @@
if claim.getSnakType() == 'value':
value = data['mainsnak']['datavalue']['value']
# The default covers string, url types
- if claim.type in cls.types or claim.type == 'wikibase-property':
+ if claim.type in cls.types:
claim.target = cls.TARGET_CONVERTER.get(
claim.type, lambda value, site: value)(value, site)
else:
@@ -1678,8 +1694,8 @@
if value:
self.setTarget(value)
- data = self.repo.changeClaimTarget(self, snaktype=snaktype,
- **kwargs)
+ data = self.on_item.repo.changeClaimTarget(self, snaktype=snaktype,
+ **kwargs)
# TODO: Re-create the entire item from JSON, not just id
self.snak = data['claim']['id']
self.on_item.latest_revision_id = data['pageinfo']['lastrevid']
@@ -1729,7 +1745,7 @@
self._assert_mainsnak('Cannot change rank on a {}')
self._assert_attached()
self.rank = rank
- return self.repo.save_claim(self, **kwargs)
+ return self.on_item.repo.save_claim(self, **kwargs)
def changeSnakType(self, value=None, **kwargs) -> None:
"""
@@ -1767,7 +1783,8 @@
raise ValueError(
'The provided Claim instance is already used in an entity')
if self.on_item is not None:
- data = self.repo.editSource(self, claims, new=True, **kwargs)
+ data = self.on_item.repo.editSource(self, claims, new=True,
+ **kwargs)
self.on_item.latest_revision_id =
data['pageinfo']['lastrevid']
for claim in claims:
claim.hash = data['reference']['hash']
@@ -1796,7 +1813,7 @@
"""
self._assert_mainsnak('Cannot remove sources from a {}')
self._assert_attached()
- data = self.repo.removeSources(self, sources, **kwargs)
+ data = self.on_item.repo.removeSources(self, sources, **kwargs)
self.on_item.latest_revision_id = data['pageinfo']['lastrevid']
for source in sources:
source_dict = defaultdict(list)
@@ -1814,7 +1831,7 @@
raise ValueError(
'The provided Claim instance is already used in an entity')
if self.on_item is not None:
- data = self.repo.editQualifier(self, qualifier, **kwargs)
+ data = self.on_item.repo.editQualifier(self, qualifier, **kwargs)
self.on_item.latest_revision_id =
data['pageinfo']['lastrevid']
qualifier.on_item = self.on_item
qualifier.isQualifier = True
@@ -1841,7 +1858,7 @@
"""
self._assert_mainsnak('Cannot remove qualifiers from a {}')
self._assert_attached()
- data = self.repo.remove_qualifiers(self, qualifiers, **kwargs)
+ data = self.on_item.repo.remove_qualifiers(self, qualifiers, **kwargs)
self.on_item.latest_revision_id = data['pageinfo']['lastrevid']
for qualifier in qualifiers:
self.qualifiers[qualifier.getID()].remove(qualifier)
diff --git a/pywikibot/site/_datasite.py b/pywikibot/site/_datasite.py
index 054452e..ab5394e 100644
--- a/pywikibot/site/_datasite.py
+++ b/pywikibot/site/_datasite.py
@@ -47,6 +47,29 @@
'sense': pywikibot.LexemeSense,
}
+ def get_repo_for_entity_type(self, entity_type: str) -> 'DataSite':
+ """
+ Get the data repository for the entity type.
+
+ When no foreign repository is defined for the entity type,
+ the method returns this repository itself even if it does not
+ support that entity type either.
+
+ .. seealso::
https://www.mediawiki.org/wiki/Wikibase/Federation
+ .. versionadded:: 8.0
+
+ :raises ValueError: when invalid entity type was provided
+ """
+ if entity_type not in self._type_to_class:
+ raise ValueError(f'Invalid entity type "{entity_type}"')
+ entity_sources = self.entity_sources()
+ if entity_type in entity_sources:
+ return pywikibot.Site(
+ *entity_sources[entity_type],
+ interface='DataSite',
+ user=self.username())
+ return self
+
def _cache_entity_namespaces(self) -> None:
"""Find namespaces for each known wikibase entity
type."""
self._entity_namespaces = {}
diff --git a/tests/file_tests.py b/tests/file_tests.py
index 64294b1..197cf94 100755
--- a/tests/file_tests.py
+++ b/tests/file_tests.py
@@ -9,6 +9,7 @@
import re
import unittest
from contextlib import suppress
+from itertools import chain
import pywikibot
from pywikibot.exceptions import (
@@ -320,6 +321,20 @@
self.assertEqual('M14634781', item.getID())
self.assertIsInstance(
item.labels, pywikibot.page._collections.LanguageDict)
+ self.assertIsInstance(
+ item.statements, pywikibot.page._collections.ClaimCollection)
+ self.assertTrue(item.claims is item.statements)
+
+ all_claims = list(chain.from_iterable(item.statements.values()))
+ self.assertEqual({claim.on_item for claim in all_claims}, {item})
+
+ claims = [claim for claim in all_claims
+ if isinstance(claim.target, pywikibot.page.WikibaseEntity)]
+ self.assertEqual({str(claim.repo) for claim in claims},
+ {'wikidata:wikidata'})
+ self.assertEqual({str(claim.target.repo) for claim in claims},
+ {'wikidata:wikidata'})
+
del item._file
self.assertEqual(page, item.file)
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871226
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Idc9890d099a9d7cf973af94fb468fe2d0da84f85
Gerrit-Change-Number: 871226
Gerrit-PatchSet: 10
Gerrit-Owner: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Lokal Profil <andre.costa(a)wikimedia.se>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged