Coverage for /home/ubuntu/lunchbox/python/lunchbox/tools.py: 100%

225 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-06-13 03:03 +0000

1from typing import Any, Callable, Dict, List, Optional, Union # noqa: F401 

2 

3from itertools import dropwhile, takewhile 

4from pathlib import Path 

5from pprint import pformat 

6import inspect 

7import json 

8import logging 

9import os 

10import re 

11import urllib.request 

12 

13import wrapt 

14 

15from lunchbox.enforce import Enforce, EnforceError 

16from lunchbox.stopwatch import StopWatch 

17 

18LOG_LEVEL = os.environ.get('LOG_LEVEL', 'WARNING').upper() 

19logging.basicConfig(level=LOG_LEVEL) 

20LOGGER = logging.getLogger(__name__) 

21# ------------------------------------------------------------------------------ 

22 

23 

24''' 

25A library of miscellaneous tools. 

26''' 

27 

28 

29def to_snakecase(string): 

30 # type: (str) -> str 

31 ''' 

32 Converts a given string to snake_case. 

33 

34 Args: 

35 string (str): String to be converted. 

36 

37 Returns: 

38 str: snake_case string. 

39 ''' 

40 output = re.sub('([A-Z]+)', r'_\1', string) 

41 output = re.sub('-', '_', output) 

42 output = re.sub(r'\.', '_', output) 

43 output = re.sub(' ', '_', output) 

44 output = re.sub('_+', '_', output) 

45 output = re.sub('^_|_$', '', output) 

46 output = output.lower() 

47 return output 

48 

49 

50def try_(function, item, return_item='item'): 

51 # type: (Callable[[Any], Any], Any, Any) -> Any 

52 ''' 

53 Call given function on given item, catch any exceptions and return given 

54 return item. 

55 

56 Args: 

57 function (function): Function of signature lambda x: x. 

58 item (object): Item used to call function. 

59 return_item (object, optional): Item to be returned. Default: "item". 

60 

61 Returns: 

62 object: Original item if return_item is "item". 

63 Exception: If return_item is "error". 

64 object: Object return by function call if return_item is not "item" or 

65 "error". 

66 ''' 

67 try: 

68 return function(item) 

69 except Exception as error: 

70 if return_item == 'item': 

71 return item 

72 elif return_item == 'error': 

73 return error 

74 return return_item 

75 

76 

77def get_ordered_unique(items): 

78 # type: (List) -> List 

79 ''' 

80 Generates a unique list of items in same order they were received in. 

81 

82 Args: 

83 items (list): List of items. 

84 

85 Returns: 

86 list: Unique ordered list. 

87 ''' 

88 output = [] 

89 temp = set() 

90 for item in items: 

91 if item not in temp: 

92 output.append(item) 

93 temp.add(item) 

94 return output 

95 

96 

97def relative_path(module, path): 

98 # type: (Union[str, Path], Union[str, Path]) -> Path 

99 ''' 

100 Resolve path given current module's file path and given suffix. 

101 

102 Args: 

103 module (str or Path): Always __file__ of current module. 

104 path (str or Path): Path relative to __file__. 

105 

106 Returns: 

107 Path: Resolved Path object. 

108 ''' 

109 module_root = Path(module).parent 

110 path_ = Path(path).parts # type: Any 

111 path_ = list(dropwhile(lambda x: x == ".", path_)) 

112 up = len(list(takewhile(lambda x: x == "..", path_))) 

113 path_ = Path(*path_[up:]) 

114 root = list(module_root.parents)[up - 1] 

115 output = Path(root, path_).absolute() 

116 

117 LOGGER.debug( 

118 f'relative_path called with: {module} and {path_}. Returned: {output}') 

119 return output 

120 

121 

122def truncate_list(items, size=3): 

123 # type (list, int) -> list 

124 ''' 

125 Truncates a given list to a given size, replaces the middle contents with 

126 "...". 

127 

128 Args: 

129 items (list): List of objects. 

130 size (int, optional): Size of output list. 

131 

132 Raises: 

133 EnforceError: If item is not a list. 

134 EnforceError: If size is not an integer greater than -1. 

135 

136 Returns: 

137 list: List of given size. 

138 ''' 

139 Enforce(items, 'instance of', list, message='Items must be a list.') 

140 msg = 'Size must be an integer greater than -1. Given value: {a}.' 

141 Enforce(size, 'instance of', int, message=msg) 

142 Enforce(size, '>', -1, message=msg) 

143 # -------------------------------------------------------------------------- 

144 

145 if len(items) <= size: 

146 return items 

147 if size == 0: 

148 return [] 

149 if size == 1: 

150 return items[:1] 

151 if size == 2: 

152 return [items[0], items[-1]] 

153 

154 output = items[:size - 2] 

155 output.append('...') 

156 output.append(items[-1]) 

157 return output 

158 

159 

160def truncate_blob_lists(blob, size=3): 

161 # type: (dict, int) -> dict 

162 ''' 

163 Truncates lists inside given JSON blob to a given size. 

164 

165 Args: 

166 blob (dict): Blob to be truncated. 

167 size (int, optional): Size of lists. Default 3. 

168 

169 Raises: 

170 EnforceError: If blob is not a dict. 

171 

172 Returns: 

173 dict: Truncated blob. 

174 ''' 

175 Enforce(blob, 'instance of', dict, message='Blob must be a dict.') 

176 # -------------------------------------------------------------------------- 

177 

178 def recurse_list(items, size): 

179 output = [] 

180 for item in truncate_list(items, size=size): 

181 if isinstance(item, dict): 

182 item = recurse(item) 

183 elif isinstance(item, list): 

184 item = recurse_list(item, size) 

185 output.append(item) 

186 return output 

187 

188 def recurse(item): 

189 output = {} 

190 for k, v in item.items(): 

191 if isinstance(v, dict): 

192 output[k] = recurse(v) 

193 elif isinstance(v, list): 

194 output[k] = recurse_list(v, size) 

195 else: 

196 output[k] = v 

197 return output 

198 return recurse(blob) 

199 

200 

201# LOGGING----------------------------------------------------------------------- 

202def log_runtime( 

203 function, *args, message_=None, _testing=False, log_level='info', **kwargs 

204): 

205 # type (Callable, ..., Optional[str], bool, str, ...) -> Any 

206 r''' 

207 Logs the duration of given function called with given arguments. 

208 

209 Args: 

210 function (function): Function to be called. 

211 \*args (object, optional): Arguments. 

212 message_ (str, optional): Message to be returned. Default: None. 

213 _testing (bool, optional): Returns message if True. Default: False. 

214 log_level (str, optional): Log level. Default: info. 

215 \*\*kwargs (object, optional): Keyword arguments. 

216 

217 Raises: 

218 EnforceError: If log level is illegal. 

219 

220 Returns: 

221 object: function(*args, **kwargs). 

222 ''' 

223 level = log_level_to_int(log_level) 

224 

225 # this may silently break file writes in multiprocessing 

226 stopwatch = StopWatch() 

227 stopwatch.start() 

228 output = function(*args, **kwargs) 

229 stopwatch.stop() 

230 

231 if message_ is not None: 

232 message_ += f'\n Runtime: {stopwatch.human_readable_delta}' 

233 else: 

234 message_ = f'''{function.__name__} 

235 Runtime: {stopwatch.human_readable_delta} 

236 Args: {pformat(args)} 

237 Kwargs: {pformat(kwargs)}''' 

238 

239 if _testing: 

240 return message_ 

241 

242 LOGGER.log(level, message_) 

243 return output 

244 

245 

246@wrapt.decorator 

247def runtime(wrapped, instance, args, kwargs): 

248 # type: (Callable, Any, Any, Any) -> Any 

249 r''' 

250 Decorator for logging the duration of given function called with given 

251 arguments. 

252 

253 Args: 

254 wrapped (function): Function to be called. 

255 instance (object): Needed by wrapt. 

256 \*args (object, optional): Arguments. 

257 \*\*kwargs (object, optional): Keyword arguments. 

258 

259 Returns: 

260 function: Wrapped function. 

261 ''' 

262 return log_runtime(wrapped, *args, **kwargs) 

263 

264 

265def log_level_to_int(level): 

266 # type: (Union[str, int]) -> int 

267 ''' 

268 Convert a given string or integer into a log level integer. 

269 

270 Args: 

271 level (str or int): Log level. 

272 

273 Raises: 

274 EnforceError: If level is illegal. 

275 

276 Returns: 

277 int: Log level as integer. 

278 ''' 

279 keys = ['critical', 'debug', 'error', 'fatal', 'info', 'warn', 'warning'] 

280 values = [getattr(logging, x.upper()) for x in keys] 

281 lut = dict(zip(keys, values)) # type: Dict[str, int] 

282 

283 msg = 'Log level must be an integer or string. Given value: {a}. ' 

284 lut_msg = ', '.join([f'{k}: {v}' for k, v in zip(keys, values)]) 

285 msg += f'Legal values: [{lut_msg}].' 

286 

287 output = 0 

288 if isinstance(level, int): 

289 Enforce(level, 'in', values, message=msg) 

290 output = level 

291 

292 elif isinstance(level, str): 

293 level = level.lower() 

294 Enforce(level, 'in', keys, message=msg) 

295 output = lut[level] 

296 

297 else: 

298 raise EnforceError(msg.format(a=level)) 

299 

300 return output 

301 

302 

303class LogRuntime: 

304 ''' 

305 LogRuntime is a class for logging the runtime of arbitrary code. 

306 

307 Attributes: 

308 message (str): Logging message with runtime line. 

309 delta (datetime.timedelta): Runtime. 

310 human_readable_delta (str): Runtime in human readable format. 

311 

312 Example: 

313 

314 >>> import time 

315 >>> def foobar(): 

316 time.sleep(1) 

317 

318 >>> with LogRuntime('Foo the bars', name=foobar.__name__, level='debug'): 

319 foobar() 

320 DEBUG:foobar:Foo the bars - Runtime: 0:00:01.001069 (1 second) 

321 

322 >>> with LogRuntime(message='Fooing all the bars', suppress=True) as log: 

323 foobar() 

324 >>> print(log.message) 

325 Fooing all the bars - Runtime: 0:00:01.001069 (1 second) 

326 ''' 

327 def __init__( 

328 self, 

329 message='', # type: str 

330 name='LogRuntime', # type: str 

331 level='info', # type: str 

332 suppress=False, # type: bool 

333 message_func=None, # type: Optional[Callable[[str, StopWatch], None]] 

334 callback=None, # type: Optional[Callable[[str], Any]] 

335 ): 

336 # type: (...) -> None 

337 ''' 

338 Constructs a LogRuntime instance. 

339 

340 Args: 

341 message (str, optional): Logging message. Default: ''. 

342 name (str, optional): Name of logger. Default: 'LogRuntime'. 

343 level (str or int, optional): Log level. Default: info. 

344 suppress (bool, optional): Whether to suppress logging. 

345 Default: False. 

346 message_func (function, optional): Custom message function of the 

347 signature (message, StopWatch) -> str. Default: None. 

348 callback (function, optional): Callback function of the signature 

349 (message) -> Any. Default: None. 

350 

351 Raises: 

352 EnforceError: If message is not a string. 

353 EnforceError: If name is not a string. 

354 EnforceError: If level is not legal logging level. 

355 EnforceError: If suppress is not a boolean. 

356 ''' 

357 Enforce(message, 'instance of', str) 

358 Enforce(name, 'instance of', str) 

359 Enforce(suppress, 'instance of', bool) 

360 # ---------------------------------------------------------------------- 

361 

362 self._message = message 

363 self._stopwatch = StopWatch() 

364 self._logger = logging.getLogger(name) 

365 self._level = log_level_to_int(level) 

366 self._suppress = suppress 

367 self._message_func = message_func 

368 self._callback = callback 

369 

370 @staticmethod 

371 def _default_message_func(message, stopwatch): 

372 # type: (str, StopWatch) -> str 

373 ''' 

374 Add runtime information to message given StopWatch instance. 

375 

376 Args: 

377 message (str): Message. 

378 stopwatch (StopWatch): StopWatch instance. 

379 

380 Raises: 

381 EnforeceError: If Message is not a string. 

382 EnforceError: If stopwatch is not a StopWatch instance. 

383 

384 Returns: 

385 str: Message with runtime information. 

386 ''' 

387 Enforce(message, 'instance of', str) 

388 Enforce(stopwatch, 'instance of', StopWatch) 

389 # ---------------------------------------------------------------------- 

390 

391 msg = f'Runtime: {stopwatch.delta} ' 

392 msg += f'({stopwatch.human_readable_delta})' 

393 if message != '': 

394 msg = message + ' - ' + msg 

395 return msg 

396 

397 def __enter__(self): 

398 # type: () -> LogRuntime 

399 ''' 

400 Starts stopwatch. 

401 

402 Returns: 

403 LogRuntime: self. 

404 ''' 

405 self._stopwatch.start() 

406 return self 

407 

408 def __exit__(self, *args): 

409 # type: (Any) -> None 

410 ''' 

411 Stops stopwatch and logs message. 

412 ''' 

413 stopwatch = self._stopwatch 

414 stopwatch.stop() 

415 self.delta = self._stopwatch.delta 

416 self.human_readable_delta = self._stopwatch.human_readable_delta 

417 

418 msg_func = self._message_func or self._default_message_func 

419 self.message = msg_func(self._message, stopwatch) 

420 

421 if not self._suppress: 

422 self._logger.log(self._level, self.message) 

423 

424 if self._callback is not None: 

425 self._callback(str(self.message)) 

426 

427 

428# HTTP-REQUESTS----------------------------------------------------------------- 

429def post_to_slack(url, channel, message): 

430 # type (str, str, str) -> urllib.request.HttpResponse 

431 ''' 

432 Post a given message to a given slack channel. 

433 

434 Args: 

435 url (str): https://hooks.slack.com/services URL. 

436 channel (str): Channel name. 

437 message (str): Message to be posted. 

438 

439 Raises: 

440 EnforceError: If URL is not a string. 

441 EnforceError: If URL does not start with https://hooks.slack.com/services 

442 EnforceError: If channel is not a string. 

443 EnforceError: If message is not a string. 

444 

445 Returns: 

446 HTTPResponse: Response. 

447 ''' 

448 Enforce(url, 'instance of', str) 

449 Enforce(channel, 'instance of', str) 

450 Enforce(message, 'instance of', str) 

451 msg = 'URL must begin with https://hooks.slack.com/services/. ' 

452 msg += f'Given URL: {url}' 

453 Enforce( 

454 url.startswith('https://hooks.slack.com/services/'), '==', True, 

455 message=msg 

456 ) 

457 # -------------------------------------------------------------------------- 

458 

459 request = urllib.request.Request( 

460 url, 

461 method='POST', 

462 headers={'Content-type': 'application/json'}, 

463 data=json.dumps(dict( 

464 channel='#' + channel, 

465 text=message, 

466 )).encode(), 

467 ) 

468 return urllib.request.urlopen(request) 

469 

470 

471# API--------------------------------------------------------------------------- 

472def get_function_signature(function): 

473 # type: (Callable) -> Dict 

474 ''' 

475 Inspect a given function and return its arguments as a list and its keyword 

476 arguments as a dict. 

477 

478 Args: 

479 function (function): Function to be inspected. 

480 

481 Returns: 

482 dict: args and kwargs. 

483 ''' 

484 spec = inspect.getfullargspec(function) 

485 args = list(spec.args) 

486 kwargs = {} # type: Any 

487 if spec.defaults is not None: 

488 args = args[:-len(spec.defaults)] 

489 kwargs = list(spec.args)[-len(spec.defaults):] 

490 kwargs = dict(zip(kwargs, spec.defaults)) 

491 return dict(args=args, kwargs=kwargs) 

492 

493 

494def _dir_table(obj, public=True, semiprivate=True, private=False, max_width=100): 

495 # type: (Any, bool, bool, bool, int) -> str 

496 ''' 

497 Create a table from results of calling dir(obj). 

498 

499 Args: 

500 obj (object): Object to call dir on. 

501 public (bool, optional): Include public attributes in table. 

502 Default: True. 

503 semiprivate (bool, optional): Include semiprivate attributes in table. 

504 Default: True. 

505 private (bool, optional): Include private attributes in table. 

506 Default: False. 

507 max_width (int, optional): Maximum table width: Default: 100. 

508 

509 Returns: 

510 str: Table. 

511 ''' 

512 dirkeys = dir(obj) 

513 pub = set(list(filter(lambda x: re.search('^[^_]', x), dirkeys))) 

514 priv = set(list(filter(lambda x: re.search('^__|^_[A-Z].*__', x), dirkeys))) 

515 semipriv = set(dirkeys).difference(pub).difference(priv) 

516 

517 keys = set() 

518 if public: 

519 keys.update(pub) 

520 if private: 

521 keys.update(priv) 

522 if semiprivate: 

523 keys.update(semipriv) 

524 

525 data = [dict(key='NAME', atype='TYPE', val='VALUE')] 

526 max_key = 0 

527 max_atype = 0 

528 for key in sorted(keys): 

529 attr = getattr(obj, key) 

530 atype = attr.__class__.__name__ 

531 val = str(attr).replace('\n', ' ') 

532 max_key = max(max_key, len(key)) 

533 max_atype = max(max_atype, len(atype)) 

534 row = dict(key=key, atype=atype, val=val) 

535 data.append(row) 

536 

537 pattern = f'{{key:<{max_key}}} {{atype:<{max_atype}}} {{val}}' 

538 lines = list(map(lambda x: pattern.format(**x)[:max_width], data)) 

539 output = '\n'.join(lines) 

540 return output 

541 

542 

543def dir_table(obj, public=True, semiprivate=True, private=False, max_width=100): 

544 # type: (Any, bool, bool, bool, int) -> None 

545 ''' 

546 Prints a table from results of calling dir(obj). 

547 

548 Args: 

549 obj (object): Object to call dir on. 

550 public (bool, optional): Include public attributes in table. 

551 Default: True. 

552 semiprivate (bool, optional): Include semiprivate attributes in table. 

553 Default: True. 

554 private (bool, optional): Include private attributes in table. 

555 Default: False. 

556 max_width (int, optional): Maximum table width: Default: 100. 

557 ''' 

558 print(_dir_table( 

559 obj, 

560 public=public, 

561 semiprivate=semiprivate, 

562 private=private, 

563 max_width=max_width, 

564 )) # pragma: no cover 

565 

566 

567def api_function(wrapped=None, **kwargs): 

568 # type: (Optional[Callable], Any) -> Callable 

569 r''' 

570 A decorator that enforces keyword argument only function signatures and 

571 required keyword argument values when called. 

572 

573 Args: 

574 wrapped (function): For dev use. Default: None. 

575 \*\*kwargs (dict): Keyword arguments. # noqa: W605 

576 

577 Raises: 

578 TypeError: If non-keyword argument found in functionn signature. 

579 ValueError: If keyword arg with value of '<required>' is found. 

580 

581 Returns: 

582 api function. 

583 ''' 

584 @wrapt.decorator 

585 def wrapper(wrapped, instance, args, kwargs): 

586 sig = get_function_signature(wrapped) 

587 

588 # ensure no arguments are present 

589 if len(sig['args']) > 0: 

590 msg = 'Function may only have keyword arguments. ' 

591 msg += f"Found non-keyword arguments: {sig['args']}." 

592 raise TypeError(msg) 

593 

594 # ensure all required kwarg values are present 

595 params = sig['kwargs'] 

596 params.update(kwargs) 

597 for key, val in params.items(): 

598 if val == '<required>': 

599 msg = f'Missing required keyword argument: {key}.' 

600 raise ValueError(msg) 

601 

602 LOGGER.debug(f'{wrapped} called with {params}.') 

603 return wrapped(*args, **kwargs) 

604 return wrapper(wrapped) 

605# ------------------------------------------------------------------------------ 

606 

607 

608def is_standard_module(name): 

609 # type: (str) -> bool 

610 ''' 

611 Determines if given module name is a python builtin. 

612 

613 Args: 

614 name (str): Python module name. 

615 

616 Returns: 

617 bool: Whether string names a python module. 

618 ''' 

619 return name in _PYTHON_STANDARD_MODULES 

620 

621 

622_PYTHON_STANDARD_MODULES = [ 

623 '__future__', 

624 '__main__', 

625 '_dummy_thread', 

626 '_thread', 

627 'abc', 

628 'aifc', 

629 'argparse', 

630 'array', 

631 'ast', 

632 'asynchat', 

633 'asyncio', 

634 'asyncore', 

635 'atexit', 

636 'audioop', 

637 'base64', 

638 'bdb', 

639 'binascii', 

640 'binhex', 

641 'bisect', 

642 'builtins', 

643 'bz2', 

644 'calendar', 

645 'cgi', 

646 'cgitb', 

647 'chunk', 

648 'cmath', 

649 'cmd', 

650 'code', 

651 'codecs', 

652 'codeop', 

653 'collections', 

654 'collections.abc', 

655 'colorsys', 

656 'compileall', 

657 'concurrent', 

658 'concurrent.futures', 

659 'configparser', 

660 'contextlib', 

661 'contextvars', 

662 'copy', 

663 'copyreg', 

664 'cProfile', 

665 'crypt', 

666 'csv', 

667 'ctypes', 

668 'curses', 

669 'curses.ascii', 

670 'curses.panel', 

671 'curses.textpad', 

672 'dataclasses', 

673 'datetime', 

674 'dbm', 

675 'dbm.dumb', 

676 'dbm.gnu', 

677 'dbm.ndbm', 

678 'decimal', 

679 'difflib', 

680 'dis', 

681 'distutils', 

682 'distutils.archive_util', 

683 'distutils.bcppcompiler', 

684 'distutils.ccompiler', 

685 'distutils.cmd', 

686 'distutils.command', 

687 'distutils.command.bdist', 

688 'distutils.command.bdist_dumb', 

689 'distutils.command.bdist_msi', 

690 'distutils.command.bdist_packager', 

691 'distutils.command.bdist_rpm', 

692 'distutils.command.bdist_wininst', 

693 'distutils.command.build', 

694 'distutils.command.build_clib', 

695 'distutils.command.build_ext', 

696 'distutils.command.build_py', 

697 'distutils.command.build_scripts', 

698 'distutils.command.check', 

699 'distutils.command.clean', 

700 'distutils.command.config', 

701 'distutils.command.install', 

702 'distutils.command.install_data', 

703 'distutils.command.install_headers', 

704 'distutils.command.install_lib', 

705 'distutils.command.install_scripts', 

706 'distutils.command.register', 

707 'distutils.command.sdist', 

708 'distutils.core', 

709 'distutils.cygwinccompiler', 

710 'distutils.debug', 

711 'distutils.dep_util', 

712 'distutils.dir_util', 

713 'distutils.dist', 

714 'distutils.errors', 

715 'distutils.extension', 

716 'distutils.fancy_getopt', 

717 'distutils.file_util', 

718 'distutils.filelist', 

719 'distutils.log', 

720 'distutils.msvccompiler', 

721 'distutils.spawn', 

722 'distutils.sysconfig', 

723 'distutils.text_file', 

724 'distutils.unixccompiler', 

725 'distutils.util', 

726 'distutils.version', 

727 'doctest', 

728 'dummy_threading', 

729 'email', 

730 'email.charset', 

731 'email.contentmanager', 

732 'email.encoders', 

733 'email.errors', 

734 'email.generator', 

735 'email.header', 

736 'email.headerregistry', 

737 'email.iterators', 

738 'email.message', 

739 'email.mime', 

740 'email.parser', 

741 'email.policy', 

742 'email.utils', 

743 'encodings', 

744 'encodings.idna', 

745 'encodings.mbcs', 

746 'encodings.utf_8_sig', 

747 'ensurepip', 

748 'enum', 

749 'errno', 

750 'faulthandler', 

751 'fcntl', 

752 'filecmp', 

753 'fileinput', 

754 'fnmatch', 

755 'formatter', 

756 'fractions', 

757 'ftplib', 

758 'functools', 

759 'gc', 

760 'getopt', 

761 'getpass', 

762 'gettext', 

763 'glob', 

764 'grp', 

765 'gzip', 

766 'hashlib', 

767 'heapq', 

768 'hmac', 

769 'html', 

770 'html.entities', 

771 'html.parser', 

772 'http', 

773 'http.client', 

774 'http.cookiejar', 

775 'http.cookies', 

776 'http.server', 

777 'imaplib', 

778 'imghdr', 

779 'imp', 

780 'importlib', 

781 'importlib.abc', 

782 'importlib.machinery', 

783 'importlib.resources', 

784 'importlib.util', 

785 'inspect', 

786 'io', 

787 'ipaddress', 

788 'itertools', 

789 'json', 

790 'json.tool', 

791 'keyword', 

792 'lib2to3', 

793 'linecache', 

794 'locale', 

795 'logging', 

796 'logging.config', 

797 'logging.handlers', 

798 'lzma', 

799 'macpath', 

800 'mailbox', 

801 'mailcap', 

802 'marshal', 

803 'math', 

804 'mimetypes', 

805 'mmap', 

806 'modulefinder', 

807 'msilib', 

808 'msvcrt', 

809 'multiprocessing', 

810 'multiprocessing.connection', 

811 'multiprocessing.dummy', 

812 'multiprocessing.managers', 

813 'multiprocessing.pool', 

814 'multiprocessing.sharedctypes', 

815 'netrc', 

816 'nis', 

817 'nntplib', 

818 'numbers', 

819 'operator', 

820 'optparse', 

821 'os', 

822 'os.path', 

823 'ossaudiodev', 

824 'parser', 

825 'pathlib', 

826 'pdb', 

827 'pickle', 

828 'pickletools', 

829 'pipes', 

830 'pkgutil', 

831 'platform', 

832 'plistlib', 

833 'poplib', 

834 'posix', 

835 'pprint', 

836 'profile', 

837 'pstats', 

838 'pty', 

839 'pwd', 

840 'py_compile', 

841 'pyclbr', 

842 'pydoc', 

843 'queue', 

844 'quopri', 

845 'random', 

846 're', 

847 'readline', 

848 'reprlib', 

849 'resource', 

850 'rlcompleter', 

851 'runpy', 

852 'sched', 

853 'secrets', 

854 'select', 

855 'selectors', 

856 'shelve', 

857 'shlex', 

858 'shutil', 

859 'signal', 

860 'site', 

861 'smtpd', 

862 'smtplib', 

863 'sndhdr', 

864 'socket', 

865 'socketserver', 

866 'spwd', 

867 'sqlite3', 

868 'ssl', 

869 'stat', 

870 'statistics', 

871 'string', 

872 'stringprep', 

873 'struct', 

874 'subprocess', 

875 'sunau', 

876 'symbol', 

877 'symtable', 

878 'sys', 

879 'sysconfig', 

880 'syslog', 

881 'tabnanny', 

882 'tarfile', 

883 'telnetlib', 

884 'tempfile', 

885 'termios', 

886 'test', 

887 'test.support', 

888 'test.support.script_helper', 

889 'textwrap', 

890 'threading', 

891 'time', 

892 'timeit', 

893 'tkinter', 

894 'tkinter.scrolledtext', 

895 'tkinter.tix', 

896 'tkinter.ttk', 

897 'token', 

898 'tokenize', 

899 'trace', 

900 'traceback', 

901 'tracemalloc', 

902 'tty', 

903 'turtle', 

904 'turtledemo', 

905 'types', 

906 'typing', 

907 'unicodedata', 

908 'unittest', 

909 'unittest.mock', 

910 'urllib', 

911 'urllib.error', 

912 'urllib.parse', 

913 'urllib.request', 

914 'urllib.response', 

915 'urllib.robotparser', 

916 'uu', 

917 'uuid', 

918 'venv', 

919 'warnings', 

920 'wave', 

921 'weakref', 

922 'webbrowser', 

923 'winreg', 

924 'winsound', 

925 'wsgiref', 

926 'wsgiref.handlers', 

927 'wsgiref.headers', 

928 'wsgiref.simple_server', 

929 'wsgiref.util', 

930 'wsgiref.validate', 

931 'xdrlib', 

932 'xml', 

933 'xml.dom', 

934 'xml.dom.minidom', 

935 'xml.dom.pulldom', 

936 'xml.etree.ElementTree', 

937 'xml.parsers.expat', 

938 'xml.parsers.expat.errors', 

939 'xml.parsers.expat.model', 

940 'xml.sax', 

941 'xml.sax.handler', 

942 'xml.sax.saxutils', 

943 'xml.sax.xmlreader', 

944 'xmlrpc', 

945 'xmlrpc.client', 

946 'xmlrpc.server', 

947 'zipapp', 

948 'zipfile', 

949 'zipimport', 

950 'zlib' 

951] # type: List[str]