Passed
Push — dependabot/pip/flake8-bugbear-... ( 8d4b2b...22089b )
by
unknown
03:19 queued 01:48
created

TargetTraversalStrategies.from_lines()   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nop 4
dl 0
loc 5
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
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
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
    def api(cls) -> ChemblApi:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
34
        raise NotImplementedError()
35
36
    def traverse(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
37
        return self.__call__(target)
38
39
    def __call__(self, target: ChemblTargetGraph) -> Sequence[ChemblTargetGraph]:
40
        """
41
42
        Returns:
43
44
        """
45
        raise NotImplementedError()
46
47
48
class StandardTargetTraversalStrategy(TargetTraversalStrategy, metaclass=abc.ABCMeta):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
49
    """"""
50
51
    @classmethod
52
    @property
53
    def edges(cls) -> Set[TargetEdgeReqs]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
54
        raise NotImplementedError()
55
56
    @classmethod
57
    @property
58
    def acceptance(cls) -> Mapping[TargetEdgeReqs, Acceptance]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
59
        raise NotImplementedError()
60
61
    def __call__(self, target: ChemblTargetGraph) -> Sequence[ChemblTarget]:
62
        if not target.type.is_traversable:
63
            return [target.target]
64
        found = target.traverse(self.edges)
65
        return [f.target for f in found if self.accept(f)]
66
67
    def accept(self, target: TargetNode) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
68
        acceptance_type = self.acceptance[target.link_reqs]
69
        return (
70
            acceptance_type is Acceptance.always
71
            or (acceptance_type is Acceptance.at_start and target.is_start)
72
            or (acceptance_type is Acceptance.at_end and target.is_end)
73
        )
74
75
76
class StandardStrategyParser:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
77
    @classmethod
78
    def read_lines(cls, path: Path) -> Sequence[str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
79
        return [
80
            line
81
            for line in path.read_text(encoding="utf8").splitlines()
82
            if not line.startswith("#") and len(line.strip()) > 0
83
        ]
84
85
    @classmethod
86
    def parse(
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (26/15).
Loading history...
introduced by
Missing function or method docstring
Loading history...
87
        cls, lines: Sequence[str]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
88
    ) -> Tup[Set[TargetEdgeReqs], Mapping[TargetEdgeReqs, Acceptance]]:
89
        pat_type = r"([a-z_]+)"
90
        pat_rel = r"([<>~=])"
91
        pat_accept = r"(?:accept:([\-*^$]?))?"
92
        pat_src_words = r"(?:src:'''(.+?)''')?"
93
        pat_dest_words = r"(?:dest:'''(.+?)''')?"
94
        comment = r"(?:#(.*))?"
95
        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 (117/100).

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

Loading history...
96
        pat = re.compile(pat)
97
        to_rel = {
98
            ">": TargetRelType.superset_of,
99
            "<": TargetRelType.subset_of,
100
            "~": TargetRelType.overlaps_with,
101
            "=": TargetRelType.equivalent_to,
102
            "*": TargetRelType.any_link,
103
            ".": TargetRelType.self_link,
104
        }
105
        to_accept = {
106
            "*": Acceptance.always,
107
            "-": Acceptance.never,
108
            "^": Acceptance.at_start,
109
            "$": Acceptance.at_end,
110
        }
111
        edges = set()
112
        edge_to_acceptance: Dict[TargetEdgeReqs, Acceptance] = {}
113
        for line in lines:
114
            match = pat.fullmatch(line)
115
            if match is None:
116
                raise AssertionError(f"Could not parse line '{line}'")
117
            try:
118
                src_str = match.group(1).lower()
119
                sources = TargetType.all_types() if src_str == "any" else [TargetType[src_str]]
120
                rel = to_rel[match.group(2)]
121
                dest_str = match.group(3).lower()
122
                targets = TargetType.all_types() if dest_str == "any" else [TargetType[dest_str]]
123
                accept = to_accept[match.group(4).lower()]
124
                src_pat = (
125
                    None
126
                    if match.group(5) is None or match.group(5) == ""
127
                    else re.compile(match.group(5))
128
                )
129
                dest_pat = (
130
                    None
131
                    if match.group(6) is None or match.group(6) == ""
132
                    else re.compile(match.group(6))
133
                )
134
            except (KeyError, TypeError, sre_compile.error):
135
                raise AssertionError(f"Could not parse line '{line}'")
136
            for source in sources:
137
                for dest in targets:
138
                    edge = TargetEdgeReqs(
139
                        src_type=source,
140
                        src_pattern=src_pat,
141
                        rel_type=rel,
142
                        dest_type=dest,
143
                        dest_pattern=dest_pat,
144
                    )
145
                    edges.add(edge)
146
                    edge_to_acceptance[edge] = accept
147
        return edges, edge_to_acceptance
148
149
150
class TargetTraversalStrategies:
151
    """
152
    Factory.
153
    """
154
155
    @classmethod
156
    def by_name(cls, fully_qualified: str, api: ChemblApi) -> TargetTraversalStrategy:
157
        """
158
        For dependency injection.
159
160
        Args:
161
            fully_qualified:
162
            api:
163
164
        Returns:
165
166
        """
167
        s = fully_qualified
0 ignored issues
show
Coding Style Naming introduced by
Variable name "s" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' 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...
168
        mod = s[: s.rfind(".")]
169
        clz = s[s.rfind(".") :]
170
        x = getattr(sys.modules[mod], clz)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "x" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' 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...
171
        return cls.create(x, api)
172
173
    @classmethod
174
    def from_resource(cls, name: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
175
        path = MandosResources.path("strategies", name).with_suffix(".txt")
176
        lines = StandardStrategyParser.read_lines(path)
177
        return cls._from_lines(lines, api, path.stem)
178
179
    @classmethod
180
    def from_file(cls, path: Path, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
181
        lines = StandardStrategyParser.read_lines(path)
182
        return cls._from_lines(lines, api, path.stem)
183
184
    @classmethod
185
    def from_lines(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
186
        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...
187
    ) -> TargetTraversalStrategy:
188
        return cls._from_lines(lines, api, "" if name is None else name)
189
190
    @classmethod
191
    def _from_lines(
192
        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...
193
    ) -> TargetTraversalStrategy:
194
        edges, accept = StandardStrategyParser.parse(lines)
195
196
        class Strategy(StandardTargetTraversalStrategy):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
197
            @classmethod
198
            def edges(cls) -> Set[TargetEdgeReqs]:
0 ignored issues
show
introduced by
Method 'edges' was expected to be 'property', found it instead as 'classmethod'
Loading history...
199
                return edges
200
201
            @classmethod
202
            def acceptance(cls) -> Mapping[TargetEdgeReqs, Acceptance]:
0 ignored issues
show
introduced by
Method 'acceptance' was expected to be 'property', found it instead as 'classmethod'
Loading history...
203
                return accept
204
205
            @classmethod
206
            def api(cls) -> ChemblApi:
207
                return api
208
209
        Strategy.__name__ = StandardTargetTraversalStrategy.__class__.__name__ + "_" + name
210
        return Strategy()
211
212
    @classmethod
213
    def null(cls, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
214
        return cls.from_resource("null.txt", api)
215
216
    # noinspection PyAbstractClass
217
    @classmethod
218
    def create(cls, clz: Type[TargetTraversalStrategy], api: ChemblApi) -> TargetTraversalStrategy:
219
        """
220
        Factory method.
221
222
        Args:
223
            clz:
224
            api:
225
226
        Returns:
227
228
        """
229
230
        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...
231
            @classmethod
232
            def api(cls) -> ChemblApi:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
233
                return api
234
235
        X.__name__ = clz.__name__
236
        return X()
237
238
239
__all__ = ["TargetTraversalStrategy", "TargetTraversalStrategies"]
240