]>
Commit | Line | Data |
---|---|---|
1 | """Data structures configuring Black behavior. | |
2 | ||
3 | Mostly around Python language feature support per version and Black configuration | |
4 | chosen by the user. | |
5 | """ | |
6 | ||
7 | from dataclasses import dataclass, field | |
8 | from enum import Enum, auto | |
9 | from hashlib import sha256 | |
10 | from operator import attrgetter | |
11 | from typing import Dict, Final, Set | |
12 | from warnings import warn | |
13 | ||
14 | from black.const import DEFAULT_LINE_LENGTH | |
15 | ||
16 | ||
17 | class TargetVersion(Enum): | |
18 | PY33 = 3 | |
19 | PY34 = 4 | |
20 | PY35 = 5 | |
21 | PY36 = 6 | |
22 | PY37 = 7 | |
23 | PY38 = 8 | |
24 | PY39 = 9 | |
25 | PY310 = 10 | |
26 | PY311 = 11 | |
27 | PY312 = 12 | |
28 | ||
29 | ||
30 | class Feature(Enum): | |
31 | F_STRINGS = 2 | |
32 | NUMERIC_UNDERSCORES = 3 | |
33 | TRAILING_COMMA_IN_CALL = 4 | |
34 | TRAILING_COMMA_IN_DEF = 5 | |
35 | # The following two feature-flags are mutually exclusive, and exactly one should be | |
36 | # set for every version of python. | |
37 | ASYNC_IDENTIFIERS = 6 | |
38 | ASYNC_KEYWORDS = 7 | |
39 | ASSIGNMENT_EXPRESSIONS = 8 | |
40 | POS_ONLY_ARGUMENTS = 9 | |
41 | RELAXED_DECORATORS = 10 | |
42 | PATTERN_MATCHING = 11 | |
43 | UNPACKING_ON_FLOW = 12 | |
44 | ANN_ASSIGN_EXTENDED_RHS = 13 | |
45 | EXCEPT_STAR = 14 | |
46 | VARIADIC_GENERICS = 15 | |
47 | DEBUG_F_STRINGS = 16 | |
48 | PARENTHESIZED_CONTEXT_MANAGERS = 17 | |
49 | TYPE_PARAMS = 18 | |
50 | FORCE_OPTIONAL_PARENTHESES = 50 | |
51 | ||
52 | # __future__ flags | |
53 | FUTURE_ANNOTATIONS = 51 | |
54 | ||
55 | ||
56 | FUTURE_FLAG_TO_FEATURE: Final = { | |
57 | "annotations": Feature.FUTURE_ANNOTATIONS, | |
58 | } | |
59 | ||
60 | ||
61 | VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = { | |
62 | TargetVersion.PY33: {Feature.ASYNC_IDENTIFIERS}, | |
63 | TargetVersion.PY34: {Feature.ASYNC_IDENTIFIERS}, | |
64 | TargetVersion.PY35: {Feature.TRAILING_COMMA_IN_CALL, Feature.ASYNC_IDENTIFIERS}, | |
65 | TargetVersion.PY36: { | |
66 | Feature.F_STRINGS, | |
67 | Feature.NUMERIC_UNDERSCORES, | |
68 | Feature.TRAILING_COMMA_IN_CALL, | |
69 | Feature.TRAILING_COMMA_IN_DEF, | |
70 | Feature.ASYNC_IDENTIFIERS, | |
71 | }, | |
72 | TargetVersion.PY37: { | |
73 | Feature.F_STRINGS, | |
74 | Feature.NUMERIC_UNDERSCORES, | |
75 | Feature.TRAILING_COMMA_IN_CALL, | |
76 | Feature.TRAILING_COMMA_IN_DEF, | |
77 | Feature.ASYNC_KEYWORDS, | |
78 | Feature.FUTURE_ANNOTATIONS, | |
79 | }, | |
80 | TargetVersion.PY38: { | |
81 | Feature.F_STRINGS, | |
82 | Feature.DEBUG_F_STRINGS, | |
83 | Feature.NUMERIC_UNDERSCORES, | |
84 | Feature.TRAILING_COMMA_IN_CALL, | |
85 | Feature.TRAILING_COMMA_IN_DEF, | |
86 | Feature.ASYNC_KEYWORDS, | |
87 | Feature.FUTURE_ANNOTATIONS, | |
88 | Feature.ASSIGNMENT_EXPRESSIONS, | |
89 | Feature.POS_ONLY_ARGUMENTS, | |
90 | Feature.UNPACKING_ON_FLOW, | |
91 | Feature.ANN_ASSIGN_EXTENDED_RHS, | |
92 | }, | |
93 | TargetVersion.PY39: { | |
94 | Feature.F_STRINGS, | |
95 | Feature.DEBUG_F_STRINGS, | |
96 | Feature.NUMERIC_UNDERSCORES, | |
97 | Feature.TRAILING_COMMA_IN_CALL, | |
98 | Feature.TRAILING_COMMA_IN_DEF, | |
99 | Feature.ASYNC_KEYWORDS, | |
100 | Feature.FUTURE_ANNOTATIONS, | |
101 | Feature.ASSIGNMENT_EXPRESSIONS, | |
102 | Feature.RELAXED_DECORATORS, | |
103 | Feature.POS_ONLY_ARGUMENTS, | |
104 | Feature.UNPACKING_ON_FLOW, | |
105 | Feature.ANN_ASSIGN_EXTENDED_RHS, | |
106 | Feature.PARENTHESIZED_CONTEXT_MANAGERS, | |
107 | }, | |
108 | TargetVersion.PY310: { | |
109 | Feature.F_STRINGS, | |
110 | Feature.DEBUG_F_STRINGS, | |
111 | Feature.NUMERIC_UNDERSCORES, | |
112 | Feature.TRAILING_COMMA_IN_CALL, | |
113 | Feature.TRAILING_COMMA_IN_DEF, | |
114 | Feature.ASYNC_KEYWORDS, | |
115 | Feature.FUTURE_ANNOTATIONS, | |
116 | Feature.ASSIGNMENT_EXPRESSIONS, | |
117 | Feature.RELAXED_DECORATORS, | |
118 | Feature.POS_ONLY_ARGUMENTS, | |
119 | Feature.UNPACKING_ON_FLOW, | |
120 | Feature.ANN_ASSIGN_EXTENDED_RHS, | |
121 | Feature.PARENTHESIZED_CONTEXT_MANAGERS, | |
122 | Feature.PATTERN_MATCHING, | |
123 | }, | |
124 | TargetVersion.PY311: { | |
125 | Feature.F_STRINGS, | |
126 | Feature.DEBUG_F_STRINGS, | |
127 | Feature.NUMERIC_UNDERSCORES, | |
128 | Feature.TRAILING_COMMA_IN_CALL, | |
129 | Feature.TRAILING_COMMA_IN_DEF, | |
130 | Feature.ASYNC_KEYWORDS, | |
131 | Feature.FUTURE_ANNOTATIONS, | |
132 | Feature.ASSIGNMENT_EXPRESSIONS, | |
133 | Feature.RELAXED_DECORATORS, | |
134 | Feature.POS_ONLY_ARGUMENTS, | |
135 | Feature.UNPACKING_ON_FLOW, | |
136 | Feature.ANN_ASSIGN_EXTENDED_RHS, | |
137 | Feature.PARENTHESIZED_CONTEXT_MANAGERS, | |
138 | Feature.PATTERN_MATCHING, | |
139 | Feature.EXCEPT_STAR, | |
140 | Feature.VARIADIC_GENERICS, | |
141 | }, | |
142 | TargetVersion.PY312: { | |
143 | Feature.F_STRINGS, | |
144 | Feature.DEBUG_F_STRINGS, | |
145 | Feature.NUMERIC_UNDERSCORES, | |
146 | Feature.TRAILING_COMMA_IN_CALL, | |
147 | Feature.TRAILING_COMMA_IN_DEF, | |
148 | Feature.ASYNC_KEYWORDS, | |
149 | Feature.FUTURE_ANNOTATIONS, | |
150 | Feature.ASSIGNMENT_EXPRESSIONS, | |
151 | Feature.RELAXED_DECORATORS, | |
152 | Feature.POS_ONLY_ARGUMENTS, | |
153 | Feature.UNPACKING_ON_FLOW, | |
154 | Feature.ANN_ASSIGN_EXTENDED_RHS, | |
155 | Feature.PARENTHESIZED_CONTEXT_MANAGERS, | |
156 | Feature.PATTERN_MATCHING, | |
157 | Feature.EXCEPT_STAR, | |
158 | Feature.VARIADIC_GENERICS, | |
159 | Feature.TYPE_PARAMS, | |
160 | }, | |
161 | } | |
162 | ||
163 | ||
164 | def supports_feature(target_versions: Set[TargetVersion], feature: Feature) -> bool: | |
165 | return all(feature in VERSION_TO_FEATURES[version] for version in target_versions) | |
166 | ||
167 | ||
168 | class Preview(Enum): | |
169 | """Individual preview style features.""" | |
170 | ||
171 | add_trailing_comma_consistently = auto() | |
172 | blank_line_after_nested_stub_class = auto() | |
173 | blank_line_between_nested_and_def_stub_file = auto() | |
174 | hex_codes_in_unicode_sequences = auto() | |
175 | improved_async_statements_handling = auto() | |
176 | multiline_string_handling = auto() | |
177 | no_blank_line_before_class_docstring = auto() | |
178 | prefer_splitting_right_hand_side_of_assignments = auto() | |
179 | # NOTE: string_processing requires wrap_long_dict_values_in_parens | |
180 | # for https://github.com/psf/black/issues/3117 to be fixed. | |
181 | string_processing = auto() | |
182 | parenthesize_conditional_expressions = auto() | |
183 | skip_magic_trailing_comma_in_subscript = auto() | |
184 | wrap_long_dict_values_in_parens = auto() | |
185 | wrap_multiple_context_managers_in_parens = auto() | |
186 | dummy_implementations = auto() | |
187 | walrus_subscript = auto() | |
188 | ||
189 | ||
190 | class Deprecated(UserWarning): | |
191 | """Visible deprecation warning.""" | |
192 | ||
193 | ||
194 | @dataclass | |
195 | class Mode: | |
196 | target_versions: Set[TargetVersion] = field(default_factory=set) | |
197 | line_length: int = DEFAULT_LINE_LENGTH | |
198 | string_normalization: bool = True | |
199 | is_pyi: bool = False | |
200 | is_ipynb: bool = False | |
201 | skip_source_first_line: bool = False | |
202 | magic_trailing_comma: bool = True | |
203 | experimental_string_processing: bool = False | |
204 | python_cell_magics: Set[str] = field(default_factory=set) | |
205 | preview: bool = False | |
206 | ||
207 | def __post_init__(self) -> None: | |
208 | if self.experimental_string_processing: | |
209 | warn( | |
210 | "`experimental string processing` has been included in `preview`" | |
211 | " and deprecated. Use `preview` instead.", | |
212 | Deprecated, | |
213 | ) | |
214 | ||
215 | def __contains__(self, feature: Preview) -> bool: | |
216 | """ | |
217 | Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag. | |
218 | ||
219 | The argument is not checked and features are not differentiated. | |
220 | They only exist to make development easier by clarifying intent. | |
221 | """ | |
222 | if feature is Preview.string_processing: | |
223 | return self.preview or self.experimental_string_processing | |
224 | return self.preview | |
225 | ||
226 | def get_cache_key(self) -> str: | |
227 | if self.target_versions: | |
228 | version_str = ",".join( | |
229 | str(version.value) | |
230 | for version in sorted(self.target_versions, key=attrgetter("value")) | |
231 | ) | |
232 | else: | |
233 | version_str = "-" | |
234 | parts = [ | |
235 | version_str, | |
236 | str(self.line_length), | |
237 | str(int(self.string_normalization)), | |
238 | str(int(self.is_pyi)), | |
239 | str(int(self.is_ipynb)), | |
240 | str(int(self.skip_source_first_line)), | |
241 | str(int(self.magic_trailing_comma)), | |
242 | str(int(self.experimental_string_processing)), | |
243 | str(int(self.preview)), | |
244 | sha256((",".join(sorted(self.python_cell_magics))).encode()).hexdigest(), | |
245 | ] | |
246 | return ".".join(parts) |