Skip to content

Commit

Permalink
Add fix to the main metaclasses
Browse files Browse the repository at this point in the history
  • Loading branch information
tarsil committed Aug 1, 2023
1 parent 8b215aa commit 70bb5e7
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 151 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test: ## Runs the tests

.PHONY: requirements
requirements: ## Install requirements for development
pip install -e .[dev,test,doc]
pip install -e .[dev,test,doc,postgres,mysql,sqlite,testing,ipython,ptpython]

ifndef VERBOSE
.SILENT:
Expand Down
3 changes: 1 addition & 2 deletions edgy/core/connection/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@

from edgy.conf import settings
from edgy.core.connection.database import Database
from edgy.core.datastructures import ArbitraryHashableBaseModel
from edgy.exceptions import ImproperlyConfigured


class Registry(ArbitraryHashableBaseModel):
class Registry:
"""
The command center for the models being generated
for Edgy.
Expand Down
3 changes: 2 additions & 1 deletion edgy/core/db/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def is_required(self) -> bool:
`True` if the argument is required, `False` otherwise.
"""
required = False if self.null else True
# self.default is PydanticUndefined and self.default_factory is None
return required

def get_alias(self) -> str:
Expand Down Expand Up @@ -128,7 +129,7 @@ def get_column(self, name: str) -> Any:
column_type,
*constraints,
primary_key=self.primary_key,
nullable=self.null and not self.primary_key,
nullable=self.is_required() and not self.primary_key,
index=self.index,
unique=self.unique,
default=self.default,
Expand Down
4 changes: 2 additions & 2 deletions edgy/core/db/models/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import functools
from typing import Annotated, Any, ClassVar, Dict, Optional, Sequence, Union
from typing import Any, ClassVar, Dict, Optional, Sequence

import sqlalchemy
from pydantic import BaseModel, ConfigDict
Expand All @@ -22,7 +22,7 @@ class EdgyBaseModel(BaseModel, DateParser, metaclass=BaseModelMeta):

model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)

query: Manager = Manager()
query: ClassVar[Manager] = Manager()
meta: ClassVar[MetaInfo] = MetaInfo(None)
__db_model__: ClassVar[bool] = False
__raw_query__: ClassVar[Optional[str]] = None
Expand Down
23 changes: 11 additions & 12 deletions edgy/core/db/models/managers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import Any

from pydantic import BaseModel, ConfigDict

from edgy.core.db.querysets.base import QuerySet


Expand Down Expand Up @@ -39,13 +37,14 @@ def get_queryset(self) -> "QuerySet":
"""
return QuerySet(self.model_class)

def __getattr__(self, item: Any) -> Any:
"""
Gets the attribute from the queryset and if it does not
exist, then lookup in the model.
"""
breakpoint()
try:
return getattr(self.get_queryset(), item)
except AttributeError:
return getattr(self.model_class, item)
# def __getattr__(self, item: Any) -> Any:
# """
# Gets the attribute from the queryset and if it does not
# exist, then lookup in the model.
# """
# if not self.model_class:
# return super().__getattr__(self, item)
# try:
# return getattr(self.get_queryset(), item)
# except AttributeError:
# return getattr(self.model_class, item)
253 changes: 124 additions & 129 deletions edgy/core/db/models/metaclasses.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import copy
import inspect
from abc import ABCMeta
from typing import TYPE_CHECKING, Annotated, Any, Dict, Optional, Sequence, Set, Tuple, Type, Union
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Set, Tuple, Type, Union

import pydantic
import sqlalchemy
from pydantic._internal._model_construction import ModelMetaclass

Expand Down Expand Up @@ -59,7 +57,7 @@ def __init__(self, meta: Any = None, **kwargs: Any) -> None:
self.many_to_many_fields: Set[str] = set()
self.foreign_key_fields: Set[str] = set()
self.model: Optional[Type["Model"]] = None
self.manager: "Manager" = getattr(meta, "manager", None)
self.manager: "Manager" = getattr(meta, "manager", Manager())
self.unique_together: Any = getattr(meta, "unique_together", None)
self.indexes: Any = getattr(meta, "indexes", None)
self.reflect: bool = getattr(meta, "reflect", False)
Expand Down Expand Up @@ -229,144 +227,141 @@ def __search_for_fields(base: Type, attrs: Any) -> None:
f"Cannot create model {name} without explicit primary key if field 'id' is already present."
)

for key, value in attrs.items():
if isinstance(value, BaseField):
if getattr(meta_class, "abstract", None):
value = copy.copy(value)

fields[key] = value

if isinstance(value, edgy_fields.OneToOneField):
one_to_one_fields.add(value)
continue
elif isinstance(value, edgy_fields.ManyToManyField):
many_to_many_fields.add(value)
continue
elif isinstance(value, edgy_fields.ForeignKey) and not isinstance(
value, edgy_fields.ManyToManyField
):
foreign_key_fields.add(value)
continue

for slot in fields:
attrs.pop(slot, None)

attrs["meta"] = meta = MetaInfo(meta_class)

meta.fields_mapping = fields
meta.foreign_key_fields = foreign_key_fields
meta.one_to_one_fields = one_to_one_fields
meta.many_to_many_fields = many_to_many_fields
meta.pk_attribute = pk_attribute
meta.pk = fields.get(pk_attribute)

if not fields:
meta.abstract = True

model_class = super().__new__

# Ensure the initialization is only performed for subclasses of Model
parents = [parent for parent in bases if isinstance(parent, BaseModelMeta)]
if not parents:
return model_class(cls, name, bases, attrs)

meta.parents = parents
new_class = model_class(cls, name, bases, attrs)

# Abstract classes do not allow multiple managers. This make sure it is enforced.
if meta.abstract:
managers = [k for k, v in attrs.items() if isinstance(v, Manager)]
if len(managers) > 1:
raise ImproperlyConfigured(
"Multiple managers are not allowed in abstract classes."
)

if getattr(meta, "unique_together", None) is not None:
raise ImproperlyConfigured("unique_together cannot be in abstract classes.")

if getattr(meta, "indexes", None) is not None:
raise ImproperlyConfigured("indexes cannot be in abstract classes.")
else:
meta.managers = [k for k, v in attrs.items() if isinstance(v, Manager)]

# Handle the registry of models
if getattr(meta, "registry", None) is None:
if hasattr(new_class, "_db_model") and new_class._db_model:
meta.registry = _check_model_inherited_registry(bases)
else:
return new_class

# Making sure the tablename is always set if the value is not provided
if getattr(meta, "tablename", None) is None:
tablename = f"{name.lower()}s"
meta.tablename = tablename
for key, value in attrs.items():
if isinstance(value, BaseField):
if getattr(meta_class, "abstract", None):
value = copy.copy(value)

fields[key] = value

if isinstance(value, edgy_fields.OneToOneField):
one_to_one_fields.add(value)
continue
elif isinstance(value, edgy_fields.ManyToManyField):
many_to_many_fields.add(value)
continue
elif isinstance(value, edgy_fields.ForeignKey) and not isinstance(
value, edgy_fields.ManyToManyField
):
foreign_key_fields.add(value)
continue

for slot in fields:
attrs.pop(slot, None)

attrs["meta"] = meta = MetaInfo(meta_class)

meta.fields_mapping = fields
meta.foreign_key_fields = foreign_key_fields
meta.one_to_one_fields = one_to_one_fields
meta.many_to_many_fields = many_to_many_fields
meta.pk_attribute = pk_attribute
meta.pk = fields.get(pk_attribute)

if not fields:
meta.abstract = True

model_class = super().__new__

# Ensure the initialization is only performed for subclasses of Model
parents = [parent for parent in bases if isinstance(parent, BaseModelMeta)]
if not parents:
return model_class(cls, name, bases, attrs)

meta.parents = parents
new_class = model_class(cls, name, bases, attrs)

# Abstract classes do not allow multiple managers. This make sure it is enforced.
if meta.abstract:
managers = [k for k, v in attrs.items() if isinstance(v, Manager)]
if len(managers) > 1:
raise ImproperlyConfigured(
"Multiple managers are not allowed in abstract classes."
)

if getattr(meta, "unique_together", None) is not None:
unique_together = meta.unique_together
if not isinstance(unique_together, (list, tuple)):
value_type = type(unique_together).__name__
raise ImproperlyConfigured(
f"unique_together must be a tuple or list. Got {value_type} instead."
)
else:
for value in unique_together:
if not isinstance(value, (str, tuple, UniqueConstraint)):
raise ValueError(
"The values inside the unique_together must be a string, a tuple of strings or an instance of UniqueConstraint."
)
raise ImproperlyConfigured("unique_together cannot be in abstract classes.")

# Handle indexes
if getattr(meta, "indexes", None) is not None:
indexes = meta.indexes
if not isinstance(indexes, (list, tuple)):
value_type = type(indexes).__name__
raise ImproperlyConfigured(
f"indexes must be a tuple or list. Got {value_type} instead."
)
else:
for value in indexes:
if not isinstance(value, Index):
raise ValueError("Meta.indexes must be a list of Index types.")

registry = meta.registry
new_class.database = registry.database
raise ImproperlyConfigured("indexes cannot be in abstract classes.")
else:
meta.managers = [k for k, v in attrs.items() if isinstance(v, Manager)]

# Handle the registry of models
if getattr(meta, "registry", None) is None:
if hasattr(new_class, "__db_model__") and new_class.__db_model__:
meta.registry = _check_model_inherited_registry(bases)
else:
return new_class

# Making sure the tablename is always set if the value is not provided
if getattr(meta, "tablename", None) is None:
tablename = f"{name.lower()}s"
meta.tablename = tablename

if getattr(meta, "unique_together", None) is not None:
unique_together = meta.unique_together
if not isinstance(unique_together, (list, tuple)):
value_type = type(unique_together).__name__
raise ImproperlyConfigured(
f"unique_together must be a tuple or list. Got {value_type} instead."
)
else:
for value in unique_together:
if not isinstance(value, (str, tuple, UniqueConstraint)):
raise ValueError(
"The values inside the unique_together must be a string, a tuple of strings or an instance of UniqueConstraint."
)

# Handle indexes
if getattr(meta, "indexes", None) is not None:
indexes = meta.indexes
if not isinstance(indexes, (list, tuple)):
value_type = type(indexes).__name__
raise ImproperlyConfigured(
f"indexes must be a tuple or list. Got {value_type} instead."
)
else:
for value in indexes:
if not isinstance(value, Index):
raise ValueError("Meta.indexes must be a list of Index types.")

# Making sure it does not generate tables if abstract it set
if not meta.abstract:
registry.models[name] = new_class
registry = meta.registry
new_class.database = registry.database

for name, field in meta.fields_mapping.items():
field.registry = registry
if field.primary_key:
new_class.pkname = name
# Making sure it does not generate tables if abstract it set
if not meta.abstract:
registry.models[name] = new_class

new_class.__db_model__ = True
new_class.fields = meta.fields_mapping
for name, field in meta.fields_mapping.items():
field.registry = registry
if field.primary_key:
new_class.pkname = name

meta.model = new_class # type: ignore
meta.manager.model_class = new_class
new_class.__db_model__ = True
new_class.fields = meta.fields_mapping
meta.model = new_class # type: ignore
meta.manager.model_class = new_class

# Set the owner of the field
for _, value in new_class.fields.items():
value.owner = new_class
# Set the owner of the field
for _, value in new_class.fields.items():
value.owner = new_class

# Sets the foreign key fields
if meta.foreign_key_fields:
related_name = _set_related_name_for_foreign_keys(
meta.foreign_key_fields, new_class
)
meta.related_names.add(related_name)
# Sets the foreign key fields
if meta.foreign_key_fields:
related_name = _set_related_name_for_foreign_keys(meta.foreign_key_fields, new_class)
meta.related_names.add(related_name)

for field, value in new_class.fields.items():
if isinstance(value, edgy_fields.ManyToManyField):
_set_many_to_many_relation(value, new_class, field)
for field, value in new_class.fields.items():
if isinstance(value, edgy_fields.ManyToManyField):
_set_many_to_many_relation(value, new_class, field)

# Set the manager
for _, value in attrs.items():
if isinstance(value, Manager):
value.model_class = new_class
# Set the manager
for _, value in attrs.items():
if isinstance(value, Manager):
value.model_class = new_class

return new_class
return new_class

@property
def table(cls) -> Any:
Expand Down
2 changes: 1 addition & 1 deletion scripts/install
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ else
fi

"$PIP" install -U pip
"$PIP" install -e .[dev,test,doc]
"$PIP" install -e .[dev,test,doc,postgres,mysql,sqlite,testing,ipython,ptpython]
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pytest


@pytest.fixture(scope="module")
def anyio_backend():
return ("asyncio", {"debug": True})
Loading

0 comments on commit 70bb5e7

Please sign in to comment.