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]

Reply via email to