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 <[email protected]>
Gerrit-Reviewer: Lokal Profil <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to