Passed
Push — dependabot/pip/pyarrow-4.0.1 ( ca09ce...b2836e )
by
unknown
02:18 queued 20s
created

mandos.model.MiscUtils.adjust_filename()   B

Complexity

Conditions 7

Size

Total Lines 11
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 11
nop 4
dl 0
loc 11
rs 8
c 0
b 0
f 0
1
from __future__ import annotations
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
3
import abc
4
import enum
5
import inspect
6
import sys
7
import typing
8
from datetime import datetime
9
from pathlib import Path
10
from typing import Any, Mapping, Optional, Sequence, Type, TypeVar, Union
11
12
from pocketutils.core.dot_dict import NestedDotDict
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core.dot_dict'
Loading history...
13
from pocketutils.tools.common_tools import CommonTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.common_tools'
Loading history...
14
from suretime import Suretime
0 ignored issues
show
introduced by
Unable to import 'suretime'
Loading history...
15
16
from mandos import logger
17
from mandos.model.settings import MANDOS_SETTINGS
18
19
20
class Api(metaclass=abc.ABCMeta):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
21
    """ """
22
23
24
class CompoundNotFoundError(LookupError):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
25
    """ """
26
27
28
class InjectionError(LookupError):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
29
    """ """
30
31
32
class MultipleMatchesError(ValueError):
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
33
    """ """
34
35
36
T = TypeVar("T", covariant=True)
0 ignored issues
show
Coding Style Naming introduced by
Class name "T" 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...
37
38
39
class ReflectionUtils:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
40
    @classmethod
41
    def get_generic_arg(cls, clazz: Type[T], bound: Optional[Type[T]] = None) -> Type:
42
        """
43
        Finds the generic argument (specific TypeVar) of a :py:class:`~typing.Generic` class.
44
        **Assumes that ``clazz`` only has one type parameter. Always returns the first.**
45
46
        Args:
47
            clazz: The Generic class
48
            bound: If non-None, requires the returned type to be a subclass of ``bound`` (or equal to it)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (105/100).

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

Loading history...
49
50
        Returns:
51
            The class
52
53
        Raises:
54
            AssertionError: For most errors
55
        """
56
        bases = clazz.__orig_bases__
57
        try:
58
            param = typing.get_args(bases[0])[0]
0 ignored issues
show
Bug introduced by
The Module typing does not seem to have a member named get_args.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
59
        except KeyError:
60
            raise AssertionError(f"Failed to get generic type on {cls}")
61
        if not issubclass(param, bound):
62
            raise AssertionError(f"{param} is not a {bound}")
63
        return param
64
65
    @classmethod
66
    def default_arg_values(cls, func) -> Mapping[str, Optional[Any]]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
67
        return {k: v.default for k, v in cls.optional_args(func).items()}
68
69
    @classmethod
70
    def required_args(cls, func):
71
        """
72
        Finds parameters that lack default values.
73
74
        Args:
75
            func: A function or method
76
77
        Returns:
78
            A dict mapping parameter names to instances of ``MappingProxyType``,
79
            just as ``inspect.signature(func).parameters`` does.
80
        """
81
        return cls._args(func, True)
82
83
    @classmethod
84
    def optional_args(cls, func):
85
        """
86
        Finds parameters that have default values.
87
88
        Args:
89
            func: A function or method
90
91
        Returns:
92
            A dict mapping parameter names to instances of ``MappingProxyType``,
93
            just as ``inspect.signature(func).parameters`` does.
94
        """
95
        return cls._args(func, False)
96
97
    @classmethod
98
    def _args(cls, func, req):
99
        signature = inspect.signature(func)
100
        return {
101
            k: v
102
            for k, v in signature.parameters.items()
103
            if req
104
            and v.default is inspect.Parameter.empty
105
            or not req
106
            and v.default is not inspect.Parameter.empty
107
        }
108
109
    @classmethod
110
    def injection(cls, fully_qualified: str, clazz: Type[T]) -> Type[T]:
111
        """
112
        Gets a **class** by its fully-resolved class name.
113
114
        Args:
115
            fully_qualified:
116
            clazz:
117
118
        Returns:
119
            The Type
120
121
        Raises:
122
            InjectionError: If the class was not found
123
        """
124
        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...
125
        mod = s[: s.rfind(".")]
126
        clz = s[s.rfind(".") :]
127
        try:
128
            return getattr(sys.modules[mod], clz)
129
        except AttributeError:
130
            raise InjectionError(
131
                f"Did not find {clazz} by fully-qualified class name {fully_qualified}"
132
            )
133
134
135
class CleverEnum(enum.Enum):
136
    """
137
    An enum with a ``.of`` method that finds values
138
    with limited string/value fixing.
139
    May support an "unmatched" type -- a fallback value when there is no match.
140
    This is similar to pocketutils' simpler ``SmartEnum``.
141
    It is mainly useful for enums corresponding to concepts in ChEMBL and PubChem,
142
    where it's acceptable for the user to input spaces (like the database concepts use)
143
    rather than the underscores that Python requires.
144
    """
145
146
    @classmethod
147
    def _unmatched_type(cls) -> Optional[__qualname__]:
148
        return None
149
150
    @classmethod
151
    def of(cls, s: Union[int, str]) -> __qualname__:
0 ignored issues
show
Coding Style Naming introduced by
Method name "of" 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...
Coding Style Naming introduced by
Argument 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...
152
        """
153
        Turns a string or int into this type.
154
        Case-insensitive. Replaces `` `` and ``-`` with ``_``.
155
        """
156
        key = s.replace(" ", "_").replace("-", "_").lower()
157
        try:
158
            if isinstance(s, str):
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
159
                return cls[key]
160
            elif isinstance(key, int):
161
                return cls(key)
162
            else:
163
                raise TypeError(f"Lookup type {type(s)} for value {s} not a str or int")
164
        except KeyError:
165
            unk = cls._unmatched_type()
166
            if unk is None:
167
                raise
168
            logger.error(f"Value {key} not found. Using TargetType.unknown.")
169
            if not isinstance(unk, cls):
170
                raise AssertionError(f"Wrong type {type(unk)} (lookup: {s})")
171
            return unk
172
173
174
class MandosResources:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
175
176
    VERTEBRATA_PATH = None
177
178
    @classmethod
179
    def contains(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> bool:
180
        """Returns whether a resource file (or dir) exists."""
181
        return cls.path(*nodes, suffix=suffix).exists()
182
183
    @classmethod
184
    def path(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> Path:
185
        """Gets a path of a test resource file under ``resources/``."""
186
        path = Path(Path(__file__).parent.parent, "resources", *nodes)
187
        return path.with_suffix(path.suffix if suffix is None else suffix)
188
189
    @classmethod
190
    def a_path(cls, *nodes: Union[Path, str], suffixes: Optional[typing.Set[str]] = None) -> Path:
191
        """Gets a path of a test resource file under ``resources/``, ignoring suffix."""
192
        path = Path(Path(__file__).parent.parent, "resources", *nodes)
193
        return CommonTools.only(
194
            [
195
                p
196
                for p in path.parent.glob(path.stem + "*")
197
                if p.is_file() and (suffixes is None or p.suffix in suffixes)
198
            ]
199
        )
200
201
    @classmethod
202
    def json(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> NestedDotDict:
203
        """Reads a JSON file under ``resources/``."""
204
        return NestedDotDict.read_json(cls.path(*nodes, suffix=suffix))
205
206
207
class MiscUtils:
208
    """
209
    These are here to make sure I always use the same NTP server, etc.
210
    """
211
212
    @classmethod
213
    def adjust_filename(cls, to: Optional[Path], default: str, replace: bool) -> Path:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Coding Style Naming introduced by
Argument name "to" 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...
214
        if to is None:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
215
            return Path(default)
216
        elif str(to).startswith("."):
217
            return Path(default).with_suffix(str(to))
218
        elif to.is_dir() or to.suffix == "":
219
            return to / default
220
        if replace or not to.exists():
221
            return to
222
        raise FileExistsError(f"File {to} already exists")
223
224
    @classmethod
225
    def ntp_utc(cls) -> datetime:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
226
        ntp = Suretime.tagged.now_utc_ntp(
227
            ntp_server=MANDOS_SETTINGS.ntp_continent, ntp_clock="client-sent"
228
        )
229
        return ntp.use_clock_as_dt.dt
230
231
    @classmethod
232
    def utc(cls) -> datetime:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
233
        return Suretime.tagged.now_utc_sys().dt
234
235
    @classmethod
236
    def serialize_list(cls, lst: Sequence[str]) -> str:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
237
        return " || ".join([str(x) for x in lst])
238
239
    @classmethod
240
    def deserialize_list(cls, s: str) -> Sequence[str]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Coding Style Naming introduced by
Argument 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...
241
        return s.split(" || ")
242
243
244
START_TIME = MiscUtils.utc()
245
START_TIMESTAMP = START_TIME.isoformat(timespec="milliseconds")
246
247
248
MandosResources.VERTEBRATA_PATH = MandosResources.a_path("7742")
249
250
251
__all__ = [
252
    "Api",
253
    "CompoundNotFoundError",
254
    "MandosResources",
255
    "CleverEnum",
256
    "ReflectionUtils",
257
    "InjectionError",
258
    "MiscUtils",
259
    "START_TIME",
260
    "START_TIMESTAMP",
261
]
262