Passed
Push — main ( 6e4731...702ebc )
by Douglas
02:44
created

FilesysTools.check_expired()   B

Complexity

Conditions 8

Size

Total Lines 52
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 24
nop 8
dl 0
loc 52
rs 7.3333
c 0
b 0
f 0

How to fix   Long Method    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
import gzip
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import hashlib
3
import logging
4
import os
5
import pathlib
6
import shutil
7
import stat
8
import sys
9
import tempfile
10
from contextlib import contextmanager
11
from dataclasses import dataclass
12
from datetime import datetime, timedelta
13
from pathlib import Path, PurePath
14
from typing import (
15
    Any,
16
    Callable,
17
    Generator,
18
    Iterable,
19
    Mapping,
20
    Optional,
21
    Sequence,
22
    SupportsBytes,
23
    Tuple,
24
    Type,
25
    Union,
26
)
27
28
import numpy as np
0 ignored issues
show
introduced by
Unable to import 'numpy'
Loading history...
29
import orjson
0 ignored issues
show
introduced by
Unable to import 'orjson'
Loading history...
30
import pandas as pd
0 ignored issues
show
introduced by
Unable to import 'pandas'
Loading history...
31
import regex
0 ignored issues
show
introduced by
Unable to import 'regex'
Loading history...
32
from defusedxml import ElementTree
0 ignored issues
show
introduced by
Unable to import 'defusedxml'
Loading history...
33
34
from pocketutils.core._internal import read_txt_or_gz, write_txt_or_gz
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
35
from pocketutils.core.chars import Chars
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
36
from pocketutils.core.exceptions import (
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
37
    AlreadyUsedError,
38
    ContradictoryRequestError,
39
    DirDoesNotExistError,
40
    FileDoesNotExistError,
41
    ParsingError,
42
)
43
from pocketutils.core.input_output import OpenMode, PathLike, Writeable
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
44
from pocketutils.core.web_resource import WebResource
0 ignored issues
show
Bug introduced by
The name core does not seem to exist in module pocketutils.
Loading history...
introduced by
Unable to import 'pocketutils.core.web_resource'
Loading history...
45
from pocketutils.tools.base_tools import BaseTools
46
from pocketutils.tools.path_tools import PathTools
47
from pocketutils.tools.sys_tools import SystemTools
48
from pocketutils.tools.unit_tools import UnitTools
49
50
logger = logging.getLogger("pocketutils")
51
COMPRESS_LEVEL = 9
52
53
54
@dataclass(frozen=True, repr=True)
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
55
class PathInfo:
56
    """
57
    Info about an extant or nonexistent path as it was at some time.
58
    Use this to avoid making repeated filesystem calls (e.g. ``.is_dir()``):
59
    None of the properties defined here make OS calls.
60
61
    Attributes:
62
        source: The original path used for lookup; may be a symlink
63
        resolved: The fully resolved path, or None if it does not exist
64
        as_of: A datetime immediately before the system calls (system timezone)
65
        real_stat: ``os.stat_result``, or None if the path does not exist
66
        link_stat: ``os.stat_result``, or None if the path is not a symlink
67
        has_access: Path exists and has the 'a' flag set
68
        has_read: Path exists and has the 'r' flag set
69
        has_write: Path exists and has the 'w' flag set
70
71
    All the additional properties refer to the resolved path,
72
    except for :meth:`is_symlink`, :meth:`is_valid_symlink`,
73
    and :meth:`is_broken_symlink`.
74
    """
75
76
    source: Path
77
    resolved: Optional[Path]
78
    as_of: datetime
79
    real_stat: Optional[os.stat_result]
80
    link_stat: Optional[os.stat_result]
81
    has_access: bool
82
    has_read: bool
83
    has_write: bool
84
85
    @property
86
    def mod_or_create_dt(self) -> Optional[datetime]:
87
        """
88
        Returns the modification or access datetime.
89
        Uses whichever is available: creation on Windows and modification on Unix-like.
90
        """
91
        if os.name == "nt":
92
            return self._get_dt("st_ctime")
93
        # will work on posix; on java try anyway
94
        return self._get_dt("st_mtime")
95
96
    @property
97
    def mod_dt(self) -> Optional[datetime]:
98
        """
99
        Returns the modification datetime, if known.
100
        Returns None on Windows or if the path does not exist.
101
        """
102
        if os.name == "nt":
103
            return None
104
        return self._get_dt("st_mtime")
105
106
    @property
107
    def create_dt(self) -> Optional[datetime]:
108
        """
109
        Returns the creation datetime, if known.
110
        Returns None on Unix-like systems or if the path does not exist.
111
        """
112
        if os.name == "posix":
113
            return None
114
        return self._get_dt("st_ctime")
115
116
    @property
117
    def access_dt(self) -> Optional[datetime]:
118
        """
119
        Returns the access datetime.
120
        *Should* never return None if the path exists, but not guaranteed.
121
        """
122
        return self._get_dt("st_atime")
123
124
    @property
125
    def exists(self) -> bool:
126
        """
127
        Returns whether the resolved path exists.
128
        """
129
        return self.real_stat is not None
130
131
    @property
132
    def is_file(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
133
        return self.exists and stat.S_ISREG(self.real_stat.st_mode)
134
135
    @property
136
    def is_dir(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
137
        return self.exists and stat.S_ISDIR(self.real_stat.st_mode)
138
139
    @property
140
    def is_readable_dir(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
141
        return self.is_file and self.has_access and self.has_read
142
143
    @property
144
    def is_writeable_dir(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
145
        return self.is_dir and self.has_access and self.has_write
146
147
    @property
148
    def is_readable_file(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
149
        return self.is_file and self.has_access and self.has_read
150
151
    @property
152
    def is_writeable_file(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
153
        return self.is_file and self.has_access and self.has_write
154
155
    @property
156
    def is_block_device(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
157
        return self.exists and stat.S_ISBLK(self.real_stat.st_mode)
158
159
    @property
160
    def is_char_device(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
161
        return self.exists and stat.S_ISCHR(self.real_stat.st_mode)
162
163
    @property
164
    def is_socket(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
165
        return self.exists and stat.S_ISSOCK(self.real_stat.st_mode)
166
167
    @property
168
    def is_fifo(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
169
        return self.exists and stat.S_ISFIFO(self.real_stat.st_mode)
170
171
    @property
172
    def is_symlink(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
173
        return self.link_stat is not None
174
175
    @property
176
    def is_valid_symlink(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
177
        return self.is_symlink and self.exists
178
179
    @property
180
    def is_broken_symlink(self) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
181
        return self.is_symlink and not self.exists
182
183
    def _get_dt(self, attr: str) -> Optional[datetime]:
184
        if self.real_stat is None:
185
            return None
186
        sec = getattr(self.real_stat, attr)
187
        return datetime.fromtimestamp(sec).astimezone()
188
189
190
class FilesysTools(BaseTools):
0 ignored issues
show
best-practice introduced by
Too many public methods (29/20)
Loading history...
191
    """
192
    Tools for file/directory creation, etc.
193
194
    .. caution::
195
        Some functions may be insecure.
196
    """
197
198
    @classmethod
199
    def read_compressed_text(cls, path: PathLike) -> str:
200
        """
201
        Reads text from a text file, optionally gzipped or bz2-ed.
202
        Recognized suffixes for compression are ``.gz``, ``.gzip``, ``.bz2``, and ``.bzip2``.
203
        """
204
        return read_txt_or_gz(path)
205
206
    @classmethod
207
    def write_compressed_text(cls, txt: str, path: PathLike, *, mkdirs: bool = False) -> None:
208
        """
209
        Writes text to a text file, optionally gzipped or bz2-ed.
210
        Recognized suffixes for compression are ``.gz``, ``.gzip``, ``.bz2``, and ``.bzip2``.
211
        """
212
        write_txt_or_gz(txt, path, mkdirs=mkdirs)
213
214
    @classmethod
215
    def new_webresource(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
216
        cls, url: str, archive_member: Optional[str], local_path: PathLike
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
217
    ) -> WebResource:
218
        return WebResource(url, archive_member, local_path)
219
220
    @classmethod
221
    def is_linux(cls) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
222
        return sys.platform == "linux"
223
224
    @classmethod
225
    def is_windows(cls) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
226
        return sys.platform == "win32"
227
228
    @classmethod
229
    def is_macos(cls) -> bool:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
230
        return sys.platform == "darwin"
231
232
    @classmethod
233
    def get_info(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
234
        cls, path: PathLike, *, expand_user: bool = False, strict: bool = False
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
235
    ) -> PathInfo:
236
        path = Path(path)
237
        has_ignore_error = hasattr(pathlib, "_ignore_error")
238
        if not has_ignore_error:
239
            logger.debug("No _ignore_error found; some OSErrors may be suppressed")
240
        resolved = None
241
        real_stat = None
242
        has_access = False
243
        has_read = False
244
        has_write = False
245
        link_stat = None
246
        as_of = datetime.now().astimezone()
247
        if has_ignore_error or path.is_symlink() or path.exists():
248
            link_stat = cls.__stat_raw(path)
249
        if link_stat is not None:
250
            if expand_user:
251
                resolved = path.expanduser().resolve(strict=strict)
252
            else:
253
                resolved = path.resolve(strict=strict)
254
            if stat.S_ISLNK(link_stat.st_mode):
255
                real_stat = cls.__stat_raw(resolved)
256
            else:
257
                real_stat = link_stat
258
            has_access = os.access(path, os.X_OK, follow_symlinks=True)
259
            has_read = os.access(path, os.R_OK, follow_symlinks=True)
260
            has_write = os.access(path, os.W_OK, follow_symlinks=True)
261
            if not stat.S_ISLNK(link_stat.st_mode):
262
                link_stat = None
263
        return PathInfo(
264
            source=path,
265
            resolved=resolved,
266
            as_of=as_of,
267
            real_stat=real_stat,
268
            link_stat=link_stat,
269
            has_access=has_access,
270
            has_read=has_read,
271
            has_write=has_write,
272
        )
273
274
    @classmethod
275
    def prep_dir(cls, path: PathLike, *, exist_ok: bool = True) -> bool:
276
        """
277
        Prepares a directory by making it if it doesn't exist.
278
        If exist_ok is False, calls ``logger.warning`` if ``path`` already exists
279
        """
280
        path = Path(path)
281
        exists = path.exists()
282
        # On some platforms we get generic exceptions like permissions errors,
283
        # so these are better
284
        if exists and not path.is_dir():
285
            raise DirDoesNotExistError(f"Path {path} exists but is not a file")
286
        if exists and not exist_ok:
287
            logger.warning(f"Directory {path} already exists")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
288
        if not exists:
289
            # NOTE! exist_ok in mkdir throws an error on Windows
290
            path.mkdir(parents=True)
291
        return exists
292
293
    @classmethod
294
    def prep_file(cls, path: PathLike, *, exist_ok: bool = True) -> None:
295
        """
296
        Prepares a file path by making its parent directory.
297
        Same as ``pathlib.Path.mkdir`` but makes sure ``path`` is a file if it exists.
298
        """
299
        # On some platforms we get generic exceptions like permissions errors, so these are better
300
        path = Path(path)
301
        # check for errors first; don't make the dirs and then fail
302
        if path.exists() and not path.is_file() and not path.is_symlink():
303
            raise FileDoesNotExistError(f"Path {path} exists but is not a file")
304
        Path(path.parent).mkdir(parents=True, exist_ok=exist_ok)
305
306
    @classmethod
307
    def dump_error(
0 ignored issues
show
Coding Style Naming introduced by
Argument name "e" 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...
308
        cls, e: Optional[BaseException], path: Union[None, PathLike, datetime] = None
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
309
    ) -> Path:
310
        """
311
        Writes a .json file containing the error message, stack trace, and sys info.
312
        System info is from :meth:`get_env_info`.
313
        """
314
        if path is None:
315
            path = f"err-dump-{cls.dt_for_filesys()}.json"
316
        elif isinstance(path, datetime):
317
            path = f"err-dump-{cls.dt_for_filesys(path)}.json"
318
        path = Path(path)
319
        data = cls.dump_error_as_dict(e)
320
        data = orjson.dumps(data, option=orjson.OPT_INDENT_2)
321
        path.write_bytes(data)
322
        return path
323
324
    @classmethod
325
    def dump_error_as_dict(cls, e: Optional[BaseException]) -> Mapping[str, Any]:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "e" 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...
introduced by
Missing function or method docstring
Loading history...
326
        try:
327
            system = SystemTools.get_env_info()
328
        except BaseException as e2:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as BaseException is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
Variable name "e2" 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...
329
            system = f"UNKNOWN << {e2} >>"
330
        msg, tb = SystemTools.serialize_exception(e)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "tb" 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...
331
        tb = [t.as_dict() for t in tb]
0 ignored issues
show
Coding Style Naming introduced by
Variable name "tb" 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...
332
        return dict(message=msg, stacktrace=tb, system=system)
333
334
    @classmethod
335
    def dt_for_filesys(cls, dt: Optional[datetime] = None) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument name "dt" 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...
introduced by
Missing function or method docstring
Loading history...
336
        if dt is None:
337
            dt = datetime.now()
338
        return dt.strftime("%Y-%m-%d_%H-%M-%S")
339
340
    @classmethod
341
    def delete_surefire(cls, path: PathLike) -> Optional[Exception]:
342
        """
343
        Deletes files or directories cross-platform, but working around multiple issues in Windows.
344
345
        Returns:
346
            None, or an Exception for minor warnings
347
348
        Raises:
349
            IOError: If it can't delete
350
        """
351
        # we need this because of Windows
352
        path = Path(path)
353
        logger.debug(f"Permanently deleting {path} ...")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
354
        chmod_err = None
355
        try:
356
            os.chmod(str(path), stat.S_IRWXU)
357
        except Exception as e:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
Coding Style Naming introduced by
Variable name "e" 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...
358
            chmod_err = e
359
        # another reason for returning exception:
360
        # We don't want to interrupt the current line being printed like in slow_delete
361
        if path.is_dir():
362
            shutil.rmtree(str(path), ignore_errors=True)  # ignore_errors because of Windows
363
            try:
364
                path.unlink(missing_ok=True)  # again, because of Windows
365
            except IOError:
366
                pass  # almost definitely because it doesn't exist
367
        else:
368
            path.unlink(missing_ok=True)
369
        logger.debug(f"Permanently deleted {path}")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
370
        return chmod_err
371
372
    @classmethod
373
    def trash(cls, path: PathLike, trash_dir: Optional[PathLike] = None) -> None:
374
        """
375
        Trash a file or directory.
376
377
        Args:
378
            path: The path to move to the trash
379
            trash_dir: If None, uses :meth:`pocketutils.tools.path_tools.PathTools.guess_trash`
380
        """
381
        if trash_dir is None:
382
            trash_dir = PathTools.guess_trash()
383
        logger.debug(f"Trashing {path} to {trash_dir} ...")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
384
        shutil.move(str(path), str(trash_dir))
385
        logger.debug(f"Trashed {path} to {trash_dir}")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
386
387
    @classmethod
388
    def try_cleanup(cls, path: Path, *, bound: Type[Exception] = PermissionError) -> None:
389
        """
390
        Try to delete a file (probably temp file), if it exists, and log any ``PermissionError``.
391
        """
392
        path = Path(path)
393
        # noinspection PyBroadException
394
        try:
395
            path.unlink(missing_ok=True)
396
        except bound:
397
            logger.error(f"Permission error preventing deleting {path}")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
398
399
    @classmethod
400
    def read_lines_file(cls, path: PathLike, *, ignore_comments: bool = False) -> Sequence[str]:
401
        """
402
        Returns a list of lines in the file.
403
        Optionally skips lines starting with ``#`` or that only contain whitespace.
404
        """
405
        lines = []
406
        with cls.open_file(path, "r") as f:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "f" 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...
407
            for line in f.readlines():
408
                line = line.strip()
409
                if not ignore_comments or not line.startswith("#") and not len(line.strip()) == 0:
410
                    lines.append(line)
411
        return lines
412
413
    @classmethod
414
    def read_properties_file(cls, path: PathLike) -> Mapping[str, str]:
415
        """
416
        Reads a .properties file.
417
        A list of lines with key=value pairs (with an equals sign).
418
        Lines beginning with # are ignored.
419
        Each line must contain exactly 1 equals sign.
420
421
        .. caution::
422
            The escaping is not compliant with the standard
423
424
        Args:
425
            path: Read the file at this local path
426
427
        Returns:
428
            A dict mapping keys to values, both with surrounding whitespace stripped
429
        """
430
        dct = {}
431
        with cls.open_file(path, "r") as f:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "f" 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...
432
            for i, line in enumerate(f.readlines()):
433
                line = line.strip()
434
                if len(line) == 0 or line.startswith("#"):
435
                    continue
436
                if line.count("=") != 1:
437
                    raise ParsingError(f"Bad line {i} in {path}", resource=path)
438
                k, v = line.split("=")
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" 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...
439
                k, v = k.strip(), v.strip()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" 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...
440
                if k in dct:
441
                    raise AlreadyUsedError(f"Duplicate property {k} (line {i})", key=k)
442
                dct[k] = v
443
        return dct
444
445
    @classmethod
446
    def write_properties_file(
447
        cls, properties: Mapping[Any, Any], path: Union[str, PurePath], mode: str = "o"
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
448
    ) -> None:
449
        """
450
        Writes a .properties file.
451
452
        .. caution::
453
            The escaping is not compliant with the standard
454
        """
455
        if not OpenMode(mode).write:
456
            raise ContradictoryRequestError(f"Cannot write text to {path} in mode {mode}")
457
        with FilesysTools.open_file(path, mode) as f:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "f" 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...
458
            bads = []
459
            for k, v in properties.items():
0 ignored issues
show
Coding Style Naming introduced by
Variable name "v" 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...
460
                if "=" in k or "=" in v or "\n" in k or "\n" in v:
461
                    bads.append(k)
462
                f.write(
463
                    str(k).replace("=", "--").replace("\n", "\\n")
464
                    + "="
465
                    + str(v).replace("=", "--").replace("\n", "\\n")
466
                    + "\n"
467
                )
468
            if 0 < len(bads) <= 10:
469
                logger.warning(
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
470
                    "At least one properties entry contains an equals sign or newline (\\n)."
471
                    f"These were escaped: {', '.join(bads)}"
472
                )
473
            elif len(bads) > 0:
474
                logger.warning(
475
                    "At least one properties entry contains an equals sign or newline (\\n),"
476
                    "which were escaped."
477
                )
478
479
    @classmethod
480
    def save_json(cls, data: Any, path: PathLike, mode: str = "w") -> None:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
481
        with cls.open_file(path, mode) as f:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "f" 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...
482
            f.write(orjson.dumps(data).decode(encoding="utf8"))
483
484
    @classmethod
485
    def load_json(cls, path: PathLike) -> Union[dict, list]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
486
        return orjson.loads(Path(path).read_text(encoding="utf8"))
487
488
    @classmethod
489
    def read_any(
0 ignored issues
show
best-practice introduced by
Too many return statements (10/6)
Loading history...
490
        cls, path: PathLike
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
491
    ) -> Union[
492
        str,
493
        bytes,
494
        Sequence[str],
495
        pd.DataFrame,
496
        Sequence[int],
497
        Sequence[float],
498
        Sequence[str],
499
        Mapping[str, str],
500
    ]:
501
        """
502
        Reads a variety of simple formats based on filename extension.
503
        Includes '.txt', 'csv', .xml', '.properties', '.json'.
504
        Also reads '.data' (binary), '.lines' (text lines).
505
        And formatted lists: '.strings', '.floats', and '.ints' (ex: "[1, 2, 3]").
506
        """
507
        path = Path(path)
508
        ext = path.suffix.lstrip(".")
509
510
        def load_list(dtype):
511
            return [
512
                dtype(s)
513
                for s in FilesysTools.read_lines_file(path)[0]
514
                .replace(" ", "")
515
                .replace("[", "")
516
                .replace("]", "")
517
                .split(",")
518
            ]
519
520
        if ext == "lines":
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
521
            return cls.read_lines_file(path)
522
        elif ext == "txt":
523
            return path.read_text(encoding="utf-8")
524
        elif ext == "data":
525
            return path.read_bytes()
526
        elif ext == "json":
527
            return cls.load_json(path)
528
        elif ext in ["npy", "npz"]:
529
            return np.load(str(path), allow_pickle=False, encoding="utf8")
530
        elif ext == "properties":
531
            return cls.read_properties_file(path)
532
        elif ext == "csv":
533
            return pd.read_csv(path, encoding="utf8")
534
        elif ext == "ints":
535
            return load_list(int)
536
        elif ext == "floats":
537
            return load_list(float)
538
        elif ext == "strings":
539
            return load_list(str)
540
        elif ext == "xml":
541
            ElementTree.parse(path).getroot()
542
        else:
543
            raise TypeError(f"Did not recognize resource file type for file {path}")
544
545
    @classmethod
546
    @contextmanager
547
    def open_file(cls, path: PathLike, mode: Union[OpenMode, str], *, mkdir: bool = False):
548
        """
549
        Opens a text file, always using utf8, optionally gzipped.
550
551
        See Also:
552
            :class:`pocketutils.core.input_output.OpenMode`
553
        """
554
        path = Path(path)
555
        mode = OpenMode(mode)
556
        if mode.write and mkdir:
557
            path.parent.mkdir(exist_ok=True, parents=True)
558
        if not mode.read:
559
            cls.prep_file(path, exist_ok=mode.overwrite or mode.append)
560
        if mode.gzipped:
561
            yield gzip.open(path, mode.internal, compresslevel=COMPRESS_LEVEL, encoding="utf8")
562
        elif mode.binary:
563
            yield open(path, mode.internal, encoding="utf8")
564
        else:
565
            yield open(path, mode.internal, encoding="utf8")
566
567
    @classmethod
568
    def write_lines(cls, iterable: Iterable[Any], path: PathLike, mode: str = "w") -> int:
569
        r"""
570
        Just writes an iterable line-by-line to a file, using '\n'.
571
572
        Makes the parent directory if needed.
573
        Checks that the iterable is a "true iterable" (not a string or bytes).
574
575
        Returns:
576
            The number of lines written (the same as len(iterable) if iterable has a length)
577
578
        Raises:
579
            FileExistsError: If the path exists and append is False
580
            PathIsNotFileError: If append is True, and the path exists but is not a file
581
        """
582
        if not cls.is_true_iterable(iterable):
583
            raise TypeError("Not a true iterable")  # TODO include iterable if small
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
584
        n = 0
0 ignored issues
show
Coding Style Naming introduced by
Variable name "n" 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...
585
        with cls.open_file(path, mode) as f:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "f" 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...
586
            for x in iterable:
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...
587
                f.write(str(x) + "\n")
588
            n += 1
0 ignored issues
show
Coding Style Naming introduced by
Variable name "n" 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...
589
        return n
590
591
    @classmethod
592
    def hash_hex(cls, x: SupportsBytes, algorithm: str) -> str:
0 ignored issues
show
Coding Style Naming introduced by
Argument 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...
593
        """
594
        Returns the hex-encoded hash of the object (converted to bytes).
595
        """
596
        m = hashlib.new(algorithm)
0 ignored issues
show
Coding Style Naming introduced by
Variable name "m" 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...
597
        m.update(bytes(x))
598
        return m.hexdigest()
599
600
    @classmethod
601
    def replace_in_file(cls, path: PathLike, changes: Mapping[str, str]) -> None:
602
        """
603
        Uses ``regex.sub`` repeatedly to modify (AND REPLACE) a file's content.
604
        """
605
        path = Path(path)
606
        data = path.read_text(encoding="utf-8")
607
        for key, value in changes.items():
608
            data = regex.sub(key, value, data, flags=regex.V1 | regex.MULTILINE | regex.DOTALL)
609
        path.write_text(data, encoding="utf-8")
610
611
    @classmethod
612
    def tmppath(cls, path: Optional[PathLike] = None, **kwargs) -> Generator[Path, None, None]:
613
        """
614
        Makes a temporary Path. Won't create ``path`` but will delete it at the end.
615
        If ``path`` is None, will use ``tempfile.mkstemp``.
616
        """
617
        if path is None:
618
            _, path = tempfile.mkstemp()
619
        try:
620
            yield Path(path, **kwargs)
621
        finally:
622
            Path(path).unlink()
623
624
    @classmethod
625
    def tmpfile(
626
        cls, path: Optional[PathLike] = None, *, spooled: bool = False, **kwargs
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
627
    ) -> Generator[Writeable, None, None]:
628
        """
629
        Simple wrapper around tempfile functions.
630
        Wraps ``TemporaryFile``, ``NamedTemporaryFile``, and ``SpooledTemporaryFile``.
631
        """
632
        if spooled:
633
            with tempfile.SpooledTemporaryFile(**kwargs) as x:
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...
634
                yield x
635
        elif path is None:
636
            with tempfile.TemporaryFile(**kwargs) as x:
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...
637
                yield x
638
        else:
639
            with tempfile.NamedTemporaryFile(str(path), **kwargs) as x:
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...
640
                yield x
641
642
    @classmethod
643
    def tmpdir(cls, **kwargs) -> Generator[Path, None, None]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
644
        with tempfile.TemporaryDirectory(**kwargs) as x:
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...
645
            yield Path(x)
646
647
    @classmethod
648
    def check_expired(
649
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
650
        path: PathLike,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
651
        max_sec: Union[timedelta, float],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
652
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
653
        parent: Optional[PathLike] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
654
        warn_expired_fmt: str = "{path_rel} is {delta} out of date [{mod_rel}]",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
655
        warn_unknown_fmt: str = "{path_rel} mod date is unknown [created: {create_rel}]",
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
656
        log: Optional[Callable[[str], Any]] = logger.warning,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
657
    ) -> Optional[bool]:
658
        """
659
        Warns and returns True if ``path`` mod date is more than ``max_sec`` in the past.
660
        Returns None if it could not be determined.
661
662
        The formatting strings can refer to any of these (will be empty if unknown):
663
            - path: Full path
664
            - name: File/dir name
665
            - path_rel: Path relative to ``self._dir``, or full path if not under
666
            - now: formatted current datetime
667
            - [mod/create]_dt: Formatted mod/creation datetime
668
            - [mod/create]_rel: Mod/create datetime in terms of offset from now
669
            - [mod/create]_delta: Formatted timedelta from now
670
            - [mod/create]_delta_sec: Number of seconds from now (negative if now < mod/create dt)
671
672
        Args:
673
            path: A specific path to check
674
            max_sec: Max seconds, or a timedelta
675
            parent: If provided, path_rel is relative to this directory (to simplify warnings)
676
            warn_expired_fmt: Formatting string in terms of the variables listed above
677
            warn_unknown_fmt: Formatting string in terms of the variables listed above
678
            log: Log about problems
679
680
        Returns:
681
            Whether it is expired, or None if it could not be determined
682
        """
683
        path = Path(path)
684
        if log is None:
685
686
            def log(_):
687
                return None
688
689
        limit = max_sec if isinstance(max_sec, timedelta) else timedelta(seconds=max_sec)
690
        now = datetime.now().astimezone()
691
        info = FilesysTools.get_info(path)
692
        if info.mod_dt and now - info.mod_dt > limit:
0 ignored issues
show
unused-code introduced by
Unnecessary "elif" after "return"
Loading history...
693
            cls._warn_expired(now, info.mod_dt, info.create_dt, path, parent, warn_expired_fmt, log)
694
            return True
695
        elif not info.mod_dt and (not info.create_dt or (now - info.create_dt) > limit):
696
            cls._warn_expired(now, info.mod_dt, info.create_dt, path, parent, warn_unknown_fmt, log)
697
            return None
698
        return False
699
700
    @classmethod
701
    def _warn_expired(
0 ignored issues
show
best-practice introduced by
Too many arguments (8/5)
Loading history...
Comprehensibility introduced by
This function exceeds the maximum number of variables (20/15).
Loading history...
702
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
703
        now: datetime,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
704
        mod: Optional[datetime],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
705
        created: Optional[datetime],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
706
        path: Path,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
707
        parent: Optional[Path],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
708
        fmt: Optional[str],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
709
        log: Callable[[str], Any],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
710
    ):
711
        if isinstance(fmt, str):
712
            fmt = fmt.format
713
        if parent is not None and path.is_relative_to(parent):
714
            path_rel = str(path.relative_to(parent))
715
        else:
716
            path_rel = str(path)
717
        now_str, mod_str, mod_rel, mod_delta, mod_delta_sec = cls._expire_warning_info(now, mod)
718
        _, create_str, create_rel, create_delta, create_delta_sec = cls._expire_warning_info(
719
            now, created
720
        )
721
        msg = fmt(
722
            path=path,
723
            path_rel=path_rel,
724
            name=path.name,
725
            now=now_str,
726
            mod_dt=mod_str,
727
            mod_rel=mod_rel,
728
            mod_delta=mod_delta,
729
            mod_sec=mod_delta_sec,
730
            create_dt=create_str,
731
            create_rel=create_rel,
732
            create_delta=create_delta,
733
            create_sec=create_delta_sec,
734
        )
735
        log(msg)
736
737
    @classmethod
738
    def _expire_warning_info(
739
        cls, now: datetime, then: Optional[datetime]
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
740
    ) -> Tuple[str, str, str, str, str]:
741
        now_str = now.strftime("%Y-%m-%d %H:%M:%S")
742
        if then is None:
743
            return now_str, "", "", "", ""
744
        delta = now - then
745
        then_str = then.strftime("%Y-%m-%d %H:%M:%S")
746
        then_rel = UnitTools.approx_time_wrt(now, then)
747
        delta_str = UnitTools.delta_time_to_str(delta, space=Chars.narrownbsp)
748
        return now_str, then_str, then_rel, delta_str, str(delta.total_seconds())
749
750
    @classmethod
751
    def __stat_raw(cls, path: Path) -> Optional[os.stat_result]:
752
        try:
753
            return path.lstat()
754
        except OSError as e:
0 ignored issues
show
Coding Style Naming introduced by
Variable name "e" 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...
755
            if hasattr(pathlib, "_ignore_error") and not pathlib._ignore_error(e):
0 ignored issues
show
Bug introduced by
The Module pathlib does not seem to have a member named _ignore_error.

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...
Coding Style Best Practice introduced by
It seems like _ignore_error was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
756
                raise
757
        return None
758
759
760
__all__ = ["FilesysTools", "PathInfo"]
761