tbr-deal-finder 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- tbr_deal_finder/__init__.py +1 -1
- tbr_deal_finder/desktop_updater.py +7 -83
- tbr_deal_finder/gui/main.py +51 -27
- tbr_deal_finder/gui/pages/book_details.py +2 -3
- tbr_deal_finder/gui/pages/latest_deals.py +18 -20
- {tbr_deal_finder-0.3.2.dist-info → tbr_deal_finder-0.3.3.dist-info}/METADATA +1 -1
- {tbr_deal_finder-0.3.2.dist-info → tbr_deal_finder-0.3.3.dist-info}/RECORD +10 -10
- {tbr_deal_finder-0.3.2.dist-info → tbr_deal_finder-0.3.3.dist-info}/WHEEL +0 -0
- {tbr_deal_finder-0.3.2.dist-info → tbr_deal_finder-0.3.3.dist-info}/entry_points.txt +0 -0
- {tbr_deal_finder-0.3.2.dist-info → tbr_deal_finder-0.3.3.dist-info}/licenses/LICENSE +0 -0
tbr_deal_finder/__init__.py
CHANGED
@@ -43,12 +43,16 @@ class DesktopUpdater:
|
|
43
43
|
release_data = response.json()
|
44
44
|
latest_version = release_data["tag_name"].lstrip("v")
|
45
45
|
if version.parse(latest_version) > version.parse(self.current_version):
|
46
|
-
|
46
|
+
download_url = release_data["html_url"]
|
47
|
+
if self.platform == "darwin":
|
48
|
+
for asset in release_data["assets"]:
|
49
|
+
if asset["browser_download_url"].endswith(".dmg"):
|
50
|
+
download_url = asset["browser_download_url"]
|
51
|
+
|
47
52
|
return {
|
48
53
|
"version": latest_version,
|
49
|
-
"download_url":
|
54
|
+
"download_url": download_url,
|
50
55
|
"release_notes": release_data.get("body", ""),
|
51
|
-
"release_url": release_url
|
52
56
|
}
|
53
57
|
|
54
58
|
return None
|
@@ -56,86 +60,6 @@ class DesktopUpdater:
|
|
56
60
|
except Exception as e:
|
57
61
|
logger.error(f"Failed to check updates for {self.github_repo}: {e}")
|
58
62
|
return None
|
59
|
-
|
60
|
-
def download_update(self, download_url: str, progress_callback=None) -> Optional[Path]:
|
61
|
-
"""
|
62
|
-
Download the update file.
|
63
|
-
Returns path to downloaded file or None if failed.
|
64
|
-
"""
|
65
|
-
try:
|
66
|
-
response = requests.get(download_url, stream=True, timeout=30)
|
67
|
-
response.raise_for_status()
|
68
|
-
|
69
|
-
# Determine file extension
|
70
|
-
filename = download_url.split("/")[-1]
|
71
|
-
temp_file = Path(tempfile.gettempdir()) / filename
|
72
|
-
|
73
|
-
total_size = int(response.headers.get('content-length', 0))
|
74
|
-
downloaded = 0
|
75
|
-
|
76
|
-
with open(temp_file, 'wb') as f:
|
77
|
-
for chunk in response.iter_content(chunk_size=8192):
|
78
|
-
if chunk:
|
79
|
-
f.write(chunk)
|
80
|
-
downloaded += len(chunk)
|
81
|
-
|
82
|
-
if progress_callback and total_size > 0:
|
83
|
-
progress = (downloaded / total_size) * 100
|
84
|
-
progress_callback(progress)
|
85
|
-
|
86
|
-
return temp_file
|
87
|
-
|
88
|
-
except Exception as e:
|
89
|
-
logger.error(f"Failed to download update: {e}")
|
90
|
-
return None
|
91
|
-
|
92
|
-
def install_update(self, update_file: Path) -> bool:
|
93
|
-
"""
|
94
|
-
Install the downloaded update.
|
95
|
-
Platform-specific installation logic.
|
96
|
-
"""
|
97
|
-
if self.platform == "darwin":
|
98
|
-
return self._install_macos_update(update_file)
|
99
|
-
elif self.platform == "windows":
|
100
|
-
return self._install_windows_update(update_file)
|
101
|
-
elif self.platform == "linux":
|
102
|
-
return self._install_linux_update(update_file)
|
103
|
-
else:
|
104
|
-
return False
|
105
|
-
|
106
|
-
def _install_macos_update(self, dmg_file: Path) -> bool:
|
107
|
-
"""Install .dmg update on macOS."""
|
108
|
-
try:
|
109
|
-
# Open the DMG file - user will need to drag to Applications
|
110
|
-
subprocess.run(["open", str(dmg_file)], check=True)
|
111
|
-
return True
|
112
|
-
except subprocess.CalledProcessError:
|
113
|
-
return False
|
114
|
-
|
115
|
-
def _install_windows_update(self, exe_file: Path) -> bool:
|
116
|
-
"""Install .exe update on Windows."""
|
117
|
-
try:
|
118
|
-
# Run the installer
|
119
|
-
subprocess.run([str(exe_file)], check=True)
|
120
|
-
return True
|
121
|
-
except subprocess.CalledProcessError:
|
122
|
-
return False
|
123
|
-
|
124
|
-
def _install_linux_update(self, appimage_file: Path) -> bool:
|
125
|
-
"""Install AppImage update on Linux."""
|
126
|
-
try:
|
127
|
-
# Make executable and offer to replace current installation
|
128
|
-
os.chmod(appimage_file, 0o755)
|
129
|
-
|
130
|
-
# For AppImage, we'd typically replace the current file
|
131
|
-
# This is more complex and might require user permission
|
132
|
-
return True
|
133
|
-
except Exception:
|
134
|
-
return False
|
135
|
-
|
136
|
-
def open_download_page(self, release_url: str):
|
137
|
-
"""Open the GitHub release page in browser."""
|
138
|
-
webbrowser.open(release_url)
|
139
63
|
|
140
64
|
|
141
65
|
# Global instance
|
tbr_deal_finder/gui/main.py
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import os
|
3
3
|
import base64
|
4
|
+
import subprocess
|
5
|
+
import sys
|
6
|
+
from datetime import datetime
|
7
|
+
from pathlib import Path
|
4
8
|
|
5
9
|
import flet as ft
|
6
10
|
|
@@ -17,6 +21,7 @@ from tbr_deal_finder.gui.pages.all_deals import AllDealsPage
|
|
17
21
|
from tbr_deal_finder.gui.pages.latest_deals import LatestDealsPage
|
18
22
|
from tbr_deal_finder.gui.pages.all_books import AllBooksPage
|
19
23
|
from tbr_deal_finder.gui.pages.book_details import BookDetailsPage
|
24
|
+
from tbr_deal_finder.utils import get_duckdb_conn, get_latest_deal_last_ran
|
20
25
|
|
21
26
|
|
22
27
|
class TBRDealFinderApp:
|
@@ -27,7 +32,8 @@ class TBRDealFinderApp:
|
|
27
32
|
self.selected_book = None
|
28
33
|
self.update_info = None # Store update information
|
29
34
|
self.nav_disabled = False # Track navigation disabled state
|
30
|
-
|
35
|
+
self._last_run_time = None
|
36
|
+
|
31
37
|
# Initialize pages
|
32
38
|
self.settings_page = SettingsPage(self)
|
33
39
|
self.all_deals_page = AllDealsPage(self)
|
@@ -295,10 +301,9 @@ class TBRDealFinderApp:
|
|
295
301
|
self.all_books_page.refresh_page_state()
|
296
302
|
|
297
303
|
def refresh_all_pages(self):
|
298
|
-
"""Refresh all pages by clearing their state and reloading data"""
|
304
|
+
"""Refresh all pages except all_books_page by clearing their state and reloading data"""
|
299
305
|
self.all_deals_page.refresh_page_state()
|
300
306
|
self.latest_deals_page.refresh_page_state()
|
301
|
-
self.all_books_page.refresh_page_state()
|
302
307
|
|
303
308
|
def disable_navigation(self):
|
304
309
|
"""Disable navigation rail during background operations"""
|
@@ -621,10 +626,6 @@ class TBRDealFinderApp:
|
|
621
626
|
def close_dialog(e):
|
622
627
|
dialog.open = False
|
623
628
|
self.page.update()
|
624
|
-
|
625
|
-
def view_release_and_close(e):
|
626
|
-
self.view_release_notes(e)
|
627
|
-
close_dialog(e)
|
628
629
|
|
629
630
|
def download_and_close(e):
|
630
631
|
self.download_update(e)
|
@@ -638,10 +639,13 @@ class TBRDealFinderApp:
|
|
638
639
|
], spacing=10),
|
639
640
|
content=ft.Column([
|
640
641
|
ft.Text(f"Version {self.update_info['version']} is now available!"),
|
641
|
-
ft.
|
642
|
-
|
642
|
+
ft.Divider(),
|
643
|
+
ft.Text(
|
644
|
+
self.update_info.get('release_notes', 'No release notes available.'),
|
645
|
+
selectable=True
|
646
|
+
),
|
647
|
+
], scroll=ft.ScrollMode.AUTO, spacing=10, tight=True),
|
643
648
|
actions=[
|
644
|
-
ft.TextButton("View Release Notes", on_click=view_release_and_close),
|
645
649
|
ft.ElevatedButton("Download Update", on_click=download_and_close),
|
646
650
|
ft.TextButton("Later", on_click=close_dialog),
|
647
651
|
],
|
@@ -675,24 +679,36 @@ class TBRDealFinderApp:
|
|
675
679
|
dialog.open = True
|
676
680
|
self.page.update()
|
677
681
|
|
678
|
-
def view_release_notes(self, e):
|
679
|
-
"""Open release notes in browser."""
|
680
|
-
if self.update_info:
|
681
|
-
import webbrowser
|
682
|
-
webbrowser.open(self.update_info['release_url'])
|
683
|
-
|
684
682
|
def download_update(self, e):
|
685
683
|
"""Handle update download."""
|
686
684
|
if not self.update_info or not self.update_info.get('download_url'):
|
687
685
|
return
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
686
|
+
|
687
|
+
if sys.platform == "darwin":
|
688
|
+
dmg_path = Path(
|
689
|
+
f"~/Downloads/TBR-Deal-Finder-{self.update_info['version']}-mac.dmg"
|
690
|
+
).expanduser()
|
691
|
+
|
692
|
+
# Show download instructions
|
693
|
+
self.show_download_instructions()
|
694
|
+
|
695
|
+
if not dmg_path.exists():
|
696
|
+
# Using curl or urllib to download to prevent Mac warning
|
697
|
+
subprocess.run([
|
698
|
+
"curl", "-L",
|
699
|
+
self.update_info['download_url'],
|
700
|
+
"-o", dmg_path
|
701
|
+
])
|
702
|
+
|
703
|
+
subprocess.run(["open", dmg_path])
|
704
|
+
else:
|
705
|
+
# For now, open download URL in browser
|
706
|
+
# In a more advanced implementation, you could download in-app
|
707
|
+
import webbrowser
|
708
|
+
webbrowser.open(self.update_info['download_url'])
|
709
|
+
|
710
|
+
# Show download instructions
|
711
|
+
self.show_download_instructions()
|
696
712
|
|
697
713
|
def show_download_instructions(self):
|
698
714
|
"""Show instructions for installing the downloaded update."""
|
@@ -701,7 +717,7 @@ class TBRDealFinderApp:
|
|
701
717
|
self.page.update()
|
702
718
|
|
703
719
|
instructions = {
|
704
|
-
"darwin": "1.
|
720
|
+
"darwin": "1. Wait for the update to download (can take a minute or 2)\n2. Close TBR Deal Finder\n3. Once the installer opens, drag the app to Applications folder\n3. When prompted, select Replace\n4. Restart the application",
|
705
721
|
"windows": "1. Download will start in your browser\n2. Run the downloaded .exe installer\n3. Follow the installation wizard\n4. Restart the application",
|
706
722
|
}
|
707
723
|
|
@@ -732,12 +748,20 @@ class TBRDealFinderApp:
|
|
732
748
|
dialog.open = True
|
733
749
|
self.page.update()
|
734
750
|
|
735
|
-
|
736
|
-
|
737
751
|
def check_for_updates_button(self):
|
738
752
|
"""Check for updates when button is clicked."""
|
739
753
|
self.check_for_updates_manual()
|
740
754
|
|
755
|
+
def update_last_run_time(self):
|
756
|
+
db_conn = get_duckdb_conn()
|
757
|
+
self._last_run_time = get_latest_deal_last_ran(db_conn)
|
758
|
+
|
759
|
+
def get_last_run_time(self) -> datetime:
|
760
|
+
if not self._last_run_time:
|
761
|
+
self.update_last_run_time()
|
762
|
+
|
763
|
+
return self._last_run_time
|
764
|
+
|
741
765
|
|
742
766
|
def main():
|
743
767
|
"""Main entry point for the GUI application"""
|
@@ -11,7 +11,7 @@ from tbr_deal_finder.utils import get_duckdb_conn, execute_query, float_to_curre
|
|
11
11
|
logger = logging.getLogger(__name__)
|
12
12
|
|
13
13
|
|
14
|
-
def build_book_price_section(historical_data: list[dict]) -> ft.Column:
|
14
|
+
def build_book_price_section(max_dt: datetime, historical_data: list[dict]) -> ft.Column:
|
15
15
|
retailer_data = dict()
|
16
16
|
available_colors = [
|
17
17
|
ft.Colors.AMBER,
|
@@ -27,7 +27,6 @@ def build_book_price_section(historical_data: list[dict]) -> ft.Column:
|
|
27
27
|
min_price = None
|
28
28
|
max_price = None
|
29
29
|
min_time = None
|
30
|
-
max_dt = datetime.now()
|
31
30
|
max_time = max_dt.timestamp()
|
32
31
|
|
33
32
|
for record in historical_data:
|
@@ -461,7 +460,7 @@ class BookDetailsPage:
|
|
461
460
|
)
|
462
461
|
|
463
462
|
# Create the chart
|
464
|
-
chart_fig = build_book_price_section(self.historical_data)
|
463
|
+
chart_fig = build_book_price_section(self.app.get_last_run_time(), self.historical_data)
|
465
464
|
|
466
465
|
return ft.Container(
|
467
466
|
content=ft.Column([
|
@@ -10,7 +10,6 @@ from tbr_deal_finder.gui.pages.base_book_page import BaseBookPage
|
|
10
10
|
class LatestDealsPage(BaseBookPage):
|
11
11
|
def __init__(self, app):
|
12
12
|
super().__init__(app, 4)
|
13
|
-
self.last_run_time = None
|
14
13
|
|
15
14
|
def get_page_title(self) -> str:
|
16
15
|
return "Latest Deals"
|
@@ -27,7 +26,7 @@ class LatestDealsPage(BaseBookPage):
|
|
27
26
|
|
28
27
|
def build(self):
|
29
28
|
"""Build the latest deals page with custom header"""
|
30
|
-
self.
|
29
|
+
self.app.update_last_run_time()
|
31
30
|
|
32
31
|
# Custom header with run button
|
33
32
|
header = self.build_header()
|
@@ -73,15 +72,16 @@ class LatestDealsPage(BaseBookPage):
|
|
73
72
|
def build_header(self):
|
74
73
|
"""Build the header with run button and status"""
|
75
74
|
can_run = self.can_run_latest_deals()
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
last_run_time = self.app.get_last_run_time()
|
76
|
+
|
77
|
+
if not can_run and last_run_time:
|
78
|
+
next_run_time = last_run_time + timedelta(hours=8)
|
79
79
|
time_remaining = next_run_time - datetime.now()
|
80
80
|
hours_remaining = max(0, int(time_remaining.total_seconds() / 3600))
|
81
81
|
status_text = f"Next run available in {hours_remaining} hours"
|
82
82
|
status_color = ft.Colors.ORANGE
|
83
|
-
elif
|
84
|
-
status_text = f"Last run: {
|
83
|
+
elif last_run_time:
|
84
|
+
status_text = f"Last run: {last_run_time.strftime('%Y-%m-%d %H:%M')}"
|
85
85
|
status_color = ft.Colors.GREEN
|
86
86
|
else:
|
87
87
|
status_text = "No previous runs"
|
@@ -194,11 +194,13 @@ class LatestDealsPage(BaseBookPage):
|
|
194
194
|
|
195
195
|
def load_items(self):
|
196
196
|
"""Load deals found at the last run time"""
|
197
|
-
|
197
|
+
last_run_time = self.app.get_last_run_time()
|
198
|
+
|
199
|
+
if last_run_time:
|
198
200
|
try:
|
199
201
|
self.items = [
|
200
202
|
book
|
201
|
-
for book in get_deals_found_at(
|
203
|
+
for book in get_deals_found_at(last_run_time)
|
202
204
|
if is_qualifying_deal(self.app.config, book)
|
203
205
|
]
|
204
206
|
self.apply_filters()
|
@@ -237,19 +239,15 @@ class LatestDealsPage(BaseBookPage):
|
|
237
239
|
)
|
238
240
|
)
|
239
241
|
|
240
|
-
def check_last_run(self):
|
241
|
-
"""Check when deals were last run"""
|
242
|
-
if not self.last_run_time:
|
243
|
-
db_conn = get_duckdb_conn()
|
244
|
-
self.last_run_time = get_latest_deal_last_ran(db_conn)
|
245
|
-
|
246
242
|
def can_run_latest_deals(self) -> bool:
|
247
243
|
"""Check if latest deals can be run (8 hour cooldown)"""
|
248
|
-
|
244
|
+
last_run_time = self.app.get_last_run_time()
|
245
|
+
|
246
|
+
if not last_run_time:
|
249
247
|
return True
|
250
248
|
|
251
249
|
min_age = datetime.now() - timedelta(hours=8)
|
252
|
-
return
|
250
|
+
return last_run_time < min_age
|
253
251
|
|
254
252
|
def run_latest_deals(self, e):
|
255
253
|
"""Run the latest deals check"""
|
@@ -286,7 +284,7 @@ class LatestDealsPage(BaseBookPage):
|
|
286
284
|
|
287
285
|
if success:
|
288
286
|
# Update the run time and load new deals
|
289
|
-
self.
|
287
|
+
self.app.update_last_run_time()
|
290
288
|
self.load_items()
|
291
289
|
self.show_success(f"Found {len(self.items)} new deals!")
|
292
290
|
else:
|
@@ -300,7 +298,7 @@ class LatestDealsPage(BaseBookPage):
|
|
300
298
|
self.is_loading = False
|
301
299
|
self.progress_container.visible = False
|
302
300
|
self.run_button.disabled = False
|
303
|
-
self.
|
301
|
+
self.app.update_last_run_time() # Refresh the status
|
304
302
|
|
305
303
|
# Re-enable navigation after the operation
|
306
304
|
self.app.enable_navigation()
|
@@ -372,5 +370,5 @@ class LatestDealsPage(BaseBookPage):
|
|
372
370
|
self.format_dropdown.value = "All"
|
373
371
|
|
374
372
|
# Check last run and reload data
|
375
|
-
self.
|
373
|
+
self.app.update_last_run_time()
|
376
374
|
self.load_items()
|
@@ -1,9 +1,9 @@
|
|
1
|
-
tbr_deal_finder/__init__.py,sha256=
|
1
|
+
tbr_deal_finder/__init__.py,sha256=Yq_k2n4fUg7OMmkDUL-qq_1rUK2wu1KPureT3sJEKx4,104
|
2
2
|
tbr_deal_finder/__main__.py,sha256=b2-3WiGIno_XVusUDijQXa2MthKahFz6CVH-hIBe7es,165
|
3
3
|
tbr_deal_finder/book.py,sha256=WbvDEeI923iQX_eIsC9H7y-qKVPkfCqwjBCqChssFCM,6740
|
4
4
|
tbr_deal_finder/cli.py,sha256=16vZnWS9TTWPhIjZsjsf7OY7Btb0ULOfws5EkUefbRY,7089
|
5
5
|
tbr_deal_finder/config.py,sha256=pocHMEoJVIoiuRzEtKDAmsGmiiLgD-lN6n6TU9gI8Lc,3982
|
6
|
-
tbr_deal_finder/desktop_updater.py,sha256
|
6
|
+
tbr_deal_finder/desktop_updater.py,sha256=-1hNJt1uAkxGo4fUf1IT_BQc9vcRR3mCBDHcYsO-_DU,2285
|
7
7
|
tbr_deal_finder/migrations.py,sha256=fO7r2JbWb6YG0CsPqauakwvbKaEFPxqX1PP8c8N03Wc,4951
|
8
8
|
tbr_deal_finder/owned_books.py,sha256=Cf1VeiSg7XBi_TXptJfy5sO1mEgMMQWbJ_P6SzAx0nQ,516
|
9
9
|
tbr_deal_finder/retailer_deal.py,sha256=l2n-79kSNZfPh73PZBbSE6tkNYYCaHPEehIPcelDPeY,7214
|
@@ -11,13 +11,13 @@ tbr_deal_finder/tracked_books.py,sha256=u3KfBNlwvsEwTfM5TAJVLbiTmm1lTe2k70JJOszQ
|
|
11
11
|
tbr_deal_finder/utils.py,sha256=LJYRNPRO3XBFIlodGzxCxlxiQ9viNYGU5QjNnjz4qMA,3635
|
12
12
|
tbr_deal_finder/version_check.py,sha256=tzuKWngnjSdjPAOdyuPk2ym6Lv3LyeObJAfgsBIH9YQ,1217
|
13
13
|
tbr_deal_finder/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
-
tbr_deal_finder/gui/main.py,sha256=
|
14
|
+
tbr_deal_finder/gui/main.py,sha256=msFtiEVd7um3Ck-dlMhjSUHG2GjSyeVuJSDsLGFarJs,28420
|
15
15
|
tbr_deal_finder/gui/pages/__init__.py,sha256=1xc9Ib3oX_hqxLdhZvbCv3AhwOmVBSklZZC6CAcvoMU,20
|
16
16
|
tbr_deal_finder/gui/pages/all_books.py,sha256=uQguQ9_NjM5kq_D5DbOWFB9om1lOdPk1iTcjH0yl9bg,3655
|
17
17
|
tbr_deal_finder/gui/pages/all_deals.py,sha256=rY08w4XXFc_Jbd39BPJ6fO00vstuuu2YHvZterHx5ZI,2389
|
18
18
|
tbr_deal_finder/gui/pages/base_book_page.py,sha256=kmHjJE4eIGrMhXtBWePn_eXfO6qJ36kIoQ7hE1xB3ic,10465
|
19
|
-
tbr_deal_finder/gui/pages/book_details.py,sha256=
|
20
|
-
tbr_deal_finder/gui/pages/latest_deals.py,sha256=
|
19
|
+
tbr_deal_finder/gui/pages/book_details.py,sha256=8ZqONLeqZ_1TGzmkJrem00VBFT3VcczEtEIzyCiHyro,22067
|
20
|
+
tbr_deal_finder/gui/pages/latest_deals.py,sha256=XFTzmWjaw8grmbSF_KTGMKFOJ2TUY2kpLljyVdte93k,13896
|
21
21
|
tbr_deal_finder/gui/pages/settings.py,sha256=gdQXi508yd_oZ5lj0mxuDL5qSxmH256LwPi5gTo2Cqg,14357
|
22
22
|
tbr_deal_finder/queries/get_active_deals.sql,sha256=nh0F1lRV6YVrUV7gsQpjsgfXmN9R0peBeMHRifjgpUM,212
|
23
23
|
tbr_deal_finder/queries/get_deals_found_at.sql,sha256=KqrtQk7FS4Hf74RyL1r-oD2D-RJz1urrxKxkwlvjAro,139
|
@@ -31,8 +31,8 @@ tbr_deal_finder/retailer/chirp.py,sha256=GR8yeXp-1DHMktepy5TecHslrUibpLM7LfueIuT
|
|
31
31
|
tbr_deal_finder/retailer/kindle.py,sha256=kA4SO2kl2SvJHADkBYyYMgq_bditStbiTiW7piQPAFI,5282
|
32
32
|
tbr_deal_finder/retailer/librofm.py,sha256=dn1kaJEQ9g_AOAFNKhUtFLxYxQZa6RAHgXhT_dx-O3k,7514
|
33
33
|
tbr_deal_finder/retailer/models.py,sha256=56xTwcLcw3bFcvTDOb85TktqtksvvyY95hZBbp9-5mY,3340
|
34
|
-
tbr_deal_finder-0.3.
|
35
|
-
tbr_deal_finder-0.3.
|
36
|
-
tbr_deal_finder-0.3.
|
37
|
-
tbr_deal_finder-0.3.
|
38
|
-
tbr_deal_finder-0.3.
|
34
|
+
tbr_deal_finder-0.3.3.dist-info/METADATA,sha256=VEp2T5WOIRAR_c5lc3y5YI8GJaaEiUuacKdl2oBQCdw,3448
|
35
|
+
tbr_deal_finder-0.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
36
|
+
tbr_deal_finder-0.3.3.dist-info/entry_points.txt,sha256=xjeRw7aX_jbX1ERC--IgYIa2oLNgeRefsMbKeTAVb70,112
|
37
|
+
tbr_deal_finder-0.3.3.dist-info/licenses/LICENSE,sha256=rNc0wNPn4d4HHu6ZheJzeUaz_FbJ4rj2Dr2FjAivkNg,1064
|
38
|
+
tbr_deal_finder-0.3.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|