Mercurial > hg > Feedworm
annotate backend/sqlalchemy/SqlAlchemyBackend.py @ 169:91a24f499318
introdue an abstraction for the name of the database so it can be changed via commandline parameter
author | dirk |
---|---|
date | Fri, 09 Sep 2011 14:52:54 +0200 |
parents | f4708d38419c |
children | 3bcf39181f6e |
rev | line source |
---|---|
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
1 |
145
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
2 from Feed import Feed |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
3 from FeedEntry import FeedEntry |
168
f4708d38419c
move methods around so the SqlAlchemyBackend only needs to import the class
dirk
parents:
167
diff
changeset
|
4 from FeedUpdater import FeedUpdater |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
5 from Preferences import Preferences |
155
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
6 from backend.AbstractBackend import AbstractBackend |
128
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
7 from datetime import datetime, timedelta |
145
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
8 from sqlalchemy.orm import joinedload |
128
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
9 from sqlalchemy.sql import and_ |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
10 import Database |
128
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
11 import Mapping |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
12 import logging |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
13 import util |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
14 |
155
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
15 class SqlAlchemyBackend(AbstractBackend): |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
16 ''' |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
17 Backend that uses sqlalchemy for persistence |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
18 ''' |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
19 |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
20 def __init__(self): |
155
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
21 AbstractBackend.__init__(self) |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
22 self._initLogging() |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
23 self.session = Database.createSession() |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
24 self.prefs = Preferences(self.session) |
121
510a5d00e98a
re-enabled AddFeed - does not work yet
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
119
diff
changeset
|
25 self.updater = None |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
26 |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
27 def _initLogging(self): |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
28 logging.getLogger("sqlalchemy.orm").setLevel(logging.WARN) |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
29 |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
30 sqlalchemyLogLevel = logging.ERROR |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
31 if util.databaseLoggingEnabled(): |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
32 sqlalchemyLogLevel = logging.INFO |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
33 logging.getLogger("sqlalchemy").setLevel(sqlalchemyLogLevel) |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
34 |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
35 def preferences(self): |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
36 return self.prefs |
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
37 |
154 | 38 def dispose(self): |
39 # save all uncommitted state, just in case | |
40 self.session.commit() | |
41 self.session.close() | |
42 | |
153
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
43 # |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
44 # handling of feeds |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
45 # |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
46 |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
47 def getFeeds(self): |
145
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
48 if self.preferences().showOnlyUnreadFeeds(): |
151
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
49 self.feeds = self._getUnreadFeeds() |
145
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
50 else: |
151
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
51 self.feeds = Feed.all(self.session) |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
52 return self.feeds |
145
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
53 |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
54 def _getUnreadFeeds(self): |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
55 query = self.session.query(FeedEntry).filter(FeedEntry.read == 0) |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
56 queryWithOptions = query.options(joinedload("feed")) |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
57 result = queryWithOptions.all() |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
58 return self._collectFeeds(result) |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
59 |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
60 def _collectFeeds(self, feedEntries): |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
61 feeds = [entry.feed for entry in feedEntries] |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
62 uniqueFeeds = set(feeds) |
71c5dc02ff87
move the code from FeedList into the backend class
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
128
diff
changeset
|
63 return list(uniqueFeeds) |
119
04a730f9d07d
move all sqlalchemy related classes to the respective sub-package. use a backend to abstract from access to the data
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
diff
changeset
|
64 |
155
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
65 def _retrieveEntriesForSelectedFeed(self, hideReadEntries): |
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
66 return self.selectedFeed.entriesSortedByUpdateDate(hideReadEntries) |
151
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
67 |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
68 def markSelectedFeedAsRead(self): |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
69 self.selectedFeed.markAllEntriesRead() |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
70 self.session.commit() |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
71 |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
72 def deleteSelectedFeed(self): |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
73 self.session.delete(self.selectedFeed) |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
74 self.session.commit() |
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
75 |
154 | 76 def createFeed(self, url): |
77 try: | |
167
a3c945ce434c
adjust the sqlalchemy backend to the changes in AbstractFeedUpdater
dirk
parents:
155
diff
changeset
|
78 newFeed = Feed(url) |
a3c945ce434c
adjust the sqlalchemy backend to the changes in AbstractFeedUpdater
dirk
parents:
155
diff
changeset
|
79 self.session.add(newFeed) |
a3c945ce434c
adjust the sqlalchemy backend to the changes in AbstractFeedUpdater
dirk
parents:
155
diff
changeset
|
80 |
168
f4708d38419c
move methods around so the SqlAlchemyBackend only needs to import the class
dirk
parents:
167
diff
changeset
|
81 FeedUpdater(self.preferences(), self.session).update(newFeed) |
154 | 82 self.session.commit() |
83 except AttributeError as ae: | |
84 self.session.rollback() | |
85 raise ae | |
86 | |
87 def updateAllFeeds(self): | |
167
a3c945ce434c
adjust the sqlalchemy backend to the changes in AbstractFeedUpdater
dirk
parents:
155
diff
changeset
|
88 FeedUpdater.updateAllFeeds(self.preferences(), self.session) |
154 | 89 self.session.commit() |
90 | |
91 def updateFeed(self, feed, changes): | |
92 feed.takeChangesFrom(changes) | |
93 feed.incrementNextUpdateDate() | |
94 self.session.commit() | |
95 | |
153
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
96 # |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
97 # handling of the selected feed entry |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
98 # |
155
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
99 |
a05719a6175e
move common functionality into an abstract backend class, have both backends inherit from it. Implement enough of the couchdb backend that reading feeds (and marking feed entries as read) is possible
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
154
diff
changeset
|
100 def _markSelectedFeedEntryRead(self): |
153
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
101 self.selectedFeedEntry.markRead() |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
102 |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
103 def markFeedEntriesAsRead(self, indices): |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
104 for index in indices: |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
105 self.feedEntries[index].markRead() |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
106 self.session.commit() |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
107 |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
108 def toggleSelectedFeedEntryRead(self): |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
109 self.selectedFeedEntry.toggleRead() |
65c4bb6d5add
move management of the selected feed entry into the backend - sqlachemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
151
diff
changeset
|
110 self.session.commit() |
151
bca9341dc67f
move the selected feed into the backend - sqlalchemy backend works, couchdb backend currently broken
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
146
diff
changeset
|
111 |
128
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
112 def expireFeedEntries(self): |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
113 logger = logging.getLogger("feedupdater") |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
114 expireDate = self._calculateExpireDate() |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
115 logger.info("expiring entries older than " + str(expireDate)) |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
116 feedEntry = Mapping.feedEntryTable |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
117 deleteStatement = feedEntry.delete().where( |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
118 and_(feedEntry.c.create_timestamp < expireDate, feedEntry.c.read == 1) |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
119 ) |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
120 deleteStatement.execute() |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
121 self.session.commit() |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
122 |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
123 def _calculateExpireDate(self): |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
124 now = datetime.now() |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
125 daysToKeepFeedEntries = self.prefs.daysToKeepFeedEntries() |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
126 delta = timedelta(days=daysToKeepFeedEntries) |
32a173cb081c
move updating the feeds to the backend
Dirk Olmes <dirk@xanthippe.ping.de>
parents:
126
diff
changeset
|
127 return now - delta |