Run Flask app in Kivy app when a button is pressed - python

Is there a way that I could run Kivy and Flask at the same time in a Kivy app? Furthermore, I need the app so once you click a button in the Kivy app, that triggers a function where a Flask web page is started. Then, using the Python builtin webbrowser module, I need it to automatically open the web page in the default browser.
When this code is ran, I get no error. Just the Kivy app freezes and does not respond anymore.
My code so far:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from flask import Flask
from werkzeug.serving import run_simple
import webbrowser
Builder.load_file('design.kv')
answers = []
class CalcScreen(Screen):
def list_view(self):
self.manager.current = "list_screen"
def cround_view(self):
self.manager.current = "round_calc_screen"
def calculate(self):
LengthVal = float(self.ids.length.text)
WidthVal = float(self.ids.width.text)
ThicknessVal = float(self.ids.thickness.text)
FinalCalc = LengthVal * WidthVal * ThicknessVal / 144
FinalCalc = round(FinalCalc,1)
answers.append(FinalCalc)
self.ids.board_feet.text = str(FinalCalc)
class ListScreen(Screen):
def calc_view(self):
self.manager.current = "calc_screen"
def UpdateInfo(self):
tot = 0
for num in answers:
tot += num
self.ids.total_board_feet.text = str(round(tot,1))
self.ids.total_boards.text = str(len(answers))
self.ids.list.text = str(', '.join(map(str, answers)))
def ClearListAnswers(self):
answers.clear()
def printerview(self):
app = Flask(__name__)
#app.route('/')
def home():
return f"<h1>BFCalc Printer Friendly View</h1>\n{self.ids.list.text}"
run_simple('localhost',5000,app)
webbrowser.open_new('localhost:5000')
class RoundCalcScreen(Screen):
def calc_view(self):
self.manager.current = "calc_screen"
def rc_calculate(self):
RC_DiameterVal = float(self.ids.rc_diameter.text)
RC_RadiusVal = RC_DiameterVal / 2
RC_ThicknessVal = float(self.ids.rc_thickness.text)
RC_FinalCalc = (3.14 * (RC_RadiusVal * RC_RadiusVal) * RC_ThicknessVal) / 144
RC_FinalCalc = round(RC_FinalCalc,1)
answers.append(RC_FinalCalc)
self.ids.rc_board_feet.text = str(RC_FinalCalc)
class RootWidget(ScreenManager):
pass
class MainApp(App):
def build(self):
self.icon = 'icon.ico'
return RootWidget()
if __name__ == "__main__":
MainApp().run()

It is a bad practice to use backend frameworks in that way (they are not used that way at all). It depends on your needs, you can try to use pure HTML instead.
def printerview(self):
import webbrowser
file_name = "my_html.html"
html = f"""<h1>BFCalc Printer Friendly View</h1>\n{self.ids.list.text}"""
with open(file_name, "w+") as f:
f.write(html)
# open html in a browser
webbrowser.open(file_name)

Related

Python EEL + wxpython: open "parentless" file dialog in front of all other windows and programs

I am developing an EEL project, and I needed to create a file dialog on the python side in order to preprocess data before sending it to javascript.
I tried to use tk.filedialog.askopenfilename, but that somehow froze the javascript event loop.
I found an answer on StackOverflow that used wxpython to create a non-blocking file picker. However, when I run the code below, the file picker always starts minimized.
However, once you use the file picker once, it works perfectly the second time.
Any help appreciated.
import base64
import json
from tkinter import Tk
Tk().withdraw()
from tkinter.filedialog import askopenfilename
import PIL.Image
import eel
import numpy as np
import wx
# Reusable wxpython App instance for the creation of non-blocking popup dialogs
app=wx.App(None)
eel.init("public")
def encode(bts):
return base64.b64encode(bts)
def array_to_json(array):
return json.dumps({
"shape": list(array.shape),
"dtype": str(array.dtype),
"data":list(np.ravel(array).astype(float)) # not efficient but quite clean
})
#eel.expose
def load_image(path):
return array_to_json(np.asarray(PIL.Image.open(path)))
#eel.expose
def pick_image():
# return askopenfilename()
""" --- Adapted from https://stackoverflow.com/a/59177064/5166365"""
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.STAY_ON_TOP | wx.DIALOG_NO_PARENT | wx.MAXIMIZE
dialog = wx.FileDialog(None, "Open File", wildcard="*", style=style)
dialog.Iconize(False)
dialog.Maximize()
dialog.Raise()
path = ""
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = ""
return path
""" --- """
eel.start("index.html")
I was able to get it working with the following code. I used a regular window instead of a wx.Dialog or similar class.
class FancyFilePickerApplication:
def __init__(self):
self.app = wx.App()
self.frame = wx.Frame(None,title="Fancy File Picker")
self.build_ui()
##private
def build_ui(self):
self.vertical_sizer = wx.BoxSizer(wx.VERTICAL)
self.control_panel = wx.Panel(self.frame,wx.ID_ANY)
self.horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.control_panel.SetSizer(self.horizontal_sizer)
self.frame.SetSizer(self.vertical_sizer)
self.dir_ctrl = wx.GenericDirCtrl(self.frame,wx.ID_ANY,wx.EmptyString,wx.DefaultPosition,wx.Size(400,300))
self.vertical_sizer.Add(self.dir_ctrl,wx.SizerFlags().Expand())
self.vertical_sizer.Add(self.control_panel,wx.SizerFlags().Expand())
self.scale_factor_label = wx.StaticText(self.control_panel,wx.ID_ANY,"Scale Factor: ")
self.scale_factor_textbox = wx.TextCtrl(self.control_panel,wx.ID_ANY)
self.open_button = wx.Button(self.control_panel,wx.ID_ANY,"Open")
self.horizontal_sizer.Add(self.scale_factor_label,wx.SizerFlags().Expand())
self.horizontal_sizer.Add(self.scale_factor_textbox,wx.SizerFlags().Expand())
self.horizontal_sizer.Add(self.open_button,wx.SizerFlags().Expand())
self.open_button.Bind(wx.EVT_BUTTON,lambda evt:self.submit())
self.frame.Bind(wx.EVT_CLOSE,self.cancel)
def open(self, file_picked_callback):
self.file_picked_callback = file_picked_callback
self.frame.Fit()
self.frame.Center()
self.frame.Show()
self.frame.Raise()
self.frame.ToggleWindowStyle(wx.STAY_ON_TOP)
self.app.MainLoop()
##private
def submit(self):
filepath =self.dir_ctrl.GetFilePath()
scale_factor_text = self.scale_factor_textbox.GetValue()
scale_factor = 1.0 if not scale_factor_text.strip() else float(scale_factor_text)
self.file_picked_callback(filepath,scale_factor)
self.frame.Destroy()
##private
def cancel(self,evt):
self.file_picked_callback("",0)
self.frame.Destroy()

Pytube/Pytube3 not working with kivy after installed on Android device [Compiled with Buildozer]

I am trying to make an app that downloads YouTube videos for my first project with kivy. The program works just fine on windows 10, however, it doesn't operate on Android. I believe my problem has come down to whether or not I am using the correct pytube library (whether that be "pytube" or "pytube3"). Also, would anyone know how to correctly set up the buildozer.spec file for the specified imports?
P.S. How would I include the DownloadedVideos folder into the buildozer.spec?
This is a test model BTW, so code improvements to come ;)
from pytube import YouTube
import pytube
import os
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.checkbox import CheckBox
import certifi
import os
# Here's all the magic !
os.environ['SSL_CERT_FILE'] = certifi.where()
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.add_widget(Label(text="Paste your YouTube link into the first box on the right. \nOnce you have done so,"
" hit the submit button. \nThe submit button will allow the app to search for the"
" video and confirm its existence. \nThen, a download button with options"
" will appear. \nSelect the options you wish to have downloaded (keeping in mind"
" that audio files need 'kbps' and '.mp3'), and they will be downloaded.",
halign="left", valign="top", font_size=12))
self.inside = GridLayout()
self.inside.cols = 2
self.inside.add_widget(Label(text="Paste YouTube Link:"))
self.link = TextInput(multiline=False)
self.inside.add_widget(self.link)
self.button1 = Button(text="Submit", on_press=self.callback)
self.button1.bind(on_press=self.callback)
self.inside.add_widget(self.button1)
self.outText = TextInput(multiline=True, readonly=True)
self.inside.add_widget(self.outText)
self.inside.add_widget(Label(text="Media:"))
self.ytTypes = TextInput(multiline=True, readonly=True)
self.inside.add_widget(self.ytTypes)
self.add_widget(self.inside)
def downloadMedia(self, instance):
global stream
print(f"Downloading... {stream}")
self.ytTypes.text = f"Downloading... {stream}"
out_file = stream.download("DownloadedVideos")
print(f"Downloaded {stream}")
global yt
self.ytTypes.text = f"Downloaded \n{yt.title}!"
try:
print("here")
self.remove_widget(self.download)
except:
print("Error")
def callback(self, instance):
youtubeLink = self.link.text
print("pressed", youtubeLink)
try:
global yt
yt = YouTube(youtubeLink)
print("Views: ", yt.title)
print("Length: ", yt.length)
print("Views", yt.views)
#Rounds the length of the YouTube video in minutes down to 2 decimal places
res = "{:.2f}".format(yt.length/60)
self.outText.text = f"Title: {yt.title}, \nLength: {res} minutes, \nViews: {yt.views}"
#Grabs video itags and displays them
global stream
stream = yt.streams.filter(progressive=True).last()
print(stream)
print("pass")
print(stream.itag)
self.ytTypes.text = f"Streams: {stream}"
self.download = Button(text="Download:")
self.download.bind(on_press=self.downloadMedia)
self.add_widget(self.download)
except:
print("error")
self.outText.text = "ERR"
self.link.text = ""
class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()

Using QApplication::exec() in a function or class

I want the following code to get the request url starting with http://down.51en.com:88 during the web loading process , and then do other processing with the response object of the url .
In my program, once targetUrl is assigned a value , I want the function targetUrlGetter(url) to return it to the caller, however , the problem is that QApplication::exec() enters the main event loop so cannot execute code at the end of thetargetUrlGetter() function after the exec() call , thus the function cannot return , I have tried with qApp.quit() in interceptRequest(self, info) in order to tell the application to exit so that targetUrlGetter(url) can return , but the function still cannot return and the program even crashes on exit(tested on Win7 32bit), so how can I return the targetUrl to the caller program ?
The difficulties here are how to exit the Qt event loop without crash and return the request url to the caller.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebEngineCore import *
from PyQt5.QtCore import *
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def __init__(self, parent=None):
super().__init__(parent)
self.page = parent
def interceptRequest(self, info):
if info.requestUrl().toString().startswith('http://down.51en.com:88'):
self.targetUrl = info.requestUrl().toString()
print('----------------------------------------------', self.targetUrl)
qApp.quit()
# self.page.load(QUrl(''))
def targetUrlGetter(url=None):
app = QApplication(sys.argv)
page = QWebEnginePage()
globalSettings = page.settings().globalSettings()
globalSettings.setAttribute(
QWebEngineSettings.PluginsEnabled, True)
globalSettings.setAttribute(
QWebEngineSettings.AutoLoadImages, False)
profile = page.profile()
webEngineUrlRequestInterceptor = WebEngineUrlRequestInterceptor(page)
profile.setRequestInterceptor(webEngineUrlRequestInterceptor)
page.load(QUrl(url))
# view = QWebEngineView()
# view.setPage(page)
# view.show()
app.exec_()
return webEngineUrlRequestInterceptor.targetUrl
url = "http://www.51en.com/news/sci/everything-there-is-20160513.html"
# url = "http://www.51en.com/news/sci/obese-dad-s-sperm-may-influence-offsprin.html"
# url = "http://www.51en.com/news/sci/mars-surface-glass-could-hold-ancient-fo.html"
targetUrl = targetUrlGetter(url)
print(targetUrl)
You should always initialize QApplication at the beginning of the program, and always call the QApplication::exec function at the end of the program.
Another thing is that QWebEngineUrlRequestInterceptor.interceptRequest is a callback function which is called asynchronously. Since info.requestUrl().toString() is called inside the callback function, there is no way to return the result it synchronously.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWebEngineCore import *
from PyQt5.QtCore import *
class WebEngineUrlRequestInterceptor(QWebEngineUrlRequestInterceptor):
def __init__(self, parent=None):
super().__init__(parent)
self.page = parent
def interceptRequest(self, info):
if info.requestUrl().toString().startswith('http://down.51en.com:88'):
self.targetUrl = info.requestUrl().toString()
print('----------------------------------------------', self.targetUrl)
# Don't do thatDo you really want to exit the whole program?
# qApp.quit()
# Do something here, rather than return it.
# It must be run asynchronously
# self.page.load(QUrl(''))
def targetUrlGetter(url=None):
page = QWebEnginePage()
globalSettings = page.settings().globalSettings()
globalSettings.setAttribute(
QWebEngineSettings.PluginsEnabled, True)
globalSettings.setAttribute(
QWebEngineSettings.AutoLoadImages, False)
profile = page.profile()
webEngineUrlRequestInterceptor = WebEngineUrlRequestInterceptor(page)
profile.setRequestInterceptor(webEngineUrlRequestInterceptor)
page.load(QUrl(url))
# view = QWebEngineView()
# view.setPage(page)
# view.show()
# Don't return this. It cannot be called synchronously. It must be called asynchronously.
# return webEngineUrlRequestInterceptor.targetUrl
app = QApplication(sys.argv) # always initialize QApplication at the beginning of the program
url = "http://www.51en.com/news/sci/everything-there-is-20160513.html"
# url = "http://www.51en.com/news/sci/obese-dad-s-sperm-may-influence-offsprin.html"
# url = "http://www.51en.com/news/sci/mars-surface-glass-could-hold-ancient-fo.html"
targetUrl = targetUrlGetter(url)
print(targetUrl)
app.exec_() # always call the QApplication::exec at the end of the program

Add filebrowser in Kivy

I have a simple working code which display 2 images, however i want it to display after the file has been browsed.
My code:
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.uix.scatter import Scatter
class CanvasApp(App):
def build(self):
f = floatlayout()
s = Scatter()
s1 = Scatter()
img_1 = Image(source='img0.jpg',pos=(10,280), size=(300,300))
img_2 = Image(source='img1.jpg',pos=(350,280), size=(300,300))
f.add_widget(s)
s.add_widget(img_1)
f.add_widget(s1)
f.add_widget(img_2)
return f
if __name__ == '_main__':
CanvasApp().run()
Issues in above code:
1. How to provide path in source using filebrowser, what i know about file browser,
from os.path import sep, expanduser, isdir, dirname
user_path = expanduser('~') + sep + 'Documents'
browser = FileBrowser(select_string='Select',
favorites=[(user_path, 'Documents')])
How can i use scatter independently for both images. In above mentioned method i can only use scatter properties on img0.jpg
In the original kivy.garden.Filebrowser example the following two imports are not mentioned:
from kivy.garden.filebrowser import FileBrowser
from kivy.utils import platform
Here is a small working example:
from kivy.app import App
from os.path import sep, expanduser, isdir, dirname
from kivy.garden.filebrowser import FileBrowser
from kivy.utils import platform
class TestApp(App):
def build(self):
if platform == 'win':
user_path = dirname(expanduser('~')) + sep + 'Documents'
else:
user_path = expanduser('~') + sep + 'Documents'
browser = FileBrowser(select_string='Select',
favorites=[(user_path, 'Documents')])
browser.bind(
on_success=self._fbrowser_success,
on_canceled=self._fbrowser_canceled)
return browser
def _fbrowser_canceled(self, instance):
print ('cancelled, Close self.')
def _fbrowser_success(self, instance):
print (instance.selection)
TestApp().run()

PyobjC : NSTextField on NSPopover

I have the same problem mentioned here : Not being able to edit NSTextField on NSPopover even though Editable behavior is set. The solution seems to be to override the canBecomeKeyWindow of NSWindow. I am trying to do the same thing in PyObjC, but I am getting an error Python signature doesn't match implied objective-C signature.
In the following code, if I comment out canBecomeKeyWindow_(), then the app runs as expected, but I am not able to click and edit the textfields.
# from Cocoa import *
from AppKit import NSWindowController, NSApplication, NSApp, NSMaxYEdge, NSImage, NSStatusBar, NSMenu, NSMenuItem, NSVariableStatusItemLength, NSRect
from Cocoa import objc
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject
from PyObjCTools import AppHelper
import webbrowser
import subprocess
import os
global popover
class TestApp(NSApplication):
def finishLaunching(self):
# Make statusbar item
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
self.icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
self.icon.setScalesWhenResized_(True)
self.icon.setSize_((20, 20))
self.statusitem.setImage_(self.icon)
self.statusitem.setHighlightMode_(1)
# make the menu
self.menubarMenu = NSMenu.alloc().init()
self.menuItem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Login', 'loginCallback:', '')
self.menubarMenu.addItem_(self.menuItem)
self.quit = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
self.menubarMenu.addItem_(self.quit)
# add menu to statusitem
self.statusitem.setMenu_(self.menubarMenu)
def loginCallback_(self, notification):
# Initiate the contrller with a XIB
viewController = SimpleXibDemoController.alloc().initWithWindowNibName_("Login")
# Show the window
viewController.showWindow_(viewController)
rect = self.statusitem.valueForKey_('button').frame()
viewController.popover.showRelativeToRect_ofView_preferredEdge_(rect, self.statusitem.valueForKey_('button'), NSMaxYEdge)
class SimpleXibDemoController(NSWindowController):
popover = objc.IBOutlet()
counterTextField = objc.IBOutlet()
username_field = objc.IBOutlet()
password_field = objc.IBOutlet()
submit_button = objc.IBOutlet()
def canBecomeKeyWindow_(self):
return 1
def windowDidLoad(self):
NSWindowController.windowDidLoad(self)
#objc.IBAction
def submit_(self, sender):
username = self.username_field.stringValue()
password = self.password_field.stringValue()
self.updateDisplay(username + ' ' + password)
def updateDisplay(self, value):
self.counterTextField.setStringValue_(value)
if __name__ == "__main__":
app = TestApp.sharedApplication()
icon = NSImage.alloc().initByReferencingFile_('app-icon.png')
app.setApplicationIconImage_(icon)
AppHelper.runEventLoop()
It looks like you're adding an underscore where you shouldn't. The PyObjC bridge will translate it into a colon. Besides that, the corresponding Python boolean value should be True. Thus, the correct function would look like this:
def canBecomeKeyWindow(self):
return True

Categories

Resources