| """Data structures configuring Black behavior. |
| |
| Mostly around Python language feature support per version and Black configuration |
| chosen by the user. |
| """ |
|
|
| from dataclasses import dataclass, field |
| from enum import Enum, auto |
| from hashlib import sha256 |
| from operator import attrgetter |
| 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 |
| PY312 = 12 |
| PY313 = 13 |
| PY314 = 14 |
|
|
| def pretty(self) -> str: |
| assert self.name[:2] == "PY" |
| return f"Python {self.name[2]}.{self.name[3:]}" |
|
|
|
|
| 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 |
| TYPE_PARAMS = 18 |
| |
| TYPE_PARAM_DEFAULTS = 20 |
| UNPARENTHESIZED_EXCEPT_TYPES = 21 |
| T_STRINGS = 22 |
| 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, |
| }, |
| TargetVersion.PY312: { |
| 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, |
| Feature.TYPE_PARAMS, |
| }, |
| TargetVersion.PY313: { |
| 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, |
| Feature.TYPE_PARAMS, |
| Feature.TYPE_PARAM_DEFAULTS, |
| }, |
| TargetVersion.PY314: { |
| 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, |
| Feature.TYPE_PARAMS, |
| Feature.TYPE_PARAM_DEFAULTS, |
| Feature.UNPARENTHESIZED_EXCEPT_TYPES, |
| Feature.T_STRINGS, |
| }, |
| } |
|
|
|
|
| def supports_feature(target_versions: set[TargetVersion], feature: Feature) -> bool: |
| if not target_versions: |
| raise ValueError("target_versions must not be empty") |
|
|
| return all(feature in VERSION_TO_FEATURES[version] for version in target_versions) |
|
|
|
|
| class Preview(Enum): |
| """Individual preview style features.""" |
|
|
| |
| |
| string_processing = auto() |
| hug_parens_with_braces_and_square_brackets = auto() |
| wrap_long_dict_values_in_parens = auto() |
| multiline_string_handling = auto() |
| always_one_newline_after_import = auto() |
| fix_fmt_skip_in_one_liners = auto() |
| standardize_type_comments = auto() |
| wrap_comprehension_in = auto() |
| |
| |
| remove_parens_around_except_types = auto() |
| normalize_cr_newlines = auto() |
| fix_module_docstring_detection = auto() |
| fix_type_expansion_split = auto() |
| remove_parens_from_assignment_lhs = auto() |
|
|
|
|
| UNSTABLE_FEATURES: set[Preview] = { |
| |
| Preview.string_processing, |
| |
| Preview.hug_parens_with_braces_and_square_brackets, |
| } |
|
|
|
|
| class Deprecated(UserWarning): |
| """Visible deprecation warning.""" |
|
|
|
|
| _MAX_CACHE_KEY_PART_LENGTH: Final = 32 |
|
|
|
|
| @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 |
| python_cell_magics: set[str] = field(default_factory=set) |
| preview: bool = False |
| unstable: bool = False |
| enabled_features: set[Preview] = field(default_factory=set) |
|
|
| def __contains__(self, feature: Preview) -> bool: |
| """ |
| Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag. |
| |
| In unstable mode, all features are enabled. In preview mode, all features |
| except those in UNSTABLE_FEATURES are enabled. Any features in |
| `self.enabled_features` are also enabled. |
| """ |
| if self.unstable: |
| return True |
| if feature in self.enabled_features: |
| return True |
| return self.preview and feature not in UNSTABLE_FEATURES |
|
|
| 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 = "-" |
| if len(version_str) > _MAX_CACHE_KEY_PART_LENGTH: |
| version_str = sha256(version_str.encode()).hexdigest()[ |
| :_MAX_CACHE_KEY_PART_LENGTH |
| ] |
| features_and_magics = ( |
| ",".join(sorted(f.name for f in self.enabled_features)) |
| + "@" |
| + ",".join(sorted(self.python_cell_magics)) |
| ) |
| if len(features_and_magics) > _MAX_CACHE_KEY_PART_LENGTH: |
| features_and_magics = sha256(features_and_magics.encode()).hexdigest()[ |
| :_MAX_CACHE_KEY_PART_LENGTH |
| ] |
| 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.preview)), |
| str(int(self.unstable)), |
| features_and_magics, |
| ] |
| return ".".join(parts) |
|
|
| def __hash__(self) -> int: |
| return hash(( |
| frozenset(self.target_versions), |
| self.line_length, |
| self.string_normalization, |
| self.is_pyi, |
| self.is_ipynb, |
| self.skip_source_first_line, |
| self.magic_trailing_comma, |
| frozenset(self.python_cell_magics), |
| self.preview, |
| self.unstable, |
| frozenset(self.enabled_features), |
| )) |
|
|