Repository: flyingcircusio/batou

Issue:
f = batou.component.Attribute(str) not raising excaption anymore when accessing without setting
Having

```
class MyService(batou.component.Component):

    foo = batou.component.Attribute(str)
```

and later either 

```
def configure(self):
    baa = self.foo
```

or inside Jinja

```jinja
{{component.foo}}
```

is not raising an exeption anymore. I think this was introduced with 2.3b4 as in b3 it was still working.


Discussion:

Patch:
diff --git a/src/batou/component.py b/src/batou/component.py
index 7b57f460..48a44921 100644
--- a/src/batou/component.py
+++ b/src/batou/component.py
@@ -1045,6 +1045,9 @@ class ConfigString(str):
     """
 
 
+NO_DEFAULT = object()
+
+
 class Attribute(object):
     """An attribute descriptor is used to provide:
 
@@ -1084,7 +1087,7 @@ class Attribute(object):
     def __init__(
         self,
         conversion=str,
-        default=None,
+        default=NO_DEFAULT,
         expand=True,
         map=False,
     ):
@@ -1105,6 +1108,8 @@ class Attribute(object):
             value = self.default
             if isinstance(self.default, ConfigString):
                 value = self.from_config_string(obj, value)
+            elif value is NO_DEFAULT:
+                raise AttributeError("No override and no default given.")
             self.__set__(obj, value)
         return self.instances[obj]
 


Test patch:
diff --git a/src/batou/tests/test_component.py b/src/batou/tests/test_component.py
index 1b50ef46..13f9f06f 100644
--- a/src/batou/tests/test_component.py
+++ b/src/batou/tests/test_component.py
@@ -577,10 +577,23 @@ def test_attribute_conversion_functions(conv_func, conf_value, expected, root):
 
     f = Foo()
     root.component += f
-    root.prepare()
     assert f.a == expected
 
 
+def test_attribute_missing_override_and_default(root):
+    class Foo(Component):
+        a = Attribute(str)
+
+        def configure(self):
+            self.a
+
+    f = Foo()
+    with pytest.raises(AttributeError) as e:
+        root.component += f
+
+    assert e.value.args[0] == "No override and no default given."
+
+
 @pytest.mark.parametrize(
     "default,expected",
     [
@@ -597,7 +610,6 @@ def test_attribute_conversion_default(default, expected, root):
 
     f = Foo()
     root.component += f
-    root.prepare()
     assert f.a == expected