From 81cde486ca27116f26b2b0b9bde48f68ad01bf86 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Tue, 4 Sep 2018 13:50:44 +0200 Subject: [PATCH 1/4] Drop python 3.4 from legacy: use new * Reduce build jobs and remove cythonize code * backport typehints from modern code and drop pyi Signed-off-by: Alexey Stepanov --- .travis.yml | 10 +- requirements.txt | 1 + setup.py | 62 +-- test/test_gevent_threadpooled.py | 17 +- test/test_pooled.py | 17 +- test/test_pooled_async.py | 111 ----- test/test_threaded.py | 11 +- threaded/__init__.py | 38 +- threaded/_base_gthreadpooled.pyi | 20 - threaded/_base_threaded.py | 251 +---------- threaded/_base_threaded.pyi | 57 --- threaded/_class_decorator.py | 20 +- threaded/_class_decorator.pyi | 16 - ...ase_gthreadpooled.py => _gthreadpooled.py} | 47 +- threaded/_gthreadpooled2.py | 53 --- threaded/_gthreadpooled2.pyi | 12 - threaded/_gthreadpooled3.py | 80 ---- threaded/_gthreadpooled3.pyi | 12 - threaded/_py3_helpers.py | 50 --- threaded/_py3_helpers.pyi | 8 - threaded/_threaded.py | 183 ++++++++ threaded/_threaded2.py | 87 ---- threaded/_threaded2.pyi | 25 -- threaded/_threaded3.py | 409 ------------------ threaded/_threaded3.pyi | 126 ------ threaded/_threadpooled.py | 162 +++++++ tools/build-wheels.sh | 64 --- tools/run_docker.sh | 32 -- tox.ini | 9 +- 29 files changed, 436 insertions(+), 1554 deletions(-) delete mode 100644 test/test_pooled_async.py delete mode 100644 threaded/_base_gthreadpooled.pyi delete mode 100644 threaded/_base_threaded.pyi delete mode 100644 threaded/_class_decorator.pyi rename threaded/{_base_gthreadpooled.py => _gthreadpooled.py} (68%) delete mode 100644 threaded/_gthreadpooled2.py delete mode 100644 threaded/_gthreadpooled2.pyi delete mode 100644 threaded/_gthreadpooled3.py delete mode 100644 threaded/_gthreadpooled3.pyi delete mode 100644 threaded/_py3_helpers.py delete mode 100644 threaded/_py3_helpers.pyi create mode 100644 threaded/_threaded.py delete mode 100644 threaded/_threaded2.py delete mode 100644 threaded/_threaded2.pyi delete mode 100644 threaded/_threaded3.py delete mode 100644 threaded/_threaded3.pyi create mode 100644 threaded/_threadpooled.py delete mode 100755 tools/build-wheels.sh delete mode 100755 tools/run_docker.sh diff --git a/.travis.yml b/.travis.yml index 8a92fed..185f3dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,8 @@ sudo: false language: python os: linux python: -- 2.7 -- 3.4 -- 3.5 -- 3.6 -- &mainstream_python 3.7-dev +- &mainstream_python 2.7 - &pypy pypy -- pypy3.5 install: - &upgrade_python_toolset pip install --upgrade pip setuptools wheel - pip install tox-travis @@ -49,8 +44,7 @@ jobs: - docker install: - *upgrade_python_toolset - script: - - ./tools/run_docker.sh "threaded" + script: [] before_deploy: - pip install -r build_requirements.txt - python setup.py bdist_wheel diff --git a/requirements.txt b/requirements.txt index a7aecab..0c7c89b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ six >=1.10.0 +futures>=3.1 ; python_version == "2.7" typing >= 3.6 ; python_version < "3.8" diff --git a/setup.py b/setup.py index 331fbbb..6a4ff2f 100644 --- a/setup.py +++ b/setup.py @@ -20,21 +20,11 @@ import collections from distutils.command import build_ext import distutils.errors -import glob import os.path import shutil -import sys - -try: - from Cython.Build import cythonize - import gevent -except ImportError: - gevent = cythonize = None import setuptools -PY3 = sys.version_info[:2] > (2, 7) # type: bool - with open( os.path.join( os.path.dirname(__file__), @@ -50,35 +40,7 @@ long_description = f.read() -def _extension(modpath): - """Make setuptools.Extension.""" - return setuptools.Extension(modpath, [modpath.replace('.', '/') + '.py']) - - -requires_optimization = [ - _extension('threaded._class_decorator'), - _extension('threaded._base_threaded'), - _extension('threaded._py3_helpers'), - _extension('threaded._threaded3'), - _extension('threaded._base_gthreadpooled'), - _extension('threaded._gthreadpooled3'), -] - -if 'win32' != sys.platform: - requires_optimization.append( - _extension('threaded.__init__') - ) - -ext_modules = cythonize( - requires_optimization, - compiler_directives=dict( - always_allow_keywords=True, - binding=True, - embedsignature=True, - overflowcheck=True, - language_level=3, - ) -) if cythonize is not None and PY3 else [] +ext_modules = [] class BuildFailed(Exception): @@ -176,8 +138,6 @@ def get_simple_vars_from_src(src): ast.Str, ast.Num, ast.List, ast.Set, ast.Dict, ast.Tuple ) - if PY3: - ast_data += (ast.Bytes, ast.NameConstant,) tree = ast.parse(src) @@ -221,11 +181,6 @@ def get_simple_vars_from_src(src): 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', @@ -255,7 +210,7 @@ def get_simple_vars_from_src(src): long_description=long_description, classifiers=classifiers, keywords=keywords, - python_requires='>=2.7.5,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*', + python_requires='>=2.7.5,<3.0', # While setuptools cannot deal with pre-installed incompatible versions, # setting a lower bound is not harmful - it makes error messages cleaner. DO # NOT set an upper bound on setuptools, as that will lead to uninstallable @@ -266,26 +221,15 @@ def get_simple_vars_from_src(src): "!=34.0.0,!=34.0.1,!=34.0.2,!=34.0.3,!=34.1.0,!=34.1.1,!=34.2.0,!=34.3.0,!=34.3.1,!=34.3.2," "!=36.2.0", extras_require={ - ':python_version == "2.7"': [ - 'futures>=3.1', - ], 'gevent': [ 'gevent >= 1.2.2' ], }, install_requires=required, package_data={ - 'threaded': [ - os.path.basename(filename) - for filename in glob.glob(os.path.join('threaded', '*.pyi')) - ] + [ - 'py.typed' - ], + 'threaded': ['py.typed'], }, ) -if PY3 and cythonize is not None: - setup_args['ext_modules'] = ext_modules - setup_args['cmdclass'] = dict(build_ext=AllowFailRepair) try: setuptools.setup(**setup_args) diff --git a/test/test_gevent_threadpooled.py b/test/test_gevent_threadpooled.py index a3e2320..fd30943 100644 --- a/test/test_gevent_threadpooled.py +++ b/test/test_gevent_threadpooled.py @@ -24,19 +24,14 @@ except ImportError: gevent = None -import six - import threaded -if six.PY3: - from os import cpu_count -else: - try: - from multiprocessing import cpu_count - except ImportError: - def cpu_count(): - """Fake CPU count.""" - return 1 +try: + from multiprocessing import cpu_count +except ImportError: + def cpu_count(): + """Fake CPU count.""" + return 1 @unittest.skipIf(gevent is None, 'No gevent') diff --git a/test/test_pooled.py b/test/test_pooled.py index 3a9db4d..0299f21 100644 --- a/test/test_pooled.py +++ b/test/test_pooled.py @@ -19,19 +19,14 @@ import threading import unittest -import six - import threaded -if six.PY3: - from os import cpu_count -else: - try: - from multiprocessing import cpu_count - except ImportError: - def cpu_count(): - """Fake CPU count.""" - return 1 +try: + from multiprocessing import cpu_count +except ImportError: + def cpu_count(): + """Fake CPU count.""" + return 1 class TestThreadPooled(unittest.TestCase): diff --git a/test/test_pooled_async.py b/test/test_pooled_async.py deleted file mode 100644 index 58d2a61..0000000 --- a/test/test_pooled_async.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright 2017 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -try: - import asyncio -except ImportError: - asyncio = None -import concurrent.futures -import threading -import unittest - -import threaded - - -@unittest.skipIf(asyncio is None, 'No asyncio') -class TestThreadPooled(unittest.TestCase): - def tearDown(self): - threaded.ThreadPooled.shutdown() - - def test_thread_pooled_default(self): - @threaded.threadpooled - @asyncio.coroutine - def test(): - return threading.current_thread().name - - pooled_name = concurrent.futures.wait([test()]) - self.assertNotEqual(pooled_name, threading.current_thread().name) - - def test_thread_pooled_construct(self): - @threaded.threadpooled() - @asyncio.coroutine - def test(): - return threading.current_thread().name - - pooled_name = concurrent.futures.wait([test()]) - self.assertNotEqual(pooled_name, threading.current_thread().name) - - def test_thread_pooled_loop(self): - loop = asyncio.get_event_loop() - - @threaded.threadpooled(loop_getter=loop) - @asyncio.coroutine - def test(): - return threading.current_thread().name - - pooled_name = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertNotEqual(pooled_name, threading.current_thread().name) - - def test_thread_pooled_loop_getter(self): - loop = asyncio.get_event_loop() - - @threaded.threadpooled(loop_getter=asyncio.get_event_loop) - @asyncio.coroutine - def test(): - return threading.current_thread().name - - pooled_name = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertNotEqual(pooled_name, threading.current_thread().name) - - def test_thread_pooled_loop_getter_context(self): - loop = asyncio.get_event_loop() - - def loop_getter(target): - return target - - @threaded.threadpooled( - loop_getter=loop_getter, - loop_getter_need_context=True - ) - @asyncio.coroutine - def test(*args, **kwargs): - return threading.current_thread().name - - pooled_name = loop.run_until_complete( - asyncio.wait_for(test(loop), 1) - ) - self.assertNotEqual(pooled_name, threading.current_thread().name) - - -@unittest.skipIf(asyncio is None, 'No asyncio') -class TestAsyncIOTask(unittest.TestCase): - def test_default(self): - @threaded.asynciotask - @asyncio.coroutine - def test(): - return 'test' - - loop = asyncio.get_event_loop() - res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') - - def test_construct(self): - @threaded.asynciotask() - @asyncio.coroutine - def test(): - return 'test' - - loop = asyncio.get_event_loop() - res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') diff --git a/test/test_threaded.py b/test/test_threaded.py index 48911a1..61c28c9 100644 --- a/test/test_threaded.py +++ b/test/test_threaded.py @@ -14,16 +14,11 @@ import unittest -import six - import threaded -# pylint: disable=import-error -if six.PY2: - # noinspection PyUnresolvedReferences - import mock -else: - from unittest import mock + +# noinspection PyUnresolvedReferences +import mock class ThreadedTest(unittest.TestCase): diff --git a/threaded/__init__.py b/threaded/__init__.py index f5754ae..945291b 100644 --- a/threaded/__init__.py +++ b/threaded/__init__.py @@ -16,37 +16,18 @@ from __future__ import absolute_import -import sys import typing # noqa # pylint: disable=unused-import -PY3 = sys.version_info[:2] > (3, 0) # type: bool # pylint: disable=no-name-in-module -if PY3: # pragma: no cover - from ._threaded3 import ( - ThreadPooled, - Threaded, - AsyncIOTask, - threadpooled, - threaded, - asynciotask - ) +from ._threaded import Threaded, threaded +from ._threadpooled import ThreadPooled, threadpooled - try: # pragma: no cover - from ._gthreadpooled3 import GThreadPooled, gthreadpooled - except ImportError: # pragma: no cover - GThreadPooled = gthreadpooled = None # type: ignore -else: # pragma: no cover - from ._threaded2 import ( - ThreadPooled, - Threaded, - threadpooled, - threaded, - ) - try: # pragma: no cover - from ._gthreadpooled2 import GThreadPooled, gthreadpooled - except ImportError: # pragma: no cover - GThreadPooled = gthreadpooled = None + +try: # pragma: no cover + from ._gthreadpooled import GThreadPooled, gthreadpooled +except ImportError: # pragma: no cover + GThreadPooled = gthreadpooled = None # type: ignore # pylint: enable=no-name-in-module @@ -55,11 +36,6 @@ 'threadpooled', 'threaded' ) # type: typing.Tuple[str, ...] -if PY3: # pragma: no cover - __all__ += ( - 'AsyncIOTask', - 'asynciotask' - ) if GThreadPooled is not None: # pragma: no cover __all__ += ( 'GThreadPooled', diff --git a/threaded/_base_gthreadpooled.pyi b/threaded/_base_gthreadpooled.pyi deleted file mode 100644 index 4292682..0000000 --- a/threaded/_base_gthreadpooled.pyi +++ /dev/null @@ -1,20 +0,0 @@ -import gevent.event # type: ignore -import gevent.threadpool # type: ignore -import typing -from . import _base_threaded - -class BaseGThreadPooled(_base_threaded.APIPooled): - @classmethod - def configure( - cls: typing.Type[BaseGThreadPooled], - max_workers: typing.Optional[int] = ..., - hub: typing.Optional[gevent.hub.Hub] = ..., - ) -> None: ... - - @classmethod - def shutdown(cls: typing.Type[BaseGThreadPooled]) -> None: ... - - @property - def executor(self) -> gevent.threadpool.ThreadPool: ... - - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable[..., gevent.event.AsyncResult]: ... diff --git a/threaded/_base_threaded.py b/threaded/_base_threaded.py index d486676..b6b09ed 100644 --- a/threaded/_base_threaded.py +++ b/threaded/_base_threaded.py @@ -17,29 +17,21 @@ from __future__ import absolute_import import abc -# noinspection PyCompatibility -import concurrent.futures -import threading import typing # noqa # pylint: disable=unused-import import six from . import _class_decorator -if six.PY3: # pragma: no cover - from os import cpu_count -else: # pragma: no cover - try: - from multiprocessing import cpu_count - except ImportError: - def cpu_count(): # type: () -> int - """Fake CPU count.""" - return 1 +try: + from multiprocessing import cpu_count +except ImportError: + def cpu_count(): # type: () -> int + """Fake CPU count.""" + return 1 __all__ = ( 'APIPooled', - 'BasePooled', - 'ThreadPoolExecutor', 'cpu_count' ) @@ -72,234 +64,3 @@ def shutdown(cls): # type: (typing.Type[APIPooled]) -> None def executor(self): # type: () -> typing.Any """Executor instance.""" raise NotImplementedError() # pragma: no cover - - -class BasePooled(APIPooled): - """Base ThreadPooled class.""" - - __slots__ = () - - __executor = None # type: typing.Optional[ThreadPoolExecutor] - - @classmethod - def configure( - cls, # type: typing.Type[BasePooled] - max_workers=None, # type: typing.Optional[int] - ): # type: (...) -> None - """Pool executor create and configure. - - :param max_workers: Maximum workers - :type max_workers: typing.Optional[int] - """ - if isinstance(cls.__executor, ThreadPoolExecutor): - if cls.__executor.max_workers == max_workers: - return - cls.__executor.shutdown() - - cls.__executor = ThreadPoolExecutor( - max_workers=max_workers, - ) - - @classmethod - def shutdown(cls): # type: (typing.Type[BasePooled]) -> None - """Shutdown executor.""" - if cls.__executor is not None: - cls.__executor.shutdown() - - @property - def executor(self): # type: () -> ThreadPoolExecutor - """Executor instance. - - :rtype: ThreadPoolExecutor - """ - if ( - not isinstance(self.__executor, ThreadPoolExecutor) or - self.__executor.is_shutdown - ): - self.configure() - return self.__executor - - def _get_function_wrapper( - self, - func # type: typing.Callable - ): # type: (...) -> typing.Callable[..., concurrent.futures.Future] - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :return: wrapped function - :rtype: typing.Callable[..., concurrent.futures.Future] - """ - # pylint: disable=missing-docstring - # noinspection PyMissingOrEmptyDocstring - @six.wraps(func) - def wrapper( - *args, - **kwargs - ): # type: (...) -> concurrent.futures.Future - return self.executor.submit(func, *args, **kwargs) - # pylint: enable=missing-docstring - return wrapper - - -class BaseThreaded(_class_decorator.BaseDecorator): - """Base Threaded class.""" - - __slots__ = ( - '__name', - '__daemon', - '__started', - ) - - def __init__( - self, - name=None, # type: typing.Optional[typing.Union[str, typing.Callable]] - daemon=False, # type: bool - started=False, # type: bool - ): # type: (...) -> None - """Run function in separate thread. - - :param name: New thread name. - If callable: use as wrapped function. - If none: use wrapped function name. - :type name: typing.Optional[typing.Union[str, typing.Callable]] - :param daemon: Daemonize thread. - :type daemon: bool - :param started: Return started thread - :type started: bool - """ - # pylint: disable=assigning-non-slot - self.__daemon = daemon - self.__started = started - if callable(name): - func = name - self.__name = 'Threaded: ' + getattr( - name, - '__name__', - str(hash(name)) - ) # type: str - else: - func, self.__name = None, name # type: None, typing.Optional[str] - super(BaseThreaded, self).__init__(func=func) - # pylint: enable=assigning-non-slot - - @property - def name(self): # type: () -> typing.Optional[str] - """Thread name. - - :rtype: typing.Optional[str] - """ - return self.__name - - @property - def daemon(self): # type: () -> bool - """Start thread as daemon. - - :rtype: bool - """ - return self.__daemon - - @property - def started(self): # type: () -> bool - """Return started thread. - - :rtype: bool - """ - return self.__started - - def _get_function_wrapper( - self, - func # type: typing.Callable - ): # type: (...) -> typing.Callable[..., threading.Thread] - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :return: wrapped function - :rtype: typing.Callable[..., threading.Thread] - """ - name = self.name - if name is None: - name = 'Threaded: ' + getattr( - func, - '__name__', - str(hash(func)) - ) - - # pylint: disable=missing-docstring - # noinspection PyMissingOrEmptyDocstring - @six.wraps(func) - def wrapper( - *args, - **kwargs - ): # type: (...) -> threading.Thread - thread = threading.Thread( - target=func, - name=name, - args=args, - kwargs=kwargs, - ) - thread.daemon = self.daemon - if self.started: - thread.start() - return thread - - # pylint: enable=missing-docstring - return wrapper - - def __repr__(self): - """For debug purposes.""" - return ( - "{cls}(" - "name={self.name!r}, " - "daemon={self.daemon!r}, " - "started={self.started!r}, " - ")".format( - cls=self.__class__.__name__, - self=self, - ) - ) # pragma: no cover - - -class ThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor): - """Provide readers for protected attributes. - - Simply extend concurrent.futures.ThreadPoolExecutor. - """ - - __slots__ = () - - def __init__( - self, - max_workers=None # type: typing.Optional[int] - ): # type: (...) -> None - """Override init due to difference between Python <3.5 and 3.5+. - - :param max_workers: Maximum workers allowed. - If none: cpu_count() or 1) * 5 - :type max_workers: typing.Optional[int] - """ - if max_workers is None: # Use 3.5+ behavior - max_workers = (cpu_count() or 1) * 5 - super( - ThreadPoolExecutor, - self - ).__init__( - max_workers=max_workers, - ) - - @property - def max_workers(self): # type: () -> int - """MaxWorkers. - - :rtype: int - """ - return self._max_workers - - @property - def is_shutdown(self): # type: () -> bool - """Executor shutdown state. - - :rtype: bool - """ - return self._shutdown diff --git a/threaded/_base_threaded.pyi b/threaded/_base_threaded.pyi deleted file mode 100644 index b25c468..0000000 --- a/threaded/_base_threaded.pyi +++ /dev/null @@ -1,57 +0,0 @@ -import abc -import concurrent.futures -import threading -import typing -from . import _class_decorator - -def cpu_count() -> int: ... - -class APIPooled(_class_decorator.BaseDecorator, metaclass=abc.ABCMeta): - @classmethod - def configure(cls: typing.Type[APIPooled], max_workers: typing.Optional[int] = ...) -> None: ... - - @classmethod - def shutdown(cls: typing.Type[APIPooled]) -> None: ... - - @property - def executor(self) -> typing.Any: ... - -class BasePooled(APIPooled): - @classmethod - def configure(cls: typing.Type[BasePooled], max_workers: typing.Optional[int] = ...) -> None: ... - - @classmethod - def shutdown(cls: typing.Type[BasePooled]) -> None: ... - - @property - def executor(self) -> ThreadPoolExecutor: ... - - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable[..., concurrent.futures.Future]: ... - -class BaseThreaded(_class_decorator.BaseDecorator): - def __init__( - self, - name: typing.Optional[typing.Union[str, typing.Callable]] = ..., - daemon: bool = ..., - started: bool = ..., - ) -> None: ... - - @property - def name(self) -> typing.Optional[str]: ... - - @property - def daemon(self) -> bool: ... - - @property - def started(self) -> bool: ... - - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable[..., threading.Thread]: ... - -class ThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor): - def __init__(self, max_workers: typing.Optional[int] = ...) -> None: ... - - @property - def max_workers(self) -> int: ... - - @property - def is_shutdown(self) -> bool: ... diff --git a/threaded/_class_decorator.py b/threaded/_class_decorator.py index ac18778..d27bf4f 100644 --- a/threaded/_class_decorator.py +++ b/threaded/_class_decorator.py @@ -76,15 +76,14 @@ def __init__( :param func: function to wrap :type func: typing.Optional[typing.Callable] """ + # noinspection PyArgumentList + super(BaseDecorator, self).__init__() # pylint: disable=assigning-non-slot self.__func = func # type: typing.Optional[typing.Callable] if self.__func is not None: functools.update_wrapper(self, self.__func) - if not six.PY3: # pragma: no cover - self.__wrapped__ = self.__func # type: typing.Callable + self.__wrapped__ = self.__func # type: typing.Callable # pylint: enable=assigning-non-slot - # noinspection PyArgumentList - super(BaseDecorator, self).__init__() @property def _func( @@ -115,14 +114,19 @@ def __call__( **kwargs # type: typing.Any ): # type: (...) -> typing.Any """Main decorator getter.""" - args = list(args) - wrapped = self.__func or args.pop(0) + l_args = list(args) + + if self._func: + wrapped = self._func # type: typing.Callable + else: + wrapped = l_args.pop(0) + wrapper = self._get_function_wrapper(wrapped) if self.__func: - return wrapper(*args, **kwargs) + return wrapper(*l_args, **kwargs) return wrapper - def __repr__(self): + def __repr__(self): # type: () -> str """For debug purposes.""" return "<{cls}({func!r}) at 0x{id:X}>".format( cls=self.__class__.__name__, diff --git a/threaded/_class_decorator.pyi b/threaded/_class_decorator.pyi deleted file mode 100644 index 436fab4..0000000 --- a/threaded/_class_decorator.pyi +++ /dev/null @@ -1,16 +0,0 @@ -import abc -import typing - - -class BaseDecorator(object, metaclass=abc.ABCMeta): - __wrapped__: typing.Optional[typing.Callable] = ... - - def __init__(self, func: typing.Optional[typing.Callable] = ...) -> None: ... - - @property - def _func(self) -> typing.Optional[typing.Callable]: ... - - @abc.abstractmethod - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable: ... - - def __call__(self, *args: typing.Tuple, **kwargs: typing.Dict) -> typing.Any: ... diff --git a/threaded/_base_gthreadpooled.py b/threaded/_gthreadpooled.py similarity index 68% rename from threaded/_base_gthreadpooled.py rename to threaded/_gthreadpooled.py index 2fa8519..70eb7cb 100644 --- a/threaded/_base_gthreadpooled.py +++ b/threaded/_gthreadpooled.py @@ -12,24 +12,28 @@ # License for the specific language governing permissions and limitations # under the License. -"""gevent.threadpool.ThreadPool usage.""" +"""Python 2 threaded implementation. + +Uses backport of concurrent.futures. +""" from __future__ import absolute_import import typing # noqa # pylint: disable=unused-import -import gevent.event # noqa # pylint: disable=unused-import -import gevent.threadpool +import gevent.event # type: ignore # noqa # pylint: disable=unused-import +import gevent.threadpool # type: ignore # noqa # pylint: disable=unused-import import six from . import _base_threaded __all__ = ( - 'BaseGThreadPooled', + 'GThreadPooled', + 'gthreadpooled', ) -class BaseGThreadPooled(_base_threaded.APIPooled): +class GThreadPooled(_base_threaded.APIPooled): """Post function to gevent.threadpool.ThreadPool.""" __slots__ = () @@ -39,7 +43,7 @@ class BaseGThreadPooled(_base_threaded.APIPooled): # pylint: disable=arguments-differ @classmethod def configure( - cls, # type: typing.Type[BaseGThreadPooled] + cls, # type: typing.Type[GThreadPooled] max_workers=None, # type: typing.Optional[int] hub=None # type: typing.Optional[gevent.hub.Hub] ): # type: (...) -> None @@ -70,7 +74,7 @@ def configure( # pylint: enable=arguments-differ @classmethod - def shutdown(cls): # type: (typing.Type[BaseGThreadPooled]) -> None + def shutdown(cls): # type: (typing.Type[GThreadPooled]) -> None """Shutdown executor. Due to not implemented method, set maxsize to 0 (do not accept new). @@ -103,9 +107,34 @@ def _get_function_wrapper( # noinspection PyMissingOrEmptyDocstring @six.wraps(func) def wrapper( - *args, - **kwargs + *args, # type: typing.Any + **kwargs # type: typing.Any ): # type: (...) -> gevent.event.AsyncResult return self.executor.spawn(func, *args, **kwargs) + # pylint: enable=missing-docstring return wrapper + + def __call__( # pylint: disable=useless-super-delegation + self, + *args, # type: typing.Union[typing.Callable, typing.Any] + **kwargs # type: typing.Any + ): # type: (...) -> typing.Union[gevent.event.AsyncResult, typing.Callable[..., gevent.event.AsyncResult]] + """Callable instance.""" + return super(GThreadPooled, self).__call__(*args, **kwargs) + + +# pylint: disable=unexpected-keyword-arg, no-value-for-parameter +def gthreadpooled( + func=None # type: typing.Optional[typing.Callable] +): # type: (...) -> typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]] + """Post function to gevent.threadpool.ThreadPool. + + :param func: function to wrap + :type func: typing.Optional[typing.Callable] + :rtype: typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]] + """ + if func is None: + return GThreadPooled(func=func) + return GThreadPooled(func=None)(func) +# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_gthreadpooled2.py b/threaded/_gthreadpooled2.py deleted file mode 100644 index 779ac6b..0000000 --- a/threaded/_gthreadpooled2.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2017-2018 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Python 2 threaded implementation. - -Uses backport of concurrent.futures. -""" - -from __future__ import absolute_import - -import typing # noqa # pylint: disable=unused-import - -import gevent.event # noqa # pylint: disable=unused-import - -from . import _base_gthreadpooled - -__all__ = ( - 'GThreadPooled', - 'gthreadpooled', -) - - -class GThreadPooled(_base_gthreadpooled.BaseGThreadPooled): - """Post function to gevent.threadpool.ThreadPool.""" - - __slots__ = () - - -# pylint: disable=unexpected-keyword-arg, no-value-for-parameter -def gthreadpooled( - func=None # type: typing.Optional[typing.Callable] -): # type: (...) -> typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]] - """Post function to gevent.threadpool.ThreadPool. - - :param func: function to wrap - :type func: typing.Optional[typing.Callable] - :rtype: typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]] - """ - if func is None: - return GThreadPooled(func=func) - return GThreadPooled(func=None)(func) -# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_gthreadpooled2.pyi b/threaded/_gthreadpooled2.pyi deleted file mode 100644 index 47d7208..0000000 --- a/threaded/_gthreadpooled2.pyi +++ /dev/null @@ -1,12 +0,0 @@ -import gevent.event # type: ignore -import typing -from . import _base_gthreadpooled - -class GThreadPooled(_base_gthreadpooled.BaseGThreadPooled): ... - - -@typing.overload -def gthreadpooled(func: typing.Callable) -> typing.Callable[..., gevent.event.AsyncResult]: ... - -@typing.overload -def gthreadpooled(func: None = ...) -> GThreadPooled: ... diff --git a/threaded/_gthreadpooled3.py b/threaded/_gthreadpooled3.py deleted file mode 100644 index 1fad00d..0000000 --- a/threaded/_gthreadpooled3.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright 2017-2018 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Python 3 threaded implementation. - -Asyncio is supported -""" - -import functools -import typing - -import gevent.event - -from . import _base_gthreadpooled -from . import _py3_helpers - -__all__ = ( - 'GThreadPooled', - 'gthreadpooled', -) - - -class GThreadPooled(_base_gthreadpooled.BaseGThreadPooled): - """Post function to ThreadPoolExecutor.""" - - __slots__ = () - - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable[ - ..., - gevent.event.AsyncResult - ]: - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :return: wrapped coroutine or function - :rtype: typing.Callable[..., gevent.event.AsyncResult] - """ - prepared = _py3_helpers.await_if_required(func) - - # pylint: disable=missing-docstring - # noinspection PyMissingOrEmptyDocstring - @functools.wraps(prepared) - def wrapper( - *args, **kwargs - ) -> gevent.event.AsyncResult: - return self.executor.spawn(prepared, *args, **kwargs) - - # pylint: enable=missing-docstring - return wrapper - - -# pylint: disable=unexpected-keyword-arg, no-value-for-parameter -def gthreadpooled( - func: typing.Optional[typing.Callable] = None -) -> typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]]: - """Post function to gevent.threadpool.ThreadPool. - - :param func: function to wrap - :type func: typing.Optional[typing.Callable] - :rtype: typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]] - """ - if func is None: - return GThreadPooled(func=func) - return GThreadPooled(func=None)(func) -# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_gthreadpooled3.pyi b/threaded/_gthreadpooled3.pyi deleted file mode 100644 index 47d7208..0000000 --- a/threaded/_gthreadpooled3.pyi +++ /dev/null @@ -1,12 +0,0 @@ -import gevent.event # type: ignore -import typing -from . import _base_gthreadpooled - -class GThreadPooled(_base_gthreadpooled.BaseGThreadPooled): ... - - -@typing.overload -def gthreadpooled(func: typing.Callable) -> typing.Callable[..., gevent.event.AsyncResult]: ... - -@typing.overload -def gthreadpooled(func: None = ...) -> GThreadPooled: ... diff --git a/threaded/_py3_helpers.py b/threaded/_py3_helpers.py deleted file mode 100644 index 0b741aa..0000000 --- a/threaded/_py3_helpers.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright 2017-2018 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Python 3 related helpers.""" - -# noinspection PyCompatibility -import asyncio -import functools -import typing - -__all__ = ( - 'get_loop', 'await_if_required' -) - - -def get_loop( - self: typing.Any, - *args: typing.Tuple, **kwargs: typing.Dict -) -> typing.Optional[asyncio.AbstractEventLoop]: - """Get event loop in decorator class.""" - if callable(self.loop_getter): - if self.loop_getter_need_context: - return self.loop_getter(*args, **kwargs) - return self.loop_getter() - return self.loop_getter - - -def await_if_required(target: typing.Callable) -> typing.Callable[..., typing.Any]: - """Await result if coroutine was returned.""" - @functools.wraps(target) - def wrapper(*args, **kwargs): # type: (...) -> typing.Any - """Decorator/wrapper.""" - result = target(*args, **kwargs) - if asyncio.iscoroutine(result): - loop = asyncio.new_event_loop() - result = loop.run_until_complete(result) - loop.close() - return result - return wrapper diff --git a/threaded/_py3_helpers.pyi b/threaded/_py3_helpers.pyi deleted file mode 100644 index 3cb199f..0000000 --- a/threaded/_py3_helpers.pyi +++ /dev/null @@ -1,8 +0,0 @@ -import asyncio -import typing - -def get_loop( - self: typing.Any, *args: typing.Tuple, **kwargs: typing.Dict -) -> typing.Optional[asyncio.AbstractEventLoop]: ... - -def await_if_required(target: typing.Callable) -> typing.Callable[..., typing.Any]: ... diff --git a/threaded/_threaded.py b/threaded/_threaded.py new file mode 100644 index 0000000..34b1f4a --- /dev/null +++ b/threaded/_threaded.py @@ -0,0 +1,183 @@ +# Copyright 2017-2018 Alexey Stepanov aka penguinolog +## +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Python 2 threaded implementation. + +Uses backport of concurrent.futures. +""" + +from __future__ import absolute_import + +import threading # noqa # pylint: disable=unused-import +import typing # noqa # pylint: disable=unused-import + +import six + +from . import _class_decorator + +__all__ = ( + 'Threaded', + 'threaded', +) + + +class Threaded(_class_decorator.BaseDecorator): + """Run function in separate thread.""" + + __slots__ = ( + '__name', + '__daemon', + '__started', + ) + + def __init__( + self, + name=None, # type: typing.Optional[typing.Union[str, typing.Callable]] + daemon=False, # type: bool + started=False, # type: bool + ): # type: (...) -> None + """Run function in separate thread. + + :param name: New thread name. + If callable: use as wrapped function. + If none: use wrapped function name. + :type name: typing.Optional[typing.Union[str, typing.Callable]] + :param daemon: Daemonize thread. + :type daemon: bool + :param started: Return started thread + :type started: bool + """ + # pylint: disable=assigning-non-slot + self.__daemon = daemon + self.__started = started + if callable(name): + func = name # type: typing.Callable + self.__name = 'Threaded: ' + getattr(name, '__name__', str(hash(name))) # type: str + else: + func, self.__name = None, name # type: ignore + super(Threaded, self).__init__(func=func) + # pylint: enable=assigning-non-slot + + @property + def name(self): # type: () -> typing.Optional[str] + """Thread name. + + :rtype: typing.Optional[str] + """ + return self.__name + + @property + def daemon(self): # type: () -> bool + """Start thread as daemon. + + :rtype: bool + """ + return self.__daemon + + @property + def started(self): # type: () -> bool + """Return started thread. + + :rtype: bool + """ + return self.__started + + def __repr__(self): # type: () -> str + """For debug purposes.""" + return ( + "{cls}(" + "name={self.name!r}, " + "daemon={self.daemon!r}, " + "started={self.started!r}, " + ")".format( + cls=self.__class__.__name__, + self=self, + ) + ) # pragma: no cover + + def _get_function_wrapper( + self, + func # type: typing.Callable + ): # type: (...) -> typing.Callable[..., threading.Thread] + """Here should be constructed and returned real decorator. + + :param func: Wrapped function + :type func: typing.Callable + :return: wrapped function + :rtype: typing.Callable[..., threading.Thread] + """ + name = self.name + if name is None: + name = 'Threaded: ' + getattr( + func, + '__name__', + str(hash(func)) + ) + + # pylint: disable=missing-docstring + # noinspection PyMissingOrEmptyDocstring + @six.wraps(func) + def wrapper( + *args, # type: typing.Any + **kwargs # type: typing.Any + ): # type: (...) -> threading.Thread + thread = threading.Thread( + target=func, + name=name, + args=args, + kwargs=kwargs, + ) + thread.daemon = self.daemon + if self.started: + thread.start() + return thread + + # pylint: enable=missing-docstring + return wrapper + + def __call__( # pylint: disable=useless-super-delegation + self, + *args, # type: typing.Union[typing.Callable, typing.Any] + **kwargs # type: typing.Any + ): # type: (...) -> typing.Union[threading.Thread, typing.Callable[..., threading.Thread]] + """Executable instance.""" + return super(Threaded, self).__call__(*args, **kwargs) # type: ignore + + +# pylint: disable=unexpected-keyword-arg, no-value-for-parameter +def threaded( + name=None, # type: typing.Optional[typing.Union[str, typing.Callable]] + daemon=False, # type: bool + started=False # type: bool +): # type: (...) -> typing.Union[Threaded, typing.Callable[..., threading.Thread]] + """Run function in separate thread. + + :param name: New thread name. + If callable: use as wrapped function. + If none: use wrapped function name. + :type name: typing.Union[None, str, typing.Callable] + :param daemon: Daemonize thread. + :type daemon: bool + :param started: Return started thread + :type started: bool + :rtype: typing.Union[Threaded, typing.Callable[..., threading.Thread]] + """ + if callable(name): + func, name = ( + name, + 'Threaded: ' + getattr(name, '__name__', str(hash(name))) + ) + return Threaded(name=name, daemon=daemon, started=started)(func) # type: ignore + return Threaded(name=name, daemon=daemon, started=started) +# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_threaded2.py b/threaded/_threaded2.py deleted file mode 100644 index ec3d58d..0000000 --- a/threaded/_threaded2.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright 2017-2018 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Python 2 threaded implementation. - -Uses backport of concurrent.futures. -""" - -from __future__ import absolute_import - -import concurrent.futures # noqa # pylint: disable=unused-import -import threading # noqa # pylint: disable=unused-import -import typing # noqa # pylint: disable=unused-import - -from . import _base_threaded - -__all__ = ( - 'ThreadPooled', - 'Threaded', - 'threadpooled', - 'threaded', -) - - -class ThreadPooled(_base_threaded.BasePooled): - """Post function to ThreadPoolExecutor.""" - - __slots__ = () - - -class Threaded(_base_threaded.BaseThreaded): - """Run function in separate thread.""" - - __slots__ = () - - -# pylint: disable=unexpected-keyword-arg, no-value-for-parameter -def threadpooled( - func=None # type: typing.Optional[typing.Callable] -): # type: (...) -> typing.Union[ThreadPooled, typing.Callable[..., concurrent.futures.Future]] - """Post function to ThreadPoolExecutor. - - :param func: function to wrap - :type func: typing.Optional[typing.Callable] - :rtype: typing.Union[ThreadPooled, typing.Callable[..., concurrent.futures.Future]] - """ - if func is None: - return ThreadPooled(func=func) - return ThreadPooled(func=None)(func) - - -def threaded( - name=None, # type: typing.Optional[typing.Union[str, typing.Callable]] - daemon=False, # type: bool - started=False # type: bool -): # type: (...) -> typing.Union[Threaded, typing.Callable[..., threading.Thread]] - """Run function in separate thread. - - :param name: New thread name. - If callable: use as wrapped function. - If none: use wrapped function name. - :type name: typing.Union[None, str, typing.Callable] - :param daemon: Daemonize thread. - :type daemon: bool - :param started: Return started thread - :type started: bool - :rtype: typing.Union[Threaded, typing.Callable[..., threading.Thread]] - """ - if callable(name): - func, name = ( - name, - 'Threaded: ' + getattr(name, '__name__', str(hash(name))) - ) - return Threaded(name=name, daemon=daemon, started=started)(func) - return Threaded(name=name, daemon=daemon, started=started) -# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_threaded2.pyi b/threaded/_threaded2.pyi deleted file mode 100644 index f220b16..0000000 --- a/threaded/_threaded2.pyi +++ /dev/null @@ -1,25 +0,0 @@ -import concurrent.futures -import threading -import typing -from . import _base_threaded - -class ThreadPooled(_base_threaded.BasePooled): ... -class Threaded(_base_threaded.BaseThreaded): ... - - -@typing.overload -def threadpooled(func: typing.Callable) -> typing.Callable[..., concurrent.futures.Future]: ... - -@typing.overload -def threadpooled(func: None = ...) -> ThreadPooled: ... - - -@typing.overload -def threaded( - name: typing.Callable, daemon: bool = ..., started: bool = ... -) -> typing.Callable[..., threading.Thread]: ... - -@typing.overload -def threaded( - name: typing.Optional[str] = ..., daemon: bool = ..., started: bool = ... -) -> Threaded: ... diff --git a/threaded/_threaded3.py b/threaded/_threaded3.py deleted file mode 100644 index 5e60811..0000000 --- a/threaded/_threaded3.py +++ /dev/null @@ -1,409 +0,0 @@ -# Copyright 2017-2018 Alexey Stepanov aka penguinolog -## -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Python 3 threaded implementation. - -Asyncio is supported -""" - -# noinspection PyCompatibility -import asyncio -# noinspection PyCompatibility -import concurrent.futures -import functools -import threading -import typing - -import six - -from . import _base_threaded -from . import _class_decorator -from . import _py3_helpers - -__all__ = ( - 'ThreadPooled', - 'Threaded', - 'AsyncIOTask', - 'threadpooled', - 'threaded', - 'asynciotask', -) - - -class ThreadPooled(_base_threaded.BasePooled): - """Post function to ThreadPoolExecutor.""" - - __slots__ = ( - '__loop_getter', - '__loop_getter_need_context' - ) - - def __init__( - self, - func: typing.Optional[typing.Callable] = None, - *, - loop_getter: typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - ]=None, - loop_getter_need_context: bool = False - ) -> None: - """Wrap function in future and return. - - :param func: function to wrap - :type func: typing.Optional[typing.Callable] - :param loop_getter: Method to get event loop, if wrap in asyncio task - :type loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - :param loop_getter_need_context: Loop getter requires function context - :type loop_getter_need_context: bool - """ - super(ThreadPooled, self).__init__(func=func) - self.__loop_getter = loop_getter - self.__loop_getter_need_context = loop_getter_need_context - - @property - def loop_getter( - self - ) -> typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - ]: - """Loop getter. - - :rtype: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - """ - return self.__loop_getter - - @property - def loop_getter_need_context(self) -> bool: - """Loop getter need execution context. - - :rtype: bool - """ - return self.__loop_getter_need_context - - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable[ - ..., - typing.Union[ - concurrent.futures.Future, - asyncio.Task - ] - ]: - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :return: wrapped coroutine or function - :rtype: typing.Callable[ - ..., - typing.Union[ - concurrent.futures.Future, - asyncio.Task - ] - ] - """ - prepared = _py3_helpers.await_if_required(func) - - # pylint: disable=missing-docstring - # noinspection PyMissingOrEmptyDocstring - @functools.wraps(prepared) - def wrapper( - *args, **kwargs - ) -> typing.Union[ - concurrent.futures.Future, - asyncio.Task - ]: - loop = _py3_helpers.get_loop(self, *args, **kwargs) - - if loop is None: - return self.executor.submit(prepared, *args, **kwargs) - - return loop.run_in_executor( - self.executor, - functools.partial( - prepared, - *args, **kwargs - ) - ) - - # pylint: enable=missing-docstring - return wrapper - - def __repr__(self) -> str: - """For debug purposes.""" - return ( - "<{cls}(" - "{func!r}, " - "loop_getter={self.loop_getter!r}, " - "loop_getter_need_context={self.loop_getter_need_context!r}, " - ") at 0x{id:X}>".format( - cls=self.__class__.__name__, - func=self._func, - self=self, - id=id(self) - ) - ) # pragma: no cover - - -class Threaded(_base_threaded.BaseThreaded): - """Run function in separate thread.""" - - __slots__ = () - - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable[..., threading.Thread]: - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :return: wrapped function - :rtype: typing.Callable[..., threading.Thread] - """ - prepared = _py3_helpers.await_if_required(func) - name = self.name - if name is None: - name = 'Threaded: ' + getattr( - func, - '__name__', - str(hash(func)) - ) - - # pylint: disable=missing-docstring - # noinspection PyMissingOrEmptyDocstring - @six.wraps(prepared) - def wrapper(*args, **kwargs) -> threading.Thread: - thread = threading.Thread( - target=prepared, - name=name, - args=args, - kwargs=kwargs, - daemon=self.daemon - ) - if self.started: - thread.start() - return thread - - # pylint: enable=missing-docstring - return wrapper - - -class AsyncIOTask(_class_decorator.BaseDecorator): - """Wrap to asyncio.Task.""" - - __slots__ = ( - '__loop_getter', - '__loop_getter_need_context', - ) - - def __init__( - self, - func: typing.Optional[typing.Callable] = None, - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ]=asyncio.get_event_loop, - loop_getter_need_context: bool = False - ) -> None: - """Wrap function in future and return. - - :param func: Function to wrap - :type func: typing.Optional[typing.Callable] - :param loop_getter: Method to get event loop, if wrap in asyncio task - :type loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - :param loop_getter_need_context: Loop getter requires function context - :type loop_getter_need_context: bool - """ - super(AsyncIOTask, self).__init__(func=func) - self.__loop_getter = loop_getter - self.__loop_getter_need_context = loop_getter_need_context - - @property - def loop_getter( - self - ) -> typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ]: - """Loop getter. - - :rtype: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - """ - return self.__loop_getter - - @property - def loop_getter_need_context(self) -> bool: - """Loop getter need execution context. - - :rtype: bool - """ - return self.__loop_getter_need_context - - def _get_function_wrapper( - self, - func: typing.Callable - ) -> typing.Callable[..., asyncio.Task]: - """Here should be constructed and returned real decorator. - - :param func: Wrapped function - :type func: typing.Callable - :rtype: typing.Callable[..., asyncio.Task] - """ - # pylint: disable=missing-docstring - # noinspection PyCompatibility,PyMissingOrEmptyDocstring - @functools.wraps(func) - def wrapper(*args, **kwargs) -> asyncio.Task: - loop = _py3_helpers.get_loop(self, *args, **kwargs) - return loop.create_task(func(*args, **kwargs)) - - # pylint: enable=missing-docstring - return wrapper - - def __repr__(self): - """For debug purposes.""" - return ( - "<{cls}(" - "{func!r}, " - "loop_getter={self.loop_getter!r}, " - "loop_getter_need_context={self.loop_getter_need_context!r}, " - ") at 0x{id:X}>".format( - cls=self.__class__.__name__, - func=self._func, - self=self, - id=id(self) - ) - ) # pragma: no cover - - -# pylint: disable=unexpected-keyword-arg, no-value-for-parameter -def threadpooled( - func: typing.Optional[typing.Callable] = None, - *, - loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ]=None, - loop_getter_need_context: bool = False -) -> typing.Union[ThreadPooled, typing.Callable[..., typing.Union[concurrent.futures.Future, asyncio.Task]]]: - """Post function to ThreadPoolExecutor. - - :param func: function to wrap - :type func: typing.Optional[typing.Callable] - :param loop_getter: Method to get event loop, if wrap in asyncio task - :type loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - :param loop_getter_need_context: Loop getter requires function context - :type loop_getter_need_context: bool - :rtype: typing.Union[ThreadPooled, typing.Callable[..., typing.Union[concurrent.futures.Future, asyncio.Task]]] - """ - if func is None: - return ThreadPooled( - func=func, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context - ) - return ThreadPooled( - func=None, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context - )(func) - - -def threaded( - name: typing.Optional[typing.Union[str, typing.Callable]] = None, - daemon: bool = False, - started: bool = False -) -> typing.Union[Threaded, typing.Callable[..., threading.Thread]]: - """Run function in separate thread. - - :param name: New thread name. - If callable: use as wrapped function. - If none: use wrapped function name. - :type name: typing.Union[None, str, typing.Callable] - :param daemon: Daemonize thread. - :type daemon: bool - :param started: Return started thread - :type started: bool - :rtype: typing.Union[Threaded, typing.Callable[..., threading.Thread]] - """ - if callable(name): - func, name = ( - name, - 'Threaded: ' + getattr(name, '__name__', str(hash(name))) - ) - return Threaded(name=name, daemon=daemon, started=started)(func) - return Threaded(name=name, daemon=daemon, started=started) - - -def asynciotask( - func: typing.Optional[typing.Callable] = None, - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ]=asyncio.get_event_loop, - loop_getter_need_context: bool = False -) -> typing.Union[AsyncIOTask, typing.Callable[..., asyncio.Task]]: - """Wrap function in future and return. - - :param func: Function to wrap - :type func: typing.Optional[typing.Callable] - :param loop_getter: Method to get event loop, if wrap in asyncio task - :type loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - :param loop_getter_need_context: Loop getter requires function context - :type loop_getter_need_context: bool - :rtype: typing.Union[AsyncIOTask, typing.Callable[..., asyncio.Task]] - """ - if func is None: - return AsyncIOTask( - func=func, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context - ) - return AsyncIOTask( - func=None, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context - )(func) -# pylint: enable=unexpected-keyword-arg, no-value-for-parameter diff --git a/threaded/_threaded3.pyi b/threaded/_threaded3.pyi deleted file mode 100644 index 3c43a24..0000000 --- a/threaded/_threaded3.pyi +++ /dev/null @@ -1,126 +0,0 @@ -import asyncio -import concurrent.futures -import threading -import typing -from . import _base_threaded, _class_decorator - -class ThreadPooled(_base_threaded.BasePooled): - def __init__( - self, - func: typing.Optional[typing.Callable] = ..., - *, - loop_getter: typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop, - ] - ] = ..., - loop_getter_need_context: bool = ... - ) -> None: ... - - @property - def loop_getter( - self - ) -> typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop - ] - ]: ... - @property - - def loop_getter_need_context(self) -> bool: ... - - def _get_function_wrapper( # type: ignore - self, func: typing.Callable - ) -> typing.Callable[..., typing.Union[concurrent.futures.Future, asyncio.Task]]: ... - -class Threaded(_base_threaded.BaseThreaded): - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable[..., threading.Thread]: ... - -class AsyncIOTask(_class_decorator.BaseDecorator): - def __init__( - self, - func: typing.Optional[typing.Callable] = ..., - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop - ] = ..., - loop_getter_need_context: bool = ... - ) -> None: ... - - @property - def loop_getter( - self - ) -> typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop - ]: ... - - @property - def loop_getter_need_context(self) -> bool: ... - - def _get_function_wrapper(self, func: typing.Callable) -> typing.Callable[..., asyncio.Task]: ... - - -@typing.overload -def threadpooled( - func: typing.Callable, - *, - loop_getter: None = ..., - loop_getter_need_context: bool = ... -) -> typing.Callable[..., concurrent.futures.Future]: ... - -@typing.overload -def threadpooled( - func: typing.Callable, - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ], - loop_getter_need_context: bool = ... -) -> typing.Callable[..., asyncio.Task]: ... - -@typing.overload -def threadpooled( - func: None = ..., - *, - loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] = ..., - loop_getter_need_context: bool = ... -) -> ThreadPooled: ... - - -@typing.overload -def threaded( - name: typing.Callable, daemon: bool = ..., started: bool = ... -) -> typing.Callable[..., threading.Thread]: ... - -@typing.overload -def threaded( - name: typing.Optional[str] = ..., daemon: bool = ..., started: bool = ... -) -> Threaded: ... - -@typing.overload -def asynciotask( - func: None = ..., - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] = asyncio.get_event_loop, - loop_getter_need_context: bool = ... -) -> AsyncIOTask: ... - -@typing.overload -def asynciotask( - func: typing.Callable, - *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] = asyncio.get_event_loop, - loop_getter_need_context: bool = ... -) -> typing.Callable[..., asyncio.Task]: ... diff --git a/threaded/_threadpooled.py b/threaded/_threadpooled.py new file mode 100644 index 0000000..77a08d1 --- /dev/null +++ b/threaded/_threadpooled.py @@ -0,0 +1,162 @@ +# Copyright 2017-2018 Alexey Stepanov aka penguinolog +## +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Python 2 threaded implementation. + +Uses backport of concurrent.futures. +""" + +from __future__ import absolute_import + +# noinspection PyCompatibility +import concurrent.futures +import typing # noqa # pylint: disable=unused-import + +import six + +from . import _base_threaded + +__all__ = ( + 'ThreadPooled', + 'threadpooled', +) + + +class ThreadPooled(_base_threaded.APIPooled): + """Post function to ThreadPoolExecutor.""" + + __slots__ = () + + __executor = None # type: typing.Optional[ThreadPoolExecutor] + + @classmethod + def configure( + cls, # type: typing.Type[ThreadPooled] + max_workers=None, # type: typing.Optional[int] + ): # type: (...) -> None + """Pool executor create and configure. + + :param max_workers: Maximum workers + :type max_workers: typing.Optional[int] + """ + if isinstance(cls.__executor, ThreadPoolExecutor): + if cls.__executor.max_workers == max_workers: + return + cls.__executor.shutdown() + + cls.__executor = ThreadPoolExecutor( + max_workers=max_workers, + ) + + @classmethod + def shutdown(cls): # type: (typing.Type[ThreadPooled]) -> None + """Shutdown executor.""" + if cls.__executor is not None: + cls.__executor.shutdown() + + @property + def executor(self): # type: () -> ThreadPoolExecutor + """Executor instance. + + :rtype: ThreadPoolExecutor + """ + if ( + not isinstance(self.__executor, ThreadPoolExecutor) or + self.__executor.is_shutdown + ): + self.configure() + return self.__executor # type: ignore + + def _get_function_wrapper( + self, + func # type: typing.Callable + ): # type: (...) -> typing.Callable[..., concurrent.futures.Future] + """Here should be constructed and returned real decorator. + + :param func: Wrapped function + :type func: typing.Callable + :return: wrapped function + :rtype: typing.Callable[..., concurrent.futures.Future] + """ + # pylint: disable=missing-docstring + # noinspection PyMissingOrEmptyDocstring + @six.wraps(func) + def wrapper( + *args, # type: typing.Any + **kwargs # type: typing.Any + ): # type: (...) -> concurrent.futures.Future + return self.executor.submit(func, *args, **kwargs) + + # pylint: enable=missing-docstring + return wrapper + + +# pylint: disable=unexpected-keyword-arg, no-value-for-parameter +def threadpooled( + func=None # type: typing.Optional[typing.Callable] +): # type: (...) -> typing.Union[ThreadPooled, typing.Callable[..., concurrent.futures.Future]] + """Post function to ThreadPoolExecutor. + + :param func: function to wrap + :type func: typing.Optional[typing.Callable] + :rtype: typing.Union[ThreadPooled, typing.Callable[..., concurrent.futures.Future]] + """ + if func is None: + return ThreadPooled(func=func) + return ThreadPooled(func=None)(func) # type: ignore +# pylint: enable=unexpected-keyword-arg, no-value-for-parameter + + +class ThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor): + """Provide readers for protected attributes. + + Simply extend concurrent.futures.ThreadPoolExecutor. + """ + + __slots__ = () + + def __init__( + self, + max_workers=None # type: typing.Optional[int] + ): # type: (...) -> None + """Override init due to difference between Python <3.5 and 3.5+. + + :param max_workers: Maximum workers allowed. + If none: cpu_count() or 1) * 5 + :type max_workers: typing.Optional[int] + """ + if max_workers is None: # Use 3.5+ behavior + max_workers = (_base_threaded.cpu_count() or 1) * 5 + super( + ThreadPoolExecutor, + self + ).__init__( + max_workers=max_workers, + ) + + @property + def max_workers(self): # type: () -> int + """MaxWorkers. + + :rtype: int + """ + return self._max_workers # type: ignore + + @property + def is_shutdown(self): # type: () -> bool + """Executor shutdown state. + + :rtype: bool + """ + return self._shutdown # type: ignore diff --git a/tools/build-wheels.sh b/tools/build-wheels.sh deleted file mode 100755 index 032245f..0000000 --- a/tools/build-wheels.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash -PYTHON_VERSIONS="cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m" - -# Avoid creation of __pycache__/*.py[c|o] -export PYTHONDONTWRITEBYTECODE=1 - -package_name="$1" -if [ -z "$package_name" ] -then - &>2 echo "Please pass package name as a first argument of this script ($0)" - exit 1 -fi - -arch=`uname -m` - -# Clean-up -rm -rf /io/.tox -rm -rf /io/*.egg-info -rm -rf /io/.pytest_cache -find -name *.py[co] -delete - -echo -echo -echo "Compile wheels" -for PYTHON in ${PYTHON_VERSIONS}; do - /opt/python/${PYTHON}/bin/pip install -U pip setuptools wheel - /opt/python/${PYTHON}/bin/pip install -r /io/build_requirements.txt - /opt/python/${PYTHON}/bin/pip wheel /io/ -w /io/dist/ - cd /io - /opt/python/${PYTHON}/bin/python setup.py bdist_wheel -done - -echo -echo -echo "Bundle external shared libraries into the wheels" -for whl in /io/dist/${package_name}*${arch}.whl; do - echo "Repairing $whl..." - auditwheel repair "$whl" -w /io/dist/ -done - -echo "Cleanup OS specific wheels" -rm -fv /io/dist/*-linux_*.whl - -echo -echo -echo "Install packages and test" -echo "dist directory:" -ls /io/dist - -for PYTHON in ${PYTHON_VERSIONS}; do - echo - echo -n "Test $PYTHON: $package_name " - /opt/python/${PYTHON}/bin/python -c "import platform;print(platform.platform())" - /opt/python/${PYTHON}/bin/pip install "$package_name" --no-index -f file:///io/dist - /opt/python/${PYTHON}/bin/pip install pytest - /opt/python/${PYTHON}/bin/py.test -vv /io/test -done - -find /io/dist/ -type f -not -name "*$package_name*" -delete -rm -rf /io/build -rm -rf /io/*.egg-info -rm -rf /io/.pytest_cache -chmod -v a+rwx /io/dist -chmod -v a+rw /io/dist/* diff --git a/tools/run_docker.sh b/tools/run_docker.sh deleted file mode 100755 index 1ea8c3a..0000000 --- a/tools/run_docker.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -package_name="$1" -if [ -z "$package_name" ] -then - &>2 echo "Please pass package name as a first argument of this script ($0)" - exit 1 -fi - -manylinux1_image_prefix="quay.io/pypa/manylinux1_" -dock_ext_args="" -declare -A docker_pull_pids=() # This syntax requires at least bash v4 - -for arch in x86_64 i686 -do - docker pull "${manylinux1_image_prefix}${arch}" & - docker_pull_pids[$arch]=$! -done - -for arch in x86_64 i686 -do - echo - echo - arch_pull_pid=${docker_pull_pids[$arch]} - echo waiting for docker pull pid $arch_pull_pid to complete downloading container for $arch arch... - wait $arch_pull_pid # await for docker image for current arch to be pulled from hub - [ $arch == "i686" ] && dock_ext_args="linux32" - - echo Building wheel for $arch arch - docker run --rm -v `pwd`:/io "${manylinux1_image_prefix}${arch}" $dock_ext_args /io/tools/build-wheels.sh "$package_name" - - dock_ext_args="" # Reset docker args, just in case -done diff --git a/tox.ini b/tox.ini index fe63f12..802711e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] minversion = 2.0 -envlist = pep8, pep257, py{27,34,35,36,37,py,py3}, pylint, docs, bandit, py{34,35,36,37}-nocov, mypy +envlist = pep8, pep257, py{27,py}, pylint, docs, bandit, mypy skipsdist = True skip_missing_interpreters = True @@ -18,13 +18,14 @@ deps = sphinx pytest pytest-cov + pytest-html pytest-sugar py{34,35,36}-nocov: Cython -r{toxinidir}/CI_REQUIREMENTS.txt py{27,py}: mock commands = - py.test -vv --junitxml=unit_result.xml --cov-config .coveragerc --cov-report html --cov=threaded {posargs:test} + py.test -vv --junitxml=unit_result.xml --cov-config .coveragerc --cov-report html --self-contained-html --html=report.html --cov=threaded {posargs:test} coverage report --fail-under 80 [testenv:py34-nocov] @@ -85,6 +86,7 @@ usedevelop = False commands = pip install ./ -vvv -U [testenv:pylint] +usedevelop = False deps = pylint < 2.0 -r{toxinidir}/CI_REQUIREMENTS.txt @@ -109,6 +111,7 @@ count = True max-line-length = 120 [testenv:docs] +usedevelop = False deps = sphinx commands = python setup.py build_sphinx @@ -121,6 +124,7 @@ deps = commands = python setup.py build_sphinx upload_sphinx [testenv:bandit] +usedevelop = False deps = bandit commands = bandit -r threaded @@ -131,6 +135,7 @@ deps = commands = pipdeptree [testenv:mypy] +usedevelop = False deps = mypy>=0.620 -r{toxinidir}/CI_REQUIREMENTS.txt From a6d43d3988eb12ea0c967c3e1b94f92f73fc26fa Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Tue, 4 Sep 2018 14:50:19 +0200 Subject: [PATCH 2/4] update README Signed-off-by: Alexey Stepanov --- README.rst | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 6429ccf..485386a 100644 --- a/README.rst +++ b/README.rst @@ -38,13 +38,9 @@ Pros: :: Python 2.7 - Python 3.4 - Python 3.5 - Python 3.6 - Python 3.7 PyPy - PyPy3 3.5+ - Jyton 2.7 + +.. note:: Update to version 2.0+ for usage with python 3.4+. This version is for legacy python and no new features are planned. Decorators: From 068bc95af948a2f4c214fabf6865b87a1c585f6c Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Tue, 4 Sep 2018 15:07:40 +0200 Subject: [PATCH 3/4] cleanup tox.ini Signed-off-by: Alexey Stepanov --- tox.ini | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/tox.ini b/tox.ini index 802711e..add6b00 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,6 @@ deps = pytest-cov pytest-html pytest-sugar - py{34,35,36}-nocov: Cython -r{toxinidir}/CI_REQUIREMENTS.txt py{27,py}: mock @@ -28,45 +27,12 @@ commands = py.test -vv --junitxml=unit_result.xml --cov-config .coveragerc --cov-report html --self-contained-html --html=report.html --cov=threaded {posargs:test} coverage report --fail-under 80 -[testenv:py34-nocov] -usedevelop = False -commands = - python setup.py bdist_wheel - pip install threaded --no-index -f dist - py.test -vv {posargs:test} - -[testenv:py35-nocov] -usedevelop = False -commands = - python setup.py bdist_wheel - pip install threaded --no-index -f dist - py.test -vv {posargs:test} - -[testenv:py36-nocov] -usedevelop = False -commands = - python setup.py bdist_wheel - pip install threaded --no-index -f dist - py.test -vv {posargs:test} - -[testenv:py37-nocov] -usedevelop = False -commands = - python setup.py bdist_wheel - pip install threaded --no-index -f dist - py.test -vv {posargs:test} - [testenv:venv] commands = {posargs:} [tox:travis] 2.7 = install, py27, -3.4 = py34, -3.5 = py35, -3.6 = py36, -3.7 = py37, pypy = install, pypy, -pypy3 = install, pypy3, [testenv:pep8] deps = From eb11aa1bccd3c96c7dbe90b6d91bb1dd0df110e4 Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Tue, 4 Sep 2018 16:54:13 +0200 Subject: [PATCH 4/4] remove cythonize code from setup.py Signed-off-by: Alexey Stepanov --- setup.py | 64 +------------------------------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/setup.py b/setup.py index 6a4ff2f..e1bd06e 100644 --- a/setup.py +++ b/setup.py @@ -18,10 +18,7 @@ import ast import collections -from distutils.command import build_ext -import distutils.errors import os.path -import shutil import setuptools @@ -40,53 +37,6 @@ long_description = f.read() -ext_modules = [] - - -class BuildFailed(Exception): - """For install clear scripts.""" - pass - - -class AllowFailRepair(build_ext.build_ext): - """This class allows C extension building to fail and repairs init.""" - - def run(self): - """Run.""" - try: - build_ext.build_ext.run(self) - - # Copy __init__.py back to repair package. - build_dir = os.path.abspath(self.build_lib) - root_dir = os.path.abspath(os.path.join(__file__, '..')) - target_dir = build_dir if not self.inplace else root_dir - - src_file = os.path.join('threaded', '__init__.py') - - src = os.path.join(root_dir, src_file) - dst = os.path.join(target_dir, src_file) - - if src != dst: - shutil.copyfile(src, dst) - except ( - distutils.errors.DistutilsPlatformError, - getattr(globals()['__builtins__'], 'FileNotFoundError', OSError) - ): - raise BuildFailed() - - def build_extension(self, ext): - """build_extension.""" - try: - build_ext.build_ext.build_extension(self, ext) - except ( - distutils.errors.CCompilerError, - distutils.errors.DistutilsExecError, - distutils.errors.DistutilsPlatformError, - ValueError - ): - raise BuildFailed() - - # noinspection PyUnresolvedReferences def get_simple_vars_from_src(src): """Get simple (string/number/boolean and None) assigned values from source. @@ -190,7 +140,6 @@ def get_simple_vars_from_src(src): 'pooling', 'multithreading', 'threading', - 'asyncio', 'gevent', 'development', ] @@ -231,15 +180,4 @@ def get_simple_vars_from_src(src): }, ) -try: - setuptools.setup(**setup_args) -except BuildFailed: - print( - '*' * 80 + '\n' - '* Build Failed!\n' - '* Use clear scripts version.\n' - '*' * 80 + '\n' - ) - del setup_args['ext_modules'] - del setup_args['cmdclass'] - setuptools.setup(**setup_args) +setuptools.setup(**setup_args)