Skip to content

Commit ad6b437

Browse files
authored
Merge pull request #2153 from nathanwilliams-ct/nathan/enum-static-type-checking
Static Type Checking: Enums
2 parents 7247e0a + 6115da1 commit ad6b437

8 files changed

Lines changed: 227 additions & 25 deletions

File tree

‎doc/source/hacking/using_the_testsuite.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,13 +190,22 @@ consists of running the ``pylint`` tool, run the following::
190190

191191
.. _contributing_formatting_code:
192192

193+
Running Static Type Checkers
194+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195+
Static Type Checking is performed separately from testing. In order to run the static type checking step which
196+
consists of running the ``mypy`` tool, run the following::
197+
198+
tox -e mypy
199+
193200
Formatting code
194201
~~~~~~~~~~~~~~~
195202
Similar to linting, code formatting is also done via a ``tox`` environment. To
196203
format the code using the ``black`` tool, run the following::
197204

198205
tox -e format
199206

207+
In CI `tox -e format-check` is used to ensure formatting has been run.
208+
200209
Observing coverage
201210
~~~~~~~~~~~~~~~~~~
202211
Once you have run the tests using `tox` (or `detox`), some coverage reports will

‎src/buildstream/_elementproxy.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def stage_artifact(
9595
sandbox: "Sandbox",
9696
*,
9797
path: Optional[str] = None,
98-
action: str = OverlapAction.WARNING,
98+
action: OverlapAction = OverlapAction.WARNING,
9999
include: Optional[List[str]] = None,
100100
exclude: Optional[List[str]] = None,
101101
orphans: bool = True
@@ -120,7 +120,7 @@ def stage_dependency_artifacts(
120120
selection: Optional[Sequence["Element"]] = None,
121121
*,
122122
path: Optional[str] = None,
123-
action: str = OverlapAction.WARNING,
123+
action: OverlapAction = OverlapAction.WARNING,
124124
include: Optional[List[str]] = None,
125125
exclude: Optional[List[str]] = None,
126126
orphans: bool = True
@@ -168,7 +168,7 @@ def _stage_artifact(
168168
sandbox: "Sandbox",
169169
*,
170170
path: Optional[str] = None,
171-
action: str = OverlapAction.WARNING,
171+
action: OverlapAction = OverlapAction.WARNING,
172172
include: Optional[List[str]] = None,
173173
exclude: Optional[List[str]] = None,
174174
orphans: bool = True,

‎src/buildstream/_overlapcollector.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, element: "Element"):
6666
# location (str): The Sandbox relative location this session was created for
6767
#
6868
@contextmanager
69-
def session(self, action: str, location: Optional[str]):
69+
def session(self, action: OverlapAction, location: Optional[str]):
7070
assert self._session is None, "Stage session already started"
7171

7272
if location is None:
@@ -108,13 +108,13 @@ def collect_stage_result(self, element: "Element", result: FileListResult):
108108
# location (str): The Sandbox relative location this session was created for
109109
#
110110
class OverlapCollectorSession:
111-
def __init__(self, element: "Element", action: str, location: str):
111+
def __init__(self, element: "Element", action: OverlapAction, location: str):
112112

113113
# The Element we are staging for, on which we'll issue warnings
114114
self._element = element # type: Element
115115

116116
# The OverlapAction for this session
117-
self._action = action # type: str
117+
self._action = action # type: OverlapAction
118118

119119
# The Sandbox relative directory this session was created for
120120
self._location = location # type: str

‎src/buildstream/_pipeline.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
# Yields:
4545
# Elements in the scope of the specified target elements
4646
#
47-
def dependencies(targets: List[Element], scope: int, *, recurse: bool = True) -> Iterator[Element]:
47+
def dependencies(targets: List[Element], scope: _Scope, *, recurse: bool = True) -> Iterator[Element]:
4848
# Keep track of 'visited' in this scope, so that all targets
4949
# share the same context.
5050
visited = (BitMap(), BitMap())
@@ -73,7 +73,12 @@ def dependencies(targets: List[Element], scope: int, *, recurse: bool = True) ->
7373
# A list of Elements appropriate for the specified selection mode
7474
#
7575
def get_selection(
76-
context: Context, targets: List[Element], mode: str, *, silent: bool = True, depth_sort: bool = False
76+
context: Context,
77+
targets: List[Element],
78+
mode: _PipelineSelection,
79+
*,
80+
silent: bool = True,
81+
depth_sort: bool = False
7782
) -> List[Element]:
7883
def redirect_and_log() -> List[Element]:
7984
# Redirect and log if permitted

‎src/buildstream/_stream.py‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def load_selection(
153153
self,
154154
targets: Iterable[str],
155155
*,
156-
selection: str = _PipelineSelection.NONE,
156+
selection: _PipelineSelection = _PipelineSelection.NONE,
157157
except_targets: Iterable[str] = (),
158158
load_artifacts: bool = False,
159159
connect_artifact_cache: bool = False,
@@ -259,7 +259,7 @@ def query_cache(self, elements, *, sources_of_cached_elements=False, only_source
259259
def shell(
260260
self,
261261
target: str,
262-
scope: int,
262+
scope: _Scope,
263263
prompt: Callable[[Element], str],
264264
*,
265265
unique_id: Optional[str] = None,
@@ -382,7 +382,7 @@ def build(
382382
self,
383383
targets: Iterable[str],
384384
*,
385-
selection: str = _PipelineSelection.NONE,
385+
selection: _PipelineSelection = _PipelineSelection.NONE,
386386
ignore_junction_targets: bool = False,
387387
artifact_remotes: Iterable[RemoteSpec] = (),
388388
source_remotes: Iterable[RemoteSpec] = (),
@@ -456,7 +456,7 @@ def fetch(
456456
self,
457457
targets: Iterable[str],
458458
*,
459-
selection: str = _PipelineSelection.NONE,
459+
selection: _PipelineSelection = _PipelineSelection.NONE,
460460
except_targets: Iterable[str] = (),
461461
source_remotes: Iterable[RemoteSpec] = (),
462462
ignore_project_source_remotes: bool = False,
@@ -579,7 +579,7 @@ def pull(
579579
self,
580580
targets: Iterable[str],
581581
*,
582-
selection: str = _PipelineSelection.NONE,
582+
selection: _PipelineSelection = _PipelineSelection.NONE,
583583
ignore_junction_targets: bool = False,
584584
artifact_remotes: Iterable[RemoteSpec] = (),
585585
ignore_project_artifact_remotes: bool = False,
@@ -633,7 +633,7 @@ def push(
633633
self,
634634
targets: Iterable[str],
635635
*,
636-
selection: str = _PipelineSelection.NONE,
636+
selection: _PipelineSelection = _PipelineSelection.NONE,
637637
ignore_junction_targets: bool = False,
638638
artifact_remotes: Iterable[RemoteSpec] = (),
639639
ignore_project_artifact_remotes: bool = False,
@@ -688,7 +688,7 @@ def checkout(
688688
*,
689689
location: Optional[str] = None,
690690
force: bool = False,
691-
selection: str = _PipelineSelection.RUN,
691+
selection: _PipelineSelection = _PipelineSelection.RUN,
692692
integrate: bool = True,
693693
hardlinks: bool = False,
694694
compression: str = "",
@@ -1668,7 +1668,7 @@ def _load(
16681668
self,
16691669
targets: Iterable[str],
16701670
*,
1671-
selection: str = _PipelineSelection.NONE,
1671+
selection: _PipelineSelection = _PipelineSelection.NONE,
16721672
except_targets: Iterable[str] = (),
16731673
ignore_junction_targets: bool = False,
16741674
dynamic_plan: bool = False,

‎src/buildstream/element.py‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
from .sandbox import _SandboxFlags, SandboxCommandError
9191
from .sandbox._config import SandboxConfig
9292
from .sandbox._sandboxremote import SandboxRemote
93-
from .types import _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey
93+
from .types import _HostMount, _Scope, _CacheBuildTrees, _KeyStrength, OverlapAction, _DisplayKey
9494
from ._artifact import Artifact
9595
from ._elementproxy import ElementProxy
9696
from ._elementsources import ElementSources
@@ -604,7 +604,7 @@ def stage_artifact(
604604
sandbox: "Sandbox",
605605
*,
606606
path: Optional[str] = None,
607-
action: str = OverlapAction.WARNING,
607+
action: OverlapAction = OverlapAction.WARNING,
608608
include: Optional[List[str]] = None,
609609
exclude: Optional[List[str]] = None,
610610
orphans: bool = True,
@@ -664,7 +664,7 @@ def stage_dependency_artifacts(
664664
selection: Optional[Sequence["Element"]] = None,
665665
*,
666666
path: Optional[str] = None,
667-
action: str = OverlapAction.WARNING,
667+
action: OverlapAction = OverlapAction.WARNING,
668668
include: Optional[List[str]] = None,
669669
exclude: Optional[List[str]] = None,
670670
orphans: bool = True,
@@ -864,7 +864,7 @@ def subsandbox(self, sandbox: "Sandbox") -> Iterator["Sandbox"]:
864864
# Yields:
865865
# (Element): The dependencies in `scope`, in deterministic staging order
866866
#
867-
def _dependencies(self, scope, *, recurse=True, visited=None):
867+
def _dependencies(self, scope: _Scope, *, recurse=True, visited=None):
868868

869869
# The format of visited is (BitMap(), BitMap()), with the first BitMap
870870
# containing element that have been visited for the `_Scope.BUILD` case
@@ -971,7 +971,7 @@ def _stage_artifact(
971971
sandbox: "Sandbox",
972972
*,
973973
path: Optional[str] = None,
974-
action: str = OverlapAction.WARNING,
974+
action: OverlapAction = OverlapAction.WARNING,
975975
include: Optional[List[str]] = None,
976976
exclude: Optional[List[str]] = None,
977977
orphans: bool = True,
@@ -2060,7 +2060,16 @@ def _push(self):
20602060
# usebuildtree (bool): Use the buildtree as its source
20612061
#
20622062
# Returns: Exit code
2063-
def _shell(self, scope=None, *, mounts=None, isolate=False, prompt=None, command=None, usebuildtree=False):
2063+
def _shell(
2064+
self,
2065+
scope: _Scope | None = None,
2066+
*,
2067+
mounts: List[_HostMount] | None = None,
2068+
isolate: bool = False,
2069+
prompt: str | None = None,
2070+
command: List[str] | None = None,
2071+
usebuildtree: bool = False,
2072+
):
20642073

20652074
with self._prepare_sandbox(scope, shell=True, usebuildtree=usebuildtree) as sandbox:
20662075
environment = sandbox._get_configured_environment() or self.get_environment()

‎src/buildstream/types.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ class FastEnum(metaclass=MetaFastEnum):
3535
3636
:class:`enum.Enum` attributes accesses can be really slow, and slow down the execution noticeably.
3737
This reimplementation doesn't suffer the same problems, but also does not reimplement everything.
38+
39+
For mypy Enum static type checking support, all FastEnum should be stubbed as inheriting from enum.Enum.
40+
Use `stubgen src/buildstream/types.py --include-docstrings` to generate the stubs, add `from enum import Enum` and replace all `(FastEnum)` with `(Enum)`
3841
"""
3942

4043
name = None
@@ -61,7 +64,7 @@ def __new__(cls, value):
6164
try:
6265
return cls._value_to_entry[value]
6366
except KeyError:
64-
if type(value) is cls: # pylint: disable=unidiomatic-typecheck
67+
if isinstance(value, cls): # pylint: disable=unidiomatic-typecheck
6568
return value
6669
raise ValueError("Unknown enum value: {}".format(value))
6770

@@ -172,7 +175,6 @@ class OverlapAction(FastEnum):
172175
# Defines the scope of dependencies to include for a given element
173176
# when iterating over the dependency graph in APIs like
174177
# Element._dependencies().
175-
#
176178
class _Scope(FastEnum):
177179

178180
# All elements which the given element depends on, following
@@ -384,7 +386,7 @@ def new_from_node(cls, node: MappingNode) -> "_SourceMirror":
384386
alias_node: MappingNode = node.get_mapping("aliases")
385387

386388
for alias, uris in alias_node.items():
387-
assert type(uris) is SequenceNode # pylint: disable=unidiomatic-typecheck
389+
assert isinstance(uris, SequenceNode) # pylint: disable=unidiomatic-typecheck
388390
aliases[alias] = uris.as_str_list()
389391

390392
return cls(name, aliases)

0 commit comments

Comments
 (0)