oldnews 0.3.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {oldnews-0.3.0 → oldnews-0.5.0}/PKG-INFO +3 -2
  2. {oldnews-0.3.0 → oldnews-0.5.0}/pyproject.toml +3 -2
  3. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/commands/__init__.py +20 -2
  4. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/commands/main.py +65 -2
  5. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/__init__.py +8 -0
  6. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/db.py +9 -36
  7. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/last_grab.py +5 -2
  8. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_articles.py +78 -9
  9. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_folders.py +5 -2
  10. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_subscriptions.py +31 -23
  11. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/local_unread.py +2 -2
  12. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/navigation_state.py +5 -2
  13. oldnews-0.5.0/src/oldnews/data/tools.py +45 -0
  14. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/providers/main.py +20 -2
  15. oldnews-0.5.0/src/oldnews/screens/folder_input.py +62 -0
  16. oldnews-0.5.0/src/oldnews/screens/information_display.py +86 -0
  17. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/main.py +342 -36
  18. oldnews-0.5.0/src/oldnews/screens/new_subscription.py +122 -0
  19. {oldnews-0.3.0/src/oldnews/sync → oldnews-0.5.0/src/oldnews}/sync.py +74 -16
  20. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/navigation.py +19 -0
  21. oldnews-0.3.0/src/oldnews/sync/__init__.py +0 -11
  22. {oldnews-0.3.0 → oldnews-0.5.0}/README.md +0 -0
  23. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/__init__.py +0 -0
  24. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/__main__.py +0 -0
  25. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/auth.py +0 -0
  26. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/config.py +0 -0
  27. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/locations.py +0 -0
  28. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/data/reset.py +0 -0
  29. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/oldnews.py +0 -0
  30. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/providers/__init__.py +0 -0
  31. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/py.typed +0 -0
  32. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/__init__.py +0 -0
  33. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/screens/login.py +0 -0
  34. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/__init__.py +0 -0
  35. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/_after_highlight.py +0 -0
  36. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/article_content.py +0 -0
  37. {oldnews-0.3.0 → oldnews-0.5.0}/src/oldnews/widgets/article_list.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oldnews
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: A terminal-based client for TheOldReader
5
5
  Keywords: atom,client,Google Reader,RSS,TheOldReader,terminal,news-reader,news
6
6
  Author: Dave Pearson
@@ -15,8 +15,9 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Programming Language :: Python :: 3.14
16
16
  Classifier: Typing :: Typed
17
17
  Requires-Dist: html-to-markdown>=2.15.0
18
- Requires-Dist: oldas>=0.3.1
18
+ Requires-Dist: oldas>=0.6.0
19
19
  Requires-Dist: textual>=6.3.0
20
+ Requires-Dist: textual-autocomplete>=4.0.6
20
21
  Requires-Dist: textual-enhanced>=1.2.0
21
22
  Requires-Dist: typedal>=4.2.2
22
23
  Requires-Dist: xdg-base-dirs>=6.0.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oldnews"
3
- version = "0.3.0"
3
+ version = "0.5.0"
4
4
  description = "A terminal-based client for TheOldReader"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -20,8 +20,9 @@ keywords = [
20
20
  ]
21
21
  dependencies = [
22
22
  "html-to-markdown>=2.15.0",
23
- "oldas>=0.3.1",
23
+ "oldas>=0.6.0",
24
24
  "textual>=6.3.0",
25
+ "textual-autocomplete>=4.0.6",
25
26
  "textual-enhanced>=1.2.0",
26
27
  "typedal>=4.2.2",
27
28
  "xdg-base-dirs>=6.0.2",
@@ -3,30 +3,48 @@
3
3
  ##############################################################################
4
4
  # Local imports.
5
5
  from .main import (
6
+ AddSubscription,
7
+ Copy,
8
+ CopyArticleToClipboard,
9
+ CopyFeedToClipboard,
10
+ CopyHomePageToClipboard,
6
11
  Escape,
12
+ Information,
7
13
  MarkAllRead,
14
+ MoveSubscription,
8
15
  Next,
9
16
  NextUnread,
10
17
  OpenArticle,
11
- OpenOrigin,
18
+ OpenHomePage,
12
19
  Previous,
13
20
  PreviousUnread,
14
21
  RefreshFromTheOldReader,
22
+ Remove,
23
+ Rename,
15
24
  ToggleShowAll,
16
25
  )
17
26
 
18
27
  ##############################################################################
19
28
  # Exports.
20
29
  __all__ = [
30
+ "AddSubscription",
31
+ "Copy",
32
+ "CopyArticleToClipboard",
33
+ "CopyFeedToClipboard",
34
+ "CopyHomePageToClipboard",
21
35
  "Escape",
36
+ "Information",
22
37
  "MarkAllRead",
23
38
  "Next",
24
39
  "NextUnread",
40
+ "MoveSubscription",
25
41
  "OpenArticle",
26
- "OpenOrigin",
42
+ "OpenHomePage",
27
43
  "Previous",
28
44
  "PreviousUnread",
29
45
  "RefreshFromTheOldReader",
46
+ "Rename",
47
+ "Remove",
30
48
  "ToggleShowAll",
31
49
  ]
32
50
 
@@ -64,8 +64,8 @@ class OpenArticle(Command):
64
64
 
65
65
 
66
66
  ##############################################################################
67
- class OpenOrigin(Command):
68
- """Open the origin for the current article in the web browser"""
67
+ class OpenHomePage(Command):
68
+ """Open the home page for the current subscription in the web browser"""
69
69
 
70
70
  BINDING_KEY = "O"
71
71
 
@@ -77,4 +77,67 @@ class MarkAllRead(Command):
77
77
  BINDING_KEY = "R"
78
78
 
79
79
 
80
+ ##############################################################################
81
+ class CopyHomePageToClipboard(Command):
82
+ """Copy the URL of the current subscription's home page to the clipboard"""
83
+
84
+ BINDING_KEY = "f3"
85
+
86
+
87
+ ##############################################################################
88
+ class CopyFeedToClipboard(Command):
89
+ """Copy the URL of the current subscription's feed to the clipboard"""
90
+
91
+ BINDING_KEY = "f4"
92
+
93
+
94
+ ##############################################################################
95
+ class CopyArticleToClipboard(Command):
96
+ """Copy the URL for the current article to the clipboard"""
97
+
98
+ BINDING_KEY = "f5"
99
+
100
+
101
+ ##############################################################################
102
+ class Copy(Command):
103
+ """Copy a URL to the clipboard depending on the context"""
104
+
105
+ BINDING_KEY = "ctrl+c"
106
+
107
+
108
+ ##############################################################################
109
+ class AddSubscription(Command):
110
+ """Add a subscription feed"""
111
+
112
+ BINDING_KEY = "plus"
113
+
114
+
115
+ ##############################################################################
116
+ class Rename(Command):
117
+ """Rename the current folder or subscription"""
118
+
119
+ BINDING_KEY = "f6"
120
+
121
+
122
+ ##############################################################################
123
+ class Remove(Command):
124
+ """Remove the current folder or subscription"""
125
+
126
+ BINDING_KEY = "delete"
127
+
128
+
129
+ ##############################################################################
130
+ class MoveSubscription(Command):
131
+ """Move the current subscription to folder"""
132
+
133
+ BINDING_KEY = "f7"
134
+
135
+
136
+ ##############################################################################
137
+ class Information(Command):
138
+ """Show low-level information about the selected item"""
139
+
140
+ BINDING_KEY = "f8"
141
+
142
+
80
143
  ### main.py ends here
@@ -17,6 +17,10 @@ from .local_articles import (
17
17
  get_unread_article_ids,
18
18
  locally_mark_article_ids_read,
19
19
  locally_mark_read,
20
+ move_subscription_articles,
21
+ remove_folder_from_articles,
22
+ remove_subscription_articles,
23
+ rename_folder_for_articles,
20
24
  save_local_articles,
21
25
  )
22
26
  from .local_folders import get_local_folders, save_local_folders
@@ -43,7 +47,11 @@ __all__ = [
43
47
  "locally_mark_read",
44
48
  "locally_mark_article_ids_read",
45
49
  "LocalUnread",
50
+ "move_subscription_articles",
46
51
  "remember_we_last_grabbed_at",
52
+ "remove_folder_from_articles",
53
+ "remove_subscription_articles",
54
+ "rename_folder_for_articles",
47
55
  "reset_data",
48
56
  "save_configuration",
49
57
  "save_local_articles",
@@ -3,14 +3,11 @@
3
3
  ##############################################################################
4
4
  # Python imports.
5
5
  from pathlib import Path
6
- from typing import Any
7
6
 
8
7
  ##############################################################################
9
8
  # TypeDAL imports.
10
- from typedal import TypeDAL, TypedField, TypedTable
9
+ from typedal import TypeDAL
11
10
  from typedal.config import TypeDALConfig
12
- from typedal.helpers import get_field
13
- from typedal.types import Field
14
11
 
15
12
  ##############################################################################
16
13
  # Local imports.
@@ -20,6 +17,7 @@ from .local_folders import LocalFolder
20
17
  from .local_subscriptions import LocalSubscription, LocalSubscriptionCategory
21
18
  from .locations import data_dir
22
19
  from .navigation_state import NavigationState
20
+ from .tools import safely_index
23
21
 
24
22
 
25
23
  ##############################################################################
@@ -32,31 +30,6 @@ def db_file() -> Path:
32
30
  return data_dir() / "oldnews.db"
33
31
 
34
32
 
35
- ##############################################################################
36
- def _safely_index(
37
- table: type[TypedTable], name: str, field: str | Field | TypedField[Any]
38
- ) -> None:
39
- """Create an index on a type, but handle errors.
40
-
41
- Args:
42
- table: The table to create the index against.
43
- name: The name of the index.
44
- field: The field to index.
45
-
46
- Notes:
47
- From what I can gather TypeDAL *should* only create the index if it
48
- doesn't exist. Instead it throws an error if it exists. So here I
49
- swallow the `RuntimeError`. Hopefully there is a better way and I've
50
- just missed it.
51
- """
52
- try:
53
- table.create_index(
54
- name, get_field(field) if isinstance(field, TypedField) else field
55
- )
56
- except RuntimeError:
57
- pass
58
-
59
-
60
33
  ##############################################################################
61
34
  def initialise_database() -> TypeDAL:
62
35
  """Create the database.
@@ -73,20 +46,20 @@ def initialise_database() -> TypeDAL:
73
46
  dal = TypeDAL(f"sqlite://{db_file()}", folder=data_dir(), config=TypeDALConfig())
74
47
 
75
48
  dal.define(LocalArticle)
76
- _safely_index(LocalArticle, "idx_local_article_article_id", LocalArticle.article_id)
77
- _safely_index(
49
+ safely_index(LocalArticle, "idx_local_article_article_id", LocalArticle.article_id)
50
+ safely_index(
78
51
  LocalArticle,
79
52
  "idx_local_article_origin_stream_id",
80
53
  LocalArticle.origin_stream_id,
81
54
  )
82
55
 
83
56
  dal.define(LocalArticleCategory)
84
- _safely_index(
57
+ safely_index(
85
58
  LocalArticleCategory,
86
59
  "idx_local_article_category_article",
87
60
  LocalArticleCategory.article,
88
61
  )
89
- _safely_index(
62
+ safely_index(
90
63
  LocalArticleCategory,
91
64
  "idx_local_article_category_category",
92
65
  LocalArticleCategory.category,
@@ -97,19 +70,19 @@ def initialise_database() -> TypeDAL:
97
70
  dal.define(LocalFolder)
98
71
 
99
72
  dal.define(LocalSubscription)
100
- _safely_index(
73
+ safely_index(
101
74
  LocalSubscription,
102
75
  "idx_local_subscription_subscription_id",
103
76
  LocalSubscription.subscription_id,
104
77
  )
105
78
 
106
79
  dal.define(LocalSubscriptionCategory)
107
- _safely_index(
80
+ safely_index(
108
81
  LocalSubscriptionCategory,
109
82
  "idx_local_subscription_category_subscription",
110
83
  LocalSubscriptionCategory.subscription,
111
84
  )
112
- _safely_index(
85
+ safely_index(
113
86
  LocalSubscriptionCategory,
114
87
  "idx_local_subscription_category_category_id",
115
88
  LocalSubscriptionCategory.category_id,
@@ -8,6 +8,10 @@ from datetime import datetime, timezone
8
8
  # TypeDAL imports.
9
9
  from typedal import TypedTable
10
10
 
11
+ ##############################################################################
12
+ # Local imports.
13
+ from .tools import commit
14
+
11
15
 
12
16
  ##############################################################################
13
17
  class LastGrabbed(TypedTable):
@@ -39,10 +43,9 @@ def remember_we_last_grabbed_at(grab_time: datetime | None = None) -> None:
39
43
  Note:
40
44
  If `grab_time` isn't supplied then it is recorded as now.
41
45
  """
42
- assert LastGrabbed._db is not None
43
46
  LastGrabbed.truncate()
44
47
  LastGrabbed.insert(at_time=grab_time or datetime.now(timezone.utc))
45
- LastGrabbed._db.commit()
48
+ commit(LastGrabbed)
46
49
 
47
50
 
48
51
  ### last_grab.py ends here
@@ -8,13 +8,17 @@ from typing import Iterable, Iterator, cast
8
8
 
9
9
  ##############################################################################
10
10
  # OldAS imports.
11
- from oldas import Article, Articles, Folder, State, Subscription
11
+ from oldas import Article, Articles, Folder, Folders, State, Subscription
12
12
  from oldas.articles import Alternate, Alternates, Direction, Origin, Summary
13
13
 
14
14
  ##############################################################################
15
15
  # TypeDAL imports.
16
16
  from typedal import TypedField, TypedTable, relationship
17
17
 
18
+ ##############################################################################
19
+ # Local imports.
20
+ from .tools import commit
21
+
18
22
 
19
23
  ##############################################################################
20
24
  class LocalArticle(TypedTable):
@@ -62,9 +66,8 @@ class LocalArticle(TypedTable):
62
66
  category: The category to add.
63
67
  """
64
68
  if not str(category) in self.categories:
65
- assert LocalArticleCategory._db is not None
66
69
  LocalArticleCategory.insert(article=self.id, category=str(category))
67
- LocalArticleCategory._db.commit()
70
+ commit(LocalArticleCategory)
68
71
 
69
72
  def remove_category(self, category: str | State) -> None:
70
73
  """Remove a given category from the local article.
@@ -73,12 +76,11 @@ class LocalArticle(TypedTable):
73
76
  category: The category to add.
74
77
  """
75
78
  if str(category) in self.categories:
76
- assert LocalArticleCategory._db is not None
77
79
  LocalArticleCategory.where(
78
80
  (LocalArticleCategory.article == self.id)
79
81
  & (LocalArticleCategory.category == str(category))
80
82
  ).delete()
81
- LocalArticleCategory._db.commit()
83
+ commit(LocalArticleCategory)
82
84
 
83
85
 
84
86
  ##############################################################################
@@ -113,7 +115,6 @@ def save_local_articles(articles: Articles) -> Articles:
113
115
  Returns:
114
116
  The articles.
115
117
  """
116
- assert LocalArticle._db is not None
117
118
  for article in articles:
118
119
  local_article = LocalArticle.update_or_insert(
119
120
  LocalArticle.article_id == article.id,
@@ -146,7 +147,7 @@ def save_local_articles(articles: Articles) -> Articles:
146
147
  for alternate in article.alternate
147
148
  ]
148
149
  )
149
- LocalArticle._db.commit()
150
+ commit(LocalArticle)
150
151
  return articles
151
152
 
152
153
 
@@ -336,7 +337,6 @@ def get_unread_article_ids() -> list[str]:
336
337
  ##############################################################################
337
338
  def clean_old_read_articles(cutoff: timedelta) -> int:
338
339
  """Clean up articles that are older than the given cutoff time."""
339
- assert LocalArticle._db is not None
340
340
  read = get_local_read_article_ids()
341
341
  retire_time = datetime.now() - cutoff
342
342
  cleaned = len(
@@ -344,8 +344,77 @@ def clean_old_read_articles(cutoff: timedelta) -> int:
344
344
  (LocalArticle.published < retire_time) & LocalArticle.id.belongs(read)
345
345
  ).delete()
346
346
  )
347
- LocalArticle._db.commit()
347
+ commit(LocalArticle)
348
348
  return cleaned
349
349
 
350
350
 
351
+ ##############################################################################
352
+ def rename_folder_for_articles(rename_from: str | Folder, rename_to: str) -> None:
353
+ """Rename a folder for all articles that are in that folder.
354
+
355
+ Args:
356
+ rename_from: The folder name to rename from.
357
+ rename_to: The folder name to rename to.
358
+ """
359
+ rename_from = Folders.full_id(rename_from)
360
+ rename_to = Folders.full_id(rename_to)
361
+ LocalArticleCategory.where(LocalArticleCategory.category == rename_from).update(
362
+ category=rename_to
363
+ )
364
+ commit(LocalArticleCategory)
365
+
366
+
367
+ ##############################################################################
368
+ def remove_folder_from_articles(folder: str | Folder) -> None:
369
+ """Remove a folder from being associated with all articles.
370
+
371
+ Args:
372
+ folder: The folder to remove from all articles.
373
+ """
374
+ LocalArticleCategory.where(
375
+ LocalArticleCategory.category == Folders.full_id(folder)
376
+ ).delete()
377
+ commit(LocalArticleCategory)
378
+
379
+
380
+ ##############################################################################
381
+ def move_subscription_articles(
382
+ subscription: Subscription,
383
+ from_folder: str | Folder | None,
384
+ to_folder: str | Folder | None,
385
+ ) -> None:
386
+ """Move the articles of a subscription from one folder to another.
387
+
388
+ Args:
389
+ subscription: The subscription whose articles we should move.
390
+ from_folder: The folder to move from.
391
+ to_folder: The folder to move to.
392
+ """
393
+ from_folder = (
394
+ Folders.full_id(from_folder) if from_folder is not None else from_folder
395
+ )
396
+ to_folder = Folders.full_id(to_folder) if to_folder is not None else to_folder
397
+ for article in LocalArticle.where(origin_stream_id=subscription.id).join().select():
398
+ if from_folder:
399
+ LocalArticleCategory.where(
400
+ (LocalArticleCategory.article == article.id)
401
+ & (LocalArticleCategory.category == from_folder)
402
+ ).delete()
403
+ commit(LocalArticleCategory)
404
+ if to_folder:
405
+ LocalArticleCategory.insert(article=article.id, category=to_folder)
406
+ commit(LocalArticleCategory)
407
+
408
+
409
+ ##############################################################################
410
+ def remove_subscription_articles(subscription: Subscription) -> None:
411
+ """Remove all the articles associated with the given subscription.
412
+
413
+ Args:
414
+ subscription: The subscription to remove the articles for.
415
+ """
416
+ LocalArticle.where(origin_stream_id=subscription.id).delete()
417
+ commit(LocalArticle)
418
+
419
+
351
420
  ### local_articles.py ends here
@@ -8,6 +8,10 @@ from oldas import Folder, Folders
8
8
  # TypeDAL imports.
9
9
  from typedal import TypedTable
10
10
 
11
+ ##############################################################################
12
+ # Local imports.
13
+ from .tools import commit
14
+
11
15
 
12
16
  ##############################################################################
13
17
  class LocalFolder(TypedTable):
@@ -42,12 +46,11 @@ def save_local_folders(folders: Folders) -> Folders:
42
46
  Returns:
43
47
  The folders.
44
48
  """
45
- assert LocalFolder._db is not None
46
49
  LocalFolder.truncate()
47
50
  LocalFolder.bulk_insert(
48
51
  [{"folder_id": folder.id, "sort_id": folder.sort_id} for folder in folders]
49
52
  )
50
- LocalFolder._db.commit()
53
+ commit(LocalFolder)
51
54
  return folders
52
55
 
53
56
 
@@ -13,6 +13,10 @@ from oldas.subscriptions import Categories, Category
13
13
  # TypeDAL imports.
14
14
  from typedal import TypedTable
15
15
 
16
+ ##############################################################################
17
+ # Local imports.
18
+ from .tools import commit
19
+
16
20
 
17
21
  ##############################################################################
18
22
  class LocalSubscription(TypedTable):
@@ -83,29 +87,33 @@ def save_local_subscriptions(subscriptions: Subscriptions) -> Subscriptions:
83
87
  Returns:
84
88
  The subscriptions.
85
89
  """
86
- assert LocalSubscription._db is not None
87
- for subscription in subscriptions:
88
- LocalSubscriptionCategory.where(subscription=subscription.id).delete()
89
- LocalSubscriptionCategory.bulk_insert(
90
- [
91
- {
92
- "subscription": subscription.id,
93
- "category_id": category.id,
94
- "label": category.label,
95
- }
96
- for category in subscription.categories
97
- ]
98
- )
99
- LocalSubscription.update_or_insert(
100
- LocalSubscription.subscription_id == subscription.id,
101
- subscription_id=subscription.id,
102
- title=subscription.title,
103
- sort_id=subscription.sort_id,
104
- first_item_time=subscription.first_item_time,
105
- url=subscription.url,
106
- html_url=subscription.html_url,
107
- )
108
- LocalSubscription._db.commit()
90
+ LocalSubscription.truncate()
91
+ LocalSubscriptionCategory.truncate()
92
+ LocalSubscription.bulk_insert(
93
+ [
94
+ {
95
+ "subscription_id": subscription.id,
96
+ "title": subscription.title,
97
+ "sort_id": subscription.sort_id,
98
+ "first_item_time": subscription.first_item_time,
99
+ "url": subscription.url,
100
+ "html_url": subscription.html_url,
101
+ }
102
+ for subscription in subscriptions
103
+ ]
104
+ )
105
+ LocalSubscriptionCategory.bulk_insert(
106
+ [
107
+ {
108
+ "subscription": subscription.id,
109
+ "category_id": category.id,
110
+ "label": category.label,
111
+ }
112
+ for subscription in subscriptions
113
+ for category in subscription.categories
114
+ ]
115
+ )
116
+ commit(LocalSubscription)
109
117
  return subscriptions
110
118
 
111
119
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ##############################################################################
4
4
  # OldAS imports.
5
- from oldas import Folders, Subscriptions, id_is_a_folder
5
+ from oldas import Folders, Subscriptions, id_is_a_feed
6
6
 
7
7
  ##############################################################################
8
8
  # Local imports.
@@ -41,7 +41,7 @@ def total_unread(unread: LocalUnread) -> int:
41
41
  Returns:
42
42
  The total unread.
43
43
  """
44
- return sum(count for category, count in unread.items() if id_is_a_folder(category))
44
+ return sum(count for category, count in unread.items() if id_is_a_feed(category))
45
45
 
46
46
 
47
47
  ### local_unread.py ends here
@@ -4,6 +4,10 @@
4
4
  # TypeDAL imports.
5
5
  from typedal import TypedTable
6
6
 
7
+ ##############################################################################
8
+ # Local imports.
9
+ from .tools import commit
10
+
7
11
 
8
12
  ##############################################################################
9
13
  class NavigationState(TypedTable):
@@ -33,10 +37,9 @@ def save_navigation_state(state: set[str]) -> None:
33
37
  Args:
34
38
  state: The state to save.
35
39
  """
36
- assert NavigationState._db is not None
37
40
  NavigationState.truncate()
38
41
  NavigationState.bulk_insert([{"expanded_folder_id": folder} for folder in state])
39
- NavigationState._db.commit()
42
+ commit(NavigationState)
40
43
 
41
44
 
42
45
  ### navigation_state.py ends here
@@ -0,0 +1,45 @@
1
+ """Database tool functions."""
2
+
3
+ ##############################################################################
4
+ # Python imports.
5
+ from typing import Any
6
+
7
+ ##############################################################################
8
+ # TypeDAL imports.
9
+ from typedal import TypedField, TypedTable
10
+ from typedal.helpers import get_db, get_field
11
+ from typedal.types import Field
12
+
13
+
14
+ ##############################################################################
15
+ def safely_index(
16
+ table: type[TypedTable], name: str, field: str | Field | TypedField[Any]
17
+ ) -> None:
18
+ """Create an index on a type, but handle errors.
19
+
20
+ Args:
21
+ table: The table to create the index against.
22
+ name: The name of the index.
23
+ field: The field to index.
24
+
25
+ Notes:
26
+ From what I can gather TypeDAL *should* only create the index if it
27
+ doesn't exist. Instead it throws an error if it exists. So here I
28
+ swallow the `RuntimeError`. Hopefully there is a better way and I've
29
+ just missed it.
30
+ """
31
+ try:
32
+ table.create_index(
33
+ name, get_field(field) if isinstance(field, TypedField) else field
34
+ )
35
+ except RuntimeError:
36
+ pass
37
+
38
+
39
+ ##############################################################################
40
+ def commit(table: type[TypedTable]) -> None:
41
+ """Commit changes."""
42
+ get_db(table()).commit()
43
+
44
+
45
+ ### tools.py ends here