Repository: dbt-labs/dbt-core

Issue:
Enable deprecation: not-a-dictionary
Introduced in: v0.15
In the last week, raised by: 17 projects (<1%)

This is a quite specific one, having to do with the fact that some internal dbt objects used to be simple dictionaries and are now dataclasses. I think we should just raise exceptions on the dunder methods of `__iter__` and `__len__` for all superclasses of `FakeAPIObject` ([`BaseRelation`](https://github.com/dbt-labs/dbt/blob/95cca277c90fb1c3139e1ae4990a29d8038afcd0/core/dbt/adapters/base/relation.py#L23) is probably the most prominent).

https://github.com/dbt-labs/dbt/blob/95cca277c90fb1c3139e1ae4990a29d8038afcd0/core/dbt/contracts/relation.py#L45-L52


_Originally posted by @jtcohen6 in https://github.com/dbt-labs/dbt/issues/2798#issuecomment-919998174_


Discussion:

Resolving patch:
diff --git a/core/dbt/contracts/relation.py b/core/dbt/contracts/relation.py
--- a/core/dbt/contracts/relation.py
+++ b/core/dbt/contracts/relation.py
@@ -1,5 +1,5 @@
 from collections.abc import Mapping
-from dataclasses import dataclass, fields
+from dataclasses import dataclass
 from typing import (
     Optional, Dict,
 )
@@ -7,9 +7,8 @@
 
 from dbt.dataclass_schema import dbtClassMixin, StrEnum
 
-from dbt import deprecations
 from dbt.contracts.util import Replaceable
-from dbt.exceptions import CompilationException
+from dbt.exceptions import raise_dataclass_not_dict, CompilationException
 from dbt.utils import deep_merge
 
 
@@ -43,13 +42,10 @@ def __getitem__(self, key):
             raise KeyError(key) from None
 
     def __iter__(self):
-        deprecations.warn('not-a-dictionary', obj=self)
-        for _, name in self._get_fields():
-            yield name
+        raise_dataclass_not_dict(self)
 
     def __len__(self):
-        deprecations.warn('not-a-dictionary', obj=self)
-        return len(fields(self.__class__))
+        raise_dataclass_not_dict(self)
 
     def incorporate(self, **kwargs):
         value = self.to_dict(omit_none=True)
diff --git a/core/dbt/deprecations.py b/core/dbt/deprecations.py
--- a/core/dbt/deprecations.py
+++ b/core/dbt/deprecations.py
@@ -57,15 +57,6 @@ class DispatchPackagesDeprecation(DBTDeprecation):
     '''
 
 
-class NotADictionaryDeprecation(DBTDeprecation):
-    _name = 'not-a-dictionary'
-
-    _description = '''\
-    The object ("{obj}") was used as a dictionary. In a future version of dbt
-    this capability will be removed from objects of this type.
-    '''
-
-
 class ModelsKeyNonModelDeprecation(DBTDeprecation):
     _name = 'models-key-mismatch'
 
@@ -129,7 +120,6 @@ def warn(name, *args, **kwargs):
 
 deprecations_list: List[DBTDeprecation] = [
     DispatchPackagesDeprecation(),
-    NotADictionaryDeprecation(),
     ModelsKeyNonModelDeprecation(),
     PackageRedirectDeprecation()
 ]
diff --git a/core/dbt/exceptions.py b/core/dbt/exceptions.py
--- a/core/dbt/exceptions.py
+++ b/core/dbt/exceptions.py
@@ -674,6 +674,14 @@ def missing_relation(relation, model=None):
         model)
 
 
+def raise_dataclass_not_dict(obj):
+    msg = (
+        'The object ("{obj}") was used as a dictionary. This '
+        'capability has been removed from objects of this type.'
+    )
+    raise_compiler_error(msg)
+
+
 def relation_wrong_type(relation, expected_type, model=None):
     raise_compiler_error(
         ('Trying to create {expected_type} {relation}, '
@@ -993,6 +1001,7 @@ def warn(msg, node=None):
         raise_ambiguous_alias,
         raise_ambiguous_catalog_match,
         raise_cache_inconsistent,
+        raise_dataclass_not_dict,
         raise_compiler_error,
         raise_database_error,
         raise_dep_not_found,


Test patch: