diff --git a/README.rst b/README.rst index 4767cfd..08f02f0 100644 --- a/README.rst +++ b/README.rst @@ -16,6 +16,8 @@ threaded :target: https://pypi.python.org/pypi/threaded .. image:: https://img.shields.io/github/license/python-useful-helpers/threaded.svg :target: https://raw.githubusercontent.com/python-useful-helpers/threaded/master/LICENSE +.. image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/ambv/black threaded is a set of decorators, which wrap functions in: diff --git a/pyproject.toml b/pyproject.toml index 1c48e2c..708fb53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,3 +5,7 @@ requires = [ "setuptools >= 21.0.0,!=24.0.0,!=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", # PSF/ZPL "wheel", ] + +[tool.black] +line-length = 120 +safe = true diff --git a/setup.cfg b/setup.cfg index a7c8525..524af15 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,13 +38,23 @@ exclude = __init__.py, docs ignore = + E203 +# whitespace before ':' show-pep8 = True show-source = True count = True max-line-length = 120 [pydocstyle] -ignore = D401, D203, D213 +ignore = + D401, + D202, + D203, + D213 +# First line should be in imperative mood; try rephrasing +# No blank lines allowed after function docstring +# 1 blank line required before class docstring +# Multi-line docstring summary should start at the second line [aliases] test=pytest diff --git a/setup.py b/setup.py index 1611f29..11baba6 100644 --- a/setup.py +++ b/setup.py @@ -23,61 +23,59 @@ import sys try: + # noinspection PyPackageRequirements from Cython.Build import cythonize + + # noinspection PyPackageRequirements import gevent except ImportError: gevent = cythonize = None import setuptools -with open( - os.path.join( - os.path.dirname(__file__), - 'threaded', '__init__.py' - ) -) as f: +with open(os.path.join(os.path.dirname(__file__), "threaded", "__init__.py")) as f: source = f.read() -with open('requirements.txt') as f: +with open("requirements.txt") as f: required = f.read().splitlines() -with open('README.rst',) as f: +with open("README.rst") as f: long_description = f.read() def _extension(modpath): """Make setuptools.Extension.""" - return setuptools.Extension(modpath, [modpath.replace('.', '/') + '.py']) + return setuptools.Extension(modpath, [modpath.replace(".", "/") + ".py"]) requires_optimization = [ - _extension('threaded._class_decorator'), - _extension('threaded._base_threaded'), - _extension('threaded._asynciotask'), - _extension('threaded._threaded'), - _extension('threaded._threadpooled'), - _extension('threaded._gthreadpooled'), + _extension("threaded._class_decorator"), + _extension("threaded._base_threaded"), + _extension("threaded._asynciotask"), + _extension("threaded._threaded"), + _extension("threaded._threadpooled"), + _extension("threaded._gthreadpooled"), ] -if 'win32' != sys.platform: - requires_optimization.append( - _extension('threaded.__init__') - ) +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, +# noinspection PyCallingNonCallable +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 else [] + if cythonize is not None + else [] +) class BuildFailed(Exception): """For install clear scripts.""" + pass @@ -91,10 +89,10 @@ def 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__, '..')) + 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_file = os.path.join("threaded", "__init__.py") src = os.path.join(root_dir, src_file) dst = os.path.join(target_dir, src_file) @@ -103,7 +101,7 @@ def run(self): shutil.copyfile(src, dst) except ( distutils.errors.DistutilsPlatformError, - getattr(globals()['__builtins__'], 'FileNotFoundError', OSError) + getattr(globals()["__builtins__"], "FileNotFoundError", OSError), ): raise BuildFailed() @@ -115,7 +113,7 @@ def build_extension(self, ext): distutils.errors.CCompilerError, distutils.errors.DistutilsExecError, distutils.errors.DistutilsPlatformError, - ValueError + ValueError, ): raise BuildFailed() @@ -167,11 +165,7 @@ def get_simple_vars_from_src(src): >>> get_simple_vars_from_src(multiple_assign) OrderedDict([('e', 1), ('f', 1), ('g', 1)]) """ - ast_data = ( - ast.Str, ast.Num, - ast.List, ast.Set, ast.Dict, ast.Tuple, - ast.Bytes, ast.NameConstant, - ) + ast_data = (ast.Str, ast.Num, ast.List, ast.Set, ast.Dict, ast.Tuple, ast.Bytes, ast.NameConstant) tree = ast.parse(src) @@ -183,9 +177,7 @@ def get_simple_vars_from_src(src): try: if isinstance(node.value, ast_data): value = ast.literal_eval(node.value) - elif isinstance( # NameConstant in python < 3.4 - node.value, ast.Name - ) and isinstance( + elif isinstance(node.value, ast.Name) and isinstance( # NameConstant in python < 3.4 node.value.ctx, ast.Load # Read constant ): value = ast.literal_eval(node.value) @@ -194,11 +186,7 @@ def get_simple_vars_from_src(src): except ValueError: continue for tgt in node.targets: - if isinstance( - tgt, ast.Name - ) and isinstance( - tgt.ctx, ast.Store - ): + if isinstance(tgt, ast.Name) and isinstance(tgt.ctx, ast.Store): result[tgt.id] = value return result @@ -206,47 +194,35 @@ def get_simple_vars_from_src(src): variables = get_simple_vars_from_src(source) classifiers = [ - 'Development Status :: 5 - Production/Stable', - - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries :: Python Modules', - - 'License :: OSI Approved :: Apache Software License', - - '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', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: Apache Software License", + "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", ] -keywords = [ - 'pooling', - 'multithreading', - 'threading', - 'asyncio', - 'gevent', - 'development', -] +keywords = ["pooling", "multithreading", "threading", "asyncio", "gevent", "development"] setup_args = dict( - name='threaded', - author=variables['__author__'], - author_email=variables['__author_email__'], - maintainer=', '.join( - '{name} <{email}>'.format(name=name, email=email) - for name, email in variables['__maintainers__'].items() + name="threaded", + author=variables["__author__"], + author_email=variables["__author_email__"], + maintainer=", ".join( + "{name} <{email}>".format(name=name, email=email) for name, email in variables["__maintainers__"].items() ), - url=variables['__url__'], - version=variables['__version__'], - license=variables['__license__'], - description=variables['__description__'], + url=variables["__url__"], + version=variables["__version__"], + license=variables["__license__"], + description=variables["__description__"], long_description=long_description, classifiers=classifiers, keywords=keywords, - python_requires='>=3.4', + python_requires=">=3.4", # 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 @@ -254,31 +230,20 @@ def get_simple_vars_from_src(src): # Blacklist setuptools 34.0.0-34.3.2 due to https://github.com/pypa/setuptools/issues/951 # Blacklist setuptools 36.2.0 due to https://github.com/pypa/setuptools/issues/1086 setup_requires="setuptools >= 21.0.0,!=24.0.0," - "!=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={ - 'gevent': [ - 'gevent >= 1.2.2' - ], - }, + "!=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={"gevent": ["gevent >= 1.2.2"]}, install_requires=required, - package_data={ - 'threaded': ['py.typed'], - }, + package_data={"threaded": ["py.typed"]}, ) if cythonize is not None: - setup_args['ext_modules'] = ext_modules - setup_args['cmdclass'] = dict(build_ext=AllowFailRepair) + setup_args["ext_modules"] = ext_modules + setup_args["cmdclass"] = dict(build_ext=AllowFailRepair) 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'] + 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) diff --git a/test/async_syntax/test_gevent_threadpooled_async.py b/test/async_syntax/test_gevent_threadpooled_async.py index 087c08d..e7f6f5e 100644 --- a/test/async_syntax/test_gevent_threadpooled_async.py +++ b/test/async_syntax/test_gevent_threadpooled_async.py @@ -24,7 +24,7 @@ import threaded -@unittest.skipIf(gevent is None, 'No gevent') +@unittest.skipIf(gevent is None, "No gevent") class TestThreadPooled(unittest.TestCase): def tearDown(self): threaded.GThreadPooled.shutdown() diff --git a/test/async_syntax/test_pooled_async.py b/test/async_syntax/test_pooled_async.py index 3a40e13..f5a0cc6 100644 --- a/test/async_syntax/test_pooled_async.py +++ b/test/async_syntax/test_pooled_async.py @@ -66,16 +66,11 @@ def test_thread_pooled_loop_getter_context(self): def loop_getter(target): return target - @threaded.threadpooled( - loop_getter=loop_getter, - loop_getter_need_context=True - ) + @threaded.threadpooled(loop_getter=loop_getter, loop_getter_need_context=True) async def test(*args, **kwargs): return threading.current_thread().name - pooled_name = loop.run_until_complete( - asyncio.wait_for(test(loop), 1) - ) + pooled_name = loop.run_until_complete(asyncio.wait_for(test(loop), 1)) self.assertNotEqual(pooled_name, threading.current_thread().name) @@ -83,17 +78,17 @@ class TestAsyncIOTask(unittest.TestCase): def test_default(self): @threaded.asynciotask async def test(): - return 'test' + return "test" loop = asyncio.get_event_loop() res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') + self.assertEqual(res, "test") def test_construct(self): @threaded.asynciotask() async def test(): - return 'test' + return "test" loop = asyncio.get_event_loop() res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') + self.assertEqual(res, "test") diff --git a/test/test_gevent_threadpooled.py b/test/test_gevent_threadpooled.py index ef06000..369cfff 100644 --- a/test/test_gevent_threadpooled.py +++ b/test/test_gevent_threadpooled.py @@ -25,7 +25,7 @@ import threaded -@unittest.skipIf(gevent is None, 'No gevent') +@unittest.skipIf(gevent is None, "No gevent") class TestThreadPooled(unittest.TestCase): def tearDown(self): threaded.GThreadPooled.shutdown() @@ -50,10 +50,7 @@ def test_thread_pooled_config(self): thread_pooled = threaded.gthreadpooled() thread_pooled.configure() - self.assertEqual( - thread_pooled.executor.maxsize, - (os.cpu_count() or 1) * 5 - ) + self.assertEqual(thread_pooled.executor.maxsize, (os.cpu_count() or 1) * 5) thread_pooled.configure(max_workers=2) diff --git a/test/test_pooled.py b/test/test_pooled.py index 3062460..cf20f84 100644 --- a/test/test_pooled.py +++ b/test/test_pooled.py @@ -43,10 +43,7 @@ def test(): def test_thread_pooled_config(self): thread_pooled = threaded.threadpooled() - self.assertEqual( - thread_pooled.executor.max_workers, - (cpu_count() or 1) * 5 - ) + self.assertEqual(thread_pooled.executor.max_workers, (cpu_count() or 1) * 5) thread_pooled.configure(max_workers=2) diff --git a/test/test_pooled_coroutine.py b/test/test_pooled_coroutine.py index 43cfe38..02e111c 100644 --- a/test/test_pooled_coroutine.py +++ b/test/test_pooled_coroutine.py @@ -70,17 +70,12 @@ def test_thread_pooled_loop_getter_context(self): def loop_getter(target): return target - @threaded.threadpooled( - loop_getter=loop_getter, - loop_getter_need_context=True - ) + @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) - ) + pooled_name = loop.run_until_complete(asyncio.wait_for(test(loop), 1)) self.assertNotEqual(pooled_name, threading.current_thread().name) @@ -89,18 +84,18 @@ def test_default(self): @threaded.asynciotask @asyncio.coroutine def test(): - return 'test' + return "test" loop = asyncio.get_event_loop() res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') + self.assertEqual(res, "test") def test_construct(self): @threaded.asynciotask() @asyncio.coroutine def test(): - return 'test' + return "test" loop = asyncio.get_event_loop() res = loop.run_until_complete(asyncio.wait_for(test(), 1)) - self.assertEqual(res, 'test') + self.assertEqual(res, "test") diff --git a/test/test_threaded.py b/test/test_threaded.py index 24f2474..2b11f9f 100644 --- a/test/test_threaded.py +++ b/test/test_threaded.py @@ -23,10 +23,11 @@ def test_add_basic(self): @threaded.threaded def func_test(): pass + # pylint: disable=assignment-from-no-return test_thread = func_test() # pylint: enable=assignment-from-no-return - self.assertEqual(test_thread.name, 'Threaded: func_test') + self.assertEqual(test_thread.name, "Threaded: func_test") self.assertFalse(test_thread.daemon) self.assertFalse(test_thread.isAlive()) @@ -38,19 +39,19 @@ def func_test(): # pylint: disable=assignment-from-no-return test_thread = func_test() # pylint: enable=assignment-from-no-return - self.assertEqual(test_thread.name, 'Threaded: func_test') + self.assertEqual(test_thread.name, "Threaded: func_test") self.assertFalse(test_thread.daemon) self.assertFalse(test_thread.isAlive()) def test_name(self): - @threaded.threaded(name='test name') + @threaded.threaded(name="test name") def func_test(): pass # pylint: disable=assignment-from-no-return test_thread = func_test() # pylint: enable=assignment-from-no-return - self.assertEqual(test_thread.name, 'test name') + self.assertEqual(test_thread.name, "test name") self.assertFalse(test_thread.daemon) self.assertFalse(test_thread.isAlive()) @@ -62,11 +63,11 @@ def func_test(): # pylint: disable=assignment-from-no-return test_thread = func_test() # pylint: enable=assignment-from-no-return - self.assertEqual(test_thread.name, 'Threaded: func_test') + self.assertEqual(test_thread.name, "Threaded: func_test") self.assertTrue(test_thread.daemon) self.assertFalse(test_thread.isAlive()) - @mock.patch('threading.Thread', autospec=True) + @mock.patch("threading.Thread", autospec=True) def test_started(self, thread): @threaded.threaded(started=True) def func_test(): diff --git a/threaded/__init__.py b/threaded/__init__.py index 082b9e5..51baa69 100644 --- a/threaded/__init__.py +++ b/threaded/__init__.py @@ -30,25 +30,25 @@ __all__ = ( - 'ThreadPooled', 'Threaded', - 'threadpooled', 'threaded', - 'AsyncIOTask', 'asynciotask' + "ThreadPooled", + "Threaded", + "threadpooled", + "threaded", + "AsyncIOTask", + "asynciotask", ) # type: typing.Tuple[str, ...] if GThreadPooled is not None: # pragma: no cover - __all__ += ( - 'GThreadPooled', - 'gthreadpooled' - ) + __all__ += ("GThreadPooled", "gthreadpooled") -__version__ = '2.0.2' +__version__ = "2.0.2" __author__ = "Alexey Stepanov" -__author_email__ = 'penguinolog@gmail.com' +__author_email__ = "penguinolog@gmail.com" __maintainers__ = { - 'Alexey Stepanov': 'penguinolog@gmail.com', - 'Antonio Esposito': 'esposito.cloud@gmail.com', - 'Dennis Dmitriev': 'dis-xcom@gmail.com', + "Alexey Stepanov": "penguinolog@gmail.com", + "Antonio Esposito": "esposito.cloud@gmail.com", + "Dennis Dmitriev": "dis-xcom@gmail.com", } -__url__ = 'https://github.com/python-useful-helpers/threaded' +__url__ = "https://github.com/python-useful-helpers/threaded" __description__ = "Decorators for running functions in Thread/ThreadPool/IOLoop" __license__ = "Apache License, Version 2.0" diff --git a/threaded/_asynciotask.py b/threaded/_asynciotask.py index b415731..77834a8 100644 --- a/threaded/_asynciotask.py +++ b/threaded/_asynciotask.py @@ -20,27 +20,20 @@ from . import _class_decorator -__all__ = ( - 'AsyncIOTask', - 'asynciotask', -) +__all__ = ("AsyncIOTask", "asynciotask") class AsyncIOTask(_class_decorator.BaseDecorator): """Wrap to asyncio.Task.""" - __slots__ = ( - '__loop_getter', - '__loop_getter_need_context', - ) + __slots__ = ("__loop_getter", "__loop_getter_need_context") def __init__( self, - func: typing.Optional[typing.Callable[..., 'typing.Awaitable']] = None, + func: typing.Optional[typing.Callable[..., "typing.Awaitable"]] = None, *, loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop + typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop ] = asyncio.get_event_loop, loop_getter_need_context: bool = False ) -> None: @@ -61,12 +54,7 @@ def __init__( self.__loop_getter_need_context = loop_getter_need_context @property - def loop_getter( - self - ) -> typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ]: + def loop_getter(self) -> typing.Union[typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop]: """Loop getter. :rtype: typing.Union[typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop] @@ -81,11 +69,7 @@ def loop_getter_need_context(self) -> bool: """ return self.__loop_getter_need_context - def get_loop( - self, - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> asyncio.AbstractEventLoop: + def get_loop(self, *args, **kwargs): # type: (typing.Any, typing.Any) -> asyncio.AbstractEventLoop """Get event loop in decorator class.""" if callable(self.loop_getter): if self.loop_getter_need_context: @@ -94,8 +78,7 @@ def get_loop( return self.loop_getter def _get_function_wrapper( - self, - func: typing.Callable[..., 'typing.Awaitable'] + self, func: typing.Callable[..., "typing.Awaitable"] ) -> typing.Callable[..., asyncio.Task]: """Here should be constructed and returned real decorator. @@ -106,19 +89,14 @@ def _get_function_wrapper( """ # noinspection PyMissingOrEmptyDocstring @functools.wraps(func) # pylint: disable=missing-docstring - def wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> asyncio.Task: + def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> asyncio.Task loop = self.get_loop(*args, **kwargs) return loop.create_task(func(*args, **kwargs)) return wrapper def __call__( # pylint: disable=useless-super-delegation - self, - *args: typing.Union[typing.Callable[..., 'typing.Awaitable'], typing.Any], - **kwargs: typing.Any + self, *args: typing.Union[typing.Callable[..., "typing.Awaitable"], typing.Any], **kwargs: typing.Any ) -> typing.Union[asyncio.Task, typing.Callable[..., asyncio.Task]]: """Callable instance.""" return super(AsyncIOTask, self).__call__(*args, **kwargs) # type: ignore @@ -130,12 +108,7 @@ def __repr__(self) -> str: "{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) - ) + ") at 0x{id:X}>".format(cls=self.__class__.__name__, func=self._func, self=self, id=id(self)) ) # pragma: no cover @@ -145,8 +118,7 @@ def asynciotask( func: None = None, *, loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop + typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop ] = asyncio.get_event_loop, loop_getter_need_context: bool = False ) -> AsyncIOTask: @@ -156,11 +128,10 @@ def asynciotask( @typing.overload # noqa: F811 def asynciotask( - func: typing.Callable[..., 'typing.Awaitable'], + func: typing.Callable[..., "typing.Awaitable"], *, loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop + typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop ] = asyncio.get_event_loop, loop_getter_need_context: bool = False ) -> typing.Callable[..., asyncio.Task]: @@ -170,11 +141,10 @@ def asynciotask( # pylint: enable=unused-argument def asynciotask( # noqa: F811 - func: typing.Optional[typing.Callable[..., 'typing.Awaitable']] = None, + func: typing.Optional[typing.Callable[..., "typing.Awaitable"]] = None, *, loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop + typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop ] = asyncio.get_event_loop, loop_getter_need_context: bool = False ) -> typing.Union[AsyncIOTask, typing.Callable[..., asyncio.Task]]: @@ -193,14 +163,10 @@ def asynciotask( # noqa: F811 :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=func, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context) return AsyncIOTask( # type: ignore - func=None, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context + func=None, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context )(func) + + # pylint: enable=function-redefined diff --git a/threaded/_base_threaded.py b/threaded/_base_threaded.py index 28e0e33..55a09f2 100644 --- a/threaded/_base_threaded.py +++ b/threaded/_base_threaded.py @@ -20,9 +20,7 @@ from . import _class_decorator -__all__ = ( - 'APIPooled', -) +__all__ = ("APIPooled",) class APIPooled(_class_decorator.BaseDecorator, metaclass=abc.ABCMeta): @@ -34,10 +32,7 @@ class APIPooled(_class_decorator.BaseDecorator, metaclass=abc.ABCMeta): @classmethod @abc.abstractmethod - def configure( - cls: typing.Type['APIPooled'], - max_workers: typing.Optional[int] = None, - ) -> None: + def configure(cls: typing.Type["APIPooled"], max_workers: typing.Optional[int] = None) -> None: """Pool executor create and configure. :param max_workers: Maximum workers @@ -47,7 +42,7 @@ def configure( @classmethod @abc.abstractmethod - def shutdown(cls: typing.Type['APIPooled']) -> None: + def shutdown(cls: typing.Type["APIPooled"]) -> None: """Shutdown executor.""" raise NotImplementedError() # pragma: no cover diff --git a/threaded/_class_decorator.py b/threaded/_class_decorator.py index 5b3d11b..9bf0d02 100644 --- a/threaded/_class_decorator.py +++ b/threaded/_class_decorator.py @@ -63,10 +63,7 @@ class BaseDecorator(metaclass=abc.ABCMeta): False """ - def __init__( - self, - func: typing.Optional[typing.Callable] = None - ) -> None: + def __init__(self, func: typing.Optional[typing.Callable] = None) -> None: """Decorator. :param func: function to wrap @@ -81,9 +78,7 @@ def __init__( # pylint: enable=assigning-non-slot @property - def _func( - self - ) -> typing.Optional[typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]]]: + def _func(self) -> typing.Optional[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]]]: """Get wrapped function. :rtype: typing.Optional[typing.Callable[..., typing.Union[typing.Awaitable, typing.Any]]] @@ -92,8 +87,7 @@ def _func( @abc.abstractmethod def _get_function_wrapper( - self, - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] + self, func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] ) -> typing.Callable: """Here should be constructed and returned real decorator. @@ -105,10 +99,7 @@ def _get_function_wrapper( def __call__( self, - *args: typing.Union[ - typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], - typing.Any - ], + *args: typing.Union[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], typing.Any], **kwargs: typing.Any ) -> typing.Any: """Main decorator getter.""" @@ -126,14 +117,12 @@ def __call__( @staticmethod def _await_if_required( - target: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] + target: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] ) -> typing.Callable[..., typing.Any]: """Await result if coroutine was returned.""" + @functools.wraps(target) - def wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> typing.Any: + def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> typing.Any """Decorator/wrapper.""" result = target(*args, **kwargs) if asyncio.iscoroutine(result): @@ -147,14 +136,13 @@ def wrapper( def __repr__(self) -> str: """For debug purposes.""" return "<{cls}({func!r}) at 0x{id:X}>".format( - cls=self.__class__.__name__, - func=self.__func, - id=id(self) + cls=self.__class__.__name__, func=self.__func, id=id(self) ) # pragma: no cover # 8<---------------------------------------------------------------------------- -if __name__ == '__main__': +if __name__ == "__main__": import doctest # pragma: no cover + doctest.testmod(verbose=True) # pragma: no cover diff --git a/threaded/_gthreadpooled.py b/threaded/_gthreadpooled.py index 4fda96b..88c1ed8 100644 --- a/threaded/_gthreadpooled.py +++ b/threaded/_gthreadpooled.py @@ -26,10 +26,7 @@ from . import _base_threaded -__all__ = ( - 'GThreadPooled', - 'gthreadpooled', -) +__all__ = ("GThreadPooled", "gthreadpooled") class GThreadPooled(_base_threaded.APIPooled): @@ -41,9 +38,9 @@ class GThreadPooled(_base_threaded.APIPooled): @classmethod def configure( # pylint: disable=arguments-differ - cls: typing.Type['GThreadPooled'], + cls: typing.Type["GThreadPooled"], max_workers: typing.Optional[int] = None, - hub: typing.Optional[gevent.hub.Hub] = None + hub: typing.Optional[gevent.hub.Hub] = None, ) -> None: """Pool executor create and configure. @@ -64,13 +61,10 @@ def configure( # pylint: disable=arguments-differ # Hub change. Very special case. cls.__executor.kill() # pragma: no cover - cls.__executor = gevent.threadpool.ThreadPool( - maxsize=max_workers, - hub=hub - ) + cls.__executor = gevent.threadpool.ThreadPool(maxsize=max_workers, hub=hub) @classmethod - def shutdown(cls: typing.Type['GThreadPooled']) -> None: + def shutdown(cls: typing.Type["GThreadPooled"]) -> None: """Shutdown executor. Due to not implemented method, set maxsize to 0 (do not accept new). @@ -89,8 +83,7 @@ def executor(self) -> gevent.threadpool.ThreadPool: return self.__executor def _get_function_wrapper( - self, - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] + self, func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] ) -> typing.Callable[..., gevent.event.AsyncResult]: """Here should be constructed and returned real decorator. @@ -103,20 +96,14 @@ def _get_function_wrapper( # noinspection PyMissingOrEmptyDocstring @functools.wraps(prepared) # pylint: disable=missing-docstring - def wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> gevent.event.AsyncResult: + def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> gevent.event.AsyncResult return self.executor.spawn(prepared, *args, **kwargs) return wrapper def __call__( # pylint: disable=useless-super-delegation self, - *args: typing.Union[ - typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], - typing.Any - ], + *args: typing.Union[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], typing.Any], **kwargs: typing.Any ) -> typing.Union[gevent.event.AsyncResult, typing.Callable[..., gevent.event.AsyncResult]]: """Callable instance.""" @@ -126,7 +113,7 @@ def __call__( # pylint: disable=useless-super-delegation # pylint: disable=function-redefined, unused-argument @typing.overload def gthreadpooled( - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] + func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] ) -> typing.Callable[..., gevent.event.AsyncResult]: """Overloaded: func provided.""" pass # pragma: no cover @@ -140,7 +127,7 @@ def gthreadpooled(func: None = None) -> GThreadPooled: # pylint: enable=unused-argument def gthreadpooled( # noqa: F811 - func: typing.Optional[typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]]] = None + func: typing.Optional[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]]] = None ) -> typing.Union[GThreadPooled, typing.Callable[..., gevent.event.AsyncResult]]: """Post function to gevent.threadpool.ThreadPool. @@ -152,4 +139,6 @@ def gthreadpooled( # noqa: F811 if func is None: return GThreadPooled(func=func) return GThreadPooled(func=None)(func) + + # pylint: enable=function-redefined diff --git a/threaded/_threaded.py b/threaded/_threaded.py index 6e3bde7..9798892 100644 --- a/threaded/_threaded.py +++ b/threaded/_threaded.py @@ -23,28 +23,18 @@ from . import _class_decorator -__all__ = ( - 'Threaded', - 'threaded', -) +__all__ = ("Threaded", "threaded") class Threaded(_class_decorator.BaseDecorator): """Run function in separate thread.""" - __slots__ = ( - '__name', - '__daemon', - '__started', - ) + __slots__ = ("__name", "__daemon", "__started") def __init__( self, name: typing.Optional[ - typing.Union[ - str, - typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] - ] + typing.Union[str, typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]]] ] = None, daemon: bool = False, started: bool = False, @@ -65,7 +55,7 @@ def __init__( self.__started = started if callable(name): func = name # type: typing.Callable - self.__name = 'Threaded: ' + getattr(name, '__name__', str(hash(name))) # type: str + 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) @@ -102,15 +92,11 @@ def __repr__(self) -> str: "name={self.name!r}, " "daemon={self.daemon!r}, " "started={self.started!r}, " - ")".format( - cls=self.__class__.__name__, - self=self, - ) + ")".format(cls=self.__class__.__name__, self=self) ) # pragma: no cover def _get_function_wrapper( - self, - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] + self, func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] ) -> typing.Callable[..., threading.Thread]: """Here should be constructed and returned real decorator. @@ -122,25 +108,12 @@ def _get_function_wrapper( prepared = self._await_if_required(func) name = self.name if name is None: - name = 'Threaded: ' + getattr( - func, - '__name__', - str(hash(func)) - ) + name = "Threaded: " + getattr(func, "__name__", str(hash(func))) # noinspection PyMissingOrEmptyDocstring @functools.wraps(prepared) # pylint: disable=missing-docstring - def wrapper( - *args, # type: typing.Any - **kwargs # type: typing.Any - ) -> threading.Thread: - thread = threading.Thread( - target=prepared, - name=name, - args=args, - kwargs=kwargs, - daemon=self.daemon - ) + def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> threading.Thread + thread = threading.Thread(target=prepared, name=name, args=args, kwargs=kwargs, daemon=self.daemon) if self.started: thread.start() return thread @@ -149,10 +122,7 @@ def wrapper( def __call__( # pylint: disable=useless-super-delegation self, - *args: typing.Union[ - typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], - typing.Any - ], + *args: typing.Union[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], typing.Any], **kwargs: typing.Any ) -> typing.Union[threading.Thread, typing.Callable[..., threading.Thread]]: """Executable instance.""" @@ -162,29 +132,21 @@ def __call__( # pylint: disable=useless-super-delegation # pylint: disable=function-redefined, unused-argument @typing.overload def threaded( - name: typing.Callable, - daemon: bool = False, - started: bool = False + name: typing.Callable, daemon: bool = False, started: bool = False ) -> typing.Callable[..., threading.Thread]: """Overload: Call decorator without arguments.""" pass # pragma: no cover @typing.overload # noqa: F811 -def threaded( - name: typing.Optional[str] = None, - daemon: bool = False, - started: bool = False -) -> Threaded: +def threaded(name: typing.Optional[str] = None, daemon: bool = False, started: bool = False) -> Threaded: """Overload: Name is not callable.""" pass # pragma: no cover # pylint: enable=unused-argument def threaded( # noqa: F811 - name: typing.Optional[typing.Union[str, typing.Callable]] = None, - daemon: bool = False, - started: bool = False + 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. @@ -200,10 +162,9 @@ def threaded( # noqa: F811 :rtype: typing.Union[Threaded, typing.Callable[..., threading.Thread]] """ if callable(name): - func, name = ( - name, - 'Threaded: ' + getattr(name, '__name__', str(hash(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=function-redefined diff --git a/threaded/_threadpooled.py b/threaded/_threadpooled.py index da01ac1..4b58246 100644 --- a/threaded/_threadpooled.py +++ b/threaded/_threadpooled.py @@ -25,27 +25,18 @@ from . import _base_threaded -__all__ = ( - 'ThreadPooled', - 'threadpooled', -) +__all__ = ("ThreadPooled", "threadpooled") class ThreadPooled(_base_threaded.APIPooled): """Post function to ThreadPoolExecutor.""" - __slots__ = ( - '__loop_getter', - '__loop_getter_need_context' - ) + __slots__ = ("__loop_getter", "__loop_getter_need_context") __executor = None # type: typing.Optional[ThreadPoolExecutor] @classmethod - def configure( - cls: typing.Type['ThreadPooled'], - max_workers: typing.Optional[int] = None, - ) -> None: + def configure(cls: typing.Type["ThreadPooled"], max_workers: typing.Optional[int] = None) -> None: """Pool executor create and configure. :param max_workers: Maximum workers @@ -56,18 +47,16 @@ def configure( return cls.__executor.shutdown() - cls.__executor = ThreadPoolExecutor( - max_workers=max_workers, - ) + cls.__executor = ThreadPoolExecutor(max_workers=max_workers) @classmethod - def shutdown(cls: typing.Type['ThreadPooled']) -> None: + def shutdown(cls: typing.Type["ThreadPooled"]) -> None: """Shutdown executor.""" if cls.__executor is not None: cls.__executor.shutdown() @property - def executor(self) -> 'ThreadPoolExecutor': + def executor(self) -> "ThreadPoolExecutor": """Executor instance. :rtype: ThreadPoolExecutor @@ -78,13 +67,10 @@ def executor(self) -> 'ThreadPoolExecutor': def __init__( self, - func: typing.Optional[typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]]] = None, + func: typing.Optional[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]]] = None, *, loop_getter: typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] + typing.Union[typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop] ] = None, loop_getter_need_context: bool = False ) -> None: @@ -108,12 +94,7 @@ def __init__( @property def loop_getter( self - ) -> typing.Optional[ - typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] - ]: + ) -> typing.Optional[typing.Union[typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop]]: """Loop getter. :rtype: typing.Union[None, typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop] @@ -128,11 +109,7 @@ def loop_getter_need_context(self) -> bool: """ return self.__loop_getter_need_context - def _get_loop( - self, - *args: typing.Any, - **kwargs: typing.Any - ) -> typing.Optional[asyncio.AbstractEventLoop]: + def _get_loop(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Optional[asyncio.AbstractEventLoop]: """Get event loop in decorator class.""" if callable(self.loop_getter): if self.loop_getter_need_context: @@ -141,9 +118,8 @@ def _get_loop( return self.loop_getter def _get_function_wrapper( - self, - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]] - ) -> typing.Callable[..., typing.Union[concurrent.futures.Future, 'typing.Awaitable']]: + self, func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]] + ) -> typing.Callable[..., typing.Union[concurrent.futures.Future, "typing.Awaitable"]]: """Here should be constructed and returned real decorator. :param func: Wrapped function @@ -156,37 +132,29 @@ def _get_function_wrapper( # noinspection PyMissingOrEmptyDocstring @functools.wraps(prepared) # pylint: disable=missing-docstring def wrapper( - *args: typing.Any, - **kwargs: typing.Any + *args: typing.Any, **kwargs: typing.Any ) -> typing.Union[ - concurrent.futures.Future, 'typing.Awaitable', - typing.Callable[..., typing.Union[concurrent.futures.Future, 'typing.Awaitable']] + concurrent.futures.Future, + "typing.Awaitable", + typing.Callable[..., typing.Union[concurrent.futures.Future, "typing.Awaitable"]], ]: loop = self._get_loop(*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 - ) - ) + return loop.run_in_executor(self.executor, functools.partial(prepared, *args, **kwargs)) return wrapper def __call__( # pylint: disable=useless-super-delegation self, - *args: typing.Union[ - typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], - typing.Any - ], + *args: typing.Union[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], typing.Any], **kwargs: typing.Any ) -> typing.Union[ - concurrent.futures.Future, 'typing.Awaitable', - typing.Callable[..., typing.Union[concurrent.futures.Future, 'typing.Awaitable']] + concurrent.futures.Future, + "typing.Awaitable", + typing.Callable[..., typing.Union[concurrent.futures.Future, "typing.Awaitable"]], ]: """Callable instance.""" return super(ThreadPooled, self).__call__(*args, **kwargs) # type: ignore @@ -198,19 +166,14 @@ def __repr__(self) -> str: "{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) - ) + ") at 0x{id:X}>".format(cls=self.__class__.__name__, func=self._func, self=self, id=id(self)) ) # pragma: no cover # pylint: disable=function-redefined, unused-argument @typing.overload def threadpooled( - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], + func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], *, loop_getter: None = None, loop_getter_need_context: bool = False @@ -221,12 +184,9 @@ def threadpooled( @typing.overload # noqa: F811 def threadpooled( - func: typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]], + func: typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]], *, - loop_getter: typing.Union[ - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ], + loop_getter: typing.Union[typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop], loop_getter_need_context: bool = False ) -> typing.Callable[..., asyncio.Task]: """Overload: function callable, loop getter available.""" @@ -237,11 +197,7 @@ def threadpooled( def threadpooled( func: None = None, *, - loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] = None, + loop_getter: typing.Union[None, typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop] = None, loop_getter_need_context: bool = False ) -> ThreadPooled: """Overload: No function.""" @@ -250,18 +206,11 @@ def threadpooled( # pylint: enable=unused-argument def threadpooled( # noqa: F811 - func: typing.Optional[typing.Callable[..., typing.Union['typing.Awaitable', typing.Any]]] = None, + func: typing.Optional[typing.Callable[..., typing.Union["typing.Awaitable", typing.Any]]] = None, *, - loop_getter: typing.Union[ - None, - typing.Callable[..., asyncio.AbstractEventLoop], - asyncio.AbstractEventLoop - ] = 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, 'typing.Awaitable']] -]: +) -> typing.Union[ThreadPooled, typing.Callable[..., typing.Union[concurrent.futures.Future, "typing.Awaitable"]]]: """Post function to ThreadPoolExecutor. :param func: function to wrap @@ -278,16 +227,12 @@ def threadpooled( # noqa: F811 :rtype: typing.Union[ThreadPooled, typing.Callable[..., typing.Union[concurrent.futures.Future, typing.Awaitable]]] """ if func is None: - return ThreadPooled( - func=func, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context - ) + return ThreadPooled(func=func, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context) return ThreadPooled( # type: ignore - func=None, - loop_getter=loop_getter, - loop_getter_need_context=loop_getter_need_context + func=None, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context )(func) + + # pylint: enable=function-redefined @@ -299,10 +244,7 @@ class ThreadPoolExecutor(concurrent.futures.ThreadPoolExecutor): __slots__ = () - def __init__( - self, - max_workers: typing.Optional[int] = None - ) -> None: + def __init__(self, max_workers: typing.Optional[int] = None) -> 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 @@ -310,12 +252,7 @@ def __init__( """ if max_workers is None: # Use 3.5+ behavior max_workers = (os.cpu_count() or 1) * 5 - super( - ThreadPoolExecutor, - self - ).__init__( - max_workers=max_workers, - ) + super(ThreadPoolExecutor, self).__init__(max_workers=max_workers) @property def max_workers(self) -> int: diff --git a/tox.ini b/tox.ini index c49ddf3..d25d7cb 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] minversion = 2.0 -envlist = pep8, pylint, mypy, bandit, pep257, py{34,35,36,37,py3}, docs, py{34,35,36,37}-nocov +envlist = black, pep8, pylint, mypy, bandit, pep257, py{34,35,36,37,py3}, docs, py{34,35,36,37}-nocov skipsdist = True skip_missing_interpreters = True @@ -100,13 +100,23 @@ exclude = __init__.py, docs ignore = + E203 +# whitespace before ':' show-pep8 = True show-source = True count = True max-line-length = 120 [pydocstyle] -ignore = D401, D203, D213 +ignore = + D401, + D202, + D203, + D213 +# First line should be in imperative mood; try rephrasing +# No blank lines allowed after function docstring +# 1 blank line required before class docstring +# Multi-line docstring summary should start at the second line [testenv:docs] deps = @@ -130,8 +140,15 @@ deps = pipdeptree commands = pipdeptree +[testenv:black] +deps = + black +usedevelop = False +commands = + black threaded + [testenv:mypy] deps = - mypy>=0.620 + mypy>=0.630 -r{toxinidir}/CI_REQUIREMENTS.txt commands = mypy --strict threaded