Skip to content

Commit 1d4cfdc

Browse files
committed
Make ParseResult and ParsedFeed generic on feed/entry type.
(In preparation for generic update pipeline base.)
1 parent 4be2800 commit 1d4cfdc

3 files changed

Lines changed: 39 additions & 26 deletions

File tree

src/reader/_parser/__init__.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from typing import ContextManager
1515
from typing import Generic
1616
from typing import NamedTuple
17-
from typing import Optional
1817
from typing import Protocol
1918
from typing import runtime_checkable
2019
from typing import TYPE_CHECKING
@@ -388,7 +387,11 @@ def process_feed_for_update(self, feed: FeedForUpdate) -> FeedForUpdate:
388387
"""
389388

390389

391-
class ParseResult(NamedTuple, Generic[F, E]):
390+
FD = TypeVar('FD')
391+
ED = TypeVar('ED')
392+
393+
394+
class ParseResultBase(NamedTuple, Generic[F, FD, ED, E]):
392395
"""The result of retrieving and parsing a feed, regardless of the outcome."""
393396

394397
#: The feed (a :class:`FeedArgument`, usually a :class:`.FeedForUpdate`).
@@ -400,19 +403,19 @@ class ParseResult(NamedTuple, Generic[F, E]):
400403
#: * :const:`None`, if the feed didn't change
401404
#: * an exception
402405
#:
403-
value: ParsedFeed | None | E
406+
value: ParsedFeedBase[FD, ED] | None | E
404407

405408
#: Details about the HTTP response.
406409
http_info: HTTPInfo | None = None
407410

408411

409-
class ParsedFeed(NamedTuple):
412+
class ParsedFeedBase(NamedTuple, Generic[FD, ED]):
410413
"""A parsed feed."""
411414

412-
#: The feed.
413-
feed: FeedData
414-
#: The entries.
415-
entries: Collection[EntryData]
415+
#: The feed; usually :class:`FeedData`.
416+
feed: FD
417+
#: The entries; usually :class:`EntryData`.
418+
entries: Collection[ED]
416419
#: The MIME type of the feed resource.
417420
#: Used by :meth:`~reader._parser.Parser.process_entry_pairs`
418421
#: to select an appropriate parser.
@@ -422,8 +425,16 @@ class ParsedFeed(NamedTuple):
422425
caching_info: JSONType | None = None
423426

424427

428+
class EntryPairBase(NamedTuple, Generic[ED]):
429+
new: ED
430+
old: EntryForUpdate | None
431+
432+
433+
ParseResult = ParseResultBase[FeedForUpdate, FeedData, EntryData, ParseError]
434+
ParsedFeed = ParsedFeedBase[FeedData, EntryData]
435+
EntryPair = EntryPairBase[EntryData]
436+
425437
FeedAndEntries = tuple[FeedData, Collection[EntryData]]
426-
EntryPair = tuple[EntryData, Optional[EntryForUpdate]]
427438

428439

429440
class ParserType(Protocol[T_cv]): # pragma: no cover

src/reader/_parser/_lazy.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from typing import cast
1313
from typing import ContextManager
1414

15+
from .._types import EntryData
16+
from .._types import FeedData
1517
from .._types import FeedForUpdate
1618
from .._utils import MapFunction
1719
from ..exceptions import InvalidFeedURLError
@@ -25,6 +27,7 @@
2527
from . import NotModified
2628
from . import ParsedFeed
2729
from . import ParseResult
30+
from . import ParseResultBase
2831
from . import ParserType
2932
from . import RetrievedFeed
3033
from . import RetrieveError
@@ -86,7 +89,7 @@ def parallel(
8689
self,
8790
feeds: Iterable[F],
8891
map: MapFunction[Any, Any] = map,
89-
) -> Iterable[ParseResult[F, ParseError]]:
92+
) -> Iterable[ParseResult]:
9093
"""Retrieve and parse many feeds, possibly in parallel.
9194
9295
Yields the parsed feeds, as soon as they are ready.
@@ -130,7 +133,7 @@ def parallel(
130133
value.__cause__ = e.__cause__
131134
result = result._replace(value=value)
132135

133-
yield cast(ParseResult[F, ParseError], result)
136+
yield cast(ParseResult, result)
134137

135138
def __call__(
136139
self, url: str, caching_info: JSONType | None = None
@@ -252,7 +255,7 @@ def _retrieve(
252255

253256
def parse_fn(
254257
self, result: RetrieveResult[F, Any, Exception]
255-
) -> ParseResult[F, Exception]:
258+
) -> ParseResultBase[F, FeedData, EntryData, Exception]:
256259
""":meth:`parse` wrapper used by :meth:`parallel`.
257260
258261
Takes one argument and does not raise exceptions.
@@ -292,7 +295,7 @@ def parse_fn(
292295
log.debug("parse_fn(): got unexpected error: %s: %s", type(e).__name__, e)
293296
value = e
294297

295-
return ParseResult(feed, value, http_info)
298+
return ParseResultBase(feed, value, http_info)
296299

297300
def parse(self, url: str, retrieved: RetrievedFeed[Any]) -> ParsedFeed:
298301
"""Parse a retrieved feed.

src/reader/_update.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
from itertools import tee
1313
from typing import Any
1414
from typing import NamedTuple
15-
from typing import Optional
1615
from typing import TYPE_CHECKING
1716

17+
from ._parser import EntryPair
1818
from ._parser import ParseResult
1919
from ._types import EntryData
2020
from ._types import EntryForUpdate
@@ -46,9 +46,6 @@
4646
HASH_CHANGED_LIMIT = 24
4747

4848

49-
EntryPairs = Iterable[tuple[EntryData, Optional[EntryForUpdate]]]
50-
51-
5249
@dataclass(frozen=True)
5350
class Decider:
5451
"""Decide whether a feed or entry should be updated.
@@ -89,8 +86,8 @@ def make_intents(
8986
now: datetime,
9087
global_now: datetime,
9188
config: UpdateConfig,
92-
result: ParseResult[FeedForUpdate, ParseError],
93-
entry_pairs: EntryPairs,
89+
result: ParseResult,
90+
entry_pairs: Iterable[EntryPair],
9491
) -> tuple[FeedUpdateIntent, Iterable[EntryUpdateIntent]]:
9592
decider = cls(
9693
old_feed,
@@ -194,7 +191,9 @@ def debug(msg: str, *args: Any) -> None:
194191
debug("entry not updated, skipping")
195192
return None
196193

197-
def get_entries_to_update(self, pairs: EntryPairs) -> Iterable[EntryUpdateIntent]:
194+
def get_entries_to_update(
195+
self, pairs: Iterable[EntryPair]
196+
) -> Iterable[EntryUpdateIntent]:
198197
for feed_order, (new, old) in reversed(list(enumerate(pairs))):
199198
# This may fail if we ever implement changing the feed URL
200199
# in response to a permanent redirect.
@@ -235,8 +234,8 @@ def get_feed_to_update(
235234

236235
def update(
237236
self,
238-
result: ParseResult[FeedForUpdate, ParseError],
239-
entry_pairs: EntryPairs,
237+
result: ParseResult,
238+
entry_pairs: Iterable[EntryPair],
240239
) -> tuple[FeedUpdateIntent, Iterable[EntryUpdateIntent]]:
241240

242241
# TODO: move entries_to_update in FeedToUpdate, maybe?
@@ -394,7 +393,7 @@ def update(self, filter: FeedFilter) -> Iterable[UpdateResult]:
394393
# Storing the exceptions until the end of the generator
395394
# might cause memory issues, but the caller may need to raise them.
396395
# TODO: Rework update pipeline to support process_feed_for_update() exceptions.
397-
parser_process_feeds_for_update_errors = []
396+
parser_process_feeds_for_update_errors: list[ParseResult] = []
398397

399398
def parser_process_feeds_for_update(
400399
feeds: Iterable[FeedForUpdate],
@@ -428,7 +427,7 @@ def parser_process_feeds_for_update(
428427
def process_parse_result(
429428
self,
430429
config: UpdateConfig,
431-
result: ParseResult[FeedForUpdate, ParseError],
430+
result: ParseResult,
432431
) -> tuple[str, UpdatedFeed | None | Exception]:
433432
feed, value, _ = result
434433

@@ -470,13 +469,13 @@ def process_parse_result(
470469

471470
return feed.url, UpdatedFeed(feed.url, *counts, total - sum(counts))
472471

473-
def get_entry_pairs(self, result: ParsedFeed) -> EntryPairs:
472+
def get_entry_pairs(self, result: ParsedFeed) -> Iterable[EntryPair]:
474473
# give storage a chance to consume entries in a streaming fashion
475474
entries1, entries2 = tee(result.entries)
476475
entries_for_update = self.reader._storage.get_entries_for_update(
477476
(e.feed_url, e.id) for e in entries1
478477
)
479-
return zip(entries2, entries_for_update, strict=True)
478+
return map(EntryPair._make, zip(entries2, entries_for_update, strict=True))
480479

481480
def update_feed(
482481
self,

0 commit comments

Comments
 (0)