|
3 | 3 | import json |
4 | 4 | import sqlite3 |
5 | 5 | from collections.abc import Iterable |
| 6 | +from collections.abc import Mapping |
6 | 7 | from datetime import datetime |
7 | 8 | from functools import partial |
8 | 9 | from typing import Any |
| 10 | +from typing import cast |
| 11 | +from typing import NewType |
9 | 12 | from typing import TYPE_CHECKING |
10 | 13 |
|
11 | 14 | from .._types import FeedFilter |
|
26 | 29 | from ._sqlite_utils import adapt_datetime |
27 | 30 | from ._sqlite_utils import convert_timestamp |
28 | 31 | from ._sqlite_utils import rowcount_exactly_one |
| 32 | +from ._sqlite_utils import SQLiteValue |
29 | 33 | from ._tags import feed_tags_filter |
30 | 34 |
|
31 | 35 | if TYPE_CHECKING: # pragma: no cover |
|
34 | 38 | StorageBase = object |
35 | 39 |
|
36 | 40 |
|
| 41 | +FeedDict = NewType('FeedDict', Mapping[str, SQLiteValue]) |
| 42 | + |
| 43 | + |
37 | 44 | class FeedsMixin(StorageBase): |
38 | 45 | @wrap_exceptions() |
39 | 46 | def add_feed(self, url: str, added: datetime) -> None: |
@@ -222,48 +229,21 @@ def set_feed_stale(self, url: str, stale: bool) -> None: |
222 | 229 | ) |
223 | 230 | rowcount_exactly_one(cursor, lambda: FeedNotFoundError(url)) |
224 | 231 |
|
225 | | - @wrap_exceptions() |
226 | 232 | def update_feed(self, intent: FeedUpdateIntent) -> None: |
227 | | - url, _, _, value = intent |
228 | | - |
229 | | - context: dict[str, Any] = { |
230 | | - 'url': url, |
231 | | - 'last_retrieved': adapt_datetime(intent.last_retrieved), |
232 | | - 'update_after': adapt_datetime(intent.update_after), |
233 | | - } |
234 | | - |
235 | | - if isinstance(value, FeedToUpdate): |
236 | | - assert url == value.feed.url, "updating feed URL not supported" |
237 | | - |
238 | | - context.update( |
239 | | - value._asdict(), |
240 | | - caching_info=( |
241 | | - json.dumps(value.caching_info) if value.caching_info else None |
242 | | - ), |
243 | | - ) |
244 | | - feed = context.pop('feed') |
245 | | - context.update( |
246 | | - feed._asdict(), |
247 | | - updated=adapt_datetime(feed.updated) if feed.updated else None, |
248 | | - last_updated=adapt_datetime(value.last_updated), |
249 | | - data_hash=feed.hash, |
250 | | - ) |
251 | | - context.pop('hash', None) |
| 233 | + return self.update_feed_dict(feed_update_intent_to_dict(intent)) |
252 | 234 |
|
253 | | - context['stale'] = 0 |
254 | | - |
255 | | - if isinstance(value, ExceptionInfo): |
256 | | - context['last_exception'] = json.dumps(value._asdict()) |
257 | | - else: |
258 | | - assert isinstance(value, FeedToUpdate | None) |
259 | | - context['last_exception'] = None |
| 235 | + @wrap_exceptions() |
| 236 | + def update_feed_dict(self, intent: FeedDict) -> None: |
| 237 | + """Low level update_feed_dict() used by database sync.""" |
260 | 238 |
|
261 | | - expressions = [f"{n} = :{n}" for n in context if n != 'url'] |
| 239 | + expressions = [f"{n} = :{n}" for n in intent if n != 'url'] |
262 | 240 | query = f"UPDATE feeds SET {', '.join(expressions)} WHERE url = :url;" |
263 | 241 |
|
264 | 242 | with self.get_db() as db: |
265 | | - cursor = db.execute(query, context) |
| 243 | + cursor = db.execute(query, intent) |
266 | 244 |
|
| 245 | + url = intent['url'] |
| 246 | + assert isinstance(url, str) |
267 | 247 | rowcount_exactly_one(cursor, lambda: FeedNotFoundError(url)) |
268 | 248 |
|
269 | 249 |
|
@@ -360,3 +340,41 @@ def feed_filter(query: Query, filter: FeedFilter) -> dict[str, Any]: |
360 | 340 | context.update(update_after=adapt_datetime(update_after)) |
361 | 341 |
|
362 | 342 | return context |
| 343 | + |
| 344 | + |
| 345 | +def feed_update_intent_to_dict(intent: FeedUpdateIntent) -> FeedDict: |
| 346 | + url, _, _, value = intent |
| 347 | + |
| 348 | + context: dict[str, Any] = { |
| 349 | + 'url': url, |
| 350 | + 'last_retrieved': adapt_datetime(intent.last_retrieved), |
| 351 | + 'update_after': adapt_datetime(intent.update_after), |
| 352 | + } |
| 353 | + |
| 354 | + if isinstance(value, FeedToUpdate): |
| 355 | + assert url == value.feed.url, "updating feed URL not supported" |
| 356 | + |
| 357 | + context.update( |
| 358 | + value._asdict(), |
| 359 | + caching_info=( |
| 360 | + json.dumps(value.caching_info) if value.caching_info else None |
| 361 | + ), |
| 362 | + ) |
| 363 | + feed = context.pop('feed') |
| 364 | + context.update( |
| 365 | + feed._asdict(), |
| 366 | + updated=adapt_datetime(feed.updated) if feed.updated else None, |
| 367 | + last_updated=adapt_datetime(value.last_updated), |
| 368 | + data_hash=feed.hash, |
| 369 | + ) |
| 370 | + context.pop('hash', None) |
| 371 | + |
| 372 | + context['stale'] = 0 |
| 373 | + |
| 374 | + if isinstance(value, ExceptionInfo): |
| 375 | + context['last_exception'] = json.dumps(value._asdict()) |
| 376 | + else: |
| 377 | + assert isinstance(value, FeedToUpdate | None) |
| 378 | + context['last_exception'] = None |
| 379 | + |
| 380 | + return cast(FeedDict, context) |
0 commit comments