diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..133357a --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +# to run the tests, run make PASS=controllerpassword test + +PREFIX=/usr/local +PYTHON_EXE_MSYS=${PREFIX}/bin/python3.sh +PIP_EXE_MSYS=${PREFIX}/bin/pip3.sh +LOCAL_DOCTEST=${PREFIX}/bin/toxcore_run_doctest3.bash +DOCTEST=${LOCAL_DOCTEST} +MOD=phantompy + +check:: + sh ${PYTHON_EXE_MSYS} -c "import ${MOD}" + +lint:: + sh .pylint.sh + +install:: + PYTHONPATH=${PWD}/src \ + ${PIP_EXE_MSYS} install --target ${PREFIX}/lib/python3.11/site-packages/ --upgrade . + +rsync:: + bash .rsync.sh + +install:: + PYTHONPATH=${PWD}/src \ + ${PIP_EXE_MSYS} --timeout=30 --disable-pip-version-check --proxy http://127.0.0.1:9128 install --only-binary :none: --progress-bar=off --target /usr/local/lib/python3.11/site-packages --upgrade . + +# execute these tests as: make test PASS=password +test:: + +doctest: + +veryclean:: clean + rm -rf build dist __pycache__ .pylint.err .pylint.out + +clean:: + find . -name \*~ -delete diff --git a/Pipfile b/Pipfile deleted file mode 100644 index bfe9dad..0000000 --- a/Pipfile +++ /dev/null @@ -1,16 +0,0 @@ -[[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 = "." diff --git a/pyproject.toml b/pyproject.toml index 19d2bb3..8e8eb62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,32 +1,33 @@ [project] name = "phantompy" -description = "Set the ExcludeNodes or ExcludeExitNodes setting of a running Tor." +description = "A simple replacement for phantomjs using PyQt" authors = [{ name = "emdee", email = "emdee@spm.plastiras.org" } ] -requires-python = ">=3.6" -keywords = ["tor", "python3", "bad exits"] +requires-python = ">=3.8" +dependencies = [ + 'qasync', + # PyQt5 PyQt6 +] +keywords = ["phantomjs", "python3", "qasync"] classifiers = [ "License :: OSI Approved", "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 :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", + "Classifier: Topic :: Software Development :: Libraries :: Python Modules", ] +# dynamic = ["version", "readme", ] # cannot be dynamic ['license'] -scripts = { phantompy = "phantompy.phantompy:iMain" } -dependencies = ['qasync', 'PyQt5'] -[tool.setuptools.dynamic] -version = {attr = "phantompy.__version__"} -readme = {file = ["README.md"]} +[project.scripts] +phantompy = "phantompy.qasync_phantompy:iMain" -[project.license] -file = "LICENSE.md" +#[project.license] +#file = "LICENSE.md" [project.urls] repository = "https://git.plastiras.org/emdee/phantompy" @@ -35,9 +36,12 @@ repository = "https://git.plastiras.org/emdee/phantompy" requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta" -# Either or both of these don't work -#[tool.setuptools] -#packages = ["phantompy"] +[tool.setuptools.dynamic] +version = {attr = "phantompy.__version__"} +readme = {file = ["README.md"]} + +[tool.setuptools] +packages = ["phantompy"] #[tool.setuptools.packages.find] -#include = ["src"] +#where = "src" diff --git a/setup.cfg b/setup.cfg index 8b7281b..f6eb397 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,19 +11,26 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Programming Language :: Python :: Implementation :: CPython Framework :: AsyncIO [options] zip_safe = false -python_requires = ~=3.6 -packages = find: +python_requires = ~=3.8 include_package_data = false install_requires = qasync cryptography rsa stem +package_dir= + =src +packages=find: + +[options.packages.find] +where=src [options.entry_points] console_scripts = @@ -40,7 +47,6 @@ ignore = E114 E128 E225 - E225 E261 E302 E305 @@ -49,10 +55,11 @@ ignore = E502 E541 E701 + E702 E704 E722 E741 F508 F541 W503 - \ No newline at end of file + W601 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..647eb27 --- /dev/null +++ b/setup.py @@ -0,0 +1,44 @@ +# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -* + +import re +from setuptools import setup + +with open("qasync/__init__.py") as f: + version = re.search(r'__version__\s+=\s+"(.*)"', f.read()).group(1) + +long_description = "\n\n".join([ + open("README.md").read(), + ]) + +if __name__ == '__main__': + setup( + name="phantompy", + version=__version__, + description="""A simple replacement for phantomjs using PyQt""", + long_description=long_description, + author="Michael Franzl (originally)", + author_email='', + license="1clause BSD", + packages=['phantompy'], + # url="", + # download_url="https://", + keywords=['JavaScript', 'phantomjs', 'asyncio'], + # maybe less - nothing fancy + python_requires="~=3.6", + # probably works on PyQt6 and PySide2 but untested + # https://github.com/CabbageDevelopment/qasync/ + install_requires=['qasync', + 'PyQt5'], + entry_points={ + 'console_scripts': ['phantompy = phantompy.__main__:iMain', ]}, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Web Developers', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development :: Documentation', + ], + ) diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/phantompy/phantompy.py b/src/phantompy/phantompy.py index e8da790..0c82d18 100644 --- a/src/phantompy/phantompy.py +++ b/src/phantompy/phantompy.py @@ -122,7 +122,9 @@ import importlib import os import sys # noqa -from qasync import QtModuleName +QtModuleName = os.environ.get('QT_API') +if not QtModuleName: + from qasync import QtModuleName from qasync.QtCore import QUrl QPrinter = importlib.import_module(QtModuleName + ".QtPrintSupport.QPrinter", package=QtModuleName) diff --git a/src/phantompy/qasync_readme.py b/src/phantompy/qasync_readme.py new file mode 100644 index 0000000..ef047b2 --- /dev/null +++ b/src/phantompy/qasync_readme.py @@ -0,0 +1,143 @@ +#!/usr/local/bin/python3.sh +# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -* + +import sys +import os +import atexit +import traceback +import functools +import asyncio +import time +import qasync +import threading + +QtModuleName = os.environ.get('QT_API') +if not QtModuleName: + from qasync import QtModuleName +QtWidgets = importlib.import_module(QtModuleName + ".QtWidgets", package=QtModuleName) +# from PyQt5.QtWidgets import (QProgressBar, QWidget, QVBoxLayout) +from qasync import QEventLoop, QThreadExecutor +from qasync import asyncSlot, asyncClose, QApplication + +from phantompy import Render +from lookupdns import LookFor + +global LOG +import logging +import warnings +warnings.filterwarnings('ignore') +LOG = logging.getLogger() + +class MainWindow(QWidget.QWidget): + """Main window.""" + def __init__(self): + super().__init__() + + self.setLayout(QVBoxLayout.QVBoxLayout()) + self.progress = QtWidgets.QProgressBar() + self.progress.setRange(0, 99) + self.layout().addWidget(self.progress) + +async def main(app): + def close_future(future, loop): + loop.call_later(10, future.cancel) + future.cancel() + + loop = asyncio.get_running_loop() + future = asyncio.Future() + app.ldone = [] + + getattr(app, "aboutToQuit").connect( + functools.partial(close_future, future, loop) + ) + + if False: + progress = QtWidgets.QProgressBar() + progress.setRange(0, 99) + progress.show() + else: + mw = MainWindow() + progress = mw.progress + mw.show() +# LOG.info(f"calling first_50 {r}") +# await first_50(progress, r) + LOG.info(f"calling last_50 {r}") + o = QThreadExecutor(max_workers=1) + app.o = o + with o as executor: + await loop.run_in_executor(executor, functools.partial(last_50, progress, sys.argv[1:], app), loop) + LOG.info(f" {dir(o)}") + + LOG.info(f"awaiting {future}") + await future + return True + +async def first_50(progress, r=None): + progress.setValue(5) + LOG.info(f"first_50 {r}") + if r is not None: + # loop = asyncio.get_running_loop() + # LOG.info(f"first_50.r.run {r}") + # loop.call_soon_threadsafe(r.run, r.url, r.outfile, r.jsfile) + # r.run( r.url, r.outfile, r.jsfile) + for i in range(50): + # LOG.info(f"first_50 {r.progress} {i}") + # if r.progress >= 100: break + # progress.setValue(max(r.progress,i)) + progress.setValue(i) + await asyncio.sleep(.1) + return + for i in range(50): + LOG.info(f"first_50 {r} {i}") + loop.call_soon_threadsafe(progress.setValue, i) + time.sleep(1) + +def last_50(progress, largs, app, loop): + url = largs[0] + outfile = largs[1] + jsfile = largs[2] if len(largs) > 2 else None + r = Render(app, do_print=False, do_save=True) + uri = url.strip() + loop.call_soon_threadsafe(r.run, uri, outfile, jsfile) + time.sleep(1) + for i in range(50, 100): + j = len(app.ldone) # r.progress + if j == 100: + LOG.info(f"last_50 None {i} {j}") + else: + LOG.debug(f"last_50 None {i} {j}") + loop.call_soon_threadsafe(progress.setValue, i) + time.sleep(1) + +if __name__ == '__main__': + url = 'https://dns.google/resolve?name=6D6EC2A2E2ED8BFF2D4834F8D669D82FC2A9FA8D.for-privacy.net&type=TXT&cd=true&do=true' + outfile = '/tmp/test1.pdf' + jsfile = '/tmp/test1.js' + from exclude_badExits import vsetup_logging + vsetup_logging(10) + app = QApplication([]) + #? + loop = qasync.QEventLoop(app) +#NOT loop = asyncio.get_event_loop() + asyncio._set_running_loop(loop) + asyncio.events._set_running_loop(loop) + r = Render(app, do_print=False, do_save=True) + #loop.call_soon_threadsafe(r.run, url, outfile, jsfile) + r.run(url, outfile, jsfile) + app.rs = [r] + for i in range(20): + for elt in app.rs: + print (elt.percent) + time.sleep(2) + try: + qasync.run(main(app)) + except asyncio.exceptions.CancelledError: + sys.exit(0) + except RuntimeError as e: + LOG.debug('Fixme') + sys.exit(0) + except KeyboardInterrupt: + sys.exit(0) + else: + val = 0 + sys.exit(val) diff --git a/src/phantompy/quamash_readme.py b/src/phantompy/quamash_readme.py new file mode 100644 index 0000000..b989621 --- /dev/null +++ b/src/phantompy/quamash_readme.py @@ -0,0 +1,49 @@ +#!/usr/local/bin/python3.sh +# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -* + +import sys +import os +import traceback + +from phantompy import Render + +global LOG +import logging +import warnings +warnings.filterwarnings('ignore') +LOG = logging.getLogger() + +import sys +import asyncio +import time + +from PyQt5.QtWidgets import QApplication, QProgressBar +from quamash import QEventLoop, QThreadExecutor + +app = QApplication(sys.argv) +loop = QEventLoop(app) +asyncio.set_event_loop(loop) # NEW must set the event loop +asyncio.events._set_running_loop(loop) + +progress = QProgressBar() +progress.setRange(0, 99) +progress.show() + +async def master(): + await first_50() + with QThreadExecutor(1) as executor: + await loop.run_in_executor(exec, last_50) + # TODO announce completion? + +async def first_50(): + for i in range(50): + progress.setValue(i) + await asyncio.sleep(.1) + +def last_50(): + for i in range(50,100): + loop.call_soon_threadsafe(progress.setValue, i) + time.sleep(.1) + +with loop: ## context manager calls .close() when loop completes, and releases all resources + loop.run_until_complete(master())