Repository: jbradberry/universe

Issue:
`name` should not be a mandatory attribute on Components
Currently a Component's name is used to determine which registry dicts an Entity gets registered into, but `name` is also a very likely name for a field.  We should do something else to set up the entity registries.

Also, change `SpeciesComponent.singular_name` to `SpeciesComponent.name`.

Comments:

Patch:
diff --git a/universe/components.py b/universe/components.py
index 98d274a..7bba5cb 100644
--- a/universe/components.py
+++ b/universe/components.py
@@ -32,13 +32,13 @@ class Component(metaclass=MetaComponent):
 
 
 class MetadataComponent(Component):
-    name = 'metadata'
+    _name = 'metadata'
 
     type = fields.CharField(required=True)
 
 
 class PositionComponent(Component):
-    name = 'position'
+    _name = 'position'
 
     x = fields.IntField()
     y = fields.IntField()
@@ -48,7 +48,7 @@ class PositionComponent(Component):
 
 
 class QueueComponent(Component):
-    name = 'queue'
+    _name = 'queue'
 
     queue = fields.ListField()
 
@@ -57,20 +57,20 @@ class QueueComponent(Component):
 
 
 class SpeciesComponent(Component):
-    name = 'species'
+    _name = 'species'
 
-    singular_name = fields.CharField(required=True)
+    name = fields.CharField(required=True)
     plural_name = fields.CharField(required=True)
     growth_rate = fields.IntField(required=True)
 
 
 class OwnershipComponent(Component):
-    name = 'ownership'
+    _name = 'ownership'
 
     owner_id = fields.IntField(required=False)
 
 
 class PopulationComponent(Component):
-    name = 'population'
+    _name = 'population'
 
     population = fields.IntField(required=False)
diff --git a/universe/engine.py b/universe/engine.py
index 9e8d022..1206f24 100644
--- a/universe/engine.py
+++ b/universe/engine.py
@@ -12,7 +12,10 @@ class Entity:
         for component in self.__dict__.get('_components', {}).values():
             if name in component._fields:
                 return self.__dict__['_data'].get(name)
-        return self.__dict__.get(name)
+        try:
+            return self.__dict__[name]
+        except KeyError:
+            raise AttributeError(f"{self.__class__.__name__!r} object has no attribute {name!r}")
 
     def __setattr__(self, name, value):
         for component in self.__dict__.get('_components', {}).values():
@@ -51,7 +54,7 @@ class Manager:
         if name in self._entity_registry:
             raise ValueError("{} is already a registered entity type.".format(name))
         _components.append(components.MetadataComponent())
-        self._entity_registry[name] = {component.name: component for component in _components}
+        self._entity_registry[name] = {component._name: component for component in _components}
 
     def get_updates(self, _id):
         return self._updates.get(_id, [])
diff --git a/universe/systems.py b/universe/systems.py
index 66f7240..7a7043b 100644
--- a/universe/systems.py
+++ b/universe/systems.py
@@ -40,7 +40,7 @@ class MovementSystem:
         remaining = self.N - self.step
         x_p = (entity.x + remaining * entity.dx).to_integral_value()
         y_p = (entity.y + remaining * entity.dy).to_integral_value()
-        if 'x_p' not in entity.__dict__:
+        if not hasattr(entity, 'x_p'):
             entity.x_p, entity.y_p = x_p, y_p
         # If the projected endpoint is not stable, intercepting objects should just use Euler.
         if (entity.x_p, entity.y_p) != (x_p, y_p):
@@ -54,7 +54,7 @@ class MovementSystem:
 
         target_entity = manager.get_entity('position', move['target_id'])
         # Only proceed with moving towards the target's projected endpoint if it is stable.
-        if target_entity.x_p is None:
+        if getattr(target_entity, 'x_p', None) is None:
             return
 
         x_p, y_p = target_entity.x_p, target_entity.y_p


Test patch:
diff --git a/tests/test_systems.py b/tests/test_systems.py
index 81cbaee..6711970 100644
--- a/tests/test_systems.py
+++ b/tests/test_systems.py
@@ -12,7 +12,7 @@ class PopulationTestCase(unittest.TestCase):
             'entities': {
                 0: {
                     'type': 'species',
-                    'singular_name': 'Human',
+                    'name': 'Human',
                     'plural_name': 'Humans',
                     'growth_rate': 15,
                 },
@@ -36,7 +36,7 @@ class PopulationTestCase(unittest.TestCase):
                           'entities': {
                               0: {
                                   'type': 'species',
-                                  'singular_name': 'Human',
+                                  'name': 'Human',
                                   'plural_name': 'Humans',
                                   'growth_rate': 15,
                                   },