]>
apps.delaporte.us Git - matrix-shop-bot.git/blob - matrix_shop_bot/db.py
d56bb6a0c7529b178a8dab60164ffbcbca07b63a
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
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 # May get used later...
47 class PurchaseRecord(SQLModel
, table
=True):
48 id: int |
None = Field(default
=None, primary_key
=True)
49 name
: str = Field(default
=None)
50 price
: Decimal |
None = Field(decimal_places
=2)
51 store
: str |
None = Field(default
=None)
52 purchase_date
: dt |
None = Field(default
=None)
54 date
= self
.purchase_date
.strftime(DATE_FORMAT
)
55 return f
'{self.name} at {self.store} on {date}'
57 def _get_with_id(cls
, item_id
):
58 return select(cls
).where(cls
.id == item_id
)
62 def __init__(self
, db_file
=None):
64 db_file
= os
.path
.join(os
.environ
.get('MATRIX_DATA_DIR', ''), f
'{MODULE}.db')
67 logger
.debug(f
'Shop bot is using db file {self.file}')
68 self
.engine
= self
.get_engine()
69 self
.last_store_id
= None
71 def _set_pref_store_id(self
, store_id
):
72 with Session(self
.engine
) as session
:
73 prefs
= session
.exec(select(PreferStore
)).all()
77 pref
.store_id
= store_id
81 def _get_pref_store_id(self
):
82 with Session(self
.engine
) as session
:
83 pref
= session
.exec(select(PreferStore
)).one()
86 def _add_with_name(self
, cls
, name
):
89 with Session(self
.engine
) as session
:
96 # recover by returning the item_id
97 item
= session
.exec(select(Item
).where(Item
.name
== name
)).first()
102 def add_store(self
, name
):
103 store_id
= self
._add
_with
_name
(Store
, name
)
104 self
._set
_pref
_store
_id
(store_id
)
107 def add_item(self
, name
):
108 names
= name
.split(',')
110 item_id
= self
._add
_with
_name
(Item
, name
.strip())
111 store_id
= self
._get
_pref
_store
_id
()
112 self
.assign_store(item_id
, store_id
)
115 def assign_last_store(self
, item_id
):
116 store_id
= self
._get
_pref
_store
_id
()
117 self
.assign_store(item_id
, store_id
)
120 def assign_store(self
, item_id
, store_id
):
121 self
._set
_pref
_store
_id
(store_id
)
123 with Session(self
.engine
) as session
:
124 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
125 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
127 item
.stores
.append(store
)
130 pass # Already assigned
133 def list_purchases(self
, store_name
):
134 with Session(self
.engine
) as session
:
135 cmd
= select(PurchaseRecord
).where(PurchaseRecord
.store
== store_name
)
136 records
= session
.exec(cmd
).all()
139 def purchase_items(self
, item_ids
):
141 with Session(self
.engine
) as session
:
142 # Mark items as purchased
143 for item_id
in item_ids
.split(' '):
144 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
146 item
.purchase_date
= dt
.today()
148 # Track purchase history (locally only!)
149 # to generate recommended shopping lists
150 record
= PurchaseRecord()
151 record
.name
= item
.name
152 record
.price
= item
.price
153 record
.purchase_date
= item
.purchase_date
155 shopping_at
= self
._get
_pref
_store
_id
() # We bought it here
156 cmd
= select(Store
).where(Store
.id == shopping_at
)
157 store
= session
.exec(cmd
).one()
158 record
.store
= store
.name
161 results
.append(str(record
))
162 # results.append(str(item))
168 def clear_list(self
, store_id
, clear_all
=False):
169 with Session(self
.engine
) as session
:
170 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
171 for item
in store
.shopping_list
:
172 if clear_all
or item
.purchase_date
:
176 def get_stores(self
):
177 with Session(self
.engine
) as session
:
179 return session
.exec(cmd
).all()
181 def get_unsorted_list(self
):
184 with Session(self
.engine
) as session
:
185 cmd
= select(Item
).where(Item
.stores
== None)
186 items
= session
.exec(cmd
).all()
189 return [f
'No unsorted items!']
193 def get_shopping_list_for_store(self
, store_id
) -> List
[str]:
194 self
._set
_pref
_store
_id
(store_id
)
195 return self
.get_shopping_list(store_id
)
197 def get_last_shopping_list(self
) -> List
[str]:
198 store_id
= self
._get
_pref
_store
_id
()
199 return self
.get_shopping_list(store_id
)
201 def get_shopping_list(self
, store_id
) -> List
[str]:
203 with Session(self
.engine
) as session
:
204 cmd
= select(Store
).where(Store
.id == store_id
)
205 store
= session
.exec(cmd
).first()
207 return [f
'Store {store_id} not found.']
209 results
.append(f
' ## {store.name}')
211 items
= store
.shopping_list
213 results
.append(f
'-- Nothing to shop for at {store.name} --')
216 if(item
.purchase_date
):
217 results
.append(f
' - [x] {item}')
219 results
.append(f
' - [ ] {item}')
222 def remove_item(self
, item_id
):
223 with Session(self
.engine
) as session
:
224 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
228 def remove_store(self
, store_id
):
229 with Session(self
.engine
) as session
:
230 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
231 session
.delete(store
)
234 def get_engine(self
):
235 return create_engine(f
"sqlite:///{self.file}")
238 SQLModel
.metadata
.create_all(self
.engine
)