Import python-click-threading_0.4.0.orig.tar.gz
This commit is contained in:
commit
577f318089
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.egg-ignore
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build/
|
||||||
|
docs/_build
|
||||||
|
click.egg-info
|
||||||
|
.tox
|
||||||
|
.cache
|
10
.travis.yml
Normal file
10
.travis.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
language: python
|
||||||
|
|
||||||
|
python:
|
||||||
|
- 2.7
|
||||||
|
- pypy
|
||||||
|
- 3.3
|
||||||
|
- 3.4
|
||||||
|
|
||||||
|
install: pip install tox
|
||||||
|
script: tox
|
19
LICENSE
Normal file
19
LICENSE
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2014-2015 Markus Unterwaditzer & contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER 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.
|
1
MANIFEST.in
Normal file
1
MANIFEST.in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include LICENSE
|
15
README.rst
Normal file
15
README.rst
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
click-threading
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/click-contrib/click-threading.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/click-contrib/click-threading
|
||||||
|
|
||||||
|
|
||||||
|
Utilities for multithreading in `click <http://click.pocoo.org/>`_.
|
||||||
|
|
||||||
|
*This is rather experimental. See tests for usage for now.*
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
Licensed under the MIT, see ``LICENSE``.
|
108
click_threading/__init__.py
Normal file
108
click_threading/__init__.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import functools
|
||||||
|
import contextlib
|
||||||
|
import click
|
||||||
|
|
||||||
|
from ._compat import reraise
|
||||||
|
|
||||||
|
try:
|
||||||
|
import queue
|
||||||
|
except ImportError:
|
||||||
|
import Queue as queue
|
||||||
|
|
||||||
|
# The docs state that "Future should not be instantiated directly, only by
|
||||||
|
# Executors", but since I'm basically implementing my own executor here, I
|
||||||
|
# think we're fine.
|
||||||
|
try:
|
||||||
|
from concurrent.futures import Future
|
||||||
|
except ImportError:
|
||||||
|
from futures import Future
|
||||||
|
|
||||||
|
__version__ = '0.4.0'
|
||||||
|
|
||||||
|
_CTX_WORKER_KEY = __name__ + '.uiworker'
|
||||||
|
|
||||||
|
|
||||||
|
def _is_main_thread(thread=None):
|
||||||
|
thread = thread or threading.current_thread()
|
||||||
|
return type(thread).__name__ == '_MainThread'
|
||||||
|
|
||||||
|
|
||||||
|
class Thread(threading.Thread):
|
||||||
|
'''A thread that automatically pushes the parent thread's context in the
|
||||||
|
new thread.'''
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._click_context = click.get_current_context()
|
||||||
|
super(Thread, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
with self._click_context:
|
||||||
|
return super(Thread, self).run()
|
||||||
|
|
||||||
|
|
||||||
|
class UiWorker(object):
|
||||||
|
SHUTDOWN = object()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not _is_main_thread():
|
||||||
|
raise RuntimeError('The UiWorker can only run on the main thread.')
|
||||||
|
|
||||||
|
self.tasks = queue.Queue()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
self.put(self.SHUTDOWN, wait=False)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
func, future = self.tasks.get()
|
||||||
|
if func is self.SHUTDOWN:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = func()
|
||||||
|
except BaseException as e:
|
||||||
|
future.set_exception(e)
|
||||||
|
else:
|
||||||
|
future.set_result(result)
|
||||||
|
|
||||||
|
def put(self, func, wait=True):
|
||||||
|
if _is_main_thread():
|
||||||
|
return func()
|
||||||
|
|
||||||
|
future = Future()
|
||||||
|
self.tasks.put((func, future))
|
||||||
|
if not wait:
|
||||||
|
return
|
||||||
|
|
||||||
|
return future.result()
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def patch_click(self):
|
||||||
|
from .monkey import patch_ui_functions
|
||||||
|
|
||||||
|
def wrapper(f, info):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def inner(*a, **kw):
|
||||||
|
return get_ui_worker() \
|
||||||
|
.put(lambda: f(*a, **kw), wait=info.interactive)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
ctx = click.get_current_context()
|
||||||
|
with patch_ui_functions(wrapper):
|
||||||
|
ctx.meta[_CTX_WORKER_KEY] = self
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
assert ctx.meta.pop(_CTX_WORKER_KEY) is self
|
||||||
|
|
||||||
|
|
||||||
|
def get_ui_worker():
|
||||||
|
try:
|
||||||
|
ctx = click.get_current_context()
|
||||||
|
return ctx.meta[_CTX_WORKER_KEY]
|
||||||
|
except (RuntimeError, KeyError):
|
||||||
|
raise RuntimeError('UI worker not found.')
|
13
click_threading/_compat.py
Normal file
13
click_threading/_compat.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
||||||
|
else:
|
||||||
|
def reraise(tp, value, tb=None):
|
||||||
|
if value.__traceback__ is not tb:
|
||||||
|
raise value.with_traceback(tb)
|
||||||
|
raise value
|
39
click_threading/monkey.py
Normal file
39
click_threading/monkey.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
class FunctionInfo(object):
|
||||||
|
def __init__(self, interactive):
|
||||||
|
self.interactive = interactive
|
||||||
|
|
||||||
|
_ui_functions = {
|
||||||
|
'echo_via_pager': FunctionInfo(interactive=True),
|
||||||
|
'prompt': FunctionInfo(interactive=True),
|
||||||
|
'confirm': FunctionInfo(interactive=True),
|
||||||
|
'clear': FunctionInfo(interactive=False),
|
||||||
|
'echo': FunctionInfo(interactive=False),
|
||||||
|
'edit': FunctionInfo(interactive=True),
|
||||||
|
'launch': FunctionInfo(interactive=True),
|
||||||
|
'getchar': FunctionInfo(interactive=True),
|
||||||
|
'pause': FunctionInfo(interactive=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def patch_ui_functions(wrapper):
|
||||||
|
'''Wrap all termui functions with a custom decorator.'''
|
||||||
|
NONE = object()
|
||||||
|
saved = {}
|
||||||
|
import click
|
||||||
|
|
||||||
|
for name, info in _ui_functions.items():
|
||||||
|
orig = getattr(click, name, NONE)
|
||||||
|
if orig is not NONE:
|
||||||
|
saved[name] = orig
|
||||||
|
setattr(click, name, wrapper(orig, info))
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
for name, orig in saved.items():
|
||||||
|
setattr(click, name, orig)
|
6
setup.cfg
Normal file
6
setup.cfg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[wheel]
|
||||||
|
universal = 1
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# W503: Line break before operator
|
||||||
|
ignore = W503
|
29
setup.py
Normal file
29
setup.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import re
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
_version_re = re.compile(r'__version__\s+=\s+(.*)')
|
||||||
|
|
||||||
|
with open('click_threading/__init__.py', 'rb') as f:
|
||||||
|
version = str(ast.literal_eval(_version_re.search(
|
||||||
|
f.read().decode('utf-8')).group(1)))
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='click-threading',
|
||||||
|
version=version,
|
||||||
|
description='Multithreaded Click apps made easy',
|
||||||
|
author='Markus Unterwaditzer',
|
||||||
|
author_email='markus@unterwaditzer.net',
|
||||||
|
url='https://github.com/click-contrib/click-threading',
|
||||||
|
license='MIT',
|
||||||
|
packages=['click_threading'],
|
||||||
|
install_requires=[
|
||||||
|
'click>=5.0',
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
':python_version < "3.2"': 'futures'
|
||||||
|
}
|
||||||
|
)
|
56
tests/test_basic.py
Normal file
56
tests/test_basic.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import click_threading
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner():
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def test_context_pushing_thread(runner):
|
||||||
|
@click.command()
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx):
|
||||||
|
contexts = []
|
||||||
|
|
||||||
|
def check_ctx():
|
||||||
|
contexts.append(click.get_current_context())
|
||||||
|
|
||||||
|
t = click_threading.Thread(target=check_ctx)
|
||||||
|
t.start()
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
assert contexts == [ctx]
|
||||||
|
|
||||||
|
runner.invoke(cli, catch_exceptions=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ui_worker_basic(runner):
|
||||||
|
orig_click_prompt = click.prompt
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
def cli():
|
||||||
|
|
||||||
|
ui = click_threading.UiWorker()
|
||||||
|
|
||||||
|
def target():
|
||||||
|
assert click.prompt is not orig_click_prompt
|
||||||
|
click.prompt('two')
|
||||||
|
ui.shutdown()
|
||||||
|
|
||||||
|
click.prompt('one')
|
||||||
|
|
||||||
|
with ui.patch_click():
|
||||||
|
t = click_threading.Thread(target=target)
|
||||||
|
t.start()
|
||||||
|
ui.run()
|
||||||
|
|
||||||
|
click.prompt('three')
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
result = runner.invoke(cli, catch_exceptions=False, input='y\n' * 3)
|
||||||
|
assert result.output.splitlines() == ['one: y', 'two: y', 'three: y']
|
Loading…
Reference in a new issue