Compare commits

...

3 commits

Author SHA1 Message Date
emdee
bdedba8d11 add Pipfile 2022-11-17 08:56:04 +00:00
emdee
146cd71281 add .github/workflows/test.yml 2022-11-17 08:51:30 +00:00
emdee
81a5e66b60 added setup.cfg 2022-11-17 08:07:23 +00:00
13 changed files with 215 additions and 36 deletions

47
.github/workflows/test.yml vendored Normal file
View 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
View 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 = "."

View file

22
appveyor.yml Normal file
View 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
View file

@ -0,0 +1,3 @@
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 2; coding: utf-8 -*-
__version__ = "0.1.0"

View file

@ -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:])

View file

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

View file

@ -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:])

View file

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

View file

@ -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
View 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([])