_imports2s module

exception pyflyby._imports2s.ImportAlreadyExistsError
pyflyby._imports2s.ImportPathForRelativeImportsCtx(codeblock)

Context manager that temporarily modifies sys.path so that relative imports for the given codeblock work as expected.

Return type:

ContextManager[Any, bool | None]

exception pyflyby._imports2s.LineNumberAmbiguousError
exception pyflyby._imports2s.LineNumberNotFoundError
exception pyflyby._imports2s.NoImportBlockError
class pyflyby._imports2s.SourceToSourceFileImportsTransformation(arg: Any)
_apply_local_import_removals()

Apply the local-import removals recorded by remove_import.

The removals are grouped by (block, statement start line) so that all aliases removed from one statement are handled in a single rewrite, and statements are processed bottom-up within each block so that edits never shift the line numbers of statements not yet processed.

Return type:

None

_create_import_block_from_group(group, lines, start_line, end_line)

Create an import block from a group of import statements.

Extracts the import lines from source text, creates a PythonBlock and transformation, and adds it to import_blocks (wrapped with line metadata).

Parameters:
  • group (list[Union[Import, ImportFrom]]) – Consecutive import AST nodes to extract.

  • lines (list[str]) – All source lines of the file (1-indexed via lines[lineno - 1]).

  • start_line (int) – First line number of the group (1-indexed).

  • end_line (int) – Last line number of the group, accounting for multiline imports.

Return type:

None

_extract_imports_from_statement(stmt)

Recursively extract imports from a statement’s body (e.g., FunctionDef, ClassDef).

Return type:

None

_extract_local_import_blocks()

Recursively extract import blocks from function and class bodies. This allows us to find and remove unused imports within functions/classes.

Return type:

None

_line_contains_import(line, imp)

Check if a line contains the given import statement.

Parse the line as an import statement and compare Import objects, rather than using string matching which is fragile with spacing.

Return type:

bool

_original_block_startpos: dict[int, int]
_pending_local_removals: list[tuple[Import, int]]
_remove_local_imports_from_output(output)

Post-process the output to remove local imports that have been deleted.

This is necessary because local imports are embedded in function bodies, which are stored in self.blocks as plain text. When we remove imports from local import blocks, we need to also remove those lines from the output.

Return type:

FileText

_rewrite_local_import_statement(lines, rel, imps, lineno)

Rewrite the import statement starting at lines[rel] with the imports in imps removed. Co-located code – other aliases in the same statement, semicolon-separated statements on the same line, parenthesized continuation lines – is preserved; the physical lines are deleted only when nothing else remains on them.

Parameters:
  • lines (List[str]) – Source lines of the block, modified in place.

  • rel (int) – Index into lines of the first line of the import statement.

  • imps (List[Import]) – The Import s to remove from the statement.

  • lineno (int) – Absolute line number in the file (for logging).

Return type:

None

_split_semicolon_chained_imports(output)

Split semicolon-chained import statements into separate lines.

For local import blocks that have semicolon_suffixes (code after semicolons), replace those lines with the import on one line and the remaining code on the next.

Return type:

FileText

add_import(imp, lineno=inf)

Add the specified import. Picks an existing global import block to add to, or if none found, creates a new one near the beginning of the module.

Parameters:

lineno (Any) – Line before which to add the import. Inf means no constraint.

Return type:

None

blocks: list[SourceToSourceImportBlockTransformation | SourceToSourceTransformation]
find_import_block_by_lineno(lineno)

Find the import block containing the given line number.

Handles both top-level and local (function/class) import blocks. For local imports wrapped in _LocalImportBlockWrapper, checks the original line range. For regular imports, checks the line number range.

Return type:

Union[SourceToSourceImportBlockTransformation, _LocalImportBlockWrapper] SourceToSourceImportBlockTransformation or _LocalImportBlockWrapper

import_blocks: list[SourceToSourceImportBlockTransformation | _LocalImportBlockWrapper]
insert_new_blocks_after_comments(blocks)
Return type:

None

insert_new_import_block()

Adds a new empty imports block. It is added before the first non-comment statement. Intended to be used when the input contains no import blocks (before uses).

Return type:

SourceToSourceImportBlockTransformation

insert_new_import_block_after_future_imports()
Return type:

SourceToSourceImportBlockTransformation

preprocess()
Return type:

None

pretty_print(params=None)
Return type:

FileText

remove_import(imp, lineno)

Remove the given import.

Return type:

Import

select_import_block_by_closest_prefix_match(imp, max_lineno)

Heuristically pick an import block that imp “fits” best into. The selection is based on the block that contains the import with the longest common prefix.

Parameters:

max_lineno (Union[int, float]) – Only return import blocks earlier than max_lineno.

Return type:

SourceToSourceImportBlockTransformation SourceToSourceImportBlockTransformation

tidy_local_imports: bool = False
class pyflyby._imports2s.SourceToSourceImportBlockTransformation(arg: Any)
importset: ImportSet
preprocess()
Return type:

None

pretty_print(params=None)
Return type:

str

class pyflyby._imports2s.SourceToSourceTransformation(arg: Any)
_output: PythonBlock
preprocess()
Return type:

None

pretty_print(params=None)
Return type:

FileText

class pyflyby._imports2s.SourceToSourceTransformationBase(arg: Any)
classmethod _from_source_code(codeblock)
Return type:

SourceToSourceTransformationBase

input: PythonBlock
output(params=None)

Pretty-print and return as a PythonBlock.

Return type:

PythonBlock PythonBlock

preprocess()
Return type:

None

pretty_print(params=None)
Return type:

Union[FileText, str]

class pyflyby._imports2s._LocalImportBlockWrapper(transform, start_lineno, end_lineno=None, semicolon_suffixes=None)

Wrapper for import blocks found within function/class bodies. Preserves the original line number range since the block’s internal line numbers may not match the file’s line numbers.

This will be useful for tidy imports which only know how to handle top level import.

_id: str
_original_imports: set[Import]
_semicolon_suffixes: dict[int, str]
end_lineno: int
get_removed_imports()

Return the set of imports that have been removed from this block.

Return type:

set[Import]

importset: ImportSet
start_lineno: int
transform: SourceToSourceImportBlockTransformation
class pyflyby._imports2s._NoImportBlockTransformer(block, transformations, transform_strings)

AST-aware textual rewriter for a single block of (non-top-level-import) code. See _transform_noimport_block for the behavior contract.

All state is set up in __init__; call run once to produce the rewritten PythonBlock.

_abspos(lineno, col_offset)

Return the absolute byte offset of a (1-based lineno, col_offset) AST position.

Return type:

int

_add_name_edit(node, end_node, v)

Queue a replacement of the span from node’s start through end_node’s end with the literal v.

Return type:

None

_add_regex_edit(node)

Queue a _regex_replace rewrite over node’s own source span.

Return type:

None

_block: PythonBlock
_data: bytes
_edits: list[tuple[int, int, bytes]]
_handle_attribute_chain(node)

Rewrite a head-anchored dotted-name reference. node is the outermost ast.Attribute of a chain; if the chain’s base is not a bare name, recurse into it instead.

Return type:

None

_key_specs: list[tuple[tuple[str, ...], str]]
_line_starts: list[int]
_match_key(components)

Return the (components, replacement) of the longest transformation key that is a component-wise prefix of components, or None if none matches.

Return type:

Optional[tuple[tuple[str, ...], str]]

_node_end(node)

Return the absolute byte offset of node’s end position.

Return type:

int

_node_start(node)

Return the absolute byte offset of node’s start position.

Return type:

int

_regex_replace(text)

Apply each transformation to text as a word-boundary regex substitution. Used for spans that contain only dotted names (local imports) or that we deliberately rewrite verbatim (strings).

Return type:

str

_transform_strings: bool
_transformations: Dict[str, str] | ImportMap
_visit(node)

Recursively walk node, queuing edits for matching name references, attribute chains, local imports, and (when transform_strings) string literals.

Return type:

None

run()

Walk the AST collecting edits, then apply them and return the rewritten block. Returns the (re-wrapped) input unchanged if no references matched.

Return type:

PythonBlock

pyflyby._imports2s._group_consecutive_imports(body)

Group consecutive import statements from an AST body.

Parameters:

body (list[stmt]) – List of AST nodes from a function/class body

Returns:

List of groups, where each group is a list of consecutive import statements

Return type:

list[list[Union[Import, ImportFrom]]]

pyflyby._imports2s._is_future_only_import_block(block)
Return type:

bool

pyflyby._imports2s._maybe_insert_pass(lines, idx, indent, lineno)

Insert a pass statement at idx when removing a line would leave a block-opener (a line ending with :) without a body.

After a line has been deleted at position idx, this function walks backwards to find the nearest non-empty preceding line. If that line ends with : (a compound-statement header such as def, class, if, etc.) and the next non-empty line after idx is at an equal or lower indentation level, the block body is gone and a pass statement is inserted at idx using indent spaces.

Parameters:
  • lines (list[str]) – Source lines of the block, already modified (the import line has been deleted before this call).

  • idx (int) – Index into lines where the deleted line used to be.

  • indent (int) – Column offset (number of leading spaces) of the deleted line, used to indent the inserted pass.

  • lineno (int) – Absolute line number in the file (used only for logging).

Return type:

None

pyflyby._imports2s._transform_noimport_block(block, transformations, transform_strings)

Apply transformations to a block of (non-top-level-import) code.

Unlike top-level import blocks – which are parsed and rewritten exactly – the rest of the code body can only be transformed heuristically. We do so in an AST-aware way:

  • References to dotted names are matched head-anchored and component-wise, so e.g. foo.bar is rewritten in foo.bar and foo.bar.baz but not in x.foo.bar (where foo.bar is an attribute of some other object x).

  • String literals are left alone by default, so the contents of e.g. "foo.bar" are not altered. Pass transform_strings=True to additionally rewrite inside string literals (including docstrings and f-string text).

  • Comments are never modified.

  • Local (e.g. function-body) import statements are rewritten with the same crude textual replacement used for top-level imports; this is safe because import statements contain only dotted names.

Note: on Python < 3.12 the positions of an f-string’s internal nodes are unreliable (PEP 701), so f-strings are treated opaquely there – their expression parts are not rewritten, and their text is only rewritten (as a whole) when transform_strings is true. On Python >= 3.12, f-string expression parts are rewritten like any other code.

See https://github.com/deshaw/pyflyby/issues/175.

Return type:

PythonBlock PythonBlock

pyflyby._imports2s.fix_unused_and_missing_imports(codeblock, add_missing=True, remove_unused='AUTOMATIC', add_mandatory=True, db=None, params=None, tidy_local_imports=False)

Check for unused and missing imports, and fix them automatically.

Also formats imports.

By default only top-level imports are tidied. Set tidy_local_imports=True to also remove unused imports inside function and class bodies.

Individual imports can be excluded from removal by adding # tidy-imports: ignore-import as a trailing comment. This is whitespace sentitive between and must be a single space after the #, and after the :

In the example below, m1 and m3 are unused, so are automatically removed. np was undefined, so an import numpy as np was automatically added.

>>> codeblock = PythonBlock(
...     'from foo import m1, m2, m3, m4\n'
...     'm2, m4, np.foo', filename="/tmp/foo.py")
>>> print(fix_unused_and_missing_imports(codeblock, add_mandatory=False))
[PYFLYBY] /tmp/foo.py: removed unused 'from foo import m1'
[PYFLYBY] /tmp/foo.py: removed unused 'from foo import m3'
[PYFLYBY] /tmp/foo.py: added 'import numpy as np'
import numpy as np
from foo import m2, m4
m2, m4, np.foo
Parameters:

tidy_local_imports (bool) – If True, also tidy imports within function and class bodies. Defaults to False.

Return type:

PythonBlock PythonBlock