Skip to content

Commit 402d1f4

Browse files
authored
Version 2.1.0 (#43)
* Cython classes: class decorator, threaded and asynciotask * temporary: pydocstyle < 3.0
1 parent 7e52ca3 commit 402d1f4

16 files changed

+441
-13
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ jobs:
119119
name: "PEP257"
120120
install:
121121
- *upgrade_python_toolset
122-
- pip install --upgrade pydocstyle
122+
- pip install --upgrade 'pydocstyle<3.0'
123123
script:
124124
- pydocstyle threaded
125125

setup.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ def _extension(modpath):
4949

5050

5151
requires_optimization = [
52-
_extension("threaded._class_decorator"),
52+
setuptools.Extension("threaded.class_decorator", ["threaded/class_decorator.pyx"]),
5353
_extension("threaded._base_threaded"),
54-
_extension("threaded._asynciotask"),
55-
_extension("threaded._threaded"),
54+
setuptools.Extension("threaded._asynciotask", ["threaded/_asynciotask.pyx"]),
55+
setuptools.Extension("threaded._threaded", ["threaded/_threaded.pyx"]),
5656
_extension("threaded._threadpooled"),
5757
_extension("threaded._gthreadpooled"),
5858
]
@@ -83,7 +83,10 @@ class AllowFailRepair(build_ext.build_ext):
8383
"""This class allows C extension building to fail and repairs init."""
8484

8585
def run(self):
86-
"""Run."""
86+
"""Run.
87+
88+
:raises BuildFailed: cythonize impossible
89+
"""
8790
try:
8891
build_ext.build_ext.run(self)
8992

threaded/__init__.pxd

Whitespace-only changes.

threaded/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
if GThreadPooled is not None: # pragma: no cover
4242
__all__ += ("GThreadPooled", "gthreadpooled")
4343

44-
__version__ = "2.0.3"
44+
__version__ = "2.1.0"
4545
__author__ = "Alexey Stepanov"
4646
__author_email__ = "[email protected]"
4747
__maintainers__ = {

threaded/__init__.pyx

Whitespace-only changes.

threaded/_asynciotask.pxd

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2017-2018 Alexey Stepanov aka penguinolog
2+
##
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
"""AsyncIOTask implementation."""
16+
17+
from threaded cimport class_decorator
18+
19+
cpdef tuple __all__
20+
21+
22+
cdef class AsyncIOTask(class_decorator.BaseDecorator):
23+
"""Wrap to asyncio.Task."""
24+
25+
cdef:
26+
readonly object loop_getter
27+
readonly bint loop_getter_need_context

threaded/_asynciotask.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
import functools
1919
import typing
2020

21-
from . import _class_decorator
21+
from . import class_decorator
2222

2323
__all__ = ("AsyncIOTask", "asynciotask")
2424

2525

26-
class AsyncIOTask(_class_decorator.BaseDecorator):
26+
class AsyncIOTask(class_decorator.BaseDecorator):
2727
"""Wrap to asyncio.Task."""
2828

2929
__slots__ = ("__loop_getter", "__loop_getter_need_context")

threaded/_asynciotask.pyx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Copyright 2017-2018 Alexey Stepanov aka penguinolog
2+
##
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
"""AsyncIOTask implementation."""
16+
17+
import asyncio
18+
import functools
19+
import typing
20+
21+
from threaded cimport class_decorator
22+
23+
cpdef tuple __all__ = ("AsyncIOTask", "asynciotask")
24+
25+
26+
cdef class AsyncIOTask(class_decorator.BaseDecorator):
27+
"""Wrap to asyncio.Task."""
28+
29+
def __init__(
30+
self,
31+
func: typing.Optional[typing.Callable[..., "typing.Awaitable"]] = None,
32+
*,
33+
loop_getter: typing.Union[
34+
typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop
35+
] = asyncio.get_event_loop,
36+
bint loop_getter_need_context: bool = False
37+
) -> None:
38+
"""Wrap function in future and return.
39+
40+
:param func: Function to wrap
41+
:type func: typing.Optional[typing.Callable[..., typing.Awaitable]]
42+
:param loop_getter: Method to get event loop, if wrap in asyncio task
43+
:type loop_getter: typing.Union[
44+
typing.Callable[..., asyncio.AbstractEventLoop],
45+
asyncio.AbstractEventLoop
46+
]
47+
:param loop_getter_need_context: Loop getter requires function context
48+
:type loop_getter_need_context: bool
49+
"""
50+
super(AsyncIOTask, self).__init__(func=func)
51+
self.loop_getter = loop_getter
52+
self.loop_getter_need_context = loop_getter_need_context
53+
54+
def get_loop(self, *args, **kwargs): # type: (typing.Any, typing.Any) -> asyncio.AbstractEventLoop
55+
"""Get event loop in decorator class."""
56+
if callable(self.loop_getter):
57+
if self.loop_getter_need_context:
58+
return self.loop_getter(*args, **kwargs) # pylint: disable=not-callable
59+
return self.loop_getter() # pylint: disable=not-callable
60+
return self.loop_getter
61+
62+
def _get_function_wrapper(
63+
self, func: typing.Callable[..., "typing.Awaitable"]
64+
) -> typing.Callable[..., asyncio.Task]:
65+
"""Here should be constructed and returned real decorator.
66+
67+
:param func: Wrapped function
68+
:type func: typing.Callable[..., typing.Awaitable]
69+
:return: wrapper, which will produce asyncio.Task on call with function called inside it
70+
:rtype: typing.Callable[..., asyncio.Task]
71+
"""
72+
# noinspection PyMissingOrEmptyDocstring
73+
@functools.wraps(func) # pylint: disable=missing-docstring
74+
def wrapper(*args, **kwargs): # type: (typing.Any, typing.Any) -> asyncio.Task
75+
loop = self.get_loop(*args, **kwargs)
76+
return loop.create_task(func(*args, **kwargs))
77+
78+
return wrapper
79+
80+
def __call__( # pylint: disable=useless-super-delegation
81+
self, *args: typing.Union[typing.Callable[..., "typing.Awaitable"], typing.Any], **kwargs: typing.Any
82+
) -> typing.Union[asyncio.Task, typing.Callable[..., asyncio.Task]]:
83+
"""Callable instance."""
84+
return super(AsyncIOTask, self).__call__(*args, **kwargs) # type: ignore
85+
86+
def __repr__(self) -> str:
87+
"""For debug purposes."""
88+
return (
89+
"<{cls}("
90+
"{func!r}, "
91+
"loop_getter={self.loop_getter!r}, "
92+
"loop_getter_need_context={self.loop_getter_need_context!r}, "
93+
") at 0x{id:X}>".format(cls=self.__class__.__name__, func=self._func, self=self, id=id(self))
94+
) # pragma: no cover
95+
96+
97+
def asynciotask( # noqa: F811
98+
func: typing.Optional[typing.Callable[..., "typing.Awaitable"]] = None,
99+
*,
100+
loop_getter: typing.Union[
101+
typing.Callable[..., asyncio.AbstractEventLoop], asyncio.AbstractEventLoop
102+
] = asyncio.get_event_loop,
103+
loop_getter_need_context: bool = False
104+
) -> typing.Union[AsyncIOTask, typing.Callable[..., asyncio.Task]]:
105+
"""Wrap function in future and return.
106+
107+
:param func: Function to wrap
108+
:type func: typing.Optional[typing.Callable[..., typing.Awaitable]]
109+
:param loop_getter: Method to get event loop, if wrap in asyncio task
110+
:type loop_getter: typing.Union[
111+
typing.Callable[..., asyncio.AbstractEventLoop],
112+
asyncio.AbstractEventLoop
113+
]
114+
:param loop_getter_need_context: Loop getter requires function context
115+
:type loop_getter_need_context: bool
116+
:return: AsyncIOTask instance, if called as function or argumented decorator, else callable wrapper
117+
:rtype: typing.Union[AsyncIOTask, typing.Callable[..., asyncio.Task]]
118+
"""
119+
if func is None:
120+
return AsyncIOTask(func=func, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context)
121+
return AsyncIOTask( # type: ignore
122+
func=None, loop_getter=loop_getter, loop_getter_need_context=loop_getter_need_context
123+
)(func)

threaded/_base_threaded.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
import abc
1818
import typing
1919

20-
from . import _class_decorator
20+
from . import class_decorator
2121

2222

2323
__all__ = ("APIPooled",)
2424

2525

26-
class APIPooled(_class_decorator.BaseDecorator, metaclass=abc.ABCMeta):
26+
class APIPooled(class_decorator.BaseDecorator, metaclass=abc.ABCMeta):
2727
"""API description for pooled."""
2828

2929
__slots__ = ()

threaded/_threaded.pxd

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2017-2018 Alexey Stepanov aka penguinolog
2+
##
3+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
4+
# not use this file except in compliance with the License. You may obtain
5+
# a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
# License for the specific language governing permissions and limitations
13+
# under the License.
14+
15+
"""Threaded implementation.
16+
17+
Asyncio is supported
18+
"""
19+
20+
import functools
21+
import threading
22+
import typing
23+
24+
from threaded cimport class_decorator
25+
26+
cpdef tuple __all__
27+
28+
29+
cdef class Threaded(class_decorator.BaseDecorator):
30+
"""Run function in separate thread."""
31+
32+
cdef:
33+
readonly bint daemon
34+
readonly bint started
35+
readonly str name

0 commit comments

Comments
 (0)