Skip to content

Commit

Permalink
experimental-decorators and some docs
Browse files Browse the repository at this point in the history
  • Loading branch information
CrispenGari committed Feb 19, 2024
1 parent 6da9c1d commit b366907
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 275 deletions.
77 changes: 56 additions & 21 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,61 @@ We have release the new `dataloom` Version `1.1.0` (`2024-02-12`)
- You can apply limits, offsets, filters and orders to your child associations during queries

```py
post = mysql_loom.find_one(
instance=Post,
filters=[Filter(column="userId", value=userId)],
select=["title", "id"],
include=[
Include(
model=User,
select=["id", "username"],
has="one",
include=[Include(model=Profile, select=["avatar", "id"], has="one")],
),
Include(
model=Category,
select=["id", "type"],
has="many",
order=[Order(column="id", order="DESC")],
limit=2,
),
],
post = mysql_loom.find_one(
instance=Post,
filters=[Filter(column="userId", value=userId)],
select=["title", "id"],
include=[
Include(
model=User,
select=["id", "username"],
has="one",
include=[Include(model=Profile, select=["avatar", "id"], has="one")],
),
Include(
model=Category,
select=["id", "type"],
has="many",
order=[Order(column="id", order="DESC")],
limit=2,
),
],
)
```

- Now `return_dict` has bee removed as an option in dataloom in the query functions like `find_by_pk`, `find_one`, `find_many` and `find_all` now works starting from this version. If you enjoy working with python objects you have to maneuver them manually using experimental features.

```py
from dataloom import experimental_decorators

@experimental_decorators.initialize(
repr=True, to_dict=True, init=True, repr_identifier="id"
)
class Profile(Model):
__tablename__: Optional[TableColumn] = TableColumn(name="profiles")
id = PrimaryKeyColumn(type="int", auto_increment=True)
avatar = Column(type="text", nullable=False)
userId = ForeignKeyColumn(
User,
maps_to="1-1",
type="int",
required=True,
onDelete="CASCADE",
onUpdate="CASCADE",
)

# now you can do this

profile = mysql_loom.find_many(
instance=Profile,
)
print([Profile(**p) for p in profile]) # ? = [<Profile:id=1>]
print([Profile(**p) for p in profile][0].id) # ? = 1
```

- `N-N` relational mapping
- Now you can return python objects when querying data meaning that the option `return_dict` in the query functions like `find_by_pk`, `find_one`, `find_many` and `find_all` now works starting from this version
- These experimental decorators as we name them `"experimental"` they are little bit slow and they work perfect in a single instance, you can not nest relationships on them.
- You can use them if you know how your data is structured and also if you know how to manipulate dictionaries

- Updated the documentation.
- Grouping data in queries will also be part of this release, using the class `Group`

Expand Down Expand Up @@ -99,3 +130,7 @@ We are pleased to release `dataloom` ORM for python version `3.12` and above. Th
- Filter Records
- Select field in records
- etc.

```
```
333 changes: 165 additions & 168 deletions README.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion dataloom/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataloom.loom import Dataloom

from dataloom.decorators import experimental_decorators
from dataloom.types import Order, Include, Filter, ColumnValue
from dataloom.model import Model
from dataloom.columns import (
Expand All @@ -24,4 +24,5 @@
Dataloom,
TableColumn,
Model,
experimental_decorators,
]
134 changes: 134 additions & 0 deletions dataloom/decorators/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from dataloom.exceptions import InvalidPropertyException
from dataloom.columns import (
PrimaryKeyColumn,
Column,
CreatedAtColumn,
UpdatedAtColumn,
ForeignKeyColumn,
)
import typing, dataloom # noqa
import inspect


class experimental_decorators:
@staticmethod
def initialize(
to_dict: bool = False,
init: bool = True,
repr: bool = False,
repr_identifier: str | None = None,
):
"""
initialize
----------
Constructor method for the initialize decorator.
Parameters
----------
to_dict : bool, optional
If True, generates a to_dict method for the decorated class. Default is False.
init : bool, optional
If True, generates an __init__ method for the decorated class. Default is True.
repr : bool, optional
If True, generates a __repr__ method for the decorated class. Default is False.
repr_identifier : str | None, optional
The identifier to be used in the __repr__ method. Default is None.
Returns
-------
Callable[[Any], type[wrapper]]
A callable that takes a class and returns a wrapped version of it.
Examples
--------
>>> from dataloom import (
... Dataloom,
... Model,
... PrimaryKeyColumn,
... Column,
... CreatedAtColumn,
... UpdatedAtColumn,
... TableColumn,
... ForeignKeyColumn,
... Filter,
... ColumnValue,
... Include,
... Order,
... experimental_decorators,
... )
...
... @initialize(repr=True, to_dict=True, init=True, repr_identifier="id")
... class Profile(Model):
... __tablename__: Optional[TableColumn] = TableColumn(name="profiles")
... id = PrimaryKeyColumn(type="int", auto_increment=True)
... avatar = Column(type="text", nullable=False)
... userId = ForeignKeyColumn(
... User,
... maps_to="1-1",
... type="int",
... required=True,
... onDelete="CASCADE",
... onUpdate="CASCADE",
... )
"""

def fn(cls):
args = []
for name, field in inspect.getmembers(cls):
if isinstance(field, PrimaryKeyColumn):
args.append(name)
elif isinstance(field, Column):
args.append(name)
elif isinstance(field, CreatedAtColumn):
args.append(name)
elif isinstance(field, UpdatedAtColumn):
args.append(name)
elif isinstance(field, ForeignKeyColumn):
args.append(name)

class wrapper(cls):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

init_code = ""

if init:
init_args = ", ".join([f"{p} = None" for p in args])
init_code += f"def __init__(self, {init_args}) -> None:\n"
for attr_name in args:
init_code += f" self.{attr_name} = {attr_name}\n"

init_code += "\n"
if to_dict:
init_code += "@property\n"
init_code += "def to_dict(self) -> dict:\n"
init_code += " return {\n"
for key in args:
init_code += f" '{key}' : self.{key},\n"
init_code += " }\n\n"

if repr_identifier is None:
identifier = args[0]
else:
identifier = repr_identifier
if repr_identifier not in args:
raise InvalidPropertyException(
f"'{cls.__name__}' has no property '{repr_identifier}'."
)

if repr:
init_code += "def __repr__(self) -> str:\n"
init_code += f" return f'<{cls.__name__}:{identifier}={{self.{identifier}}}>'\n\n"

local_ns = {}
# Execute the dynamically generated methods
exec(init_code, globals(), local_ns)
wrapper.__init__ = local_ns["__init__"]
wrapper.__repr__ = local_ns.get("__repr__")
wrapper.to_dict = local_ns.get("to_dict")
wrapper.__name__ = cls.__name__
return wrapper

return fn
4 changes: 4 additions & 0 deletions dataloom/exceptions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class InvalidArgumentsException(Exception):
pass


class InvalidPropertyException(Exception):
pass


class TooManyPkException(Exception):
pass

Expand Down
2 changes: 1 addition & 1 deletion dataloom/keys.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
push = False
push = True


class PgConfig:
Expand Down
17 changes: 1 addition & 16 deletions dataloom/loom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ def find_many(
filters: Optional[Filter | list[Filter]] = None,
select: list[str] = [],
include: list[Model] = [],
return_dict: bool = True,
limit: Optional[int] = None,
offset: Optional[int] = None,
order: Optional[list[Order]] = [],
Expand All @@ -279,8 +278,7 @@ def find_many(
Columns to select in the query. Default is an empty list, which selects all columns.
include : list[Model], optional
Models to include in the query (e.g., for JOIN operations).
return_dict : bool, optional
If True, returns results as dictionaries. If False, returns results as instances of the Model class. Default is True.
limit : int | None, optional
The maximum number of rows to retrieve. Default is None.
offset : int | None, optional
Expand Down Expand Up @@ -331,7 +329,6 @@ def find_many(
include=include,
offset=offset,
filters=filters,
return_dict=return_dict,
order=order,
)

Expand All @@ -340,7 +337,6 @@ def find_all(
instance: Model,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
limit: Optional[int] = None,
offset: Optional[int] = None,
order: Optional[list[Order]] = [],
Expand All @@ -359,8 +355,6 @@ def find_all(
Columns to select in the query. Default is an empty list, which selects all columns.
include : list[Include], optional
Models to include in the query (e.g., for JOIN operations).
return_dict : bool, optional
If True, returns results as dictionaries. If False, returns results as instances of the Model class. Default is True.
limit : int | None, optional
The maximum number of rows to retrieve. Default is None.
offset : int | None, optional
Expand Down Expand Up @@ -408,7 +402,6 @@ def find_all(
instance=instance,
select=select,
include=include,
return_dict=return_dict,
limit=limit,
offset=offset,
order=order,
Expand All @@ -420,7 +413,6 @@ def find_by_pk(
pk,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
):
"""
find_by_pk
Expand All @@ -438,8 +430,6 @@ def find_by_pk(
Columns to select in the query. Default is an empty list, which selects all columns.
include : list[Include], optional
Models to include in the query (e.g., for JOIN operations).
return_dict : bool, optional
If True, returns the result as a dictionary. If False, returns the result as an instance of the Model class. Default is True.
Returns
-------
Expand Down Expand Up @@ -480,7 +470,6 @@ def find_by_pk(
return query(dialect=self.dialect, _execute_sql=self._execute_sql).find_by_pk(
include=include,
pk=pk,
return_dict=return_dict,
select=select,
instance=instance,
)
Expand All @@ -491,7 +480,6 @@ def find_one(
filters: Optional[Filter | list[Filter]] = None,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
offset: Optional[int] = None,
):
"""
Expand All @@ -510,8 +498,6 @@ def find_one(
Columns to select in the query. Default is an empty list, which selects all columns.
include : list[Include], optional
Models to include in the query (e.g., for JOIN operations).
return_dict : bool, optional
If True, returns the result as a dictionary. If False, returns the result as an instance of the Model class. Default is True.
offset : int | None, optional
The offset of the row to retrieve, useful for pagination. Default is None.
Expand Down Expand Up @@ -557,7 +543,6 @@ def find_one(
filters=filters,
offset=offset,
include=include,
return_dict=return_dict,
)

def update_by_pk(
Expand Down
4 changes: 0 additions & 4 deletions dataloom/loom/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def find_many(
filters: Optional[Filter | list[Filter]] = None,
select: list[str] = [],
include: list[Model] = [],
return_dict: bool = True,
limit: Optional[int] = None,
offset: Optional[int] = None,
order: Optional[list[Order]] = [],
Expand All @@ -85,7 +84,6 @@ def find_all(
instance: Model,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
limit: Optional[int] = None,
offset: Optional[int] = None,
order: Optional[list[Order]] = [],
Expand All @@ -99,7 +97,6 @@ def find_by_pk(
pk,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
) -> dict | None:
raise NotImplementedError("The find_by_pk method was not implemented.")

Expand All @@ -110,7 +107,6 @@ def find_one(
filters: Optional[Filter | list[Filter]] = None,
select: list[str] = [],
include: list[Include] = [],
return_dict: bool = True,
offset: Optional[int] = None,
) -> dict | None:
raise NotImplementedError("The find_one method was not implemented.")
Expand Down
Loading

0 comments on commit b366907

Please sign in to comment.