Skip to content

Commit

Permalink
wasp_general/polling.py: WPollingHandlerProto class was moved from wa…
Browse files Browse the repository at this point in the history
…sp_general/network/service/proto.py. WSelectPollingHandler class was added
  • Loading branch information
a1ezzz committed May 5, 2018
1 parent d93531b commit 763b3b4
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 128 deletions.
130 changes: 130 additions & 0 deletions tests/wasp_general_polling_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-

import pytest
import socket
from enum import Enum
from threading import Event

from wasp_general.polling import WPollingHandlerProto, WSelectPollingHandler, __default_polling_handler_cls__


def test():
assert(issubclass(__default_polling_handler_cls__, WPollingHandlerProto) is True)


def test_abstract():
pytest.raises(TypeError, WPollingHandlerProto)
pytest.raises(NotImplementedError, WPollingHandlerProto.polling_function, None)


class TestWPollingHandlerProto:

class Handler(WPollingHandlerProto):

def polling_function(self):
return lambda: None

def test_exception(self):
assert(issubclass(WPollingHandlerProto.PollingError, Exception) is True)
assert(WPollingHandlerProto.PollingError().file_objects() == tuple())
assert(WPollingHandlerProto.PollingError(1, 2, 3).file_objects() == (1, 2, 3))

def test_enum(self):
assert(issubclass(WPollingHandlerProto.PollingEvent, Enum) is True)
assert(WPollingHandlerProto.PollingEvent.read is not None)
assert(WPollingHandlerProto.PollingEvent.write is not None)

def test(self):
handler = TestWPollingHandlerProto.Handler()

assert(handler.file_obj() == tuple())
assert(handler.event_mask() is None)
assert(handler.timeout() is None)

handler = TestWPollingHandlerProto.Handler.create_handler()
assert(handler.file_obj() == tuple())
assert(handler.event_mask() is None)
assert(handler.timeout() is None)

handler.setup_poll(WPollingHandlerProto.PollingEvent.read, 0.1)
assert(0 < handler.timeout() < 0.2)
assert(handler.event_mask() == WPollingHandlerProto.PollingEvent.read.value)

handler.setup_poll(
WPollingHandlerProto.PollingEvent.read.value + WPollingHandlerProto.PollingEvent.write.value, 1
)
assert(handler.timeout() == 1)
event_mask = \
WPollingHandlerProto.PollingEvent.read.value + WPollingHandlerProto.PollingEvent.write.value
assert(handler.event_mask() == event_mask)

handler.setup_poll(WPollingHandlerProto.PollingEvent.read)
assert (handler.timeout() is None)

pytest.raises(TypeError, handler.setup_poll, 0.1, 10)

assert(handler.file_obj() == tuple())
handler.poll_fd(1)
assert(handler.file_obj() == (1,))
handler.poll_fd(10)
assert(handler.file_obj() == (1, 10))

handler.reset()
assert(handler.file_obj() == tuple())
assert(handler.event_mask() is None)
assert(handler.timeout() is None)


class TestWSelectPollingHandler:

__test_port__ = 22722

def test(self):
assert(issubclass(WSelectPollingHandler, WPollingHandlerProto) is True)

read_handler = WSelectPollingHandler.create_handler()
read_handler.setup_poll(WPollingHandlerProto.PollingEvent.read, 0.1)

write_handler = WSelectPollingHandler.create_handler()
write_handler.setup_poll(WPollingHandlerProto.PollingEvent.write, 0.1)

socket_ln = socket.socket()
socket_out = socket.socket()
socket_in = None
try:
socket_ln.bind(('127.0.0.1', TestWSelectPollingHandler.__test_port__))
socket_ln.listen()

socket_out.connect(('127.0.0.1', TestWSelectPollingHandler.__test_port__))
read_handler.poll_fd(socket_out)
read_poll_fn = read_handler.polling_function()
assert(read_poll_fn() is None)
assert(read_poll_fn() is None)

socket_in = socket_ln.accept()[0]
write_handler.poll_fd(socket_in)
write_poll_fn = write_handler.polling_function()
assert(read_poll_fn() is None)
assert(read_poll_fn() is None)
assert(write_poll_fn() == ([], [socket_in]))
assert(write_poll_fn() == ([], [socket_in]))

socket_in.send(b'\x00')
assert(read_poll_fn() == ([socket_out], []))
assert(read_poll_fn() == ([socket_out], []))

socket_out.recv(1)
assert(read_poll_fn() is None)
assert(read_poll_fn() is None)

socket_in.send(b'\x01', socket.MSG_OOB)
pytest.raises(WPollingHandlerProto.PollingError, read_poll_fn)

socket_out.close()
pytest.raises(WPollingHandlerProto.PollingError, read_poll_fn)

finally:
if socket_in is not None:
socket_in.close()
socket_out.close()
socket_ln.close()
129 changes: 1 addition & 128 deletions wasp_general/network/service/proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,135 +27,8 @@
from abc import ABCMeta, abstractmethod
from socket import socket
from threading import Event
from enum import Enum

from wasp_general.verify import verify_type, verify_value


class WPollingHandlerProto(metaclass=ABCMeta):
""" Prototype of handler that is capable to produce polling function. That polling function awaits for specific
events on specific file objects. Once a polling function is returned, that function must ignore any changes
that may be happen to this handler.
The derived classes should respect timeout property. It should be used by a polling function that waits for
events for the specific time. This function may be called from an infinite loop; that loop at each iteration
polls some file objects and checks a stop event.
"""

class PollingError(Exception):
""" Exception in case of errors during polling
"""

def __init__(self, *file_objects):
""" Create new exception
:param file_objects: file objects that were sources of an exception
"""
Exception.__init__(self, 'Error during polling file-objects')
self.__file_objects = file_objects

def file_objects(self):
""" Return file objects that were sources of an exception
:return: any file-like objects
"""
return self.__file_objects

class PollingEvent(Enum):
""" Event that may be awaited during polling
"""
read = 1
write = 2

def __init__(self):
""" Create new polling handler
"""
self.__file_obj = []
self.__event_mask = None
self.__timeout = None

def file_obj(self):
""" Return file objects that must be polled
:return: any file-like objects
"""
return tuple(self.__file_obj)

def poll_fd(self, file_obj):
""" Append file object that should be polled.
:param file_obj: file object to add
:return: None
"""
self.__file_obj.append(file_obj)

def event_mask(self):
""" Return binary mask of events that must be polled
:return: int
"""
return self.__event_mask

def timeout(self):
""" Return timeout that may be used by a polling function. The polling function should not block
current process or thread for more then this timeout.
:return: int, float or None (if a timeout does not set)
"""
return self.__timeout

@verify_type(timeout=(int, float, None))
@verify_value(timeout=lambda x: x is None or x >= 0)
def setup_poll(self, event_mask, timeout=None):
""" Setup this handler and save specified parameters. This method does not discard file-objects that
were set before.
:param event_mask: WPollingHandlerProto.PollingEvent or int - replace polling events, that this handler
should wait. In case of a :class:`.WPollingHandlerProto.PollingEvent` instance - this single event will
be awaited. In case of int value - bit-mask may be used. Bit mask is a sum of
:class:`.WPollingHandlerProto.PollingEvent` values
:param timeout: set a timeout that may be used by polling function
:return: None
"""
if isinstance(event_mask, WPollingHandlerProto.PollingEvent) is True:
event_mask = event_mask.value
elif isinstance(event_mask, int) is False:
raise TypeError('Invalid type of event_mask')

self.__event_mask = event_mask
self.__timeout = timeout

def reset(self):
""" Reset this handler settings and return it to a default state.
:return: None
"""
self.__file_obj = []
self.__event_mask = None
self.__timeout = None

@abstractmethod
def polling_function(self):
""" Return polling function, that does not accept any argument, but awaits for specific events on
specific file objects during a specific timeout. Values of events, file objects and timeout that
are used by that function are obtained from this handler. No matter what will happen to this handler,
a polling function must use values that were set at the moment of function generation.
:return: callable
"""
raise NotImplementedError('This method is abstract')

@classmethod
def create_handler(cls):
""" Create new handler. As constructors of derived classes may have some extra arguments, this method
should be used prior to a direct constructor call.
:return: WPollingHandlerProto
"""
return cls()
from wasp_general.verify import verify_type


class WServiceWorkerProto(metaclass=ABCMeta):
Expand Down
Loading

0 comments on commit 763b3b4

Please sign in to comment.