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.
- python_utils/__about__.py +13 -1
- python_utils/__init__.py +81 -32
- python_utils/aio.py +72 -9
- python_utils/containers.py +283 -33
- python_utils/converters.py +143 -63
- python_utils/decorators.py +47 -24
- python_utils/exceptions.py +20 -2
- python_utils/formatters.py +36 -15
- python_utils/generators.py +38 -6
- python_utils/import_.py +34 -14
- python_utils/logger.py +134 -17
- python_utils/loguru.py +36 -1
- python_utils/terminal.py +46 -20
- python_utils/time.py +98 -51
- python_utils/types.py +109 -92
- {python_utils-3.8.2.dist-info → python_utils-3.9.1.dist-info}/METADATA +20 -16
- python_utils-3.9.1.dist-info/RECORD +21 -0
- {python_utils-3.8.2.dist-info → python_utils-3.9.1.dist-info}/WHEEL +1 -1
- python_utils/compat.py +0 -0
- python_utils-3.8.2.dist-info/RECORD +0 -22
- {python_utils-3.8.2.dist-info → python_utils-3.9.1.dist-info}/LICENSE +0 -0
- {python_utils-3.8.2.dist-info → python_utils-3.9.1.dist-info}/top_level.txt +0 -0
python_utils/generators.py
CHANGED
|
@@ -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
|
|
5
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
#
|
|
49
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
22
|
-
h:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
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
|
|
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)
|
|
150
|
-
size =
|
|
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:
|