jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1311863?usp=email )
Change subject: IMPR: Add a new collection class DataRecord an derive Revision
from it
......................................................................
IMPR: Add a new collection class DataRecord an derive Revision from it
Bug: T432464
Change-Id: I8f2e6f04ebd733aad333190df7cbc4f182cef62a
---
M docs/conf.py
M pywikibot/page/_revision.py
M pywikibot/tools/collections.py
3 files changed, 99 insertions(+), 57 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/docs/conf.py b/docs/conf.py
index dc02ef8..e113e7c 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -668,6 +668,6 @@
autodoc_default_options = {
'members': True,
'undoc-members': True,
- 'special-members': False,
+ 'special-members': '__missing__',
'show-inheritance': True,
}
diff --git a/pywikibot/page/_revision.py b/pywikibot/page/_revision.py
index 81f093d..dc4a724 100644
--- a/pywikibot/page/_revision.py
+++ b/pywikibot/page/_revision.py
@@ -7,13 +7,17 @@
from __future__ import annotations
import hashlib
-from collections.abc import Mapping
from contextlib import suppress
+from typing import Any
from pywikibot import Timestamp
+from pywikibot.tools.collections import DataRecord
-class Revision(Mapping):
+__all__ = ('Revision', )
+
+
+class Revision(DataRecord):
"""A structure holding information about a single revision of a Page.
@@ -32,68 +36,35 @@
- :api:`Alldeletedrevisions`
"""
- def __init__(self, **kwargs) -> None:
- """Initializer."""
- self._data = kwargs
- self._upcast_dict(self._data)
- super().__init__()
-
@staticmethod
- def _upcast_dict(map_) -> None:
+ def normalize(data: dict[str, Any]) -> None:
"""Upcast dictionary values."""
with suppress(KeyError): # enable doctest
- map_['timestamp'] = Timestamp.fromISOformat(map_['timestamp'])
+ data['timestamp'] = Timestamp.fromISOformat(data['timestamp'])
- map_.update(anon='anon' in map_)
- map_.update(minor='minor' in map_)
- map_.update(userhidden='userhidden' in map_)
- map_.update(commenthidden='commenthidden' in map_)
+ data.update(anon='anon' in data)
+ data.update(minor='minor' in data)
+ data.update(userhidden='userhidden' in data)
+ data.update(commenthidden='commenthidden' in data)
- map_.setdefault('comment', '')
- map_.setdefault('user', '')
+ data.setdefault('comment', '')
+ data.setdefault('user', '')
- if 'slots' in map_: # mw 1.32+
- mainslot = map_['slots'].get('main', {})
- map_['text'] = mainslot.get('*')
- map_['contentmodel'] = mainslot.get('contentmodel')
+ if 'slots' in data: # mw 1.32+
+ mainslot = data['slots'].get('main', {})
+ data['text'] = mainslot.get('*')
+ data['contentmodel'] = mainslot.get('contentmodel')
else:
- map_['slots'] = None
- map_['text'] = map_.get('*')
+ data['slots'] = None
+ data['text'] = data.get('*')
- map_.setdefault('sha1')
- if map_['sha1'] is None and map_['text'] is not None:
- map_['sha1'] = hashlib.sha1(
- map_['text'].encode('utf8')).hexdigest()
+ data.setdefault('sha1')
+ if data['sha1'] is None and data['text'] is not None:
+ data['sha1'] = hashlib.sha1(
+ data['text'].encode('utf8')).hexdigest()
- def __len__(self) -> int:
- """Return the number of data items."""
- return len(self._data)
-
- def __getitem__(self, name: str):
- """Return a single Revision item given by name."""
- if name in self._data:
- return self._data[name]
-
- return self.__missing__(name)
-
- # provide attribute access
- __getattr__ = __getitem__
-
- def __iter__(self):
- """Provide Revision data as iterator."""
- return iter(self._data)
-
- def __repr__(self) -> str:
- """String representation of Revision."""
- return f'{self.__class__.__name__}({self._data})'
-
- def __str__(self) -> str:
- """Printable representation of Revision data."""
- return str(self._data)
-
- def __missing__(self, key):
+ def __missing__(self, key: str, /):
"""Provide backward compatibility for exceptions."""
- # raise AttributeError instead of KeyError for backward compatibility
raise AttributeError(
- f"'{type(self).__name__}' object has no attribute '{key}'"
+ f'{type(self).__name__!r} object has no attribute {key!r}'
)
diff --git a/pywikibot/tools/collections.py b/pywikibot/tools/collections.py
index 9bec020..96f241d 100644
--- a/pywikibot/tools/collections.py
+++ b/pywikibot/tools/collections.py
@@ -11,7 +11,7 @@
from collections.abc import Collection, Generator, Iterator, Mapping
from contextlib import suppress
from itertools import chain
-from types import TracebackType
+from types import MappingProxyType, TracebackType
from typing import Any, NamedTuple
from pywikibot.exceptions import ArgumentDeprecationWarning
@@ -20,6 +20,7 @@
__all__ = (
'CombinedError',
+ 'DataRecord',
'DequeGenerator',
'EmptyDefault',
'GeneratorWrapper',
@@ -380,3 +381,73 @@
def ratio(self) -> float:
"""Calculate a ratio how many hits can be done within one second."""
return self.hits / self.seconds if self.seconds != 0 else float('inf')
+
+
+class DataRecord(Mapping):
+
+ """A read-only structure holding named data items.
+
+ Each data item can be accessed either by its key or as an attribute
+ with the attribute name equal to the key.
+
+ For example:
+
+ >>> d = DataRecord(sample='Sample for DataRecord access')
+ >>> d.sample == d['sample']
+ True
+ >>> d.sample
+ 'Sample for DataRecord access'
+
+ .. version-added:: 11.6
+ """
+
+ def __init__(self, **kwargs) -> None:
+ """Initializer."""
+ self.normalize(kwargs)
+ self._data = MappingProxyType(kwargs)
+
+ @staticmethod
+ def normalize(data: dict[str, Any]) -> None:
+ """Normalize *data* items in-place.
+
+ This method can be overridden by subclasses to normalize their
+ specific data items. Changes should be applied directly to the
+ provided dictionary.
+
+ :param data: The data dictionary to normalize in-place.
+ """
+
+ def __len__(self) -> int:
+ """Return the number of data items."""
+ return len(self._data)
+
+ def __getitem__(self, name: str) -> Any:
+ """Return a data item by name."""
+ return self._data[name]
+
+ def __getattr__(self, name: str) -> Any:
+ """Return a data item by attribute name."""
+ if name in self._data:
+ return self._data[name]
+
+ return self.__missing__(name)
+
+ def __iter__(self):
+ """Return an iterator over data item names."""
+ return iter(self._data)
+
+ def __repr__(self) -> str:
+ """Return the formal string representation."""
+ return f'{type(self).__name__}({self._data})'
+
+ def __str__(self) -> str:
+ """Return the string representation of the data."""
+ return str(self._data)
+
+ def __missing__(self, key: str, /) -> Any:
+ """Handle missing data items.
+
+ Subclasses can override this method to provide custom handling
+ for missing data items.
+ """
+ raise KeyError(key)
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1311863?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I8f2e6f04ebd733aad333190df7cbc4f182cef62a
Gerrit-Change-Number: 1311863
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]