Compare commits
3 commits
97db0946da
...
bdedba8d11
Author | SHA1 | Date | |
---|---|---|---|
|
bdedba8d11 | ||
|
146cd71281 | ||
|
81a5e66b60 |
13 changed files with 215 additions and 36 deletions
47
.github/workflows/test.yml
vendored
Normal file
47
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
name: test
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
name: Python-${{ matrix.python }} ${{ matrix.qt.qt_api }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
qt:
|
||||||
|
- package: PyQt5
|
||||||
|
qt_api: "pyqt5"
|
||||||
|
- package: PyQt6
|
||||||
|
qt_api: "pyqt6"
|
||||||
|
- package: PySide2
|
||||||
|
qt_api: "pyside2"
|
||||||
|
- package: PySide6
|
||||||
|
qt_api: "pyside6"
|
||||||
|
python: [3.6, 3.7, 3.8, 3.9]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python }}
|
||||||
|
architecture: x64
|
||||||
|
- name: Install pipenv
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pipenv wheel
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pipenv install --dev
|
||||||
|
pipenv run pip install ${{ matrix.qt.package }} pytest
|
||||||
|
- name: Install Libxcb dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev
|
||||||
|
- name: Run headless test
|
||||||
|
uses: GabrielBB/xvfb-action@v1
|
||||||
|
env:
|
||||||
|
QT_API: ${{ matrix.qt.qt_api }}
|
||||||
|
with:
|
||||||
|
run: pipenv run py.test --forked -v
|
16
Pipfile
Normal file
16
Pipfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[[source]]
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
name = "pypi"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
atomicwrites = "*"
|
||||||
|
pytest = "*"
|
||||||
|
pytest-forked = "*"
|
||||||
|
pytest-raises = "*"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
|
||||||
|
[dev-packages.phantomjs]
|
||||||
|
editable = true
|
||||||
|
path = "."
|
22
appveyor.yml
Normal file
22
appveyor.yml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
environment:
|
||||||
|
matrix:
|
||||||
|
- PYTHON: "C:\\Python36"
|
||||||
|
- PYTHON: "C:\\Python37"
|
||||||
|
- PYTHON: "C:\\Python38"
|
||||||
|
- PYTHON: "C:\\Python39"
|
||||||
|
|
||||||
|
init:
|
||||||
|
- set PATH=%PYTHON%;%PYTHON%\Scripts;%PATH%
|
||||||
|
|
||||||
|
install:
|
||||||
|
- pip install pipenv
|
||||||
|
- pipenv install --dev
|
||||||
|
- pipenv run pip install PyQt5 PySide2
|
||||||
|
# FIX: colorama not installed by pipenv
|
||||||
|
- pipenv run pip install colorama
|
||||||
|
|
||||||
|
build: off
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- set QT_API=PyQt5&& pipenv run py.test -v
|
||||||
|
- set QT_API=PySide2&& pipenv run py.test -v
|
3
phantompy/__init__.py
Normal file
3
phantompy/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 2; coding: utf-8 -*-
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
|
@ -1,10 +1,13 @@
|
||||||
#!/usr/local/bin/python3.sh
|
#!/usr/local/bin/python3.sh
|
||||||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
||||||
|
|
||||||
from qasync_phantompy import iMain
|
from __future__ import absolute_import
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .qasync_phantompy import iMain
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from support_phantompy import vsetup_logging
|
from .support_phantompy import vsetup_logging
|
||||||
d = int(os.environ.get('DEBUG', 0))
|
d = int(os.environ.get('DEBUG', 0))
|
||||||
if d > 0:
|
if d > 0:
|
||||||
vsetup_logging(10, stream=sys.stderr)
|
vsetup_logging(10, stream=sys.stderr)
|
||||||
|
@ -14,4 +17,4 @@ try:
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
iMain(sys.argv[1:], bgui=False)
|
iMain(sys.argv[1:])
|
|
@ -118,21 +118,20 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import importlib
|
||||||
import os
|
import os
|
||||||
import traceback
|
import sys # noqa
|
||||||
import atexit
|
|
||||||
import time
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl
|
from qasync import QtModuleName
|
||||||
from PyQt5.QtCore import QTimer
|
from qasync.QtCore import QUrl
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
from PyQt5.QtPrintSupport import QPrinter
|
QPrinter = importlib.import_module(QtModuleName + ".QtPrintSupport.QPrinter", package=QtModuleName)
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage
|
QWebEnginePage = importlib.import_module(QtModuleName + ".QtWebEngineWidgets.QWebEnginePage", package=QtModuleName)
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
warnings.filterwarnings('ignore')
|
warnings.filterwarnings('ignore')
|
||||||
LOG = logging.getLogger()
|
LOG = logging.getLogger()
|
||||||
|
|
||||||
|
@ -181,19 +180,19 @@ class Render(QWebEnginePage):
|
||||||
self.htmlfile = htmlfile
|
self.htmlfile = htmlfile
|
||||||
self.pdffile = pdffile
|
self.pdffile = pdffile
|
||||||
self.outfile = pdffile or htmlfile
|
self.outfile = pdffile or htmlfile
|
||||||
LOG.debug(f"phantom.py: URL={url} OUTFILE={outfile} JSFILE={jsfile}")
|
LOG.debug(f"phantom.py: URL={url} htmlfile={htmlfile} pdffile={pdffile} JSFILE={jsfile}")
|
||||||
qurl = QUrl.fromUserInput(url)
|
qurl = QUrl.fromUserInput(url)
|
||||||
|
|
||||||
# The PDF generation only happens when the special string __PHANTOM_PY_DONE__
|
# The PDF generation only happens when the special string __PHANTOM_PY_DONE__
|
||||||
# is sent to console.log(). The following JS string will be executed by
|
# is sent to console.log(). The following JS string will be executed by
|
||||||
# default, when no external JavaScript file is specified.
|
# default, when no external JavaScript file is specified.
|
||||||
self.js_contents = "setTimeout(function() { console.log('__PHANTOM_PY_DONE__') }, 5000);";
|
self.js_contents = "setTimeout(function() { console.log('__PHANTOM_PY_DONE__') }, 5000);"
|
||||||
|
|
||||||
if jsfile:
|
if jsfile:
|
||||||
try:
|
try:
|
||||||
with open(self.jsfile, 'rt') as f:
|
with open(self.jsfile, 'rt') as f:
|
||||||
self.js_contents = f.read()
|
self.js_contents = f.read()
|
||||||
except Exception as e:
|
except Exception as e: # noqa
|
||||||
LOG.exception(f"error reading jsfile {self.jsfile}")
|
LOG.exception(f"error reading jsfile {self.jsfile}")
|
||||||
|
|
||||||
self.loadFinished.connect(self._loadFinished)
|
self.loadFinished.connect(self._loadFinished)
|
||||||
|
@ -239,7 +238,7 @@ class Render(QWebEnginePage):
|
||||||
"""print(self, QPrinter, Callable[[bool], None])"""
|
"""print(self, QPrinter, Callable[[bool], None])"""
|
||||||
if type(args[0]) is str:
|
if type(args[0]) is str:
|
||||||
self._save(args[0])
|
self._save(args[0])
|
||||||
self._onConsoleMessage(0, "__PHANTOM_PY_SAVED__", 0 , '')
|
self._onConsoleMessage(0, "__PHANTOM_PY_SAVED__", 0, '')
|
||||||
|
|
||||||
def _save(self, html):
|
def _save(self, html):
|
||||||
sfile = self.htmlfile
|
sfile = self.htmlfile
|
||||||
|
@ -254,7 +253,7 @@ class Render(QWebEnginePage):
|
||||||
i = 1
|
i = 1
|
||||||
else:
|
else:
|
||||||
i = 0
|
i = 0
|
||||||
self._onConsoleMessage(i, "__PHANTOM_PY_PRINTED__", 0 , '')
|
self._onConsoleMessage(i, "__PHANTOM_PY_PRINTED__", 0, '')
|
||||||
|
|
||||||
def _print(self):
|
def _print(self):
|
||||||
sfile = self.pdffile
|
sfile = self.pdffile
|
||||||
|
@ -262,7 +261,7 @@ class Render(QWebEnginePage):
|
||||||
printer.setPageMargins(10, 10, 10, 10, QPrinter.Millimeter)
|
printer.setPageMargins(10, 10, 10, 10, QPrinter.Millimeter)
|
||||||
printer.setPaperSize(QPrinter.A4)
|
printer.setPaperSize(QPrinter.A4)
|
||||||
printer.setCreator("phantom.py by Michael Karl Franzl")
|
printer.setCreator("phantom.py by Michael Karl Franzl")
|
||||||
printer.setOutputFormat(QPrinter.PdfFormat);
|
printer.setOutputFormat(QPrinter.PdfFormat)
|
||||||
printer.setOutputFileName(sfile)
|
printer.setOutputFileName(sfile)
|
||||||
self.print(printer, self._printer_callback)
|
self.print(printer, self._printer_callback)
|
||||||
LOG.debug("phantom.py: Printed")
|
LOG.debug("phantom.py: Printed")
|
||||||
|
@ -272,4 +271,3 @@ class Render(QWebEnginePage):
|
||||||
LOG.debug(f"phantom.py: Exiting with val {val}")
|
LOG.debug(f"phantom.py: Exiting with val {val}")
|
||||||
# threadsafe?
|
# threadsafe?
|
||||||
self._app.ldone.append(self.uri)
|
self._app.ldone.append(self.uri)
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
#!/usr/local/bin/python3.sh
|
#!/usr/local/bin/python3.sh
|
||||||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
import os
|
||||||
import random
|
import sys
|
||||||
|
|
||||||
# let qasync figure out what Qt we are using - we dont care
|
# let qasync figure out what Qt we are using - we dont care
|
||||||
from qasync import QApplication, QtWidgets, QEventLoop
|
from qasync import QApplication, QEventLoop, QtWidgets
|
||||||
|
|
||||||
from phantompy import Render
|
from phantompy import Render
|
||||||
# if you want an example of looking for things in downloaded HTML:
|
# if you want an example of looking for things in downloaded HTML:
|
||||||
# from lookupdns import LookFor as Render
|
# from lookupdns import LookFor as Render
|
||||||
from support_phantompy import vsetup_logging, omain_argparser
|
from support_phantompy import omain_argparser, vsetup_logging
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
warnings.filterwarnings('ignore')
|
warnings.filterwarnings('ignore')
|
||||||
LOG = logging.getLogger()
|
LOG = logging.getLogger()
|
||||||
|
|
||||||
|
try:
|
||||||
|
import shtab
|
||||||
|
except:
|
||||||
|
shtab = None
|
||||||
|
|
||||||
class Widget(QtWidgets.QWidget):
|
class Widget(QtWidgets.QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QWidget.__init__(self)
|
QtWidgets.QWidget.__init__(self)
|
||||||
|
@ -38,13 +42,17 @@ class Widget(QtWidgets.QWidget):
|
||||||
self.progress.setValue(int(text))
|
self.progress.setValue(int(text))
|
||||||
|
|
||||||
class ContextManager:
|
class ContextManager:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._seconds = 0
|
self._seconds = 0
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
LOG.debug("ContextManager enter")
|
LOG.debug("ContextManager enter")
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, *args):
|
async def __aexit__(self, *args):
|
||||||
LOG.debug("ContextManager exit")
|
LOG.debug("ContextManager exit")
|
||||||
|
|
||||||
async def tick(self):
|
async def tick(self):
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
self._seconds += 1
|
self._seconds += 1
|
||||||
|
@ -65,13 +73,15 @@ async def main(widget, app, ilen):
|
||||||
# raise asyncio.CancelledError
|
# raise asyncio.CancelledError
|
||||||
return
|
return
|
||||||
LOG.debug(f"{app.ldone} {seconds}")
|
LOG.debug(f"{app.ldone} {seconds}")
|
||||||
except asyncio.CancelledError as ex:
|
except asyncio.CancelledError as ex: # noqa
|
||||||
LOG.debug("Task cancelled")
|
LOG.debug("Task cancelled")
|
||||||
|
|
||||||
def iMain(largs):
|
def iMain(largs):
|
||||||
parser = omain_argparser()
|
parser = omain_argparser()
|
||||||
|
if shtab:
|
||||||
|
shtab.add_argument_to(parser, ["-s", "--print-completion"]) # magic!
|
||||||
oargs = parser.parse_args(largs)
|
oargs = parser.parse_args(largs)
|
||||||
bgui=oargs.show_gui
|
bgui = oargs.show_gui
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d = int(os.environ.get('DEBUG', 0))
|
d = int(os.environ.get('DEBUG', 0))
|
||||||
|
@ -115,6 +125,4 @@ def iMain(largs):
|
||||||
loop.run_until_complete(asyncio.gather(*tasks))
|
loop.run_until_complete(asyncio.gather(*tasks))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
iMain(sys.argv[1:])
|
iMain(sys.argv[1:])
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
#!/usr/local/bin/python3.sh
|
#!/usr/local/bin/python3.sh
|
||||||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||||
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||||
# https://pypi.org/project/coloredlogs/
|
# https://pypi.org/project/coloredlogs/
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
except ImportError as e:
|
except ImportError:
|
||||||
coloredlogs = False
|
coloredlogs = False
|
||||||
|
|
||||||
global LOG
|
global LOG
|
||||||
import logging
|
import logging
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
warnings.filterwarnings('ignore')
|
warnings.filterwarnings('ignore')
|
||||||
LOG = logging.getLogger()
|
LOG = logging.getLogger()
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ def vsetup_logging(log_level, logfile='', stream=sys.stdout):
|
||||||
add = True
|
add = True
|
||||||
|
|
||||||
# stem fucks up logging
|
# stem fucks up logging
|
||||||
from stem.util import log
|
# from stem.util import log
|
||||||
logging.getLogger('stem').setLevel(30)
|
logging.getLogger('stem').setLevel(30)
|
||||||
|
|
||||||
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
|
57
setup.cfg
Normal file
57
setup.cfg
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
[metadata]
|
||||||
|
classifiers =
|
||||||
|
License :: OSI Approved
|
||||||
|
License :: OSI Approved :: BSD 1-clause
|
||||||
|
Intended Audience :: Web Developers
|
||||||
|
Operating System :: Microsoft :: Windows
|
||||||
|
Operating System :: POSIX :: BSD :: FreeBSD
|
||||||
|
Operating System :: POSIX :: Linux
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Framework :: AsyncIO
|
||||||
|
|
||||||
|
[options]
|
||||||
|
zip_safe = false
|
||||||
|
python_requires = ~=3.6
|
||||||
|
packages = find:
|
||||||
|
include_package_data = false
|
||||||
|
install_requires =
|
||||||
|
qasync
|
||||||
|
attrs
|
||||||
|
typing-extensions ; python_version < '3.8'
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
phantompy = phantompy.__main__:iMain
|
||||||
|
|
||||||
|
[easy_install]
|
||||||
|
zip_ok = false
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
jobs = 1
|
||||||
|
max-line-length = 88
|
||||||
|
ignore =
|
||||||
|
E111
|
||||||
|
E114
|
||||||
|
E128
|
||||||
|
E225
|
||||||
|
E225
|
||||||
|
E261
|
||||||
|
E302
|
||||||
|
E305
|
||||||
|
E402
|
||||||
|
E501
|
||||||
|
E502
|
||||||
|
E541
|
||||||
|
E701
|
||||||
|
E704
|
||||||
|
E722
|
||||||
|
E741
|
||||||
|
F508
|
||||||
|
F541
|
||||||
|
W503
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -1,8 +1,10 @@
|
||||||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*
|
||||||
|
|
||||||
|
import re
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
with open("qasync/__init__.py") as f:
|
||||||
|
version = re.search(r'__version__\s+=\s+"(.*)"', f.read()).group(1)
|
||||||
|
|
||||||
long_description = "\n\n".join([
|
long_description = "\n\n".join([
|
||||||
open("README.md").read(),
|
open("README.md").read(),
|
||||||
|
@ -20,9 +22,9 @@ if __name__ == '__main__':
|
||||||
packages=['phantompy'],
|
packages=['phantompy'],
|
||||||
# url="",
|
# url="",
|
||||||
# download_url="https://",
|
# download_url="https://",
|
||||||
keywords=['JavaScript', 'phantomjs'],
|
keywords=['JavaScript', 'phantomjs', 'asyncio'],
|
||||||
# maybe less - nothing fancy
|
# maybe less - nothing fancy
|
||||||
python_requires=">=3.6",
|
python_requires="~=3.6",
|
||||||
# probably works on PyQt6 and PySide2 but untested
|
# probably works on PyQt6 and PySide2 but untested
|
||||||
# https://github.com/CabbageDevelopment/qasync/
|
# https://github.com/CabbageDevelopment/qasync/
|
||||||
install_requires=['qasync', 'PyQt5'],
|
install_requires=['qasync', 'PyQt5'],
|
||||||
|
|
22
tests/conftest.py
Normal file
22
tests/conftest.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
# (c) 2018 Gerard Marull-Paretas <gerard@teslabs.com>
|
||||||
|
# (c) 2014 Mark Harviston <mark.harviston@gmail.com>
|
||||||
|
# (c) 2014 Arve Knudsen <arve.knudsen@gmail.com>
|
||||||
|
# BSD License
|
||||||
|
|
||||||
|
# phantompy test - just test qasync for now
|
||||||
|
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from pytest import fixture
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(name)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
@fixture(scope="session")
|
||||||
|
def application():
|
||||||
|
from phantompy.qasync_phantompy import QApplication
|
||||||
|
|
||||||
|
return QApplication([])
|
Loading…
Reference in a new issue