Passed
Push — dependabot/pip/flake8-bugbear-... ( 22089b...82a4d5 )
by
unknown
01:29
created

TargetTraversalStrategies.by_classname()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nop 3
dl 0
loc 12
rs 9.85
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, name: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
157
        if MandosResources.contains("strategies", name, suffix=".strat"):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
158
            return cls.from_resource(name, api)
159
        elif name.endswith(".strat"):
160
            return cls.from_file(Path(name), api)
161
        return cls.by_classname(name, api)
162
163
    @classmethod
164
    def by_classname(cls, fully_qualified: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
165
        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...
166
        mod = s[: s.rfind(".")]
167
        clz = s[s.rfind(".") :]
168
        try:
169
            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...
170
        except AttributeError:
171
            raise LookupError(
172
                f"Did not find strategy by fully-qualified class name {fully_qualified}"
173
            )
174
        return cls.create(x, api)
175
176
    @classmethod
177
    def from_resource(cls, name: str, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
178
        path = MandosResources.path("strategies", name)
179
        lines = StandardStrategyParser.read_lines(path)
180
        return cls._from_lines(lines, api, path.stem)
181
182
    @classmethod
183
    def from_file(cls, path: Path, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
184
        lines = StandardStrategyParser.read_lines(path)
185
        return cls._from_lines(lines, api, path.stem)
186
187
    @classmethod
188
    def from_lines(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
189
        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...
190
    ) -> TargetTraversalStrategy:
191
        return cls._from_lines(lines, api, "" if name is None else name)
192
193
    @classmethod
194
    def _from_lines(
195
        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...
196
    ) -> TargetTraversalStrategy:
197
        edges, accept = StandardStrategyParser.parse(lines)
198
199
        class Strategy(StandardTargetTraversalStrategy):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
200
            @classmethod
201
            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...
202
                return edges
203
204
            @classmethod
205
            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...
206
                return accept
207
208
            @classmethod
209
            def api(cls) -> ChemblApi:
210
                return api
211
212
        Strategy.__name__ = StandardTargetTraversalStrategy.__class__.__name__ + "_" + name
213
        return Strategy()
214
215
    @classmethod
216
    def null(cls, api: ChemblApi) -> TargetTraversalStrategy:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
217
        return cls.from_resource("null.txt", api)
218
219
    # noinspection PyAbstractClass
220
    @classmethod
221
    def create(cls, clz: Type[TargetTraversalStrategy], api: ChemblApi) -> TargetTraversalStrategy:
222
        """
223
        Factory method.
224
225
        Args:
226
            clz:
227
            api:
228
229
        Returns:
230
231
        """
232
233
        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...
234
            @classmethod
235
            def api(cls) -> ChemblApi:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
236
                return api
237
238
        X.__name__ = clz.__name__
239
        return X()
240
241
242
__all__ = ["TargetTraversalStrategy", "TargetTraversalStrategies"]
243