refactor merging of instances from queryset to fakepydantic

This commit is contained in:
collerek
2020-08-09 12:53:28 +02:00
parent 3f2568b27e
commit 836836c136
5 changed files with 49 additions and 37 deletions

BIN
.coverage

Binary file not shown.

View File

@ -10,6 +10,9 @@
<a href="https://www.codefactor.io/repository/github/collerek/async-orm"> <a href="https://www.codefactor.io/repository/github/collerek/async-orm">
<img src="https://www.codefactor.io/repository/github/collerek/async-orm/badge" alt="CodeFactor" /> <img src="https://www.codefactor.io/repository/github/collerek/async-orm/badge" alt="CodeFactor" />
</a> </a>
<a href="https://app.codacy.com/manual/collerek/async-orm?utm_source=github.com&utm_medium=referral&utm_content=collerek/async-orm&utm_campaign=Badge_Grade_Dashboard">
<img src="https://api.codacy.com/project/badge/Grade/62568734f70f49cd8ea7a1a0b2d0c107" alt="Codacy" />
</a>
</p> </p>
The `async-orm` package is an async ORM for Python, with support for Postgres, The `async-orm` package is an async ORM for Python, with support for Postgres,
@ -26,7 +29,7 @@ The goal was to create a simple orm that can be used directly with [`fastapi`][f
Initial work was inspired by [`encode/orm`][encode/orm]. Initial work was inspired by [`encode/orm`][encode/orm].
The encode package was too simple (i.e. no ability to join two times to the same table) and used typesystem for data checks. The encode package was too simple (i.e. no ability to join two times to the same table) and used typesystem for data checks.
**aysn-orm is still under development:** We recommend pinning any dependencies with `aorm~=0.0.1` **async-orm is still under development:** We recommend pinning any dependencies with `aorm~=0.0.1`
**Note**: Use `ipython` to try this from the console, since it supports `await`. **Note**: Use `ipython` to try this from the console, since it supports `await`.
@ -44,8 +47,9 @@ class Note(orm.Model):
__database__ = database __database__ = database
__metadata__ = metadata __metadata__ = metadata
# primary keys of type int by dafault are set to autoincrement
id = orm.Integer(primary_key=True) id = orm.Integer(primary_key=True)
text = orm.String(max_length=100) text = orm.String(length=100)
completed = orm.Boolean(default=False) completed = orm.Boolean(default=False)
# Create the database # Create the database
@ -97,7 +101,7 @@ class Album(orm.Model):
__database__ = database __database__ = database
id = orm.Integer(primary_key=True) id = orm.Integer(primary_key=True)
name = orm.String(max_length=100) name = orm.String(length=100)
class Track(orm.Model): class Track(orm.Model):
@ -107,7 +111,7 @@ class Track(orm.Model):
id = orm.Integer(primary_key=True) id = orm.Integer(primary_key=True)
album = orm.ForeignKey(Album) album = orm.ForeignKey(Album)
title = orm.String(max_length=100) title = orm.String(length=100)
position = orm.Integer() position = orm.Integer()
@ -138,6 +142,12 @@ assert track.album.name == "Malibu"
track = await Track.objects.select_related("album").get(title="The Bird") track = await Track.objects.select_related("album").get(title="The Bird")
assert track.album.name == "Malibu" assert track.album.name == "Malibu"
# By default you also get a second side of the relation
# constructed as lowercase source model name +'s' (tracks in this case)
# you can also provide custom name with parameter related_name
album = await Album.objects.select_related("tracks").all()
assert len(album.tracks) == 3
# Fetch instances, with a filter across an FK relationship. # Fetch instances, with a filter across an FK relationship.
tracks = Track.objects.filter(album__name="Fantasies") tracks = Track.objects.filter(album__name="Fantasies")
assert len(tracks) == 2 assert len(tracks) == 2

View File

@ -249,8 +249,10 @@ class ForeignKey(BaseField):
else: else:
if not isinstance(value, self.to.pk_type()): if not isinstance(value, self.to.pk_type()):
raise RelationshipInstanceError( raise RelationshipInstanceError(
f"Relationship error - ForeignKey {self.to.__name__} is of type {self.to.pk_type()} " f"Relationship error - ForeignKey {self.to.__name__} "
f"of type {self.__type__} while {type(value)} passed as a parameter." f"is of type {self.to.pk_type()} "
f"of type {self.__type__} "
f"while {type(value)} passed as a parameter."
) )
model = create_dummy_instance(fk=self.to, pk=value) model = create_dummy_instance(fk=self.to, pk=value)

View File

@ -210,7 +210,7 @@ class FakePydantic(list, metaclass=ModelMetaclass):
return self.__table__.primary_key.columns.values()[0] return self.__table__.primary_key.columns.values()[0]
@classmethod @classmethod
def pk_type(cls): def pk_type(cls) -> Any:
return cls.__model_fields__[cls.__pkname__].__type__ return cls.__model_fields__[cls.__pkname__].__type__
def dict(self) -> Dict: # noqa: A003 def dict(self) -> Dict: # noqa: A003
@ -259,6 +259,35 @@ class FakePydantic(list, metaclass=ModelMetaclass):
) )
return self_fields return self_fields
@classmethod
def merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]:
merged_rows = []
for index, model in enumerate(result_rows):
if index > 0 and model.pk == result_rows[index - 1].pk:
result_rows[-1] = cls.merge_two_instances(model, merged_rows[-1])
else:
merged_rows.append(model)
return merged_rows
@classmethod
def merge_two_instances(cls, one: "Model", other: "Model") -> "Model":
for field in one.__model_fields__.keys():
# print(field, one.dict(), other.dict())
if isinstance(getattr(one, field), list) and not isinstance(
getattr(one, field), Model
):
setattr(other, field, getattr(one, field) + getattr(other, field))
elif isinstance(getattr(one, field), Model):
if getattr(one, field).pk == getattr(other, field).pk:
setattr(
other,
field,
cls.merge_two_instances(
getattr(one, field), getattr(other, field)
),
)
return other
class Model(FakePydantic): class Model(FakePydantic):
__abstract__ = True __abstract__ = True

View File

@ -479,39 +479,10 @@ class QuerySet:
for row in rows for row in rows
] ]
result_rows = self.merge_result_rows(result_rows) result_rows = self.model_cls.merge_instances_list(result_rows)
return result_rows return result_rows
@classmethod
def merge_result_rows(cls, result_rows: List["Model"]) -> List["Model"]:
merged_rows = []
for index, model in enumerate(result_rows):
if index > 0 and model.pk == result_rows[index - 1].pk:
result_rows[-1] = cls.merge_two_instances(model, merged_rows[-1])
else:
merged_rows.append(model)
return merged_rows
@classmethod
def merge_two_instances(cls, one: "Model", other: "Model") -> "Model":
for field in one.__model_fields__.keys():
# print(field, one.dict(), other.dict())
if isinstance(getattr(one, field), list) and not isinstance(
getattr(one, field), orm.models.Model
):
setattr(other, field, getattr(one, field) + getattr(other, field))
elif isinstance(getattr(one, field), orm.models.Model):
if getattr(one, field).pk == getattr(other, field).pk:
setattr(
other,
field,
cls.merge_two_instances(
getattr(one, field), getattr(other, field)
),
)
return other
async def create(self, **kwargs: Any) -> "Model": async def create(self, **kwargs: Any) -> "Model":
new_kwargs = dict(**kwargs) new_kwargs = dict(**kwargs)