jenkins-bot submitted this change.

View Change

Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
[IMPR] raise TypeError in AliasesDict.normalizeData if data value is not a list

Also update wikibase documentation

Bug: T318034
Change-Id: Iec54152b97efe2987f67c8274ba3f8fb7ebffc53
---
M pywikibot/page/_collections.py
M pywikibot/page/_wikibase.py
2 files changed, 116 insertions(+), 55 deletions(-)

diff --git a/pywikibot/page/_collections.py b/pywikibot/page/_collections.py
index cbf9c6c..cd8af8e 100644
--- a/pywikibot/page/_collections.py
+++ b/pywikibot/page/_collections.py
@@ -149,8 +149,12 @@
def normalizeData(cls, data: dict) -> dict:
"""Helper function to expand data into the Wikibase API structure.

+ .. versionchanged:: 7.7
+ raises TypeError *data* value is not a list.
+
:param data: Data to normalize
:return: The dict with normalized data
+ :raises TypeError: data values must be a list
"""
norm_data = {}
for key, values in data.items():
@@ -162,6 +166,11 @@
else:
strings.append(value)
norm_data[key] = strings
+ else:
+ raise TypeError(
+ "Unsupported value type {!r} for '{}'; list expected."
+ .format(type(values).__name__, values))
+
return norm_data

def toJSON(self, diffto: Optional[dict] = None) -> dict:
diff --git a/pywikibot/page/_wikibase.py b/pywikibot/page/_wikibase.py
index 4193764..a01ac8e 100644
--- a/pywikibot/page/_wikibase.py
+++ b/pywikibot/page/_wikibase.py
@@ -18,10 +18,10 @@
from collections import OrderedDict, defaultdict
from contextlib import suppress
from itertools import chain
-from typing import Any, Optional
+from typing import Any, Optional, Union

import pywikibot
-from pywikibot.backports import Dict
+from pywikibot.backports import Dict, List
from pywikibot.exceptions import (
APIError,
EntityTypeUnknownError,
@@ -63,6 +63,12 @@
)


+ALIASES_TYPE = Dict[Union[str, pywikibot.APISite], List[str]]
+LANGUAGE_TYPE = Dict[Union[str, pywikibot.APISite], str]
+SITELINK_TYPE = Union['pywikibot.page.BasePage', 'pywikibot.page.BaseLink',
+ Dict[str, str]]
+
+
class WikibaseEntity:

"""
@@ -272,12 +278,22 @@
data[key] = value
return data

- def editEntity(self, data=None, **kwargs) -> None:
- """
- Edit an entity using Wikibase wbeditentity API.
+ def editEntity(
+ self,
+ data: Union[LANGUAGE_TYPE, ALIASES_TYPE, SITELINK_TYPE, None] = None,
+ **kwargs
+ ) -> None:
+ """Edit an entity using Wikibase ``wbeditentity`` API.
+
+ This function is wrapped around by:
+ - :meth:`editLabels`
+ - :meth:`editDescriptions`
+ - :meth:`editAliases`
+ - :meth:`ItemPage.setSitelinks`
+
+ .. seealso:: :meth:`WikibasePage.editEntity`

:param data: Data to be saved
- :type data: dict, or None to save the current content of the entity.
"""
if data is None:
data = self.toJSON(diffto=getattr(self, '_content', None))
@@ -589,62 +605,96 @@
self.clear_cache()

@allow_asynchronous
- def editEntity(self, data=None, **kwargs) -> None:
- """
- Edit an entity using Wikibase wbeditentity API.
+ def editEntity(
+ self,
+ data: Union[LANGUAGE_TYPE, ALIASES_TYPE, SITELINK_TYPE, None] = None,
+ **kwargs: Any
+ ) -> None:
+ """Edit an entity using Wikibase ``wbeditentity`` API.

This function is wrapped around by:
- - editLabels
- - editDescriptions
- - editAliases
- - ItemPage.setSitelinks
+ - :meth:`editLabels`
+ - :meth:`editDescriptions`
+ - :meth:`editAliases`
+ - :meth:`ItemPage.setSitelinks`
+
+ It supports *asynchronous* and *callback* keyword arguments. The
+ callback function is intended for use by bots that need to keep
+ track of which saves were successful. The minimal callback
+ function signature is::
+
+ def my_callback(page: WikibasePage, err: Optional[Exception]) -> Any:
+
+ The arguments are:
+
+ ``page``
+ a :class:`WikibasePage` object
+
+ ``err``
+ an Exception instance, which will be None if the page was
+ saved successfully
+
+ .. seealso:: :meth:`WikibaseEntity.editEntity`

:param data: Data to be saved
- :type data: dict, or None to save the current content of the entity.
- :keyword asynchronous: if True, launch a separate thread to edit
- asynchronously
- :type asynchronous: bool
- :keyword callback: a callable object that will be called after the
- entity has been updated. It must take two arguments: (1) a
- WikibasePage object, and (2) an exception instance, which will be
- None if the page was saved successfully. This is intended for use
- by bots that need to keep track of which saves were successful.
- :type callback: callable
+ :keyword bool asynchronous: if True, launch a separate thread to
+ edit asynchronously
+ :keyword Callable[[WikibasePage, Optional[Exception]], Any] callback:
+ a callable object that will be called after the entity has
+ been updated. It must take two arguments, see above.
"""
- # kept for the decorator
+ # kept for the decorator which provides the keyword arguments
super().editEntity(data, **kwargs)

- def editLabels(self, labels, **kwargs) -> None:
- """
- Edit entity labels.
+ def editLabels(self, labels: LANGUAGE_TYPE, **kwargs) -> None:
+ """Edit entity labels.

- Labels should be a dict, with the key
- as a language or a site object. The
- value should be the string to set it to.
- You can set it to '' to remove the label.
+ *labels* should be a dict, with the key as a language or a site
+ object. The value should be the string to set it to. You can set
+ it to ``''`` to remove the label.
+
+ Refer :meth:`editEntity` for *asynchronous* and *callback* usage.
+
+ Usage:
+
+ >>> repo = pywikibot.Site('wikidata:test')
+ >>> item = pywikibot.ItemPage(repo, 'Q68')
+ >>> item.editLabels({'en': 'Test123'}) # doctest: +SKIP
"""
data = {'labels': labels}
self.editEntity(data, **kwargs)

- def editDescriptions(self, descriptions, **kwargs) -> None:
- """
- Edit entity descriptions.
+ def editDescriptions(self, descriptions: LANGUAGE_TYPE, **kwargs) -> None:
+ """Edit entity descriptions.

- Descriptions should be a dict, with the key
- as a language or a site object. The
- value should be the string to set it to.
- You can set it to '' to remove the description.
+ *descriptions* should be a dict, with the key as a language or a
+ site object. The value should be the string to set it to. You
+ can set it to ``''`` to remove the description.
+
+ Refer :meth:`editEntity` for *asynchronous* and *callback* usage.
+
+ Usage:
+
+ >>> repo = pywikibot.Site('wikidata:test')
+ >>> item = pywikibot.ItemPage(repo, 'Q68')
+ >>> item.editDescriptions({'en': 'Pywikibot test'}) # doctest: +SKIP
"""
data = {'descriptions': descriptions}
self.editEntity(data, **kwargs)

- def editAliases(self, aliases, **kwargs) -> None:
- """
- Edit entity aliases.
+ def editAliases(self, aliases: ALIASES_TYPE, **kwargs) -> None:
+ """Edit entity aliases.

- Aliases should be a dict, with the key
- as a language or a site object. The
- value should be a list of strings.
+ *aliases* should be a dict, with the key as a language or a site
+ object. The value should be a list of strings.
+
+ Refer :meth:`editEntity` for *asynchronous* and *callback* usage.
+
+ Usage:
+
+ >>> repo = pywikibot.Site('wikidata:test')
+ >>> item = pywikibot.ItemPage(repo, 'Q68')
+ >>> item.editAliases({'en': ['pwb test item']}) # doctest: +SKIP
"""
data = {'aliases': aliases}
self.editEntity(data, **kwargs)
@@ -1004,12 +1054,13 @@

return self.sitelinks[site].canonical_title()

- def setSitelink(self, sitelink, **kwargs) -> None:
- """
- Set sitelinks. Calls setSitelinks().
+ def setSitelink(self, sitelink: SITELINK_TYPE, **kwargs) -> None:
+ """Set sitelinks. Calls :meth:`setSitelinks`.

- A sitelink can be a Page object, a BaseLink object
- or a {'site':dbname,'title':title} dictionary.
+ A *sitelink* can be a Page object, a BaseLink object or a
+ ``{'site': dbname, 'title': title}`` dictionary.
+
+ Refer :meth:`editEntity` for *asynchronous* and *callback* usage.
"""
self.setSitelinks([sitelink], **kwargs)

@@ -1034,13 +1085,14 @@
data.append({'site': site, 'title': ''})
self.setSitelinks(data, **kwargs)

- def setSitelinks(self, sitelinks, **kwargs) -> None:
- """
- Set sitelinks.
+ def setSitelinks(self, sitelinks: List[SITELINK_TYPE], **kwargs) -> None:
+ """Set sitelinks.

- Sitelinks should be a list. Each item in the
- list can either be a Page object, a BaseLink object, or a dict
- with a value for 'site' and 'title'.
+ *sitelinks* should be a list. Each item in the list can either
+ be a Page object, a BaseLink object, or a dict with key for
+ 'site' and a value for 'title'.
+
+ Refer :meth:`editEntity` for *asynchronous* and *callback* usage.
"""
data = {'sitelinks': sitelinks}
self.editEntity(data, **kwargs)

To view, visit change 832739. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Iec54152b97efe2987f67c8274ba3f8fb7ebffc53
Gerrit-Change-Number: 832739
Gerrit-PatchSet: 3
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: JJMC89 <JJMC89.Wikimedia@gmail.com>
Gerrit-Reviewer: Matěj Suchánek <matejsuchanek97@gmail.com>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged