Skip to content

Commit

Permalink
feat: serialize sqlalchemy relations
Browse files Browse the repository at this point in the history
  • Loading branch information
vinyguedess committed Nov 5, 2021
1 parent 4196e17 commit c4f735e
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ lint:
isort alcherializer tests

test:
python -m pytest -p no:warnings --cov=alcherializer -v
python -m pytest -p no:warnings --cov=alcherializer -vv
coverage html
30 changes: 27 additions & 3 deletions alcherializer/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
)

import sqlalchemy
from sqlalchemy.ext.declarative import DeclarativeMeta

from alcherializer import fields
from alcherializer.exceptions import MalformedMetaClassException
Expand Down Expand Up @@ -32,7 +33,7 @@ def data(self) -> Dict[str, Any]:
for instance in instances:
obj_dict = {}
for key in self.fields.keys():
obj_dict[key] = getattr(instance, key)
obj_dict[key] = self._get_instance_field_value(instance, key)

results.append(obj_dict)

Expand Down Expand Up @@ -77,8 +78,10 @@ def _get_fields(self) -> Dict[str, Any]:
continue

columns[key] = {
"type": value.type,
"required": value.nullable is False,
"type": value.type if hasattr(value, "type") else value,
"required": value.nullable is False
if hasattr(value, "nullable")
else False,
"validator": self._get_field_validator(key, value),
}

Expand All @@ -102,3 +105,24 @@ def _get_field_validator(self, key: str, field):
return fields.BooleanField(key, field)

return fields.BaseField(key, field)

def _get_instance_field_value(self, instance, field: str) -> Any:
value = getattr(instance, field)
if isinstance(value.__class__, DeclarativeMeta):
serializer = self.fields[field]["validator"]
if isinstance(serializer, Serializer):
serializer.instance = value
return serializer.data

if (
isinstance(value, list)
and len(value) > 0
and isinstance(value[0].__class__, DeclarativeMeta)
):
serializer = self.fields[field]["validator"]
if isinstance(serializer, Serializer):
serializer.instance = value
serializer.many = True
return serializer.data

return value
105 changes: 105 additions & 0 deletions tests/unit/serializer/test_data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

from alcherializer import Serializer

Expand Down Expand Up @@ -66,3 +67,107 @@ class Meta:

serializer = MySerializer(model)
assert serializer.data == {"id": 1}


def test_data_get_related_one_to_one_model() -> None:
class MyRelatedModel(declarative_base()):
id = sqlalchemy.Column(
sqlalchemy.Integer, primary_key=True, nullable=False
)
hello = sqlalchemy.Column(sqlalchemy.String, nullable=False)

__tablename__ = "my_related_model"

class MyModel(declarative_base()):
id = sqlalchemy.Column(
sqlalchemy.Integer, primary_key=True, nullable=False
)
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
related_id = sqlalchemy.Column(
sqlalchemy.Integer, sqlalchemy.ForeignKey(MyRelatedModel.id)
)

related = relationship(MyRelatedModel, uselist=False)

__tablename__ = "my_model"

class MyRelatedModelSerializer(Serializer):
class Meta:
model = MyRelatedModel

class MyModelSerializer(Serializer):
related = MyRelatedModelSerializer()

class Meta:
model = MyModel
fields = ["id", "name", "related"]

model = MyModel(
id=1,
name="my name",
related=MyRelatedModel(
id=1,
hello="world",
),
)

serializer = MyModelSerializer(model)
assert serializer.data == {
"id": 1,
"name": "my name",
"related": {"id": 1, "hello": "world"},
}


def test_data_get_related_has_many_models() -> None:
class MyRelatedModel(declarative_base()):
id = sqlalchemy.Column(
sqlalchemy.Integer, primary_key=True, nullable=False
)
hello = sqlalchemy.Column(sqlalchemy.String, nullable=False)

__tablename__ = "my_related_model"

class MyModel(declarative_base()):
id = sqlalchemy.Column(
sqlalchemy.Integer, primary_key=True, nullable=False
)
name = sqlalchemy.Column(sqlalchemy.String, nullable=False)
related_id = sqlalchemy.Column(
sqlalchemy.Integer, sqlalchemy.ForeignKey(MyRelatedModel.id)
)

related = relationship(MyRelatedModel, uselist=False)

__tablename__ = "my_model"

class MyRelatedModelSerializer(Serializer):
class Meta:
model = MyRelatedModel

class MyModelSerializer(Serializer):
related = MyRelatedModelSerializer()

class Meta:
model = MyModel
fields = ["id", "name", "related"]

model = MyModel(
id=1,
name="my name",
related=[
MyRelatedModel(
id=1,
hello="world",
),
],
)

serializer = MyModelSerializer(model)
assert serializer.data == {
"id": 1,
"name": "my name",
"related": [
{"id": 1, "hello": "world"},
],
}

0 comments on commit c4f735e

Please sign in to comment.