Repository: googlefonts/glyphsLib

Issue:
GSLayer.name fails with AttributeError if parent GSGlyph is 'orphan'
```python
>>> from glyphsLib.classes import GSGlyph, GSLayer
>>> l = GSLayer()
>>> l.layerId = l.associatedMasterId = "layer-0"
>>> print(l)
<GSLayer "" (orphan)>
>>> g = GSGlyph()
>>> g.layers.append(l)
>>> print(l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/google/home/clupo/oss/glyphsLib/Lib/glyphsLib/classes.py", line 3690, in __repr__
    name = self.name
           ^^^^^^^^^
  File "/usr/local/google/home/clupo/oss/glyphsLib/Lib/glyphsLib/classes.py", line 3746, in name
    master = self.parent.parent.masterForId(self.associatedMasterId)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'masterForId'
```

The `name` property of GSLayer (used in its `__repr__` e.g. when printing) should not fail if the GSGlyph which the layer belongs to in turn does not belong to any GSFont but is free-floating (e.g. when writing unit tests or when a parent GSFont is not needed at all).

Discussion:

Patch:
diff --git a/Lib/glyphsLib/classes.py b/Lib/glyphsLib/classes.py
index 0e9e5c33..bb40b5ee 100755
--- a/Lib/glyphsLib/classes.py
+++ b/Lib/glyphsLib/classes.py
@@ -3742,7 +3742,12 @@ class GSLayer(GSBase):
 
     @property
     def name(self):
-        if self.associatedMasterId and self._is_master_layer and self.parent:
+        if (
+            self.associatedMasterId
+            and self._is_master_layer
+            and self.parent
+            and self.parent.parent
+        ):
             master = self.parent.parent.masterForId(self.associatedMasterId)
             if master:
                 return master.name


Test patch:
diff --git a/tests/classes_test.py b/tests/classes_test.py
index 5f335cc9..f4819c56 100755
--- a/tests/classes_test.py
+++ b/tests/classes_test.py
@@ -1091,6 +1091,27 @@ class GSLayerFromFileTest(GSObjectsTestCase):
         layer = self.layer
         self.assertIsNotNone(layer.__repr__())
 
+    def test_repr_orphan_glyph(self):
+        # https://github.com/googlefonts/glyphsLib/issues/1014
+        layer = GSLayer()
+        self.assertIsNone(layer.parent)  # orphan layer
+
+        expected = '<GSLayer "" (orphan)>'
+        self.assertEqual(repr(layer), expected)
+
+        layer.layerId = layer.associatedMasterId = "layer-0"
+        self.assertTrue(layer._is_master_layer)
+        self.assertEqual(repr(layer), expected)
+
+        parent = GSGlyph()
+        parent.layers.append(layer)
+        self.assertEqual(layer.parent, parent)  # no longer orphan layer
+        self.assertIsNone(parent.parent)  # but still orphan glyph
+
+        # this should not crash with
+        #   AttributeError: 'NoneType' object has no attribute 'masterForId'
+        self.assertEqual(repr(layer), expected)
+
     def test_parent(self):
         self.assertIs(self.layer.parent, self.glyph)
         self.assertIs(self.layer._background.parent, self.glyph)