]>
apps.delaporte.us Git - matrix-shop-bot.git/blob - matrix_shop_bot/db.py
60339ee13e9d290c71d6ea395046b733a63502d2
3 from datetime
import datetime
as dt
4 from decimal
import Decimal
5 from typing
import List
7 from sqlmodel
import Field
, Session
, SQLModel
, create_engine
, select
, text
, Relationship
, func
9 from sqlite3
import IntegrityError
13 DATE_FORMAT
= '%Y-%m-%d'
16 return dt
.now().strftime(DATE_FORMAT
)
19 logger
= logging
.getLogger(__name__
)
21 class ItemStoreLink(SQLModel
, table
=True):
22 item_id
: int |
None = Field(default
=None, foreign_key
="item.id", primary_key
=True)
23 store_id
: int |
None = Field(default
=None, foreign_key
="store.id", primary_key
=True)
26 class Item(SQLModel
, table
=True):
27 id: int |
None = Field(default
=None, primary_key
=True)
28 name
: str = Field(default
=None, unique
=True)
29 price
: Decimal |
None = Field(decimal_places
=2)
30 stores
: list["Store"] = Relationship(back_populates
="shopping_list", link_model
=ItemStoreLink
)
31 purchase_date
: dt |
None = Field(default
=None)
33 return f
'{self.name} (#{self.id})'
35 class PreferStore(SQLModel
, table
=True):
36 store_id
: int |
None = Field(default
=None, foreign_key
="store.id", primary_key
=True)
38 class Store(SQLModel
, table
=True):
39 id: int |
None = Field(default
=None, primary_key
=True)
40 name
: str = Field(default
=None, unique
=True)
41 shopping_list
: list["Item"] = Relationship(back_populates
="stores", link_model
=ItemStoreLink
)
42 # purchase_history: list["PurchaseRecord"] = Relationship(back_populates="store")
44 return f
'{self.name} (#{self.id})'
46 def get_pretty_shopping_list(self
) -> List
[str]:
48 results
.append(f
' ## {self.name}')
49 items
= self
.shopping_list
51 results
.append(f
'-- Nothing to shop for at {self.name} --')
54 if(item
.purchase_date
):
55 results
.append(f
' - [x] {item}')
57 results
.append(f
' - [ ] {item}')
61 # May get used later...
62 class PurchaseRecord(SQLModel
, table
=True):
63 id: int |
None = Field(default
=None, primary_key
=True)
64 name
: str = Field(default
=None)
65 price
: Decimal |
None = Field(decimal_places
=2)
66 store
: str |
None = Field(default
=None)
67 purchase_date
: dt |
None = Field(default
=None)
69 date
= self
.purchase_date
.strftime(DATE_FORMAT
)
70 return f
'Bought {self.name} at {self.store} on {date}'
72 def _get_with_id(cls
, item_id
):
73 return select(cls
).where(cls
.id == item_id
)
75 def _get_with_name(cls
, name
):
76 return select(cls
).where(func
.lower(cls
.name
) == func
.lower(name
))
80 def __init__(self
, db_file
=None):
82 db_file
= os
.path
.join(os
.environ
.get('MATRIX_DATA_DIR', ''), f
'{MODULE}.db')
85 logger
.debug(f
'Shop bot is using db file {self.file}')
86 self
.engine
= self
.get_engine()
87 self
.last_store_id
= None
89 def _set_pref_store_id(self
, store_id
):
90 with Session(self
.engine
) as session
:
91 prefs
= session
.exec(select(PreferStore
)).all()
95 pref
.store_id
= store_id
99 def _get_pref_store_id(self
):
100 with Session(self
.engine
) as session
:
101 pref
= session
.exec(select(PreferStore
)).one()
104 def _add_with_name(self
, cls
, name
):
107 with Session(self
.engine
) as session
:
114 # recover by returning the item_id
115 item
= session
.exec(select(Item
).where(Item
.name
== name
)).first()
120 def add_store(self
, name
):
121 store_id
= self
._add
_with
_name
(Store
, name
)
122 self
._set
_pref
_store
_id
(store_id
)
125 def add_item(self
, name
):
126 names
= name
.split(',')
128 item_id
= self
._add
_with
_name
(Item
, name
.strip())
129 store_id
= self
._get
_pref
_store
_id
()
130 self
.assign_store(item_id
, store_id
)
133 def assign_last_store(self
, item_id
):
134 store_id
= self
._get
_pref
_store
_id
()
135 self
.assign_store(item_id
, store_id
)
138 def assign_store(self
, item_id
, store_id
):
139 self
._set
_pref
_store
_id
(store_id
)
141 with Session(self
.engine
) as session
:
142 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
143 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
145 item
.stores
.append(store
)
148 pass # Already assigned
151 def list_purchases(self
, store_name
):
152 with Session(self
.engine
) as session
:
153 cmd
= select(PurchaseRecord
).where(PurchaseRecord
.store
== store_name
)
154 records
= session
.exec(cmd
).all()
157 def purchase_items(self
, item_ids
):
159 with Session(self
.engine
) as session
:
160 # Mark items as purchased
161 for item_id
in item_ids
.split(' '):
162 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
164 item
.purchase_date
= dt
.today()
166 # Track purchase history (locally only!)
167 # to generate recommended shopping lists
168 record
= PurchaseRecord()
169 record
.name
= item
.name
170 record
.price
= item
.price
171 record
.purchase_date
= item
.purchase_date
173 shopping_at
= self
._get
_pref
_store
_id
() # We bought it here
174 cmd
= select(Store
).where(Store
.id == shopping_at
)
175 store
= session
.exec(cmd
).one()
176 record
.store
= store
.name
179 results
.append(str(record
))
180 # results.append(str(item))
186 def clear_list(self
, store_id
, clear_all
=False):
187 with Session(self
.engine
) as session
:
188 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
189 for item
in store
.shopping_list
:
190 if clear_all
or item
.purchase_date
:
194 def get_stores(self
):
195 with Session(self
.engine
) as session
:
197 return session
.exec(cmd
).all()
199 def get_unsorted_list(self
):
202 with Session(self
.engine
) as session
:
203 cmd
= select(Item
).where(Item
.stores
== None)
204 items
= session
.exec(cmd
).all()
207 return [f
'No unsorted items!']
210 def get_last_shopping_list(self
) -> List
[str]:
211 store_id
= self
._get
_pref
_store
_id
()
212 with Session(self
.engine
) as session
:
213 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
215 return store
.get_pretty_shopping_list()
216 return [f
'Last shopping list store not found.']
218 def get_shopping_list(self
, store_name
) -> List
[str]:
220 with Session(self
.engine
) as session
:
221 store
= session
.exec(_get_with_name(Store
, store_name
)).first()
223 return [f
'Store {store_name} not found.']
224 results
= store
.get_pretty_shopping_list()
225 self
._set
_pref
_store
_id
(store
.id)
228 def remove_item(self
, item_id
):
229 with Session(self
.engine
) as session
:
230 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
234 def remove_store(self
, store_id
):
235 with Session(self
.engine
) as session
:
236 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
237 session
.delete(store
)
240 def get_engine(self
):
241 return create_engine(f
"sqlite:///{self.file}")
244 SQLModel
.metadata
.create_all(self
.engine
)