diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 00000000..7e18dd84
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,177 @@
+# GH Actions script to build the Pyzo binaries.
+
+name: CD
+
+on:
+ workflow_dispatch:
+ push:
+ tags: [ 'v*' ]
+ branches: [ cd ]
+
+jobs:
+
+ # The default Windows build serving the majority of users.
+ # Not needed because already installed: choco install innosetup --version=5.6.1
+ win64:
+ name: Build Windows 64
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.10'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -U pyside6 pyinstaller
+ pip install -r freeze/frozen_libs.txt
+ - name: Freeze
+ run: python freeze/pyzo_freeze.py
+ - name: Package
+ run: python freeze/pyzo_package.py
+ - name: Test frozen
+ run: python freeze/pyzo_test_frozen.py
+ - name: Upload distributions
+ uses: actions/upload-artifact@v2
+ with:
+ path: |
+ freeze/dist/*.zip
+ freeze/dist/*.exe
+ name: dist
+
+ # A 32bit windows build for people on old machines.
+ # Win32 is on the brink of deprecation, so we tune down on Py and Qt versions.
+ win32:
+ name: Build Windows 32
+ runs-on: windows-latest
+ env:
+ PYZO_QT_API: PyQt5
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+ architecture: x86
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -U ${{ env.PYZO_QT_API }} pyinstaller
+ pip install -r freeze/frozen_libs.txt
+ - name: Freeze
+ run: python freeze/pyzo_freeze.py
+ - name: Package
+ run: python freeze/pyzo_package.py
+ - name: Test frozen
+ run: python freeze/pyzo_test_frozen.py
+ - name: Upload distributions
+ uses: actions/upload-artifact@v2
+ with:
+ path: |
+ freeze/dist/*.zip
+ name: dist
+
+ # A MacOS build for x86_64. Via Rosetta this should work on all modern Macs,
+ # but an arm64 (M1) build would be nice in the future.
+ macos_amd64:
+ name: Build MacOS amd64
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.10'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -U pyside6 pyinstaller
+ pip install -r freeze/frozen_libs.txt
+ - name: Freeze
+ run: python freeze/pyzo_freeze.py
+ - name: Package
+ run: python freeze/pyzo_package.py
+ - name: Test frozen
+ shell: bash
+ run: python freeze/pyzo_test_frozen.py
+ - name: Upload distributions
+ uses: actions/upload-artifact@v2
+ with:
+ path: |
+ freeze/dist/*.zip
+ freeze/dist/*.dmg
+ name: dist
+
+ # For Linux we make a build on a somewhat older ubuntu. Most Linux users prefer (or are fine with)
+ # running Pyzo from source anyway.
+ linux_amd64:
+ name: Build Linux amd64
+ runs-on: ubuntu-18.04
+ env:
+ PYZO_QT_API: PyQt5
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.9
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.9'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -U ${{ env.PYZO_QT_API }} pyinstaller
+ pip install -r freeze/frozen_libs.txt
+ sudo apt install -y libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 \
+ libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
+ libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0
+ - name: Freeze
+ run: python freeze/pyzo_freeze.py
+ - name: Package
+ run: python freeze/pyzo_package.py
+ - name: Test frozen
+ run: xvfb-run --auto-servernum python freeze/pyzo_test_frozen.py
+ - name: Upload distributions
+ uses: actions/upload-artifact@v2
+ with:
+ path: |
+ freeze/dist/*.tar.gz
+ name: dist
+
+
+ publish:
+ name: Publish binaries to Github
+ runs-on: ubuntu-latest
+ needs: [ win64, win32, macos_amd64, linux_amd64 ]
+ if: success() && startsWith(github.ref, 'refs/tags/v')
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.10'
+ - name: Download assets
+ uses: actions/download-artifact@v1.0.0
+ with:
+ name: dist
+ - name: Get version from git ref
+ id: get_version
+ run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
+ - name: Create GH release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ steps.get_version.outputs.VERSION }}
+ release_name: Release ${{ steps.get_version.outputs.VERSION }}
+ body: |
+ Autogenerated binary wheels that include wgpu-native.
+ See [the changelog](https://github.com/pygfx/wgpu-py/blob/main/CHANGELOG.md) for details.
+ draft: false
+ prerelease: false
+ - name: Upload release assets
+ # Move back to official action after fix https://github.com/actions/upload-release-asset/issues/4
+ uses: AButler/upload-release-assets@v2.0
+ with:
+ release-tag: ${{ steps.get_version.outputs.VERSION }}
+ files: 'dist/*.zip;dist/*.tar.gz;dist/*.dmg;dist/*.exe;dist/*.msi'
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d535c7c8..051b84cd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,15 +1,17 @@
+# GH Actions script to test Pyzo.
+
name: CI
on:
push:
- branches: [ master ]
+ branches: [ main ]
pull_request:
- branches: [ master ]
+ branches: [ main ]
jobs:
lint-build:
- name: Linting
+ name: Test linting
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -51,6 +53,10 @@ jobs:
os: ubuntu-latest
pyversion: '3.9'
qtlib: pyside6
+ - name: Test Linux py310
+ os: ubuntu-latest
+ pyversion: '3.10'
+ qtlib: pyside6
# OS's
- name: Test Windows py310
os: windows-latest
@@ -61,28 +67,24 @@ jobs:
pyversion: '3.10'
qtlib: pyside6
# Qt libs
- - name: Test Linux py310 PyQt5
+ - name: Test Linux py39 PyQt5
os: ubuntu-latest
- pyversion: '3.10'
+ pyversion: '3.9'
qtlib: pyqt5
- - name: Test Linux py310 PyQt6
+ - name: Test Linux py39 PyQt6
os: ubuntu-latest
- pyversion: '3.10'
+ pyversion: '3.9'
qtlib: pyqt6
- - name: Test Linux py310 PySide2
+ - name: Test Linux py39 PySide2
os: ubuntu-latest
- pyversion: '3.10'
+ pyversion: '3.9'
qtlib: pyside2
- - name: Test Linux py310 PySide6
- os: ubuntu-latest
- pyversion: '3.10'
- qtlib: pyside6
steps:
- uses: actions/checkout@v2
- name: Setup os
if: matrix.os == 'ubuntu-latest'
run: |
- sudo apt-get install libegl1-mesa
+ sudo apt install libegl1-mesa
- name: Set up Python ${{ matrix.pyversion }}
uses: actions/setup-python@v2
with:
@@ -98,4 +100,14 @@ jobs:
- name: Test on repo
run: |
pytest -v tests
- # todo: add a dry-run or something so we actually boot Pyzo and close it
+ - name: Run Pyzo
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ sudo apt install -y libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 \
+ libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 \
+ libxcb-xinerama0 libxcb-xfixes0 libxcb-shape0
+ xvfb-run --auto-servernum python pyzo --test
+ - name: Run Pyzo
+ if: matrix.os != 'ubuntu-latest'
+ run: |
+ python pyzo --test
diff --git a/.gitignore b/.gitignore
index df0fe446..32093668 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,7 @@ _feedstock/
build/
dist/
frozen/
+freeze/build/
+freeze/dist/
*.pyc
pyzo.egg-info/*
diff --git a/README.md b/README.md
index 6e32d88d..b5b9d253 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# Pyzo - The Interactive editor for scientific Python
[](https://pypi.python.org/pypi/pyzo/)
-[](https://dev.azure.com/pyzo/pyzo/_build/latest?definitionId=1&branchName=master)
+[](https://github.com/pyzo/pyzo/actions/workflows/ci.yml)
Website: [pyzo.org](http://pyzo.org)
diff --git a/TRANSLATIONS.md b/TRANSLATIONS.md
index 15af97e8..abbb5ef6 100644
--- a/TRANSLATIONS.md
+++ b/TRANSLATIONS.md
@@ -5,7 +5,7 @@ Simplified Chinese.
For the translations we make use of Qt's translation system. To update
a translation, run Qt linguist on any of the `.tr` files
-[here](https://github.com/pyzo/pyzo/tree/master/pyzo/resources/translations).
+[here](https://github.com/pyzo/pyzo/tree/main/pyzo/resources/translations).
Then submit the result, preferably via a Github pull request (but
emailing it to me is fine too).
diff --git a/freeze/download_count.py b/download_count.py
similarity index 100%
rename from freeze/download_count.py
rename to download_count.py
diff --git a/freeze/boot.py b/freeze/boot.py
new file mode 100644
index 00000000..bb4078da
--- /dev/null
+++ b/freeze/boot.py
@@ -0,0 +1,102 @@
+import os
+import sys
+import platform
+import traceback
+import importlib
+
+import dialite
+
+
+TEST = "--test" in sys.argv
+
+
+# %% Utils
+
+
+def write(*msg):
+ print(*msg)
+ if TEST and os.getenv("PYZO_LOG", ""):
+ with open(os.getenv("PYZO_LOG"), "at") as f:
+ f.write(" ".join(msg) + "\n")
+
+
+class SourceImporter:
+ def __init__(self, dir):
+ self.module_names = set()
+ for name in os.listdir(dir):
+ fullname = os.path.join(dir, name)
+ if name.endswith(".py"):
+ self.module_names.add(name)
+ elif os.path.isdir(fullname):
+ if os.path.isfile(os.path.join(fullname, "__init__.py")):
+ self.module_names.add(name)
+
+ def find_spec(self, fullname, path, target=None):
+ if fullname.split(".")[0] in self.module_names:
+ return sys.meta_path[1].find_spec(fullname, path, target)
+ else:
+ return None
+
+
+def error_handler(cls, err, tb, action=""):
+ title = "Application aborted"
+ if action:
+ title += f" while {action}"
+ msg = f"{cls.__name__}: {err}"
+ # Try writing traceback to stderr
+ try:
+ tb_info = "".join(traceback.format_list(traceback.extract_tb(tb)))
+ write(f"{title}\n{msg}\n{tb_info}")
+ except Exception:
+ pass
+ # Use dialite to show error in modal window
+ if not TEST:
+ dialite.fail(title, msg)
+
+
+class BootAction:
+ def __init__(self, action):
+ self._action = action
+ try:
+ write(action)
+ except Exception:
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, cls, err, tb):
+ if err:
+ error_handler(cls, err, tb, self._action)
+ sys.exit(1)
+
+
+# %% Boot
+
+if TEST:
+ write("Checking Pyzo container")
+ write(platform.platform())
+ write(sys.version)
+
+
+with BootAction("Setting up source importer"):
+ source_dir = os.path.join(sys._MEIPASS, "source")
+ sys.path.insert(0, source_dir)
+ sys.meta_path.insert(0, SourceImporter(source_dir))
+
+
+with BootAction("Applying pre-import Qt tweaks"):
+ importlib.import_module("pyzo.pre_qt_import")
+
+
+with BootAction("Importing Qt"):
+ QtCore = importlib.import_module("pyzo.qt." + "QtCore")
+ QtGui = importlib.import_module("pyzo.qt." + "QtGui")
+ QtWidgets = importlib.import_module("pyzo.qt." + "QtWidgets")
+
+
+with BootAction("Running Pyzo"):
+ pyzo = importlib.import_module("pyzo")
+ write(f"Pyzo {pyzo.__version__}")
+ pyzo.start()
+ write("Stopped")
diff --git a/freeze/dllutils.py b/freeze/dllutils.py
deleted file mode 100644
index b9f04e42..00000000
--- a/freeze/dllutils.py
+++ /dev/null
@@ -1,248 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (C) 2012 Almar Klein
-# This module is distributed under the terms of the (new) BSD License.
-
-""" Various utilities to modify Dynamic Link libraries.
-
-Needed to build the Pyzo distro, and it's possible that this
-functionality is needed to fix extension modules after installation in
-a Pyzo distro.
-
-This is a mix of utilities for Windows, Mac and Linux.
-
-"""
-
-import os
-import stat
-import sys
-import subprocess
-import time
-import re
-
-
-_COMMAND_TO_SEARCH_PATH = []
-
-
-def get_command_to_set_search_path():
- """Get the command to change the RPATH of executables and dynamic
- libraries. Returns None if there is no such command or if it
- cannot be found.
- """
-
- # Check if already computed
- if _COMMAND_TO_SEARCH_PATH:
- return _COMMAND_TO_SEARCH_PATH[0]
-
- # Get name of the utility
- # In Pyzo it should be present in 'shared'.
- utilCommand = None
- if sys.platform.startswith("win"):
- return
- if sys.platform.startswith("linux"):
- utilname = "patchelf"
- if sys.platform.startswith("darwin"):
- utilname = "install_name_tool"
- if True:
- # Try old Pyzo
- utilCommand = os.path.join(sys.prefix, "shared", utilname)
- if not os.path.isfile(utilCommand):
- utilCommand = utilname
- # Try new Pyzo / anaconda
- utilCommand = os.path.join(sys.prefix, "bin", utilname)
- if not os.path.isfile(utilCommand):
- utilCommand = utilname
- # Test whether it exists
- try:
- subprocess.check_output(["which", utilCommand])
- except Exception:
- raise RuntimeError(
- "Could not get command (%s) to set search path." % utilCommand
- )
-
- # Store and return
- _COMMAND_TO_SEARCH_PATH.append(utilCommand)
- return utilCommand
-
-
-def set_search_path(fname, *args):
- """set_search_path(fname, *args)
- For the given library/executable, set the search path to the
- relative paths specified in args.
-
- For Linux: The RPATH is the path to search for its dependencies.
- http://enchildfone.wordpress.com/2010/03/23/a-description-of-rpath-origin-ld_library_path-and-portable-linux-binaries/
-
- For Mac: We use the @rpath identifier to get similar behavior to
- Linux. But each dependency must be specified. To realize this, we
- need to check for each dependency whether it is on one of te given
- search paths.
-
- For Windows: not supported in any way. Windows searches next to the
- library and then in system paths and PATH.
-
- """
-
- # Prepare
- args = [arg.lstrip("/") for arg in args if arg]
- args = [arg for arg in args if arg != "."] # Because we add empty dir anyway
- args.append("") # make libs search next to themselves
- command = get_command_to_set_search_path()
-
- if sys.platform.startswith("linux"):
- # Create search path value
- rpath = ":".join(["$ORIGIN/" + arg for arg in args])
- # Modify rpath using a call to patchelf utility
- cmd = [command, "--set-rpath", rpath, fname]
- subprocess.check_call(cmd)
- print("Set RPATH for %r" % os.path.basename(fname))
- # print('Set RPATH for %r: %r' % (os.path.basename(fname), rpath))
-
- elif sys.platform.startswith("darwin"):
- # ensure write permissions
- mode = os.stat(fname).st_mode
- if not (mode & stat.S_IWUSR):
- os.chmod(fname, mode | stat.S_IWUSR)
- # let the file itself know its place (simpyl on rpath)
- name = os.path.basename(fname)
- subprocess.call(("install_name_tool", "-id", "@rpath/" + name, fname))
- # find the references: call otool -L on the file
- otool = subprocess.Popen(("otool", "-L", fname), stdout=subprocess.PIPE)
- references = otool.stdout.readlines()[1:]
-
- # Replace each reference
- rereferencedlibs = []
- for reference in references:
- # find the actual referenced file name
- referencedFile = reference.decode().strip().split()[0]
- if referencedFile.startswith("@"):
- continue # the referencedFile is already a relative path
- # Get lib name
- _, name = os.path.split(referencedFile)
- if name.lower() == "python":
- name = "libpython" # Rename Python lib on Mac
- # see if we provided the referenced file
- potentiallibs = [
- os.path.join(os.path.dirname(fname), arg, name) for arg in args
- ]
- # if so, change the reference and rpath
- if any([os.path.isfile(p) for p in potentiallibs]):
- subprocess.call(
- (
- "install_name_tool",
- "-change",
- referencedFile,
- "@rpath/" + name,
- fname,
- )
- )
- for arg in args:
- mac_add_rpath(fname, "@loader_path/" + arg)
- mac_add_rpath(fname, "@executable_path/") # use libpython next to exe
- rereferencedlibs.append(name)
- if rereferencedlibs:
- print(
- 'Replaced refs for "%s": %s'
- % (os.path.basename(fname), ", ".join(rereferencedlibs))
- )
-
- elif sys.platform.startswith("win"):
- raise RuntimeError(
- "Windows has no way of setting the search path on a library or exe."
- )
- else:
- raise RuntimeError(
- "Do not know how to set search path of library or exe on %s" % sys.platform
- )
-
-
-def mac_add_rpath(fname, rpath):
- """mac_add_rpath(fname, rpath)
- Set the rpath for a Mac library or executble. If the rpath is already
- registered, it is ignored.
- """
- cmd = ["install_name_tool", "-add_rpath", rpath, fname]
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- while p.poll() is None:
- time.sleep(0.01)
- if p.returncode:
- msg = p.stdout.read().decode("utf-8")
- if "would duplicate path" in msg:
- pass # Ignore t
- else:
- raise RuntimeError("Could not set rpath: " + msg)
-
-
-def remove_CRT_dependencies(dirname, recurse=True):
- """remove_CRT_dependencies(path, recurse=True)
- Check all .dll and .pyd files in the given directory (and its
- subdirectories if recurse is True), removing the dependency on the
- Windows C runtime from the embedded manifest.
- """
- dllExt = [".dll", ".pyd"]
- for entry in os.listdir(dirname):
- p = os.path.join(dirname, entry)
- if recurse and os.path.isdir(p):
- remove_CRT_dependencies(p, recurse)
- elif os.path.isfile(p) and os.path.splitext(p)[1].lower() in dllExt:
- remove_CRT_dependency(p)
-
-
-def remove_CRT_dependency(filename):
- """remove_CRT_dependency(filename)
- Modify the embedded manifest of a Windows dll (or pyd file),
- such that it no longer depends on the Windows C runtime.
- In effect, the dll will fall back to using the C runtime that
- the executable depends on (and has loaded in memory).
-
- This function is not necessary for dll's and pyd's that come with
- Python, because these are build without the CRT dependencies for a
- while. However, some third party packages (e.g. PySide) do have
- these dependencies, and they need to be removed in order to work
- on a system that does not have the C-runtime installed.
-
- Based on this diff by C. Gohlke:
- http://bugs.python.org/file15113/msvc9compiler_stripruntimes_regexp2.diff
- See discussion at: http://bugs.python.org/issue4120
-
- """
- if "QtCore" in filename:
- 1 / 0
-
- # Read the whole file
- with open(filename, "rb") as f:
- try:
- bb = f.read()
- except IOError:
- # raise IOError('Could not read %s'%filename)
- print("Warning: could not read %s" % filename)
- return
-
- # Remove assemblyIdentity tag
- # This code is different from that in python's distutils/msvc9compiler.py
- # by removing re.DOTALL and replaceing the second DOT with "(.|\n|\r)",
- # which means that the first DOT cannot contain newlines. Would we not do
- # this, the match is too greedy (and causes tk85.dll to break).
- pattern = (
- r"""|)"""
- )
- pattern = re.compile(pattern.encode("ascii"))
- bb, hasMatch = _replacePatternWithSpaces(pattern, bb)
- if hasMatch:
- # Remove dependentAssembly tag if it's empty
- pattern = "\s*".encode("ascii")
- bb, hasMatch = _replacePatternWithSpaces(pattern, bb)
- # Write back
- with open(filename, "wb") as f:
- f.write(bb)
- print("Removed embedded MSVCR dependency for: %s" % filename)
-
-
-def _replacePatternWithSpaces(pattern, bb):
- match = re.search(pattern, bb)
- if match is not None:
- L = match.end() - match.start()
- bb = re.sub(pattern, b" " * L, bb)
- return bb, True
- else:
- return bb, False
diff --git a/freeze/freezeScript.py b/freeze/freezeScript.py
deleted file mode 100644
index 6f5e28ab..00000000
--- a/freeze/freezeScript.py
+++ /dev/null
@@ -1,327 +0,0 @@
-#!/usr/bin/env python3
-
-""" FREEZING Pyzo WITH PYINSTALLER
-
-Pyzo is frozen in such a way that it still uses the plain source code.
-This is achieved by putting the Pyzo package in a subdirectory called
-"source". This source directory is added to sys.path by __main__.py.
-
-In case we need better support for older MacOS:
-https://gist.github.com/phfaist/a5b8a895b003822df5397731f4673042
-
-"""
-
-import os
-import re
-import sys
-import shutil
-import zipfile
-import tarfile
-import subprocess
-
-import PyInstaller.__main__
-
-
-# Define app name and such
-name = "pyzo"
-thisDir = os.path.abspath(os.path.dirname(__file__))
-baseDir = os.path.abspath(os.path.join(thisDir, "..")) + "/"
-srcDir = baseDir + "pyzo/"
-distDir = baseDir + "frozen/"
-iconFile = srcDir + "resources/appicons/pyzologo.ico"
-
-sys.path.insert(0, baseDir)
-
-
-## Includes and excludes
-
-# The Qt toolkit that we use
-QT_API = "PyQt5"
-
-# All known Qt toolkits, mainly to exclude them
-qt_kits = {"PySide", "PySide2", "PyQt4", "PyQt5"}
-
-# Imports that PyInstaller may have missed, or that are simply common/useful
-# and may be used by some tools.
-includes = ["code", "shutil"]
-
-# Exclude stuff that somehow gets, or may get, selected by PyInstaller
-excludes = ["numpy", "scipy", "win32com", "conda", "pip", "IPython"]
-
-# Excludes for tk
-tk_excludes = [
- "pywin",
- "pywin.debugger",
- "pywin.debugger.dbgcon",
- "pywin.dialogs",
- "pywin.dialogs.list",
- "Tkconstants",
- "Tkinter",
- "tcl",
-]
-excludes.extend(tk_excludes)
-
-# Excludes for Qt
-qt_excludes = [
- "QtNetwork",
- "QtOpenGL",
- "QtXml",
- "QtTest",
- "QtSql",
- "QtSvg",
- "QtBluetooth",
- "QtDBus",
- "QtDesigner",
- "QtLocation",
- "QtPositioning",
- "QtMultimedia",
- "QtMultimediaWidgets",
- "QtQml",
- "QtQuick",
- "QtSql",
- "QtSvg",
- "QtTest",
- "QtWebKit",
- "QtXml",
- "QtXmlPatterns",
- "QtDeclarative",
- "QtScript",
- "QtScriptTools",
- "QtUiTools",
- "QtQuickWidgets",
- "QtSensors",
- "QtSerialPort",
- "QtWebChannel",
- "QtWebKitWidgets",
- "QtWebSockets",
-]
-
-for qt_ver in qt_kits:
- for excl in qt_excludes:
- excludes.append(qt_ver + "." + excl)
-
-excludes.extend(qt_kits.difference([QT_API]))
-
-
-## Freeze
-
-# Clear first
-if os.path.isdir(distDir):
- shutil.rmtree(distDir)
-os.makedirs(distDir)
-
-
-cmd = ["--clean", "--onedir", "--name", name, "--distpath", distDir]
-
-for m in includes:
- cmd.extend(["--hidden-import", m])
-for m in excludes:
- cmd.extend(["--exclude-module", m])
-
-if sys.platform.startswith("win"):
- cmd.append("--windowed") # not a console app
- cmd.extend(["--icon", iconFile])
-elif sys.platform.startswith("darwin"):
- cmd.append("--windowed") # makes a .app bundle
- cmd.extend(["--icon", iconFile[:-3] + "icns"])
- cmd.extend(["--osx-bundle-identifier", "org.pyzo.pyzo4"])
-
-cmd.append(srcDir + "__main__.py")
-
-PyInstaller.__main__.run(cmd)
-
-try:
- os.remove(os.path.join(thisDir, "pyzo.spec"))
-except Exception:
- pass
-
-
-## Process source code and other resources
-
-with open(srcDir + "__init__.py") as fh:
- __version__ = re.search(r"__version__ = \"(.*?)\"", fh.read()).group(1)
-
-bitness = "32" if sys.maxsize <= 2 ** 32 else "64"
-
-
-def copydir_smart(path1, path2):
- """like shutil.copytree, but ...
- * ignores __pycache__directories
- * ignores hg, svn and git directories
- """
- # Ensure destination directory does exist
- if not os.path.isdir(path2):
- os.makedirs(path2)
- # Itereate over elements
- count = 0
- for sub in os.listdir(path1):
- fullsub1 = os.path.join(path1, sub)
- fullsub2 = os.path.join(path2, sub)
- if sub in ["__pycache__", ".hg", ".svn", ".git"]:
- continue
- elif sub.endswith(".pyc") and os.path.isfile(fullsub1[:-1]):
- continue
- elif os.path.isdir(fullsub1):
- count += copydir_smart(fullsub1, fullsub2)
- elif os.path.isfile(fullsub1):
- shutil.copy(fullsub1, fullsub2)
- count += 1
- # Return number of copies files
- return count
-
-
-SETTINGS_TEXT = """
-Portable settings folder
-------------------------
-This folder can be used to let the application and the libaries that
-it uses to store configuration files local to the executable. One use
-case is having this app on a USB drive that you use on different
-computers.
-
-This functionality is enabled if the folder is named "settings" and is
-writable by the application (i.e. should not be in "c:\program files\..."
-or "/usr/..."). This functionality can be deactivated by renaming
-it (e.g. prepending an underscore). To reset config files, clear the
-contents of the "pyzo" sub-folder (but do not remove the folder itself).
-
-Note that some libraries may ignore this functionality and use the
-normal system configuration directory instead.
-
-This "standard" was discussed between the authors of WinPython,
-PortablePython and Pyzo. Developers can use the appdata_dir() function
-from https://bitbucket.org/pyzo/pyzolib/src/tip/paths.py to
-use this standard. For more info, contact either of us.
-
-""".lstrip()
-
-# Post process the frozen dir (and the frozen app-dir on OS X)
-frozenDirs = [os.path.join(distDir, "pyzo")]
-if sys.platform.startswith("darwin"):
- frozenDirs.append(os.path.join(distDir, "pyzo.app", "Contents", "MacOS"))
-
-for frozenDir in frozenDirs:
-
- # Copy the whole Pyzo package
- copydir_smart(os.path.join(srcDir), os.path.join(frozenDir, "source", "pyzo"))
-
- # Create settings folder and put in a file
- os.mkdir(os.path.join(frozenDir, "_settings"))
- os.mkdir(os.path.join(frozenDir, "_settings", "pyzo"))
- with open(os.path.join(frozenDir, "_settings", "README.txt"), "wb") as file:
- file.write(SETTINGS_TEXT.encode("utf-8"))
-
-
-# Patch info.plist
-if sys.platform.startswith("darwin"):
- extra_plist_info = """
- CFBundleShortVersionString
- X.Y.Z
- NSHighResolutionCapable
-
- """.strip()
- extra_plist_info = "\n\t".join(
- line.strip() for line in extra_plist_info.splitlines()
- )
- extra_plist_info = extra_plist_info.replace("X.Y.Z", __version__)
- plist_filename = os.path.join(distDir, "pyzo.app", "Contents", "Info.plist")
- text = open(plist_filename, "rb").read().decode()
- i1 = text.index("CFBundleShortVersionString", i1) + len("")
- text = text[:i1] + extra_plist_info + text[i2:]
- with open(plist_filename, "wb") as f:
- f.write(text.encode())
-
-
-## Package things up
-
-# Linux: .tar.gz
-# Windows: zip and exe installer
-# MacOS: DMG
-
-
-if sys.platform.startswith("linux"):
- print("Packing up into tar.gz ...")
-
- oridir = os.getcwd()
- os.chdir(distDir)
- try:
- tarfilename = "pyzo-" + __version__ + "-linux" + bitness + ".tar.gz"
- tf = tarfile.open(tarfilename, "w|gz")
- with tf:
- tf.add("pyzo", arcname="pyzo-" + __version__)
- finally:
- os.chdir(oridir)
-
-
-if sys.platform.startswith("win"):
- print("Packing up into zip ...")
-
- zipfilename = "pyzo-" + __version__ + "-win" + bitness + ".zip"
- zf = zipfile.ZipFile(
- os.path.join(distDir, zipfilename), "w", compression=zipfile.ZIP_DEFLATED
- )
- with zf:
- for root, dirs, files in os.walk(os.path.join(distDir, "pyzo")):
- for fname in files:
- filename1 = os.path.join(root, fname)
- filename2 = os.path.relpath(filename1, os.path.join(distDir, "pyzo"))
- filename2 = os.path.join("pyzo-" + __version__, filename2)
- zf.write(filename1, filename2)
-
-
-if sys.platform.startswith("win") and bitness == "64":
- # Note: for some reason the 32bit installer is broken. Ah well, the zip works.
- print("Packing up into exe installer (via Inno Setup) ...")
-
- exes = [
- r"c:\Program Files (x86)\Inno Setup 5\ISCC.exe",
- r"c:\Program Files (x86)\Inno Setup 6\ISCC.exe",
- ]
- for exe in exes:
- if os.path.isfile(exe):
- break
- else:
- raise RuntimeError("Could not find Inno Setup exe")
-
- # Set inno file
- innoFile1 = os.path.join(thisDir, "installerBuilderScript.iss")
- innoFile2 = os.path.join(thisDir, "installerBuilderScript2.iss")
- text = open(innoFile1, "rb").read().decode()
- text = text.replace("X.Y.Z", __version__).replace("64", bitness)
- if bitness == "32":
- text = text.replace("ArchitecturesInstallIn64BitMode = x64", "")
- with open(innoFile2, "wb") as f:
- f.write(text.encode())
- try:
- subprocess.check_call([exe, "/Qp", innoFile2])
- finally:
- os.remove(innoFile2)
-
-
-if sys.platform.startswith("darwin"):
- print("Packing up into DMG ...")
-
- appDir = distDir + "pyzo.app"
- dmgFile = distDir + "pyzo-" + __version__ + "-macos.dmg"
-
- if (
- os.spawnlp(
- os.P_WAIT,
- "hdiutil",
- "hdiutil",
- "create",
- "-fs",
- "HFSX",
- "-format",
- "UDZO",
- dmgFile,
- "-imagekey",
- "zlib-level=9",
- "-srcfolder",
- appDir,
- "-volname",
- "pyzo",
- )
- != 0
- ):
- raise OSError("creation of the dmg failed")
diff --git a/freeze/frozen_libs.txt b/freeze/frozen_libs.txt
new file mode 100644
index 00000000..e39bdaae
--- /dev/null
+++ b/freeze/frozen_libs.txt
@@ -0,0 +1,2 @@
+packaging
+dialite
diff --git a/freeze/installerBuilderScript.iss b/freeze/installerBuilderScript.iss
index 06a90472..b37094fb 100644
--- a/freeze/installerBuilderScript.iss
+++ b/freeze/installerBuilderScript.iss
@@ -11,7 +11,7 @@ ArchitecturesInstallIn64BitMode = x64
DefaultDirName = {pf}\pyzo
DefaultGroupName = pyzo
-SourceDir = ../frozen/pyzo
+SourceDir = dist/pyzo
OutputDir = ..
OutputBaseFilename = pyzo-X.Y.Z-win64
diff --git a/freeze/pyzo_freeze.py b/freeze/pyzo_freeze.py
new file mode 100644
index 00000000..cc41f9d2
--- /dev/null
+++ b/freeze/pyzo_freeze.py
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+
+""" PyInstaller script
+"""
+
+import os
+import sys
+import shutil
+from distutils.sysconfig import get_python_lib
+
+
+# Definitions
+name = "pyzo"
+qt_api = os.getenv("PYZO_QT_API", "PySide6")
+this_dir = os.path.abspath(os.path.dirname(__file__)) + "/"
+exe_script = this_dir + "boot.py"
+dist_dir = this_dir + "dist/"
+icon_file = os.path.abspath(
+ os.path.join(this_dir, "..", "pyzo", "resources", "appicons", "pyzologo.ico")
+)
+
+# Run the script from the freeze dir
+os.chdir(this_dir)
+
+
+## Utils
+
+
+def _find_modules(root, extensions, skip, parent=""):
+ """Yield all modules and packages and their submodules and subpackages found at `root`.
+ Nested folders that do _not_ contain an __init__.py file are assumed to also be on sys.path.
+ `extensions` should be a set of allowed file extensions (without the .). `skip` should be
+ a set of file or folder names to skip. The `parent` argument is for internal use only.
+ """
+ for filename in os.listdir(root):
+ if filename.startswith("_"):
+ continue
+ if filename in skip:
+ continue
+ path = os.path.join(root, filename)
+ if os.path.isdir(path):
+ if filename.isidentifier() and os.path.exists(
+ os.path.join(path, "__init__.py")
+ ):
+ if parent:
+ packageName = parent + "." + filename
+ else:
+ packageName = filename
+ for module in _find_modules(path, extensions, skip, packageName):
+ yield module
+ elif not parent:
+ for module in _find_modules(path, extensions, skip, ""):
+ yield module
+ elif "." in filename:
+ moduleName, ext = filename.split(".", 1)
+ if ext in extensions and moduleName.isidentifier():
+ if parent and moduleName == "__init__":
+ yield parent
+ elif parent:
+ yield parent + "." + moduleName
+ else:
+ yield moduleName
+
+
+def get_stdlib_modules():
+ """Return a list of all module names that are part of the Python Standard Library."""
+ stdlib_path = get_python_lib(standard_lib=True)
+ extensions = {"py", "so", "dll", "pyd"}
+ skip = {
+ "site-packages", # not stdlib
+ "idlelib", # irrelevant for us
+ "lib2to3", # irrelevant for us
+ "test", # irrelevant for us
+ "turtledemo", # irrelevant for us
+ "tkinter", # not needed
+ "tk",
+ "tcl",
+ "unittest", # not needed
+ "distutils", # not needed - also must be avoided*
+ }
+ # On distutils: one distutils submodule causes IPython to be included,
+ # and with that, a sloth of libs like matplotlib and multiple Qt libs.
+ return list(_find_modules(stdlib_path, extensions, skip))
+
+
+def copydir_smart(path1, path2):
+ """like shutil.copytree, but ...
+ * ignores __pycache__directories
+ * ignores hg, svn and git directories
+ """
+ # Ensure destination directory does exist
+ if not os.path.isdir(path2):
+ os.makedirs(path2)
+ # Itereate over elements
+ count = 0
+ for sub in os.listdir(path1):
+ fullsub1 = os.path.join(path1, sub)
+ fullsub2 = os.path.join(path2, sub)
+ if sub in ["__pycache__", ".hg", ".svn", ".git"]:
+ continue
+ elif sub.endswith(".pyc") and os.path.isfile(fullsub1[:-1]):
+ continue
+ elif os.path.isdir(fullsub1):
+ count += copydir_smart(fullsub1, fullsub2)
+ elif os.path.isfile(fullsub1):
+ shutil.copy(fullsub1, fullsub2)
+ count += 1
+ # Return number of copies files
+ return count
+
+
+# All known Qt toolkits, excluded the one we will use
+other_qt_kits = {"PySide", "PySide2", "PySide6", "PyQt4", "PyQt5", "PyQt6"}
+other_qt_kits.remove(qt_api)
+
+
+## Includes and excludes
+
+# We don't really make use of PySides detection mechanism, but instead specify
+# explicitly what our binaries need. We include almost the whole stdlib, and
+# a small subset of Qt. This way, future versions of Pyzo can work in the same
+# container, and we still have a relatively small footprint.
+
+includes = []
+excludes = []
+
+
+# Include almost all stdlib modules
+includes += get_stdlib_modules()
+
+# Include a few 3d party packages, e.g. deps of qtpy
+includes += open(os.path.join(this_dir, "frozen_libs.txt"), "rt").read().split()
+
+# Include a subset of Qt modules
+qt_includes = [
+ "QtCore", # Standard
+ "QtGui", # Standard
+ "QtWidgets", # Standard
+ "QtHelp", # For docs
+ "QtOpenGLWidgets", # Because qtpy imports QOpenGLQWidget into QtWidgets
+]
+includes += [f"{qt_api}.{sub}" for sub in qt_includes]
+
+
+# There is a tendency to include tk modules
+excludes += ["tkinter", "tk", "tcl"]
+
+# Also exclude other Qt toolkits just to be sure
+excludes += list(other_qt_kits)
+
+# PySide tends to include *all* qt modules, resulting in a 300MB or so folder,
+# so we mark them as unwanted, getting us at around 120MB.
+qt_excludes = [
+ "QtNetwork",
+ "QtOpenGL",
+ "QtXml",
+ "QtTest",
+ "QtSql",
+ "QtSvg",
+ "QtBluetooth",
+ "QtDBus",
+ "QtDesigner",
+ "QtLocation",
+ "QtPositioning",
+ "QtMultimedia",
+ "QtMultimediaWidgets",
+ "QtQml",
+ "QtQuick",
+ "QtSql",
+ "QtSvg",
+ "QtTest",
+ "QtWebKit",
+ "QtXml",
+ "QtXmlPatterns",
+ "QtDeclarative",
+ "QtScript",
+ "QtScriptTools",
+ "QtUiTools",
+ "QtQuickWidgets",
+ "QtSensors",
+ "QtSerialPort",
+ "QtWebChannel",
+ "QtWebKitWidgets",
+ "QtWebSockets",
+]
+excludes += [f"{qt_api}.{sub}" for sub in qt_excludes]
+
+
+## Freeze
+
+import PyInstaller.__main__
+
+
+# Clear first
+if os.path.isdir(dist_dir):
+ shutil.rmtree(dist_dir)
+os.makedirs(dist_dir)
+
+
+cmd = ["--clean", "--onedir", "--name", name, "--distpath", dist_dir]
+
+for m in includes:
+ cmd.extend(["--hidden-import", m])
+for m in excludes:
+ cmd.extend(["--exclude-module", m])
+
+if sys.platform.startswith("win"):
+ cmd.append("--windowed") # not a console app
+ cmd.extend(["--icon", icon_file])
+elif sys.platform.startswith("darwin"):
+ cmd.append("--windowed") # makes a .app bundle
+ cmd.extend(["--icon", icon_file[:-3] + "icns"])
+ cmd.extend(["--osx-bundle-identifier", "org.pyzo.app"])
+
+cmd.append(exe_script)
+
+PyInstaller.__main__.run(cmd)
+
+try:
+ os.remove(os.path.join(this_dir, f"{name}.spec"))
+except Exception:
+ pass
+
+
+## Add Pyzo source
+
+if sys.platform.startswith("darwin"):
+ target_dir = os.path.join(dist_dir, "pyzo.app", "Contents", "MacOS")
+else:
+ target_dir = os.path.join(dist_dir, name)
+
+copydir_smart(
+ os.path.join(this_dir, "..", "pyzo"), os.path.join(target_dir, "source", "pyzo")
+)
+
+
+## Add portable settings dir
+
+SETTINGS_TEXT = """
+Portable settings folder
+------------------------
+This folder can be used to let the application and the libaries that
+it uses to store configuration files local to the executable. One use
+case is having this app on a USB drive that you use on different
+computers.
+
+This functionality is enabled if the folder is named "settings" and is
+writable by the application (i.e. should not be in "c:\program files\..."
+or "/usr/..."). This functionality can be deactivated by renaming
+it (e.g. prepending an underscore). To reset config files, clear the
+contents of the "pyzo" sub-folder (but do not remove the folder itself).
+
+Note that some libraries may ignore this functionality and use the
+normal system configuration directory instead.
+""".lstrip()
+
+# Create settings folder and put in a file
+os.mkdir(os.path.join(target_dir, "_settings"))
+os.mkdir(os.path.join(target_dir, "_settings", "pyzo"))
+with open(os.path.join(target_dir, "_settings", "README.txt"), "wb") as file:
+ file.write(SETTINGS_TEXT.encode("utf-8"))
diff --git a/freeze/pyzo_package.py b/freeze/pyzo_package.py
new file mode 100644
index 00000000..265aec5c
--- /dev/null
+++ b/freeze/pyzo_package.py
@@ -0,0 +1,128 @@
+import os
+import re
+import sys
+import zipfile
+import tarfile
+import platform
+import subprocess
+
+
+this_dir = os.path.abspath(os.path.dirname(__file__)) + "/"
+dist_dir = this_dir + "dist/"
+
+
+with open(os.path.join(this_dir, "..", "pyzo", "__init__.py")) as fh:
+ __version__ = re.search(r"__version__ = \"(.*?)\"", fh.read()).group(1)
+
+bitness = "32" if sys.maxsize <= 2 ** 32 else "64"
+
+osname = os.getenv("PYZO_OSNAME", "")
+if osname:
+ pass
+elif sys.platform.startswith("linux"):
+ osname = "linux_" + platform.machine()
+elif sys.platform.startswith("win"):
+ osname = f"win{bitness}"
+elif sys.platform.startswith("darwin"):
+ osname = "macos_" + platform.machine()
+else:
+ raise RuntimeError("Unknown platform")
+
+basename = f"pyzo-{__version__}-{osname}"
+
+
+## Utils
+
+
+def package_tar_gz():
+ print("Packing up into tar.gz ...")
+
+ oridir = os.getcwd()
+ os.chdir(dist_dir)
+ try:
+ tf = tarfile.open(basename + ".tar.gz", "w|gz")
+ with tf:
+ tf.add("pyzo", arcname="pyzo-" + __version__)
+ finally:
+ os.chdir(oridir)
+
+
+def package_zip():
+ print("Packing up into zip ...")
+
+ zf = zipfile.ZipFile(
+ os.path.join(dist_dir, basename + ".zip"), "w", compression=zipfile.ZIP_DEFLATED
+ )
+ with zf:
+ for root, dirs, files in os.walk(os.path.join(dist_dir, "pyzo")):
+ for fname in files:
+ filename1 = os.path.join(root, fname)
+ filename2 = os.path.relpath(filename1, os.path.join(dist_dir, "pyzo"))
+ filename2 = os.path.join("pyzo-" + __version__, filename2)
+ zf.write(filename1, filename2)
+
+
+def package_inno_installer():
+ print("Packing up into exe installer (via Inno Setup) ...")
+
+ exes = [
+ r"c:\Program Files (x86)\Inno Setup 5\ISCC.exe",
+ r"c:\Program Files (x86)\Inno Setup 6\ISCC.exe",
+ ]
+ for exe in exes:
+ if os.path.isfile(exe):
+ break
+ else:
+ raise RuntimeError("Could not find Inno Setup exe")
+
+ # Set inno file
+ innoFile1 = os.path.join(this_dir, "installerBuilderScript.iss")
+ innoFile2 = os.path.join(this_dir, "installerBuilderScript2.iss")
+ text = open(innoFile1, "rb").read().decode()
+ text = text.replace("X.Y.Z", __version__).replace("64", bitness)
+ if bitness == "32":
+ text = text.replace("ArchitecturesInstallIn64BitMode = x64", "")
+ with open(innoFile2, "wb") as f:
+ f.write(text.encode())
+ try:
+ subprocess.check_call([exe, "/Qp", innoFile2], cwd=dist_dir)
+ finally:
+ os.remove(innoFile2)
+
+
+def package_dmg():
+ print("Packing up into DMG ...")
+
+ app_dir = "pyzo.app"
+ dmg_file = basename + ".dmg"
+
+ cmd = ["hdiutil", "create"]
+ cmd.extend(["-srcfolder", app_dir])
+ cmd.extend(["-volname", "pyzo"])
+ cmd.extend(["-format", "UDZO"])
+ cmd.extend(["-fs", "HFSX"])
+ # cmd.extend(["-uid", "99"]) # who ever is mounting
+ # cmd.extend(["-gid", "99"]) # who ever is mounting
+ cmd.extend(["-mode", "555"]) # readonly
+ cmd.append("-noscrub")
+ cmd.append(dmg_file)
+
+ subprocess.check_call(cmd, cwd=dist_dir)
+
+
+## Build
+
+
+if sys.platform.startswith("linux"):
+ package_zip()
+ package_tar_gz()
+
+if sys.platform.startswith("win"):
+ package_zip()
+ if bitness == "64":
+ # Note: for some reason the 32bit installer is broken. Ah well, the zip works.
+ package_inno_installer()
+
+if sys.platform.startswith("darwin"):
+ package_zip()
+ package_dmg()
diff --git a/freeze/pyzo_test_frozen.py b/freeze/pyzo_test_frozen.py
new file mode 100644
index 00000000..c7cdf8d3
--- /dev/null
+++ b/freeze/pyzo_test_frozen.py
@@ -0,0 +1,34 @@
+import os
+import sys
+import subprocess
+
+this_dir = os.path.abspath(os.path.dirname(__file__)) + "/"
+dist_dir = os.path.join(this_dir, "dist")
+
+# Get what executable to run
+if sys.platform.startswith("win"):
+ exe = os.path.join(dist_dir, "pyzo", "pyzo.exe")
+elif sys.platform.startswith("darwin"):
+ exe = os.path.join(dist_dir, "pyzo.app", "Contents", "MacOS", "pyzo")
+else:
+ exe = os.path.join(dist_dir, "pyzo", "pyzo")
+
+# Prepare log file
+logfile = os.path.join(this_dir, "log.txt")
+with open(logfile, "wt") as f:
+ f.write("")
+
+# Run Pyzo
+os.environ["PYZO_LOG"] = logfile
+subprocess.run([exe, "--test"])
+
+# Process log
+print("=" * 80)
+with open(logfile, "rt") as f:
+ log = f.read()
+os.remove(logfile)
+print(log)
+if log.strip().endswith("Stopped"):
+ sys.exit(0)
+else:
+ sys.exit("Unsuccessful Pyzo test run")
diff --git a/freeze/receive_file_from_vm.py b/freeze/receive_file_from_vm.py
deleted file mode 100644
index fc722e27..00000000
--- a/freeze/receive_file_from_vm.py
+++ /dev/null
@@ -1,27 +0,0 @@
-"""
-Getting files from/to the OS X VM can be a pain. This is a simple webserver
-to allow posting a file.
-"""
-
-import os
-import asgish
-
-
-@asgish.to_asgi
-async def handler(request):
- assert request.method == "POST"
- filename2 = request.path.strip("/")
- filename1 = filename2 + ".part"
- assert "/" not in filename1
- with open(filename1, "wb") as f:
- async for chunk in request.iter_body():
- f.write(chunk)
- if os.path.isfile(filename2):
- os.remove(filename2)
- os.rename(filename1, filename2)
- print("received", filename2)
- return "Success!"
-
-
-if __name__ == "__main__":
- asgish.run(handler, "uvicorn", "0.0.0.0:80")
diff --git a/pyzo/__init__.py b/pyzo/__init__.py
index 4af089c0..e7260e18 100644
--- a/pyzo/__init__.py
+++ b/pyzo/__init__.py
@@ -43,319 +43,15 @@
# Set version number
__version__ = "4.11.7"
-import os
import sys
-import ctypes
-import locale
-import traceback
# Check Python version
-if sys.version < "3":
- raise RuntimeError("Pyzo requires Python 3.x to run.")
-
-# Make each OS find platform plugins etc. - or let PyInstaller do its thing?
-if getattr(sys, "frozen", False):
- app_dir = os.path.dirname(sys.executable)
- # if sys.platform.startswith('win'):
- # os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = app_dir
- # if sys.platform.startswith("linux"):
- # # os.environ['QT_XKB_CONFIG_ROOT'] = '.'
- # os.environ["FONTCONFIG_FILE"] = os.path.join(
- # app_dir, "source/pyzo/resources", "fonts/linux_fonts.conf"
- # )
-
-# Automatically scale along on HDPI displays. See issue #531 and e.g.
-# https://wiki.archlinux.org/index.php/HiDPI#Qt_5
-if "QT_AUTO_SCREEN_SCALE_FACTOR" not in os.environ:
- os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
-
-# Fix Qt now showing a window on MacOS Big Sur
-os.environ["QT_MAC_WANTS_LAYER"] = "1"
-
-# Import yoton as an absolute package
-from pyzo import yotonloader # noqa
-from pyzo.util import paths
-
-# If there already is an instance of Pyzo, and the user is trying an
-# Pyzo command, we should send the command to the other process and quit.
-# We do this here, were we have not yet loaded Qt, so we are very light.
-from pyzo.core import commandline
-
-if commandline.is_our_server_running():
- print("Started our command server")
-else:
- # Handle command line args now
- res = commandline.handle_cmd_args()
- if res:
- print(res)
- sys.exit()
- else:
- # No args, proceed with starting up
- print("Our command server is *not* running")
-
-
-from pyzo.util import zon as ssdf # zon is ssdf-light
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
-
-# Enable high-res displays
-try:
- ctypes.windll.shcore.SetProcessDpiAwareness(1)
- ctypes.windll.shcore.SetProcessDpiAwareness(2)
-except Exception:
- pass # fail on non-windows
-try:
- QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
- QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
-except Exception:
- pass # fail on older Qt's
-
-# Import language/translation tools
-from pyzo.util._locale import translate, setLanguage # noqa
-
-# Set environ to let kernel know some stats about us
-os.environ["PYZO_PREFIX"] = sys.prefix
-_is_pyqt4 = hasattr(QtCore, "PYQT_VERSION_STR")
-os.environ["PYZO_QTLIB"] = "PyQt4" if _is_pyqt4 else "PySide"
-
-
-class MyApp(QtWidgets.QApplication):
- """So we an open .py files on OSX.
- OSX is smart enough to call this on the existing process.
- """
-
- def event(self, event):
- if isinstance(event, QtGui.QFileOpenEvent):
- fname = str(event.file())
- if fname and fname != "pyzo":
- sys.argv[1:] = []
- sys.argv.append(fname)
- res = commandline.handle_cmd_args()
- if not commandline.is_our_server_running():
- print(res)
- sys.exit()
- return QtWidgets.QApplication.event(self, event)
-
-
-if not sys.platform.startswith("darwin"):
- MyApp = QtWidgets.QApplication # noqa
-
-
-## Install excepthook
-# In PyQt5 exceptions in Python will cuase an abort
-# http://pyqt.sourceforge.net/Docs/PyQt5/incompatibilities.html
-
-
-def pyzo_excepthook(type, value, tb):
- out = "Uncaught Python exception: " + str(value) + "\n"
- out += "".join(traceback.format_list(traceback.extract_tb(tb)))
- out += "\n"
- sys.stderr.write(out)
-
-
-sys.excepthook = pyzo_excepthook
-
-
-## Define some functions
-
-
-# todo: move some stuff out of this module ...
-
-
-def getResourceDirs():
- """getResourceDirs()
- Get the directories to the resources: (pyzoDir, appDataDir, appConfigDir).
- Also makes sure that the appDataDir has a "tools" directory and
- a style file.
- """
-
- # # Get root of the Pyzo code. If frozen its in a subdir of the app dir
- # pyzoDir = paths.application_dir()
- # if paths.is_frozen():
- # pyzoDir = os.path.join(pyzoDir, 'source')
- pyzoDir = os.path.abspath(os.path.dirname(__file__))
- if ".zip" in pyzoDir:
- raise RuntimeError("The Pyzo package cannot be run from a zipfile.")
-
- # Get where the application data is stored (use old behavior on Mac)
- appDataDir, appConfigDir = paths.appdata_dir("pyzo", roaming=True, macAsLinux=True)
-
- # Create tooldir if necessary
- toolDir = os.path.join(appDataDir, "tools")
- os.makedirs(toolDir, exist_ok=True)
-
- return pyzoDir, appDataDir, appConfigDir
-
-
-def resetConfig(preserveState=True):
- """resetConfig()
- Deletes the config file to revert to default and prevent Pyzo from storing
- its config on the next shutdown.
- """
- # Get filenames
- configFileName2 = os.path.join(appConfigDir, "config.ssdf")
- os.remove(configFileName2)
- global _saveConfigFile
- _saveConfigFile = False
- print("Deleted user config file. Restart Pyzo to revert to the default config.")
-
-
-def loadThemes():
- """
- Load default and user themes (if exist)
- """
-
- def loadThemesFromDir(dname, isBuiltin=False):
- if not os.path.isdir(dname):
- return
- for fname in [fname for fname in os.listdir(dname) if fname.endswith(".theme")]:
- try:
- theme = ssdf.load(os.path.join(dname, fname))
- assert (
- theme.name.lower() == fname.lower().split(".")[0]
- ), "Theme name does not match filename"
- theme.data = {
- key.replace("_", "."): val for key, val in theme.data.items()
- }
- theme["builtin"] = isBuiltin
- themes[theme.name.lower()] = theme
- print("Loaded theme %r" % theme.name)
- except Exception as ex:
- print("Warning ! Error while reading %s: %s" % (fname, ex))
-
- loadThemesFromDir(os.path.join(pyzoDir, "resources", "themes"), True)
- loadThemesFromDir(os.path.join(appDataDir, "themes"))
-
-
-def loadConfig(defaultsOnly=False):
- """loadConfig(defaultsOnly=False)
- Load default and site-wide configuration file(s) and that of the user (if it exists).
- Any missing fields in the user config are set to the defaults.
- """
-
- # Function to insert names from one config in another
- def replaceFields(base, new):
- for key in new:
- if key in base and isinstance(base[key], ssdf.Struct):
- replaceFields(base[key], new[key])
- else:
- base[key] = new[key]
-
- # Reset our pyzo.config structure
- ssdf.clear(config)
-
- # Load default and inject in the pyzo.config
- fname = os.path.join(pyzoDir, "resources", "defaultConfig.ssdf")
- defaultConfig = ssdf.load(fname)
- replaceFields(config, defaultConfig)
-
- # Platform specific keybinding: on Mac, Ctrl+Tab (actually Cmd+Tab) is a system shortcut
- if sys.platform == "darwin":
- config.shortcuts2.view__select_previous_file = "Alt+Tab,"
-
- # Load site-wide config if it exists and inject in pyzo.config
- fname = os.path.join(pyzoDir, "resources", "siteConfig.ssdf")
- if os.path.isfile(fname):
- try:
- siteConfig = ssdf.load(fname)
- replaceFields(config, siteConfig)
- except Exception:
- t = "Error while reading config file %r, maybe its corrupt?"
- print(t % fname)
- raise
-
- # Load user config and inject in pyzo.config
- fname = os.path.join(appConfigDir, "config.ssdf")
- if os.path.isfile(fname):
- try:
- userConfig = ssdf.load(fname)
- replaceFields(config, userConfig)
- except Exception:
- t = "Error while reading config file %r, maybe its corrupt?"
- print(t % fname)
- raise
-
-
-def saveConfig():
- """saveConfig()
- Save all configureations to file.
- """
-
- # Let the editorStack save its state
- if editors:
- editors.saveEditorState()
-
- # Let the main window save its state
- if main:
- main.saveWindowState()
-
- # Store config
- if _saveConfigFile:
- ssdf.save(os.path.join(appConfigDir, "config.ssdf"), config)
+if sys.version_info < (3, 6):
+ raise RuntimeError("Pyzo requires Python 3.6+ to run.")
def start():
- """Run Pyzo."""
-
- # Do some imports
- from pyzo.core import pyzoLogging # noqa - to start logging asap
- from pyzo.core.main import MainWindow
-
- # Apply users' preferences w.r.t. date representation etc
- # this is required for e.g. strftime("%c")
- # Just using '' does not seem to work on OSX. Thus
- # this odd loop.
- # locale.setlocale(locale.LC_ALL, "")
- for x in ("", "C", "en_US", "en_US.utf8", "en_US.UTF-8"):
- try:
- locale.setlocale(locale.LC_ALL, x)
- break
- except Exception:
- pass
-
- # Set to be aware of the systems native colors, fonts, etc.
- QtWidgets.QApplication.setDesktopSettingsAware(True)
-
- # Instantiate the application
- QtWidgets.qApp = MyApp(sys.argv) # QtWidgets.QApplication([])
-
- # Choose language, get locale
- appLocale = setLanguage(config.settings.language)
-
- # Create main window, using the selected locale
- MainWindow(None, appLocale)
-
- # Enter the main loop
- QtWidgets.qApp.exec_()
-
-
-## Init
-
-# List of names that are later overriden (in main.py)
-editors = None # The editor stack instance
-shells = None # The shell stack instance
-main = None # The mainwindow
-icon = None # The icon
-parser = None # The source parser
-status = None # The statusbar (or None)
-
-# Get directories of interest
-pyzoDir, appDataDir, appConfigDir = getResourceDirs()
-
-# Whether the config file should be saved
-_saveConfigFile = True
-
-# Create ssdf in module namespace, and fill it
-config = ssdf.new()
-loadConfig()
-
-try:
- # uses the fact that float("") raises ValueError to be NOP when qtscalefactor setting is not set
- os.environ["QT_SCREEN_SCALE_FACTORS"] = str(float(config.settings.qtscalefactor))
-except Exception:
- pass
+ """Start Pyzo."""
+ from ._start import start
-# Create style dict and fill it
-themes = {}
-loadThemes()
-# Init default style name (set in main.restorePyzoState())
-defaultQtStyleName = ""
+ start()
diff --git a/pyzo/__main__.py b/pyzo/__main__.py
index 4fc7b98e..8e19fe73 100755
--- a/pyzo/__main__.py
+++ b/pyzo/__main__.py
@@ -1,63 +1,25 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# Copyright (C) 2016, the Pyzo development team
-#
-# Pyzo is distributed under the terms of the 2-Clause BSD License.
-# The full license can be found in 'license.txt'.
-
-""" Pyzo __main__ module
+"""
+Pyzo __main__ module
-This module takes enables starting Pyzo via either "python3 -m pyzo" or
+This module enables starting Pyzo via either "python3 -m pyzo" or
"python3 path/to/pyzo".
-
-In the first case it simply imports pyzo. In the latter case, that import
-will generally fail, in which case the parent directory is added to sys.path
-and the import is tried again. Then "pyzo.start()" is called.
-
"""
import os
import sys
-class SourceImporter:
- def __init__(self, dir):
- self.module_names = {"pyzo", "yoton"}
- for name in os.listdir(dir):
- self.module_names.add(name)
-
- def find_spec(self, fullname, path, target=None):
- if fullname.split(".")[0] in self.module_names:
- return sys.meta_path[-1].find_spec(fullname, path, target)
- else:
- return None
-
-
-if getattr(sys, "frozen", False):
- # Allow importing from the source dir, and install spec finder to overload
- # PyInstaller's finder when appropriate.
- source_dir = os.path.join(sys._MEIPASS, "source")
- sys.path.insert(0, source_dir)
- sys.meta_path.insert(0, SourceImporter(source_dir))
- # Import
- import pyzo
+# Very probably run as a script, either the package or the __main__
+# directly. Add parent directory to sys.path and try again.
+this_dir = os.path.abspath(os.path.dirname(__file__))
+sys.path.insert(0, os.path.split(this_dir)[0])
-else:
- # Try importing
- try:
- import pyzo
- except ImportError:
- # Very probably run as a script, either the package or the __main__
- # directly. Add parent directory to sys.path and try again.
- thisDir = os.path.abspath(os.path.dirname(__file__))
- sys.path.insert(0, os.path.split(thisDir)[0])
- try:
- import pyzo
- except ImportError:
- raise ImportError("Could not import Pyzo in either way.")
+import pyzo
def main():
+ # Must have a function main here, as the entry-point to this module
pyzo.start()
diff --git a/pyzo/_start.py b/pyzo/_start.py
new file mode 100644
index 00000000..c9f88344
--- /dev/null
+++ b/pyzo/_start.py
@@ -0,0 +1,325 @@
+import os
+import sys
+import ctypes
+import locale
+import traceback
+
+import pyzo
+
+# Import this module that applies some tweaks that need to be applied
+# before import qt. This is a separate module, so that the frozen app
+# can import before checking the qt import.
+from . import pre_qt_import # noqa: F401
+
+# Import yoton as an absolute package
+from pyzo import yotonloader # noqa
+from pyzo.util import paths
+
+# If there already is an instance of Pyzo, and the user is trying an
+# Pyzo command, we should send the command to the other process and quit.
+# We do this here, were we have not yet loaded Qt, so we are very light.
+from pyzo.core import commandline
+
+if commandline.is_our_server_running():
+ print("Started our command server")
+else:
+ # Handle command line args now
+ res = commandline.handle_cmd_args()
+ if res:
+ print(res)
+ sys.exit()
+ else:
+ # No args, proceed with starting up
+ print("Our command server is *not* running")
+
+
+from pyzo.util import zon as ssdf # zon is ssdf-light
+from pyzo.qt import QtCore, QtGui, QtWidgets
+
+# Enable high-res displays
+try:
+ ctypes.windll.shcore.SetProcessDpiAwareness(1)
+ ctypes.windll.shcore.SetProcessDpiAwareness(2)
+except Exception:
+ pass # fail on non-windows
+try:
+ QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
+ QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
+except Exception:
+ pass # fail on older Qt's
+
+# Import language/translation tools
+from pyzo.util._locale import translate, setLanguage # noqa
+
+pyzo.translate = translate
+pyzo.setLanguage = setLanguage
+
+# Set environ to let kernel know some stats about us
+os.environ["PYZO_PREFIX"] = sys.prefix
+_is_pyqt4 = hasattr(QtCore, "PYQT_VERSION_STR")
+os.environ["PYZO_QTLIB"] = "PyQt4" if _is_pyqt4 else "PySide"
+
+
+class MyApp(QtWidgets.QApplication):
+ """So we an open .py files on OSX.
+ OSX is smart enough to call this on the existing process.
+ """
+
+ def event(self, event):
+ if isinstance(event, QtGui.QFileOpenEvent):
+ fname = str(event.file())
+ if fname and fname != "pyzo":
+ sys.argv[1:] = []
+ sys.argv.append(fname)
+ res = commandline.handle_cmd_args()
+ if not commandline.is_our_server_running():
+ print(res)
+ sys.exit()
+ return QtWidgets.QApplication.event(self, event)
+
+
+if not sys.platform.startswith("darwin"):
+ MyApp = QtWidgets.QApplication # noqa
+
+
+## Install excepthook
+# In PyQt5 exceptions in Python will cause an abort
+# http://pyqt.sourceforge.net/Docs/PyQt5/incompatibilities.html
+
+
+def pyzo_excepthook(type, value, tb):
+ out = "Uncaught Python exception: " + str(value) + "\n"
+ out += "".join(traceback.format_list(traceback.extract_tb(tb)))
+ out += "\n"
+ sys.stderr.write(out)
+
+
+sys.excepthook = pyzo_excepthook
+
+
+## Define some functions
+
+
+# todo: move some stuff out of this module ...
+
+
+def getResourceDirs():
+ """getResourceDirs()
+ Get the directories to the resources: (pyzoDir, appDataDir, appConfigDir).
+ Also makes sure that the appDataDir has a "tools" directory and
+ a style file.
+ """
+
+ pyzoDir = os.path.abspath(os.path.dirname(__file__))
+ if ".zip" in pyzoDir:
+ raise RuntimeError("The Pyzo package cannot be run from a zipfile.")
+
+ # Get where the application data is stored (use old behavior on Mac)
+ appDataDir, appConfigDir = paths.appdata_dir("pyzo", roaming=True, macAsLinux=True)
+
+ # Create tooldir if necessary
+ toolDir = os.path.join(appDataDir, "tools")
+ os.makedirs(toolDir, exist_ok=True)
+
+ return pyzoDir, appDataDir, appConfigDir
+
+
+def resetConfig(preserveState=True):
+ """resetConfig()
+ Deletes the config file to revert to default and prevent Pyzo from storing
+ its config on the next shutdown.
+ """
+ # Get filenames
+ configFileName2 = os.path.join(pyzo.appConfigDir, "config.ssdf")
+ os.remove(configFileName2)
+ pyzo._saveConfigFile = False
+ print("Deleted user config file. Restart Pyzo to revert to the default config.")
+
+
+def loadThemes():
+ """
+ Load default and user themes (if exist)
+ """
+
+ def loadThemesFromDir(dname, isBuiltin=False):
+ if not os.path.isdir(dname):
+ return
+ for fname in [fname for fname in os.listdir(dname) if fname.endswith(".theme")]:
+ try:
+ theme = ssdf.load(os.path.join(dname, fname))
+ assert (
+ theme.name.lower() == fname.lower().split(".")[0]
+ ), "Theme name does not match filename"
+ theme.data = {
+ key.replace("_", "."): val for key, val in theme.data.items()
+ }
+ theme["builtin"] = isBuiltin
+ pyzo.themes[theme.name.lower()] = theme
+ print("Loaded theme %r" % theme.name)
+ except Exception as ex:
+ print("Warning ! Error while reading %s: %s" % (fname, ex))
+
+ loadThemesFromDir(os.path.join(pyzo.pyzoDir, "resources", "themes"), True)
+ loadThemesFromDir(os.path.join(pyzo.appDataDir, "themes"))
+
+
+def loadConfig(defaultsOnly=False):
+ """loadConfig(defaultsOnly=False)
+ Load default and site-wide configuration file(s) and that of the user (if it exists).
+ Any missing fields in the user config are set to the defaults.
+ """
+
+ # Function to insert names from one config in another
+ def replaceFields(base, new):
+ for key in new:
+ if key in base and isinstance(base[key], ssdf.Struct):
+ replaceFields(base[key], new[key])
+ else:
+ base[key] = new[key]
+
+ config = pyzo.config
+
+ # Reset our pyzo.config structure
+ ssdf.clear(config)
+
+ # Load default and inject in the pyzo.config
+ fname = os.path.join(pyzo.pyzoDir, "resources", "defaultConfig.ssdf")
+ defaultConfig = ssdf.load(fname)
+ replaceFields(config, defaultConfig)
+
+ # Platform specific keybinding: on Mac, Ctrl+Tab (actually Cmd+Tab) is a system shortcut
+ if sys.platform == "darwin":
+ config.shortcuts2.view__select_previous_file = "Alt+Tab,"
+
+ # Load site-wide config if it exists and inject in pyzo.config
+ fname = os.path.join(pyzo.pyzoDir, "resources", "siteConfig.ssdf")
+ if os.path.isfile(fname):
+ try:
+ siteConfig = ssdf.load(fname)
+ replaceFields(config, siteConfig)
+ except Exception:
+ t = "Error while reading config file %r, maybe its corrupt?"
+ print(t % fname)
+ raise
+
+ # Load user config and inject in pyzo.config
+ fname = os.path.join(pyzo.appConfigDir, "config.ssdf")
+ if os.path.isfile(fname):
+ try:
+ userConfig = ssdf.load(fname)
+ replaceFields(config, userConfig)
+ except Exception:
+ t = "Error while reading config file %r, maybe its corrupt?"
+ print(t % fname)
+ raise
+
+
+def saveConfig():
+ """saveConfig()
+ Save all configureations to file.
+ """
+
+ # Let the editorStack save its state
+ if pyzo.editors:
+ pyzo.editors.saveEditorState()
+
+ # Let the main window save its state
+ if pyzo.main:
+ pyzo.main.saveWindowState()
+
+ # Store config
+ if pyzo._saveConfigFile:
+ ssdf.save(os.path.join(pyzo.appConfigDir, "config.ssdf"), pyzo.config)
+
+
+pyzo.getResourceDirs = getResourceDirs
+pyzo.resetConfig = resetConfig
+pyzo.loadThemes = loadThemes
+pyzo.saveConfig = saveConfig
+
+
+def start():
+ """Run Pyzo."""
+
+ # Do some imports
+ import pyzo
+ from pyzo.core import pyzoLogging # noqa - to start logging asap
+ from pyzo.core.main import MainWindow
+
+ # Apply users' preferences w.r.t. date representation etc
+ # this is required for e.g. strftime("%c")
+ # Just using '' does not seem to work on OSX. Thus
+ # this odd loop.
+ # locale.setlocale(locale.LC_ALL, "")
+ for x in ("", "C", "en_US", "en_US.utf8", "en_US.UTF-8"):
+ try:
+ locale.setlocale(locale.LC_ALL, x)
+ break
+ except Exception:
+ pass
+
+ # # Set to be aware of the systems native colors, fonts, etc.
+ # QtWidgets.QApplication.setDesktopSettingsAware(True)
+
+ # Instantiate the application
+ QtWidgets.qApp = MyApp(sys.argv) # QtWidgets.QApplication([])
+
+ # Choose language, get locale
+ appLocale = setLanguage(pyzo.config.settings.language)
+
+ # Create main window, using the selected locale
+ MainWindow(None, appLocale)
+
+ # In test mode, we close after 5 seconds
+ # We also write "Closed" to the log (if a filename is provided) which we use
+ # in our tests to determine that Pyzo did a successful run.
+ if "--test" in sys.argv:
+ close_signal = lambda: print("Stopped")
+ if os.getenv("PYZO_LOG", ""):
+ close_signal = lambda: open(os.getenv("PYZO_LOG"), "at").write("Stopped")
+ pyzo.test_close_timer = t = QtCore.QTimer()
+ t.setInterval(5000)
+ t.setSingleShot(True)
+ t.timeout.connect(lambda: [close_signal(), pyzo.main.close()])
+ t.start()
+
+ # Enter the main loop
+ if hasattr(QtWidgets.qApp, "exec"):
+ QtWidgets.qApp.exec()
+ else:
+ QtWidgets.qApp.exec_()
+
+
+## Init
+
+# List of names that are later overriden (in main.py)
+pyzo.editors = None # The editor stack instance
+pyzo.shells = None # The shell stack instance
+pyzo.main = None # The mainwindow
+pyzo.icon = None # The icon
+pyzo.parser = None # The source parser
+pyzo.status = None # The statusbar (or None)
+
+# Get directories of interest
+pyzo.pyzoDir, pyzo.appDataDir, pyzo.appConfigDir = getResourceDirs()
+
+# Whether the config file should be saved
+pyzo._saveConfigFile = True
+
+# Create ssdf in module namespace, and fill it
+pyzo.config = ssdf.new()
+loadConfig()
+
+try:
+ # uses the fact that float("") raises ValueError to be NOP when qtscalefactor setting is not set
+ os.environ["QT_SCREEN_SCALE_FACTORS"] = str(
+ float(pyzo.config.settings.qtscalefactor)
+ )
+except Exception:
+ pass
+
+# Create style dict and fill it
+pyzo.themes = {}
+loadThemes()
+# Init default style name (set in main.restorePyzoState())
+pyzo.defaultQtStyleName = ""
diff --git a/pyzo/codeeditor/qt.py b/pyzo/codeeditor/qt.py
index d59119e8..f806cd27 100644
--- a/pyzo/codeeditor/qt.py
+++ b/pyzo/codeeditor/qt.py
@@ -1,2 +1,2 @@
# This is the one place where codeeditor depends on Pyzo itself
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
diff --git a/pyzo/core/about.py b/pyzo/core/about.py
index cd24f0d7..1b8ac259 100644
--- a/pyzo/core/about.py
+++ b/pyzo/core/about.py
@@ -1,8 +1,8 @@
import os
import sys
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
-from pyzo.util import qt
+from pyzo.qt import QtCore, QtGui, QtWidgets
+from pyzo import qt
import pyzo
from pyzo.util import paths
diff --git a/pyzo/core/assistant.py b/pyzo/core/assistant.py
index bd1fad03..b4382c38 100644
--- a/pyzo/core/assistant.py
+++ b/pyzo/core/assistant.py
@@ -15,7 +15,7 @@
"""
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from pyzo import getResourceDirs
import os
@@ -135,7 +135,7 @@ def __init__(self, parent=None, collection_filename=None):
When collection_file is none, it is determined from the
appDataDir.
"""
- from pyzo.util.qt import QtHelp
+ from pyzo.qt import QtHelp
super().__init__(parent)
self.setWindowTitle("Help")
@@ -264,7 +264,7 @@ def onSearchFinish(self, hits):
self._helpBrowser.setSource(QtCore.QUrl(url))
def showHelpForTerm(self, name):
- from pyzo.util.qt import QtHelp
+ from pyzo.qt import QtHelp
# Cache for later use:
self._search_term = name
diff --git a/pyzo/core/baseTextCtrl.py b/pyzo/core/baseTextCtrl.py
index fe5301e2..dea890aa 100644
--- a/pyzo/core/baseTextCtrl.py
+++ b/pyzo/core/baseTextCtrl.py
@@ -17,7 +17,7 @@
from pyzo.core.pyzoLogging import print
import pyzo.codeeditor.parsers.tokens as Tokens
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
qt = QtGui
diff --git a/pyzo/core/commandline.py b/pyzo/core/commandline.py
index c42090cc..f10a5fd0 100644
--- a/pyzo/core/commandline.py
+++ b/pyzo/core/commandline.py
@@ -108,7 +108,7 @@ def handle_cmd_args():
otherwise.
"""
args = sys.argv[1:]
- request = " ".join(args)
+ request = " ".join(arg for arg in args if not arg.startswith("--"))
if "psn_" in request and not os.path.isfile(request):
request = " ".join(args[1:]) # An OSX thing when clicking app icon
request = request.strip()
diff --git a/pyzo/core/compactTabWidget.py b/pyzo/core/compactTabWidget.py
index a18c6c1e..e6f79dd7 100644
--- a/pyzo/core/compactTabWidget.py
+++ b/pyzo/core/compactTabWidget.py
@@ -10,7 +10,7 @@
"""
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
import sys
if sys.version_info[0] < 3:
diff --git a/pyzo/core/editor.py b/pyzo/core/editor.py
index c9235bc3..91a70a99 100644
--- a/pyzo/core/editor.py
+++ b/pyzo/core/editor.py
@@ -15,7 +15,7 @@
import os, sys
import re, codecs
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
qt = QtGui
diff --git a/pyzo/core/editorTabs.py b/pyzo/core/editorTabs.py
index b46a9600..1873562e 100644
--- a/pyzo/core/editorTabs.py
+++ b/pyzo/core/editorTabs.py
@@ -15,7 +15,7 @@
"""
import os, time, gc
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
import pyzo
from pyzo.core.compactTabWidget import CompactTabWidget
diff --git a/pyzo/core/history.py b/pyzo/core/history.py
index 2d44f82d..e054e119 100644
--- a/pyzo/core/history.py
+++ b/pyzo/core/history.py
@@ -2,7 +2,7 @@
import datetime
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
class CommandHistory(QtCore.QObject):
diff --git a/pyzo/core/icons.py b/pyzo/core/icons.py
index ce7f31e0..653032f7 100644
--- a/pyzo/core/icons.py
+++ b/pyzo/core/icons.py
@@ -12,7 +12,7 @@
"""
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
import pyzo
diff --git a/pyzo/core/main.py b/pyzo/core/main.py
index 17b5f263..f5483b1b 100644
--- a/pyzo/core/main.py
+++ b/pyzo/core/main.py
@@ -20,8 +20,8 @@
from pyzo.core.icons import IconArtist
from pyzo.core import commandline
from pyzo.core.statusbar import StatusBar
-from pyzo.util import qt
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo import qt
+from pyzo.qt import QtCore, QtGui, QtWidgets
from pyzo.core.splash import SplashWidget
from pyzo.util import paths
from pyzo.util import zon as ssdf # zon is ssdf-light
@@ -71,8 +71,8 @@ def __init__(self, parent=None, locale=None):
# Init dockwidget settings
self.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtWidgets.QTabWidget.South)
self.setDockOptions(
- QtWidgets.QMainWindow.AllowNestedDocks
- | QtWidgets.QMainWindow.AllowTabbedDocks
+ QtWidgets.QMainWindow.AllowTabbedDocks
+ | QtWidgets.QMainWindow.AllowNestedDocks
# | QtWidgets.QMainWindow.AnimatedDocks
)
@@ -524,7 +524,7 @@ def loadFonts():
fontDir = os.path.join(pyzo.pyzoDir, "resources", "fonts")
# Get database object
- db = QtGui.QFontDatabase()
+ db = QtGui.QFontDatabase # static class
# Set default font
pyzo.codeeditor.Manager.setDefaultFontFamily("DejaVu Sans Mono")
diff --git a/pyzo/core/menu.py b/pyzo/core/menu.py
index 5fb9016f..ccf1fe09 100644
--- a/pyzo/core/menu.py
+++ b/pyzo/core/menu.py
@@ -19,7 +19,7 @@
import ast
import json
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
import pyzo
from pyzo.core.compactTabWidget import CompactTabWidget
diff --git a/pyzo/core/pdfExport.py b/pyzo/core/pdfExport.py
index 28d43804..0da061f8 100644
--- a/pyzo/core/pdfExport.py
+++ b/pyzo/core/pdfExport.py
@@ -1,4 +1,4 @@
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
from pyzo import translate
import pyzo
import os
@@ -15,7 +15,7 @@ class PdfExport(QtWidgets.QDialog):
def __init__(self):
super().__init__()
- from pyzo.util.qt import QtPrintSupport
+ from pyzo.qt import QtPrintSupport
self.printer = QtPrintSupport.QPrinter(
QtPrintSupport.QPrinter.HighResolution,
diff --git a/pyzo/core/shell.py b/pyzo/core/shell.py
index ef358010..46744771 100644
--- a/pyzo/core/shell.py
+++ b/pyzo/core/shell.py
@@ -24,7 +24,7 @@
import pyzo
from pyzo.util import zon as ssdf # zon is ssdf-light
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
Qt = QtCore.Qt
diff --git a/pyzo/core/shellInfoDialog.py b/pyzo/core/shellInfoDialog.py
index 56c44a05..dc1ac862 100644
--- a/pyzo/core/shellInfoDialog.py
+++ b/pyzo/core/shellInfoDialog.py
@@ -12,7 +12,7 @@
"""
import os, sys
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
import pyzo
from pyzo.core.pyzoLogging import print
diff --git a/pyzo/core/shellStack.py b/pyzo/core/shellStack.py
index e0a123ce..0dcfbfef 100644
--- a/pyzo/core/shellStack.py
+++ b/pyzo/core/shellStack.py
@@ -14,7 +14,7 @@
import time
import webbrowser
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
import pyzo
from pyzo import translate
diff --git a/pyzo/core/splash.py b/pyzo/core/splash.py
index e6dcfe8d..9e82aaed 100644
--- a/pyzo/core/splash.py
+++ b/pyzo/core/splash.py
@@ -13,7 +13,7 @@
import os
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from pyzo import translate
STYLESHEET = """
diff --git a/pyzo/core/statusbar.py b/pyzo/core/statusbar.py
index 37d34617..5b05822c 100644
--- a/pyzo/core/statusbar.py
+++ b/pyzo/core/statusbar.py
@@ -3,7 +3,7 @@
Functionality for status bar in pyzo.
"""
-from pyzo.util.qt import QtWidgets
+from pyzo.qt import QtWidgets
class StatusBar(QtWidgets.QStatusBar):
diff --git a/pyzo/core/themeEdit.py b/pyzo/core/themeEdit.py
index b5cdb188..2624a7d9 100644
--- a/pyzo/core/themeEdit.py
+++ b/pyzo/core/themeEdit.py
@@ -1,6 +1,6 @@
import os
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
import pyzo
from pyzo.util import zon as ssdf
diff --git a/pyzo/pre_qt_import.py b/pyzo/pre_qt_import.py
new file mode 100644
index 00000000..a0a42461
--- /dev/null
+++ b/pyzo/pre_qt_import.py
@@ -0,0 +1,10 @@
+import os
+
+
+# Automatically scale along on HDPI displays. See issue #531 and e.g.
+# https://wiki.archlinux.org/index.php/HiDPI#Qt_5
+if "QT_AUTO_SCREEN_SCALE_FACTOR" not in os.environ:
+ os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
+
+# Fix Qt now showing a window on MacOS Big Sur
+os.environ["QT_MAC_WANTS_LAYER"] = "1"
diff --git a/pyzo/util/qt/QtCore.py b/pyzo/qt/QtCore.py
similarity index 100%
rename from pyzo/util/qt/QtCore.py
rename to pyzo/qt/QtCore.py
diff --git a/pyzo/util/qt/QtGui.py b/pyzo/qt/QtGui.py
similarity index 100%
rename from pyzo/util/qt/QtGui.py
rename to pyzo/qt/QtGui.py
diff --git a/pyzo/util/qt/QtHelp.py b/pyzo/qt/QtHelp.py
similarity index 100%
rename from pyzo/util/qt/QtHelp.py
rename to pyzo/qt/QtHelp.py
diff --git a/pyzo/util/qt/QtPrintSupport.py b/pyzo/qt/QtPrintSupport.py
similarity index 100%
rename from pyzo/util/qt/QtPrintSupport.py
rename to pyzo/qt/QtPrintSupport.py
diff --git a/pyzo/util/qt/QtWidgets.py b/pyzo/qt/QtWidgets.py
similarity index 95%
rename from pyzo/util/qt/QtWidgets.py
rename to pyzo/qt/QtWidgets.py
index 4ae634fd..6e7c5546 100644
--- a/pyzo/util/qt/QtWidgets.py
+++ b/pyzo/qt/QtWidgets.py
@@ -14,7 +14,7 @@
if PYQT6:
from PyQt6 import QtWidgets
from PyQt6.QtWidgets import *
- from PyQt6.QtGui import QAction, QActionGroup, QShortcut
+ from PyQt6.QtGui import QAction, QActionGroup, QShortcut, QFileSystemModel
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
# Map missing/renamed methods
diff --git a/pyzo/util/qt/__init__.py b/pyzo/qt/__init__.py
similarity index 100%
rename from pyzo/util/qt/__init__.py
rename to pyzo/qt/__init__.py
diff --git a/pyzo/util/qt/_patch/__init__.py b/pyzo/qt/_patch/__init__.py
similarity index 100%
rename from pyzo/util/qt/_patch/__init__.py
rename to pyzo/qt/_patch/__init__.py
diff --git a/pyzo/util/qt/_patch/qheaderview.py b/pyzo/qt/_patch/qheaderview.py
similarity index 100%
rename from pyzo/util/qt/_patch/qheaderview.py
rename to pyzo/qt/_patch/qheaderview.py
diff --git a/pyzo/util/qt/compat.py b/pyzo/qt/compat.py
similarity index 100%
rename from pyzo/util/qt/compat.py
rename to pyzo/qt/compat.py
diff --git a/pyzo/util/qt/enums_compat.py b/pyzo/qt/enums_compat.py
similarity index 100%
rename from pyzo/util/qt/enums_compat.py
rename to pyzo/qt/enums_compat.py
diff --git a/pyzo/util/qt/sip.py b/pyzo/qt/sip.py
similarity index 100%
rename from pyzo/util/qt/sip.py
rename to pyzo/qt/sip.py
diff --git a/pyzo/util/qt/uic.py b/pyzo/qt/uic.py
similarity index 100%
rename from pyzo/util/qt/uic.py
rename to pyzo/qt/uic.py
diff --git a/pyzo/tools/__init__.py b/pyzo/tools/__init__.py
index 344cb1d8..bf02f17b 100644
--- a/pyzo/tools/__init__.py
+++ b/pyzo/tools/__init__.py
@@ -36,7 +36,7 @@
import os, sys, imp
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from pyzo.util import zon as ssdf
from pyzo import translate # noqa (we have an eval down here)
diff --git a/pyzo/tools/pyzoFileBrowser/__init__.py b/pyzo/tools/pyzoFileBrowser/__init__.py
index b5d75671..0967a981 100644
--- a/pyzo/tools/pyzoFileBrowser/__init__.py
+++ b/pyzo/tools/pyzoFileBrowser/__init__.py
@@ -44,7 +44,7 @@
import pyzo
from pyzo.util import zon as ssdf
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from .browser import Browser
from .utils import cleanpath, isdir
diff --git a/pyzo/tools/pyzoHistoryViewer.py b/pyzo/tools/pyzoHistoryViewer.py
index b8a709c5..f51e622b 100644
--- a/pyzo/tools/pyzoHistoryViewer.py
+++ b/pyzo/tools/pyzoHistoryViewer.py
@@ -13,7 +13,7 @@
"""
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from pyzo import translate
from pyzo.core.menu import Menu
diff --git a/pyzo/tools/pyzoInteractiveHelp.py b/pyzo/tools/pyzoInteractiveHelp.py
index a63c8b1b..e263f7a7 100644
--- a/pyzo/tools/pyzoInteractiveHelp.py
+++ b/pyzo/tools/pyzoInteractiveHelp.py
@@ -8,7 +8,7 @@
import sys, re
from functools import partial
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
import pyzo
tool_name = pyzo.translate("pyzoInteractiveHelp", "Interactive help")
diff --git a/pyzo/tools/pyzoLogger.py b/pyzo/tools/pyzoLogger.py
index 804a37a9..6f383a22 100644
--- a/pyzo/tools/pyzoLogger.py
+++ b/pyzo/tools/pyzoLogger.py
@@ -7,7 +7,7 @@
import sys, os, code
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets # noqa
+from pyzo.qt import QtCore, QtGui, QtWidgets # noqa
from pyzo.core.shell import BaseShell
from pyzo.core.pyzoLogging import splitConsole
diff --git a/pyzo/tools/pyzoSourceStructure.py b/pyzo/tools/pyzoSourceStructure.py
index 33f35cbd..5824e9c5 100644
--- a/pyzo/tools/pyzoSourceStructure.py
+++ b/pyzo/tools/pyzoSourceStructure.py
@@ -5,7 +5,7 @@
# The full license can be found in 'license.txt'.
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
from pyzo import translate
tool_name = translate("pyzoSourceStructure", "Source structure")
diff --git a/pyzo/tools/pyzoWebBrowser.py b/pyzo/tools/pyzoWebBrowser.py
index f5aeaaea..28c565b3 100644
--- a/pyzo/tools/pyzoWebBrowser.py
+++ b/pyzo/tools/pyzoWebBrowser.py
@@ -7,11 +7,11 @@
import urllib.request, urllib.parse
-from pyzo.util.qt import QtCore, QtWidgets
+from pyzo.qt import QtCore, QtWidgets
imported_qtwebkit = True
try:
- from pyzo.util.qt import QtWebKit
+ from pyzo.qt import QtWebKit
except ImportError:
imported_qtwebkit = False
diff --git a/pyzo/tools/pyzoWorkspace.py b/pyzo/tools/pyzoWorkspace.py
index afa0b3f4..70430b98 100644
--- a/pyzo/tools/pyzoWorkspace.py
+++ b/pyzo/tools/pyzoWorkspace.py
@@ -6,7 +6,7 @@
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
tool_name = pyzo.translate("pyzoWorkspace", "Workspace")
tool_summary = pyzo.translate(
diff --git a/pyzo/util/_locale.py b/pyzo/util/_locale.py
index 3da9ee0e..66b4219d 100644
--- a/pyzo/util/_locale.py
+++ b/pyzo/util/_locale.py
@@ -11,7 +11,7 @@
import os, sys, time
import pyzo
-from pyzo.util.qt import QtCore, QtWidgets
+from pyzo.qt import QtCore, QtWidgets
QLocale = QtCore.QLocale
diff --git a/pyzo/util/bootstrapconda.py b/pyzo/util/bootstrapconda.py
index 471ca61e..aefe8fb0 100644
--- a/pyzo/util/bootstrapconda.py
+++ b/pyzo/util/bootstrapconda.py
@@ -14,7 +14,7 @@
import urllib.request
import pyzo
-from pyzo.util.qt import QtCore, QtWidgets
+from pyzo.qt import QtCore, QtWidgets
from pyzo import translate
base_url = "http://repo.continuum.io/miniconda/"
diff --git a/pyzo/util/paths.py b/pyzo/util/paths.py
index c72a64d8..c268ca60 100644
--- a/pyzo/util/paths.py
+++ b/pyzo/util/paths.py
@@ -20,7 +20,7 @@
# * See docstring: that's why the functions tend to not re-use each-other
import sys
-from pyzo.util.qt import QtCore
+from pyzo.qt import QtCore
ISWIN = sys.platform.startswith("win")
ISMAC = sys.platform.startswith("darwin")
diff --git a/pyzo/util/pyzowizard.py b/pyzo/util/pyzowizard.py
index 23b51d2f..f65eb05e 100644
--- a/pyzo/util/pyzowizard.py
+++ b/pyzo/util/pyzowizard.py
@@ -14,7 +14,7 @@
import re
import pyzo
-from pyzo.util.qt import QtCore, QtGui, QtWidgets
+from pyzo.qt import QtCore, QtGui, QtWidgets
from pyzo import translate
from pyzo.util._locale import LANGUAGES, LANGUAGE_SYNONYMS, setLanguage
diff --git a/setup.cfg b/setup.cfg
index 8667450d..330bedc3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -5,8 +5,8 @@
max_line_length = 88
-exclude: test_*.py,docs/*,build/*,dist/*,frozen/*,_feedstock/*,pyzo/util/qt/,
- pyzo/util/paths.py,pyzo/resources/tutorial.py
+exclude: test_*.py,docs/*,build/*,dist/*,frozen/*,freeze/dist*,_feedstock/*,pyzo/util/qt/,
+ pyzo/qt/,pyzo/util/paths.py,pyzo/resources/tutorial.py
extend-ignore = F821, E203, E501, E231, E401, E402, E262, E265, E266, E302, E731, W293, W605, D, N, B
diff --git a/tests/test_api.py b/tests/test_api.py
index f9b62625..a875c4b5 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -1,5 +1,54 @@
+import sys
import pyzo
+import subprocess
def test_api():
assert pyzo.__version__
+
+
+qt_libs = ["PySide", "PySide2", "PySide6", "PyQt4", "PyQt5", "PyQt6"]
+
+code1 = """
+import sys
+import pyzo
+print(list(sys.modules.keys()))
+"""
+
+
+def test_import1():
+ x = subprocess.check_output([sys.executable, "-c", code1])
+ modules = eval(x.decode())
+ assert isinstance(modules, list)
+ assert "sys" in modules
+ assert "pyzo" in modules
+
+ assert "pyzo.core" not in modules
+ assert not any(qt_lib in modules for qt_lib in qt_libs)
+
+ assert "pyzo.qt" not in modules
+
+
+code2 = """
+import sys
+import pyzo
+import pyzo.qt
+print(list(sys.modules.keys()))
+"""
+
+
+def test_import2():
+ x = subprocess.check_output([sys.executable, "-c", code2])
+ modules = eval(x.decode())
+ assert isinstance(modules, list)
+ assert "sys" in modules
+ assert "pyzo" in modules
+
+ assert "pyzo.qt" in modules
+ assert any(qt_lib in modules for qt_lib in qt_libs)
+
+ assert "pyzo.core" not in modules
+
+
+test_import1()
+test_import2()