-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmixins.py
More file actions
140 lines (104 loc) · 4.1 KB
/
mixins.py
File metadata and controls
140 lines (104 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import typing as t
import sqlalchemy as sa
import sqlalchemy.orm as sa_orm
from ellar_sql.constant import ABSTRACT_KEY, DATABASE_KEY, DEFAULT_KEY, TABLE_KEY
from ellar_sql.model.utils import (
camel_to_snake_case,
make_metadata,
should_set_table_name,
)
from ellar_sql.schemas import ModelBaseConfig, ModelMetaStore
if t.TYPE_CHECKING:
from .base import ModelBase
__ellar_sqlalchemy_models__: t.Dict[str, t.Type["ModelBase"]] = {}
def get_registered_models() -> t.Dict[str, t.Type["ModelBase"]]:
return __ellar_sqlalchemy_models__.copy()
class NameMixin:
metadata: sa.MetaData
__tablename__: str
__table__: sa.Table
def __init_subclass__(cls, **kwargs: t.Dict[str, t.Any]) -> None:
if should_set_table_name(cls):
cls.__tablename__ = camel_to_snake_case(cls.__name__)
super().__init_subclass__(**kwargs)
class DatabaseBindKeyMixin:
metadata: sa.MetaData
def __init_subclass__(cls, **kwargs: t.Dict[str, t.Any]) -> None:
if not ("metadata" in cls.__dict__ or TABLE_KEY in cls.__dict__) and hasattr(
cls, DATABASE_KEY
):
database_bind_key = getattr(cls, DATABASE_KEY, DEFAULT_KEY)
parent_metadata = getattr(cls, "metadata", None)
db_metadata = make_metadata(database_bind_key)
if db_metadata.metadata is not parent_metadata:
cls.metadata = db_metadata.metadata
cls.registry = db_metadata.registry # type:ignore[attr-defined]
super().__init_subclass__(**kwargs)
class ModelTrackMixin:
metadata: sa.MetaData
__mms__: ModelMetaStore
__table__: sa.Table
def __init_subclass__(cls, **kwargs: t.Dict[str, t.Any]) -> None:
options: ModelBaseConfig = kwargs.pop( # type:ignore[assignment]
"options",
ModelBaseConfig(as_base=False, use_bases=[sa_orm.DeclarativeBase]),
)
super().__init_subclass__(**kwargs)
if TABLE_KEY in cls.__dict__ and ABSTRACT_KEY not in cls.__dict__:
__ellar_sqlalchemy_models__[str(cls)] = cls # type:ignore[assignment]
cls.__mms__ = ModelMetaStore(
base_config=options,
pk_column=None,
columns=list(cls.__table__.columns), # type:ignore[arg-type]
)
class ModelDataExportMixin:
__mms__: t.Optional[ModelMetaStore] = None
def __repr__(self) -> str:
state = sa.inspect(self)
assert state is not None
if state.transient:
pk = f"(transient {id(self)})"
elif state.pending:
pk = f"(pending {id(self)})"
else:
pk = ", ".join(map(str, state.identity))
return f"<{type(self).__name__} {pk}>"
def _calculate_keys(
self,
data: t.Dict[str, t.Any],
include: t.Optional[t.Set[str]],
exclude: t.Optional[t.Set[str]],
) -> t.Set[str]:
keys: t.Set[str] = set(data.keys())
if include is None and exclude is None:
return keys
if include is not None:
keys &= include
if exclude:
keys -= exclude
return keys
def _iter(
self,
include: t.Optional[t.Set[str]],
exclude: t.Optional[t.Set[str]],
exclude_none: bool = False,
) -> t.Generator[t.Tuple[str, t.Any], None, None]:
data = dict(self.__dict__)
if len(data.keys()) != len(self.__mms__.columns):
data = {c.key: getattr(self, c.key, None) for c in self.__mms__.columns}
allowed_keys = self._calculate_keys(include=include, exclude=exclude, data=data)
for field_key, v in data.items():
if (allowed_keys is not None and field_key not in allowed_keys) or (
exclude_none and v is None
):
continue
yield field_key, v
def dict(
self,
include: t.Optional[t.Set[str]] = None,
exclude: t.Optional[t.Set[str]] = None,
exclude_none: bool = False,
) -> t.Dict[str, t.Any]:
return dict(
self._iter(include=include, exclude_none=exclude_none, exclude=exclude)
)