Passed
Push — main ( 60119b...e5c7f7 )
by Douglas
02:02
created

pocketutils.tools.loop_tools   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 108
dl 0
loc 149
rs 10
c 0
b 0
f 0
wmc 18

4 Methods

Rating   Name   Duplication   Size   Complexity  
B LoopTools._loop_timing() 0 25 5
B LoopTools.parallel() 0 36 6
A LoopTools.loop() 0 31 3
A LoopTools._loop_logging() 0 22 4
1
import itertools
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
import logging
3
import multiprocessing
4
import time
5
import warnings
0 ignored issues
show
Unused Code introduced by
The import warnings seems to be unused.
Loading history...
6
from datetime import datetime
7
from typing import (
8
    Any,
9
    Callable,
10
    Collection,
11
    Generator,
12
    Iterable,
13
    Iterator,
14
    Optional,
15
    TypeVar,
16
    Union,
17
)
18
19
from pocketutils.tools.base_tools import BaseTools
20
from pocketutils.tools.unit_tools import UnitTools
21
22
logger = logging.getLogger("pocketutils")
23
T = TypeVar("T")
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...
24
K = TypeVar("K", covariant=True)
0 ignored issues
show
Coding Style Naming introduced by
Class name "K" 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...
25
V = TypeVar("V", contravariant=True)
0 ignored issues
show
Coding Style Naming introduced by
Class name "V" 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...
26
27
28
class LoopTools(BaseTools):
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
29
    @classmethod
30
    def loop(
31
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
32
        things: Iterable[T],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
33
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
34
        log: Union[None, str, Callable[[str], Any]] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
35
        every_i: int = 10,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
36
        n_total: Optional[int] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
37
    ) -> Generator[T, None, None]:
38
        """
39
        Loops over elements while logging time taken, % processed, etc.
40
41
        Args:
42
            things: Items to process; if sized or ``n_total`` is passed,
43
                    will log estimates and % complete
44
            log: Write by calling this function.
45
                 The returned value is ignored.
46
                 if ``str``, gets a function from python logging
47
                 (via :attr:`pocketutils.tools.loop_tools.logger`);
48
                 see :meth:`get_log_function`.
49
                 If ``None``, does not log.
50
            every_i: Log after every ``every_i`` items processed
51
            n_total: Provide if ``things`` is not sized yet the total number
52
                     of items is known
53
        """
54
        log = cls.get_log_function(log)
55
        if hasattr(things, "__len__") or n_total is not None:
56
            # noinspection PyTypeChecker
57
            yield from cls._loop_timing(things, log, every_i, n_total)
58
        else:
59
            yield from cls._loop_logging(things, log, every_i)
60
61
    @classmethod
62
    def parallel(
63
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
64
        items: Collection[K],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
65
        function: Callable[[K], V],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
66
        *,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
67
        to=print,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
Coding Style Naming introduced by
Variable 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...
68
        n_cores: int = 2,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
69
        poll_sec: float = 0.4,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
70
    ) -> None:
71
        """
72
        Process items with multiprocessing and a rotating cursor with % complete.
73
74
        Args:
75
            items: Items to process; must have ``__len__`` defined
76
            function: Called per item
77
            to: Write (log) to this function; ``end="\r"`` is passed for the cursor
78
            n_cores: The number pool cores
79
            poll_sec: Check for new every ``poll_sec`` seconds
80
        """
81
        t0 = time.monotonic()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t0" 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...
82
        if to is not None:
83
            to(f"Using {n_cores} cores...")
84
        with multiprocessing.Pool(n_cores) as pool:
85
            queue = multiprocessing.Manager().Queue()
86
            result = pool.starmap_async(function, items)
87
            cycler = itertools.cycle(r"\|/―")
88
            while not result.ready():
89
                percent = queue.qsize() / len(items)
90
                if to is not None:
91
                    to(f"% complete: {percent:.0%} {next(cycler)}", end="\r")
92
                time.sleep(poll_sec)
93
            got = result.get()
94
        delta = UnitTools.delta_time_to_str(time.monotonic() - t0)
95
        if to is not None:
96
            to(f"\nProcessed {len(got)} items in {delta}")
97
98
    @classmethod
99
    def _loop_logging(
100
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
101
        things: Iterable[T],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
102
        log: Union[None, str, Callable[[str], None]] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
103
        every_i: int = 10,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
104
    ) -> Iterator[T]:
105
        log = cls.get_log_function(log)
106
        initial_start_time = time.monotonic()
107
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
108
        log(f"Started processing at {now}.")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
109
        i = 0
110
        for i, thing in enumerate(things):
111
            t0 = time.monotonic()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t0" 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...
112
            yield thing
113
            t1 = time.monotonic()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t1" 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...
114
            if i % every_i == 0 and i > 0:
115
                elapsed_s = UnitTools.delta_time_to_str(t1 - t0)
116
                log(f"Processed next {every_i} in {elapsed_s}")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
117
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
118
        delta = UnitTools.delta_time_to_str(time.monotonic() - initial_start_time)
119
        log(f"Processed {i}/{i} in {delta}. Done at {now}.")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
120
121
    @classmethod
122
    def _loop_timing(
123
        cls,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
124
        things: Collection[Any],
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
125
        log: Union[None, str, Callable[[str], None]] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
126
        every_i: int = 10,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
127
        n_total: Optional[int] = None,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
128
    ):
129
        log = cls.get_log_function(log)
130
        n_total = len(things) if n_total is None else n_total
131
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
132
        log(f"Started processing {n_total} items at {now}.")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
133
        t0 = time.monotonic()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t0" 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...
134
        initial_start_time = time.monotonic()
135
        for i, thing in enumerate(things):
136
            yield thing
137
            t1 = time.monotonic()
0 ignored issues
show
Coding Style Naming introduced by
Variable name "t1" 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...
138
            if i % every_i == 0 and i < n_total - 1:
139
                estimate = (t1 - initial_start_time) / (i + 1) * (n_total - i - 1)
140
                elapsed_s = UnitTools.delta_time_to_str(t1 - t0)
141
                estimate_s = UnitTools.delta_time_to_str(estimate)
142
                log(f"Processed {i+1}/{n_total} in {elapsed_s}. Estimated {estimate_s} left.")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
143
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
144
        delta = UnitTools.delta_time_to_str(time.monotonic() - initial_start_time)
145
        log(f"Processed {n_total}/{n_total} in {delta}. Done at {now}.")
0 ignored issues
show
introduced by
Use lazy % formatting in logging functions
Loading history...
146
147
148
__all__ = ["LoopTools"]
149