Skip to content

Commit bdb47ed

Browse files
authored
Merge pull request #3 from FusionSolutions/patch
Update to v0.0.4
2 parents ccfe2ef + f9d1668 commit bdb47ed

File tree

9 files changed

+152
-100
lines changed

9 files changed

+152
-100
lines changed

.github/workflows/python-package.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,17 @@ jobs:
2323
- name: Install dependencies
2424
run: |
2525
python -m pip install --upgrade pip
26-
python -m pip install mypy
26+
python -m pip install mypy pyflakes
2727
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
2828
- name: MyPy check
2929
run: |
30-
mypy fsSignal
30+
mypy --strict fsSignal
31+
- name: PyFlakes check
32+
run: |
33+
pyflakes fsSignal
3134
- name: Install package
3235
run: |
3336
python setup.py install
34-
- name: Unittest
37+
- name: Test package
3538
run: |
36-
python -m fsSignal
39+
python setup.py test

.github/workflows/python-publish.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ jobs:
2626
- name: Install dependencies
2727
run: |
2828
python -m pip install --upgrade pip
29-
python -m fsSignal
3029
pip install build
3130
- name: Build package
3231
run: python -m build

fsSignal/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.0.3"
1+
__version__ = "0.0.4"
22
__doc__ = """
33
Signal capturer v{}
44
Copyright (C) 2021 Fusion Solutions KFT <contact@fusionsolutions.io>
@@ -18,4 +18,4 @@
1818
""".format(__version__)
1919
from .fsSignal import KillSignal, SignalIterator, Signal
2020

21-
__all__ = "KillSignal", "SignalIterator", "Signal"
21+
__all__ = "KillSignal", "SignalIterator", "Signal"

fsSignal/__main__.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

fsSignal/fsSignal.py

Lines changed: 76 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
# Builtin modules
22
from __future__ import annotations
3-
import os, traceback, unittest, signal as _signal
4-
from threading import Timer, Event
3+
import traceback, signal as _signal
4+
from threading import Event
55
from time import monotonic, sleep
66
from typing import Callable, Dict, Any, Iterator, Iterable, Optional, Union
7+
from types import FrameType
8+
# Third party modules
79
# Local modules
810
# Program
11+
Signals = _signal.Signals
12+
913
class KillSignal(Exception): pass
1014

11-
class SignalIterator(Iterator):
15+
class SignalIterator(Iterator[Any]):
1216
__slots__ = ("event", "it", "checkDelay", "lastCheck")
13-
def __init__(self, event:Event, it:Iterable, checkDelay:float=1.0):
14-
self.event:Event = event
15-
self.it:Iterator = it.__iter__()
16-
self.checkDelay:float = checkDelay
17-
self.lastCheck:float = monotonic()
18-
def __iter__(self) -> Iterator:
17+
event:Event
18+
it:Iterator[Any]
19+
checkDelay:float
20+
lastCheck:float
21+
def __init__(self, event:Event, it:Iterable[Any], checkDelay:float=1.0):
22+
self.event = event
23+
self.it = it.__iter__()
24+
self.checkDelay = checkDelay
25+
self.lastCheck = monotonic()
26+
def __iter__(self) -> Iterator[Any]:
1927
return self
2028
def __next__(self) -> Any:
21-
m:float = monotonic()
29+
m = monotonic()
2230
if m-self.lastCheck > self.checkDelay:
2331
self.lastCheck = m
2432
if self.event.is_set():
@@ -28,38 +36,50 @@ def __next__(self) -> Any:
2836
class BaseSignal:
2937
_force:bool
3038
@classmethod
31-
def check(self) -> bool:
32-
if not isinstance(Signal._handler, Signal):
33-
return False
34-
return Signal._handler._check(self._force)
39+
def get(self) -> bool:
40+
if isinstance(Signal._handler, Signal):
41+
return Signal._handler._get(self._force)
42+
return False
3543
@classmethod
36-
def checkSoft(self) -> bool:
37-
if not isinstance(Signal._handler, Signal):
38-
return False
39-
return Signal._handler._check(False)
44+
def getSoft(self) -> bool:
45+
if isinstance(Signal._handler, Signal):
46+
return Signal._handler._get(False)
47+
return False
4048
@classmethod
41-
def checkHard(self) -> bool:
42-
if not isinstance(Signal._handler, Signal):
43-
return False
44-
return Signal._handler._check(True)
49+
def getHard(self) -> bool:
50+
if isinstance(Signal._handler, Signal):
51+
return Signal._handler._get(True)
52+
return False
53+
@classmethod
54+
def check(self) -> None:
55+
if isinstance(Signal._handler, Signal):
56+
return Signal._handler._check(self._force)
57+
@classmethod
58+
def checkSoft(self) -> None:
59+
if isinstance(Signal._handler, Signal):
60+
return Signal._handler._check(False)
61+
@classmethod
62+
def checkHard(self) -> None:
63+
if isinstance(Signal._handler, Signal):
64+
return Signal._handler._check(True)
4565
@classmethod
4666
def sleep(self, seconds:Union[int, float], raiseOnKill:bool=False) -> None:
47-
if not isinstance(Signal._handler, Signal):
48-
return sleep(seconds)
49-
return Signal._handler._sleep(seconds, raiseOnKill, self._force)
67+
if isinstance(Signal._handler, Signal):
68+
return Signal._handler._sleep(seconds, raiseOnKill, self._force)
69+
return sleep(seconds)
5070
@classmethod
51-
def signalSoftKill(self, *args, **kwargs) -> None:
71+
def signalSoftKill(self, *args:Any, **kwargs:Any) -> None:
5272
if isinstance(Signal._handler, Signal):
5373
return Signal._handler._signalSoftKill(*args, **kwargs)
5474
@classmethod
55-
def signalHardKill(self, *args, **kwargs) -> None:
75+
def signalHardKill(self, *args:Any, **kwargs:Any) -> None:
5676
if isinstance(Signal._handler, Signal):
5777
return Signal._handler._signalHardKill(*args, **kwargs)
5878
@classmethod
59-
def iter(self, it:Iterable, checkDelay:float=1.0) -> Iterable:
60-
if not isinstance(Signal._handler, Signal):
61-
return it
62-
return Signal._handler._iter(it, checkDelay, self._force)
79+
def iter(self, it:Iterable[Any], checkDelay:float=1.0) -> Iterable[Any]:
80+
if isinstance(Signal._handler, Signal):
81+
return Signal._handler._iter(it, checkDelay, self._force)
82+
return it
6383
@classmethod
6484
def softKill(self) -> None:
6585
if isinstance(Signal._handler, Signal):
@@ -89,16 +109,22 @@ class HardSignal(BaseSignal):
89109
_force:bool = True
90110

91111
class Signal(HardSignal):
112+
_handler:Optional[Signal] = None
113+
softKillFn:Optional[Callable[[Signals, FrameType], Any]]
114+
hardKillFn:Optional[Callable[[Signals, FrameType], Any]]
115+
forceKillCounterFn:Optional[Callable[[int, int], Any]]
116+
counter:int
117+
forceCounter:int
92118
eSoft:Event
93119
eHard:Event
94-
_handler:Optional[Signal] = None
95-
def __init__(self, softKillFn:Optional[Callable]=None, hardKillFn:Optional[Callable]=None,
96-
forceKillCounterFn:Optional[Callable]=None, forceCounter:int=10):
97-
self.softKillFn:Optional[Callable] = softKillFn
98-
self.hardKillFn:Optional[Callable] = hardKillFn
99-
self.forceKillCounterFn:Optional[Callable] = forceKillCounterFn
100-
self.counter:int = 0
101-
self.forceCounter:int = forceCounter
120+
def __init__(self, softKillFn:Optional[Callable[[Signals, FrameType], Any]]=None,
121+
hardKillFn:Optional[Callable[[Signals, FrameType], Any]]=None,
122+
forceKillCounterFn:Optional[Callable[[int, int], Any]]=None, forceCounter:int=10):
123+
self.softKillFn = softKillFn
124+
self.hardKillFn = hardKillFn
125+
self.forceKillCounterFn = forceKillCounterFn
126+
self.counter = 0
127+
self.forceCounter = forceCounter
102128
self.eSoft = Event()
103129
self.eHard = Event()
104130
Signal._handler = self
@@ -123,16 +149,21 @@ def __setstate__(self, states:Dict[str, Any]) -> None:
123149
def _activate(self) -> None:
124150
_signal.signal(_signal.SIGINT, Signal.signalSoftKill)
125151
_signal.signal(_signal.SIGTERM, Signal.signalHardKill)
126-
def _check(self, force:bool=True) -> bool:
152+
def _get(self, force:bool=True) -> bool:
127153
if force:
128154
return self.eHard.is_set()
129155
return self.eSoft.is_set()
156+
def _check(self, force:bool=True) -> None:
157+
if (force and self.eHard.is_set()) or (not force and self.eSoft.is_set()):
158+
raise KillSignal
159+
return None
130160
def _sleep(self, seconds:Union[int, float], raiseOnKill:bool=False, force:bool=True) -> None:
131161
if (self.eHard if force else self.eSoft).wait(float(seconds)) and raiseOnKill:
132162
raise KillSignal
133-
def _iter(self, it:Iterable, checkDelay:float=1.0, force:bool=True) -> Iterator:
163+
return None
164+
def _iter(self, it:Iterable[Any], checkDelay:float=1.0, force:bool=True) -> Iterator[Any]:
134165
return SignalIterator(self.eHard if force else self.eSoft, it, checkDelay)
135-
def _signalSoftKill(self, *args, **kwargs) -> None:
166+
def _signalSoftKill(self, *args:Any, **kwargs:Any) -> None:
136167
self._softKill()
137168
if not self.eHard.is_set():
138169
self.counter += 1
@@ -143,67 +174,26 @@ def _signalSoftKill(self, *args, **kwargs) -> None:
143174
traceback.print_exc()
144175
if self.counter >= self.forceCounter:
145176
self._hardKill()
146-
def _signalHardKill(self, *args, **kwargs) -> None:
177+
def _signalHardKill(self, *args:Any, **kwargs:Any) -> None:
147178
self._softKill()
148179
self._hardKill()
149180
def _softKill(self) -> None:
150181
if not self.eSoft.is_set():
151182
self.eSoft.set()
152183
if callable(self.softKillFn):
153184
try:
154-
self.softKillFn()
185+
self.softKillFn() # type: ignore
155186
except:
156187
traceback.print_exc()
157188
def _hardKill(self) -> None:
158189
if not self.eHard.is_set():
159190
self.eHard.set()
160191
if callable(self.hardKillFn):
161192
try:
162-
self.hardKillFn()
193+
self.hardKillFn() # type: ignore
163194
except:
164195
traceback.print_exc()
165196
def _reset(self) -> None:
166197
self.eSoft.clear()
167198
self.eHard.clear()
168199
self.counter = 0
169-
170-
class SignalTest(unittest.TestCase):
171-
rootSignal:Signal
172-
@classmethod
173-
def setUpClass(self) -> None:
174-
self.rootSignal = Signal()
175-
def tearDown(self) -> None:
176-
self.rootSignal.reset()
177-
def killmeTimer(self) -> None:
178-
def suicide():
179-
os.kill(os.getpid(), _signal.SIGINT)
180-
Timer(1, suicide).start()
181-
def test_sleep(self) -> None:
182-
t:float = monotonic()
183-
self.rootSignal.sleep(2)
184-
self.assertGreater(monotonic()-t, 2.0)
185-
def test_sleepRaise(self) -> None:
186-
self.killmeTimer()
187-
with self.assertRaises(KillSignal):
188-
self.rootSignal.getSoftSignal().sleep(2, raiseOnKill=True)
189-
def test_iter(self) -> None:
190-
s:list = list(range(5))
191-
d:list = []
192-
i:int
193-
signal:SoftSignal = self.rootSignal.getSoftSignal()
194-
self.killmeTimer()
195-
with self.assertRaises(KillSignal):
196-
for i in s:
197-
signal.sleep(0.5, raiseOnKill=True)
198-
d.append(i)
199-
def test_hardkill(self) -> None:
200-
self.killmeTimer()
201-
sleep(0.1)
202-
self.killmeTimer()
203-
sleep(0.1)
204-
self.killmeTimer()
205-
sleep(0.1)
206-
self.rootSignal.forceCounter = 3
207-
with self.assertRaises(KillSignal):
208-
self.rootSignal.sleep(10, raiseOnKill=True)
209-
self.rootSignal.forceCounter = 10

fsSignal/py.typed

Whitespace-only changes.

fsSignal/test/__init__.py

Whitespace-only changes.

fsSignal/test/fsSignal.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Builtin modules
2+
import os, unittest, signal as _signal
3+
from threading import Timer
4+
from time import monotonic, sleep
5+
from typing import List
6+
# Third party modules
7+
# Local modules
8+
from .. import Signal, KillSignal
9+
# Program
10+
class SignalTest(unittest.TestCase):
11+
rootSignal:Signal
12+
@classmethod
13+
def setUpClass(self) -> None:
14+
self.rootSignal = Signal()
15+
return None
16+
def tearDown(self) -> None:
17+
self.rootSignal.reset()
18+
return None
19+
def killmeTimer(self) -> None:
20+
def suicide() -> None:
21+
os.kill(os.getpid(), _signal.SIGINT)
22+
return None
23+
Timer(1, suicide).start()
24+
return None
25+
def test_sleep(self) -> None:
26+
t = monotonic()
27+
self.rootSignal.sleep(2)
28+
self.assertGreater(monotonic()-t, 2.0)
29+
return None
30+
def test_sleepRaise(self) -> None:
31+
self.killmeTimer()
32+
with self.assertRaises(KillSignal):
33+
self.rootSignal.getSoftSignal().sleep(2, raiseOnKill=True)
34+
return None
35+
def test_iter(self) -> None:
36+
s = list(range(5))
37+
d:List[int] = []
38+
signal = self.rootSignal.getSoftSignal()
39+
self.killmeTimer()
40+
with self.assertRaises(KillSignal):
41+
for i in s:
42+
signal.sleep(0.5, raiseOnKill=True)
43+
d.append(i)
44+
return None
45+
def test_hardkill(self) -> None:
46+
self.killmeTimer()
47+
sleep(0.1)
48+
self.killmeTimer()
49+
sleep(0.1)
50+
self.killmeTimer()
51+
sleep(0.1)
52+
self.rootSignal.forceCounter = 3
53+
with self.assertRaises(KillSignal):
54+
self.rootSignal.sleep(10, raiseOnKill=True)
55+
self.rootSignal.forceCounter = 10
56+
return None
57+
def test_check(self) -> None:
58+
signal = self.rootSignal.getSoftSignal()
59+
signal.check()
60+
self.assertEqual(signal.get(), False)
61+
self.killmeTimer()
62+
signal.sleep(2, raiseOnKill=False)
63+
with self.assertRaises(KillSignal):
64+
signal.check()
65+
self.assertEqual(signal.get(), True)

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
author_email = "ifa@fusionsolutions.io",
1818
url = "https://github.com/FusionSolutions/python-fssignal",
1919
license = "GPL-3",
20-
package_dir={"fsSignal": "fsSignal"},
2120
packages=["fsSignal"],
2221
long_description=open(os.path.join(pwd, "README.md")).read(),
2322
long_description_content_type="text/markdown",
2423
zip_safe=False,
2524
python_requires=">=3.7.0",
25+
test_suite="fsSignal.test",
26+
package_data={ "":["py.typed"] },
2627
classifiers=[ # https://pypi.org/pypi?%3Aaction=list_classifiers
2728
"Development Status :: 4 - Beta",
2829
"Topic :: Utilities",

0 commit comments

Comments
 (0)