]>
apps.delaporte.us Git - matrix-shop-bot.git/blob - matrix_shop_bot/db.py
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 def _get_with_id(cls
, item_id
):
55 return select(cls
).where(cls
.id == item_id
)
59 def __init__(self
, db_file
=None):
61 db_file
= os
.path
.join(os
.environ
.get('MATRIX_DATA_DIR', ''), f
'{MODULE}.db')
64 logger
.debug(f
'Using db file {self.file}')
65 self
.engine
= self
.get_engine()
66 self
.last_store_id
= None
68 def _set_pref_store_id(self
, store_id
):
69 with Session(self
.engine
) as session
:
70 prefs
= session
.exec(select(PreferStore
)).all()
74 pref
.store_id
= store_id
78 def _get_pref_store_id(self
):
79 with Session(self
.engine
) as session
:
80 pref
= session
.exec(select(PreferStore
)).one()
83 def _add_with_name(self
, cls
, name
):
86 with Session(self
.engine
) as session
:
93 # recover by returning the item_id
94 item
= session
.exec(select(Item
).where(Item
.name
== name
)).first()
99 def add_store(self
, name
):
100 store_id
= self
._add
_with
_name
(Store
, name
)
101 self
._set
_pref
_store
_id
(store_id
)
104 def add_item(self
, name
):
105 names
= name
.split(',')
107 item_id
= self
._add
_with
_name
(Item
, name
.strip())
108 store_id
= self
._get
_pref
_store
_id
()
109 self
.assign_store(item_id
, store_id
)
112 def assign_last_store(self
, item_id
):
113 store_id
= self
._get
_pref
_store
_id
()
114 self
.assign_store(item_id
, store_id
)
117 def assign_store(self
, item_id
, store_id
):
118 self
._set
_pref
_store
_id
(store_id
)
120 with Session(self
.engine
) as session
:
121 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
122 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
124 item
.stores
.append(store
)
127 pass # Already assigned
130 def list_purchases(self
, store_name
):
131 with Session(self
.engine
) as session
:
132 cmd
= select(PurchaseRecord
).where(PurchaseRecord
.store
== store_name
)
133 records
= session
.exec(cmd
).all()
136 def purchase_items(self
, item_ids
):
138 with Session(self
.engine
) as session
:
139 # Mark items as purchased
140 for item_id
in item_ids
.split(' '):
141 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
143 item
.purchase_date
= dt
.today()
144 results
.append(str(item
))
146 # Track purchase history (locally only!)
147 # to generate recommended shopping lists
148 record
= PurchaseRecord()
149 record
.name
= item
.name
150 record
.price
= item
.price
151 record
.purchase_date
= item
.purchase_date
152 record
.store
= self
._get
_pref
_store
_id
() # We bought it here
159 def clear_list(self
, store_id
, clear_all
=False):
160 with Session(self
.engine
) as session
:
161 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
162 for item
in store
.shopping_list
:
163 if clear_all
or item
.purchase_date
:
167 def get_stores(self
):
168 with Session(self
.engine
) as session
:
170 return session
.exec(cmd
).all()
172 def get_unsorted_list(self
):
175 with Session(self
.engine
) as session
:
176 cmd
= select(Item
).where(Item
.stores
== None)
177 items
= session
.exec(cmd
).all()
180 return [f
'No unsorted items!']
184 def get_shopping_list_for_store(self
, store_id
) -> List
[str]:
185 self
._set
_pref
_store
_id
(store_id
)
186 return self
.get_shopping_list(store_id
)
188 def get_last_shopping_list(self
) -> List
[str]:
189 store_id
= self
._get
_pref
_store
_id
()
190 return self
.get_shopping_list(store_id
)
192 def get_shopping_list(self
, store_id
) -> List
[str]:
194 with Session(self
.engine
) as session
:
195 cmd
= select(Store
).where(Store
.id == store_id
)
196 store
= session
.exec(cmd
).first()
198 return [f
'Store {store_id} not found.']
200 results
.append(f
' ## {store.name}')
202 items
= store
.shopping_list
204 results
.append(f
'-- Nothing to shop for at {store.name} --')
207 if(item
.purchase_date
):
208 results
.append(f
' - [x] {item}')
210 results
.append(f
' - [ ] {item}')
213 def remove_item(self
, item_id
):
214 with Session(self
.engine
) as session
:
215 item
= session
.exec(_get_with_id(Item
, item_id
)).first()
219 def remove_store(self
, store_id
):
220 with Session(self
.engine
) as session
:
221 store
= session
.exec(_get_with_id(Store
, store_id
)).first()
222 session
.delete(store
)
225 def get_engine(self
):
226 return create_engine(f
"sqlite:///{self.file}")
229 SQLModel
.metadata
.create_all(self
.engine
)