finpull
Advanced tools
+1
-1
| Metadata-Version: 2.4 | ||
| Name: finpull | ||
| Version: 1.0.10 | ||
| Version: 1.0.11 | ||
| Summary: Financial data scraper with CLI interface | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/Lavarite/FinPull |
+1
-1
@@ -16,3 +16,3 @@ #!/usr/bin/env python3 | ||
| name="finpull", | ||
| version="1.0.10", | ||
| version="1.0.11", | ||
| author="Yevhenii Vasylevskyi", | ||
@@ -19,0 +19,0 @@ author_email="yevhenii+finpull@vasylevskyi.net", |
| Metadata-Version: 2.4 | ||
| Name: finpull | ||
| Version: 1.0.10 | ||
| Version: 1.0.11 | ||
| Summary: Financial data scraper with CLI interface | ||
@@ -5,0 +5,0 @@ Home-page: https://github.com/Lavarite/FinPull |
@@ -8,3 +8,3 @@ """ | ||
| __version__ = "1.0.10" | ||
| __version__ = "1.0.11" | ||
| __author__ = "Yevhenii Vasylevskyi" | ||
@@ -11,0 +11,0 @@ __email__ = "yevhenii+finpull@vasylevskyi.net" |
@@ -25,3 +25,3 @@ #!/usr/bin/env python3 | ||
| epilog="""Examples: | ||
| finpull Launch GUI or interactive mode | ||
| finpull Launch GUI (if available) | ||
| finpull --gui Launch GUI explicitly | ||
@@ -170,4 +170,43 @@ finpull add AAPL GOOGL Add multiple tickers | ||
| if format_type and not args.path: | ||
| custom_path = input("Enter file path (press Enter for current directory): ").strip() | ||
| output_path = os.path.expanduser(custom_path) if custom_path else None | ||
| current_dir = os.getcwd() | ||
| custom_path = input(f"Enter file path (press Enter for current directory: {current_dir}, or # for file dialog): ").strip() | ||
| if custom_path == "#": | ||
| # Try to use GUI file dialog | ||
| try: | ||
| from .utils.compatibility import HAS_TKINTER | ||
| if HAS_TKINTER: | ||
| import tkinter as tk | ||
| from tkinter import filedialog | ||
| root = tk.Tk() | ||
| root.withdraw() # Hide the main window | ||
| # Set file types based on format | ||
| if format_type == 'csv': | ||
| filetypes = [("CSV files", "*.csv"), ("All files", "*.*")] | ||
| defaultextension = ".csv" | ||
| elif format_type == 'xlsx': | ||
| filetypes = [("Excel files", "*.xlsx"), ("All files", "*.*")] | ||
| defaultextension = ".xlsx" | ||
| else: | ||
| filetypes = [("JSON files", "*.json"), ("All files", "*.*")] | ||
| defaultextension = ".json" | ||
| output_path = filedialog.asksaveasfilename( | ||
| title=f"Save {format_type.upper()} file", | ||
| filetypes=filetypes, | ||
| defaultextension=defaultextension | ||
| ) | ||
| root.destroy() | ||
| if not output_path: # User cancelled | ||
| print("Export cancelled") | ||
| return 0 | ||
| else: | ||
| print("GUI file dialog not available (tkinter not found)") | ||
| return 1 | ||
| except Exception as e: | ||
| print(f"Could not open file dialog: {e}") | ||
| return 1 | ||
| else: | ||
| output_path = os.path.expanduser(custom_path) if custom_path else None | ||
| elif args.path: | ||
@@ -197,6 +236,48 @@ output_path = os.path.expanduser(args.path) | ||
| custom_path = input("Enter file path (press Enter for current directory): ").strip() | ||
| output_path = os.path.expanduser(custom_path) if custom_path else None | ||
| current_dir = os.getcwd() | ||
| custom_path = input(f"Enter file path (press Enter for current directory: {current_dir}, or # for file dialog): ").strip() | ||
| if custom_path == "#": | ||
| # Try to use GUI file dialog | ||
| try: | ||
| from .utils.compatibility import HAS_TKINTER | ||
| if HAS_TKINTER: | ||
| import tkinter as tk | ||
| from tkinter import filedialog | ||
| root = tk.Tk() | ||
| root.withdraw() # Hide the main window | ||
| # Set file types based on format | ||
| if format_type == 'csv': | ||
| filetypes = [("CSV files", "*.csv"), ("All files", "*.*")] | ||
| defaultextension = ".csv" | ||
| elif format_type == 'xlsx': | ||
| filetypes = [("Excel files", "*.xlsx"), ("All files", "*.*")] | ||
| defaultextension = ".xlsx" | ||
| else: | ||
| filetypes = [("JSON files", "*.json"), ("All files", "*.*")] | ||
| defaultextension = ".json" | ||
| output_path = filedialog.asksaveasfilename( | ||
| title=f"Save {format_type.upper()} file", | ||
| filetypes=filetypes, | ||
| defaultextension=defaultextension | ||
| ) | ||
| root.destroy() | ||
| if not output_path: # User cancelled | ||
| print("Export cancelled") | ||
| return 0 | ||
| else: | ||
| print("GUI file dialog not available (tkinter not found)") | ||
| return 1 | ||
| except Exception as e: | ||
| print(f"Could not open file dialog: {e}") | ||
| return 1 | ||
| else: | ||
| output_path = os.path.expanduser(custom_path) if custom_path else None | ||
| filename = scraper.export_data(format_type, output_path) | ||
| if output_path: | ||
| filename = scraper.export_data(format_type, output_path) | ||
| else: | ||
| filename = scraper.export_data(format_type) | ||
| print(f"✅ Exported {len(data_list)} records to {filename}") | ||
@@ -203,0 +284,0 @@ except Exception as e: |
@@ -168,4 +168,43 @@ """ | ||
| if format_type and not export_path: | ||
| custom_path = input("Enter file path (press Enter for current directory): ").strip() | ||
| if custom_path: | ||
| import os | ||
| current_dir = os.getcwd() | ||
| custom_path = input(f"Enter file path (press Enter for current directory: {current_dir}, or # for file dialog): ").strip() | ||
| if custom_path == "#": | ||
| # Try to use GUI file dialog | ||
| try: | ||
| from ..utils.compatibility import HAS_TKINTER | ||
| if HAS_TKINTER: | ||
| import tkinter as tk | ||
| from tkinter import filedialog | ||
| root = tk.Tk() | ||
| root.withdraw() # Hide the main window | ||
| # Set file types based on format | ||
| if format_type == 'csv': | ||
| filetypes = [("CSV files", "*.csv"), ("All files", "*.*")] | ||
| defaultextension = ".csv" | ||
| elif format_type == 'xlsx': | ||
| filetypes = [("Excel files", "*.xlsx"), ("All files", "*.*")] | ||
| defaultextension = ".xlsx" | ||
| else: | ||
| filetypes = [("JSON files", "*.json"), ("All files", "*.*")] | ||
| defaultextension = ".json" | ||
| export_path = filedialog.asksaveasfilename( | ||
| title=f"Save {format_type.upper()} file", | ||
| filetypes=filetypes, | ||
| defaultextension=defaultextension | ||
| ) | ||
| root.destroy() | ||
| if not export_path: # User cancelled | ||
| print("Export cancelled") | ||
| return | ||
| else: | ||
| print("GUI file dialog not available (tkinter not found)") | ||
| return | ||
| except Exception as e: | ||
| print(f"Could not open file dialog: {e}") | ||
| return | ||
| elif custom_path: | ||
| export_path = os.path.expanduser(custom_path) | ||
@@ -185,3 +224,6 @@ | ||
| try: | ||
| filename = self.scraper.export_data(format_type, export_path) | ||
| if export_path: | ||
| filename = self.scraper.export_data(format_type, export_path) | ||
| else: | ||
| filename = self.scraper.export_data(format_type) | ||
| print(f"✓ Exported {len(data_list)} records to {filename}") | ||
@@ -504,5 +546,44 @@ except Exception as e: | ||
| # Ask for file path | ||
| custom_path = input("Enter file path (press Enter for current directory): ").strip() | ||
| if custom_path: | ||
| import os | ||
| import os | ||
| current_dir = os.getcwd() | ||
| custom_path = input(f"Enter file path (press Enter for current directory: {current_dir}, or # for file dialog): ").strip() | ||
| if custom_path == "#": | ||
| # Try to use GUI file dialog | ||
| try: | ||
| from ..utils.compatibility import HAS_TKINTER | ||
| if HAS_TKINTER: | ||
| import tkinter as tk | ||
| from tkinter import filedialog | ||
| root = tk.Tk() | ||
| root.withdraw() # Hide the main window | ||
| # Set file types based on format | ||
| if format_type == 'csv': | ||
| filetypes = [("CSV files", "*.csv"), ("All files", "*.*")] | ||
| defaultextension = ".csv" | ||
| elif format_type == 'xlsx': | ||
| filetypes = [("Excel files", "*.xlsx"), ("All files", "*.*")] | ||
| defaultextension = ".xlsx" | ||
| else: | ||
| filetypes = [("JSON files", "*.json"), ("All files", "*.*")] | ||
| defaultextension = ".json" | ||
| custom_filename = filedialog.asksaveasfilename( | ||
| title=f"Save {format_type.upper()} file", | ||
| filetypes=filetypes, | ||
| defaultextension=defaultextension | ||
| ) | ||
| root.destroy() | ||
| if not custom_filename: # User cancelled | ||
| print("Export cancelled") | ||
| return | ||
| else: | ||
| print("GUI file dialog not available (tkinter not found)") | ||
| return | ||
| except Exception as e: | ||
| print(f"Could not open file dialog: {e}") | ||
| return | ||
| elif custom_path: | ||
| custom_filename = os.path.expanduser(custom_path) | ||
@@ -513,3 +594,6 @@ else: | ||
| try: | ||
| filename = self.scraper.export_data(format_type, custom_filename) | ||
| if custom_filename: | ||
| filename = self.scraper.export_data(format_type, custom_filename) | ||
| else: | ||
| filename = self.scraper.export_data(format_type) | ||
| print(f"\n✓ Exported {len(data_list)} records to {filename}") | ||
@@ -516,0 +600,0 @@ except Exception as e: |
@@ -349,3 +349,5 @@ """ | ||
| def refresh_all_data(self): | ||
| """Refresh all ticker data""" | ||
| """Refresh all ticker data asynchronously""" | ||
| import threading | ||
| ticker_count = len(self.scraper.get_ticker_list()) | ||
@@ -356,14 +358,60 @@ if ticker_count == 0: | ||
| def refresh_worker(): | ||
| try: | ||
| self.scraper.refresh_data() | ||
| # Update UI in main thread | ||
| self.root.after(0, lambda: self._refresh_complete(ticker_count)) | ||
| except Exception as e: | ||
| # Handle error in main thread | ||
| self.root.after(0, lambda: self._refresh_error(str(e))) | ||
| # Disable refresh button and show status | ||
| self._disable_refresh_button() | ||
| self.set_status(f"Refreshing {ticker_count} tickers...") | ||
| self.root.update() | ||
| try: | ||
| self.scraper.refresh_data() | ||
| self.refresh_display() | ||
| self.set_status("Refreshed all data") | ||
| messagebox.showinfo("Success", f"Refreshed all {ticker_count} tickers") | ||
| except Exception as e: | ||
| self.set_status("Error refreshing data") | ||
| messagebox.showerror("Error", f"Failed to refresh data: {str(e)}") | ||
| # Start refresh in background thread | ||
| thread = threading.Thread(target=refresh_worker, daemon=True) | ||
| thread.start() | ||
| def _refresh_complete(self, ticker_count): | ||
| """Handle successful refresh completion""" | ||
| self.refresh_display() | ||
| self.set_status("Refreshed all data") | ||
| self._enable_refresh_button() | ||
| messagebox.showinfo("Success", f"Refreshed all {ticker_count} tickers") | ||
| def _refresh_error(self, error_msg): | ||
| """Handle refresh error""" | ||
| self.set_status("Error refreshing data") | ||
| self._enable_refresh_button() | ||
| messagebox.showerror("Error", f"Failed to refresh data: {error_msg}") | ||
| def _disable_refresh_button(self): | ||
| """Disable refresh button during refresh""" | ||
| # Find and disable the refresh button in the input section | ||
| for widget in self.root.winfo_children(): | ||
| if isinstance(widget, ttk.Frame): | ||
| for child in widget.winfo_children(): | ||
| if isinstance(child, ttk.LabelFrame) and child.cget('text') == "Add Ticker": | ||
| for frame in child.winfo_children(): | ||
| if isinstance(frame, ttk.Frame): | ||
| for btn in frame.winfo_children(): | ||
| if isinstance(btn, ttk.Button) and btn.cget('text') == "Refresh All": | ||
| btn.config(state='disabled', text='Refreshing...') | ||
| return | ||
| def _enable_refresh_button(self): | ||
| """Enable refresh button after refresh""" | ||
| # Find and enable the refresh button in the input section | ||
| for widget in self.root.winfo_children(): | ||
| if isinstance(widget, ttk.Frame): | ||
| for child in widget.winfo_children(): | ||
| if isinstance(child, ttk.LabelFrame) and child.cget('text') == "Add Ticker": | ||
| for frame in child.winfo_children(): | ||
| if isinstance(frame, ttk.Frame): | ||
| for btn in frame.winfo_children(): | ||
| if isinstance(btn, ttk.Button) and btn.cget('text') in ['Refreshing...', 'Refresh All']: | ||
| btn.config(state='normal', text='Refresh All') | ||
| return | ||
| def clear_all_data(self): | ||
@@ -544,3 +592,4 @@ """Clear all data""" | ||
| """Show about dialog""" | ||
| about_text = """FinPull v1.0.0 | ||
| from .. import __version__ | ||
| about_text = f"""FinPull v{__version__} | ||
@@ -554,2 +603,3 @@ Comprehensive Financial Data Scraper | ||
| • Cross-platform compatibility | ||
| • Async GUI operations | ||
@@ -556,0 +606,0 @@ Built with Python and Tkinter |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
155266
7.71%2903
7.24%