From b9ad7c2c43af39fd57251fe35ed4cd97772a029a Mon Sep 17 00:00:00 2001 From: Mike Alfare Date: Thu, 29 Aug 2024 18:11:10 -0400 Subject: [PATCH] move flag initialization into rendered flag, adopt Rendered naming convention --- dbt_common/behavior_flags.py | 122 +++++++++++++----------------- tests/unit/test_behavior_flags.py | 28 +++---- 2 files changed, 63 insertions(+), 87 deletions(-) diff --git a/dbt_common/behavior_flags.py b/dbt_common/behavior_flags.py index 83e9eb3..8161912 100644 --- a/dbt_common/behavior_flags.py +++ b/dbt_common/behavior_flags.py @@ -8,26 +8,44 @@ # This is the suggested way to implement a TypedDict with optional arguments from typing import Optional as NotRequired -from dbt_common.events.base_types import WarnLevel from dbt_common.events.functions import fire_event from dbt_common.events.types import BehaviorDeprecationEvent from dbt_common.exceptions import DbtInternalError -class BehaviorFlag: +class BehaviorFlag(TypedDict): + """ + A set of config used to create a BehaviorFlag + + Args: + name: the name of the behavior flag + default: default setting, starts as False, becomes True in the next minor release + deprecation_version: the version when the default will change to True + deprecation_message: an additional message to send when the flag evaluates to False + docs_url: the url to the relevant docs on docs.getdbt.com + """ + + name: str + default: bool + source: NotRequired[str] + deprecation_version: NotRequired[str] + deprecation_message: NotRequired[str] + docs_url: NotRequired[str] + + +class BehaviorFlagRendered: """ The canonical behavior flag that gets used through dbt packages Args: - name: the name of the behavior flag, e.g. enforce_quoting_on_relation_creation - setting: the flag setting, after accounting for user input and the default - deprecation_event: the event to fire if the flag evaluates to False + flag: the configuration for the behavior flag + user_overrides: a set of user settings, one of which may be an override on this behavior flag """ - def __init__(self, name: str, setting: bool, deprecation_event: WarnLevel) -> None: - self.name = name - self.setting = setting - self.deprecation_event = deprecation_event + def __init__(self, flag: BehaviorFlag, user_overrides: Dict[str, Any]) -> None: + self.name = flag["name"] + self.setting = user_overrides.get(flag["name"], flag["default"]) + self.deprecation_event = self._deprecation_event(flag) @property def setting(self) -> bool: @@ -43,74 +61,40 @@ def setting(self, value: bool) -> None: def no_warn(self) -> bool: return self._setting + def _deprecation_event(self, flag: BehaviorFlag) -> BehaviorDeprecationEvent: + return BehaviorDeprecationEvent( + flag_name=flag["name"], + flag_source=flag.get("source", self._default_source()), + deprecation_version=flag.get("deprecation_version"), + deprecation_message=flag.get("deprecation_message"), + docs_url=flag.get("docs_url"), + ) + + @staticmethod + def _default_source() -> str: + """ + If the maintainer did not provide a source, default to the module that called `register`. + For adapters, this will likely be `dbt.adapters..impl` for `dbt-foo`. + """ + frame = inspect.stack()[5] + if module := inspect.getmodule(frame[0]): + return module.__name__ + return "Unknown" + def __bool__(self) -> bool: return self.setting -class RawBehaviorFlag(TypedDict): - """ - A set of config used to create a BehaviorFlag - - Args: - name: the name of the behavior flag - default: default setting, starts as False, becomes True in the next minor release - deprecation_version: the version when the default will change to True - deprecation_message: an additional message to send when the flag evaluates to False - docs_url: the url to the relevant docs on docs.getdbt.com - """ - - name: str - default: bool - source: NotRequired[str] - deprecation_version: NotRequired[str] - deprecation_message: NotRequired[str] - docs_url: NotRequired[str] - - # this is effectively a dictionary that supports dot notation # it makes usage easy, e.g. adapter.behavior.my_flag class Behavior: - _flags: List[BehaviorFlag] - - def __init__( - self, - behavior_flags: List[RawBehaviorFlag], - user_overrides: Dict[str, Any], - ) -> None: - flags = [] - for raw_flag in behavior_flags: - flags.append( - BehaviorFlag( - name=raw_flag["name"], - setting=user_overrides.get(raw_flag["name"], raw_flag["default"]), - deprecation_event=_behavior_deprecation_event(raw_flag), - ) - ) - self._flags = flags - - def __getattr__(self, name: str) -> BehaviorFlag: + _flags: List[BehaviorFlagRendered] + + def __init__(self, flags: List[BehaviorFlag], user_overrides: Dict[str, Any]) -> None: + self._flags = [BehaviorFlagRendered(flag, user_overrides) for flag in flags] + + def __getattr__(self, name: str) -> BehaviorFlagRendered: for flag in self._flags: if flag.name == name: return flag raise DbtInternalError(f"The flag {name} has not be registered.") - - -def _behavior_deprecation_event(flag: RawBehaviorFlag) -> BehaviorDeprecationEvent: - return BehaviorDeprecationEvent( - flag_name=flag["name"], - flag_source=flag.get("source", _default_source()), - deprecation_version=flag.get("deprecation_version"), - deprecation_message=flag.get("deprecation_message"), - docs_url=flag.get("docs_url"), - ) - - -def _default_source() -> str: - """ - If the maintainer did not provide a source, default to the module that called `register`. - For adapters, this will likely be `dbt.adapters..impl` for `dbt-foo`. - """ - frame = inspect.stack()[3] - if module := inspect.getmodule(frame[0]): - return module.__name__ - return "Unknown" diff --git a/tests/unit/test_behavior_flags.py b/tests/unit/test_behavior_flags.py index 9d5dc3b..7739bfa 100644 --- a/tests/unit/test_behavior_flags.py +++ b/tests/unit/test_behavior_flags.py @@ -10,11 +10,11 @@ def test_behavior_default(): behavior = Behavior( - behavior_flags=[ + [ {"name": "default_false_flag", "default": False}, {"name": "default_true_flag", "default": True}, ], - user_overrides={}, + {}, ) assert behavior.default_false_flag.setting is False @@ -23,7 +23,7 @@ def test_behavior_default(): def test_behavior_user_override(): behavior = Behavior( - behavior_flags=[ + [ {"name": "flag_default_false", "default": False}, {"name": "flag_default_false_override_false", "default": False}, {"name": "flag_default_false_override_true", "default": False}, @@ -31,7 +31,7 @@ def test_behavior_user_override(): {"name": "flag_default_true_override_false", "default": True}, {"name": "flag_default_true_override_true", "default": True}, ], - user_overrides={ + { "flag_default_false_override_false": False, "flag_default_false_override_true": True, "flag_default_true_override_false": False, @@ -49,11 +49,11 @@ def test_behavior_user_override(): def test_behavior_flag_can_be_used_as_conditional(): behavior = Behavior( - behavior_flags=[ + [ {"name": "flag_false", "default": False}, {"name": "flag_true", "default": True}, ], - user_overrides={}, + {}, ) assert False if behavior.flag_false else True @@ -71,11 +71,11 @@ def event_catcher(mocker: MockerFixture) -> EventCatcher: def test_behavior_flags_emit_deprecation_event_on_evaluation(event_catcher) -> None: behavior = Behavior( - behavior_flags=[ + [ {"name": "flag_false", "default": False}, {"name": "flag_true", "default": True}, ], - user_overrides={}, + {}, ) # trigger the evaluation, no event should fire @@ -90,10 +90,7 @@ def test_behavior_flags_emit_deprecation_event_on_evaluation(event_catcher) -> N def test_behavior_flags_emit_correct_deprecation(event_catcher) -> None: - behavior = Behavior( - behavior_flags=[{"name": "flag_false", "default": False}], - user_overrides={}, - ) + behavior = Behavior([{"name": "flag_false", "default": False}], {}) # trigger the evaluation if behavior.flag_false: @@ -106,12 +103,7 @@ def test_behavior_flags_emit_correct_deprecation(event_catcher) -> None: def test_behavior_flags_no_deprecation_event_on_no_warn(event_catcher) -> None: - behavior = Behavior( - behavior_flags=[ - {"name": "flag_false", "default": False}, - ], - user_overrides={}, - ) + behavior = Behavior([{"name": "flag_false", "default": False}], {}) # trigger the evaluation with no_warn, no event should fire if behavior.flag_false.no_warn: