Completed
Pull Request — master (#307)
by Björn
22:50
created

AsyncioEventLoop   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 152
Duplicated Lines 0 %

Test Coverage

Coverage 71.43%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 152
ccs 35
cts 49
cp 0.7143
rs 8.2769
wmc 41

21 Methods

Rating   Name   Duplication   Size   Complexity  
A _setup_signals() 0 9 3
A pipe_connection_lost() 0 3 2
A _init() 0 7 2
A _connect_socket() 0 6 2
A process_exited() 0 3 1
A _stop() 0 2 1
A _stdin_reader() 0 6 2
A _teardown_signals() 0 3 2
A pipe_data_received() 0 8 3
B _connect_stdio() 0 30 4
A _start_reading() 0 2 1
A connection_lost() 0 3 2
A _connect_child() 0 5 1
A _send() 0 7 2
A _close() 0 6 2
A _threadsafe_call() 0 2 1
A _on_stdin() 0 6 3
A _connect_tcp() 0 3 1
A _run() 0 4 2
A connection_made() 0 6 2
A data_received() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like AsyncioEventLoop often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Event loop implementation that uses the `asyncio` standard module.
2
3
The `asyncio` module was added to python standard library on 3.4, and it
4
provides a pure python implementation of an event loop library. It is used
5
as a fallback in case pyuv is not available(on python implementations other
6
than CPython).
7
8
Earlier python versions are supported through the `trollius` package, which
9
is a backport of `asyncio` that works on Python 2.6+.
10
"""
11 5
from __future__ import absolute_import
12
13 5
import logging
14 5
import os
15 5
import sys
16
from collections import deque
17 5
import threading
18
19 5
try:
20 3
    # For python 3.4+, use the standard library module
21
    import asyncio
22 3
except (ImportError, SyntaxError):
23
    # Fallback to trollius
24 5
    import trollius as asyncio
0 ignored issues
show
Configuration introduced by
The import trollius could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
25
26
from .base import BaseEventLoop
27 5
28 5
logger = logging.getLogger(__name__)
29
debug, info, warn = (logger.debug, logger.info, logger.warning,)
30
31
loop_cls = asyncio.SelectorEventLoop
32
if os.name == 'nt':
33
    # On windows use ProactorEventLoop which support pipes and is backed by the
34 5
    # more powerful IOCP facility
35
    # NOTE: we override in the stdio case, because it doesn't work.
36
    loop_cls = asyncio.ProactorEventLoop
37
38
    import msvcrt
0 ignored issues
show
Configuration introduced by
The import msvcrt could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
39 5
    from ctypes import windll, byref, wintypes, GetLastError, WinError, POINTER
0 ignored issues
show
Unused Code introduced by
Unused GetLastError imported from ctypes
Loading history...
40
    from ctypes.wintypes import HANDLE, DWORD, BOOL
41 5
42 5
    LPDWORD = POINTER(DWORD)
43 5
44
    PIPE_NOWAIT = wintypes.DWORD(0x00000001)
45 5
46
    ERROR_NO_DATA = 232
47
48
    def pipe_no_wait(pipefd):
49 5
        SetNamedPipeHandleState = windll.kernel32.SetNamedPipeHandleState
50
        SetNamedPipeHandleState.argtypes = [HANDLE, LPDWORD, LPDWORD, LPDWORD]
51
        SetNamedPipeHandleState.restype = BOOL
52
53
        h = msvcrt.get_osfhandle(pipefd)
54
55
        res = windll.kernel32.SetNamedPipeHandleState(h, byref(PIPE_NOWAIT), None, None)
56 5
        if res == 0:
57
            print(WinError())
58 4
            return False
59
        return True
60 5
61
class AsyncioEventLoop(BaseEventLoop, asyncio.Protocol,
62 5
                       asyncio.SubprocessProtocol):
63 4
64 5
    """`BaseEventLoop` subclass that uses `asyncio` as a backend."""
65 5
66
    def connection_made(self, transport):
67
        """Used to signal `asyncio.Protocol` of a successful connection."""
68
        self._transport = transport
0 ignored issues
show
Coding Style introduced by
The attribute _transport was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
69 5
        self._raw_transport = transport
0 ignored issues
show
Coding Style introduced by
The attribute _raw_transport was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
70
        if isinstance(transport, asyncio.SubprocessTransport):
71
            self._transport = transport.get_pipe_transport(0)
0 ignored issues
show
Coding Style introduced by
The attribute _transport was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
72
73 5
    def connection_lost(self, exc):
74 5
        """Used to signal `asyncio.Protocol` of a lost connection."""
75 5
        self._on_error(exc.args[0] if exc else 'EOF')
76 5
77
    def data_received(self, data):
78 5
        """Used to signal `asyncio.Protocol` of incoming data."""
79
        if self._on_data:
80
            self._on_data(data)
81
            return
82 5
        self._queued_data.append(data)
83
84
    def pipe_connection_lost(self, fd, exc):
85
        """Used to signal `asyncio.SubprocessProtocol` of a lost connection."""
86
        self._on_error(exc.args[0] if exc else 'EOF')
87
88
    def pipe_data_received(self, fd, data):
89 5
        """Used to signal `asyncio.SubprocessProtocol` of incoming data."""
90
        if fd == 2:  # stderr fd number
91
            self._on_stderr(data)
0 ignored issues
show
Bug introduced by
The Instance of AsyncioEventLoop does not seem to have a member named _on_stderr.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
92
        elif self._on_data:
93 1
            self._on_data(data)
94
        else:
95 5
            self._queued_data.append(data)
96 5
97 5
    def process_exited(self):
98
        """Used to signal `asyncio.SubprocessProtocol` when the child exits."""
99 5
        self._on_error('EOF')
100 5
101
    def _init(self):
102 5
        self._loop = loop_cls()
0 ignored issues
show
Coding Style introduced by
The attribute _loop was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
103 5
        self._queued_data = deque()
0 ignored issues
show
Coding Style introduced by
The attribute _queued_data was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
104
        self._fact = lambda: self
0 ignored issues
show
Coding Style introduced by
The attribute _fact was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
105 5
        self._raw_transport = None
0 ignored issues
show
Coding Style introduced by
The attribute _raw_transport was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
106 5
        self._raw_stdout = False
0 ignored issues
show
Coding Style introduced by
The attribute _raw_stdout was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
107
        self._active = False
0 ignored issues
show
Coding Style introduced by
The attribute _active was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
108 5
109
    def _connect_tcp(self, address, port):
110 5
        coroutine = self._loop.create_connection(self._fact, address, port)
111 5
        self._loop.run_until_complete(coroutine)
112
113 5
    def _connect_socket(self, path):
114 5
        if os.name == 'nt':
115
            coroutine = self._loop.create_pipe_connection(self._fact, path)
116 5
        else:
117 5
            coroutine = self._loop.create_unix_connection(self._fact, path)
118
        self._loop.run_until_complete(coroutine)
119
120
    def _on_stdin(self):
121
        while True:
122 5
            data = sys.stdin.buffer.read(1)
123 5
            if len(data) == 0:
124 5
                break
125
            self.data_received(data)
126 5
127 5
    def _stdin_reader(self):
128 5
        debug("started reader thread")
129
        while True: #self._active
130
            data = self._stdin.read(1)
131
            debug("reader thread read %d", len(data))
132
            self._loop.call_soon_threadsafe(self.data_received, data)
133
134
    def _connect_stdio(self):
135
        try:
136
            coroutine = self._loop.connect_read_pipe(self._fact, sys.stdin)
137
            self._loop.run_until_complete(coroutine)
138
            debug("native stdin connection successful")
139
        except OSError:
140
            debug("native stdin connection failed, using reader thread")
141
            if os.name == "nt":
142
                pass
143
                #pipe_no_wait(sys.stdin.fileno())
144
            else:
145
                import fcntl
0 ignored issues
show
Unused Code introduced by
The variable fcntl seems to be unused.
Loading history...
146
                #orig_fl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
147
                #fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, orig_fl | os.O_NONBLOCK)
148
            #coro = self._loop.run_in_executor(None, self._stdin_reader)
149
            #self._loop.create_task(coro)
150
            self._stdin = sys.stdin.buffer
0 ignored issues
show
Coding Style introduced by
The attribute _stdin was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
151
            self._active = True
0 ignored issues
show
Coding Style introduced by
The attribute _active was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
152
            threading.Thread(target=self._stdin_reader).start()
153
154
155
        try:
156
            coroutine = self._loop.connect_write_pipe(self._fact, sys.stdout)
157
            self._loop.run_until_complete(coroutine)
158
            debug("native stdout connection successful")
159
        except OSError:
160
            debug("native stdin connection failed, using reader thread")
161
162
            self._stdout = sys.stdout.buffer
0 ignored issues
show
Bug introduced by
The Instance of RedirectStream does not seem to have a member named buffer.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
Coding Style introduced by
The attribute _stdout was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
163
            self._raw_stdout = True
0 ignored issues
show
Coding Style introduced by
The attribute _raw_stdout was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
164
165
    def _connect_child(self, argv):
166
        self._child_watcher = asyncio.get_child_watcher()
0 ignored issues
show
Coding Style introduced by
The attribute _child_watcher was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
167
        self._child_watcher.attach_loop(self._loop)
168
        coroutine = self._loop.subprocess_exec(self._fact, *argv)
169
        self._loop.run_until_complete(coroutine)
170
171
    def _start_reading(self):
172
        pass
173
174
    def _send(self, data):
175
        debug("sent %s %s", repr(data), str(self._raw_stdout))
176
        if self._raw_stdout:
177
            self._stdout.write(data)
178
            self._stdout.flush()
179
        else:
180
            self._transport.write(data)
181
182
    def _run(self):
183
        while self._queued_data:
184
            self._on_data(self._queued_data.popleft())
185
        self._loop.run_forever()
186
187
    def _stop(self):
188
        self._loop.stop()
189
190
    def _close(self):
191
        if self._raw_transport is not None:
192
            self._raw_transport.close()
193
        # FIXME: this is racy and stuff
194
        self._active = False
0 ignored issues
show
Coding Style introduced by
The attribute _active was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
195
        self._loop.close()
196
197
    def _threadsafe_call(self, fn):
198
        self._loop.call_soon_threadsafe(fn)
199
200
    def _setup_signals(self, signals):
201
        if os.name == 'nt':
202
            # add_signal_handler is not supported in win32
203
            self._signals = []
0 ignored issues
show
Coding Style introduced by
The attribute _signals was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
204
            return
205
206
        self._signals = list(signals)
0 ignored issues
show
Coding Style introduced by
The attribute _signals was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
207
        for signum in self._signals:
208
            self._loop.add_signal_handler(signum, self._on_signal, signum)
209
210
    def _teardown_signals(self):
211
        for signum in self._signals:
212
            self._loop.remove_signal_handler(signum)
213