Skip to content

Commit

Permalink
feat: WIP to add more ORM features (dynamic querying. some better typ…
Browse files Browse the repository at this point in the history
…ing etc)
  • Loading branch information
robinvandernoord committed Aug 19, 2023
1 parent 5d85995 commit 64c9b67
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 27 deletions.
4 changes: 2 additions & 2 deletions coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 48 additions & 24 deletions src/typedal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import pydal
from pydal._globals import DEFAULT
from pydal.objects import Field, Query, Row, Rows, Table
from typing_extensions import Self

# use typing.cast(type, ...) to make mypy happy with unions
T_annotation = typing.Type[Any] | types.UnionType
Expand Down Expand Up @@ -87,28 +88,7 @@ class TypeDAL(pydal.DAL): # type: ignore
"notnull": True,
}

def define(self, cls: T) -> T:
"""
Can be used as a decorator on a class that inherits `TypedTable`, \
or as a regular method if you need to define your classes before you have access to a 'db' instance.
Args:
cls:
Example:
@db.define
class Person(TypedTable):
...
class Article(TypedTable):
...
# at a later time:
db.define(Article)
Returns:
the result of pydal.define_table
"""
def _define(self, cls: T) -> T:
# when __future__.annotations is implemented, cls.__annotations__ will not work anymore as below.
# proper way to handle this would be (but gives error right now due to Table implementing magic methods):
# typing.get_type_hints(cls, globalns=None, localns=None)
Expand Down Expand Up @@ -146,6 +126,50 @@ class Article(TypedTable):
# but telling the editor it is T helps with hinting.
return cls

@typing.overload
def define(self, maybe_cls: None = None) -> typing.Callable[[T], T]:
"""
Typing Overload for define without a class.
@db.define()
class MyTable(TypedTable): ...
"""

@typing.overload
def define(self, maybe_cls: T) -> T:
"""
Typing Overload for define with a class.
@db.define
class MyTable(TypedTable): ...
"""

def define(self, maybe_cls: T = None) -> T | typing.Callable[[T], T]:
"""
Can be used as a decorator on a class that inherits `TypedTable`, \
or as a regular method if you need to define your classes before you have access to a 'db' instance.
Example:
@db.define
class Person(TypedTable):
...
class Article(TypedTable):
...
# at a later time:
db.define(Article)
Returns:
the result of pydal.define_table
"""

def wrapper(cls: T) -> T:
return self._define(cls)

return wrapper(maybe_cls) if maybe_cls else wrapper

def __call__(self, *_args: T_Query, **kwargs: typing.Any) -> "TypedSet":
"""
A db instance can be called directly to perform a query.
Expand Down Expand Up @@ -316,7 +340,7 @@ def __new__(cls, *a: typing.Any, **kw: typing.Any) -> Row: # or none!
return cls.__table(*a, **kw)

@classmethod
def insert(cls, **fields: typing.Any) -> int:
def insert(cls, **fields: typing.Any) -> Self:
"""
This is only called when db.define is not used as a decorator.
Expand All @@ -333,7 +357,7 @@ def insert(cls, **fields: typing.Any) -> int:

result = super().insert(cls.__table, **fields)
# it already is an int but mypy doesn't understand that
return typing.cast(int, result)
return cls(result)

@classmethod
def update_or_insert(cls, query: T_Query = DEFAULT, **values: typing.Any) -> Optional[int]:
Expand Down
1 change: 0 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import re
import typing
from sqlite3 import IntegrityError

import pydal
Expand Down
63 changes: 63 additions & 0 deletions tests/test_orm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import uuid

from src.typedal import TypeDAL, TypedTable
from src.typedal.fields import StringField

db = TypeDAL('sqlite:memory:')


@db.define
class User(TypedTable):
name: str
gid = StringField(default=uuid.uuid4)


class Post(TypedTable):
title: str
gid = StringField(default=uuid.uuid4)


db.define(Post)


@db.define()
class Tag(TypedTable):
slug: str
gid = StringField(default=uuid.uuid4)


@db.define()
class Tagged(TypedTable):
entity: str # uuid
tag: Tag


def test_orm_classes():
henkie = User.insert(
name="Henkie"
)

ijsjes = Post.insert(
title="IJsjes"
)

post_by_henkie = Tag.insert(
slug='post-by-henkie'
)

melk_producten = Tag.insert(
slug='melk-producten'
)

Tagged.insert(
entity=henkie.gid,
tag=post_by_henkie
)

Tagged.insert(
entity=ijsjes.gid,
tag=melk_producten
)

print(Tagged.select(Tagged.ALL).where(Tagged.id).first())
print(list(Tagged.where(Tagged.id).select(Tagged.ALL)))

0 comments on commit 64c9b67

Please sign in to comment.