jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/869747 )
Change subject: [IMPR] add some tests and documentation and simplify some code
......................................................................
[IMPR] add some tests and documentation and simplify some code
path detached from 1105d0c
Change-Id: I8f481428da13b7b6fa7796c965e49d2bda24bfba
---
M tests/time_tests.py
M pywikibot/time.py
2 files changed, 78 insertions(+), 13 deletions(-)
Approvals:
RoySmith: Looks good to me, but someone else must approve
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/time.py b/pywikibot/time.py
index e96a5f7..d6e7854 100644
--- a/pywikibot/time.py
+++ b/pywikibot/time.py
@@ -11,6 +11,7 @@
import math
import re
import types
+from contextlib import suppress
from typing import Type, Union
import pywikibot
@@ -118,8 +119,8 @@
m = re.match(RE_MW, timestr)
if not m:
- msg = "time data '{timestr}' does not match MW format."
- raise ValueError(msg.format(timestr=timestr))
+ raise ValueError(
+ f'time data {timestr!r} does not match MW format.')
return cls.strptime(timestr, cls.mediawikiTSFormat)
@@ -139,8 +140,8 @@
m = re.match(RE_ISO8601, timestr)
if not m:
- msg = "time data '{timestr}' does not match ISO8601 format."
- raise ValueError(msg.format(timestr=timestr))
+ raise ValueError(
+ f'time data {timestr!r} does not match ISO8601 format.')
strpfmt = '%Y-%m-%d{sep}%H:%M:%S'.format(sep=m.group('sep'))
strpstr = timestr[:19]
@@ -205,13 +206,10 @@
]
for handler in handlers:
- try:
+ with suppress(ValueError):
return handler(timestr)
- except ValueError:
- continue
- msg = "time data '{timestr}' does not match any format."
- raise ValueError(msg.format(timestr=timestr))
+ raise ValueError(f'time data {timestr!r} does not match any format.')
def clone(self) -> 'Timestamp':
"""Clone this instance."""
@@ -251,14 +249,43 @@
return cls._from_iso8601(_ts)
@classmethod
- def fromtimestampformat(cls: Type['Timestamp'], ts: Union[str, 'Timestamp']
- ) -> 'Timestamp':
- """Convert a MediaWiki internal timestamp to a Timestamp object."""
+ def fromtimestampformat(cls: Type['Timestamp'],
+ ts: Union[str, 'Timestamp'],
+ strict: bool = False) -> 'Timestamp':
+ """Convert a MediaWiki internal timestamp to a Timestamp object.
+
+ .. versionchanged:: 3.0
+ create a Timestamp if only year, month and day are given.
+ .. versionchanged:: 8.0
+ the *strict* parameter was added which discards missing
+ element tolerance.
+
+ Example
+ -------
+
+ >>> Timestamp.fromtimestampformat('20220705082234')
+ Timestamp(2022, 7, 5, 8, 22, 34)
+ >>> Timestamp.fromtimestampformat('20220927')
+ Timestamp(2022, 9, 27, 0, 0)
+ >>> Timestamp.fromtimestampformat('20221109', strict=True)
+ Traceback (most recent call last):
+ ...
+ ValueError: time data '20221109' does not match MW format.
+
+ :param ts: the timestamp to be converted
+ :param strict: If true, do not ignore missing timestamp elements
+ for hours, minutes or seconds
+ :return: return the *Timestamp* object from given *ts*.
+ :raises ValueError: The timestamp is invalid, e.g. missing or
+ invalid timestamp component.
+ """
# If inadvertently passed a Timestamp object, use replace()
# to create a clone.
if isinstance(ts, cls):
return ts.clone()
- if len(ts) == 8: # year, month and day are given only
+
+ if len(ts) == 8 and not strict:
+ # year, month and day are given only
ts += '000000'
return cls._from_mw(ts)
diff --git a/tests/time_tests.py b/tests/time_tests.py
index ff208f5..7d4f8d8 100755
--- a/tests/time_tests.py
+++ b/tests/time_tests.py
@@ -196,6 +196,33 @@
self.assertEqual(t1, t2)
self.assertEqual(ts1, ts2)
+ tests = [
+ ('202211', None),
+ ('2022112', None),
+ ('20221127', (2022, 11, 27)),
+ ('202211271', None),
+ ('2022112712', None),
+ ('20221127123', None),
+ ('202211271234', None),
+ ('2022112712345', None),
+ ('20221127123456', (2022, 11, 27, 12, 34, 56)),
+ ]
+ for mw_ts, ts in tests:
+ with self.subTest(timestamp=mw_ts):
+ if ts is None:
+ with self.assertRaisesRegex(
+ ValueError,
+ f'time data {mw_ts!r} does not match MW format'):
+ Timestamp.fromtimestampformat(mw_ts)
+ else:
+ self.assertEqual(Timestamp.fromtimestampformat(mw_ts),
+ Timestamp(*ts))
+
+ for mw_ts, ts in tests[1:-1]:
+ with self.subTest(timestamp=mw_ts), self.assertRaisesRegex(
+ ValueError, f'time data {mw_ts!r} does not match MW'):
+ Timestamp.fromtimestampformat(mw_ts, strict=True)
+
def test_add_timedelta(self):
"""Test addin a timedelta to a Timestamp."""
t1 = Timestamp.utcnow()
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/869747
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: I8f481428da13b7b6fa7796c965e49d2bda24bfba
Gerrit-Change-Number: 869747
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: RoySmith <roy(a)panix.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871893 )
Change subject: [fix] Skip invalid link titles in fixing_redirects.py
......................................................................
[fix] Skip invalid link titles in fixing_redirects.py
- If a title contains a replacement character ignore this link from processing
- Raise a more informative exception if replacement char was found in title.
Bug: T324434
Change-Id: I3c802a5790ef6239342b5f2e01a3c5cfa6d2d21d
---
M pywikibot/page/_links.py
M scripts/fixing_redirects.py
2 files changed, 18 insertions(+), 2 deletions(-)
Approvals:
Meno25: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page/_links.py b/pywikibot/page/_links.py
index 1dd4e3f..36ba716 100644
--- a/pywikibot/page/_links.py
+++ b/pywikibot/page/_links.py
@@ -314,7 +314,7 @@
# This code was adapted from Title.php : secureAndSplit()
if '\ufffd' in t:
raise InvalidTitleError(
- '{!r} contains illegal char {!r}'.format(t, '\ufffd'))
+ fr'{t!r} contains illegal replacement char \ufffd')
# Cleanup whitespace
sep = self._source.family.title_delimiter_and_aliases[0]
diff --git a/scripts/fixing_redirects.py b/scripts/fixing_redirects.py
index 98cbc59..defde99 100755
--- a/scripts/fixing_redirects.py
+++ b/scripts/fixing_redirects.py
@@ -84,7 +84,10 @@
# Make sure that next time around we will not find this same hit.
curpos = m.start() + 1
- is_interwikilink = mysite.isInterwikiLink(m['title'])
+ try:
+ is_interwikilink = mysite.isInterwikiLink(m['title'])
+ except InvalidTitleError:
+ continue # skip invalid title
# ignore interwiki links, links in the disabled area
# and links to sections of the same page
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871893
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: I3c802a5790ef6239342b5f2e01a3c5cfa6d2d21d
Gerrit-Change-Number: 871893
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: D3r1ck01 <xsavitar.wiki(a)aol.com>
Gerrit-Reviewer: Meno25 <meno25mail(a)gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
Xqt has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871166 )
Change subject: [IMPR] Assert internal state on Claim modifications
......................................................................
[IMPR] Assert internal state on Claim modifications
Add an assertion to ensure some operations cannot be
performed on a qualifier or a reference (such as
adding qualifiers/references or changing the rank)
and another one to ensure some operations can only
be performed on an existing (saved) claim.
Note that in the second case the operations would
already crash. But now the error messages are more
clear.
Change-Id: If7867b67ac0af72a5be0650ece2813fe1a964077
---
M pywikibot/page/_wikibase.py
1 file changed, 45 insertions(+), 8 deletions(-)
Approvals:
Xqt: Verified; Looks good to me, approved
diff --git a/pywikibot/page/_wikibase.py b/pywikibot/page/_wikibase.py
index 9e857fc..d2af3ac 100644
--- a/pywikibot/page/_wikibase.py
+++ b/pywikibot/page/_wikibase.py
@@ -46,7 +46,7 @@
from pywikibot.page._filepage import FilePage
from pywikibot.page._page import BasePage
from pywikibot.site import DataSite, Namespace
-from pywikibot.tools import cached
+from pywikibot.tools import cached, first_upper
__all__ = (
@@ -271,7 +271,7 @@
data = {}
- # This initializes all data,
+ # This initializes all data
for key, cls in self.DATA_ATTRIBUTES.items():
value = cls.fromJSON(self._content.get(key, {}), self.repo)
setattr(self, key, value)
@@ -1416,8 +1416,8 @@
self._on_item = None # The item it's on
@property
- def on_item(self):
- """Return item this claim is attached to."""
+ def on_item(self) -> Optional[WikibaseEntity]:
+ """Return entity this claim is attached to."""
return self._on_item
@on_item.setter
@@ -1431,6 +1431,16 @@
for source in values:
source.on_item = item
+ def _assert_attached(self) -> None:
+ if self.on_item is None:
+ raise RuntimeError('The claim is not attached to an entity')
+
+ def _assert_mainsnak(self, message: str) -> None:
+ if self.isQualifier:
+ raise RuntimeError(first_upper(message.format('qualifier')))
+ if self.isReference:
+ raise RuntimeError(first_upper(message.format('reference')))
+
def __repr__(self) -> str:
"""Return the representation string."""
return '{cls_name}.fromJSON({}, {})'.format(
@@ -1664,6 +1674,7 @@
:param snaktype: The new snak type ('value', 'somevalue', or
'novalue').
"""
+ self._assert_attached()
if value:
self.setTarget(value)
@@ -1710,10 +1721,13 @@
def setRank(self, rank) -> None:
"""Set the rank of the Claim."""
+ self._assert_mainsnak('Cannot set rank on a {}')
self.rank = rank
def changeRank(self, rank, **kwargs):
"""Change the rank of the Claim and save."""
+ self._assert_mainsnak('Cannot change rank on a {}')
+ self._assert_attached()
self.rank = rank
return self.repo.save_claim(self, **kwargs)
@@ -1747,6 +1761,7 @@
:param claims: the claims to add
:type claims: list of pywikibot.Claim
"""
+ self._assert_mainsnak('Cannot add sources to a {}')
for claim in claims:
if claim.on_item is not None:
raise ValueError(
@@ -1779,6 +1794,8 @@
:param sources: the sources to remove
:type sources: list of pywikibot.Claim
"""
+ self._assert_mainsnak('Cannot remove sources from a {}')
+ self._assert_attached()
data = self.repo.removeSources(self, sources, **kwargs)
self.on_item.latest_revision_id = data['pageinfo']['lastrevid']
for source in sources:
@@ -1792,6 +1809,7 @@
:param qualifier: the qualifier to add
:type qualifier: pywikibot.page.Claim
"""
+ self._assert_mainsnak('Cannot add qualifiers to a {}')
if qualifier.on_item is not None:
raise ValueError(
'The provided Claim instance is already used in an entity')
@@ -1819,8 +1837,10 @@
Remove the qualifiers.
:param qualifiers: the qualifiers to remove
- :type qualifiers: list Claim
+ :type qualifiers: list of pywikibot.Claim
"""
+ self._assert_mainsnak('Cannot remove qualifiers from a {}')
+ self._assert_attached()
data = self.repo.remove_qualifiers(self, qualifiers, **kwargs)
self.on_item.latest_revision_id = data['pageinfo']['lastrevid']
for qualifier in qualifiers:
@@ -1879,9 +1899,7 @@
:param target: qualifier target to check presence of
:return: true if the qualifier was found, false otherwise
"""
- if self.isQualifier or self.isReference:
- raise ValueError('Qualifiers and references cannot have '
- 'qualifiers.')
+ self._assert_mainsnak('{}s cannot have qualifiers')
return any(qualifier.target_equals(target)
for qualifier in self.qualifiers.get(qualifier_id, []))
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/871166
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: If7867b67ac0af72a5be0650ece2813fe1a964077
Gerrit-Change-Number: 871166
Gerrit-PatchSet: 1
Gerrit-Owner: Matěj Suchánek <matejsuchanek97(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged