diff --git a/.coverage b/.coverage index ecaa279..09e8343 100644 Binary files a/.coverage and b/.coverage differ diff --git a/README.md b/README.md index 15272f9..3186786 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ CodeFactor + +Codacy +

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]. 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`. @@ -44,8 +47,9 @@ class Note(orm.Model): __database__ = database __metadata__ = metadata + # primary keys of type int by dafault are set to autoincrement id = orm.Integer(primary_key=True) - text = orm.String(max_length=100) + text = orm.String(length=100) completed = orm.Boolean(default=False) # Create the database @@ -97,7 +101,7 @@ class Album(orm.Model): __database__ = database id = orm.Integer(primary_key=True) - name = orm.String(max_length=100) + name = orm.String(length=100) class Track(orm.Model): @@ -107,7 +111,7 @@ class Track(orm.Model): id = orm.Integer(primary_key=True) album = orm.ForeignKey(Album) - title = orm.String(max_length=100) + title = orm.String(length=100) position = orm.Integer() @@ -138,6 +142,12 @@ assert track.album.name == "Malibu" track = await Track.objects.select_related("album").get(title="The Bird") 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. tracks = Track.objects.filter(album__name="Fantasies") assert len(tracks) == 2 diff --git a/orm/fields.py b/orm/fields.py index eece9ea..f101346 100644 --- a/orm/fields.py +++ b/orm/fields.py @@ -249,8 +249,10 @@ class ForeignKey(BaseField): else: if not isinstance(value, self.to.pk_type()): raise RelationshipInstanceError( - f"Relationship error - ForeignKey {self.to.__name__} is of type {self.to.pk_type()} " - f"of type {self.__type__} while {type(value)} passed as a parameter." + f"Relationship error - ForeignKey {self.to.__name__} " + 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) diff --git a/orm/models.py b/orm/models.py index 6dc7fa3..d41b09c 100644 --- a/orm/models.py +++ b/orm/models.py @@ -210,7 +210,7 @@ class FakePydantic(list, metaclass=ModelMetaclass): return self.__table__.primary_key.columns.values()[0] @classmethod - def pk_type(cls): + def pk_type(cls) -> Any: return cls.__model_fields__[cls.__pkname__].__type__ def dict(self) -> Dict: # noqa: A003 @@ -259,6 +259,35 @@ class FakePydantic(list, metaclass=ModelMetaclass): ) 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): __abstract__ = True diff --git a/orm/queryset.py b/orm/queryset.py index 23e4a76..727f9f7 100644 --- a/orm/queryset.py +++ b/orm/queryset.py @@ -479,39 +479,10 @@ class QuerySet: 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 - @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": new_kwargs = dict(**kwargs)