Passed
Push — dependabot/pip/flake8-bugbear-... ( 5c5892...6076c0 )
by
unknown
01:34
created

NullTargetTraversalStrategy.traverse()   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nop 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
from __future__ import annotations
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import abc
3
import enum
4
import sys
0 ignored issues
show
Unused Code introduced by
The import sys seems to be unused.
Loading history...
5
import sre_compile
6
import re
7
from pathlib import Path
8
from typing import Dict, Sequence, Type, Set, Optional, Mapping
9
from typing import Tuple as Tup
10
11
from mandos.model import MandosResources, ReflectionUtils
12
from mandos.model.chembl_api import ChemblApi
13
from mandos.model.chembl_support.chembl_targets import TargetType, ChemblTarget
14
from mandos.model.chembl_support.chembl_target_graphs import (
15
    ChemblTargetGraph,
16
    TargetNode,
17
    TargetEdgeReqs,
18
    TargetRelType,
19
)
20
21
22
class Acceptance(enum.Enum):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
23
    always = enum.auto()
24
    never = enum.auto()
25
    at_start = enum.auto()
26
    at_end = enum.auto()
27
28
29
class TargetTraversalStrategy(metaclass=abc.ABCMeta):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
30
    """"""
31
32
    @classmethod
33
    @property
34
    def api(cls) -> ChemblApi:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
35
        raise NotImplementedError()
36
37
    def traverse(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
38
        return self.__call__(target)
39
40
    def __call__(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
41
        """
42
43
        Returns:
44
45
        """
46
        raise NotImplementedError()
47
48
49
class NullTargetTraversalStrategy(TargetTraversalStrategy, metaclass=abc.ABCMeta):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
50
    """"""
51
52
    @classmethod
53
    @property
54
    def api(cls) -> ChemblApi:
55
        raise NotImplementedError()
56
57
    def traverse(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
58
        return self.__call__(target)
59
60
    def __call__(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
61
        return [target]
62
63
64
class StandardTargetTraversalStrategy(TargetTraversalStrategy, metaclass=abc.ABCMeta):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
65
    """"""
66
67
    @classmethod
68
    @property
69
    def edges(cls) -> Set[TargetEdgeReqs]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
70
        raise NotImplementedError()
71
72
    @classmethod
73
    @property
74
    def acceptance(cls) -> Mapping[TargetEdgeReqs, Acceptance]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
75
        raise NotImplementedError()
76
77
    def __call__(self, target: ChemblTargetGraph) -> Sequence[ChemblTarget]:
78
        found = target.traverse(self.edges)
79
        return [f.target for f in found if self.accept(f)]
80
81
    def accept(self, target: TargetNode) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
82
        if target.link_reqs is None:
83
            # typically for root nodes -- we'll have a self-loop to check, too
84
            return False
85
        acceptance_type = self.acceptance[target.link_reqs]
86
        return (
87
            acceptance_type is Acceptance.always
88
            or (acceptance_type is Acceptance.at_start and target.is_start)
89
            or (acceptance_type is Acceptance.at_end and target.is_end)
90
        )
91
92
93
class StandardStrategyParser:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
94
    @classmethod
95
    def read_lines(cls, path: Path) -> Sequence[str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
96
        return [
97
            line
98
            for line in path.read_text(encoding="utf8").splitlines()
99
            if not line.startswith("#") and len(line.strip()) > 0
100
        ]
101
102
    @classmethod
103
    def parse(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Comprehensibility introduced by
This function exceeds the maximum number of variables (24/15).
Loading history...
104
        cls, lines: Sequence[str]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
105
    ) -> Tup[Set[TargetEdgeReqs], Mapping[TargetEdgeReqs, Acceptance]]:
106
        pat_type = r"(@?[a-z_]+)"
107
        pat_rel = r"([<>~=.*])"
108
        pat_accept = r"(?:accept:([\-*^$]))?"
109
        pat_src_words = r"(?:src:'''(.+?)''')?"
110
        pat_dest_words = r"(?:dest:'''(.+?)''')?"
111
        comment = r"(?:#(.*))?"
112
        pat = f"^ *{pat_type} *{pat_rel} *{pat_type} *{pat_accept} *{pat_src_words} *{pat_dest_words} *{comment} *$"
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (116/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
113
        pat = re.compile(pat)
114
        to_rel = {
115
            ">": TargetRelType.superset_of,
116
            "<": TargetRelType.subset_of,
117
            "~": TargetRelType.overlaps_with,
118
            "=": TargetRelType.equivalent_to,
119
            "*": TargetRelType.any_link,
120
            ".": TargetRelType.self_link,
121
        }
122
        to_accept = {
123
            "*": Acceptance.always,
124
            "-": Acceptance.never,
125
            "^": Acceptance.at_start,
126
            "$": Acceptance.at_end,
127
        }
128
        edges = set()
129
        edge_to_acceptance: Dict[TargetEdgeReqs, Acceptance] = {}
130
        for line in lines:
131
            match = pat.fullmatch(line)
132
            if match is None:
133
                raise AssertionError(f"Could not parse line '{line}'")
134
            try:
135
                sources = TargetType.resolve(match.group(1))
136
                rel = to_rel[match.group(2)]
137
                dests = TargetType.resolve(match.group(3))
138
                accept = to_accept[match.group(4).lower()]
139
                src_pat = (
140
                    None
141
                    if match.group(5) is None or match.group(5) == ""
142
                    else re.compile(match.group(5))
143
                )
144
                dest_pat = (
145
                    None
146
                    if match.group(6) is None or match.group(6) == ""
147
                    else re.compile(match.group(6))
148
                )
149
            except (KeyError, TypeError, sre_compile.error):
150
                raise AssertionError(f"Could not parse line '{line}'")
151
            for source in sources:
152
                for dest in dests:
153
                    edge = TargetEdgeReqs(
154
                        src_type=source,
155
                        src_pattern=src_pat,
156
                        rel_type=rel,
157
                        dest_type=dest,
158
                        dest_pattern=dest_pat,
159
                    )
160
                    edges.add(edge)
161
                    edge_to_acceptance[edge] = accept
162
        return edges, edge_to_acceptance
163
164
165
class TargetTraversalStrategies:
166
    """
167
    Factory.
168
    """
169
170
    @classmethod
171
    def standard_strategies(cls) -> Set[str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
172
        return {
173
            p.stem for p in MandosResources.path("strategies").iterdir() if p.suffix == ".strat"
174
        }
175
176
    @classmethod
177
    def by_name(cls, name: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
178
        if name == "@null":  # just slightly more efficient
179
            return cls.null(api)
180
        if MandosResources.contains("strategies", name, suffix=".strat"):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
181
            return cls.from_resource(name, api)
182
        elif name.endswith(".strat"):
183
            return cls.from_file(Path(name), api)
184
        return cls.by_classname(name, api)
185
186
    @classmethod
187
    def by_classname(cls, fully_qualified: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
188
        clazz = ReflectionUtils.injection(fully_qualified, TargetTraversalStrategy)
189
        return cls.create(clazz, api)
190
191
    @classmethod
192
    def from_resource(cls, name: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
193
        path = MandosResources.path("strategies", name, suffix=".strat")
194
        lines = StandardStrategyParser.read_lines(path)
195
        return cls._from_lines(lines, api, path.stem)
196
197
    @classmethod
198
    def from_file(cls, path: Path, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
199
        lines = StandardStrategyParser.read_lines(path)
200
        return cls._from_lines(lines, api, path.stem)
201
202
    @classmethod
203
    def from_lines(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
204
        cls, lines: Sequence[str], api: ChemblApi, name: Optional[str]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
205
    ) -> TargetTraversalStrategy:
206
        return cls._from_lines(lines, api, "" if name is None else name)
207
208
    @classmethod
209
    def _from_lines(
210
        cls, lines: Sequence[str], api: ChemblApi, name: str
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
211
    ) -> TargetTraversalStrategy:
212
        edges, accept = StandardStrategyParser.parse(lines)
213
214
        class Strategy(StandardTargetTraversalStrategy):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
215
            @classmethod
216
            @property
217
            def edges(cls) -> Set[TargetEdgeReqs]:
218
                return edges
219
220
            @classmethod
221
            @property
222
            def acceptance(cls) -> Mapping[TargetEdgeReqs, Acceptance]:
223
                return accept
224
225
            @classmethod
226
            @property
227
            def api(cls) -> ChemblApi:
228
                return api
229
230
        Strategy.__name__ = StandardTargetTraversalStrategy.__class__.__name__ + "_" + name
231
        return Strategy()
232
233
    @classmethod
234
    def null(cls, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
235
        class NullStrategy(NullTargetTraversalStrategy):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
236
            @classmethod
237
            @property
238
            def api(cls) -> ChemblApi:
239
                return api
240
241
        return NullStrategy()
242
243
    # noinspection PyAbstractClass
244
    @classmethod
245
    def create(cls, clz: Type[TargetTraversalStrategy], api: ChemblApi) -> TargetTraversalStrategy:
246
        """
247
        Factory method.
248
249
        Args:
250
            clz:
251
            api:
252
253
        Returns:
254
255
        """
256
257
        class X(clz):
0 ignored issues
show
Coding Style Naming introduced by
Class name "X" doesn't conform to PascalCase naming style ('[^\\W\\da-z][^\\W_]+$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
introduced by
Missing class docstring
Loading history...
258
            @classmethod
259
            @property
260
            def api(cls) -> ChemblApi:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
261
                return api
262
263
        X.__name__ = clz.__name__
264
        return X()
265
266
267
__all__ = ["TargetTraversalStrategy", "TargetTraversalStrategies"]
268