Passed
Push — main ( 83a9fb...fa90c4 )
by Douglas
03:43
created

MandosResources.check_expired()   A

Complexity

Conditions 4

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 15
nop 4
dl 0
loc 20
rs 9.65
c 0
b 0
f 0
1
import os
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import typing
3
from datetime import datetime, timedelta
4
from pathlib import Path
5
from typing import MutableMapping, Optional, TypeVar, Union
6
7
import orjson
0 ignored issues
show
introduced by
Unable to import 'orjson'
Loading history...
8
import pint
0 ignored issues
show
introduced by
Unable to import 'pint'
Loading history...
9
from pint import Quantity
0 ignored issues
show
introduced by
Unable to import 'pint'
Loading history...
10
from pint.errors import PintTypeError
0 ignored issues
show
introduced by
Unable to import 'pint.errors'
Loading history...
11
from pocketutils.core.chars import Chars
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core.chars'
Loading history...
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.core.exceptions import FileDoesNotExistError, MissingResourceError, PathExistsError
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core.exceptions'
Loading history...
14
from pocketutils.core.hashers import Hasher
0 ignored issues
show
introduced by
Unable to import 'pocketutils.core.hashers'
Loading history...
15
from pocketutils.tools.common_tools import CommonTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.common_tools'
Loading history...
16
from pocketutils.tools.unit_tools import UnitTools
0 ignored issues
show
introduced by
Unable to import 'pocketutils.tools.unit_tools'
Loading history...
17
18
from mandos import logger
19
20
_UNIT_REG = pint.UnitRegistry()
21
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...
22
23
24
class MandosResources:
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
25
26
    hasher: Hasher = Hasher("sha256", buffer_size=16 * 1024)
27
    resource_dir = Path(__file__).parent.parent.parent
28
29
    @classmethod
30
    def contains(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> bool:
31
        """Returns whether a resource file (or dir) exists."""
32
        return cls.path(*nodes, suffix=suffix).exists()
33
34
    @classmethod
35
    def path(
36
        cls, *nodes: Union[Path, str], suffix: Optional[str] = None, exists: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
37
    ) -> Path:
38
        """Gets a path of a test resource file under ``resources/``."""
39
        path = Path(cls.resource_dir, "resources", *nodes)
40
        path = path.with_suffix(path.suffix if suffix is None else suffix)
41
        if exists and not path.exists():
42
            raise MissingResourceError(f"Resource {path} missing")
43
        return path
44
45
    @classmethod
46
    def file(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> Path:
47
        """Gets a path of a test resource file under ``resources/``."""
48
        path = cls.path(*nodes, suffix=suffix)
49
        if not path.is_file():
50
            raise PathExistsError(f"Resource {path} is not a file!")
51
        if not os.access(path, os.R_OK):
52
            raise FileDoesNotExistError(f"Resource {path} is not readable")
53
        return path
54
55
    @classmethod
56
    def dir(cls, *nodes: Union[Path, str]) -> Path:
57
        """Gets a path of a test resource file under ``resources/``."""
58
        path = cls.path(*nodes)
59
        if not path.is_dir() and not path.is_mount():
60
            raise PathExistsError(f"Resource {path} is not a directory!")
61
        return path
62
63
    @classmethod
64
    def a_file(cls, *nodes: Union[Path, str], suffixes: Optional[typing.Set[str]] = None) -> Path:
65
        """Gets a path of a test resource file under ``resources/``, ignoring suffix."""
66
        path = Path(cls.resource_dir, "resources", *nodes)
67
        options = [
68
            p
69
            for p in path.parent.glob(path.stem + "*")
70
            if p.is_file() and (suffixes is None or p.suffix in suffixes)
71
        ]
72
        try:
73
            return CommonTools.only(options)
74
        except LookupError:
75
            raise MissingResourceError(f"Resource {path} missing") from None
76
77
    @classmethod
78
    def json(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> NestedDotDict:
79
        """Reads a JSON file under ``resources/``."""
80
        path = cls.path(*nodes, suffix=suffix)
81
        data = orjson.loads(Path(path).read_text(encoding="utf8"))
82
        return NestedDotDict(data)
83
84
    @classmethod
85
    def json_dict(cls, *nodes: Union[Path, str], suffix: Optional[str] = None) -> MutableMapping:
86
        """Reads a JSON file under ``resources/``."""
87
        path = cls.path(*nodes, suffix=suffix)
88
        data = orjson.loads(Path(path).read_text(encoding="utf8"))
89
        return data
90
91
    strings = None
92
93
    @classmethod
94
    def check_expired(cls, path: Path, max_sec: Union[timedelta, int], what: str) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
95
        if isinstance(max_sec, timedelta):
96
            max_sec = max_sec.total_seconds()
97
        # getting the mod date because creation dates are iffy cross-platform
98
        # (in fact the Linux kernel doesn't bother to expose them)
99
        when = datetime.fromtimestamp(path.stat().st_mtime)
100
        delta_sec = (datetime.now() - when).total_seconds()
101
        if delta_sec > max_sec:
102
            delta_str = UnitTools.delta_time_to_str(Chars.narrownbsp)
103
            if delta_sec > 60 * 60 * 24 * 2:
104
                logger.warning(
105
                    f"{what} may be {delta_str} out of date. [downloaded: {when.strftime('%Y-%m-%d')}]"
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

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

Loading history...
106
                )
107
            else:
108
                logger.warning(
109
                    f"{what} may be {delta_str} out of date. [downloaded: {when.strftime('%Y-%m-%d %H:%M:%s')}]"
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/100).

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

Loading history...
110
                )
111
            return True
112
        return False
113
114
    @classmethod
115
    def canonicalize_quantity(cls, s: str, dimensionality: str) -> Quantity:
0 ignored issues
show
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...
116
        """
117
        Returns a quantity in reduced units from a magnitude with units.
118
119
        Args:
120
            s: The string to parse; e.g. ``"1 m/s^2"``.
121
               Unit names and symbols permitted, and spaces may be omitted.
122
            dimensionality: The resulting Quantity is check against this;
123
                            e.g. ``"[length]/[meter]^2"``
124
125
        Returns:
126
            a pint ``Quantity``
127
128
        Raise:
129
            PintTypeError: If the dimensionality is inconsistent
130
        """
131
        q = _UNIT_REG.Quantity(s).to_reduced_units()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "q" 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...
132
        if not q.is_compatible_with(dimensionality):
133
            raise PintTypeError(f"{s} not of dimensionality {dimensionality}")
134
        return q
135
136
137
MandosResources.strings = {
138
    k.partition(":")[2]: v for k, v in MandosResources.json("strings.json").items()
139
}
140
141
142
__all__ = ["MandosResources"]
143