Repository: dbt-labs/dbt-core

Issue:
[CT-2879] [Bug] UnboundLocalError: local variable 'default_agg_time_dimesion' referenced before assignment
### Is this a new bug in dbt-core?

- [X] I believe this is a new bug in dbt-core
- [X] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

In core 1.6.0rc1 if you have semantic models and metrics defined, and if a semantic model (that has no default.agg_time_dimension specified) has a measure that doesn't specify a `agg_time_dimension` then when you run `dbt parse` you get the error
```
An error occurred while checking aggregation time dimension for a semantic model - UnboundLocalError: local variable 'default_agg_time_dimesion' referenced before assignment
 ```

### Expected Behavior

If a semantic model (that has no default.agg_time_dimension specified) has a measure that doesn't specify a `agg_time_dimension` then when you run `dbt parse` then a validation issue should be logged
```
Aggregation time dimension for measure {measure.name} is not set! This should either be set directly on the measure specification in the model, or else defaulted to the primary time dimension in the data source containing the measure.
```

### Steps To Reproduce

1. Define a measure without a `agg_time_dimension` on a semantic model which doesn't have a default `agg_time_dimension` set
2. Run `dbt parse`
3. be sad about an `UnboundLocalError`

### Relevant log output

```shell
An error occurred while checking aggregation time dimension for a semantic model - UnboundLocalError: local variable 'default_agg_time_dimesion' referenced before assignment
```
This happens due to a logical error in https://github.com/dbt-labs/dbt-core/blob/fe9c875d32554cf36a08a94a6ee5d356066d80f8/core/dbt/contracts/graph/nodes.py#L1571. Specifically if a semantic model doesn't have `defaults` defined, and a measure of that semantic model doesn't have an `agg_time_dimension` we try to set it to the detected default value (which due to the logical error hasn't been bound). It's kind of ironic because we're blowing up on the thing we're trying to detect.
```


### Environment

```markdown
- OS: macOS 13.4.1 (Ventura)
- Python: 3.8.10
- dbt: 1.6.0rc1
```


### Which database adapter are you using with dbt?

_No response_

### Additional Context

The fix is to change this [if](https://github.com/dbt-labs/dbt-core/blob/fe9c875d32554cf36a08a94a6ee5d356066d80f8/core/dbt/contracts/graph/nodes.py#L1571) statement to
```
default_agg_time_dimension = self.defaults.agg_time_dimension if self.defaults is not None else None
```


Discussion:

Resolving patch:
diff --git a/core/dbt/contracts/graph/nodes.py b/core/dbt/contracts/graph/nodes.py
--- a/core/dbt/contracts/graph/nodes.py
+++ b/core/dbt/contracts/graph/nodes.py
@@ -1568,14 +1568,15 @@ def checked_agg_time_dimension_for_measure(
             measure is not None
         ), f"No measure with name ({measure_reference.element_name}) in semantic_model with name ({self.name})"
 
-        if self.defaults is not None:
-            default_agg_time_dimesion = self.defaults.agg_time_dimension
+        default_agg_time_dimension = (
+            self.defaults.agg_time_dimension if self.defaults is not None else None
+        )
 
-        agg_time_dimension_name = measure.agg_time_dimension or default_agg_time_dimesion
+        agg_time_dimension_name = measure.agg_time_dimension or default_agg_time_dimension
         assert agg_time_dimension_name is not None, (
-            f"Aggregation time dimension for measure {measure.name} is not set! This should either be set directly on "
-            f"the measure specification in the model, or else defaulted to the primary time dimension in the data "
-            f"source containing the measure."
+            f"Aggregation time dimension for measure {measure.name} on semantic model {self.name} is not set! "
+            "To fix this either specify a default `agg_time_dimension` for the semantic model or define an "
+            "`agg_time_dimension` on the measure directly."
         )
         return TimeDimensionReference(element_name=agg_time_dimension_name)
 


Test patch:
diff --git a/tests/unit/test_semantic_models.py b/tests/unit/test_semantic_models.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/test_semantic_models.py
@@ -0,0 +1,81 @@
+import pytest
+
+from typing import List
+
+from dbt.contracts.graph.nodes import SemanticModel
+from dbt.contracts.graph.semantic_models import Dimension, Entity, Measure, Defaults
+from dbt.node_types import NodeType
+from dbt_semantic_interfaces.references import MeasureReference
+from dbt_semantic_interfaces.type_enums import AggregationType, DimensionType, EntityType
+
+
+@pytest.fixture(scope="function")
+def dimensions() -> List[Dimension]:
+    return [Dimension(name="ds", type=DimensionType)]
+
+
+@pytest.fixture(scope="function")
+def entities() -> List[Entity]:
+    return [Entity(name="test_entity", type=EntityType.PRIMARY, expr="id")]
+
+
+@pytest.fixture(scope="function")
+def measures() -> List[Measure]:
+    return [Measure(name="test_measure", agg=AggregationType.COUNT, expr="id")]
+
+
+@pytest.fixture(scope="function")
+def default_semantic_model(
+    dimensions: List[Dimension], entities: List[Entity], measures: List[Measure]
+) -> SemanticModel:
+    return SemanticModel(
+        name="test_semantic_model",
+        resource_type=NodeType.SemanticModel,
+        model="ref('test_model')",
+        package_name="test",
+        path="test_path",
+        original_file_path="test_fixture",
+        unique_id=f"{NodeType.SemanticModel}.test.test_semantic_model",
+        fqn=[],
+        defaults=Defaults(agg_time_dimension="ds"),
+        dimensions=dimensions,
+        entities=entities,
+        measures=measures,
+        node_relation=None,
+    )
+
+
+def test_checked_agg_time_dimension_for_measure_via_defaults(
+    default_semantic_model: SemanticModel,
+):
+    assert default_semantic_model.defaults.agg_time_dimension is not None
+    measure = default_semantic_model.measures[0]
+    measure.agg_time_dimension = None
+    default_semantic_model.checked_agg_time_dimension_for_measure(
+        MeasureReference(element_name=measure.name)
+    )
+
+
+def test_checked_agg_time_dimension_for_measure_via_measure(default_semantic_model: SemanticModel):
+    default_semantic_model.defaults = None
+    measure = default_semantic_model.measures[0]
+    measure.agg_time_dimension = default_semantic_model.dimensions[0].name
+    default_semantic_model.checked_agg_time_dimension_for_measure(
+        MeasureReference(element_name=measure.name)
+    )
+
+
+def test_checked_agg_time_dimension_for_measure_exception(default_semantic_model: SemanticModel):
+    default_semantic_model.defaults = None
+    measure = default_semantic_model.measures[0]
+    measure.agg_time_dimension = None
+
+    with pytest.raises(AssertionError) as execinfo:
+        default_semantic_model.checked_agg_time_dimension_for_measure(
+            MeasureReference(measure.name)
+        )
+
+    assert (
+        f"Aggregation time dimension for measure {measure.name} on semantic model {default_semantic_model.name}"
+        in str(execinfo.value)
+    )