PyObjC app not allowed to access the accessibility API - python

On OSX Maverick, I have this PyObjC(python3.3) based simple APP
test.py
class MyDelegate(NSObject):
def applicationDidFinishLaunching_(self, sender):
NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(NSKeyDownMask, lambda event: NSLog("hello"))
NSApp.activateIgnoringOtherApps_(YES)
delegate = MyDelegate.alloc().init()
app = NSApplication.sharedApplication()
app.setDelegate_(delegate)
menu = NSMenu.alloc().initWithTitle_("My Menu")
app.setMainMenu_(menu)
window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
NSMakeRect(0, 0, 100, 100),
NSTitledWindowMask,
NSBackingStoreBuffered,
NO
)
window.setTitle_("my app")
window.makeKeyAndOrderFront_(None)
windowController = NSWindowController.alloc().initWithWindow_(window)
AppHelper.runEventLoop()
Ran it with
python3 test.py
and give accessibility access to Terminal in System Preferences -> Privacy & Security -> Accessibility
I could see the global monitor works.
Then I froze it with cx_Freeze
python3 setup.py bdist_mac
ends up with
build/Test.app
with
open build/Test.app
I'm seeing error in OSX system log
universalAccessAuthWarn[546]: AccessibilityAPI: pid 3809, is not allowed to access the accessibility API. Path: /path/to/build/Test.app/Contents/MacOS/test
I've enabled access for "test" in "System Preferences" -> "Privacy & Security" -> "Accessibility" which doesn't work.
What I'm missing here?
UPDATE:
Probably was cx_Freeze's problem, using py2app(0.8) made the global event monitor work. Thanks for Ronald Oussoren's hint (see answer & comment below).

I don't know why this doesn't work with cx_Freeze, it does work for me with py2app using this setup.py file:
from setuptools import setup
setup(
name='main',
app=['test.py'],
setup_requires=['py2app'],
)
That is, when I launch the application, give it accessibility access and then relaunch the application I see logging from the application in Console.app.

Related

Python macOS builds run from Terminal but crash on Finder launch

My py2app build displays an error message with a Terminate button when the app is launched with a double-click. However, if I open it directly in Terminal then it works perfectly well. All of the following Terminal commands launch the app successfully:
# .MyApp.app/Contents/MacOS/MyApp
# open MyApp.app
# open -a MyApp.app
I've read several posts about similar py2app errors but I can't figure out what could be the issue here. The problem seems to have been around for at least four years, is not specific to py2app and seems related to a general issue with Python on macOS. Users of pyinstaller are reporting the exact same problem with no obvious solution in sight AFAIK.
My setup.py file:
from setuptools import setup
APP = ['myapp.py']
DATA_FILES = [('img', ['img/myapp-logo.png']), ('data', ['data/data.yml'])]
OPTIONS = {'iconfile': 'icon.icns'}
setup(
app=APP,
name='My App',
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
The macOS Console shows the following message in system.log: com.apple.xpc.launchd[1] (org.pythonmac.unspecified.MyApp.4952[16783]): Service exited with abnormal code: 255
Suspect 1: file access?
This thread on the pyinstaller GitHub issues page has multiple people reporting the same error with no clear fix. This proposed solution seems related to the working directory issues described in the pyinstaller thread but doesn't solve the problem on my system.
My app is only reading a single yml file from its working directory and doesn't write anything to disk. It is nothing more than a simple file access statement:
file = os.path.realpath('data/data.yml')
with open(file) as f:
# etc
In Catalina, I've added the app to the list of apps allowed Full Disk Access in the Security Preferences but this doesn't solve the issue either (it would have been surprising if it did, though, since the open command works as mentioned above).
Suspect 2: Tkinter?
This thread on pyinstaller GitHub suggests the problem might be related to a Tkinter version. The proposed solution seems to have fixed the issue for some users. However, on my end I have a working app bundle from an earlier version, before adding the open file statement, which launches just fine when double clicked.
Suspect 1 vs 2
I have forked my code into two branches, one completely removing references to Tkinter, the other removing file access in favour of initialisation via variables in the code. The crash does not happen in the second case. This would seem to rule out Tkinter as the source of the problem, although weirdly enough a hack to Tkinter has fixed it for other users.
macOS versions
I've tested both bundle versions (working and not working) on both Catalina 10.15.6 and El Capitan 10.11.6 and the behaviour is identical.
The Firing of the Death Sentinel
Here's what the Console log looks like after launching the app normally through the Finder:
default 21:26:13.836380+0200 runningboardd Acquiring assertion targeting executable<MyPythonApplication(501)> from originator [daemon<com.apple.coreservices.launchservicesd>:165] with description <RBSAssertionDescriptor; frontmost:27280; ID: 391-165-34219; target: 27280> attributes = {
<RBSDomainAttribute: 0x7f9f1a712910; domain: com.apple.launchservicesd; name: RoleUserInteractiveFocal; sourceEnvironment: 0x0>;
}
default 21:26:13.836649+0200 runningboardd Assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) will be created as active
default 21:26:13.838258+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Ignoring jetsam update because this process is not memory-managed
default 21:26:13.839703+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Set darwin role to: UserInteractiveFocal
default 21:26:13.840634+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Ignoring GPU update because this process is not GPU managed
default 21:26:13.840912+0200 runningboardd Finished acquiring assertion 391-165-34219 (target:executable<MyPythonApplication(501)>)
default 21:26:15.166436+0200 hidd Connection removed: IOHIDEventSystemConnection uuid:B1D40AB3-FD55-455C-9E1B-2E4C4C6E4982 pid:27280 process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
default 21:26:15.171128+0200 runningboardd [executable<MyPythonApplication(501)>:27280] Death sentinel fired!
default 21:26:15.174315+0200 runningboardd Invalidating assertion 391-165-34219 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.176832+0200 loginwindow -[PersistentAppsSupport applicationQuit:] | for app:MyPythonApplication, _appTrackingState = 2
default 21:26:15.176856+0200 loginwindow -[PersistentAppsSupport applicationQuit:] | App: MyPythonApplication, quit, updating active tracking timer
default 21:26:15.179589+0200 runningboardd Invalidating assertion 391-165-34209 (target:executable<MyPythonApplication(501)>) from originator 165
default 21:26:15.281529+0200 runningboardd Removing process: [executable<MyPythonApplication(501)>:27280]
default 21:26:15.282124+0200 runningboardd Removing assertions for terminated process: [executable<MyPythonApplication(501)>:27280]
error 21:26:15.292603+0200 runningboardd RBSStateCapture remove item called for untracked item 391-165-34209 (target:executable<MyPythonApplication(501)>)
error 21:26:15.292622+0200 runningboardd RBSStateCapture remove item called for untracked item 391-165-34219 (target:executable<MyPythonApplication(501)>)
Apparently the error is mentioned in this line, in case someone can make anything out of it:
hidd Connection removed: IOHIDEventSystemConnection uuid:foo pid:bar process:MyPythonApplication type:Passive entitlements:0x0 caller:HIToolbox: ___GetIOHIDEventSystemClient_block_invoke + 26 attributes:(null) state:0x1 events:0 mask:0x0
This is immediately followed by RunningBoard firing the Death Sentinel, whatever this entity might be. The system is described in this article by Howard Oakley but it is way beyond my level of expertise.
Some more info gathered by Ulbow confirms an error with the highly informative message "MacOS error: -67062":
com.apple.runningboard 4941060 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.launchservices 4940599 802 OSStatus _LSLaunch(LSContext *, FSNode *, LSLaunchFlags, void *, CFArrayRef, const AppleEvent *, const AEDescList *, CFArrayRef, CFDictionaryRef, LSBundleID, const audit_token_t *, const _LSOpen2Options *, ProcessSerialNumber *, Boolean *, NSError **): launching '<private>' result=0
com.apple.securityd 9807 904 MacOS error: -67062
com.apple.runningboard 4941063 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.sharedfilelist 4940599 802 -[SFLGenericList _insertItem:atIndex:error:]_block_invoke com.apple.LSSharedFileList.RecentApplications
com.apple.securityd 4940933 530 UNIX error exception: 8
4941117 27642 MyPythonApplication Error
com.apple.securityd 4941064 165 UNIX error exception: 22
com.apple.securityd 4941064 165 MacOS error: -67062
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.TCC 4941161 198 Failed to copy signing info for 27642, responsible for file:///Users/me/Files/Docs/Code/Python/MyPythonApplication/dist/MyPythonApplication.app/Contents/MacOS/Pandemic%20Deck%20Tracker: #-67062: Error Domain=NSOSStatusErrorDomain Code=-67062 "(null)"
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.securityd 4941161 198 MacOS error: -67062
com.apple.launchservices 4941064 165 CLIENT: 0x7fcec00b33b0/27642 Received XPC_ERROR on connection from our client, so invalidating it and our connection.
com.apple.appleevents 4941120 462 CONNECTION: peer=? peer-pid=27642 got event (Error "Connection invalid")
com.apple.appleevents 4941120 462 CONNECTION: Recevied XPC_ERROR on a connection from 27642, a client of ours, so unregistering any application with that pid.
com.apple.appleevents 4941120 462 CONNECTION: releasing app App:"MyPythonApplication"/"MyPythonApplication"/"org.pythonmac.unspecified.MyPythonApplication" 27642/0x0:0x308d08a ????1010 sess=100020 because we received error on its connection.
com.apple.runningboard 4941002 391 Received message from [daemon<com.apple.coreservices.launchservicesd>:165] (euid 0): acquireAssertionWithDescriptor:error:
Several exchanges on recent tests, trying to pinpoint the cause for the bug, can be found on this GitHub issues page, but it still remains a mystery (and a very bizarre one, too).
An Apple engineer should really take a look at this.
A pretty thorough investigation of this issue has been conducted on this GitHub issue page by the maintainers of pyinstaller. It turns out that if a Python script accesses resources on a macOS file system using the built-in Python os module, the app bundle crashes. The bundle does not crash if the script is run directly from a Terminal command.
The proposed solution is to check if the script is running on macOS and, in that case, use AppKit to open the file. This requires installing the pyobjc module but otherwise it is not a major hassle.
Instead of doing this:
import os
file = os.path.realpath('path/somefile.ext')
with open(file) as f:
# ...
Do this:
import os
import platform
def get_path(filename):
name = os.path.splitext(filename)[0]
ext = os.path.splitext(filename)[1]
if platform.system() == "Darwin":
from AppKit import NSBundle
file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)
return file or os.path.realpath(filename)
else:
return os.path.realpath(filename)
file = get_path('path/somefile.ext')
with open(file) as f:
# ...
I can confirm this works on macOS Catalina with pyinstaller. I haven't had the chance to test this on .exe builds for Windows.
After hours of digging I finally found an answer! Even after every Step I count gain the right /Contents/Resources path.
First I had to get this working:
from AppKit import NSBundle
file = NSBundle.mainBundle().pathForResource_ofType_(name, ext)
For my Python App, finding the "AppKit" module I had to install
pip install pyobjc
But even then, the path for
NSBundle.mainBundle().pathForResource_ofType_(name, ext)
was strange. It was a path in "/private/var/" where I don't have Write Access
The final answer was to run
xattr -d com.apple.quarantine /Applications/YouPythonApp.app/
then I finally got the right path
/Applications/YouPythonApp.app/Contents/Resources/
where I also had write access and now I can save my .yaml file
with open(file, "w") as f:
yaml=YAML()
yaml.default_flow_style = False
yaml.dump(config, f)

QGIS3.14 Resource Sharing symbols are not appearing in atlas export via standalone python script

I created a standalone python script to export my atlas layouts. Everything is working great except that the SVG symbols that I am using from the Resource Sharing plugin are just question marks, assuming that it is having trouble locating them. However, if I run the script via the startup.py in the QGIS3 folder everything works like expected. I would really like to avoid using this method though as it prevents you from using QGIS until the script finishes, which takes about 2 hours. I am hoping that I just need to add a simple environmental variable to my .bat file so that it can locate the Resource Sharing plugin. Thanks in advance for any help!
.bat file
#ECHO off
set OSGEO4W_ROOT=C:\OSGeo4W64
call "%OSGEO4W_ROOT%\bin\o4w_env.bat"
call "%OSGEO4W_ROOT%\bin\qt5_env.bat"
call "%OSGEO4W_ROOT%\bin\py3_env.bat"
path %OSGEO4W_ROOT%\apps\qgis\bin;%PATH%
set QGIS_PREFIX_PATH=%OSGEO4W_ROOT%\apps\qgis
set GDAL_FILENAME_IS_UTF8=YES
set VSI_CACHE=TRUE
set VSI_CACHE_SIZE=1000000
set QT_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\qgis\qtplugins;%OSGEO4W_ROOT%\apps\qt5\plugins
SET PYCHARM="C:\Program Files\JetBrains\PyCharm 2019.2.3\bin\pycharm64.exe"
set PYTHONPATH=%OSGEO4W_ROOT%\apps\qgis\python
set PYTHONHOME=%OSGEO4W_ROOT%\apps\Python37
set PYTHONPATH=%OSGEO4W_ROOT%\apps\Python37\lib\site-packages;%PYTHONPATH%
set QT_QPA_PLATFORM_PLUGIN_PATH=%OSGEO4W_ROOT%\apps\Qt5\plugins\platforms
set QGIS_PREFIX_PATH=%OSGEO4W_ROOT%\apps\qgis
start "PyCharm aware of QGIS" /B %PYCHARM% %*
Python Script
from qgis.core import QgsApplication, QgsProject, QgsLayoutExporter
import os
import sys
def export_atlas(qgs_project_path, layout_name, outputs_folder):
# Open existing project
project = QgsProject.instance()
project.read(qgs_project_path)
print(f'Project in "{project.fileName()} loaded successfully')
# Open prepared layout that as atlas enabled and set
layout = project.layoutManager().layoutByName(layout_name)
# Export atlas
exporter = QgsLayoutExporter(layout)
settings = QgsLayoutExporter.PdfExportSettings()
exporter.exportToPdfs(layout.atlas(), outputs_folder, settings)
def run():
# Start a QGIS application without GUI
QgsApplication.setPrefixPath(r"C:\\OSGeo4W64\\apps\\qgis", True)
qgs = QgsApplication([], False)
qgs.initQgis()
sys.path.append(r'C:\OSGeo4W64\apps\qgis\python\plugins')
project_path = [project_path]
output_folder = [export_location]
layout_name_portrait = [portrait layout name]
layout_name_landscape = [landscape laytout name]
export_atlas(project_path, layout_name_portrait, output_folder)
export_atlas(project_path, layout_name_landscape, output_folder)
# Close the QGIS application
qgs.exitQgis()
run()
I guess that it might have something to do with the setting svg/searchPathsForSVG.
QgsSettings().setValue('svg/searchPathsForSVG', <your path>)

pyinstaller Error starting service: The service did not respond to the start or control request in a timely fashion

I have been searching since a couple of days for a solution without success.
We have a windows service build to copy some files from one location to another one.
So I build the code shown below with Python 3.7.
The full coding can be found on Github.
When I run the service using python all is working fine, I can install the service and also start the service.
This using commands:
Install the service:
python jis53_backup.py install
Run the service:
python jis53_backup.py start
When I now compile this code using pyinstaller with command:
pyinstaller -F --hidden-import=win32timezone jis53_backup.py
After the exe is created, I can install the service but when trying to start the service I get the error:
Error starting service: The service did not respond to the start or
control request in a timely fashion
I have gone through multiple posts on Stackoverflow and on Google related to this error however, without success. I don't have the option to install the python 3.7 programs on the PC's that would need to run this service. That's why we are trying to get a .exe build.
I have made sure to have the path updated according to the information that I found in the different questions.
Image of path definitions:
I also copied the pywintypes37.dll file.
From -> Python37\Lib\site-packages\pywin32_system32
To -> Python37\Lib\site-packages\win32
Does anyone have any other suggestions on how to get this working?
'''
Windows service to copy a file from one location to another
at a certain interval.
'''
import sys
import time
from distutils.dir_util import copy_tree
import servicemanager
import win32serviceutil
import win32service
from HelperModules.CheckFileExistance import check_folder_exists, create_folder
from HelperModules.ReadConfig import (check_config_file_exists,
create_config_file, read_config_file)
from ServiceBaseClass.SMWinService import SMWinservice
sys.path += ['filecopy_service/ServiceBaseClass',
'filecopy_service/HelperModules']
class Jis53Backup(SMWinservice):
_svc_name_ = "Jis53Backup"
_svc_display_name_ = "JIS53 backup copy"
_svc_description_ = "Service to copy files from server to local drive"
def start(self):
self.conf = read_config_file()
if not check_folder_exists(self.conf['dest']):
create_folder(self.conf['dest'])
self.isrunning = True
def stop(self):
self.isrunning = False
def main(self):
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
while self.isrunning:
# Copy the files from the server to a local folder
# TODO: build function to trigger only when a file is changed.
copy_tree(self.conf['origin'], self.conf['dest'], update=1)
time.sleep(30)
if __name__ == '__main__':
if sys.argv[1] == 'install':
if not check_config_file_exists():
create_config_file()
if len(sys.argv) == 1:
servicemanager.Initialize()
servicemanager.PrepareToHostSingle(Jis53Backup)
servicemanager.StartServiceCtrlDispatcher()
else:
win32serviceutil.HandleCommandLine(Jis53Backup)
I was also facing this issue after compiling using pyinstaller. For me, the issue was that I was using the paths to configs and logs file in dynamic way, for ex:
curr_path = os.path.dirname(os.path.abspath(__file__))
configs_path = os.path.join(curr_path, 'configs', 'app_config.json')
opc_configs_path = os.path.join(curr_path, 'configs', 'opc.json')
log_file_path = os.path.join(curr_path, 'logs', 'application.log')
This was working fine when I was starting the service using python service.py install/start. But after compiling it using pyinstaller, it always gave me error of not starting in timely fashion.
To resolve this, I made all the dynamic paths to static, for ex:
configs_path = 'C:\\Program Files (x86)\\ScantechOPC\\configs\\app_config.json'
opc_configs_path = 'C:\\Program Files (x86)\\ScantechOPC\\configs\\opc.json'
debug_file = 'C:\\Program Files (x86)\\ScantechOPC\\logs\\application.log'
After compiling via pyinstaller, it is now working fine without any error. Looks like when we do dynamic path, it doesn't get the actual path to files and thus it gives error.
Hope this solves your problem too. Thanks

How to run Python test case with Appium?

I've created simple test case using Python (in PyCharm editor) which should click on Join/Login button on iOS app, but it doesn't work. Here is the code:
from appium import webdriver
import unittest
import os
class LoginTests(unittest.TestCase):
def setUp(self):
desired_caps = {}
desired_caps['platformName'] = 'iOS'
desired_caps['platformVersion'] = '11.0'
desired_caps['deviceName'] = 'iPhone Simulator' # Run on simulator
desired_caps['bundleId'] = 'com.matchbook.MatchbookApp'
desired_caps['app'] = os.path.abspath('/Users/majdukovic/Library/Developer/Xcode/DerivedData/MatchBook-bgvchkbwrithuaegnjgpoffewdag/Build/Products/Debug-iphonesimulator/MatchBook.app') # Path to .app
self.wd = webdriver.Remote('http://0.0.0.0:4723/wd/hub', desired_caps)
self.wd.implicitly_wait(60)
loginButton = self.wd.find_element_by_id("JOIN/LOGIN") # Button ID
self.assertTrue(loginButton.is_displayed())
loginButton.click()
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(LoginTests)
unittest.TextTestRunner(verbosity=2).run(suite)
If I run this test case it returns:
"Ran 0 tests in 0.000s
OK"
Appium server is already running and app is opened. In PyCharm Preferences I've selected py.test as default test runner.
Details of my setup:
macOS - HighSierra v10.13
Appium desktop app - v1.2.6
Python - v2.7.10
xCode - v9.0.1
Simulator - iPhone 8, iOS v11.0
How about simple code like:
if __name__ == '__main__':
unittest.main()
I'm still not sure what is the problem, I tried a lot of things, but it won't work from Pycharm , so I've written code in Atom editor and run it from Terminal with command:
"python -m py.test name_of_test.py -s" and it works.
Met the same issue. Found out, that "setUp" name is not reads as "test"case.
try this:
def test_testcase1(self):
Testcases should be named like "test_*testcasename_or_whatever*(self)"
File name should be test*.py , For example:
test1.py
Function name should be test*, For example:
def test_testcase1(self):

How do I use cx_freeze?

I've created my setup.py file as instructed but I don't actually.. understand what to do next. Typing "python setup.py build" into the command line just gets a syntax error.
So, what do I do?
setup.py:
from cx_Freeze import setup, Executable
setup(
name = "On Dijkstra's Algorithm",
version = "3.1",
description = "A Dijkstra's Algorithm help tool.",
exectuables = [Executable(script = "Main.py", base = "Win32GUI")])
Add import sys as the new topline
You misspelled "executables" on the last line.
Remove script = on last line.
The code should now look like:
import sys
from cx_Freeze import setup, Executable
setup(
name = "On Dijkstra's Algorithm",
version = "3.1",
description = "A Dijkstra's Algorithm help tool.",
executables = [Executable("Main.py", base = "Win32GUI")])
Use the command prompt (cmd) to run python setup.py build. (Run this command from the folder containing setup.py.) Notice the build parameter we added at the end of the script call.
I'm really not sure what you're doing to get that error, it looks like you're trying to run cx_Freeze on its own, without arguments. So here is a short step-by-step guide on how to do it in windows (Your screenshot looks rather like the windows command line, so I'm assuming that's your platform)
Write your setup.py file. Your script above looks correct so it should work, assuming that your script exists.
Open the command line (Start -> Run -> "cmd")
Go to the location of your setup.py file and run python setup.py build
Notes:
There may be a problem with the name of your script. "Main.py" contains upper case letters, which might cause confusion since windows' file names are not case sensitive, but python is. My approach is to always use lower case for scripts to avoid any conflicts.
Make sure that python is on your PATH (read http://docs.python.org/using/windows.html)1
Make sure are are looking at the new cx_Freeze documentation. Google often seems to bring up the old docs.
I ran into a similar issue. I solved it by setting the Executable options in a variable and then simply calling the variable. Below is a sample setup.py that I use:
from cx_Freeze import setup, Executable
import sys
productName = "ProductName"
if 'bdist_msi' in sys.argv:
sys.argv += ['--initial-target-dir', 'C:\InstallDir\\' + productName]
sys.argv += ['--install-script', 'install.py']
exe = Executable(
script="main.py",
base="Win32GUI",
targetName="Product.exe"
)
setup(
name="Product.exe",
version="1.0",
author="Me",
description="Copyright 2012",
executables=[exe],
scripts=[
'install.py'
]
)
You can change the setup.py code to this:
from cx_freeze import setup, Executable
setup( name = "foo",
version = "1.1",
description = "Description of the app here.",
executables = [Executable("foo.py")]
)
I am sure it will work. I have tried it on both windows 7 as well as ubuntu 12.04
find the cxfreeze script and run it. It will be in the same path as your other python helper scripts, such as pip.
cxfreeze Main.py --target-dir dist
read more at:
http://cx-freeze.readthedocs.org/en/latest/script.html#script
I usually put the calling setup.py command into .bat file to easy recall.
Here is simple code in COMPILE.BAT file:
python setup.py build
#ECHO:
#ECHO . : ` . * F I N I S H E D * . ` : .
#ECHO:
#Pause
And the setup.py is organized to easy customizable parameters that let you set icon, add importe module library:
APP_NAME = "Meme Studio"; ## < Your App's name
Python_File = "app.py"; ## < Main Python file to run
Icon_Path = "./res/iconApp48.ico"; ## < Icon
UseFile = ["LANGUAGE.TXT","THEME.TXT"];
UseAllFolder = True; ## Auto scan folder which is same level with Python_File and append to UseFile.
Import = ["infi","time","webbrowser", "cv2","numpy","PIL","tkinter","math","random","datetime","threading","pathlib","os","sys"]; ## < Your Imported modules (cv2,numpy,PIL,...)
Import+=["pkg_resources","xml","email","urllib","ctypes", "json","logging"]
################################### CX_FREEZE IGNITER ###################################
from os import walk
def dirFolder(folderPath="./"): return next(walk(folderPath), (None, None, []))[1]; # [ Folder ]
def dirFile(folderPath="./"): return next(walk(folderPath), (None, None, []))[2]; # [ File ]
if UseAllFolder: UseFile += dirFolder();
import sys, pkgutil;
from cx_Freeze import setup, Executable;
BasicPackages=["collections","encodings","importlib"] + Import;
def AllPackage(): return [i.name for i in list(pkgutil.iter_modules()) if i.ispkg]; # Return name of all package
#Z=AllPackage();Z.sort();print(Z);
#while True:pass;
def notFound(A,v): # Check if v outside A
try: A.index(v); return False;
except: return True;
build_msi_options = {
'add_to_path': False,
"upgrade_code": "{22a35bac-14af-4159-7e77-3afcc7e2ad2c}",
"target_name": APP_NAME,
"install_icon": Icon_Path,
'initial_target_dir': r'[ProgramFilesFolder]\%s\%s' % ("Picox", APP_NAME)
}
build_exe_options = {
"includes": BasicPackages,
"excludes": [i for i in AllPackage() if notFound(BasicPackages,i)],
"include_files":UseFile,
"zip_include_packages": ["encodings"] ##
}
setup( name = APP_NAME,
options = {"build_exe": build_exe_options},#"bdist_msi": build_msi_options},#,
executables = [Executable(
Python_File,
base='Win32GUI',#Win64GUI
icon=Icon_Path,
targetName=APP_NAME,
copyright="Copyright (C) 2900AD Muc",
)]
);
The modules library list in the code above is minimum for workable opencv + pillow + win32 application.
Example of my project file organize:
========== U P D A T E ==========
Although cx_Freeze is a good way to create setup file. It's really consume disk space if you build multiple different software project that use large library module like opencv, torch, ai... Sometimes, users download installer, and receive false positive virus alert about your .exe file after install.
Thus, you should consider use SFX archive (.exe) your app package and SFX python package separate to share between app project instead.
You can create .bat that launch .py file and then convert .bat file to .exe with microsoft IExpress.exe.
Next, you can change .exe icon to your own icon with Resource Hacker: http://www.angusj.com/resourcehacker/
And then, create SFX archive of your package with PeaZip: https://peazip.github.io/
Finally change the icon.
The Python Package can be pack to .exe and the PATH register can made with .bat that also convertable to .exe.
If you learn more about command in .bat file and make experiments with Resource Hacker & self extract ARC in PeaZip & IExpress, you can group both your app project, python package into one .exe file only. It'll auto install what it need on user machine. Although this way more complex and harder, but then you can custom many install experiences included create desktop shorcut, and install UI, and add to app & feature list, and uninstall ability, and portable app, and serial key require, and custom license agreement, and fast window run box, and many more,..... but the important features you get is non virus false positive block, reduce 200MB to many GB when user install many your python graphic applications.

Categories

Resources