diff --git a/sqlalchemy_jsonapi/declarative/serializer.py b/sqlalchemy_jsonapi/declarative/serializer.py index 8aef4ae..a397a33 100644 --- a/sqlalchemy_jsonapi/declarative/serializer.py +++ b/sqlalchemy_jsonapi/declarative/serializer.py @@ -7,18 +7,22 @@ class JSONAPISerializer(object): """A JSON API serializer that serializes SQLAlchemy models.""" + model = None + primary_key = 'id' + fields = [] + dasherize = True + + def __init__(self): + """Ensure required members are not defaults.""" + if self.model is None: + raise TypeError("Model cannot be of type 'None'.") + if self.primary_key not in self.fields: + raise ValueError( + "Serializer fields must contain primary key '{}'".format( + self.primary_key)) def serialize(self, resources): """Serialize resource(s) according to json-api spec.""" - try: - self.fields - self.model - self.dasherize - except AttributeError: - raise - - if 'id' not in self.fields: - raise ValueError("Serializer fields must contain an 'id'") serialized = { 'meta': { 'sqlalchemy_jsonapi_version': '4.0.9' @@ -53,12 +57,14 @@ def _render_resource(self, resource): 'Resource(s) type must be the same as the serializer model type.') top_level_members = {} - top_level_members['id'] = str(resource.id) + try: + top_level_members['id'] = str(getattr(resource, self.primary_key)) + except AttributeError: + raise top_level_members['type'] = resource.__tablename__ top_level_members['attributes'] = self._render_attributes(resource) top_level_members['relationships'] = self._render_relationships( resource) - return top_level_members def _render_attributes(self, resource): @@ -77,7 +83,7 @@ def _render_attributes(self, resource): mapped_fields = {x: x for x in self.fields} for attribute in self.fields: - if attribute == 'id': + if attribute == self.primary_key: continue # Per json-api spec, we cannot render foreign keys # or relationsips in attributes. @@ -98,6 +104,7 @@ def _render_relationships(self, resource): """Render the resource's relationships.""" relationships = {} related_models = resource.__mapper__.relationships.keys() + primary_key_val = getattr(resource, self.primary_key) if self.dasherize: mapped_relationships = { x: dasherize(underscore(x)) for x in related_models} @@ -108,10 +115,12 @@ def _render_relationships(self, resource): relationships[mapped_relationships[model]] = { 'links': { 'self': '/{}/{}/relationships/{}'.format( - resource.__tablename__, resource.id, + resource.__tablename__, + primary_key_val, mapped_relationships[model]), 'related': '/{}/{}/{}'.format( - resource.__tablename__, resource.id, + resource.__tablename__, + primary_key_val, mapped_relationships[model]) } } diff --git a/sqlalchemy_jsonapi/unittests/declarative_tests/test_serialize.py b/sqlalchemy_jsonapi/unittests/declarative_tests/test_serialize.py index 5f6b507..f24d69e 100644 --- a/sqlalchemy_jsonapi/unittests/declarative_tests/test_serialize.py +++ b/sqlalchemy_jsonapi/unittests/declarative_tests/test_serialize.py @@ -298,7 +298,6 @@ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'first_name'] model = self.User - dasherize = True user = self.User(first_name='Sally') self.session.add(user) @@ -340,7 +339,6 @@ class PostSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['id', 'title'] model = self.Post - dasherize = True blog_post = self.Post(title='Foo') self.session.add(blog_post) @@ -464,7 +462,6 @@ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id'] model = self.Post - dasherize = True user = self.User(first_name='Sally') self.session.add(user) @@ -475,24 +472,6 @@ class UserSerializer(serializer.JSONAPISerializer): with self.assertRaises(TypeError): user_serializer.serialize(user) - def test_serialize_resource_with_missing_id_field(self): - """An 'id' is required in serializer fields.""" - - class UserSerializer(serializer.JSONAPISerializer): - """Declarative serializer for User.""" - fields = ['first_name'] - model = self.User - dasherize = True - - user = self.User(first_name='Sally') - self.session.add(user) - self.session.commit() - user = self.session.query(self.User).get(user.id) - - user_serializer = UserSerializer() - with self.assertRaises(ValueError): - user_serializer.serialize(user) - def test_serialize_resource_with_unknown_attribute_in_fields(self): """Cannot serialize attributes that are unknown to resource.""" @@ -500,7 +479,6 @@ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'firsts_names_unknown'] model = self.User - dasherize = True user = self.User(first_name='Sally') self.session.add(user) @@ -521,7 +499,6 @@ class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" fields = ['id', 'posts'] model = self.User - dasherize = True user = self.User(first_name='Sally') self.session.add(user) @@ -542,7 +519,6 @@ class PostSerializer(serializer.JSONAPISerializer): """Declarative serializer for Post.""" fields = ['id', 'author_id'] model = self.Post - dasherize = False blog_post = self.Post(title='Foo') self.session.add(blog_post) @@ -553,12 +529,16 @@ class PostSerializer(serializer.JSONAPISerializer): with self.assertRaises(AttributeError): blog_post_serializer.serialize(post) - def test_serialize_resource_with_no_defined_dasherize(self): - """Serializer requires dasherize member.""" + def test_serialize_resource_with_invalid_primary_key(self): + """Resource cannot have unknown primary key. + + The primary key must be an attribute on the resource. + """ class UserSerializer(serializer.JSONAPISerializer): - """Declarative serializer for User.""" - fields = ['id', 'firsts_names_unknown'] + """Declarative serializer for Post.""" + fields = ['unknown_primary_key', 'first_name'] + primary_key = 'unknown_primary_key' model = self.User user = self.User(first_name='Sally') @@ -570,36 +550,55 @@ class UserSerializer(serializer.JSONAPISerializer): with self.assertRaises(AttributeError): user_serializer.serialize(user) - def test_serialize_resource_with_no_defined_model(self): + +class TestSerializerInstantiationErrors(unittest.TestCase): + """Test exceptions raised in instantiation of serializer.""" + + def setUp(self): + """Configure sqlalchemy and session.""" + self.engine = create_engine('sqlite://') + Session = sessionmaker(bind=self.engine) + self.session = Session() + self.Base = declarative_base() + + class User(self.Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + first_name = Column(String(50), nullable=False) + + self.User = User + self.Base.metadata.create_all(self.engine) + + def tearDown(self): + """Reset the sqlalchemy engine.""" + self.Base.metadata.drop_all(self.engine) + + def test_serializer_with_no_defined_model(self): """Serializer requires model member.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" - fields = ['id', 'firsts_names_unknown'] - dasherize = True + fields = ['id'] - user = self.User(first_name='Sally') - self.session.add(user) - self.session.commit() - user = self.session.query(self.User).get(user.id) + with self.assertRaises(TypeError): + UserSerializer() - user_serializer = UserSerializer() - with self.assertRaises(AttributeError): - user_serializer.serialize(user) + def test_serializer_with_no_defined_fields(self): + """At minimum fields must exist.""" + class UserSerializer(serializer.JSONAPISerializer): + """Declarative serializer for User.""" + model = self.User - def test_serialize_resource_with_no_defined_fields(self): - """Serializer requires field member.""" + with self.assertRaises(ValueError): + UserSerializer() + + def test_serializer_with_missing_id_field(self): + """An 'id' is required in serializer fields.""" class UserSerializer(serializer.JSONAPISerializer): """Declarative serializer for User.""" - dasherize = True + fields = ['first_name'] model = self.User - user = self.User(first_name='Sally') - self.session.add(user) - self.session.commit() - user = self.session.query(self.User).get(user.id) - - user_serializer = UserSerializer() - with self.assertRaises(AttributeError): - user_serializer.serialize(user) + with self.assertRaises(ValueError): + UserSerializer()