Skip to content

Commit 1a91ee2

Browse files
authored
Merge pull request #160 from labthings/typing
Add more type hints and mypy
2 parents 69370c0 + 0568b48 commit 1a91ee2

24 files changed

+336
-254
lines changed

.github/workflows/publish.yml

+10-7
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ jobs:
1919
with:
2020
python-version: ${{ matrix.python }}
2121

22-
- name: Install Poetry
23-
uses: dschep/install-poetry-action@v1.3
24-
25-
- name: Set Poetry config
26-
run: |
27-
poetry config virtualenvs.in-project false
28-
poetry config virtualenvs.path ~/.virtualenvs
22+
- name: Install and configure Poetry
23+
uses: snok/install-poetry@v1.1.1
24+
with:
25+
version: 1.1.4
26+
virtualenvs-create: true
27+
virtualenvs-in-project: false
28+
virtualenvs-path: ~/.virtualenvs
2929

3030
- name: Install Dependencies
3131
run: poetry install
@@ -34,6 +34,9 @@ jobs:
3434
run: poetry run black . --check
3535
continue-on-error: true
3636

37+
- name: Analyse with MyPy
38+
run: poetry run mypy src
39+
3740
- name: Test with pytest
3841
run: poetry run pytest
3942

.github/workflows/test.yml

+10-7
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ jobs:
2121
with:
2222
python-version: ${{ matrix.python }}
2323

24-
- name: Install Poetry
25-
uses: dschep/install-poetry-action@v1.3
26-
27-
- name: Set Poetry config
28-
run: |
29-
poetry config virtualenvs.in-project false
30-
poetry config virtualenvs.path ~/.virtualenvs
24+
- name: Install and configure Poetry
25+
uses: snok/install-poetry@v1.1.1
26+
with:
27+
version: 1.1.4
28+
virtualenvs-create: true
29+
virtualenvs-in-project: false
30+
virtualenvs-path: ~/.virtualenvs
3131

3232
- name: Install Dependencies
3333
run: poetry install
@@ -36,6 +36,9 @@ jobs:
3636
run: poetry run black . --check
3737
continue-on-error: true
3838

39+
- name: Analyse with MyPy
40+
run: poetry run mypy src
41+
3942
- name: Test with pytest
4043
run: poetry run pytest
4144

poetry.lock

+33-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pylint = "^2.6.0"
3131
sphinx = "^3.2.1"
3232
sphinx-autoapi = "^1.4.0"
3333
sphinx-rtd-theme = "^0.5.0"
34+
mypy = "^0.790"
3435

3536
[tool.black]
3637
exclude = '(\.eggs|\.git|\.venv)'

src/labthings/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@
5151
"update_action_progress",
5252
"update_action_data",
5353
"ActionKilledException",
54+
"marshalling",
5455
"extensions",
5556
"views",
5657
"fields",
5758
"Schema",
5859
"json",
5960
"PropertyView",
6061
"ActionView",
62+
"EventView",
6163
"op",
6264
]

src/labthings/actions/pool.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import threading
3-
from functools import wraps
3+
4+
from typing import Dict
45

56
from ..deque import Deque
67
from .thread import ActionThread
@@ -41,7 +42,7 @@ def spawn(self, action: str, function, *args, **kwargs):
4142
self.start(thread)
4243
return thread
4344

44-
def kill(self, timeout=5):
45+
def kill(self, timeout: int = 5):
4546
"""
4647
4748
:param timeout: (Default value = 5)
@@ -73,7 +74,7 @@ def states(self):
7374
"""
7475
return {str(t.id): t.state for t in self.threads}
7576

76-
def to_dict(self):
77+
def to_dict(self) -> Dict[str, ActionThread]:
7778
"""
7879
7980
@@ -84,8 +85,8 @@ def to_dict(self):
8485
"""
8586
return {str(t.id): t for t in self.threads}
8687

87-
def get(self, task_id):
88-
return self.to_dict.get(task_id, None)
88+
def get(self, task_id: str):
89+
return self.to_dict().get(task_id, None)
8990

9091
def discard_id(self, task_id):
9192
"""

src/labthings/actions/thread.py

+38-38
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from flask import copy_current_request_context, has_request_context, request
99
from werkzeug.exceptions import BadRequest
1010

11+
from typing import Optional, Iterable, Dict, Any, Callable
12+
1113
from ..deque import LockableDeque
1214
from ..utilities import TimeoutTracker
1315

@@ -25,12 +27,12 @@ class ActionThread(threading.Thread):
2527

2628
def __init__(
2729
self,
28-
action,
29-
target=None,
30-
name=None,
31-
args=None,
32-
kwargs=None,
33-
daemon=True,
30+
action: str,
31+
target: Optional[Callable] = None,
32+
name: Optional[str] = None,
33+
args: Optional[Iterable[Any]] = None,
34+
kwargs: Optional[Dict[str, Any]] = None,
35+
daemon: bool = True,
3436
default_stop_timeout: int = 5,
3537
log_len: int = 100,
3638
):
@@ -39,34 +41,32 @@ def __init__(
3941
group=None,
4042
target=target,
4143
name=name,
42-
args=args,
43-
kwargs=kwargs,
44+
args=args or (),
45+
kwargs=kwargs or {},
4446
daemon=daemon,
4547
)
4648

47-
# Safely populate missing arguments
48-
args = args or ()
49-
kwargs = kwargs or {}
50-
5149
# Action resource corresponding to this action object
5250
self.action = action
5351

5452
# A UUID for the ActionThread (not the same as the threading.Thread ident)
5553
self._ID = uuid.uuid4() # Task ID
5654

5755
# Event to track if the task has started
58-
self.started = threading.Event()
56+
self.started: threading.Event = threading.Event()
5957
# Event to track if the user has requested stop
60-
self.stopping = threading.Event()
61-
self.default_stop_timeout = default_stop_timeout
58+
self.stopping: threading.Event = threading.Event()
59+
self.default_stop_timeout: int = default_stop_timeout
6260

6361
# Make _target, _args, and _kwargs available to the subclass
64-
self._target = target
65-
self._args = args
66-
self._kwargs = kwargs
62+
self._target: Optional[Callable] = target
63+
self._args: Iterable[Any] = args or ()
64+
self._kwargs: Dict[str, Any] = kwargs or {}
6765

6866
# Nice string representation of target function
69-
self.target_string = f"{self._target}(args={self._args}, kwargs={self._kwargs})"
67+
self.target_string: str = (
68+
f"{self._target}(args={self._args}, kwargs={self._kwargs})"
69+
)
7070

7171
# copy_current_request_context allows threads to access flask current_app
7272
if has_request_context():
@@ -82,14 +82,14 @@ def __init__(
8282

8383
# Private state properties
8484
self._status: str = "pending" # Task status
85-
self._return_value = None # Return value
86-
self._request_time = datetime.datetime.now()
87-
self._start_time = None # Task start time
88-
self._end_time = None # Task end time
85+
self._return_value: Optional[Any] = None # Return value
86+
self._request_time: datetime.datetime = datetime.datetime.now()
87+
self._start_time: Optional[datetime.datetime] = None # Task start time
88+
self._end_time: Optional[datetime.datetime] = None # Task end time
8989

9090
# Public state properties
91-
self.progress: int = None # Percent progress of the task
92-
self.data = {} # Dictionary of custom data added during the task
91+
self.progress: Optional[int] = None # Percent progress of the task
92+
self.data: dict = {} # Dictionary of custom data added during the task
9393
self._log = LockableDeque(
9494
None, log_len
9595
) # The log will hold dictionary objects with log information
@@ -100,14 +100,14 @@ def __init__(
100100
) # Lock obtained while self._target is running
101101

102102
@property
103-
def id(self):
103+
def id(self) -> uuid.UUID:
104104
"""
105105
UUID for the thread. Note this not the same as the native thread ident.
106106
"""
107107
return self._ID
108108

109109
@property
110-
def output(self):
110+
def output(self) -> Any:
111111
"""
112112
Return value of the Action function. If the Action is still running, returns None.
113113
"""
@@ -119,7 +119,7 @@ def log(self):
119119
return list(logdeque)
120120

121121
@property
122-
def status(self):
122+
def status(self) -> str:
123123
"""
124124
Current running status of the thread.
125125
@@ -136,19 +136,19 @@ def status(self):
136136
return self._status
137137

138138
@property
139-
def dead(self):
139+
def dead(self) -> bool:
140140
"""
141141
Has the thread finished, by any means (return, exception, termination).
142142
"""
143143
return not self.is_alive()
144144

145145
@property
146-
def stopped(self):
146+
def stopped(self) -> bool:
147147
"""Has the thread been cancelled"""
148148
return self.stopping.is_set()
149149

150150
@property
151-
def cancelled(self):
151+
def cancelled(self) -> bool:
152152
"""Alias of `stopped`"""
153153
return self.stopped
154154

@@ -162,7 +162,7 @@ def update_progress(self, progress: int):
162162
# Update progress of the task
163163
self.progress = progress
164164

165-
def update_data(self, data: dict):
165+
def update_data(self, data: Dict[Any, Any]):
166166
"""
167167
168168
:param data: dict:
@@ -183,7 +183,7 @@ def run(self):
183183
# an argument that has a member that points to the thread.
184184
del self._target, self._args, self._kwargs
185185

186-
def _thread_proc(self, f):
186+
def _thread_proc(self, f: Callable):
187187
"""Wraps the target function to handle recording `status` and `return` to `state`.
188188
Happens inside the task thread.
189189
@@ -228,7 +228,7 @@ def wrapped(*args, **kwargs):
228228

229229
return wrapped
230230

231-
def get(self, block=True, timeout=None):
231+
def get(self, block: bool = True, timeout: Optional[int] = None):
232232
"""Start waiting for the task to finish before returning
233233
234234
:param block: (Default value = True)
@@ -275,7 +275,7 @@ def _async_raise(self, exc_type):
275275
% (exc_type, self.name, self.ident, result)
276276
)
277277

278-
def _is_thread_proc_running(self):
278+
def _is_thread_proc_running(self) -> bool:
279279
"""Test if thread funtion (_thread_proc) is running,
280280
by attemtping to acquire the lock _thread_proc acquires at runtime.
281281
@@ -285,13 +285,13 @@ def _is_thread_proc_running(self):
285285
:rtype: bool
286286
287287
"""
288-
could_acquire = self._running_lock.acquire(0)
288+
could_acquire = self._running_lock.acquire(False)
289289
if could_acquire:
290290
self._running_lock.release()
291291
return False
292292
return True
293293

294-
def terminate(self, exception=ActionKilledException):
294+
def terminate(self, exception=ActionKilledException) -> bool:
295295
"""
296296
297297
:param exception: (Default value = ActionKilledException)
@@ -314,7 +314,7 @@ def terminate(self, exception=ActionKilledException):
314314
self.progress = None
315315
return True
316316

317-
def stop(self, timeout=None, exception=ActionKilledException):
317+
def stop(self, timeout=None, exception=ActionKilledException) -> bool:
318318
"""Sets the threads internal stopped event, waits for timeout seconds for the
319319
thread to stop nicely, then forcefully kills the thread.
320320

src/labthings/apispec/plugins.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22

3-
from apispec import BasePlugin
4-
from apispec.ext.marshmallow import MarshmallowPlugin as _MarshmallowPlugin
3+
from apispec import BasePlugin # type: ignore
4+
from apispec.ext.marshmallow import MarshmallowPlugin as _MarshmallowPlugin # type: ignore
55
from apispec.ext.marshmallow import OpenAPIConverter
66
from flask.views import http_method_funcs
77

0 commit comments

Comments
 (0)