| """Data structures configuring Black behavior. |
| |
| Mostly around Python language feature support per version and Black configuration |
| chosen by the user. |
| """ |
|
|
| import sys |
| from dataclasses import dataclass, field |
| from enum import Enum, auto |
| from hashlib import sha256 |
| from operator import attrgetter |
| from typing import Dict, Set |
| from warnings import warn |
|
|
| if sys.version_info < (3, 8): |
| from typing_extensions import Final |
| else: |
| from typing import Final |
|
|
| from black.const import DEFAULT_LINE_LENGTH |
|
|
|
|
| class TargetVersion(Enum): |
| PY33 = 3 |
| PY34 = 4 |
| PY35 = 5 |
| PY36 = 6 |
| PY37 = 7 |
| PY38 = 8 |
| PY39 = 9 |
| PY310 = 10 |
| PY311 = 11 |
|
|
|
|
| class Feature(Enum): |
| F_STRINGS = 2 |
| NUMERIC_UNDERSCORES = 3 |
| TRAILING_COMMA_IN_CALL = 4 |
| TRAILING_COMMA_IN_DEF = 5 |
| |
| |
| ASYNC_IDENTIFIERS = 6 |
| ASYNC_KEYWORDS = 7 |
| ASSIGNMENT_EXPRESSIONS = 8 |
| POS_ONLY_ARGUMENTS = 9 |
| RELAXED_DECORATORS = 10 |
| PATTERN_MATCHING = 11 |
| UNPACKING_ON_FLOW = 12 |
| ANN_ASSIGN_EXTENDED_RHS = 13 |
| EXCEPT_STAR = 14 |
| VARIADIC_GENERICS = 15 |
| DEBUG_F_STRINGS = 16 |
| PARENTHESIZED_CONTEXT_MANAGERS = 17 |
| FORCE_OPTIONAL_PARENTHESES = 50 |
|
|
| |
| FUTURE_ANNOTATIONS = 51 |
|
|
|
|
| FUTURE_FLAG_TO_FEATURE: Final = { |
| "annotations": Feature.FUTURE_ANNOTATIONS, |
| } |
|
|
|
|
| VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = { |
| TargetVersion.PY33: {Feature.ASYNC_IDENTIFIERS}, |
| TargetVersion.PY34: {Feature.ASYNC_IDENTIFIERS}, |
| TargetVersion.PY35: {Feature.TRAILING_COMMA_IN_CALL, Feature.ASYNC_IDENTIFIERS}, |
| TargetVersion.PY36: { |
| Feature.F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_IDENTIFIERS, |
| }, |
| TargetVersion.PY37: { |
| Feature.F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_KEYWORDS, |
| Feature.FUTURE_ANNOTATIONS, |
| }, |
| TargetVersion.PY38: { |
| Feature.F_STRINGS, |
| Feature.DEBUG_F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_KEYWORDS, |
| Feature.FUTURE_ANNOTATIONS, |
| Feature.ASSIGNMENT_EXPRESSIONS, |
| Feature.POS_ONLY_ARGUMENTS, |
| Feature.UNPACKING_ON_FLOW, |
| Feature.ANN_ASSIGN_EXTENDED_RHS, |
| }, |
| TargetVersion.PY39: { |
| Feature.F_STRINGS, |
| Feature.DEBUG_F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_KEYWORDS, |
| Feature.FUTURE_ANNOTATIONS, |
| Feature.ASSIGNMENT_EXPRESSIONS, |
| Feature.RELAXED_DECORATORS, |
| Feature.POS_ONLY_ARGUMENTS, |
| Feature.UNPACKING_ON_FLOW, |
| Feature.ANN_ASSIGN_EXTENDED_RHS, |
| Feature.PARENTHESIZED_CONTEXT_MANAGERS, |
| }, |
| TargetVersion.PY310: { |
| Feature.F_STRINGS, |
| Feature.DEBUG_F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_KEYWORDS, |
| Feature.FUTURE_ANNOTATIONS, |
| Feature.ASSIGNMENT_EXPRESSIONS, |
| Feature.RELAXED_DECORATORS, |
| Feature.POS_ONLY_ARGUMENTS, |
| Feature.UNPACKING_ON_FLOW, |
| Feature.ANN_ASSIGN_EXTENDED_RHS, |
| Feature.PARENTHESIZED_CONTEXT_MANAGERS, |
| Feature.PATTERN_MATCHING, |
| }, |
| TargetVersion.PY311: { |
| Feature.F_STRINGS, |
| Feature.DEBUG_F_STRINGS, |
| Feature.NUMERIC_UNDERSCORES, |
| Feature.TRAILING_COMMA_IN_CALL, |
| Feature.TRAILING_COMMA_IN_DEF, |
| Feature.ASYNC_KEYWORDS, |
| Feature.FUTURE_ANNOTATIONS, |
| Feature.ASSIGNMENT_EXPRESSIONS, |
| Feature.RELAXED_DECORATORS, |
| Feature.POS_ONLY_ARGUMENTS, |
| Feature.UNPACKING_ON_FLOW, |
| Feature.ANN_ASSIGN_EXTENDED_RHS, |
| Feature.PARENTHESIZED_CONTEXT_MANAGERS, |
| Feature.PATTERN_MATCHING, |
| Feature.EXCEPT_STAR, |
| Feature.VARIADIC_GENERICS, |
| }, |
| } |
|
|
|
|
| def supports_feature(target_versions: Set[TargetVersion], feature: Feature) -> bool: |
| return all(feature in VERSION_TO_FEATURES[version] for version in target_versions) |
|
|
|
|
| class Preview(Enum): |
| """Individual preview style features.""" |
|
|
| add_trailing_comma_consistently = auto() |
| blank_line_after_nested_stub_class = auto() |
| hex_codes_in_unicode_sequences = auto() |
| improved_async_statements_handling = auto() |
| multiline_string_handling = auto() |
| prefer_splitting_right_hand_side_of_assignments = auto() |
| |
| |
| string_processing = auto() |
| parenthesize_conditional_expressions = auto() |
| skip_magic_trailing_comma_in_subscript = auto() |
| wrap_long_dict_values_in_parens = auto() |
| wrap_multiple_context_managers_in_parens = auto() |
|
|
|
|
| class Deprecated(UserWarning): |
| """Visible deprecation warning.""" |
|
|
|
|
| @dataclass |
| class Mode: |
| target_versions: Set[TargetVersion] = field(default_factory=set) |
| line_length: int = DEFAULT_LINE_LENGTH |
| string_normalization: bool = True |
| is_pyi: bool = False |
| is_ipynb: bool = False |
| skip_source_first_line: bool = False |
| magic_trailing_comma: bool = True |
| experimental_string_processing: bool = False |
| python_cell_magics: Set[str] = field(default_factory=set) |
| preview: bool = False |
|
|
| def __post_init__(self) -> None: |
| if self.experimental_string_processing: |
| warn( |
| ( |
| "`experimental string processing` has been included in `preview`" |
| " and deprecated. Use `preview` instead." |
| ), |
| Deprecated, |
| ) |
|
|
| def __contains__(self, feature: Preview) -> bool: |
| """ |
| Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag. |
| |
| The argument is not checked and features are not differentiated. |
| They only exist to make development easier by clarifying intent. |
| """ |
| if feature is Preview.string_processing: |
| return self.preview or self.experimental_string_processing |
| return self.preview |
|
|
| def get_cache_key(self) -> str: |
| if self.target_versions: |
| version_str = ",".join( |
| str(version.value) |
| for version in sorted(self.target_versions, key=attrgetter("value")) |
| ) |
| else: |
| version_str = "-" |
| parts = [ |
| version_str, |
| str(self.line_length), |
| str(int(self.string_normalization)), |
| str(int(self.is_pyi)), |
| str(int(self.is_ipynb)), |
| str(int(self.skip_source_first_line)), |
| str(int(self.magic_trailing_comma)), |
| str(int(self.experimental_string_processing)), |
| str(int(self.preview)), |
| sha256((",".join(sorted(self.python_cell_magics))).encode()).hexdigest(), |
| ] |
| return ".".join(parts) |
|
|