File: _runner.py

package info (click to toggle)
python-libcst 1.4.0-1.2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,928 kB
  • sloc: python: 76,235; makefile: 10; sh: 2
file content (160 lines) | stat: -rw-r--r-- 5,467 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
#

"""
Provides everything needed to run a CodemodCommand.
"""

import traceback
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Sequence, Union

from libcst import parse_module, PartialParserConfig
from libcst.codemod._codemod import Codemod

# All datastructures defined in this class are pickleable so that they can be used
# as a return value with the multiprocessing module.


@dataclass(frozen=True)
class TransformSuccess:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod was successful.
    Stores all the information we might need to display to the user upon success, as
    well as the transformed file contents.
    """

    #: All warning messages that were generated during the codemod.
    warning_messages: Sequence[str]

    #: The updated code, post-codemod.
    code: str


@dataclass(frozen=True)
class TransformFailure:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod failed.
    Stores all the information we might need to display to the user upon a failure.
    """

    #: All warning messages that were generated before the codemod crashed.
    warning_messages: Sequence[str]

    #: The exception that was raised during the codemod.
    error: Exception

    #: The traceback string that was recorded at the time of exception.
    traceback_str: str


@dataclass(frozen=True)
class TransformExit:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod was interrupted
    by the user (e.g. KeyboardInterrupt).
    """

    #: An empty list of warnings, included so that all
    #: :class:`~libcst.codemod.TransformResult` have a ``warning_messages`` attribute.
    warning_messages: Sequence[str] = ()


class SkipReason(Enum):
    """
    An enumeration of all valid reasons for a codemod to skip.
    """

    #: The module was skipped because we detected that it was generated code, and
    #: we were configured to skip generated files.
    GENERATED = "generated"

    #: The module was skipped because we detected that it was blacklisted, and we
    #: were configured to skip blacklisted files.
    BLACKLISTED = "blacklisted"

    #: The module was skipped because the codemod requested us to skip using the
    #: :class:`~libcst.codemod.SkipFile` exception.
    OTHER = "other"


@dataclass(frozen=True)
class TransformSkip:
    """
    A :class:`~libcst.codemod.TransformResult` used when the codemod requested to
    be skipped. This could be because it's a generated file, or due to filename
    blacklist, or because the transform raised :class:`~libcst.codemod.SkipFile`.
    """

    #: The reason that we skipped codemodding this module.
    skip_reason: SkipReason

    #: The description populated from the :class:`~libcst.codemod.SkipFile` exception.
    skip_description: str

    #: All warning messages that were generated before the codemod decided to skip.
    warning_messages: Sequence[str] = ()


class SkipFile(Exception):
    """
    Raise this exception to skip codemodding the current file.

    The exception message should be the reason for skipping.
    """


TransformResult = Union[
    TransformSuccess, TransformFailure, TransformExit, TransformSkip
]


def transform_module(
    transformer: Codemod, code: str, *, python_version: Optional[str] = None
) -> TransformResult:
    """
    Given a module as represented by a string and a :class:`~libcst.codemod.Codemod`
    that we wish to run, execute the codemod on the code and return a
    :class:`~libcst.codemod.TransformResult`. This should never raise an exception.
    On success, this returns a :class:`~libcst.codemod.TransformSuccess` containing
    any generated warnings as well as the transformed code. If the codemod is
    interrupted with a Ctrl+C, this returns a :class:`~libcst.codemod.TransformExit`.
    If the codemod elected to skip by throwing a :class:`~libcst.codemod.SkipFile`
    exception, this will return a :class:`~libcst.codemod.TransformSkip` containing
    the reason for skipping as well as any warnings that were generated before
    the codemod decided to skip. If the codemod throws an unexpected exception,
    this will return a :class:`~libcst.codemod.TransformFailure` containing the
    exception that occured as well as any warnings that were generated before the
    codemod crashed.
    """
    try:
        input_tree = parse_module(
            code,
            config=(
                PartialParserConfig(python_version=python_version)
                if python_version is not None
                else PartialParserConfig()
            ),
        )
        output_tree = transformer.transform_module(input_tree)
        return TransformSuccess(
            code=output_tree.code, warning_messages=transformer.context.warnings
        )
    except KeyboardInterrupt:
        return TransformExit()
    except SkipFile as ex:
        return TransformSkip(
            skip_description=str(ex),
            skip_reason=SkipReason.OTHER,
            warning_messages=transformer.context.warnings,
        )
    except Exception as ex:
        return TransformFailure(
            error=ex,
            traceback_str=traceback.format_exc(),
            warning_messages=transformer.context.warnings,
        )