Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of the lib3to6 project
2# https://github.com/mbarkhau/lib3to6
3#
4# Copyright (c) 2019-2021 Manuel Barkhau (mbarkhau@gmail.com) - MIT License
5# SPDX-License-Identifier: MIT
7import ast
8import sys
9import typing as typ
11from . import utils
12from . import common
13from . import fixer_base as fb
14from .fixers_future import DivisionFutureFixer
15from .fixers_future import GeneratorsFutureFixer
16from .fixers_future import AnnotationsFutureFixer
17from .fixers_future import NestedScopesFutureFixer
18from .fixers_future import GeneratorStopFutureFixer
19from .fixers_future import PrintFunctionFutureFixer
20from .fixers_future import WithStatementFutureFixer
21from .fixers_future import AbsoluteImportFutureFixer
22from .fixers_future import UnicodeLiteralsFutureFixer
23from .fixers_future import RemoveUnsupportedFuturesFixer
24from .fixers_builtin_rename import UnichrToChrFixer
25from .fixers_builtin_rename import UnicodeToStrFixer
26from .fixers_builtin_rename import XrangeToRangeFixer
27from .fixers_builtin_rename import RawInputToInputFixer
28from .fixers_import_fallback import QueueImportFallbackFixer
29from .fixers_import_fallback import DbmGnuImportFallbackFixer
30from .fixers_import_fallback import PickleImportFallbackFixer
31from .fixers_import_fallback import ThreadImportFallbackFixer
32from .fixers_import_fallback import WinRegImportFallbackFixer
33from .fixers_import_fallback import CopyRegImportFallbackFixer
34from .fixers_import_fallback import ReprLibImportFallbackFixer
35from .fixers_import_fallback import TkinterImportFallbackFixer
36from .fixers_import_fallback import BuiltinsImportFallbackFixer
37from .fixers_import_fallback import HtmlParserImportFallbackFixer
38from .fixers_import_fallback import HttpClientImportFallbackFixer
39from .fixers_import_fallback import TkinterDndImportFallbackFixer
40from .fixers_import_fallback import TkinterTixImportFallbackFixer
41from .fixers_import_fallback import TkinterTtkImportFallbackFixer
42from .fixers_import_fallback import DummyThreadImportFallbackFixer
43from .fixers_import_fallback import HttpCookiesImportFallbackFixer
44from .fixers_import_fallback import TkinterFontImportFallbackFixer
45from .fixers_import_fallback import UrllibErrorImportFallbackFixer
46from .fixers_import_fallback import UrllibParseImportFallbackFixer
47from .fixers_import_fallback import ConfigParserImportFallbackFixer
48from .fixers_import_fallback import SocketServerImportFallbackFixer
49from .fixers_import_fallback import XMLRPCClientImportFallbackFixer
50from .fixers_import_fallback import XmlrpcServerImportFallbackFixer
51from .fixers_import_fallback import EmailMimeBaseImportFallbackFixer
52from .fixers_import_fallback import EmailMimeTextImportFallbackFixer
53from .fixers_import_fallback import HttpCookiejarImportFallbackFixer
54from .fixers_import_fallback import TkinterDialogImportFallbackFixer
55from .fixers_import_fallback import UrllibRequestImportFallbackFixer
56from .fixers_import_fallback import EmailMimeImageImportFallbackFixer
57from .fixers_import_fallback import TkinterConstantsImportFallbackFixer
58from .fixers_import_fallback import TkinterMessageboxImportFallbackFixer
59from .fixers_import_fallback import UrllibRobotParserImportFallbackFixer
60from .fixers_import_fallback import EmailMimeMultipartImportFallbackFixer
61from .fixers_import_fallback import TkinterColorchooserImportFallbackFixer
62from .fixers_import_fallback import TkinterCommonDialogImportFallbackFixer
63from .fixers_import_fallback import TkinterScrolledTextImportFallbackFixer
64from .fixers_import_fallback import EmailMimeNonmultipartImportFallbackFixer
65from .fixers_unpacking_generalization import UnpackingGeneralizationsFixer
67AstStr = getattr(ast, 'Str', ast.Constant)
70def is_const_node(node: ast.AST) -> bool:
71 return node is None or any(isinstance(node, cntype) for cntype in common.ConstantNodeTypes)
74Elt = typ.Union[ast.expr, ast.Name, ast.Constant, ast.Subscript]
75Elts = typ.List[Elt]
78AnnoNode = typ.Union[ast.arg, ast.AnnAssign, ast.FunctionDef]
81class _FRAFContext:
83 local_classes: typ.Set[str]
84 known_classes: typ.Set[str]
86 def __init__(self, local_classes: typ.Set[str]) -> None:
87 self.local_classes = local_classes
88 self.known_classes = set()
90 def is_forward_ref(self, name: str) -> bool:
91 return name in self.local_classes and name not in self.known_classes
93 def update_index_elts(self, elts: Elts) -> None:
94 # NOTE (mb 2020-07-19): We modify elts during iteration
95 # pylint:disable=consider-using-enumerate
96 for i in range(len(elts)):
97 elt = elts[i]
98 if is_const_node(elt) or isinstance(elt, ast.Attribute):
99 continue
101 if isinstance(elt, ast.Name):
102 if self.is_forward_ref(elt.id):
103 elts[i] = ast.Constant(elt.id)
104 elif isinstance(elt, ast.Subscript):
105 self.update_subscript(elt)
106 elif isinstance(elt, ast.List):
107 self.update_index_elts(elt.elts)
108 else:
109 msg = f"Error fixing index element with forward ref of type {type(elt)}"
110 raise common.FixerError(msg, elt)
112 def update_subscript(self, val: ast.Subscript) -> None:
113 idx = val.slice
114 if isinstance(idx, ast.Tuple):
115 self.update_index_elts(idx.elts)
116 elif isinstance(idx, ast.Index):
117 self.update_index(idx)
118 elif isinstance(idx, ast.Subscript):
119 self.update_subscript(idx)
120 elif isinstance(idx, ast.Name):
121 if self.is_forward_ref(idx.id):
122 val.slice = AstStr(idx.id)
123 elif isinstance(idx, ast.Attribute):
124 return
125 elif isinstance(idx, ast.Constant):
126 return
127 else:
128 msg = f"Error fixing annotation of forward ref with type {type(idx)}"
129 raise common.FixerError(msg, idx)
131 def update_index(self, idx: ast.Index) -> None:
132 val = idx.value
133 if is_const_node(val) or isinstance(val, ast.Attribute):
134 return
136 if isinstance(val, ast.Name):
137 if self.is_forward_ref(val.id):
138 idx.value = AstStr(val.id)
139 elif isinstance(val, ast.Subscript):
140 self.update_subscript(val)
141 elif isinstance(val, (ast.Tuple, ast.List)):
142 self.update_index_elts(val.elts)
143 else:
144 msg = f"Error fixing index with forward ref of type {type(val)}"
145 raise common.FixerError(msg, val)
147 def update_annotation_refs(self, node: AnnoNode, attrname: str) -> None:
148 anno = getattr(node, attrname)
149 if is_const_node(anno) or isinstance(anno, ast.Attribute):
150 return
152 if isinstance(anno, ast.Name):
153 if self.is_forward_ref(anno.id):
154 setattr(node, attrname, AstStr(anno.id))
155 elif isinstance(anno, ast.Subscript):
156 self.update_subscript(anno)
157 else:
158 msg = f"Error fixing annotation of forward ref with type {type(anno)}"
159 raise common.FixerError(msg, anno)
161 def remove_forward_references(self, node: ast.AST) -> None:
162 for sub_node in ast.iter_child_nodes(node):
163 if isinstance(sub_node, ast.FunctionDef):
164 self.update_annotation_refs(sub_node, 'returns')
166 for arg in sub_node.args.args:
167 self.update_annotation_refs(arg, 'annotation')
168 for arg in sub_node.args.kwonlyargs:
169 self.update_annotation_refs(arg, 'annotation')
171 kwarg = sub_node.args.kwarg
172 if kwarg:
173 self.update_annotation_refs(kwarg, 'annotation')
174 vararg = sub_node.args.vararg
175 if vararg:
176 self.update_annotation_refs(vararg, 'annotation')
177 elif isinstance(sub_node, ast.AnnAssign):
178 self.update_annotation_refs(sub_node, 'annotation')
180 if hasattr(sub_node, 'body'):
181 self.remove_forward_references(sub_node)
183 if isinstance(sub_node, ast.ClassDef):
184 self.known_classes.add(sub_node.name)
187class ForwardReferenceAnnotationsFixer(fb.FixerBase):
189 version_info = common.VersionInfo(apply_since="3.0", apply_until="3.6")
191 def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
192 local_classes: typ.Set[str] = set()
193 for node in ast.walk(tree):
194 if isinstance(node, ast.ClassDef):
195 local_classes.add(node.name)
197 fraf_ctx = _FRAFContext(local_classes)
198 fraf_ctx.remove_forward_references(tree)
199 return tree
202class RemoveFunctionDefAnnotationsFixer(fb.FixerBase):
204 version_info = common.VersionInfo(apply_since="1.0", apply_until="2.7")
206 def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
207 for node in ast.walk(tree):
208 if isinstance(node, ast.FunctionDef):
209 node.returns = None
210 for arg in node.args.args:
211 arg.annotation = None
212 for arg in node.args.kwonlyargs:
213 arg.annotation = None
215 if node.args.kwarg:
216 node.args.kwarg.annotation = None
217 if node.args.vararg:
218 node.args.vararg.annotation = None
220 return tree
223class RemoveAnnAssignFixer(fb.TransformerFixerBase):
225 version_info = common.VersionInfo(apply_since="1.0", apply_until="3.5")
227 @staticmethod
228 def visit_AnnAssign(node: ast.AnnAssign) -> ast.Assign:
229 tgt_node = node.target
230 if isinstance(tgt_node, (ast.Name, ast.Attribute)):
231 value: ast.expr
232 if node.value is None:
233 value = ast.NameConstant(value=None)
234 else:
235 value = node.value
236 return ast.Assign(targets=[tgt_node], value=value)
237 else:
238 raise common.FixerError("Unexpected Node type", tgt_node)
241class ShortToLongFormSuperFixer(fb.TransformerFixerBase):
243 version_info = common.VersionInfo(apply_since="2.2", apply_until="2.7")
245 @staticmethod
246 def visit_ClassDef(node: ast.ClassDef) -> ast.ClassDef:
247 for maybe_method in ast.walk(node):
248 if not isinstance(maybe_method, ast.FunctionDef):
249 continue
251 method : ast.FunctionDef = maybe_method
252 method_args: ast.arguments = method.args
253 if len(method_args.args) == 0:
254 continue
256 self_arg: ast.arg = method_args.args[0]
258 for maybe_super_call in ast.walk(method):
259 if not isinstance(maybe_super_call, ast.Call):
260 continue
262 func_node = maybe_super_call.func
263 if not (isinstance(func_node, ast.Name) and func_node.id == "super"):
264 continue
266 super_call = maybe_super_call
267 if len(super_call.args) > 0:
268 continue
270 super_call.args = [
271 ast.Name(id=node.name , ctx=ast.Load()),
272 ast.Name(id=self_arg.arg, ctx=ast.Load()),
273 ]
274 return node
277class InlineKWOnlyArgsFixer(fb.TransformerFixerBase):
279 version_info = common.VersionInfo(apply_since="1.0", apply_until="2.99")
281 @staticmethod
282 def visit_FunctionDef(node: ast.FunctionDef) -> ast.FunctionDef:
283 if not node.args.kwonlyargs:
284 return node
286 if node.args.kwarg:
287 kw_name = node.args.kwarg.arg
288 else:
289 kw_name = "kwargs"
290 node.args.kwarg = ast.arg(arg=kw_name, annotation=None)
292 kwonlyargs = reversed(node.args.kwonlyargs)
293 kw_defaults = reversed(node.args.kw_defaults)
294 for arg, default in zip(kwonlyargs, kw_defaults):
295 arg_name = arg.arg
296 node_value: ast.expr
298 # NOTE (mb 2018-06-03): Only use defaults for kwargs
299 # if they are literals. Everything else would
300 # change the semantics too much and so we should
301 # raise an error.
302 if default is None:
303 node_value = ast.Subscript(
304 value=ast.Name(id=kw_name, ctx=ast.Load()),
305 slice=ast.Index(value=AstStr(s=arg_name)),
306 ctx=ast.Load(),
307 )
308 elif not isinstance(default, common.ConstantNodeTypes):
309 msg = f"Keyword only arguments must be immutable. Found: {default} for {arg_name}"
310 raise common.FixerError(msg, node)
311 else:
312 node_value = ast.Call(
313 func=ast.Attribute(
314 value=ast.Name(id=kw_name, ctx=ast.Load()), attr="get", ctx=ast.Load()
315 ),
316 args=[AstStr(s=arg_name), default],
317 keywords=[],
318 )
320 new_node = ast.Assign(
321 targets=[ast.Name(id=arg_name, ctx=ast.Store())], value=node_value
322 )
324 node.body.insert(0, new_node)
326 node.args.kwonlyargs = []
328 return node
331class NewStyleClassesFixer(fb.TransformerFixerBase):
333 version_info = common.VersionInfo(apply_since="2.0", apply_until="2.7")
335 def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef:
336 self.generic_visit(node)
337 if len(node.bases) == 0:
338 node.bases.append(ast.Name(id="object", ctx=ast.Load()))
339 return node
342class ItertoolsBuiltinsFixer(fb.TransformerFixerBase):
344 version_info = common.VersionInfo(
345 apply_since="2.3", # introduction of the itertools module
346 apply_until="2.7",
347 works_until="3.99",
348 )
350 # WARNING (mb 2018-06-09): This fix is very broad, and should
351 # only be used in combination with a sanity check that the
352 # builtin names are not being overridden.
354 def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
355 new_tree = self.visit(tree)
356 return typ.cast(ast.Module, new_tree)
358 def visit_Name(self, node: ast.Name) -> typ.Union[ast.Name, ast.Attribute]:
359 if isinstance(node.ctx, ast.Load) and node.id in ("map", "zip", "filter"):
360 self.required_imports.add(common.ImportDecl("itertools", None, None))
361 global_decl = f"{node.id} = getattr(itertools, 'i{node.id}', {node.id})"
362 self.module_declarations.add(global_decl)
364 return node
367class NamedTupleClassToAssignFixer(fb.TransformerFixerBase):
369 version_info = common.VersionInfo(apply_since="2.6", apply_until="3.4")
371 _typing_module_name : typ.Optional[str]
372 _namedtuple_class_name: typ.Optional[str]
374 def __init__(self) -> None:
375 self._typing_module_name = None
376 self._namedtuple_class_name = None
377 super().__init__()
379 def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.ImportFrom:
380 if node.module == 'typing':
381 for alias in node.names:
382 if alias.name == 'NamedTuple':
383 if alias.asname is None:
384 self._namedtuple_class_name = alias.name
385 else:
386 self._namedtuple_class_name = alias.asname
388 return node
390 def visit_Import(self, node: ast.Import) -> ast.Import:
391 for alias in node.names:
392 if alias.name == 'typing':
393 if alias.asname is None:
394 self._typing_module_name = alias.name
395 else:
396 self._typing_module_name = alias.asname
397 return node
399 def visit_ClassDef(self, node: ast.ClassDef) -> typ.Union[ast.ClassDef, ast.Assign]:
400 self.generic_visit(node)
401 if len(node.bases) == 0:
402 return node
404 if not (self._typing_module_name or self._namedtuple_class_name):
405 # no import of typing.NamedTuple -> class cannot have it as one its bases
406 return node
408 has_namedtuple_base = utils.has_base_class(
409 node, self._typing_module_name, self._namedtuple_class_name or 'NamedTuple'
410 )
411 if not has_namedtuple_base:
412 return node
414 func: typ.Union[ast.Attribute, ast.Name]
416 if self._typing_module_name:
417 func = ast.Attribute(
418 value=ast.Name(id=self._typing_module_name, ctx=ast.Load()),
419 attr="NamedTuple",
420 ctx=ast.Load(),
421 )
422 elif self._namedtuple_class_name:
423 func = ast.Name(id=self._namedtuple_class_name, ctx=ast.Load())
424 else:
425 raise RuntimeError("")
427 elts: typ.List[ast.Tuple] = []
429 for assign in node.body:
430 if not isinstance(assign, ast.AnnAssign):
431 continue
432 tgt = assign.target
433 if not isinstance(tgt, ast.Name):
434 continue
436 elts.append(ast.Tuple(elts=[AstStr(s=tgt.id), assign.annotation], ctx=ast.Load()))
438 return ast.Assign(
439 targets=[ast.Name(id=node.name, ctx=ast.Store())],
440 value=ast.Call(
441 func=func,
442 args=[AstStr(s=node.name), ast.List(elts=elts, ctx=ast.Load())],
443 keywords=[],
444 ),
445 )
448if sys.version_info >= (3, 6):
449 from .fixers_fstring import FStringToStrFormatFixer
450else:
451 FStringToStrFormatFixer = None
454if sys.version_info >= (3, 8):
455 from .fixers_namedexpr import NamedExprFixer
456else:
457 NamedExprFixer = None
460__all__ = [
461 "AnnotationsFutureFixer",
462 "GeneratorStopFutureFixer",
463 "UnicodeLiteralsFutureFixer",
464 "RemoveUnsupportedFuturesFixer",
465 "PrintFunctionFutureFixer",
466 "WithStatementFutureFixer",
467 "AbsoluteImportFutureFixer",
468 "DivisionFutureFixer",
469 "GeneratorsFutureFixer",
470 "NestedScopesFutureFixer",
471 "ConfigParserImportFallbackFixer",
472 "SocketServerImportFallbackFixer",
473 "BuiltinsImportFallbackFixer",
474 "QueueImportFallbackFixer",
475 "CopyRegImportFallbackFixer",
476 "WinRegImportFallbackFixer",
477 "ReprLibImportFallbackFixer",
478 "ThreadImportFallbackFixer",
479 "DummyThreadImportFallbackFixer",
480 "HttpCookiejarImportFallbackFixer",
481 "UrllibParseImportFallbackFixer",
482 "UrllibRequestImportFallbackFixer",
483 "UrllibErrorImportFallbackFixer",
484 "UrllibRobotParserImportFallbackFixer",
485 "XMLRPCClientImportFallbackFixer",
486 "XmlrpcServerImportFallbackFixer",
487 "HtmlParserImportFallbackFixer",
488 "HttpClientImportFallbackFixer",
489 "HttpCookiesImportFallbackFixer",
490 "PickleImportFallbackFixer",
491 "DbmGnuImportFallbackFixer",
492 "EmailMimeBaseImportFallbackFixer",
493 "EmailMimeImageImportFallbackFixer",
494 "EmailMimeMultipartImportFallbackFixer",
495 "EmailMimeNonmultipartImportFallbackFixer",
496 "EmailMimeTextImportFallbackFixer",
497 "TkinterImportFallbackFixer",
498 "TkinterDialogImportFallbackFixer",
499 "TkinterScrolledTextImportFallbackFixer",
500 "TkinterTixImportFallbackFixer",
501 "TkinterTtkImportFallbackFixer",
502 "TkinterConstantsImportFallbackFixer",
503 "TkinterDndImportFallbackFixer",
504 "TkinterColorchooserImportFallbackFixer",
505 "TkinterCommonDialogImportFallbackFixer",
506 "TkinterFontImportFallbackFixer",
507 "TkinterMessageboxImportFallbackFixer",
508 "XrangeToRangeFixer",
509 "UnicodeToStrFixer",
510 "UnichrToChrFixer",
511 "RawInputToInputFixer",
512 "RemoveFunctionDefAnnotationsFixer",
513 "ForwardReferenceAnnotationsFixer",
514 "RemoveAnnAssignFixer",
515 "ShortToLongFormSuperFixer",
516 "InlineKWOnlyArgsFixer",
517 "NewStyleClassesFixer",
518 "ItertoolsBuiltinsFixer",
519 "UnpackingGeneralizationsFixer",
520 "NamedTupleClassToAssignFixer",
521 "FStringToStrFormatFixer",
522 "NamedExprFixer",
523 "UnpackingGeneralizationsFixer",
524]
526# class GeneratorReturnToStopIterationExceptionFixer(fb.FixerBase):
527#
528# version_info = common.VersionInfo(
529# apply_since="2.0",
530# apply_until="3.3",
531# )
532#
533# def apply_fix(self, ctx: common.BuildContext, tree: ast.Module) -> ast.Module:
534# return tree
535#
536# def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
537# # NOTE (mb 2018-06-15): What about a generator nested in a function definition?
538# is_generator = any(
539# isinstance(sub_node, (ast.Yield, ast.YieldFrom))
540# for sub_node in ast.walk(node)
541# )
542# if not is_generator:
543# return node
544#
545# for sub_node in ast.walk(node):
546# pass
549# YIELD_FROM_EQUIVALENT = """
550# _i = iter(EXPR)
551# try:
552# _y = next(_i)
553# except StopIteration as _e:
554# _r = _e.value
555# else:
556# while 1:
557# try:
558# _s = yield _y
559# except GeneratorExit as _e:
560# try:
561# _m = _i.close
562# except AttributeError:
563# pass
564# else:
565# _m()
566# raise _e
567# except BaseException as _e:
568# _x = sys.exc_info()
569# try:
570# _m = _i.throw
571# except AttributeError:
572# raise _e
573# else:
574# try:
575# _y = _m(*_x)
576# except StopIteration as _e:
577# _r = _e.value
578# break
579# else:
580# try:
581# if _s is None:
582# _y = next(_i)
583# else:
584# _y = _i.send(_s)
585# except StopIteration as _e:
586# _r = _e.value
587# break
588# RESULT = _r
589# """in_len_body
592# class YieldFromFixer(fb.FixerBase):
593# # see https://www.python.org/dev/peps/pep-0380/
594# NOTE (mb 2018-06-14): We should definetly do the most simple case
595# but maybe we can also detect the more complex cases involving
596# send and return values and at least throw an error
598# class MetaclassFixer(fb.TransformerFixerBase):
599#
600# version_info = common.VersionInfo(
601# apply_since="2.0",
602# apply_until="2.7",
603# )
604#
605# def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef:
606# # class Foo(metaclass=X): => class Foo(object):\n __metaclass__ = X
609# class MatMulFixer(fb.TransformerFixerBase):
610#
611# version_info = common.VersionInfo(
612# apply_since="2.0",
613# apply_until="3.5",
614# )
615#
616# def visit_Binop(self, node: ast.BinOp) -> ast.Call:
617# # replace a @ b with a.__matmul__(b)
620# NOTE (mb 2018-06-24): I'm not gonna do it, but feel free to
621# implement it if you feel like it.
622#
623# class DecoratorFixer(fb.FixerBase):
624# """Replaces use of @decorators with function calls
625#
626# > @mydec1()
627# > @mydec2
628# > def myfn():
629# > pass
630# < def myfn():
631# < pass
632# < myfn = mydec2(myfn)
633# < myfn = mydec1()(myfn)
634# """
635#
636# version_info = common.VersionInfo(
637# apply_since="2.0",
638# apply_until="2.4",
639# )
640#
642# NOTE (mb 2018-06-24): I'm not gonna do it, but feel free to
643# implement it if you feel like it.
644#
645# class WithStatementToTryExceptFixer(fb.FixerBase):
646# """
647# > with expression as name:
648# > name
649#
650# < import sys
651# < __had_exception = False
652# < __manager = expression
653# < try:
654# < name = manager.__enter__()
655# < except:
656# < __had_exception = True
657# < ex_type, ex_value, traceback = sys.exc_info()
658# < __manager.__exit__(ex_type, ex_value, traceback)
659# < finally:
660# < if not __had_exception:
661# < __manager.__exit__(None, None, None)
662# """
663#
664# version_info = common.VersionInfo(
665# apply_since="2.0",
666# apply_until="2.4",
667# )
668#
670# NOTE (mb 2018-06-25): I'm not gonna do it, but feel free to
671# implement it if you feel like it.
672#
673# class ImplicitFormatIndexesFixer(fb.FixerBase):
674# """Replaces use of @decorators with function calls
675#
676# > "first: {} second: {:>9}".format(0, 1)
677# < "first: {0} second: {1:>9}".format(0, 1)
678# """
679#
680# version_info = common.VersionInfo(
681# apply_since="2.6",
682# apply_until="2.6",
683# )
684#