New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

finpull

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

finpull - pypi Package Compare versions

Comparing version
1.0.10
to
1.0.11
+1
-1
PKG-INFO
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

@@ -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