python-utils 3.8.2__py2.py3-none-any.whl → 3.9.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,22 @@
1
+ """
2
+ This module provides generator utilities for batching items from
3
+ iterables and async iterables.
4
+
5
+ Functions:
6
+ abatcher(generator, batch_size=None, interval=None):
7
+ Asyncio generator wrapper that returns items with a given batch
8
+ size or interval (whichever is reached first).
9
+
10
+ batcher(iterable, batch_size=10):
11
+ Generator wrapper that returns items with a given batch size.
12
+ """
13
+
1
14
  import asyncio
2
15
  import time
3
16
 
4
17
  import python_utils
5
18
  from python_utils import types
6
19
 
7
-
8
20
  _T = types.TypeVar('_T')
9
21
 
10
22
 
@@ -16,10 +28,21 @@ async def abatcher(
16
28
  batch_size: types.Optional[int] = None,
17
29
  interval: types.Optional[types.delta_type] = None,
18
30
  ) -> types.AsyncGenerator[types.List[_T], None]:
19
- '''
31
+ """
20
32
  Asyncio generator wrapper that returns items with a given batch size or
21
33
  interval (whichever is reached first).
22
- '''
34
+
35
+ Args:
36
+ generator: The async generator or iterator to batch.
37
+ batch_size (types.Optional[int], optional): The number of items per
38
+ batch. Defaults to None.
39
+ interval (types.Optional[types.delta_type], optional): The time
40
+ interval to wait before yielding a batch. Defaults to None.
41
+
42
+ Yields:
43
+ types.AsyncGenerator[types.List[_T], None]: A generator that yields
44
+ batches of items.
45
+ """
23
46
  batch: types.List[_T] = []
24
47
 
25
48
  assert batch_size or interval, 'Must specify either batch_size or interval'
@@ -80,9 +103,18 @@ def batcher(
80
103
  iterable: types.Iterable[_T],
81
104
  batch_size: int = 10,
82
105
  ) -> types.Generator[types.List[_T], None, None]:
83
- '''
84
- Generator wrapper that returns items with a given batch size
85
- '''
106
+ """
107
+ Generator wrapper that returns items with a given batch size.
108
+
109
+ Args:
110
+ iterable (types.Iterable[_T]): The iterable to batch.
111
+ batch_size (int, optional): The number of items per batch. Defaults
112
+ to 10.
113
+
114
+ Yields:
115
+ types.Generator[types.List[_T], None, None]: A generator that yields
116
+ batches of items.
117
+ """
86
118
  batch: types.List[_T] = []
87
119
  for item in iterable:
88
120
  batch.append(item)
python_utils/import_.py CHANGED
@@ -1,33 +1,51 @@
1
+ """
2
+ This module provides utilities for importing modules and handling exceptions.
3
+
4
+ Classes:
5
+ DummyError(Exception):
6
+ A custom exception class used as a default for exception handling.
7
+
8
+ Functions:
9
+ import_global(name, modules=None, exceptions=DummyError, locals_=None,
10
+ globals_=None, level=-1):
11
+ Imports the requested items into the global scope, with support for
12
+ relative imports and custom exception handling.
13
+ """
14
+
1
15
  from . import types
2
16
 
3
17
 
4
- class DummyException(Exception):
5
- pass
18
+ class DummyError(Exception):
19
+ """A custom exception class used as a default for exception handling."""
20
+
21
+
22
+ # Legacy alias for DummyError
23
+ DummyException = DummyError
6
24
 
7
25
 
8
- def import_global(
26
+ def import_global( # noqa: C901
9
27
  name: str,
10
28
  modules: types.Optional[types.List[str]] = None,
11
- exceptions: types.ExceptionsType = DummyException,
29
+ exceptions: types.ExceptionsType = DummyError,
12
30
  locals_: types.OptionalScope = None,
13
31
  globals_: types.OptionalScope = None,
14
32
  level: int = -1,
15
- ) -> types.Any:
16
- '''Import the requested items into the global scope
33
+ ) -> types.Any: # sourcery skip: hoist-if-from-if
34
+ """Import the requested items into the global scope.
17
35
 
18
36
  WARNING! this method _will_ overwrite your global scope
19
- If you have a variable named "path" and you call import_global('sys')
20
- it will be overwritten with sys.path
37
+ If you have a variable named `path` and you call `import_global('sys')`
38
+ it will be overwritten with `sys.path`
21
39
 
22
40
  Args:
23
41
  name (str): the name of the module to import, e.g. sys
24
42
  modules (str): the modules to import, use None for everything
25
- exception (Exception): the exception to catch, e.g. ImportError
26
- `locals_`: the `locals()` method (in case you need a different scope)
27
- `globals_`: the `globals()` method (in case you need a different scope)
43
+ exceptions (Exception): the exception to catch, e.g. ImportError
44
+ locals_: the `locals()` method (in case you need a different scope)
45
+ globals_: the `globals()` method (in case you need a different scope)
28
46
  level (int): the level to import from, this can be used for
29
47
  relative imports
30
- '''
48
+ """
31
49
  frame = None
32
50
  name_parts: types.List[str] = name.split('.')
33
51
  modules_set: types.Set[str] = set()
@@ -65,8 +83,10 @@ def import_global(
65
83
  try:
66
84
  for attr in name_parts[1:]:
67
85
  module = getattr(module, attr)
68
- except AttributeError:
69
- raise ImportError('No module named ' + '.'.join(name_parts))
86
+ except AttributeError as e:
87
+ raise ImportError(
88
+ 'No module named ' + '.'.join(name_parts)
89
+ ) from e
70
90
 
71
91
  # If no list of modules is given, autodetect from either __all__
72
92
  # or a dir() of the module
python_utils/logger.py CHANGED
@@ -1,3 +1,30 @@
1
+ """
2
+ This module provides a base class `LoggerBase` and a derived class `Logged`
3
+ for adding logging capabilities to classes. The `LoggerBase` class expects
4
+ a `logger` attribute to be a `logging.Logger` or compatible instance and
5
+ provides methods for logging at various levels. The `Logged` class
6
+ automatically adds a named logger to the class.
7
+
8
+ Classes:
9
+ LoggerBase:
10
+ A base class that adds logging utilities to a class.
11
+ Logged:
12
+ A derived class that automatically adds a named logger to a class.
13
+
14
+ Example:
15
+ >>> class MyClass(Logged):
16
+ ... def __init__(self):
17
+ ... Logged.__init__(self)
18
+
19
+ >>> my_class = MyClass()
20
+ >>> my_class.debug('debug')
21
+ >>> my_class.info('info')
22
+ >>> my_class.warning('warning')
23
+ >>> my_class.error('error')
24
+ >>> my_class.exception('exception')
25
+ >>> my_class.log(0, 'log')
26
+ """
27
+
1
28
  import abc
2
29
  import logging
3
30
 
@@ -24,8 +51,81 @@ _P = types.ParamSpec('_P')
24
51
  _T = types.TypeVar('_T', covariant=True)
25
52
 
26
53
 
54
+ class LoggerProtocol(types.Protocol):
55
+ def debug(
56
+ self,
57
+ msg: object,
58
+ *args: object,
59
+ exc_info: _ExcInfoType = None,
60
+ stack_info: bool = False,
61
+ stacklevel: int = 1,
62
+ extra: types.Union[types.Mapping[str, object], None] = None,
63
+ ) -> None: ...
64
+
65
+ def info(
66
+ self,
67
+ msg: object,
68
+ *args: object,
69
+ exc_info: _ExcInfoType = None,
70
+ stack_info: bool = False,
71
+ stacklevel: int = 1,
72
+ extra: types.Union[types.Mapping[str, object], None] = None,
73
+ ) -> None: ...
74
+
75
+ def warning(
76
+ self,
77
+ msg: object,
78
+ *args: object,
79
+ exc_info: _ExcInfoType = None,
80
+ stack_info: bool = False,
81
+ stacklevel: int = 1,
82
+ extra: types.Union[types.Mapping[str, object], None] = None,
83
+ ) -> None: ...
84
+
85
+ def error(
86
+ self,
87
+ msg: object,
88
+ *args: object,
89
+ exc_info: _ExcInfoType = None,
90
+ stack_info: bool = False,
91
+ stacklevel: int = 1,
92
+ extra: types.Union[types.Mapping[str, object], None] = None,
93
+ ) -> None: ...
94
+
95
+ def critical(
96
+ self,
97
+ msg: object,
98
+ *args: object,
99
+ exc_info: _ExcInfoType = None,
100
+ stack_info: bool = False,
101
+ stacklevel: int = 1,
102
+ extra: types.Union[types.Mapping[str, object], None] = None,
103
+ ) -> None: ...
104
+
105
+ def exception(
106
+ self,
107
+ msg: object,
108
+ *args: object,
109
+ exc_info: _ExcInfoType = None,
110
+ stack_info: bool = False,
111
+ stacklevel: int = 1,
112
+ extra: types.Union[types.Mapping[str, object], None] = None,
113
+ ) -> None: ...
114
+
115
+ def log(
116
+ self,
117
+ level: int,
118
+ msg: object,
119
+ *args: object,
120
+ exc_info: _ExcInfoType = None,
121
+ stack_info: bool = False,
122
+ stacklevel: int = 1,
123
+ extra: types.Union[types.Mapping[str, object], None] = None,
124
+ ) -> None: ...
125
+
126
+
27
127
  class LoggerBase(abc.ABC):
28
- '''Class which automatically adds logging utilities to your class when
128
+ """Class which automatically adds logging utilities to your class when
29
129
  interiting. Expects `logger` to be a logging.Logger or compatible instance.
30
130
 
31
131
  Adds easy access to debug, info, warning, error, exception and log methods
@@ -43,11 +143,13 @@ class LoggerBase(abc.ABC):
43
143
  >>> my_class.error('error')
44
144
  >>> my_class.exception('exception')
45
145
  >>> my_class.log(0, 'log')
46
- '''
146
+ """
47
147
 
48
- # Being a tad lazy here and not creating a Protocol.
49
- # The actual classes define the correct type anyway
148
+ # I've tried using a protocol to properly type the logger but it gave all
149
+ # sorts of issues with mypy so we're using the lazy solution for now. The
150
+ # actual classes define the correct type anyway
50
151
  logger: types.Any
152
+ # logger: LoggerProtocol
51
153
 
52
154
  @classmethod
53
155
  def __get_name( # pyright: ignore[reportUnusedFunction]
@@ -66,7 +168,7 @@ class LoggerBase(abc.ABC):
66
168
  stacklevel: int = 1,
67
169
  extra: types.Union[types.Mapping[str, object], None] = None,
68
170
  ) -> None:
69
- return cls.logger.debug(
171
+ return cls.logger.debug( # type: ignore[no-any-return]
70
172
  msg,
71
173
  *args,
72
174
  exc_info=exc_info,
@@ -86,7 +188,7 @@ class LoggerBase(abc.ABC):
86
188
  stacklevel: int = 1,
87
189
  extra: types.Union[types.Mapping[str, object], None] = None,
88
190
  ) -> None:
89
- return cls.logger.info(
191
+ return cls.logger.info( # type: ignore[no-any-return]
90
192
  msg,
91
193
  *args,
92
194
  exc_info=exc_info,
@@ -106,7 +208,7 @@ class LoggerBase(abc.ABC):
106
208
  stacklevel: int = 1,
107
209
  extra: types.Union[types.Mapping[str, object], None] = None,
108
210
  ) -> None:
109
- return cls.logger.warning(
211
+ return cls.logger.warning( # type: ignore[no-any-return]
110
212
  msg,
111
213
  *args,
112
214
  exc_info=exc_info,
@@ -126,7 +228,7 @@ class LoggerBase(abc.ABC):
126
228
  stacklevel: int = 1,
127
229
  extra: types.Union[types.Mapping[str, object], None] = None,
128
230
  ) -> None:
129
- return cls.logger.error(
231
+ return cls.logger.error( # type: ignore[no-any-return]
130
232
  msg,
131
233
  *args,
132
234
  exc_info=exc_info,
@@ -146,7 +248,7 @@ class LoggerBase(abc.ABC):
146
248
  stacklevel: int = 1,
147
249
  extra: types.Union[types.Mapping[str, object], None] = None,
148
250
  ) -> None:
149
- return cls.logger.critical(
251
+ return cls.logger.critical( # type: ignore[no-any-return]
150
252
  msg,
151
253
  *args,
152
254
  exc_info=exc_info,
@@ -166,7 +268,7 @@ class LoggerBase(abc.ABC):
166
268
  stacklevel: int = 1,
167
269
  extra: types.Union[types.Mapping[str, object], None] = None,
168
270
  ) -> None:
169
- return cls.logger.exception(
271
+ return cls.logger.exception( # type: ignore[no-any-return]
170
272
  msg,
171
273
  *args,
172
274
  exc_info=exc_info,
@@ -187,7 +289,7 @@ class LoggerBase(abc.ABC):
187
289
  stacklevel: int = 1,
188
290
  extra: types.Union[types.Mapping[str, object], None] = None,
189
291
  ) -> None:
190
- return cls.logger.log(
292
+ return cls.logger.log( # type: ignore[no-any-return]
191
293
  level,
192
294
  msg,
193
295
  *args,
@@ -199,8 +301,8 @@ class LoggerBase(abc.ABC):
199
301
 
200
302
 
201
303
  class Logged(LoggerBase):
202
- '''Class which automatically adds a named logger to your class when
203
- interiting
304
+ """Class which automatically adds a named logger to your class when
305
+ interiting.
204
306
 
205
307
  Adds easy access to debug, info, warning, error, exception and log methods
206
308
 
@@ -218,16 +320,31 @@ class Logged(LoggerBase):
218
320
 
219
321
  >>> my_class._Logged__get_name('spam')
220
322
  'spam'
221
- '''
323
+ """
222
324
 
223
325
  logger: logging.Logger # pragma: no cover
224
326
 
225
327
  @classmethod
226
328
  def __get_name(cls, *name_parts: str) -> str:
227
- return LoggerBase._LoggerBase__get_name(*name_parts) # type: ignore
329
+ return types.cast(
330
+ str,
331
+ LoggerBase._LoggerBase__get_name(*name_parts), # type: ignore[attr-defined] # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue]
332
+ )
333
+
334
+ def __new__(cls, *args: types.Any, **kwargs: types.Any) -> 'Logged':
335
+ """
336
+ Create a new instance of the class and initialize the logger.
337
+
338
+ The logger is named using the module and class name.
339
+
340
+ Args:
341
+ *args: Variable length argument list.
342
+ **kwargs: Arbitrary keyword arguments.
228
343
 
229
- def __new__(cls, *args: types.Any, **kwargs: types.Any):
344
+ Returns:
345
+ An instance of the class.
346
+ """
230
347
  cls.logger = logging.getLogger(
231
348
  cls.__get_name(cls.__module__, cls.__name__)
232
349
  )
233
- return super(Logged, cls).__new__(cls)
350
+ return super().__new__(cls)
python_utils/loguru.py CHANGED
@@ -1,4 +1,21 @@
1
+ """
2
+ This module provides a `Logurud` class that integrates the `loguru` logger
3
+ with the base logging functionality defined in `logger_module.LoggerBase`.
4
+
5
+ Classes:
6
+ Logurud: A class that extends `LoggerBase` and uses `loguru` for logging.
7
+
8
+ Usage example:
9
+ >>> from python_utils.loguru import Logurud
10
+ >>> class MyClass(Logurud):
11
+ ... def __init__(self):
12
+ ... Logurud.__init__(self)
13
+ >>> my_class = MyClass()
14
+ >>> my_class.logger.info('This is an info message')
15
+ """
16
+
1
17
  from __future__ import annotations
18
+
2
19
  import typing
3
20
 
4
21
  import loguru
@@ -9,8 +26,26 @@ __all__ = ['Logurud']
9
26
 
10
27
 
11
28
  class Logurud(logger_module.LoggerBase):
29
+ """
30
+ A class that extends `LoggerBase` and uses `loguru` for logging.
31
+
32
+ Attributes:
33
+ logger (loguru.Logger): The `loguru` logger instance.
34
+ """
35
+
12
36
  logger: loguru.Logger
13
37
 
14
- def __new__(cls, *args: typing.Any, **kwargs: typing.Any):
38
+ def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Logurud:
39
+ """
40
+ Creates a new instance of `Logurud` and initializes the `loguru`
41
+ logger.
42
+
43
+ Args:
44
+ *args (typing.Any): Variable length argument list.
45
+ **kwargs (typing.Any): Arbitrary keyword arguments.
46
+
47
+ Returns:
48
+ Logurud: A new instance of `Logurud`.
49
+ """
15
50
  cls.logger: loguru.Logger = loguru.logger.opt(depth=1)
16
51
  return super().__new__(cls)
python_utils/terminal.py CHANGED
@@ -1,15 +1,33 @@
1
+ """
2
+ This module provides functions to get the terminal size across different
3
+ platforms.
4
+
5
+ Functions:
6
+ get_terminal_size: Get the current size of the terminal.
7
+ _get_terminal_size_windows: Get terminal size on Windows.
8
+ _get_terminal_size_tput: Get terminal size using `tput`.
9
+ _get_terminal_size_linux: Get terminal size on Linux.
10
+
11
+ Usage example:
12
+ >>> width, height = get_terminal_size()
13
+ """
14
+
15
+ from __future__ import annotations
16
+
1
17
  import contextlib
2
18
  import os
3
19
  import typing
4
20
 
5
21
  from . import converters
6
22
 
7
- Dimensions = typing.Tuple[int, int]
23
+ Dimensions = tuple[int, int]
8
24
  OptionalDimensions = typing.Optional[Dimensions]
25
+ _StrDimensions = tuple[str, str]
26
+ _OptionalStrDimensions = typing.Optional[_StrDimensions]
9
27
 
10
28
 
11
29
  def get_terminal_size() -> Dimensions: # pragma: no cover
12
- '''Get the current size of your terminal
30
+ """Get the current size of your terminal.
13
31
 
14
32
  Multiple returns are not always a good idea, but in this case it greatly
15
33
  simplifies the code so I believe it's justified. It's not the prettiest
@@ -17,16 +35,16 @@ def get_terminal_size() -> Dimensions: # pragma: no cover
17
35
 
18
36
  Returns:
19
37
  width, height: Two integers containing width and height
20
- '''
21
- w: typing.Optional[int]
22
- h: typing.Optional[int]
38
+ """
39
+ w: int | None
40
+ h: int | None
23
41
 
24
42
  with contextlib.suppress(Exception):
25
43
  # Default to 79 characters for IPython notebooks
26
- from IPython import get_ipython # type: ignore
44
+ from IPython import get_ipython # type: ignore[attr-defined]
27
45
 
28
- ipython = get_ipython()
29
- from ipykernel import zmqshell # type: ignore
46
+ ipython = get_ipython() # type: ignore[no-untyped-call]
47
+ from ipykernel import zmqshell # type: ignore[import-not-found]
30
48
 
31
49
  if isinstance(ipython, zmqshell.ZMQInteractiveShell):
32
50
  return 79, 24
@@ -46,7 +64,7 @@ def get_terminal_size() -> Dimensions: # pragma: no cover
46
64
  if w and h:
47
65
  return w, h
48
66
  with contextlib.suppress(Exception):
49
- import blessings # type: ignore
67
+ import blessings # type: ignore[import-untyped]
50
68
 
51
69
  terminal = blessings.Terminal()
52
70
  w = terminal.width
@@ -77,7 +95,10 @@ def get_terminal_size() -> Dimensions: # pragma: no cover
77
95
  def _get_terminal_size_windows() -> OptionalDimensions: # pragma: no cover
78
96
  res = None
79
97
  try:
80
- from ctypes import windll, create_string_buffer # type: ignore
98
+ from ctypes import ( # type: ignore[attr-defined]
99
+ create_string_buffer,
100
+ windll,
101
+ )
81
102
 
82
103
  # stdin handle is -10
83
104
  # stdout handle is -11
@@ -93,7 +114,7 @@ def _get_terminal_size_windows() -> OptionalDimensions: # pragma: no cover
93
114
  import struct
94
115
 
95
116
  (_, _, _, _, _, left, top, right, bottom, _, _) = struct.unpack(
96
- "hhhhHhhhhhh", csbi.raw
117
+ 'hhhhHhhhhhh', csbi.raw
97
118
  )
98
119
  w = right - left
99
120
  h = bottom - top
@@ -123,31 +144,36 @@ def _get_terminal_size_tput() -> OptionalDimensions: # pragma: no cover
123
144
  )
124
145
  output = proc.communicate(input=None)
125
146
  h = int(output[0])
126
- return w, h
127
147
  except Exception:
128
148
  return None
149
+ else:
150
+ return w, h
129
151
 
130
152
 
131
153
  def _get_terminal_size_linux() -> OptionalDimensions: # pragma: no cover
132
- def ioctl_GWINSZ(fd):
154
+ def ioctl_gwinsz(fd: int) -> tuple[str, str] | None:
133
155
  try:
134
156
  import fcntl
135
- import termios
136
157
  import struct
158
+ import termios
137
159
 
138
- return struct.unpack(
139
- 'hh',
140
- fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'), # type: ignore
160
+ return typing.cast(
161
+ _OptionalStrDimensions,
162
+ struct.unpack(
163
+ 'hh',
164
+ fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'), # type: ignore[call-overload]
165
+ ),
141
166
  )
142
167
  except Exception:
143
168
  return None
144
169
 
145
- size = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
170
+ size: _OptionalStrDimensions
171
+ size = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
146
172
 
147
173
  if not size:
148
174
  with contextlib.suppress(Exception):
149
- fd = os.open(os.ctermid(), os.O_RDONLY) # type: ignore
150
- size = ioctl_GWINSZ(fd)
175
+ fd = os.open(os.ctermid(), os.O_RDONLY)
176
+ size = ioctl_gwinsz(fd)
151
177
  os.close(fd)
152
178
  if not size:
153
179
  try: