Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ def is_dirty(self) -> bool:
def get_dirty(self) -> dict:
return {key: value for key, value in self.get_attributes().items() if not self.original_is_equivalent(key)}

def delete_attribute(self, key: str):
"""Remove a transient attribute that was added during query processing."""
self._attributes.pop(key, None)
self._dirty_attributes.pop(key, None)

def get_attributes_for_insert(self) -> dict:
# _dirty_attributes already went through set_attribute (casts applied on assignment).
# _attributes is set raw via new_model_instance, so apply set casts here.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,42 @@


class QueryBuilder(EagerLoadMixin, SupportMixin):
operators = [
"=",
"<",
">",
"<=",
">=",
"<>",
"!=",
"<=>",
"like",
"like binary",
"not like",
"ilike",
"&",
"|",
"^",
"<<",
">>",
"&~",
"is",
"is not",
"rlike",
"not rlike",
"regexp",
"not regexp",
"~",
"~*",
"!~",
"!~*",
"similar to",
"not similar to",
"not ilike",
"~~*",
"!~~*",
]

def __init__(self, connection: "Connection", grammar, processor):
super().__init__()
self.connection = connection
Expand Down Expand Up @@ -63,6 +99,10 @@ def with_(self, *eagers) -> "QueryBuilder":
def get_table_name(self) -> str:
return self._table

def table(self, table: str) -> "QueryBuilder":
self._table = table
return self

def where_in(self, column: str, values) -> "QueryBuilder":
if hasattr(values, "_items"):
values = values._items
Expand Down Expand Up @@ -134,6 +174,10 @@ def run_scopes(self) -> "QueryBuilder":
scope(self)
return self

def without_global_scopes(self) -> "QueryBuilder":
self._global_scopes = {}
return self

def get_grammar(self):
return self.grammar(
columns=self._columns,
Expand Down Expand Up @@ -424,6 +468,10 @@ async def chunk_by_id_desc(self, count: int, column: str = None, alias: str = No
def new(self):
return self.connection.query()

def invalid_operator(self, operator):
"""Determine whether an operator is not supported by the builder."""
return not isinstance(operator, str) or operator.lower() not in self.operators

def where(self, column, *args):
"""Specifies a where expression.

Expand Down
17 changes: 13 additions & 4 deletions fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from __future__ import annotations
import inflection

from typing import TYPE_CHECKING

import inflection

from fastapi_startkit.carbon import Carbon
from fastapi_startkit.masoniteorm.collection import Collection
from fastapi_startkit.masoniteorm.models.fields import CreatedAtField, UpdatedAtField
from fastapi_startkit.masoniteorm.models.registry import Registry
from fastapi_startkit.masoniteorm.observers import ObservesEvents
from fastapi_startkit.masoniteorm.connections.manager import DatabaseManager
from fastapi_startkit.masoniteorm.models.attribute import Attribute
from fastapi_startkit.masoniteorm.models.fields import CreatedAtField, UpdatedAtField
from fastapi_startkit.masoniteorm.models.registry import Registry
from fastapi_startkit.masoniteorm.models.relationship import Relationship
from fastapi_startkit.masoniteorm.observers import ObservesEvents

if TYPE_CHECKING:
from fastapi_startkit.masoniteorm.models.builder import QueryBuilder
Expand Down Expand Up @@ -251,6 +252,14 @@ async def all(cls):
async def count(cls, column: str = "*"):
return await cls.query().count(column)

def table(self, table: str):
self.__table__ = table
return self

def timestamps(self, timestamps: bool = True):
self.__timestamps__ = timestamps
return self

@classmethod
def chunk(cls, count: int):
return cls.query().chunk(count)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import pendulum
from inflection import singularize

from fastapi_startkit.masoniteorm.models import registry
from .BaseRelationship import BaseRelationship
from ..collection import Collection
from ..models.pivot import Pivot
from .BaseRelationship import BaseRelationship
from fastapi_startkit.masoniteorm.models import registry


class BelongsToMany(BaseRelationship):
"""Has Many Relationship Class."""

def __init__(
self,
fn=None,
fn: str,
local_foreign_key=None,
other_foreign_key=None,
local_owner_key=None,
Expand All @@ -23,8 +23,7 @@ def __init__(
attribute="pivot",
with_fields=[],
):
if isinstance(fn, str):
self.fn = self.fn = lambda x: registry.Registry.resolve(fn)
self.fn = lambda: registry.Registry.resolve(fn)

self.local_key = local_foreign_key
self.foreign_key = other_foreign_key
Expand Down Expand Up @@ -134,15 +133,15 @@ async def apply_query(self, query, owner):
pivot_data.update({field: getattr(model, field)})
model.delete_attribute(field)

model.__original_attributes__.update(
{
self._as: (
Pivot.on(query.connection)
.table(self._table)
.hydrate(pivot_data)
.activate_timestamps(self.with_timestamps)
)
}
model.set_attribute(
self._as,
(
Pivot()
.on(query.connection)
.table(self._table)
.set_raw_attributes(pivot_data, True)
.timestamps(self.with_timestamps)
),
)

return result
Expand All @@ -151,11 +150,6 @@ def table(self, table):
self._table = table
return self

def make_builder(self, eagers=None):
builder = self.get_builder().with_(eagers)

return builder

async def make_query(self, query, relation, eagers=None, callback=None):
"""Used during eager loading a relationship

Expand Down Expand Up @@ -239,7 +233,6 @@ async def make_query(self, query, relation, eagers=None, callback=None):

async def get_related(self, query, relation, eagers=None, callback=None):
final_result = await self.make_query(query, relation, eagers=eagers, callback=callback)
builder = self.make_builder(eagers)

for model in final_result:
pivot_data = {
Expand All @@ -266,15 +259,15 @@ async def get_related(self, query, relation, eagers=None, callback=None):
pivot_data.update({field: getattr(model, field)})
model.delete_attribute(field)

model.__original_attributes__.update(
{
self._as: (
Pivot.on(builder.connection_name)
.table(self._table)
.hydrate(pivot_data)
.activate_timestamps(self.with_timestamps)
)
}
model.set_attribute(
self._as,
(
Pivot()
.on(query.connection)
.table(self._table)
.set_raw_attributes(pivot_data, True)
.timestamps(self.with_timestamps)
),
)

return final_result
Expand Down Expand Up @@ -497,7 +490,7 @@ def attach(self, current_model, related_record):
}
)

return Pivot.on(current_model.get_builder().connection).table(self._table).without_global_scopes().create(data)
return current_model.get_builder().connection.query().table(self._table).insert(data)

def detach(self, current_model, related_record):
data = {
Expand All @@ -508,9 +501,10 @@ def detach(self, current_model, related_record):
self._table = self._table or self.get_pivot_table_name(current_model, related_record)

return (
Pivot.on(current_model.get_builder().connection)
.table(self._table)
current_model.get_builder()
.connection.query()
.without_global_scopes()
.table(self._table)
.where(data)
.delete()
)
Expand All @@ -531,12 +525,7 @@ def attach_related(self, current_model, related_record):
}
)

return (
Pivot.table(self._table)
.on(current_model.get_builder().connection_name)
.without_global_scopes()
.create(data)
)
return current_model.get_builder().connection.query().table(self._table).insert(data)

def detach_related(self, current_model, related_record):
data = {
Expand All @@ -554,10 +543,4 @@ def detach_related(self, current_model, related_record):
}
)

return (
Pivot.on(current_model.get_builder().connection_name)
.table(self._table)
.without_global_scopes()
.where(data)
.delete()
)
return current_model.get_builder().connection.query().table(self._table).where(data).delete()
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@ def set_keys(self, owner, attribute):
return self

def __get__(self, instance, owner):
attribute = self.fn.__name__
self._related_builder = instance.builder
self.polymorphic_builder = self.fn(self)()
self.set_keys(owner, self.fn)
if instance is None:
return self

self._related_builder = instance.get_builder()
self.polymorphic_builder = registry.Registry.resolve(self.fn).query()
self.set_keys(owner, self.attribute)

if not instance.is_loaded():
return self

if attribute in instance._relationships:
return instance._relationships[attribute]
if self.attribute in instance._relationships:
return instance._relationships[self.attribute]

return self.apply_query(self._related_builder, instance)

Expand All @@ -45,11 +47,11 @@ def apply_query(self, builder, instance):
Returns:
dict -- A dictionary of data which will be hydrated.
"""
polymorphic_key = self.get_record_key_lookup(builder._model)
polymorphic_key = self.get_record_key_lookup(instance)
polymorphic_builder = self.polymorphic_builder
return (
polymorphic_builder.where(self.morph_key, polymorphic_key)
.where(self.morph_id, instance.get_primary_key_value())
.where(self.morph_id, instance.get_attribute(instance.__primary_key__))
.get()
)

Expand All @@ -65,6 +67,7 @@ def get_related(self, query, relation, eagers=None, callback=None):
Returns:
Model|Collection
"""
self.polymorphic_builder = registry.Registry.resolve(self.fn).query()

if isinstance(relation, Collection):
record_type = self.get_record_key_lookup(relation.first())
Expand All @@ -75,7 +78,7 @@ def get_related(self, query, relation, eagers=None, callback=None):
record_type,
).where_in(
self.morph_id,
relation.pluck(relation.first().get_primary_key(), keep_nulls=False).unique(),
relation.pluck(relation.first().__primary_key__, keep_nulls=False).unique(),
)
).get()
return (
Expand All @@ -85,7 +88,7 @@ def get_related(self, query, relation, eagers=None, callback=None):
)
.where_in(
self.morph_id,
relation.pluck(relation.first().get_primary_key(), keep_nulls=False).unique(),
relation.pluck(relation.first().__primary_key__, keep_nulls=False).unique(),
)
.get()
)
Expand All @@ -96,32 +99,31 @@ def get_related(self, query, relation, eagers=None, callback=None):
if callback:
return callback(
self.polymorphic_builder.where(self.morph_key, record_type).where(
self.morph_id, relation.get_primary_key_value()
self.morph_id, relation.get_attribute(relation.__primary_key__)
)
).get()
return (
self.polymorphic_builder.where(self.morph_key, record_type)
.where(self.morph_id, relation.get_primary_key_value())
.where(self.morph_id, relation.get_attribute(relation.__primary_key__))
.get()
)

def register_related(self, key, model, collection):
record_type = self.get_record_key_lookup(model)
related = collection.where(self.morph_key, record_type).where(self.morph_id, model.get_primary_key_value())
related = collection.where(self.morph_key, record_type).where(
self.morph_id, model.get_attribute(model.__primary_key__)
)

model.add_relation({key: related})

def map_related(self, related_result):
return related_result

def morph_map(self):
return registry.Registry.get_morph_map()

def get_record_key_lookup(self, relation):
record_type = None
for record_type_loop, model in self.morph_map().items():
if model == relation.__class__:
record_type = record_type_loop
break

if not record_type:
record_type = registry.Registry.get_morph_name(relation.__class__)
if record_type == relation.__class__.__name__:
raise ValueError(f"Could not find the record type key for the {relation} class")

return record_type
Loading
Loading